Messaging
A messaging line is a dedicated number for messages, separate from your calling numbers. It sends and receives iMessage, SMS, MMS and WhatsApp.
Messaging lines
- One number per line, used only for messaging and calls from that line.
- Unlimited iMessage, SMS, MMS and WhatsApp messages.
- Calls from the line are included.
- Number reputation and daily spam label monitoring on the line are included.
Calls to the line from a lead ring the agent who last spoke with them, the same way as callbacks.
| Messaging lineA dedicated number for messaging, separate from your calling numbers. Unlimited iMessage, SMS, MMS and WhatsApp. Calls from the line and number reputation on it are included. | $500/ line / month |
Registration in your business name
- SMS and MMS: we register the brand and the messaging campaign for each line in your business name during setup. For a subaccount, that is the client's business name. The
smscapability turns on when the registration is approved, andaccount.capability_updatedtells you. - WhatsApp: the line uses the client's own WhatsApp Business account, which they connect during setup. Messages show their business name, and templates are approved under their account.
- iMessage: messages come from the line's number.
Send a message
POST/v1/messages
| Parameters | Type | Description |
|---|---|---|
torequired | string | The recipient, E.164. |
fromrequired | string | Your messaging line. |
channel | auto | imessage | sms | mms | whatsapp | auto sends iMessage when the recipient has it, and SMS or MMS otherwise. Default auto. |
body | string | Up to 1,600 characters. |
media_urls | string[] | Images or files to attach. Sent as MMS on the SMS path. |
lead_id | string | Links the message to the lead and their last call. |
template | object | WhatsApp only. An approved template with name, language and variables. Needed when the recipient has not written to you in the last 24 hours. |
metadata | object | Up to 20 keys. Returned on every event for the message. |
curl https://api.liveleg.dev/v1/messages \
-H "Authorization: Bearer $LIVELEG_API_KEY" \
-H "Idempotency-Key: 3f9c1e7a-5b2d-4c8e-9a01-6d2f8b7e4c10" \
-d to=+15125550147 \
-d from=+15125550188 \
-d channel=auto \
-d body="Thanks for the time today, Dana. Here is the inspection checklist we talked about." \
-d lead_id=led_44Hc2const res = await fetch('https://api.liveleg.dev/v1/messages', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.LIVELEG_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': '3f9c1e7a-5b2d-4c8e-9a01-6d2f8b7e4c10',
},
body: JSON.stringify({
to: '+15125550147',
from: '+15125550188',
channel: 'auto',
body: 'Thanks for the time today, Dana. Here is the inspection checklist we talked about.',
lead_id: 'led_44Hc2',
}),
});
const message = await res.json();import os, requests
message = requests.post(
"https://api.liveleg.dev/v1/messages",
headers={
"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}",
"Idempotency-Key": "3f9c1e7a-5b2d-4c8e-9a01-6d2f8b7e4c10",
},
json={
"to": "+15125550147",
"from": "+15125550188",
"channel": "auto",
"body": "Thanks for the time today, Dana. Here is the inspection checklist we talked about.",
"lead_id": "led_44Hc2",
},
).json() {
"id": "msg_Rq40",
"object": "message",
"direction": "outbound",
"channel": "imessage",
"from": "+15125550188",
"to": "+15125550147",
"body": "Thanks for the time today, Dana. Here is the inspection checklist we talked about.",
"media_urls": [],
"lead_id": "led_44Hc2",
"status": "sent",
"send_at": null,
"created_at": "2026-11-03T15:51:02.880Z"
} Before a message is sent we check your do-not-call list, opt-outs and quiet hours. Blocked messages return 422 with the rule.
Message statuses
| Status | Meaning |
|---|---|
queued | Held for the recipient's quiet hours. send_at says when it goes out. Sends message.queued. |
sent | Accepted for delivery on the channel. Sends message.sent. |
delivered | The recipient's phone or app confirmed delivery. Sends message.delivered. |
failed | Not delivered. error has the code, such as unreachable, opted_out or template_required. Sends message.failed. |
received | An inbound message from the recipient. Sends message.received. |
List the messages with a lead, newest first, to show a conversation in your app. Filter by lead_id, to, from or channel.
GET/v1/messages
curl "https://api.liveleg.dev/v1/messages?lead_id=led_44Hc2&limit=20" \
-H "Authorization: Bearer $LIVELEG_API_KEY" Channels
autosends iMessage when the recipient has it, and SMS otherwise. With media, the SMS path sends MMS.imessage,smsandmmssend on that channel only.whatsappsends on WhatsApp when the recipient has an account.
The channel used is on every message event.
Within 24 hours of the recipient's last message, send any text or file. Outside that window, WhatsApp accepts only an approved template. Send template with its name, language and variables. A free-form message outside the window fails with template_required.
curl https://api.liveleg.dev/v1/messages \
-H "Authorization: Bearer $LIVELEG_API_KEY" \
-d to=+15125550147 \
-d from=+15125550188 \
-d channel=whatsapp \
-d body="Here are the photos from the roof inspection." \
-d media_urls=https://files.example.com/ridgeline/roof-1.jpg curl https://api.liveleg.dev/v1/messages \
-H "Authorization: Bearer $LIVELEG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "+15125550147",
"from": "+15125550188",
"channel": "whatsapp",
"template": {
"name": "inspection_followup",
"language": "en_US",
"variables": ["Dana", "Thursday"]
}
}' Media and MMS
Attach files with media_urls. Each URL must be public HTTPS. We fetch the file when the message is sent. Inbound files arrive in media_urls on message.received, stored with your account for as long as the message.
| Channel | File types | Size |
|---|---|---|
| MMS | JPEG, PNG, GIF, PDF, vCard, MP4 | Up to 5 MB per message, up to 10 files. Larger images are resized to fit. |
| iMessage | Images, video, PDF and other files | Up to 16 MB per file, up to 10 files. |
| JPEG, PNG, MP4, PDF | Up to 16 MB per file, 1 file per message. |
Quiet hours
Messages go out between 8 am and 9 pm in the recipient's local time, with the same state rules as calling hours. A message sent outside that window is queued, sends message.queued with send_at, and goes out when the window opens. Replies from the recipient are delivered to you at any time.
Opt-out words
STOP, STOPALL, UNSUBSCRIBE, CANCEL, END, QUIT, REVOKE and OPT OUT all opt the number out of messages and add it to your do-not-call list. Words are matched on any channel, in any letter case. The opt-out covers every messaging line in the account or subaccount. The recipient gets one confirmation message, and you get message.opted_out and dnc.added.
START or UNSTOP opts back in to messages and sends message.opted_in. The do-not-call entry for calls stays in place.
Replies
Replies arrive as message.received with the channel, the lead and their last call. Delivery status comes as message.delivered or message.failed.
{
"id": "evt_6Mn40Yr2",
"type": "message.received",
"created_at": "2026-11-03T16:03:51.300Z",
"livemode": true,
"data": {
"message_id": "msg_Rr51",
"channel": "imessage",
"from": "+15125550147",
"to": "+15125550188",
"body": "Got it. Can we talk Thursday?",
"lead_id": "led_44Hc2",
"last_call_id": "cal_8Jq2mT4x"
}
}