Messages
Messages go out from a messaging line, a dedicated number separate from your calling numbers. One request sends iMessage, SMS, MMS or WhatsApp. Before a message is sent we check your do-not-call list, opt-outs and quiet hours. Replies arrive as message.received.
Guide: Messaging.
Endpoints
| POST | /v1/messages | Send a message |
| GET | /v1/messages/{id} | Retrieve a message |
| GET | /v1/messages | List messages |
| GET | /v1/messaging_lines | List messaging lines |
| GET | /v1/messaging_lines/{id} | Retrieve a messaging line |
| GET | /v1/opt_outs | List opt-outs |
The message object
| Field | Type | Description |
|---|---|---|
id | string | Unique ID, starts with msg_. |
direction | string | outbound or inbound. |
channel | string | The channel used: imessage, sms, mms or whatsapp. |
messaging_line_id | string | The messaging line that sent or received the message. |
from | string | Sender, E.164. |
to | string | Recipient, E.164. |
body | string | The text, up to 1,600 characters. |
media_urls | string[] | Attached images or files. |
lead_id | string | null | The lead the message is linked to. |
last_call_id | string | null | The lead's last call. |
status | string | queued, sent, delivered or failed for outbound. received for inbound. |
send_at | string | null | When a queued message goes out, after quiet hours end for the recipient. |
error | object | null | code and message when status is failed. code is unreachable, opted_out or template_required. |
created_at | string | When the message was accepted or received, ISO 8601. |
delivered_at | string | null | When delivery was confirmed. |
Send a message
Sends a message from one of your messaging lines. Outside 8 am to 9 pm in the recipient's local time the message is queued until the window opens. A message to a number on your do-not-call list or opt-out list returns 422 with the rule. On WhatsApp, a free-form message more than 24 hours after the recipient last wrote fails with template_required. Send template instead.
POST/v1/messages
| Body parameters | Type | Description |
|---|---|---|
torequired | string | The recipient, E.164. |
fromrequired | string | Your messaging line number. |
channel | string | auto, imessage, sms, mms or whatsapp. auto sends iMessage when the recipient has it, and SMS or MMS otherwise. Default auto. |
body | string | Up to 1,600 characters. Send body, media_urls or both. |
media_urls | string[] | Public HTTPS links to images or files. Sent as MMS on the SMS path. MMS takes up to 5 MB and 10 files per message. iMessage takes up to 16 MB per file. WhatsApp takes 1 file up to 16 MB. |
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 "Content-Type: application/json" \
-d '{
"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 res = await fetch('https://api.liveleg.dev/v1/messages', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.LIVELEG_API_KEY}`,
'Content-Type': 'application/json',
},
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 data = await res.json();import os, requests
data = requests.post(
"https://api.liveleg.dev/v1/messages",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
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",
"messaging_line_id": "mln_8Rd2",
"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",
"last_call_id": "cal_8Jq2mT4x",
"status": "sent",
"send_at": null,
"error": null,
"created_at": "2026-11-03T15:50:12Z",
"delivered_at": null
} Retrieve a message
Returns one message with its current status.
GET/v1/messages/{id}
curl https://api.liveleg.dev/v1/messages/msg_Rq40 \
-H "Authorization: Bearer $LIVELEG_API_KEY"const res = await fetch('https://api.liveleg.dev/v1/messages/msg_Rq40', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.LIVELEG_API_KEY}`,
},
});
const data = await res.json();import os, requests
data = requests.get(
"https://api.liveleg.dev/v1/messages/msg_Rq40",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
).json() {
"id": "msg_Rq40",
"object": "message",
"direction": "outbound",
"channel": "imessage",
"messaging_line_id": "mln_8Rd2",
"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",
"last_call_id": "cal_8Jq2mT4x",
"status": "delivered",
"send_at": null,
"error": null,
"created_at": "2026-11-03T15:50:12Z",
"delivered_at": "2026-11-03T15:50:13Z"
} List messages
Returns sent and received messages, newest first.
GET/v1/messages
| Query parameters | Type | Description |
|---|---|---|
lead_id | string | Only messages with this lead. |
messaging_line_id | string | Only messages on this line. |
phone_number | string | Only messages to or from this number. |
direction | string | outbound or inbound. |
channel | string | imessage, sms, mms or whatsapp. |
status | string | queued, sent, delivered, failed or received. |
limit | integer | 1 to 100. Default 20. |
starting_after | string | The last ID from the previous page. |
curl "https://api.liveleg.dev/v1/messages?lead_id=led_44Hc2&limit=20" \
-H "Authorization: Bearer $LIVELEG_API_KEY"const res = await fetch('https://api.liveleg.dev/v1/messages?lead_id=led_44Hc2&limit=20', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.LIVELEG_API_KEY}`,
},
});
const data = await res.json();import os, requests
data = requests.get(
"https://api.liveleg.dev/v1/messages",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
params={
"lead_id": "led_44Hc2",
"limit": 20,
},
).json() {
"object": "list",
"data": [
{
"id": "msg_Rr51",
"object": "message",
"direction": "inbound",
"channel": "imessage",
"messaging_line_id": "mln_8Rd2",
"from": "+15125550147",
"to": "+15125550188",
"body": "Got it. Can we talk Thursday?",
"media_urls": [],
"lead_id": "led_44Hc2",
"last_call_id": "cal_8Jq2mT4x",
"status": "received",
"send_at": null,
"error": null,
"created_at": "2026-11-03T16:03:51Z",
"delivered_at": null
},
{
"id": "msg_Rq40",
"object": "message",
"direction": "outbound",
"channel": "imessage",
"messaging_line_id": "mln_8Rd2",
"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",
"last_call_id": "cal_8Jq2mT4x",
"status": "delivered",
"send_at": null,
"error": null,
"created_at": "2026-11-03T15:50:12Z",
"delivered_at": "2026-11-03T15:50:13Z"
}
],
"has_more": false
} List messaging lines
Returns the messaging lines on the account. We set up each line and register it for SMS in your business name. For a subaccount, that is the client's business name.
GET/v1/messaging_lines
| Query parameters | Type | Description |
|---|---|---|
limit | integer | 1 to 100. Default 20. |
starting_after | string | The last ID from the previous page. |
curl https://api.liveleg.dev/v1/messaging_lines \
-H "Authorization: Bearer $LIVELEG_API_KEY"const res = await fetch('https://api.liveleg.dev/v1/messaging_lines', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.LIVELEG_API_KEY}`,
},
});
const data = await res.json();import os, requests
data = requests.get(
"https://api.liveleg.dev/v1/messaging_lines",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
).json() {
"object": "list",
"data": [
{
"id": "mln_8Rd2",
"object": "messaging_line",
"phone_number": "+15125550188",
"name": "Ridgeline sales",
"channels": {
"imessage": "active",
"sms": "active",
"mms": "active",
"whatsapp": "active"
},
"sms_registration": {
"status": "approved",
"business_name": "Ridgeline Roofing LLC"
},
"whatsapp": {
"status": "connected",
"business_name": "Ridgeline Roofing"
},
"label_status": "clean",
"created_at": "2026-10-28T17:03:19Z"
}
],
"has_more": false
} Retrieve a messaging line
Returns one line with the status of each channel. sms_registration shows the business name the line is registered under. WhatsApp uses the client's own WhatsApp Business account, connected during setup, and whatsapp shows its status. label_status comes from number reputation, which is included on every line.
GET/v1/messaging_lines/{id}
curl https://api.liveleg.dev/v1/messaging_lines/mln_8Rd2 \
-H "Authorization: Bearer $LIVELEG_API_KEY"const res = await fetch('https://api.liveleg.dev/v1/messaging_lines/mln_8Rd2', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.LIVELEG_API_KEY}`,
},
});
const data = await res.json();import os, requests
data = requests.get(
"https://api.liveleg.dev/v1/messaging_lines/mln_8Rd2",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
).json() {
"id": "mln_8Rd2",
"object": "messaging_line",
"phone_number": "+15125550188",
"name": "Ridgeline sales",
"channels": {
"imessage": "active",
"sms": "active",
"mms": "active",
"whatsapp": "active"
},
"sms_registration": {
"status": "approved",
"business_name": "Ridgeline Roofing LLC"
},
"whatsapp": {
"status": "connected",
"business_name": "Ridgeline Roofing"
},
"label_status": "clean",
"created_at": "2026-10-28T17:03:19Z"
} List opt-outs
Returns numbers that opted out of messages with STOP, STOPALL, UNSUBSCRIBE, CANCEL, END, QUIT, REVOKE or OPT OUT. Each opt-out also adds the number to your do-not-call list. A number that texts START or UNSTOP opts back in to messages and leaves this list. The do-not-call entry for calls stays in place.
GET/v1/opt_outs
| Query parameters | Type | Description |
|---|---|---|
phone_number | string | Check one number, E.164. |
messaging_line_id | string | Only opt-outs received on this line. |
created_after | string | ISO 8601. |
limit | integer | 1 to 100. Default 20. |
starting_after | string | The last ID from the previous page. |
curl "https://api.liveleg.dev/v1/opt_outs?created_after=2026-11-01T00%3A00%3A00Z&limit=20" \
-H "Authorization: Bearer $LIVELEG_API_KEY"const res = await fetch('https://api.liveleg.dev/v1/opt_outs?created_after=2026-11-01T00%3A00%3A00Z&limit=20', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.LIVELEG_API_KEY}`,
},
});
const data = await res.json();import os, requests
data = requests.get(
"https://api.liveleg.dev/v1/opt_outs",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
params={
"created_after": "2026-11-01T00:00:00Z",
"limit": 20,
},
).json() {
"object": "list",
"data": [
{
"id": "opt_7Ln52Hs",
"object": "opt_out",
"phone_number": "+15125550163",
"word": "STOP",
"channel": "sms",
"messaging_line_id": "mln_8Rd2",
"message_id": "msg_Rt77",
"created_at": "2026-11-03T18:40:02Z"
}
],
"has_more": false
}