Channels runtime object: same bodies, same deliveryId, same statuses.
Verified against lua-cli 3.33.0.
Base URL and authentication
Every route needschannels:send on the agent; the host, the bearer header, and the error envelope are on the REST API overview. Each send route honors an X-Idempotency-Key header: replaying a key returns the first send’s stored outcome instead of sending again, for as long as the delivery record lives; a key whose first attempt failed replays the failure; the same key from another agent is a different key.
Recipients and delivery records
A recipient is addressed by exactly one ofuserId, phoneNumber, email, conversationId, or identity. userId works on every channel and resolves the address from the end user’s channel history (on slack, from their email address in Lua); a raw phoneNumber only cold-starts whatsapp and sms, a raw email only email and slack. teams, instagram, and messenger are warm-only: the end user must have written first, so address them by userId, or on Teams by conversationId to post into a shared conversation. slack posts through the agent’s own Slack bot to a channel it is in, a thread, or a DM to a workspace member, and refuses a phoneNumber.
Every send answers the same shape.
string
Id of the delivery record; stable for the send’s whole life and accepted by
GET .../deliveries/:deliveryId.string
Where the delivery stands as the call returns:
accepted on a live send, or queued when the WhatsApp window was closed; receipts move it to sent, delivered, read, failed, or expired later. A replayed key returns wherever the first send has got to, so any status is possible there.boolean
The vendor accepted the message.
boolean
The message was written to the agent’s memory. A conversation-scoped send is recorded in the shared conversation when the agent has one there (a Teams group chat it was added to, a Slack thread or group DM where the bot was mentioned), and a Slack DM in the conversation of the post’s own thread.
false, with a warning, for a conversation-scoped send the agent has no shared conversation for, and on a memory write that failed after a delivered send; that case is still 200, with warning set to message delivered but not recorded to agent memory.boolean
true when the WhatsApp 24-hour window was closed and the text waits for the end user’s reply; delivered is false until then.string
The end user the message was recorded against, the channel-native address used, the vendor message id when the channel returns one, and a warning when something non-fatal happened.
Endpoints
POST /developer/agents/:agentId/channels/send
Sends a text message on any supported channel.string
required
The sending agent.
string
Replay key, see Base URL and authentication.
string
required
One of
whatsapp, sms, email, webchat, teams, instagram, messenger, slack.object
required
Exactly one of
userId, phoneNumber, email, conversationId, or identity. webchat requires userId; conversationId is Teams and Slack only. identity is { kind, scope, value } and sends to the agent’s user who holds it, as userId would: whatsapp_bsuid on whatsapp, slack_user on slack, teams_user on teams, messenger_psid on messenger, instagram_igsid on instagram; any other pairing is a 400, and an id none of the agent’s users holds is a 404.Slack conversationId forms: Lua.request.conversation.externalId from a Slack channel-thread or group-DM turn (T…:A…:C…:threadTs or T…:A…:G…, which names the bot); a channel id C… or G… for a new top-level message; C…:threadTs for a reply in that thread (the thread’s first message ts); a member id U… for their DM with the bot; a DM channel D… or D…:threadTs. A group DM, U…,U… with 2 to 8 member ids, works once the app has been re-authorised with the mpim:write scope from the updated manifest. On Slack, userId DMs that Lua user, found in the bot’s workspace by their email address, and email DMs the workspace member with that address. See Post from code.string
required
The message. It may contain
::: formatting component blocks, which each channel renders its own way.object
channelIdentifier pins one of the agent’s channel configurations; on Slack it is the connection T…-A…, required when the agent has more than one Slack connection and to.conversationId is not a Lua.request.conversation.externalId. whatsapp.onClosedWindow is queue (default: send the platform’s message-request template, queue the text, deliver when the end user replies) or fail (answer 400 so you can send your own approved WhatsApp message template). A queued send to a +1 number answers with a warning, because Meta blocks the message-request template to US numbers. ignoreHold: true sends even while a colleague holds the end user’s conversation (human takeover); without it, such a send answers 409.200 with the send result described under Recipients and delivery records.
Errors
Equivalent:
Channels.send().
POST /developer/agents/:agentId/channels/whatsapp/template
Sends an approved WhatsApp message template to one recipient, which is the only way to start a conversation outside the 24-hour window. The SDK’s bulkTemplates.whatsapp.send() is a different route (POST /admin/agents/:agentId/channels/:channelId/whatsapp-templates/:templateId/trigger): its results[i] is Meta’s raw per-recipient response, with the message id at messages[0].id, not the send result documented here.
string
required
The sending agent.
string
Replay key.
object
required
userId, phoneNumber, or a whatsapp_bsuid identity. A userId or identity needs an existing WhatsApp conversation with the agent.string
required
The template’s name as approved by Meta.
string
Template language, for example
en_US.array
Meta template components: the header, body, and button parameter values.
string
Text recorded in the agent’s memory as what the template said; a placeholder is recorded when omitted.
object
channelIdentifier pins a channel configuration.200 with the send result.
Errors
Equivalent:
Channels.whatsapp.sendTemplate().
POST /developer/agents/:agentId/channels/whatsapp/reaction
Reacts to a WhatsApp message with one emoji.string
required
The sending agent.
string
Replay key.
object
required
userId, phoneNumber, or a whatsapp_bsuid identity.string
required
The vendor id (
wamid...) of the message to react to, for example an inbound message’s id from the conversation history. Meta accepts reactions on messages up to 30 days old.string
required
A single emoji. An empty string removes the agent’s existing reaction.
object
channelIdentifier pins a channel configuration.200 with the send result.
Errors
Equivalent:
Channels.whatsapp.sendReaction().
POST /developer/agents/:agentId/channels/whatsapp/phone-request
Asks a WhatsApp user to share their phone number with a button.string
required
The sending agent.
string
Replay key.
object
required
userId, phoneNumber, or a whatsapp_bsuid identity, exactly one.string
required
The message shown above the button. At most 1024 characters.
object
channelIdentifier pins a channel configuration; ignoreHold sends while a colleague holds the conversation.Shared my WhatsApp number: +254712345678. When another Lua user already has the number, the WhatsApp account moves to that user, and this message and later ones run as them. If they don’t tap, nothing comes back.
Response
200 with the send result.
Errors
Equivalent:
Channels.whatsapp.requestPhoneNumber().
POST /developer/agents/:agentId/channels/email/send
Sends an email from the agent’s email channel.string
required
The sending agent.
string
Replay key.
object
required
email for a cold address, or userId to resolve the end user’s address from their channel history.string
Plain-text body, sent as the
text/plain part.string
Exact HTML body, sent as is with no template wrap.
string
Markdown and
::: component markers, rendered server-side into the branded email template that the agent’s own replies use. Send one of text, html, or richBody.string
Subject line. An explicit empty string sends a subjectless email; omitting it applies the channel’s default.
string[]
Copied addresses.
string[]
Blind-copied addresses.
array
[{ filename, contentType, url }]. The bytes are fetched server-side from url; the attachments of one email may total at most 28 MiB, and a larger total answers 400 with Attachments exceed the 28MB limit.string
The
Message-ID of the email this one replies to, so mail clients thread it. Use the inbound email’s id, which agent code reads from webhookPayload.messageId; angle brackets are optional. Honored on the branded (existing-address) email channel. A send with inReplyTo is checked for a hold against that thread’s conversation; a send without it, against the end user’s default conversation.string[]
The thread’s accumulated
Message-ID chain.object
channelIdentifier pins a channel configuration. ignoreHold: true sends even while a colleague holds the conversation.200 with the send result; messageId is the mail provider’s id.
Errors
Equivalent:
Channels.email.send().
GET /developer/agents/:agentId/channels/deliveries
Lists the agent’s recent outbound deliveries, newest first.string
required
The agent.
string
Only deliveries addressed to this end user.
string
One of
queued, accepted, sent, delivered, read, failed, expired.string
Channel name, for example
whatsapp, email, sms.string
ISO 8601 instant; only deliveries created at or after it.
integer
default:"50"
From 1 to 200.
200 with { "success": true, "data": [...] }, each item a delivery record.
string
The
deliveryId.string
Who sent it, to whom, on which channel configuration;
provider is one of meta, vonage, bird, ses, agentmail, slack, front, teams, pusher.string
channels_send for these routes; agent replies, templates, queued flushes, and notifications have their own values.string
Current status;
failed and expired are terminal.object
On a failure:
category (window_closed, unreachable, billing, opted_out, throttled, auth, template, media, invalid_request, compliance, experiment, provider, unknown), provider, the vendor code, title, detail, href, retryable, and owner (customer, recipient, lua, vendor).array
[{ status, at, source }], one per transition; source is send, callback, or sweep.various
The vendor id, the template used, the replay key you sent, and vendor cost metadata (Meta only).
string
ISO 8601 timestamps.
400 on an invalid filter, for example a limit outside 1 to 200 or a since that is not ISO 8601.
Equivalent: Channels.listDeliveries().
GET /developer/agents/:agentId/channels/deliveries/:deliveryId
Reads one delivery record by thedeliveryId a send returned.
string
required
The agent that sent it.
string
required
The delivery.
200 with { "success": true, "data": <delivery> } in the shape the list route documents; 404 with No delivery <id> for agent <id> when it belongs to another agent or does not exist.
Equivalent: Channels.getStatus().
See also
Channels— the same operations from agent code- Send proactive messages — when to use a template and how the 24-hour window works
- WhatsApp and Email — connecting the channels these routes send on
- REST API overview — authentication, scopes, and idempotency

