Open the live event stream (WebSocket)
GET /v0/streamUpgrades to a WebSocket that pushes your app's events as they happen, in log
order. Pass after to resume from the last event you saw: the stream first
replays everything after it, then goes live, so a reconnect never loses an
event.
Every WebSocket text frame is one JSON object (StreamFrame). The server
sends event frames; the client may send send frames with the same bodies
as the HTTP send endpoints, and gets one ack or error frame back for each,
matched by ref. When the server is about to restart it sends a reconnect
frame; reconnect with after set to the last event you received.
Authenticate with the Authorization header where your WebSocket client can
set headers. Where it cannot (a browser's WebSocket, and Node's global
WebSocket on the server), offer the key as a WebSocket subprotocol instead:
offer both flow and flow.key.<api key>, for example
new WebSocket("wss://api.flow.engineer/v0/stream", ["flow", "flow.key." + key]).
This works from server-side clients as well as browsers. The server selects
flow and never echoes the key. Offering the key protocol without flow is
refused with a plain 400 invalid_request answer, without an upgrade. When an Authorization header is present it
takes precedence. Keys are never accepted in the query string, since URLs end
up in logs. A key used in a browser is visible to whoever uses that page: do
this only for internal tools or with test keys (fk_test_).
Refusals arrive on the socket. Many WebSocket clients (Node's and
browsers' among them) cannot read the HTTP status of a refused upgrade, so
a WebSocket request that Flow refuses (a missing, unknown, revoked or
expired key, a bad parameter, too many streams for the key, a restart) is
still upgraded: the server sends one error frame with the usual error
body (type, message, hint, retry_after, channel_code, ...), then
closes with an application close code of 4000 plus the HTTP status the
error has elsewhere, and a short reason:
4401:authentication. The key is missing, malformed, unknown, revoked or expired (channel_codesandbox_key_expired). Stop reconnecting until you have a working key.4403:permission. The key may not open this stream. Stop reconnecting.4400:invalid_request, for example a badafterortype. Fix the request; reconnecting unchanged fails again.4429:rate_limited, for example more open streams than the key may hold. Reconnect afterretry_afterseconds.4500,4503: Flow could not open the stream just now, or is restarting. Reconnect afterretry_afterseconds with the sameafter.
An open stream re-checks its key about once a minute: when the key is
revoked, the stream sends an authentication error frame and closes with
4401 too. Other closes: 1012 after a reconnect frame (reconnect at
once with after), 1008 after too many rate-limited send frames in a
row, 1001 when pings go unanswered, 1011 when the event log is
unavailable; reconnect with after after any of these. A request without
a WebSocket upgrade gets the same errors as plain HTTP answers.
Authorizations
AuthorizationstringheaderrequiredAn API key of one app, sent as Authorization: Bearer <key>. Keys start with
fk_test_ (test mode: sandbox senders and test data only) or fk_live_
(live mode). Keep live keys on your server; never ship them in an app or page.
Headers
Flow-Versionstring<date>The API version to use, as a date. Without it, the version pinned to your app when it was created is used.
Query Parameters
afterstringAn item ID. Returns the items that come after it in the list's order.
typeenum<string>[]Only events of these types. Repeat the parameter for several.
Response
101 application/json
Switching to the WebSocket protocol. The schema below describes each JSON
frame on the socket, in either direction. A refused WebSocket request is
upgraded too, then answered with one error frame and a 4000-range close
code (see the description); the 4xx answers below are what a request
without an upgrade gets.
One of 6 options, chosen by `type`:
type: event
Server to client. One event from the log.
type enum<string> required Always event.
event Event required One entry in your app's log. type says what happened and selects the shape
of data. conversation is set for every event that happened in a
conversation (all but sender.status_changed, template.status_changed and
contact.deleted).
Show child attributes
One of 16 options, chosen by `type`:
type: message.received
The contact sent a message. data.message.content is what they sent.
id string required An event ID, evt_ and a ULID.
created_at string<date-time> required When the event was created. Events are ordered in the log by commit, not by this time.
app string required An app ID, app_ and a ULID.
livemode boolean required Whether the event belongs to live mode.
conversation ConversationRef The conversation an event happened in, inlined so most handlers need no extra call. A group's participants are not inlined; read the conversation for them.
Show child attributes
id string required A conversation ID, conv_ and a ULID.
type enum<string> Flow always sends it; read a missing one as direct (answers from
before groups existed). direct: one sender and one contact (every
channel). group: a group chat
one of your dedicated iMessage lines was added to; it has group and no
contact. Flow's shared iMessage line ignores group chats, so they never
reach your app from it.
channel enum<string> required A messaging channel. sms is US SMS and MMS (in pilot), from dedicated numbers only.
sender string required A sender ID, snd_ and a ULID.
contact string The contact the conversation is with. Always set when type is direct; absent on group conversations (the message's from says who wrote).
group_name string Group conversations only. The group chat's name, when it has one.
window_open_until string<date-time> WhatsApp only. Until when free-form messages may be sent. Absent when closed or not applicable.
simulated boolean read-only true for a simulated conversation, whose contact exists only inside Flow (test mode only, from POST /v0/test/conversations); absent otherwise.
timing Timing required When the event moved through Flow, to measure Flow's overhead
(delivered_at minus received_at).
Show child attributes
received_at string<date-time> required When Flow received it from the channel, or when it happened inside Flow.
stored_at string<date-time> required When it was committed to the log.
delivered_at string<date-time> When the event went out to a webhook endpoint. Its value depends on
where you read it. In a webhook delivery's body, it is when that delivery
attempt started (each attempt to each endpoint gets its own), unless an
endpoint had already accepted the event, in which case it is that
acceptance time. In the event log (GET /v0/events,
GET /v0/events/{event_id}, the stream), it is when the first endpoint
accepted the event, and it is absent until one has (always, for an app
with no webhook endpoints).
type enum<string> required Always message.received.
data MessageEventData required The message the event is about.
Show child attributes
message Message required One message in or out of a conversation, with its kind and content and its delivery status. What you send is what the contact gets: Flow never converts a message into another kind.
Show child attributes
id string required A message ID, msg_ and a ULID.
conversation string required A conversation ID, conv_ and a ULID.
direction enum<string> required in from the contact, out from your app.
from string read-only Inbound messages only, on every channel. The contact who wrote it; in a group conversation, which participant. Absent on your own messages.
status enum<string> required received: inbound from the contact.queued: accepted by the gate, waiting for its turn in the conversation.sent: the channel accepted it.delivered: it reached the contact's device, where the channel reports this.read: the contact read it, where the channel reports this.failed: it could not be sent; seeerror.unsent: you unsent it.
content MessageContent required A message's content: one kind object, in either direction, with kind
(always present here) saying which. It is exactly what was sent or
received, except metadata, which is kept once, at the message's own
metadata, and not repeated here.
network enum<string> read-only iMessage channel only: the network that carried the message. imessage,
or sms or rcs when the line has the SMS/RCS add-on
(Sender.imessage.fallback) and the contact could not get iMessage. The
network is chosen for each message when it is sent, never by your app.
Set on inbound messages, and on your messages once the channel reports it
(in message.sent, message.delivered or message.failed, as
data.message.network); absent before that and on other channels. A
message that went by sms or rcs showed no imessage.styles, no
imessage.subject and no effect, and edits and unsends of it answer 422 unsupported_content with channel_code not_on_sms.
error ErrorBody The details of an error.
channel_message_id string The channel's own ID for the message, once the channel accepted it.
sms SmsSegments read-only SMS text messages only: how the text travels as SMS. Carriers bill and
deliver SMS in segments: 160 GSM-7 characters fit one segment (153 each
when split), but a single character outside GSM-7 (an emoji, curly quotes,
most non-Latin letters) makes the whole text UCS-2, 70 characters in one
segment (67 each when split). An SMS send may use at most 10 segments:
1530 GSM-7 characters (fewer when it uses GSM-7's two-unit characters such
as { or €), or 670 when it holds an emoji or other character outside
GSM-7; a longer text is refused with 400 invalid_request. Absent on MMS
and on other channels.
reply_to string read-only The Flow ID of the message this one replies to. For a sent message, the
message the send named in reply_to. For a received message, the message it
replies to inline (a Telegram or WhatsApp reply; on iMessage, the thread's
root message). Absent when the message is not a reply or the quoted
message is not known to Flow.
livemode boolean required Whether the message belongs to live mode.
metadata Metadata Up to 20 string pairs of your own, kept with the object and returned unchanged. Keys up to 40 characters, values up to 500.
created_at string<date-time> required When Flow received (inbound) or accepted (outbound) the message.
updated_at string<date-time> When the status last changed.
type: message.sent
The channel accepted one of your messages.
id string required An event ID, evt_ and a ULID.
created_at string<date-time> required When the event was created. Events are ordered in the log by commit, not by this time.
app string required An app ID, app_ and a ULID.
livemode boolean required Whether the event belongs to live mode.
conversation ConversationRef The conversation an event happened in, inlined so most handlers need no extra call. A group's participants are not inlined; read the conversation for them.
Show child attributes
id string required A conversation ID, conv_ and a ULID.
type enum<string> Flow always sends it; read a missing one as direct (answers from
before groups existed). direct: one sender and one contact (every
channel). group: a group chat
one of your dedicated iMessage lines was added to; it has group and no
contact. Flow's shared iMessage line ignores group chats, so they never
reach your app from it.
channel enum<string> required A messaging channel. sms is US SMS and MMS (in pilot), from dedicated numbers only.
sender string required A sender ID, snd_ and a ULID.
contact string The contact the conversation is with. Always set when type is direct; absent on group conversations (the message's from says who wrote).
group_name string Group conversations only. The group chat's name, when it has one.
window_open_until string<date-time> WhatsApp only. Until when free-form messages may be sent. Absent when closed or not applicable.
simulated boolean read-only true for a simulated conversation, whose contact exists only inside Flow (test mode only, from POST /v0/test/conversations); absent otherwise.
timing Timing required When the event moved through Flow, to measure Flow's overhead
(delivered_at minus received_at).
Show child attributes
received_at string<date-time> required When Flow received it from the channel, or when it happened inside Flow.
stored_at string<date-time> required When it was committed to the log.
delivered_at string<date-time> When the event went out to a webhook endpoint. Its value depends on
where you read it. In a webhook delivery's body, it is when that delivery
attempt started (each attempt to each endpoint gets its own), unless an
endpoint had already accepted the event, in which case it is that
acceptance time. In the event log (GET /v0/events,
GET /v0/events/{event_id}, the stream), it is when the first endpoint
accepted the event, and it is absent until one has (always, for an app
with no webhook endpoints).
type enum<string> required Always message.sent.
data MessageEventData required The message the event is about.
Show child attributes
message Message required One message in or out of a conversation, with its kind and content and its delivery status. What you send is what the contact gets: Flow never converts a message into another kind.
Show child attributes
id string required A message ID, msg_ and a ULID.
conversation string required A conversation ID, conv_ and a ULID.
direction enum<string> required in from the contact, out from your app.
from string read-only Inbound messages only, on every channel. The contact who wrote it; in a group conversation, which participant. Absent on your own messages.
status enum<string> required received: inbound from the contact.queued: accepted by the gate, waiting for its turn in the conversation.sent: the channel accepted it.delivered: it reached the contact's device, where the channel reports this.read: the contact read it, where the channel reports this.failed: it could not be sent; seeerror.unsent: you unsent it.
content MessageContent required A message's content: one kind object, in either direction, with kind
(always present here) saying which. It is exactly what was sent or
received, except metadata, which is kept once, at the message's own
metadata, and not repeated here.
network enum<string> read-only iMessage channel only: the network that carried the message. imessage,
or sms or rcs when the line has the SMS/RCS add-on
(Sender.imessage.fallback) and the contact could not get iMessage. The
network is chosen for each message when it is sent, never by your app.
Set on inbound messages, and on your messages once the channel reports it
(in message.sent, message.delivered or message.failed, as
data.message.network); absent before that and on other channels. A
message that went by sms or rcs showed no imessage.styles, no
imessage.subject and no effect, and edits and unsends of it answer 422 unsupported_content with channel_code not_on_sms.
error ErrorBody The details of an error.
channel_message_id string The channel's own ID for the message, once the channel accepted it.
sms SmsSegments read-only SMS text messages only: how the text travels as SMS. Carriers bill and
deliver SMS in segments: 160 GSM-7 characters fit one segment (153 each
when split), but a single character outside GSM-7 (an emoji, curly quotes,
most non-Latin letters) makes the whole text UCS-2, 70 characters in one
segment (67 each when split). An SMS send may use at most 10 segments:
1530 GSM-7 characters (fewer when it uses GSM-7's two-unit characters such
as { or €), or 670 when it holds an emoji or other character outside
GSM-7; a longer text is refused with 400 invalid_request. Absent on MMS
and on other channels.
reply_to string read-only The Flow ID of the message this one replies to. For a sent message, the
message the send named in reply_to. For a received message, the message it
replies to inline (a Telegram or WhatsApp reply; on iMessage, the thread's
root message). Absent when the message is not a reply or the quoted
message is not known to Flow.
livemode boolean required Whether the message belongs to live mode.
metadata Metadata Up to 20 string pairs of your own, kept with the object and returned unchanged. Keys up to 40 characters, values up to 500.
created_at string<date-time> required When Flow received (inbound) or accepted (outbound) the message.
updated_at string<date-time> When the status last changed.
type: message.delivered
One of your messages reached the contact's device.
id string required An event ID, evt_ and a ULID.
created_at string<date-time> required When the event was created. Events are ordered in the log by commit, not by this time.
app string required An app ID, app_ and a ULID.
livemode boolean required Whether the event belongs to live mode.
conversation ConversationRef The conversation an event happened in, inlined so most handlers need no extra call. A group's participants are not inlined; read the conversation for them.
Show child attributes
id string required A conversation ID, conv_ and a ULID.
type enum<string> Flow always sends it; read a missing one as direct (answers from
before groups existed). direct: one sender and one contact (every
channel). group: a group chat
one of your dedicated iMessage lines was added to; it has group and no
contact. Flow's shared iMessage line ignores group chats, so they never
reach your app from it.
channel enum<string> required A messaging channel. sms is US SMS and MMS (in pilot), from dedicated numbers only.
sender string required A sender ID, snd_ and a ULID.
contact string The contact the conversation is with. Always set when type is direct; absent on group conversations (the message's from says who wrote).
group_name string Group conversations only. The group chat's name, when it has one.
window_open_until string<date-time> WhatsApp only. Until when free-form messages may be sent. Absent when closed or not applicable.
simulated boolean read-only true for a simulated conversation, whose contact exists only inside Flow (test mode only, from POST /v0/test/conversations); absent otherwise.
timing Timing required When the event moved through Flow, to measure Flow's overhead
(delivered_at minus received_at).
Show child attributes
received_at string<date-time> required When Flow received it from the channel, or when it happened inside Flow.
stored_at string<date-time> required When it was committed to the log.
delivered_at string<date-time> When the event went out to a webhook endpoint. Its value depends on
where you read it. In a webhook delivery's body, it is when that delivery
attempt started (each attempt to each endpoint gets its own), unless an
endpoint had already accepted the event, in which case it is that
acceptance time. In the event log (GET /v0/events,
GET /v0/events/{event_id}, the stream), it is when the first endpoint
accepted the event, and it is absent until one has (always, for an app
with no webhook endpoints).
type enum<string> required Always message.delivered.
data MessageEventData required The message the event is about.
Show child attributes
message Message required One message in or out of a conversation, with its kind and content and its delivery status. What you send is what the contact gets: Flow never converts a message into another kind.
Show child attributes
id string required A message ID, msg_ and a ULID.
conversation string required A conversation ID, conv_ and a ULID.
direction enum<string> required in from the contact, out from your app.
from string read-only Inbound messages only, on every channel. The contact who wrote it; in a group conversation, which participant. Absent on your own messages.
status enum<string> required received: inbound from the contact.queued: accepted by the gate, waiting for its turn in the conversation.sent: the channel accepted it.delivered: it reached the contact's device, where the channel reports this.read: the contact read it, where the channel reports this.failed: it could not be sent; seeerror.unsent: you unsent it.
content MessageContent required A message's content: one kind object, in either direction, with kind
(always present here) saying which. It is exactly what was sent or
received, except metadata, which is kept once, at the message's own
metadata, and not repeated here.
network enum<string> read-only iMessage channel only: the network that carried the message. imessage,
or sms or rcs when the line has the SMS/RCS add-on
(Sender.imessage.fallback) and the contact could not get iMessage. The
network is chosen for each message when it is sent, never by your app.
Set on inbound messages, and on your messages once the channel reports it
(in message.sent, message.delivered or message.failed, as
data.message.network); absent before that and on other channels. A
message that went by sms or rcs showed no imessage.styles, no
imessage.subject and no effect, and edits and unsends of it answer 422 unsupported_content with channel_code not_on_sms.
error ErrorBody The details of an error.
channel_message_id string The channel's own ID for the message, once the channel accepted it.
sms SmsSegments read-only SMS text messages only: how the text travels as SMS. Carriers bill and
deliver SMS in segments: 160 GSM-7 characters fit one segment (153 each
when split), but a single character outside GSM-7 (an emoji, curly quotes,
most non-Latin letters) makes the whole text UCS-2, 70 characters in one
segment (67 each when split). An SMS send may use at most 10 segments:
1530 GSM-7 characters (fewer when it uses GSM-7's two-unit characters such
as { or €), or 670 when it holds an emoji or other character outside
GSM-7; a longer text is refused with 400 invalid_request. Absent on MMS
and on other channels.
reply_to string read-only The Flow ID of the message this one replies to. For a sent message, the
message the send named in reply_to. For a received message, the message it
replies to inline (a Telegram or WhatsApp reply; on iMessage, the thread's
root message). Absent when the message is not a reply or the quoted
message is not known to Flow.
livemode boolean required Whether the message belongs to live mode.
metadata Metadata Up to 20 string pairs of your own, kept with the object and returned unchanged. Keys up to 40 characters, values up to 500.
created_at string<date-time> required When Flow received (inbound) or accepted (outbound) the message.
updated_at string<date-time> When the status last changed.
type: message.read
The contact read one of your messages.
id string required An event ID, evt_ and a ULID.
created_at string<date-time> required When the event was created. Events are ordered in the log by commit, not by this time.
app string required An app ID, app_ and a ULID.
livemode boolean required Whether the event belongs to live mode.
conversation ConversationRef The conversation an event happened in, inlined so most handlers need no extra call. A group's participants are not inlined; read the conversation for them.
Show child attributes
id string required A conversation ID, conv_ and a ULID.
type enum<string> Flow always sends it; read a missing one as direct (answers from
before groups existed). direct: one sender and one contact (every
channel). group: a group chat
one of your dedicated iMessage lines was added to; it has group and no
contact. Flow's shared iMessage line ignores group chats, so they never
reach your app from it.
channel enum<string> required A messaging channel. sms is US SMS and MMS (in pilot), from dedicated numbers only.
sender string required A sender ID, snd_ and a ULID.
contact string The contact the conversation is with. Always set when type is direct; absent on group conversations (the message's from says who wrote).
group_name string Group conversations only. The group chat's name, when it has one.
window_open_until string<date-time> WhatsApp only. Until when free-form messages may be sent. Absent when closed or not applicable.
simulated boolean read-only true for a simulated conversation, whose contact exists only inside Flow (test mode only, from POST /v0/test/conversations); absent otherwise.
timing Timing required When the event moved through Flow, to measure Flow's overhead
(delivered_at minus received_at).
Show child attributes
received_at string<date-time> required When Flow received it from the channel, or when it happened inside Flow.
stored_at string<date-time> required When it was committed to the log.
delivered_at string<date-time> When the event went out to a webhook endpoint. Its value depends on
where you read it. In a webhook delivery's body, it is when that delivery
attempt started (each attempt to each endpoint gets its own), unless an
endpoint had already accepted the event, in which case it is that
acceptance time. In the event log (GET /v0/events,
GET /v0/events/{event_id}, the stream), it is when the first endpoint
accepted the event, and it is absent until one has (always, for an app
with no webhook endpoints).
type enum<string> required Always message.read.
data MessageEventData required The message the event is about.
Show child attributes
message Message required One message in or out of a conversation, with its kind and content and its delivery status. What you send is what the contact gets: Flow never converts a message into another kind.
Show child attributes
id string required A message ID, msg_ and a ULID.
conversation string required A conversation ID, conv_ and a ULID.
direction enum<string> required in from the contact, out from your app.
from string read-only Inbound messages only, on every channel. The contact who wrote it; in a group conversation, which participant. Absent on your own messages.
status enum<string> required received: inbound from the contact.queued: accepted by the gate, waiting for its turn in the conversation.sent: the channel accepted it.delivered: it reached the contact's device, where the channel reports this.read: the contact read it, where the channel reports this.failed: it could not be sent; seeerror.unsent: you unsent it.
content MessageContent required A message's content: one kind object, in either direction, with kind
(always present here) saying which. It is exactly what was sent or
received, except metadata, which is kept once, at the message's own
metadata, and not repeated here.
network enum<string> read-only iMessage channel only: the network that carried the message. imessage,
or sms or rcs when the line has the SMS/RCS add-on
(Sender.imessage.fallback) and the contact could not get iMessage. The
network is chosen for each message when it is sent, never by your app.
Set on inbound messages, and on your messages once the channel reports it
(in message.sent, message.delivered or message.failed, as
data.message.network); absent before that and on other channels. A
message that went by sms or rcs showed no imessage.styles, no
imessage.subject and no effect, and edits and unsends of it answer 422 unsupported_content with channel_code not_on_sms.
error ErrorBody The details of an error.
channel_message_id string The channel's own ID for the message, once the channel accepted it.
sms SmsSegments read-only SMS text messages only: how the text travels as SMS. Carriers bill and
deliver SMS in segments: 160 GSM-7 characters fit one segment (153 each
when split), but a single character outside GSM-7 (an emoji, curly quotes,
most non-Latin letters) makes the whole text UCS-2, 70 characters in one
segment (67 each when split). An SMS send may use at most 10 segments:
1530 GSM-7 characters (fewer when it uses GSM-7's two-unit characters such
as { or €), or 670 when it holds an emoji or other character outside
GSM-7; a longer text is refused with 400 invalid_request. Absent on MMS
and on other channels.
reply_to string read-only The Flow ID of the message this one replies to. For a sent message, the
message the send named in reply_to. For a received message, the message it
replies to inline (a Telegram or WhatsApp reply; on iMessage, the thread's
root message). Absent when the message is not a reply or the quoted
message is not known to Flow.
livemode boolean required Whether the message belongs to live mode.
metadata Metadata Up to 20 string pairs of your own, kept with the object and returned unchanged. Keys up to 40 characters, values up to 500.
created_at string<date-time> required When Flow received (inbound) or accepted (outbound) the message.
updated_at string<date-time> When the status last changed.
type: message.failed
One of your messages could not be sent. data.message.error says why, mapped to an ErrorType.
id string required An event ID, evt_ and a ULID.
created_at string<date-time> required When the event was created. Events are ordered in the log by commit, not by this time.
app string required An app ID, app_ and a ULID.
livemode boolean required Whether the event belongs to live mode.
conversation ConversationRef The conversation an event happened in, inlined so most handlers need no extra call. A group's participants are not inlined; read the conversation for them.
Show child attributes
id string required A conversation ID, conv_ and a ULID.
type enum<string> Flow always sends it; read a missing one as direct (answers from
before groups existed). direct: one sender and one contact (every
channel). group: a group chat
one of your dedicated iMessage lines was added to; it has group and no
contact. Flow's shared iMessage line ignores group chats, so they never
reach your app from it.
channel enum<string> required A messaging channel. sms is US SMS and MMS (in pilot), from dedicated numbers only.
sender string required A sender ID, snd_ and a ULID.
contact string The contact the conversation is with. Always set when type is direct; absent on group conversations (the message's from says who wrote).
group_name string Group conversations only. The group chat's name, when it has one.
window_open_until string<date-time> WhatsApp only. Until when free-form messages may be sent. Absent when closed or not applicable.
simulated boolean read-only true for a simulated conversation, whose contact exists only inside Flow (test mode only, from POST /v0/test/conversations); absent otherwise.
timing Timing required When the event moved through Flow, to measure Flow's overhead
(delivered_at minus received_at).
Show child attributes
received_at string<date-time> required When Flow received it from the channel, or when it happened inside Flow.
stored_at string<date-time> required When it was committed to the log.
delivered_at string<date-time> When the event went out to a webhook endpoint. Its value depends on
where you read it. In a webhook delivery's body, it is when that delivery
attempt started (each attempt to each endpoint gets its own), unless an
endpoint had already accepted the event, in which case it is that
acceptance time. In the event log (GET /v0/events,
GET /v0/events/{event_id}, the stream), it is when the first endpoint
accepted the event, and it is absent until one has (always, for an app
with no webhook endpoints).
type enum<string> required Always message.failed.
data MessageEventData required The message the event is about.
Show child attributes
message Message required One message in or out of a conversation, with its kind and content and its delivery status. What you send is what the contact gets: Flow never converts a message into another kind.
Show child attributes
id string required A message ID, msg_ and a ULID.
conversation string required A conversation ID, conv_ and a ULID.
direction enum<string> required in from the contact, out from your app.
from string read-only Inbound messages only, on every channel. The contact who wrote it; in a group conversation, which participant. Absent on your own messages.
status enum<string> required received: inbound from the contact.queued: accepted by the gate, waiting for its turn in the conversation.sent: the channel accepted it.delivered: it reached the contact's device, where the channel reports this.read: the contact read it, where the channel reports this.failed: it could not be sent; seeerror.unsent: you unsent it.
content MessageContent required A message's content: one kind object, in either direction, with kind
(always present here) saying which. It is exactly what was sent or
received, except metadata, which is kept once, at the message's own
metadata, and not repeated here.
network enum<string> read-only iMessage channel only: the network that carried the message. imessage,
or sms or rcs when the line has the SMS/RCS add-on
(Sender.imessage.fallback) and the contact could not get iMessage. The
network is chosen for each message when it is sent, never by your app.
Set on inbound messages, and on your messages once the channel reports it
(in message.sent, message.delivered or message.failed, as
data.message.network); absent before that and on other channels. A
message that went by sms or rcs showed no imessage.styles, no
imessage.subject and no effect, and edits and unsends of it answer 422 unsupported_content with channel_code not_on_sms.
error ErrorBody The details of an error.
channel_message_id string The channel's own ID for the message, once the channel accepted it.
sms SmsSegments read-only SMS text messages only: how the text travels as SMS. Carriers bill and
deliver SMS in segments: 160 GSM-7 characters fit one segment (153 each
when split), but a single character outside GSM-7 (an emoji, curly quotes,
most non-Latin letters) makes the whole text UCS-2, 70 characters in one
segment (67 each when split). An SMS send may use at most 10 segments:
1530 GSM-7 characters (fewer when it uses GSM-7's two-unit characters such
as { or €), or 670 when it holds an emoji or other character outside
GSM-7; a longer text is refused with 400 invalid_request. Absent on MMS
and on other channels.
reply_to string read-only The Flow ID of the message this one replies to. For a sent message, the
message the send named in reply_to. For a received message, the message it
replies to inline (a Telegram or WhatsApp reply; on iMessage, the thread's
root message). Absent when the message is not a reply or the quoted
message is not known to Flow.
livemode boolean required Whether the message belongs to live mode.
metadata Metadata Up to 20 string pairs of your own, kept with the object and returned unchanged. Keys up to 40 characters, values up to 500.
created_at string<date-time> required When Flow received (inbound) or accepted (outbound) the message.
updated_at string<date-time> When the status last changed.
type: reaction.added
The contact reacted to a message.
id string required An event ID, evt_ and a ULID.
created_at string<date-time> required When the event was created. Events are ordered in the log by commit, not by this time.
app string required An app ID, app_ and a ULID.
livemode boolean required Whether the event belongs to live mode.
conversation ConversationRef The conversation an event happened in, inlined so most handlers need no extra call. A group's participants are not inlined; read the conversation for them.
Show child attributes
id string required A conversation ID, conv_ and a ULID.
type enum<string> Flow always sends it; read a missing one as direct (answers from
before groups existed). direct: one sender and one contact (every
channel). group: a group chat
one of your dedicated iMessage lines was added to; it has group and no
contact. Flow's shared iMessage line ignores group chats, so they never
reach your app from it.
channel enum<string> required A messaging channel. sms is US SMS and MMS (in pilot), from dedicated numbers only.
sender string required A sender ID, snd_ and a ULID.
contact string The contact the conversation is with. Always set when type is direct; absent on group conversations (the message's from says who wrote).
group_name string Group conversations only. The group chat's name, when it has one.
window_open_until string<date-time> WhatsApp only. Until when free-form messages may be sent. Absent when closed or not applicable.
simulated boolean read-only true for a simulated conversation, whose contact exists only inside Flow (test mode only, from POST /v0/test/conversations); absent otherwise.
timing Timing required When the event moved through Flow, to measure Flow's overhead
(delivered_at minus received_at).
Show child attributes
received_at string<date-time> required When Flow received it from the channel, or when it happened inside Flow.
stored_at string<date-time> required When it was committed to the log.
delivered_at string<date-time> When the event went out to a webhook endpoint. Its value depends on
where you read it. In a webhook delivery's body, it is when that delivery
attempt started (each attempt to each endpoint gets its own), unless an
endpoint had already accepted the event, in which case it is that
acceptance time. In the event log (GET /v0/events,
GET /v0/events/{event_id}, the stream), it is when the first endpoint
accepted the event, and it is absent until one has (always, for an app
with no webhook endpoints).
type enum<string> required Always reaction.added.
data ReactionEventData required A reaction the contact added or removed.
Show child attributes
from string The contact who reacted, where the channel reports it (in a group conversation, which participant).
message_id string required A message ID, msg_ and a ULID.
emoji string required The emoji (iMessage tapbacks are mapped to their closest emoji).
type: reaction.removed
The contact removed a reaction.
id string required An event ID, evt_ and a ULID.
created_at string<date-time> required When the event was created. Events are ordered in the log by commit, not by this time.
app string required An app ID, app_ and a ULID.
livemode boolean required Whether the event belongs to live mode.
conversation ConversationRef The conversation an event happened in, inlined so most handlers need no extra call. A group's participants are not inlined; read the conversation for them.
Show child attributes
id string required A conversation ID, conv_ and a ULID.
type enum<string> Flow always sends it; read a missing one as direct (answers from
before groups existed). direct: one sender and one contact (every
channel). group: a group chat
one of your dedicated iMessage lines was added to; it has group and no
contact. Flow's shared iMessage line ignores group chats, so they never
reach your app from it.
channel enum<string> required A messaging channel. sms is US SMS and MMS (in pilot), from dedicated numbers only.
sender string required A sender ID, snd_ and a ULID.
contact string The contact the conversation is with. Always set when type is direct; absent on group conversations (the message's from says who wrote).
group_name string Group conversations only. The group chat's name, when it has one.
window_open_until string<date-time> WhatsApp only. Until when free-form messages may be sent. Absent when closed or not applicable.
simulated boolean read-only true for a simulated conversation, whose contact exists only inside Flow (test mode only, from POST /v0/test/conversations); absent otherwise.
timing Timing required When the event moved through Flow, to measure Flow's overhead
(delivered_at minus received_at).
Show child attributes
received_at string<date-time> required When Flow received it from the channel, or when it happened inside Flow.
stored_at string<date-time> required When it was committed to the log.
delivered_at string<date-time> When the event went out to a webhook endpoint. Its value depends on
where you read it. In a webhook delivery's body, it is when that delivery
attempt started (each attempt to each endpoint gets its own), unless an
endpoint had already accepted the event, in which case it is that
acceptance time. In the event log (GET /v0/events,
GET /v0/events/{event_id}, the stream), it is when the first endpoint
accepted the event, and it is absent until one has (always, for an app
with no webhook endpoints).
type enum<string> required Always reaction.removed.
data ReactionEventData required A reaction the contact added or removed.
Show child attributes
from string The contact who reacted, where the channel reports it (in a group conversation, which participant).
message_id string required A message ID, msg_ and a ULID.
emoji string required The emoji (iMessage tapbacks are mapped to their closest emoji).
type: typing.started
The contact started typing, where the channel reports it.
id string required An event ID, evt_ and a ULID.
created_at string<date-time> required When the event was created. Events are ordered in the log by commit, not by this time.
app string required An app ID, app_ and a ULID.
livemode boolean required Whether the event belongs to live mode.
conversation ConversationRef The conversation an event happened in, inlined so most handlers need no extra call. A group's participants are not inlined; read the conversation for them.
Show child attributes
id string required A conversation ID, conv_ and a ULID.
type enum<string> Flow always sends it; read a missing one as direct (answers from
before groups existed). direct: one sender and one contact (every
channel). group: a group chat
one of your dedicated iMessage lines was added to; it has group and no
contact. Flow's shared iMessage line ignores group chats, so they never
reach your app from it.
channel enum<string> required A messaging channel. sms is US SMS and MMS (in pilot), from dedicated numbers only.
sender string required A sender ID, snd_ and a ULID.
contact string The contact the conversation is with. Always set when type is direct; absent on group conversations (the message's from says who wrote).
group_name string Group conversations only. The group chat's name, when it has one.
window_open_until string<date-time> WhatsApp only. Until when free-form messages may be sent. Absent when closed or not applicable.
simulated boolean read-only true for a simulated conversation, whose contact exists only inside Flow (test mode only, from POST /v0/test/conversations); absent otherwise.
timing Timing required When the event moved through Flow, to measure Flow's overhead
(delivered_at minus received_at).
Show child attributes
received_at string<date-time> required When Flow received it from the channel, or when it happened inside Flow.
stored_at string<date-time> required When it was committed to the log.
delivered_at string<date-time> When the event went out to a webhook endpoint. Its value depends on
where you read it. In a webhook delivery's body, it is when that delivery
attempt started (each attempt to each endpoint gets its own), unless an
endpoint had already accepted the event, in which case it is that
acceptance time. In the event log (GET /v0/events,
GET /v0/events/{event_id}, the stream), it is when the first endpoint
accepted the event, and it is absent until one has (always, for an app
with no webhook endpoints).
type enum<string> required Always typing.started.
data TypingEventData required Who is typing.
Show child attributes
contact string required A contact ID, ct_ and a ULID.
type: typing.stopped
The contact stopped typing, where the channel reports it.
id string required An event ID, evt_ and a ULID.
created_at string<date-time> required When the event was created. Events are ordered in the log by commit, not by this time.
app string required An app ID, app_ and a ULID.
livemode boolean required Whether the event belongs to live mode.
conversation ConversationRef The conversation an event happened in, inlined so most handlers need no extra call. A group's participants are not inlined; read the conversation for them.
Show child attributes
id string required A conversation ID, conv_ and a ULID.
type enum<string> Flow always sends it; read a missing one as direct (answers from
before groups existed). direct: one sender and one contact (every
channel). group: a group chat
one of your dedicated iMessage lines was added to; it has group and no
contact. Flow's shared iMessage line ignores group chats, so they never
reach your app from it.
channel enum<string> required A messaging channel. sms is US SMS and MMS (in pilot), from dedicated numbers only.
sender string required A sender ID, snd_ and a ULID.
contact string The contact the conversation is with. Always set when type is direct; absent on group conversations (the message's from says who wrote).
group_name string Group conversations only. The group chat's name, when it has one.
window_open_until string<date-time> WhatsApp only. Until when free-form messages may be sent. Absent when closed or not applicable.
simulated boolean read-only true for a simulated conversation, whose contact exists only inside Flow (test mode only, from POST /v0/test/conversations); absent otherwise.
timing Timing required When the event moved through Flow, to measure Flow's overhead
(delivered_at minus received_at).
Show child attributes
received_at string<date-time> required When Flow received it from the channel, or when it happened inside Flow.
stored_at string<date-time> required When it was committed to the log.
delivered_at string<date-time> When the event went out to a webhook endpoint. Its value depends on
where you read it. In a webhook delivery's body, it is when that delivery
attempt started (each attempt to each endpoint gets its own), unless an
endpoint had already accepted the event, in which case it is that
acceptance time. In the event log (GET /v0/events,
GET /v0/events/{event_id}, the stream), it is when the first endpoint
accepted the event, and it is absent until one has (always, for an app
with no webhook endpoints).
type enum<string> required Always typing.stopped.
data TypingEventData required Who is typing.
Show child attributes
contact string required A contact ID, ct_ and a ULID.
type: conversation.started
A new conversation began. Sent before the first message.received of a new
contact, or, when you started the conversation (data.via outbound), with
your first send to the contact. A sandbox join (data.via sandbox_join) also starts one: the
sandbox bot's confirmation ("You're connected to ") is sent by Flow
itself, not by your app, and counts against no allowance. Then the join
message reaches your app as the contact's first message.received, after
this event, with the code taken out: the words before (ref <code>),
trimmed (Hi Sandbox app), or Hi when nothing is left; a Telegram link's
start arrives as /start. Answer it like any first message. Joining again
in a conversation that already exists sends no new conversation.started;
the join message is still delivered as a message.received.
id string required An event ID, evt_ and a ULID.
created_at string<date-time> required When the event was created. Events are ordered in the log by commit, not by this time.
app string required An app ID, app_ and a ULID.
livemode boolean required Whether the event belongs to live mode.
conversation ConversationRef The conversation an event happened in, inlined so most handlers need no extra call. A group's participants are not inlined; read the conversation for them.
Show child attributes
id string required A conversation ID, conv_ and a ULID.
type enum<string> Flow always sends it; read a missing one as direct (answers from
before groups existed). direct: one sender and one contact (every
channel). group: a group chat
one of your dedicated iMessage lines was added to; it has group and no
contact. Flow's shared iMessage line ignores group chats, so they never
reach your app from it.
channel enum<string> required A messaging channel. sms is US SMS and MMS (in pilot), from dedicated numbers only.
sender string required A sender ID, snd_ and a ULID.
contact string The contact the conversation is with. Always set when type is direct; absent on group conversations (the message's from says who wrote).
group_name string Group conversations only. The group chat's name, when it has one.
window_open_until string<date-time> WhatsApp only. Until when free-form messages may be sent. Absent when closed or not applicable.
simulated boolean read-only true for a simulated conversation, whose contact exists only inside Flow (test mode only, from POST /v0/test/conversations); absent otherwise.
timing Timing required When the event moved through Flow, to measure Flow's overhead
(delivered_at minus received_at).
Show child attributes
received_at string<date-time> required When Flow received it from the channel, or when it happened inside Flow.
stored_at string<date-time> required When it was committed to the log.
delivered_at string<date-time> When the event went out to a webhook endpoint. Its value depends on
where you read it. In a webhook delivery's body, it is when that delivery
attempt started (each attempt to each endpoint gets its own), unless an
endpoint had already accepted the event, in which case it is that
acceptance time. In the event log (GET /v0/events,
GET /v0/events/{event_id}, the stream), it is when the first endpoint
accepted the event, and it is absent until one has (always, for an app
with no webhook endpoints).
type enum<string> required Always conversation.started.
data ConversationStartedData required The new conversation and how it began.
Show child attributes
conversation Conversation required One sender talking with one contact (type direct), or with an iMessage
group chat (type group). Holds the window state and whether the contact
opted in.
Show child attributes
id string required A conversation ID, conv_ and a ULID.
app string required An app ID, app_ and a ULID.
type enum<string> Flow always sends it; read a missing one as direct (answers from
before groups existed). direct: one sender and one contact (every
channel). group: a group chat
one of your dedicated iMessage lines was added to; it has group and no
contact. Flow's shared iMessage line ignores group chats, so they never
reach your app from it.
channel enum<string> required A messaging channel. sms is US SMS and MMS (in pilot), from dedicated numbers only.
sender string required A sender ID, snd_ and a ULID.
contact string The contact the conversation is with. Always set when type is direct; absent on group conversations (each message's from says who wrote).
group Group read-only Group conversations only (iMessage): the group chat as Flow saw it last.
People who join or leave change participants without an event; Flow
updates the list when the group next writes. The channel's own group ID is
never exposed: use the conversation's ID.
livemode boolean required Whether the conversation belongs to live mode.
last_inbound_at string<date-time> When the contact last wrote. Absent if they never have.
window_open_until string<date-time> WhatsApp only. Until when free-form messages may be sent; after it, only templates. Absent when the window is closed or the channel has none.
opted_in boolean required Whether the contact has agreed to receive messages (wrote first, joined the sandbox, or you recorded consent). Always true on group conversations, which begin with the group writing.
simulated boolean read-only true for a simulated conversation, whose contact exists only inside Flow (test mode only, from POST /v0/test/conversations); absent otherwise.
metadata Metadata Up to 20 string pairs of your own, kept with the object and returned unchanged. Keys up to 40 characters, values up to 500.
created_at string<date-time> required When the conversation began.
via enum<string> required inbound (the contact wrote first), sandbox_join (they sent your app's join message or opened its Telegram link) or outbound (you started it).
type: conversation.window_closing
WhatsApp only, opt-in per endpoint. The conversation's 24-hour window closes in 1 hour.
id string required An event ID, evt_ and a ULID.
created_at string<date-time> required When the event was created. Events are ordered in the log by commit, not by this time.
app string required An app ID, app_ and a ULID.
livemode boolean required Whether the event belongs to live mode.
conversation ConversationRef The conversation an event happened in, inlined so most handlers need no extra call. A group's participants are not inlined; read the conversation for them.
Show child attributes
id string required A conversation ID, conv_ and a ULID.
type enum<string> Flow always sends it; read a missing one as direct (answers from
before groups existed). direct: one sender and one contact (every
channel). group: a group chat
one of your dedicated iMessage lines was added to; it has group and no
contact. Flow's shared iMessage line ignores group chats, so they never
reach your app from it.
channel enum<string> required A messaging channel. sms is US SMS and MMS (in pilot), from dedicated numbers only.
sender string required A sender ID, snd_ and a ULID.
contact string The contact the conversation is with. Always set when type is direct; absent on group conversations (the message's from says who wrote).
group_name string Group conversations only. The group chat's name, when it has one.
window_open_until string<date-time> WhatsApp only. Until when free-form messages may be sent. Absent when closed or not applicable.
simulated boolean read-only true for a simulated conversation, whose contact exists only inside Flow (test mode only, from POST /v0/test/conversations); absent otherwise.
timing Timing required When the event moved through Flow, to measure Flow's overhead
(delivered_at minus received_at).
Show child attributes
received_at string<date-time> required When Flow received it from the channel, or when it happened inside Flow.
stored_at string<date-time> required When it was committed to the log.
delivered_at string<date-time> When the event went out to a webhook endpoint. Its value depends on
where you read it. In a webhook delivery's body, it is when that delivery
attempt started (each attempt to each endpoint gets its own), unless an
endpoint had already accepted the event, in which case it is that
acceptance time. In the event log (GET /v0/events,
GET /v0/events/{event_id}, the stream), it is when the first endpoint
accepted the event, and it is absent until one has (always, for an app
with no webhook endpoints).
type enum<string> required Always conversation.window_closing.
data WindowClosingData required When the window closes.
Show child attributes
window_open_until string<date-time> required After this time only templates can be sent.
type: sender.status_changed
A sender's status or WhatsApp quality rating changed.
id string required An event ID, evt_ and a ULID.
created_at string<date-time> required When the event was created. Events are ordered in the log by commit, not by this time.
app string required An app ID, app_ and a ULID.
livemode boolean required Whether the event belongs to live mode.
conversation ConversationRef The conversation an event happened in, inlined so most handlers need no extra call. A group's participants are not inlined; read the conversation for them.
Show child attributes
id string required A conversation ID, conv_ and a ULID.
type enum<string> Flow always sends it; read a missing one as direct (answers from
before groups existed). direct: one sender and one contact (every
channel). group: a group chat
one of your dedicated iMessage lines was added to; it has group and no
contact. Flow's shared iMessage line ignores group chats, so they never
reach your app from it.
channel enum<string> required A messaging channel. sms is US SMS and MMS (in pilot), from dedicated numbers only.
sender string required A sender ID, snd_ and a ULID.
contact string The contact the conversation is with. Always set when type is direct; absent on group conversations (the message's from says who wrote).
group_name string Group conversations only. The group chat's name, when it has one.
window_open_until string<date-time> WhatsApp only. Until when free-form messages may be sent. Absent when closed or not applicable.
simulated boolean read-only true for a simulated conversation, whose contact exists only inside Flow (test mode only, from POST /v0/test/conversations); absent otherwise.
timing Timing required When the event moved through Flow, to measure Flow's overhead
(delivered_at minus received_at).
Show child attributes
received_at string<date-time> required When Flow received it from the channel, or when it happened inside Flow.
stored_at string<date-time> required When it was committed to the log.
delivered_at string<date-time> When the event went out to a webhook endpoint. Its value depends on
where you read it. In a webhook delivery's body, it is when that delivery
attempt started (each attempt to each endpoint gets its own), unless an
endpoint had already accepted the event, in which case it is that
acceptance time. In the event log (GET /v0/events,
GET /v0/events/{event_id}, the stream), it is when the first endpoint
accepted the event, and it is absent until one has (always, for an app
with no webhook endpoints).
type enum<string> required Always sender.status_changed.
data SenderStatusChangedData required The sender as it is now, and what changed.
Show child attributes
sender Sender required What your agent talks from: a Telegram bot, a WhatsApp number, an iMessage
line or a US SMS number. shared senders are Flow's sandbox, used by many apps in test mode;
dedicated senders are yours alone; simulated senders exist only in test
mode, for simulated conversations. Each sender has its own limits and warm-up
state.
Show child attributes
id string required A sender ID, snd_ and a ULID.
channel enum<string> required A messaging channel. sms is US SMS and MMS (in pilot), from dedicated numbers only.
kind enum<string> required shared (the sandbox), dedicated (yours) or simulated (test mode only:
your app's stand-in for a channel, used by simulated conversations from
POST /v0/test/conversations; it never touches a network).
livemode boolean required false for sandbox and simulated senders, true for dedicated senders used in live mode.
display_name string The name contacts see, where the channel shows one.
status enum<string> required pending: requested, being provisioned.active: sending normally.warming_up: active, with a new-contact budget that grows day by day.throttled: the gate slowed it after an abuse signal; it recovers by itself.flagged: the channel or Flow flagged it; starts are paused. A Telegram bot is alsoflaggedwhen Telegram rejects its token (revoked in @BotFather): it then sends nothing, new sends answer403 permission, and queued messages wait until you connect the bot again with its new token (POST /v0/senders). They wait at most 72 hours after Flow accepted them; older ones fail withoutside_window(channel_codequeued_too_long) inmessage.failedinstead of going out late.banned: it cannot send or receive: the channel banned it, you disconnected it (DELETE /v0/senders/{sender_id}), or its bot or line was connected to another app.
throttled_until string<date-time> With status throttled, when starts are allowed again. Recovery is automatic.
address SenderAddress required How contacts reach the sender. Which fields are set depends on the channel.
limits SenderLimits required The sender's current budget for starting conversations, and how fast it sends.
quality_rating enum<string> WhatsApp only. Meta's quality rating for the number.
sms SmsSender read-only SMS senders only: how the number is registered with US carriers and what it
can do. An SMS number stays pending until its registration is approved
(carriers block traffic from unregistered numbers).
imessage ImessageSender read-only iMessage senders only: what the line can do beyond replying in iMessage. Set by the Flow team (ask them to change it); absent on other channels.
setup SenderSetup read-only Only in the answer to POST /v0/senders that connected a WhatsApp number:
what you still set in your own Meta app, which Flow cannot set for you.
Flow already points the number's webhooks at callback_url (Meta's
phone-number-level override); entering the same URL in your app makes its
field subscriptions apply. The verify token is shown only in this answer
(connecting the number again shows it again); keep it like a password.
join_code string Shared senders only. The whole message a contact sends to this sender to join your app: Hi , the app's name (there when it has none), then the app's sandbox_join_code (from GET /v0/app) in brackets after ref, for example Hi Sandbox app (ref brave-otter-40718263). Show it to testers as is; a link that fills it in (wa.me, sms:) carries it URL-encoded. Any message of at most 160 characters that ends with (ref <code>) joins, so a tester may change the words before the brackets.
created_at string<date-time> required When the sender was created.
previous_status enum<string> required pending: requested, being provisioned.active: sending normally.warming_up: active, with a new-contact budget that grows day by day.throttled: the gate slowed it after an abuse signal; it recovers by itself.flagged: the channel or Flow flagged it; starts are paused. A Telegram bot is alsoflaggedwhen Telegram rejects its token (revoked in @BotFather): it then sends nothing, new sends answer403 permission, and queued messages wait until you connect the bot again with its new token (POST /v0/senders). They wait at most 72 hours after Flow accepted them; older ones fail withoutside_window(channel_codequeued_too_long) inmessage.failedinstead of going out late.banned: it cannot send or receive: the channel banned it, you disconnected it (DELETE /v0/senders/{sender_id}), or its bot or line was connected to another app.
reason string required Why, in one sentence (for example "Many new conversations got no reply; starts are slowed for 24 hours").
type: template.status_changed
Meta approved, rejected or paused a template.
id string required An event ID, evt_ and a ULID.
created_at string<date-time> required When the event was created. Events are ordered in the log by commit, not by this time.
app string required An app ID, app_ and a ULID.
livemode boolean required Whether the event belongs to live mode.
conversation ConversationRef The conversation an event happened in, inlined so most handlers need no extra call. A group's participants are not inlined; read the conversation for them.
Show child attributes
id string required A conversation ID, conv_ and a ULID.
type enum<string> Flow always sends it; read a missing one as direct (answers from
before groups existed). direct: one sender and one contact (every
channel). group: a group chat
one of your dedicated iMessage lines was added to; it has group and no
contact. Flow's shared iMessage line ignores group chats, so they never
reach your app from it.
channel enum<string> required A messaging channel. sms is US SMS and MMS (in pilot), from dedicated numbers only.
sender string required A sender ID, snd_ and a ULID.
contact string The contact the conversation is with. Always set when type is direct; absent on group conversations (the message's from says who wrote).
group_name string Group conversations only. The group chat's name, when it has one.
window_open_until string<date-time> WhatsApp only. Until when free-form messages may be sent. Absent when closed or not applicable.
simulated boolean read-only true for a simulated conversation, whose contact exists only inside Flow (test mode only, from POST /v0/test/conversations); absent otherwise.
timing Timing required When the event moved through Flow, to measure Flow's overhead
(delivered_at minus received_at).
Show child attributes
received_at string<date-time> required When Flow received it from the channel, or when it happened inside Flow.
stored_at string<date-time> required When it was committed to the log.
delivered_at string<date-time> When the event went out to a webhook endpoint. Its value depends on
where you read it. In a webhook delivery's body, it is when that delivery
attempt started (each attempt to each endpoint gets its own), unless an
endpoint had already accepted the event, in which case it is that
acceptance time. In the event log (GET /v0/events,
GET /v0/events/{event_id}, the stream), it is when the first endpoint
accepted the event, and it is absent until one has (always, for an app
with no webhook endpoints).
type enum<string> required Always template.status_changed.
data TemplateStatusChangedData required The template as it is now, and what changed.
Show child attributes
template Template required A WhatsApp message template, mirrored from Meta with its approval status.
Show child attributes
id string required A template ID, tpl_ and a ULID.
sender string required A sender ID, snd_ and a ULID.
name string required The template's name at Meta.
language string required The template's language, as Meta names it.
category enum<string> required Meta's category for the template, which sets its price.
status enum<string> required A template's approval status at Meta.
components TemplateComponent[] required The template's parts, as submitted.
rejection_reason string Meta's reason, when the template was rejected.
created_at string<date-time> required When the template was submitted.
previous_status enum<string> required A template's approval status at Meta.
reason string Meta's reason, when it gives one.
type: contact.deleted
A contact was deleted with DELETE /v0/contacts/{contact_id}: their address,
messages, files and events are erased at Flow. Delete what you keep about
them too. Sent once, for the first delete; it has no conversation.
id string required An event ID, evt_ and a ULID.
created_at string<date-time> required When the event was created. Events are ordered in the log by commit, not by this time.
app string required An app ID, app_ and a ULID.
livemode boolean required Whether the event belongs to live mode.
conversation ConversationRef The conversation an event happened in, inlined so most handlers need no extra call. A group's participants are not inlined; read the conversation for them.
Show child attributes
id string required A conversation ID, conv_ and a ULID.
type enum<string> Flow always sends it; read a missing one as direct (answers from
before groups existed). direct: one sender and one contact (every
channel). group: a group chat
one of your dedicated iMessage lines was added to; it has group and no
contact. Flow's shared iMessage line ignores group chats, so they never
reach your app from it.
channel enum<string> required A messaging channel. sms is US SMS and MMS (in pilot), from dedicated numbers only.
sender string required A sender ID, snd_ and a ULID.
contact string The contact the conversation is with. Always set when type is direct; absent on group conversations (the message's from says who wrote).
group_name string Group conversations only. The group chat's name, when it has one.
window_open_until string<date-time> WhatsApp only. Until when free-form messages may be sent. Absent when closed or not applicable.
simulated boolean read-only true for a simulated conversation, whose contact exists only inside Flow (test mode only, from POST /v0/test/conversations); absent otherwise.
timing Timing required When the event moved through Flow, to measure Flow's overhead
(delivered_at minus received_at).
Show child attributes
received_at string<date-time> required When Flow received it from the channel, or when it happened inside Flow.
stored_at string<date-time> required When it was committed to the log.
delivered_at string<date-time> When the event went out to a webhook endpoint. Its value depends on
where you read it. In a webhook delivery's body, it is when that delivery
attempt started (each attempt to each endpoint gets its own), unless an
endpoint had already accepted the event, in which case it is that
acceptance time. In the event log (GET /v0/events,
GET /v0/events/{event_id}, the stream), it is when the first endpoint
accepted the event, and it is absent until one has (always, for an app
with no webhook endpoints).
type enum<string> required Always contact.deleted.
data ContactDeletedData required The contact that was deleted. Only the ID is kept.
Show child attributes
contact string required A contact ID, ct_ and a ULID.
type: contact.left
A person on a shared sender (the sandbox) joined another app, or the other
mode of your app, by sending its join message. Nothing more reaches you from
them, and sends to them fail with permission (channel_code
sandbox_contact_left), until they send your join message again, which starts
a new conversation.started. The contact and their history are kept;
conversation is the conversation they left.
id string required An event ID, evt_ and a ULID.
created_at string<date-time> required When the event was created. Events are ordered in the log by commit, not by this time.
app string required An app ID, app_ and a ULID.
livemode boolean required Whether the event belongs to live mode.
conversation ConversationRef required The conversation an event happened in, inlined so most handlers need no extra call. A group's participants are not inlined; read the conversation for them.
Show child attributes
id string required A conversation ID, conv_ and a ULID.
type enum<string> Flow always sends it; read a missing one as direct (answers from
before groups existed). direct: one sender and one contact (every
channel). group: a group chat
one of your dedicated iMessage lines was added to; it has group and no
contact. Flow's shared iMessage line ignores group chats, so they never
reach your app from it.
channel enum<string> required A messaging channel. sms is US SMS and MMS (in pilot), from dedicated numbers only.
sender string required A sender ID, snd_ and a ULID.
contact string The contact the conversation is with. Always set when type is direct; absent on group conversations (the message's from says who wrote).
group_name string Group conversations only. The group chat's name, when it has one.
window_open_until string<date-time> WhatsApp only. Until when free-form messages may be sent. Absent when closed or not applicable.
simulated boolean read-only true for a simulated conversation, whose contact exists only inside Flow (test mode only, from POST /v0/test/conversations); absent otherwise.
timing Timing required When the event moved through Flow, to measure Flow's overhead
(delivered_at minus received_at).
Show child attributes
received_at string<date-time> required When Flow received it from the channel, or when it happened inside Flow.
stored_at string<date-time> required When it was committed to the log.
delivered_at string<date-time> When the event went out to a webhook endpoint. Its value depends on
where you read it. In a webhook delivery's body, it is when that delivery
attempt started (each attempt to each endpoint gets its own), unless an
endpoint had already accepted the event, in which case it is that
acceptance time. In the event log (GET /v0/events,
GET /v0/events/{event_id}, the stream), it is when the first endpoint
accepted the event, and it is absent until one has (always, for an app
with no webhook endpoints).
type enum<string> required Always contact.left.
data ContactLeftData required The contact who left, the shared sender they left, and why.
Show child attributes
contact string required A contact ID, ct_ and a ULID.
sender string required A sender ID, snd_ and a ULID.
reason enum<string> required Why the contact left. joined_other_app: they sent the join message of
another app on the shared sender, or of your app's other mode (test or live).
type: call.received
iMessage only. A contact tried to FaceTime one of your lines. The line
cannot take calls, so nothing is answered. conversation is the direct conversation with the contact, made if it is
new (with conversation.started, via inbound). A call is not a
message: it does not count toward plan caps on people, and it does not
open the conversation for a reply-only line (a send there before the
contact writes is a start, refused with 403 permission unless
Sender.imessage.can_initiate is on). Phone calls forwarded by
Sender.imessage.call_forwarding send no event. Delivered only to webhook
endpoints that list call.received in events.
id string required An event ID, evt_ and a ULID.
created_at string<date-time> required When the event was created. Events are ordered in the log by commit, not by this time.
app string required An app ID, app_ and a ULID.
livemode boolean required Whether the event belongs to live mode.
conversation ConversationRef required The conversation an event happened in, inlined so most handlers need no extra call. A group's participants are not inlined; read the conversation for them.
Show child attributes
id string required A conversation ID, conv_ and a ULID.
type enum<string> Flow always sends it; read a missing one as direct (answers from
before groups existed). direct: one sender and one contact (every
channel). group: a group chat
one of your dedicated iMessage lines was added to; it has group and no
contact. Flow's shared iMessage line ignores group chats, so they never
reach your app from it.
channel enum<string> required A messaging channel. sms is US SMS and MMS (in pilot), from dedicated numbers only.
sender string required A sender ID, snd_ and a ULID.
contact string The contact the conversation is with. Always set when type is direct; absent on group conversations (the message's from says who wrote).
group_name string Group conversations only. The group chat's name, when it has one.
window_open_until string<date-time> WhatsApp only. Until when free-form messages may be sent. Absent when closed or not applicable.
simulated boolean read-only true for a simulated conversation, whose contact exists only inside Flow (test mode only, from POST /v0/test/conversations); absent otherwise.
timing Timing required When the event moved through Flow, to measure Flow's overhead
(delivered_at minus received_at).
Show child attributes
received_at string<date-time> required When Flow received it from the channel, or when it happened inside Flow.
stored_at string<date-time> required When it was committed to the log.
delivered_at string<date-time> When the event went out to a webhook endpoint. Its value depends on
where you read it. In a webhook delivery's body, it is when that delivery
attempt started (each attempt to each endpoint gets its own), unless an
endpoint had already accepted the event, in which case it is that
acceptance time. In the event log (GET /v0/events,
GET /v0/events/{event_id}, the stream), it is when the first endpoint
accepted the event, and it is absent until one has (always, for an app
with no webhook endpoints).
type enum<string> required Always call.received.
data CallData required The call the contact tried to make.
Show child attributes
call Call required One call attempt to an iMessage line.
Show child attributes
contact string required The contact who called.
network enum<string> required How they called. facetime (a FaceTime call) today.
type: ack
Server to client. A send or start frame was accepted.
type enum<string> required Always ack.
ref string required The ref of the frame being answered.
message Message required One message in or out of a conversation, with its kind and content and its delivery status. What you send is what the contact gets: Flow never converts a message into another kind.
Show child attributes
id string required A message ID, msg_ and a ULID.
conversation string required A conversation ID, conv_ and a ULID.
direction enum<string> required in from the contact, out from your app.
from string read-only Inbound messages only, on every channel. The contact who wrote it; in a group conversation, which participant. Absent on your own messages.
status enum<string> required received: inbound from the contact.queued: accepted by the gate, waiting for its turn in the conversation.sent: the channel accepted it.delivered: it reached the contact's device, where the channel reports this.read: the contact read it, where the channel reports this.failed: it could not be sent; seeerror.unsent: you unsent it.
content MessageContent required A message's content: one kind object, in either direction, with kind
(always present here) saying which. It is exactly what was sent or
received, except metadata, which is kept once, at the message's own
metadata, and not repeated here.
Show child attributes
kind enum<string> required The kinds of message. A closed list: a new kind arrives only with a new API
version. Each kind has its own schema (BasicMessage, VoiceMessage, ...)
and, where it can be sent over HTTP, its own endpoint of the same name
(POST /v0/conversations/{conversation_id}/<kind>, with contact-card for
contact_card). edit and unsend are sent over HTTP with PATCH and
DELETE /v0/messages/{message_id}, and are kinds in webhook replies, on
the stream and in a message's content. button_reply and file_blocked
are inbound only.
Received from a contact: basic, voice, button_reply, reaction,
location, contact_card and file_blocked. Sent: every kind but
button_reply and file_blocked.
One of 16 options, chosen by `kind`:
kind: basic
A basic message with kind given, as in a message's content, a webhook reply or a stream frame (BasicMessage with kind required).
kind string required Always basic. Optional on POST /v0/conversations/{conversation_id}/basic; required where no URL names the kind (webhook replies, the stream, starts, a message's content).
text string The text, sent exactly as written: no markdown, no stripping. At most
4096 UTF-16 code units on Telegram (an emoji such as 😀 counts as 2),
4096 characters on WhatsApp, 9999 on iMessage, and 10 segments on SMS
(see SmsSegments). With media it is the caption, at most 1024 (UTF-16
code units on Telegram). Optional when media is given. Longer text is
refused with 400 invalid_request.
media Media One image, video, document or audio file. When sending, give exactly one of
url (an HTTPS URL Flow fetches; Flow neither stores nor scans it, and on
Telegram a document by URL works for PDF and ZIP files) or file_id (from
POST /v0/files). Each channel takes files up to its own size: Telegram
50 MB, WhatsApp and iMessage 100 MB, SMS (MMS) 5 MB; a larger file answers
422 unsupported_content with param media. SMS carries images, audio
and video, never documents (param media.type), and only from numbers
with MMS (channel_code no_mms).
Show child attributes
type enum<string> required What kind of file it is.
url string<uri> Where the bytes are. Sending, an HTTPS URL Flow can fetch; inbound, a Flow file URL.
file_id string A file ID, file_ and a ULID.
filename string The file's name, shown for documents and taken only with type document (on another type it answers 422 unsupported_content with param media.filename). On Telegram it works only with file_id: Telegram names a document sent by url itself, so filename with url answers 422 unsupported_content with param media.filename.
mime_type string read-only Inbound only. The real type, checked from the file's first bytes.
size_bytes integer read-only Inbound only. The file's size.
reply_to string Send this as an inline reply to an earlier message in the same
conversation (Telegram, WhatsApp and iMessage show it quoted). SMS has no
inline replies: there it answers 422 unsupported_content with param
reply_to. Refused with 404 not_found (param reply_to) when the
target is not a message in this conversation, and with 400 invalid_request (param reply_to) when the target never reached the
channel (for example, it failed). If the target fails after the send was
accepted, the reply goes out as a normal message.
link_preview boolean Whether links in the text show a rich preview. Left out, the channel's
default applies (Telegram, WhatsApp and iMessage show previews).
false sends links as plain text. SMS never shows previews, and no
channel shows one on a media message: false is accepted there, and
true on SMS or with media answers 422 unsupported_content with
param link_preview.
telegram BasicTelegramOptions Telegram only: how the message looks and notifies on Telegram. On another
channel the block answers 422 unsupported_content with param
telegram; an unknown key 400 invalid_request.
Show child attributes
parse_mode enum<string> How Telegram reads the text: HTML or MarkdownV2, Telegram's own
formatting (see Telegram's Bot API, "Formatting options"). Without it the
text is plain. Text that Telegram cannot parse is refused by Telegram:
the message fails (message.failed, channel_error).
disable_notification boolean Deliver silently, with no sound.
protect_content boolean Stop the contact forwarding or saving the message.
show_caption_above_media boolean Media only. Show the caption above the photo or video instead of below it.
has_spoiler boolean Media only (photos and videos). Cover it with a spoiler animation until tapped.
imessage BasicImessageOptions iMessage only: styles and a subject line. On another channel the block
answers 422 unsupported_content with param imessage; an unknown key
400 invalid_request. A message that goes by SMS or RCS (see
Message.network) shows neither.
Show child attributes
styles ImessageStyle[] Styled ranges of text (see ImessageStyle). Each range must lie within text; one that does not answers 400 invalid_request naming it (imessage.styles[0]).
subject string iMessage's subject line, shown in bold above the text.
metadata Metadata Up to 20 string pairs of your own, kept with the object and returned unchanged. Keys up to 40 characters, values up to 500.
kind: voice
A voice message with kind given, as in a message's content, a webhook reply or a stream frame (VoiceMessage with kind required).
kind string required Always voice. Optional on its endpoint; required where no URL names the kind.
url string<uri> Where the audio is. Sending, an HTTPS URL Flow can fetch; inbound, a Flow file URL.
file_id string A file ID, file_ and a ULID.
reply_to string Send this as an inline reply to an earlier message in the same
conversation (Telegram, WhatsApp and iMessage show it quoted). SMS has no
inline replies: there it answers 422 unsupported_content with param
reply_to. Refused with 404 not_found (param reply_to) when the
target is not a message in this conversation, and with 400 invalid_request (param reply_to) when the target never reached the
channel (for example, it failed). If the target fails after the send was
accepted, the reply goes out as a normal message.
metadata Metadata Up to 20 string pairs of your own, kept with the object and returned unchanged. Keys up to 40 characters, values up to 500.
duration_seconds number read-only Inbound only. The voice note's length.
transcript string read-only Inbound only, on iMessage, when iMessage made a transcript of the voice note. What was said.
kind: buttons
A buttons message with kind given, as in a message's content, a webhook reply or a stream frame (ButtonsMessage with kind required).
kind string required Always buttons. Optional on its endpoint; required where no URL names the kind.
header ContentHeader What is shown above a buttons message's text, or a carousel card's image or
video: an image, a video, a document or a short title. A media header
takes exactly one of url (an HTTPS URL Flow fetches) or file_id (from
POST /v0/files), and is fetched and checked for the channel like a basic
message's media (so WebP and other formats work); a text header takes
text. Anything else, or a value over a limit, is refused with 400 invalid_request naming the field in param.
WhatsApp shows the header at the top of the same message bubble. Telegram
sends one message: the media with the text as its caption and the buttons
under it; a text header is the message's first line, in bold.
Show child attributes
type enum<string> required What the header holds.
url string<uri> Media headers only. An HTTPS URL of the media, which Flow fetches.
file_id string A file ID, file_ and a ULID.
text string text headers only. The title, 1 to 60 characters (WhatsApp's header limit).
filename string document headers only. The file's name, shown with the document.
text string required The text above the buttons, sent exactly as written: at most 4096 UTF-16
code units on Telegram, or 1024 when the message has a media header
(Telegram's caption limit), and 1024 characters on WhatsApp (its limit
for messages with buttons). Longer text is refused with 400 invalid_request.
buttons Button[] required The buttons, in order: 1 to 10 on Telegram; on WhatsApp 1 to 3 reply
buttons or exactly one URL button (anything else answers 422 unsupported_content with param buttons; send a list for more
choices).
Show child attributes
One of 2 options:
ReplyButton
A button that sends its id back as a button_reply when tapped.
id string required Your ID for the button, returned in button_reply as button_id: 1 to 64
bytes (Telegram's limit for a button's data), so at most 64 ASCII
characters and fewer for other characters. Labels may repeat; the ID is
what tells the taps apart, so buttons that share a label (a "Details" on
every card) get their own IDs, such as details:listing-17.
label string required The button's text. WhatsApp shows at most 20 characters.
UrlButton
A button that opens a link.
url string<uri> required The HTTPS link to open.
label string required The button's text.
reply_to string Send this as an inline reply to an earlier message in the same
conversation (Telegram, WhatsApp and iMessage show it quoted). SMS has no
inline replies: there it answers 422 unsupported_content with param
reply_to. Refused with 404 not_found (param reply_to) when the
target is not a message in this conversation, and with 400 invalid_request (param reply_to) when the target never reached the
channel (for example, it failed). If the target fails after the send was
accepted, the reply goes out as a normal message.
telegram ButtonsTelegramOptions Telegram only: how the message looks and notifies on Telegram. On another
channel the block answers 422 unsupported_content with param
telegram; an unknown key 400 invalid_request.
Show child attributes
parse_mode enum<string> How Telegram reads the text: HTML or MarkdownV2, Telegram's own
formatting (see Telegram's Bot API, "Formatting options"). Without it the
text is plain. Text that Telegram cannot parse is refused by Telegram:
the message fails (message.failed, channel_error).
disable_notification boolean Deliver silently, with no sound.
protect_content boolean Stop the contact forwarding or saving the message.
metadata Metadata Up to 20 string pairs of your own, kept with the object and returned unchanged. Keys up to 40 characters, values up to 500.
kind: list
A list message with kind given, as in a message's content, a webhook reply or a stream frame (ListMessage with kind required).
kind string required Always list. Optional on its endpoint; required where no URL names the kind.
header string A title shown above the text, 1 to 60 characters.
text string required The message's text, sent exactly as written, 1 to 4096 characters.
button string required The label of the button that opens the list, 1 to 20 characters.
sections ListSection[] required 1 to 10 sections, in order, with at most 10 rows in all.
Show child attributes
title string The section's title, 1 to 24 characters. Required when the list has more than one section.
rows ListRow[] required The section's rows, in order.
reply_to string Send this as an inline reply to an earlier message in the same
conversation (Telegram, WhatsApp and iMessage show it quoted). SMS has no
inline replies: there it answers 422 unsupported_content with param
reply_to. Refused with 404 not_found (param reply_to) when the
target is not a message in this conversation, and with 400 invalid_request (param reply_to) when the target never reached the
channel (for example, it failed). If the target fails after the send was
accepted, the reply goes out as a normal message.
metadata Metadata Up to 20 string pairs of your own, kept with the object and returned unchanged. Keys up to 40 characters, values up to 500.
kind: carousel
A carousel message with kind given, as in a message's content, a webhook reply or a stream frame (CarouselMessage with kind required).
kind string required Always carousel. Optional on its endpoint; required where no URL names the kind.
text string required The message text shown above the cards, 1 to 1024 characters. Longer text is refused with 400 invalid_request.
cards CarouselCard[] required 2 to 10 cards, in the order shown (left to right).
Show child attributes
header ContentHeader required The card's image or video, a ContentHeader of type image or video only (by url or file_id); any other type is refused with 400 invalid_request at cards[i].header.type.
text string The card's text, at most 160 characters and 2 line breaks.
buttons Button[] required Exactly one URL button, or 1 to 2 reply buttons; the same number and kind on every card. Labels are at most 20 characters.
metadata Metadata Up to 20 string pairs of your own, kept with the object and returned unchanged. Keys up to 40 characters, values up to 500.
kind: template
A template message with kind given, as in a message's content, a webhook reply or a stream frame (TemplateMessage with kind required).
kind string required Always template. Optional on its endpoint; required where no URL names the kind (and on POST /v0/messages).
template_id string required A template ID, tpl_ and a ULID.
language string required The template language to use, as Meta names it (en, en_US, hi).
params TemplateParams Values for the template's placeholders.
Show child attributes
header TemplateHeaderParam The value of a template's header, when it has a placeholder or media.
body string[] Values for the body's {{1}}, {{2}}, ... in order.
buttons TemplateButtonParam[] Values for buttons that take one (URL suffixes, quick-reply payloads).
cards TemplateCardParams[] Carousel templates only. Values for each card's header, body placeholders and buttons.
reply_to string Send this as an inline reply to an earlier message in the same
conversation (Telegram, WhatsApp and iMessage show it quoted). SMS has no
inline replies: there it answers 422 unsupported_content with param
reply_to. Refused with 404 not_found (param reply_to) when the
target is not a message in this conversation, and with 400 invalid_request (param reply_to) when the target never reached the
channel (for example, it failed). If the target fails after the send was
accepted, the reply goes out as a normal message.
metadata Metadata Up to 20 string pairs of your own, kept with the object and returned unchanged. Keys up to 40 characters, values up to 500.
kind: contact_card
A contact_card message with kind given, as in a message's content, a webhook reply or a stream frame (ContactCardMessage with kind required).
kind string required Always contact_card. Optional on its endpoint; required where no URL names the kind.
name string required The person's or business's name.
phones string[] Phone numbers, E.164 where known. Telegram needs at least one (else 422 unsupported_content, param phones).
emails string<email>[] Email addresses.
reply_to string Send this as an inline reply to an earlier message in the same
conversation (Telegram, WhatsApp and iMessage show it quoted). SMS has no
inline replies: there it answers 422 unsupported_content with param
reply_to. Refused with 404 not_found (param reply_to) when the
target is not a message in this conversation, and with 400 invalid_request (param reply_to) when the target never reached the
channel (for example, it failed). If the target fails after the send was
accepted, the reply goes out as a normal message.
metadata Metadata Up to 20 string pairs of your own, kept with the object and returned unchanged. Keys up to 40 characters, values up to 500.
kind: location
A location message with kind given, as in a message's content, a webhook reply or a stream frame (LocationMessage with kind required).
kind string required Always location. Optional on its endpoint; required where no URL names the kind.
lat number required Latitude in degrees.
lng number required Longitude in degrees.
name string The place's name.
address string The place's address.
reply_to string Send this as an inline reply to an earlier message in the same
conversation (Telegram, WhatsApp and iMessage show it quoted). SMS has no
inline replies: there it answers 422 unsupported_content with param
reply_to. Refused with 404 not_found (param reply_to) when the
target is not a message in this conversation, and with 400 invalid_request (param reply_to) when the target never reached the
channel (for example, it failed). If the target fails after the send was
accepted, the reply goes out as a normal message.
metadata Metadata Up to 20 string pairs of your own, kept with the object and returned unchanged. Keys up to 40 characters, values up to 500.
kind: effect
An effect message with kind given, as in a message's content, a webhook reply or a stream frame (EffectMessage with kind required).
kind string required Always effect. Optional on its endpoint; required where no URL names the kind.
text string required The text, sent exactly as written, at most 9999 characters. Longer text is refused with 400 invalid_request.
effect enum<string> required The effect.
reply_to string Send this as an inline reply to an earlier message in the same
conversation (Telegram, WhatsApp and iMessage show it quoted). SMS has no
inline replies: there it answers 422 unsupported_content with param
reply_to. Refused with 404 not_found (param reply_to) when the
target is not a message in this conversation, and with 400 invalid_request (param reply_to) when the target never reached the
channel (for example, it failed). If the target fails after the send was
accepted, the reply goes out as a normal message.
metadata Metadata Up to 20 string pairs of your own, kept with the object and returned unchanged. Keys up to 40 characters, values up to 500.
kind: reaction
A reaction message with kind given, as in a message's content, a webhook reply or a stream frame (ReactionMessage with kind required).
kind string required Always reaction. Optional on its endpoint; required where no URL names the kind.
message_id string required A message ID, msg_ and a ULID.
emoji string | null required One emoji, or null to remove the reaction. On Telegram only its reaction emoji (listed on the endpoint); any other answers 422 unsupported_content with param emoji.
telegram ReactionTelegramOptions Telegram only. On another channel the block answers 422 unsupported_content with param telegram; an unknown key 400 invalid_request.
Show child attributes
is_big boolean Show the reaction with a large animation.
kind: typing
A typing message with kind given, as in a message's content, a webhook reply or a stream frame (TypingMessage with kind required).
kind string required Always typing. Optional on its endpoint; required where no URL names the kind.
imessage TypingImessageOptions iMessage only. On another channel the block answers 422 unsupported_content with param imessage; an unknown key 400 invalid_request.
Show child attributes
duration_seconds integer How long to show the indicator, 1 to 60 seconds, unless a message goes first.
kind: read
A read message with kind given, as in a message's content, a webhook reply or a stream frame (ReadMessage with kind required).
kind string required Always read. Optional on its endpoint; required where no URL names the kind.
up_to string WhatsApp only. The latest inbound message to mark read (default the latest inbound message). iMessage marks the whole conversation read, so up_to there answers 422 unsupported_content with param up_to.
kind: edit
A change to the text of one of your sent messages, as a kind: a piece of a
webhook reply or on the stream, and the content of the message that
carries an edit (PATCH /v0/messages/{message_id} over HTTP, whose rules
apply). message_id names the message to change.
text string required The new text: at most 4096 UTF-16 code units on Telegram (1024 when it
replaces a media message's caption) and 9999 characters on iMessage.
Longer text is refused with 400 invalid_request.
telegram EditTelegramOptions Telegram only. How Telegram reads the new text. An unknown key is refused with 400 invalid_request.
Show child attributes
parse_mode enum<string> How Telegram reads the text: HTML or MarkdownV2, Telegram's own
formatting (see Telegram's Bot API, "Formatting options"). Without it the
text is plain. Text that Telegram cannot parse is refused by Telegram:
the message fails (message.failed, channel_error).
imessage EditImessageOptions iMessage only. Styles for the new text. An unknown key is refused with 400 invalid_request.
Show child attributes
styles ImessageStyle[] Styled ranges of the new text (see ImessageStyle).
kind string required Always edit.
message_id string required A message ID, msg_ and a ULID.
kind: unsend
Removes one of your sent messages from the contact's chat, as a kind: a
piece of a webhook reply or on the stream, and the content of the message
that carries an unsend (DELETE /v0/messages/{message_id} over HTTP,
whose rules apply).
kind string required Always unsend.
message_id string required A message ID, msg_ and a ULID.
kind: button_reply
Inbound only. The contact tapped a reply button of a buttons or carousel message, or a row of a list.
kind string required Always button_reply.
button_id string required The id of the tapped button or list row.
label string required The tapped button's label, or the list row's title.
message_id string A message ID, msg_ and a ULID.
kind: file_blocked
Inbound only. The contact sent a file that Flow did not keep: it failed the
malware scan, its real type is one Flow does not pass on (programs, web
pages), it could not be scanned, or it is larger than the channel lets Flow
download. The file is not stored and has no url; what is known about it is
here. Images, audio and video are not scanned; documents (PDF, Office,
archives) are, and one Flow could not scan arrives with reason
scan_failed.
kind string required Always file_blocked.
reason enum<string> required Why the file was not kept: malware (the scan found something), type_not_allowed, scan_failed (the scanner could not check it; ask the contact to send it again), or too_large.
media_type enum<string> What kind of file the channel said it was.
filename string The file's name, if it had one.
mime_type string The real type, read from the file's first bytes, when Flow read them.
size_bytes integer The file's size, when known.
caption string Text the contact sent with the file.
network enum<string> read-only iMessage channel only: the network that carried the message. imessage,
or sms or rcs when the line has the SMS/RCS add-on
(Sender.imessage.fallback) and the contact could not get iMessage. The
network is chosen for each message when it is sent, never by your app.
Set on inbound messages, and on your messages once the channel reports it
(in message.sent, message.delivered or message.failed, as
data.message.network); absent before that and on other channels. A
message that went by sms or rcs showed no imessage.styles, no
imessage.subject and no effect, and edits and unsends of it answer 422 unsupported_content with channel_code not_on_sms.
error ErrorBody The details of an error.
Show child attributes
type enum<string> required What went wrong. A closed list: new types arrive only with a new API version.
Each type has a page at https://api.flow.engineer/docs/errors/<type>, given in
the error's doc_url; the error's hint says what to change for the case.
invalid_request(400, or 413 for a request body over the size limit): the request is malformed or a parameter is invalid. Fix the parameter named inparam; thehintsays what it must look like. Every endpoint answers it for aFlow-Versionheader that is not a version this API serves.authentication(401): the API key is missing, malformed, unknown, revoked or expired. AnAuthorization: Bearerheader with nothing after it usually means the environment variable holding the key was empty when the header was built (for an MCP client, when the server was added): thehintsays so. SendAuthorization: Bearer fk_test_...orfk_live_...with a current key; with no key at all, get a test key withPOST /v0/sandbox/keys. A sandbox key past itsexpires_athaschannel_codesandbox_key_expired: sign in to claim the app, or get a new key.permission(403): the key may not do this, for example a test key using a live sender, a sandbox contact who joined another app, a new conversation from an iMessage line that may only reply, or a send past the app's sandbox allowance (channel_codesandbox_allowance_used,sandbox_contact_limitorsandbox_channel_not_included). Use the key of the right mode, have the contact send your join message, wait for the contact to message the line first, or sign in through the device flow to lift the allowance. On SMS:contact_opted_out(the contact texted STOP; nothing more goes to them until they text START),consent_required(a start to someone who never texted your numbers: record their consent withconsenton the start). In a HIPAA account:hipaa_channel_not_covered(the channel has no business associate agreement; use SMS). Simulated conversations:simulated_contact(a real sender was pointed at a simulated contact; reply in the contact's simulated conversation),not_simulated(a simulated sender or test endpoint was pointed at a real contact),test_mode_only,simulated_conversation_limit. Inmessage.failed:sandbox_contact_left(the contact joined another app on the sandbox sender while the message was queued; have them send your join message again).not_found(404): no such object for this app and mode. Check the ID's prefix and that it was made with a key of the same mode (test and live data are separate).idempotency_conflict(409): the idempotency key was used for a different request (channel_codebody_mismatch: use a new key), or that request is still running (in_progress: waitretry_afterseconds and repeat it with the same key), or it already created a secret that is shown only once (secret_not_kept: creating a webhook endpoint, rotating its secret, connecting a WhatsApp number; a secret that was lost must be rotated again, and a WhatsApp number connected again to see its verify token).outside_window(409, or inmessage.failed): the channel will not deliver outside its conversation window. On WhatsApp the 24-hour window is closed: send a template (POST /v0/conversations/{conversation_id}/template, orPOST /v0/messageswithkindtemplateto start), or wait for the contact to write. On iMessage the contact has not messaged the line (or opted in) yet, or not recently, so the failure arrives as amessage.failedevent: wait for the contact to write, then reply in that conversation. Typing and read receipts answer409 outside_windowdirectly when the channel's window for them is closed (iMessage typing works only within 5 minutes of the contact's last message; WhatsApp read receipts go at any time); ignore it and send your reply. On SMS,channel_codequiet_hours: the contact has not texted in the last hour and it is inside the sender's quiet hours in their time zone; retry afterretry_afterseconds, or wait for them to text. Inmessage.failed:window_closed(WhatsApp's 24-hour window closed while the message was queued; send a template) andqueued_too_long(the message waited in the queue longer than its sender allows, for example while the sender was paused; send it again if it still applies).unsupported_content(422): the conversation's channel does not take this kind, field, block or value, and nothing was sent; Flow never converts a message.paramnames what was refused:kind(the endpoint's kind is not on this channel, such as a carousel on Telegram), a field or block (reply_toon SMS,telegramon WhatsApp,telegram.is_big), or the field whose value is not taken (emojioutside Telegram's reaction emoji,buttonsfor more than 3 WhatsApp buttons).channelis the conversation's channel andhintsays what to send instead over plain HTTP; each endpoint's page lists its channels. In an iMessage group conversation, a reaction, typing or a read receipt answers it withparamkindandchannel_codegroup_unsupported: leave it out. Media to an SMS number without MMS answers it withparammediaandchannel_codeno_mms. An edit or unsend of an iMessage message that went by SMS or RCS (itsnetwork) answers it withchannel_codenot_on_sms: send a new message instead. An edit or unsend past the channel's time limit answers it withchannel_codeedit_window_closedorunsend_window_closed(iMessage: edits within 15 minutes of sending, unsends within 2 minutes; Telegram: unsends within 48 hours), and an iMessage edit of the message that started the conversation withfirst_message_not_editable;messagestates the limit and how long ago the message was sent. Send a new message instead.POST /v0/filesanswers it for a type Flow does not take (programs, web pages, unknown types). A send byfile_idof a document a contact sent before Flow malware-scanned documents answers it too: Flow sends only documents it scanned.new_contact_limit(429): the sender has used its budget for starting conversations. Waitretry_afterseconds; replies into existing conversations still go.sender_throttled(429): abuse signals tripped (the same text to many new contacts, many starts with no reply, blocks), so the sender may not start conversations untilretry_after; replies into existing conversations still go. Personalise first messages and start only conversations people expect.file_blocked(422): the file failed the malware scan and was not stored. Send a different file. (When the scan cannot run at all, the upload answers a retryable500 api_errorwithRetry-Afterinstead.)rate_limited(429): either too many requests for this key (the per-key request limit, reported in theRateLimit-*headers) or sends faster than the sender's sending rate (pacing). WaitRetry-After(retry_after) seconds and retry with the sameIdempotency-Key.channel_error(502, or inmessage.failed): the channel refused or failed the message, or timed out;channel_codecarries its own code. Readmessage, change what the channel objected to, and send again. From typing and read receipts, which call the channel at once, it is safe to ignore. SMS codes are Flow's, the same whichever carrier network carries the message:carrier_filtered(a carrier's spam filter blocked it: make it read like a person's message, no shortened links),unreachable,invalid_number,landline,registration_required(the number's 10DLC or toll-free registration is not approved yet),carrier_error, andprovider_not_configured(the SMS sender is not fully set up on Flow's side, so nothing was sent; contact the Flow team). Flow's own codes on every channel:channel_timeout_unknown_outcome(inmessage.failed: the channel did not answer after the message was sent, and the resends its policy allows did not settle it, so the message may have arrived or not; check with the contact before sending it again) andunknown_outcome(from typing and read receipts: the call timed out after it was sent).not_implemented(501): this endpoint or channel is not live yet during the beta. Use a channel that is live (Telegram, iMessage), or check the changelog.api_error(500, 503): something went wrong on Flow's side. Retry with the same idempotency key afterretry_afterseconds, and quoterequest_idif it persists.
message string required A sentence for a developer saying what went wrong. Do not parse it; switch on type.
hint string required One sentence saying what to change to make the request succeed, specific
to this case, for example "Send a template instead: POST
/v0/conversations/{conversation_id}/template." For unsupported_content
it says what to send instead over plain HTTP. Written for developers and
coding agents alike. Do not parse it; it may be reworded at any time.
doc_url string<uri> required The documentation page for this error's type, https://api.flow.engineer/docs/errors/<type>. It says what the error means, why it happens and how to fix it, with code.
param string The request parameter the error is about, as a dotted path (kind, telegram.is_big, buttons, cards[1].header.type, limit).
retry_after integer Seconds to wait before retrying, for errors that clear by themselves.
channel enum<string> The conversation's channel, on errors about what it takes (unsupported_content) and its windows (outside_window).
conversation string A conversation ID, conv_ and a ULID.
sender string A sender ID, snd_ and a ULID.
channel_code string For channel_error, the channel's own error code, as given by the
channel. For some other errors, Flow's own code naming the case, for
example sandbox_allowance_used (permission: the sandbox allowance is
used up), sandbox_contact_limit (permission: the allowance has no
room for another contact), sandbox_channel_not_included (permission:
the allowance does not cover this sandbox channel), sign_in_required
(permission: an app made without an account cannot do this) or
sandbox_key_expired (authentication: a key from POST /v0/sandbox/keys passed its expires_at; permission, for a send
that carries no key, such as a reply in a webhook answer, from such an
app). For idempotency_conflict, always one of body_mismatch (the
key was used for a different request), in_progress (the first
request is still running; retry_after says when to repeat it) or
secret_not_kept (its answer carried a secret shown only once).
SMS: contact_opted_out and consent_required (permission),
quiet_hours (outside_window, with retry_after), and
carrier_filtered, unreachable, invalid_number, landline,
registration_required, carrier_error and provider_not_configured
(channel_error). HIPAA accounts: hipaa_channel_not_covered
(permission). Connecting your own WhatsApp number (POST /v0/senders): whatsapp_token_malformed, whatsapp_token_rejected,
whatsapp_app_secret_malformed and whatsapp_number_not_in_account
(invalid_request), and whatsapp_number_taken,
whatsapp_account_taken and whatsapp_number_managed (permission).
Simulated conversations (permission): test_mode_only,
not_simulated, simulated_contact and simulated_conversation_limit.
For a message that failed after it was queued (message.failed):
sandbox_contact_left (permission), window_closed and
queued_too_long (outside_window), and
channel_timeout_unknown_outcome (channel_error: the channel never
confirmed the send, so it may or may not have arrived). iMessage
(unsupported_content): group_unsupported (a reaction, typing or a
read receipt in a group conversation; param kind) and
not_on_sms (an edit or unsend of a message that went by SMS or RCS;
param kind). SMS (unsupported_content): no_mms (media to a
number without MMS; param media).
Edits and unsends (unsupported_content): edit_window_closed and
unsend_window_closed (the message is older than the channel allows
for the change) and first_message_not_editable (iMessage: the message
that started the conversation). From typing and
read receipts, unknown_outcome (channel_error: the call timed out after
it was sent).
request_id string Flow's ID for this request. Quote it when asking for help.
channel_message_id string The channel's own ID for the message, once the channel accepted it.
sms SmsSegments read-only SMS text messages only: how the text travels as SMS. Carriers bill and
deliver SMS in segments: 160 GSM-7 characters fit one segment (153 each
when split), but a single character outside GSM-7 (an emoji, curly quotes,
most non-Latin letters) makes the whole text UCS-2, 70 characters in one
segment (67 each when split). An SMS send may use at most 10 segments:
1530 GSM-7 characters (fewer when it uses GSM-7's two-unit characters such
as { or €), or 670 when it holds an emoji or other character outside
GSM-7; a longer text is refused with 400 invalid_request. Absent on MMS
and on other channels.
Show child attributes
segments integer required How many SMS segments the text takes.
encoding enum<string> required gsm7 or ucs2.
reply_to string read-only The Flow ID of the message this one replies to. For a sent message, the
message the send named in reply_to. For a received message, the message it
replies to inline (a Telegram or WhatsApp reply; on iMessage, the thread's
root message). Absent when the message is not a reply or the quoted
message is not known to Flow.
livemode boolean required Whether the message belongs to live mode.
metadata Metadata Up to 20 string pairs of your own, kept with the object and returned unchanged. Keys up to 40 characters, values up to 500.
created_at string<date-time> required When Flow received (inbound) or accepted (outbound) the message.
updated_at string<date-time> When the status last changed.
type: error
Server to client. A send or start frame was refused (with its ref), or the stream itself was refused or failed (without ref; the server then closes the socket with a 4000-range close code, 4401 for authentication, see GET /v0/stream).
type enum<string> required Always error.
ref string The ref of the frame being answered, if any.
error ErrorBody required The details of an error.
Show child attributes
type enum<string> required What went wrong. A closed list: new types arrive only with a new API version.
Each type has a page at https://api.flow.engineer/docs/errors/<type>, given in
the error's doc_url; the error's hint says what to change for the case.
invalid_request(400, or 413 for a request body over the size limit): the request is malformed or a parameter is invalid. Fix the parameter named inparam; thehintsays what it must look like. Every endpoint answers it for aFlow-Versionheader that is not a version this API serves.authentication(401): the API key is missing, malformed, unknown, revoked or expired. AnAuthorization: Bearerheader with nothing after it usually means the environment variable holding the key was empty when the header was built (for an MCP client, when the server was added): thehintsays so. SendAuthorization: Bearer fk_test_...orfk_live_...with a current key; with no key at all, get a test key withPOST /v0/sandbox/keys. A sandbox key past itsexpires_athaschannel_codesandbox_key_expired: sign in to claim the app, or get a new key.permission(403): the key may not do this, for example a test key using a live sender, a sandbox contact who joined another app, a new conversation from an iMessage line that may only reply, or a send past the app's sandbox allowance (channel_codesandbox_allowance_used,sandbox_contact_limitorsandbox_channel_not_included). Use the key of the right mode, have the contact send your join message, wait for the contact to message the line first, or sign in through the device flow to lift the allowance. On SMS:contact_opted_out(the contact texted STOP; nothing more goes to them until they text START),consent_required(a start to someone who never texted your numbers: record their consent withconsenton the start). In a HIPAA account:hipaa_channel_not_covered(the channel has no business associate agreement; use SMS). Simulated conversations:simulated_contact(a real sender was pointed at a simulated contact; reply in the contact's simulated conversation),not_simulated(a simulated sender or test endpoint was pointed at a real contact),test_mode_only,simulated_conversation_limit. Inmessage.failed:sandbox_contact_left(the contact joined another app on the sandbox sender while the message was queued; have them send your join message again).not_found(404): no such object for this app and mode. Check the ID's prefix and that it was made with a key of the same mode (test and live data are separate).idempotency_conflict(409): the idempotency key was used for a different request (channel_codebody_mismatch: use a new key), or that request is still running (in_progress: waitretry_afterseconds and repeat it with the same key), or it already created a secret that is shown only once (secret_not_kept: creating a webhook endpoint, rotating its secret, connecting a WhatsApp number; a secret that was lost must be rotated again, and a WhatsApp number connected again to see its verify token).outside_window(409, or inmessage.failed): the channel will not deliver outside its conversation window. On WhatsApp the 24-hour window is closed: send a template (POST /v0/conversations/{conversation_id}/template, orPOST /v0/messageswithkindtemplateto start), or wait for the contact to write. On iMessage the contact has not messaged the line (or opted in) yet, or not recently, so the failure arrives as amessage.failedevent: wait for the contact to write, then reply in that conversation. Typing and read receipts answer409 outside_windowdirectly when the channel's window for them is closed (iMessage typing works only within 5 minutes of the contact's last message; WhatsApp read receipts go at any time); ignore it and send your reply. On SMS,channel_codequiet_hours: the contact has not texted in the last hour and it is inside the sender's quiet hours in their time zone; retry afterretry_afterseconds, or wait for them to text. Inmessage.failed:window_closed(WhatsApp's 24-hour window closed while the message was queued; send a template) andqueued_too_long(the message waited in the queue longer than its sender allows, for example while the sender was paused; send it again if it still applies).unsupported_content(422): the conversation's channel does not take this kind, field, block or value, and nothing was sent; Flow never converts a message.paramnames what was refused:kind(the endpoint's kind is not on this channel, such as a carousel on Telegram), a field or block (reply_toon SMS,telegramon WhatsApp,telegram.is_big), or the field whose value is not taken (emojioutside Telegram's reaction emoji,buttonsfor more than 3 WhatsApp buttons).channelis the conversation's channel andhintsays what to send instead over plain HTTP; each endpoint's page lists its channels. In an iMessage group conversation, a reaction, typing or a read receipt answers it withparamkindandchannel_codegroup_unsupported: leave it out. Media to an SMS number without MMS answers it withparammediaandchannel_codeno_mms. An edit or unsend of an iMessage message that went by SMS or RCS (itsnetwork) answers it withchannel_codenot_on_sms: send a new message instead. An edit or unsend past the channel's time limit answers it withchannel_codeedit_window_closedorunsend_window_closed(iMessage: edits within 15 minutes of sending, unsends within 2 minutes; Telegram: unsends within 48 hours), and an iMessage edit of the message that started the conversation withfirst_message_not_editable;messagestates the limit and how long ago the message was sent. Send a new message instead.POST /v0/filesanswers it for a type Flow does not take (programs, web pages, unknown types). A send byfile_idof a document a contact sent before Flow malware-scanned documents answers it too: Flow sends only documents it scanned.new_contact_limit(429): the sender has used its budget for starting conversations. Waitretry_afterseconds; replies into existing conversations still go.sender_throttled(429): abuse signals tripped (the same text to many new contacts, many starts with no reply, blocks), so the sender may not start conversations untilretry_after; replies into existing conversations still go. Personalise first messages and start only conversations people expect.file_blocked(422): the file failed the malware scan and was not stored. Send a different file. (When the scan cannot run at all, the upload answers a retryable500 api_errorwithRetry-Afterinstead.)rate_limited(429): either too many requests for this key (the per-key request limit, reported in theRateLimit-*headers) or sends faster than the sender's sending rate (pacing). WaitRetry-After(retry_after) seconds and retry with the sameIdempotency-Key.channel_error(502, or inmessage.failed): the channel refused or failed the message, or timed out;channel_codecarries its own code. Readmessage, change what the channel objected to, and send again. From typing and read receipts, which call the channel at once, it is safe to ignore. SMS codes are Flow's, the same whichever carrier network carries the message:carrier_filtered(a carrier's spam filter blocked it: make it read like a person's message, no shortened links),unreachable,invalid_number,landline,registration_required(the number's 10DLC or toll-free registration is not approved yet),carrier_error, andprovider_not_configured(the SMS sender is not fully set up on Flow's side, so nothing was sent; contact the Flow team). Flow's own codes on every channel:channel_timeout_unknown_outcome(inmessage.failed: the channel did not answer after the message was sent, and the resends its policy allows did not settle it, so the message may have arrived or not; check with the contact before sending it again) andunknown_outcome(from typing and read receipts: the call timed out after it was sent).not_implemented(501): this endpoint or channel is not live yet during the beta. Use a channel that is live (Telegram, iMessage), or check the changelog.api_error(500, 503): something went wrong on Flow's side. Retry with the same idempotency key afterretry_afterseconds, and quoterequest_idif it persists.
message string required A sentence for a developer saying what went wrong. Do not parse it; switch on type.
hint string required One sentence saying what to change to make the request succeed, specific
to this case, for example "Send a template instead: POST
/v0/conversations/{conversation_id}/template." For unsupported_content
it says what to send instead over plain HTTP. Written for developers and
coding agents alike. Do not parse it; it may be reworded at any time.
doc_url string<uri> required The documentation page for this error's type, https://api.flow.engineer/docs/errors/<type>. It says what the error means, why it happens and how to fix it, with code.
param string The request parameter the error is about, as a dotted path (kind, telegram.is_big, buttons, cards[1].header.type, limit).
retry_after integer Seconds to wait before retrying, for errors that clear by themselves.
channel enum<string> The conversation's channel, on errors about what it takes (unsupported_content) and its windows (outside_window).
conversation string A conversation ID, conv_ and a ULID.
sender string A sender ID, snd_ and a ULID.
channel_code string For channel_error, the channel's own error code, as given by the
channel. For some other errors, Flow's own code naming the case, for
example sandbox_allowance_used (permission: the sandbox allowance is
used up), sandbox_contact_limit (permission: the allowance has no
room for another contact), sandbox_channel_not_included (permission:
the allowance does not cover this sandbox channel), sign_in_required
(permission: an app made without an account cannot do this) or
sandbox_key_expired (authentication: a key from POST /v0/sandbox/keys passed its expires_at; permission, for a send
that carries no key, such as a reply in a webhook answer, from such an
app). For idempotency_conflict, always one of body_mismatch (the
key was used for a different request), in_progress (the first
request is still running; retry_after says when to repeat it) or
secret_not_kept (its answer carried a secret shown only once).
SMS: contact_opted_out and consent_required (permission),
quiet_hours (outside_window, with retry_after), and
carrier_filtered, unreachable, invalid_number, landline,
registration_required, carrier_error and provider_not_configured
(channel_error). HIPAA accounts: hipaa_channel_not_covered
(permission). Connecting your own WhatsApp number (POST /v0/senders): whatsapp_token_malformed, whatsapp_token_rejected,
whatsapp_app_secret_malformed and whatsapp_number_not_in_account
(invalid_request), and whatsapp_number_taken,
whatsapp_account_taken and whatsapp_number_managed (permission).
Simulated conversations (permission): test_mode_only,
not_simulated, simulated_contact and simulated_conversation_limit.
For a message that failed after it was queued (message.failed):
sandbox_contact_left (permission), window_closed and
queued_too_long (outside_window), and
channel_timeout_unknown_outcome (channel_error: the channel never
confirmed the send, so it may or may not have arrived). iMessage
(unsupported_content): group_unsupported (a reaction, typing or a
read receipt in a group conversation; param kind) and
not_on_sms (an edit or unsend of a message that went by SMS or RCS;
param kind). SMS (unsupported_content): no_mms (media to a
number without MMS; param media).
Edits and unsends (unsupported_content): edit_window_closed and
unsend_window_closed (the message is older than the channel allows
for the change) and first_message_not_editable (iMessage: the message
that started the conversation). From typing and
read receipts, unknown_outcome (channel_error: the call timed out after
it was sent).
request_id string Flow's ID for this request. Quote it when asking for help.
type: reconnect
Server to client. The server is restarting; reconnect with after set to the last event you received. The socket closes after this frame.
type enum<string> required Always reconnect.
after string An event ID, evt_ and a ULID.
type: send
Client to server. Sends one message into a conversation, the same as the
endpoint of its kind (POST /v0/conversations/{conversation_id}/<kind>,
or PATCH and DELETE /v0/messages/{message_id} for edit and
unsend); ref is also the idempotency key. Frames into one conversation
go out in the order accepted, in line with the conversation's other sends.
The frame's type is the frame's own type; the message's kind is
message.kind.
type enum<string> required Always send.
ref string required Your ID for this frame, echoed in the answer and used as the idempotency key.
conversation string required A conversation ID, conv_ and a ULID.
message OutboundKind required One message to send where no URL names the kind: a piece of a webhook
reply (WebhookReply) or the message of a stream send frame. kind
is required and selects the shape; the fields and channel rules are the
same as on the kind's endpoint (edit and unsend as on PATCH and
DELETE /v0/messages/{message_id}, with the target in message_id).
Show child attributes
One of 14 options, chosen by `kind`:
kind: basic
A basic message with kind given, as in a message's content, a webhook reply or a stream frame (BasicMessage with kind required).
kind string required Always basic. Optional on POST /v0/conversations/{conversation_id}/basic; required where no URL names the kind (webhook replies, the stream, starts, a message's content).
text string The text, sent exactly as written: no markdown, no stripping. At most
4096 UTF-16 code units on Telegram (an emoji such as 😀 counts as 2),
4096 characters on WhatsApp, 9999 on iMessage, and 10 segments on SMS
(see SmsSegments). With media it is the caption, at most 1024 (UTF-16
code units on Telegram). Optional when media is given. Longer text is
refused with 400 invalid_request.
media Media One image, video, document or audio file. When sending, give exactly one of
url (an HTTPS URL Flow fetches; Flow neither stores nor scans it, and on
Telegram a document by URL works for PDF and ZIP files) or file_id (from
POST /v0/files). Each channel takes files up to its own size: Telegram
50 MB, WhatsApp and iMessage 100 MB, SMS (MMS) 5 MB; a larger file answers
422 unsupported_content with param media. SMS carries images, audio
and video, never documents (param media.type), and only from numbers
with MMS (channel_code no_mms).
Show child attributes
type enum<string> required What kind of file it is.
url string<uri> Where the bytes are. Sending, an HTTPS URL Flow can fetch; inbound, a Flow file URL.
file_id string A file ID, file_ and a ULID.
filename string The file's name, shown for documents and taken only with type document (on another type it answers 422 unsupported_content with param media.filename). On Telegram it works only with file_id: Telegram names a document sent by url itself, so filename with url answers 422 unsupported_content with param media.filename.
mime_type string read-only Inbound only. The real type, checked from the file's first bytes.
size_bytes integer read-only Inbound only. The file's size.
reply_to string Send this as an inline reply to an earlier message in the same
conversation (Telegram, WhatsApp and iMessage show it quoted). SMS has no
inline replies: there it answers 422 unsupported_content with param
reply_to. Refused with 404 not_found (param reply_to) when the
target is not a message in this conversation, and with 400 invalid_request (param reply_to) when the target never reached the
channel (for example, it failed). If the target fails after the send was
accepted, the reply goes out as a normal message.
link_preview boolean Whether links in the text show a rich preview. Left out, the channel's
default applies (Telegram, WhatsApp and iMessage show previews).
false sends links as plain text. SMS never shows previews, and no
channel shows one on a media message: false is accepted there, and
true on SMS or with media answers 422 unsupported_content with
param link_preview.
telegram BasicTelegramOptions Telegram only: how the message looks and notifies on Telegram. On another
channel the block answers 422 unsupported_content with param
telegram; an unknown key 400 invalid_request.
Show child attributes
parse_mode enum<string> How Telegram reads the text: HTML or MarkdownV2, Telegram's own
formatting (see Telegram's Bot API, "Formatting options"). Without it the
text is plain. Text that Telegram cannot parse is refused by Telegram:
the message fails (message.failed, channel_error).
disable_notification boolean Deliver silently, with no sound.
protect_content boolean Stop the contact forwarding or saving the message.
show_caption_above_media boolean Media only. Show the caption above the photo or video instead of below it.
has_spoiler boolean Media only (photos and videos). Cover it with a spoiler animation until tapped.
imessage BasicImessageOptions iMessage only: styles and a subject line. On another channel the block
answers 422 unsupported_content with param imessage; an unknown key
400 invalid_request. A message that goes by SMS or RCS (see
Message.network) shows neither.
Show child attributes
styles ImessageStyle[] Styled ranges of text (see ImessageStyle). Each range must lie within text; one that does not answers 400 invalid_request naming it (imessage.styles[0]).
Show child attributes
start integer required Where the range starts, in UTF-16 code units from the start of text.
length integer required How long the range is, in UTF-16 code units.
style enum<string> required The style. big and small set the text's size; shake, nod, explode, ripple, bloom and jitter animate it.
subject string iMessage's subject line, shown in bold above the text.
metadata Metadata Up to 20 string pairs of your own, kept with the object and returned unchanged. Keys up to 40 characters, values up to 500.
kind: voice
A voice message with kind given, as in a message's content, a webhook reply or a stream frame (VoiceMessage with kind required).
kind string required Always voice. Optional on its endpoint; required where no URL names the kind.
url string<uri> Where the audio is. Sending, an HTTPS URL Flow can fetch; inbound, a Flow file URL.
file_id string A file ID, file_ and a ULID.
reply_to string Send this as an inline reply to an earlier message in the same
conversation (Telegram, WhatsApp and iMessage show it quoted). SMS has no
inline replies: there it answers 422 unsupported_content with param
reply_to. Refused with 404 not_found (param reply_to) when the
target is not a message in this conversation, and with 400 invalid_request (param reply_to) when the target never reached the
channel (for example, it failed). If the target fails after the send was
accepted, the reply goes out as a normal message.
metadata Metadata Up to 20 string pairs of your own, kept with the object and returned unchanged. Keys up to 40 characters, values up to 500.
duration_seconds number read-only Inbound only. The voice note's length.
transcript string read-only Inbound only, on iMessage, when iMessage made a transcript of the voice note. What was said.
kind: buttons
A buttons message with kind given, as in a message's content, a webhook reply or a stream frame (ButtonsMessage with kind required).
kind string required Always buttons. Optional on its endpoint; required where no URL names the kind.
header ContentHeader What is shown above a buttons message's text, or a carousel card's image or
video: an image, a video, a document or a short title. A media header
takes exactly one of url (an HTTPS URL Flow fetches) or file_id (from
POST /v0/files), and is fetched and checked for the channel like a basic
message's media (so WebP and other formats work); a text header takes
text. Anything else, or a value over a limit, is refused with 400 invalid_request naming the field in param.
WhatsApp shows the header at the top of the same message bubble. Telegram
sends one message: the media with the text as its caption and the buttons
under it; a text header is the message's first line, in bold.
Show child attributes
type enum<string> required What the header holds.
url string<uri> Media headers only. An HTTPS URL of the media, which Flow fetches.
file_id string A file ID, file_ and a ULID.
text string text headers only. The title, 1 to 60 characters (WhatsApp's header limit).
filename string document headers only. The file's name, shown with the document.
text string required The text above the buttons, sent exactly as written: at most 4096 UTF-16
code units on Telegram, or 1024 when the message has a media header
(Telegram's caption limit), and 1024 characters on WhatsApp (its limit
for messages with buttons). Longer text is refused with 400 invalid_request.
buttons Button[] required The buttons, in order: 1 to 10 on Telegram; on WhatsApp 1 to 3 reply
buttons or exactly one URL button (anything else answers 422 unsupported_content with param buttons; send a list for more
choices).
Show child attributes
One of 2 options:
ReplyButton
A button that sends its id back as a button_reply when tapped.
id string required Your ID for the button, returned in button_reply as button_id: 1 to 64
bytes (Telegram's limit for a button's data), so at most 64 ASCII
characters and fewer for other characters. Labels may repeat; the ID is
what tells the taps apart, so buttons that share a label (a "Details" on
every card) get their own IDs, such as details:listing-17.
label string required The button's text. WhatsApp shows at most 20 characters.
UrlButton
A button that opens a link.
url string<uri> required The HTTPS link to open.
label string required The button's text.
reply_to string Send this as an inline reply to an earlier message in the same
conversation (Telegram, WhatsApp and iMessage show it quoted). SMS has no
inline replies: there it answers 422 unsupported_content with param
reply_to. Refused with 404 not_found (param reply_to) when the
target is not a message in this conversation, and with 400 invalid_request (param reply_to) when the target never reached the
channel (for example, it failed). If the target fails after the send was
accepted, the reply goes out as a normal message.
telegram ButtonsTelegramOptions Telegram only: how the message looks and notifies on Telegram. On another
channel the block answers 422 unsupported_content with param
telegram; an unknown key 400 invalid_request.
Show child attributes
parse_mode enum<string> How Telegram reads the text: HTML or MarkdownV2, Telegram's own
formatting (see Telegram's Bot API, "Formatting options"). Without it the
text is plain. Text that Telegram cannot parse is refused by Telegram:
the message fails (message.failed, channel_error).
disable_notification boolean Deliver silently, with no sound.
protect_content boolean Stop the contact forwarding or saving the message.
metadata Metadata Up to 20 string pairs of your own, kept with the object and returned unchanged. Keys up to 40 characters, values up to 500.
kind: list
A list message with kind given, as in a message's content, a webhook reply or a stream frame (ListMessage with kind required).
kind string required Always list. Optional on its endpoint; required where no URL names the kind.
header string A title shown above the text, 1 to 60 characters.
text string required The message's text, sent exactly as written, 1 to 4096 characters.
button string required The label of the button that opens the list, 1 to 20 characters.
sections ListSection[] required 1 to 10 sections, in order, with at most 10 rows in all.
Show child attributes
title string The section's title, 1 to 24 characters. Required when the list has more than one section.
rows ListRow[] required The section's rows, in order.
Show child attributes
id string required Your ID for the row, returned in button_reply as button_id. 1 to 200 characters, unique across the list.
title string required The row's title, 1 to 24 characters, returned in button_reply as label.
description string A line under the title, at most 72 characters.
reply_to string Send this as an inline reply to an earlier message in the same
conversation (Telegram, WhatsApp and iMessage show it quoted). SMS has no
inline replies: there it answers 422 unsupported_content with param
reply_to. Refused with 404 not_found (param reply_to) when the
target is not a message in this conversation, and with 400 invalid_request (param reply_to) when the target never reached the
channel (for example, it failed). If the target fails after the send was
accepted, the reply goes out as a normal message.
metadata Metadata Up to 20 string pairs of your own, kept with the object and returned unchanged. Keys up to 40 characters, values up to 500.
kind: carousel
A carousel message with kind given, as in a message's content, a webhook reply or a stream frame (CarouselMessage with kind required).
kind string required Always carousel. Optional on its endpoint; required where no URL names the kind.
text string required The message text shown above the cards, 1 to 1024 characters. Longer text is refused with 400 invalid_request.
cards CarouselCard[] required 2 to 10 cards, in the order shown (left to right).
Show child attributes
header ContentHeader required The card's image or video, a ContentHeader of type image or video only (by url or file_id); any other type is refused with 400 invalid_request at cards[i].header.type.
Show child attributes
type enum<string> required What the header holds.
url string<uri> Media headers only. An HTTPS URL of the media, which Flow fetches.
file_id string A file ID, file_ and a ULID.
text string text headers only. The title, 1 to 60 characters (WhatsApp's header limit).
filename string document headers only. The file's name, shown with the document.
text string The card's text, at most 160 characters and 2 line breaks.
buttons Button[] required Exactly one URL button, or 1 to 2 reply buttons; the same number and kind on every card. Labels are at most 20 characters.
Show child attributes
One of 2 options:
ReplyButton
A button that sends its id back as a button_reply when tapped.
id string required Your ID for the button, returned in button_reply as button_id: 1 to 64
bytes (Telegram's limit for a button's data), so at most 64 ASCII
characters and fewer for other characters. Labels may repeat; the ID is
what tells the taps apart, so buttons that share a label (a "Details" on
every card) get their own IDs, such as details:listing-17.
label string required The button's text. WhatsApp shows at most 20 characters.
UrlButton
A button that opens a link.
url string<uri> required The HTTPS link to open.
label string required The button's text.
metadata Metadata Up to 20 string pairs of your own, kept with the object and returned unchanged. Keys up to 40 characters, values up to 500.
kind: template
A template message with kind given, as in a message's content, a webhook reply or a stream frame (TemplateMessage with kind required).
kind string required Always template. Optional on its endpoint; required where no URL names the kind (and on POST /v0/messages).
template_id string required A template ID, tpl_ and a ULID.
language string required The template language to use, as Meta names it (en, en_US, hi).
params TemplateParams Values for the template's placeholders.
Show child attributes
header TemplateHeaderParam The value of a template's header, when it has a placeholder or media.
Show child attributes
kind enum<string> required What the header holds.
text string For text headers, the placeholder's value.
url string<uri> For media headers, an HTTPS URL of the media.
file_id string A file ID, file_ and a ULID.
body string[] Values for the body's {{1}}, {{2}}, ... in order.
buttons TemplateButtonParam[] Values for buttons that take one (URL suffixes, quick-reply payloads).
Show child attributes
index integer required The button's position in the template, from 0.
value string required The URL suffix or the quick-reply payload.
cards TemplateCardParams[] Carousel templates only. Values for each card's header, body placeholders and buttons.
Show child attributes
card_index integer required The card's position in the template, from 0.
header TemplateHeaderParam The card's image or video (kind image or video, by url or file_id).
body string[] Values for the card body's {{1}}, {{2}}, ... in order.
buttons TemplateButtonParam[] Values for the card's buttons that take one (URL suffixes, quick-reply payloads).
reply_to string Send this as an inline reply to an earlier message in the same
conversation (Telegram, WhatsApp and iMessage show it quoted). SMS has no
inline replies: there it answers 422 unsupported_content with param
reply_to. Refused with 404 not_found (param reply_to) when the
target is not a message in this conversation, and with 400 invalid_request (param reply_to) when the target never reached the
channel (for example, it failed). If the target fails after the send was
accepted, the reply goes out as a normal message.
metadata Metadata Up to 20 string pairs of your own, kept with the object and returned unchanged. Keys up to 40 characters, values up to 500.
kind: contact_card
A contact_card message with kind given, as in a message's content, a webhook reply or a stream frame (ContactCardMessage with kind required).
kind string required Always contact_card. Optional on its endpoint; required where no URL names the kind.
name string required The person's or business's name.
phones string[] Phone numbers, E.164 where known. Telegram needs at least one (else 422 unsupported_content, param phones).
emails string<email>[] Email addresses.
reply_to string Send this as an inline reply to an earlier message in the same
conversation (Telegram, WhatsApp and iMessage show it quoted). SMS has no
inline replies: there it answers 422 unsupported_content with param
reply_to. Refused with 404 not_found (param reply_to) when the
target is not a message in this conversation, and with 400 invalid_request (param reply_to) when the target never reached the
channel (for example, it failed). If the target fails after the send was
accepted, the reply goes out as a normal message.
metadata Metadata Up to 20 string pairs of your own, kept with the object and returned unchanged. Keys up to 40 characters, values up to 500.
kind: location
A location message with kind given, as in a message's content, a webhook reply or a stream frame (LocationMessage with kind required).
kind string required Always location. Optional on its endpoint; required where no URL names the kind.
lat number required Latitude in degrees.
lng number required Longitude in degrees.
name string The place's name.
address string The place's address.
reply_to string Send this as an inline reply to an earlier message in the same
conversation (Telegram, WhatsApp and iMessage show it quoted). SMS has no
inline replies: there it answers 422 unsupported_content with param
reply_to. Refused with 404 not_found (param reply_to) when the
target is not a message in this conversation, and with 400 invalid_request (param reply_to) when the target never reached the
channel (for example, it failed). If the target fails after the send was
accepted, the reply goes out as a normal message.
metadata Metadata Up to 20 string pairs of your own, kept with the object and returned unchanged. Keys up to 40 characters, values up to 500.
kind: effect
An effect message with kind given, as in a message's content, a webhook reply or a stream frame (EffectMessage with kind required).
kind string required Always effect. Optional on its endpoint; required where no URL names the kind.
text string required The text, sent exactly as written, at most 9999 characters. Longer text is refused with 400 invalid_request.
effect enum<string> required The effect.
reply_to string Send this as an inline reply to an earlier message in the same
conversation (Telegram, WhatsApp and iMessage show it quoted). SMS has no
inline replies: there it answers 422 unsupported_content with param
reply_to. Refused with 404 not_found (param reply_to) when the
target is not a message in this conversation, and with 400 invalid_request (param reply_to) when the target never reached the
channel (for example, it failed). If the target fails after the send was
accepted, the reply goes out as a normal message.
metadata Metadata Up to 20 string pairs of your own, kept with the object and returned unchanged. Keys up to 40 characters, values up to 500.
kind: reaction
A reaction message with kind given, as in a message's content, a webhook reply or a stream frame (ReactionMessage with kind required).
kind string required Always reaction. Optional on its endpoint; required where no URL names the kind.
message_id string required A message ID, msg_ and a ULID.
emoji string | null required One emoji, or null to remove the reaction. On Telegram only its reaction emoji (listed on the endpoint); any other answers 422 unsupported_content with param emoji.
telegram ReactionTelegramOptions Telegram only. On another channel the block answers 422 unsupported_content with param telegram; an unknown key 400 invalid_request.
Show child attributes
is_big boolean Show the reaction with a large animation.
kind: typing
A typing message with kind given, as in a message's content, a webhook reply or a stream frame (TypingMessage with kind required).
kind string required Always typing. Optional on its endpoint; required where no URL names the kind.
imessage TypingImessageOptions iMessage only. On another channel the block answers 422 unsupported_content with param imessage; an unknown key 400 invalid_request.
Show child attributes
duration_seconds integer How long to show the indicator, 1 to 60 seconds, unless a message goes first.
kind: read
A read message with kind given, as in a message's content, a webhook reply or a stream frame (ReadMessage with kind required).
kind string required Always read. Optional on its endpoint; required where no URL names the kind.
up_to string WhatsApp only. The latest inbound message to mark read (default the latest inbound message). iMessage marks the whole conversation read, so up_to there answers 422 unsupported_content with param up_to.
kind: edit
A change to the text of one of your sent messages, as a kind: a piece of a
webhook reply or on the stream, and the content of the message that
carries an edit (PATCH /v0/messages/{message_id} over HTTP, whose rules
apply). message_id names the message to change.
text string required The new text: at most 4096 UTF-16 code units on Telegram (1024 when it
replaces a media message's caption) and 9999 characters on iMessage.
Longer text is refused with 400 invalid_request.
telegram EditTelegramOptions Telegram only. How Telegram reads the new text. An unknown key is refused with 400 invalid_request.
Show child attributes
parse_mode enum<string> How Telegram reads the text: HTML or MarkdownV2, Telegram's own
formatting (see Telegram's Bot API, "Formatting options"). Without it the
text is plain. Text that Telegram cannot parse is refused by Telegram:
the message fails (message.failed, channel_error).
imessage EditImessageOptions iMessage only. Styles for the new text. An unknown key is refused with 400 invalid_request.
Show child attributes
styles ImessageStyle[] Styled ranges of the new text (see ImessageStyle).
Show child attributes
start integer required Where the range starts, in UTF-16 code units from the start of text.
length integer required How long the range is, in UTF-16 code units.
style enum<string> required The style. big and small set the text's size; shake, nod, explode, ripple, bloom and jitter animate it.
kind string required Always edit.
message_id string required A message ID, msg_ and a ULID.
kind: unsend
Removes one of your sent messages from the contact's chat, as a kind: a
piece of a webhook reply or on the stream, and the content of the message
that carries an unsend (DELETE /v0/messages/{message_id} over HTTP,
whose rules apply).
kind string required Always unsend.
message_id string required A message ID, msg_ and a ULID.
type: start
Client to server. Same as POST /v0/messages (a basic or template message with kind, sender and to); ref is also the idempotency key.
type enum<string> required Always start.
ref string required Your ID for this frame, echoed in the answer and used as the idempotency key.
message StartConversationRequest required The first message from one of your senders to a contact: a basic or a
template message, with kind saying which (required here), plus
sender, to and, on SMS, consent. A field not listed for the kind is
refused with 400 invalid_request naming it in param, never ignored; a
kind other than basic or template answers 422 unsupported_content
with param kind. On iMessage the sender must be able to write first
(Sender.imessage.can_initiate, available on request); otherwise the
start answers 403 permission. A start always makes a direct
conversation: there is no way to start a group. There are no scheduled
sends: to start a conversation later, wait in your own code and send then.
Show child attributes
One of 2 options, chosen by `kind`:
kind: basic
A start whose first message is a basic message (kind basic).
sender string required A sender ID, snd_ and a ULID.
to Recipient required Who to start a conversation with: an existing contact, or an address on the
sender's channel. Give exactly one field.
Show child attributes
contact string A contact ID, ct_ and a ULID.
phone string WhatsApp, iMessage or SMS. E.164 phone number; on SMS a US or Canadian number, +1 and ten digits.
telegram_user_id string Telegram. The user's numeric ID; they must have started the bot.
handle string iMessage. A phone number or an email address.
consent Consent SMS only. The contact's consent, required when they have never texted any of your SMS numbers; recorded on the contact. On another channel it answers 422 unsupported_content with param consent.
Show child attributes
obtained_at string<date-time> required When the person gave consent. Not in the future.
method enum<string> required How they gave it.
proof string Your own reference to the record of consent (a form submission ID or URL). Do not put health information here.
kind string required Always basic. Optional on POST /v0/conversations/{conversation_id}/basic; required where no URL names the kind (webhook replies, the stream, starts, a message's content).
text string The text, sent exactly as written: no markdown, no stripping. At most
4096 UTF-16 code units on Telegram (an emoji such as 😀 counts as 2),
4096 characters on WhatsApp, 9999 on iMessage, and 10 segments on SMS
(see SmsSegments). With media it is the caption, at most 1024 (UTF-16
code units on Telegram). Optional when media is given. Longer text is
refused with 400 invalid_request.
media Media One image, video, document or audio file. When sending, give exactly one of
url (an HTTPS URL Flow fetches; Flow neither stores nor scans it, and on
Telegram a document by URL works for PDF and ZIP files) or file_id (from
POST /v0/files). Each channel takes files up to its own size: Telegram
50 MB, WhatsApp and iMessage 100 MB, SMS (MMS) 5 MB; a larger file answers
422 unsupported_content with param media. SMS carries images, audio
and video, never documents (param media.type), and only from numbers
with MMS (channel_code no_mms).
Show child attributes
type enum<string> required What kind of file it is.
url string<uri> Where the bytes are. Sending, an HTTPS URL Flow can fetch; inbound, a Flow file URL.
file_id string A file ID, file_ and a ULID.
filename string The file's name, shown for documents and taken only with type document (on another type it answers 422 unsupported_content with param media.filename). On Telegram it works only with file_id: Telegram names a document sent by url itself, so filename with url answers 422 unsupported_content with param media.filename.
mime_type string read-only Inbound only. The real type, checked from the file's first bytes.
size_bytes integer read-only Inbound only. The file's size.
reply_to string Send this as an inline reply to an earlier message in the same
conversation (Telegram, WhatsApp and iMessage show it quoted). SMS has no
inline replies: there it answers 422 unsupported_content with param
reply_to. Refused with 404 not_found (param reply_to) when the
target is not a message in this conversation, and with 400 invalid_request (param reply_to) when the target never reached the
channel (for example, it failed). If the target fails after the send was
accepted, the reply goes out as a normal message.
link_preview boolean Whether links in the text show a rich preview. Left out, the channel's
default applies (Telegram, WhatsApp and iMessage show previews).
false sends links as plain text. SMS never shows previews, and no
channel shows one on a media message: false is accepted there, and
true on SMS or with media answers 422 unsupported_content with
param link_preview.
telegram BasicTelegramOptions Telegram only: how the message looks and notifies on Telegram. On another
channel the block answers 422 unsupported_content with param
telegram; an unknown key 400 invalid_request.
Show child attributes
parse_mode enum<string> How Telegram reads the text: HTML or MarkdownV2, Telegram's own
formatting (see Telegram's Bot API, "Formatting options"). Without it the
text is plain. Text that Telegram cannot parse is refused by Telegram:
the message fails (message.failed, channel_error).
disable_notification boolean Deliver silently, with no sound.
protect_content boolean Stop the contact forwarding or saving the message.
show_caption_above_media boolean Media only. Show the caption above the photo or video instead of below it.
has_spoiler boolean Media only (photos and videos). Cover it with a spoiler animation until tapped.
imessage BasicImessageOptions iMessage only: styles and a subject line. On another channel the block
answers 422 unsupported_content with param imessage; an unknown key
400 invalid_request. A message that goes by SMS or RCS (see
Message.network) shows neither.
Show child attributes
styles ImessageStyle[] Styled ranges of text (see ImessageStyle). Each range must lie within text; one that does not answers 400 invalid_request naming it (imessage.styles[0]).
Show child attributes
start integer required Where the range starts, in UTF-16 code units from the start of text.
length integer required How long the range is, in UTF-16 code units.
style enum<string> required The style. big and small set the text's size; shake, nod, explode, ripple, bloom and jitter animate it.
subject string iMessage's subject line, shown in bold above the text.
metadata Metadata Up to 20 string pairs of your own, kept with the object and returned unchanged. Keys up to 40 characters, values up to 500.
kind: template
A start whose first message is a WhatsApp template (kind template).
sender string required A sender ID, snd_ and a ULID.
to Recipient required Who to start a conversation with: an existing contact, or an address on the
sender's channel. Give exactly one field.
Show child attributes
contact string A contact ID, ct_ and a ULID.
phone string WhatsApp, iMessage or SMS. E.164 phone number; on SMS a US or Canadian number, +1 and ten digits.
telegram_user_id string Telegram. The user's numeric ID; they must have started the bot.
handle string iMessage. A phone number or an email address.
consent Consent SMS only. The contact's consent, required when they have never texted any of your SMS numbers; recorded on the contact. On another channel it answers 422 unsupported_content with param consent.
Show child attributes
obtained_at string<date-time> required When the person gave consent. Not in the future.
method enum<string> required How they gave it.
proof string Your own reference to the record of consent (a form submission ID or URL). Do not put health information here.
kind string required Always template. Optional on its endpoint; required where no URL names the kind (and on POST /v0/messages).
template_id string required A template ID, tpl_ and a ULID.
language string required The template language to use, as Meta names it (en, en_US, hi).
params TemplateParams Values for the template's placeholders.
Show child attributes
header TemplateHeaderParam The value of a template's header, when it has a placeholder or media.
Show child attributes
kind enum<string> required What the header holds.
text string For text headers, the placeholder's value.
url string<uri> For media headers, an HTTPS URL of the media.
file_id string A file ID, file_ and a ULID.
body string[] Values for the body's {{1}}, {{2}}, ... in order.
buttons TemplateButtonParam[] Values for buttons that take one (URL suffixes, quick-reply payloads).
Show child attributes
index integer required The button's position in the template, from 0.
value string required The URL suffix or the quick-reply payload.
cards TemplateCardParams[] Carousel templates only. Values for each card's header, body placeholders and buttons.
Show child attributes
card_index integer required The card's position in the template, from 0.
header TemplateHeaderParam The card's image or video (kind image or video, by url or file_id).
body string[] Values for the card body's {{1}}, {{2}}, ... in order.
buttons TemplateButtonParam[] Values for the card's buttons that take one (URL suffixes, quick-reply payloads).
reply_to string Send this as an inline reply to an earlier message in the same
conversation (Telegram, WhatsApp and iMessage show it quoted). SMS has no
inline replies: there it answers 422 unsupported_content with param
reply_to. Refused with 404 not_found (param reply_to) when the
target is not a message in this conversation, and with 400 invalid_request (param reply_to) when the target never reached the
channel (for example, it failed). If the target fails after the send was
accepted, the reply goes out as a normal message.
metadata Metadata Up to 20 string pairs of your own, kept with the object and returned unchanged. Keys up to 40 characters, values up to 500.
400 application/json
The request is malformed or a parameter is invalid (invalid_request).
error.param names the parameter. Every endpoint answers this for a
Flow-Version header that is not a date this API serves (param
Flow-Version), and every POST that takes an Idempotency-Key for a key
over 255 characters (param Idempotency-Key).
error ErrorBody required The details of an error.
Show child attributes
type enum<string> required What went wrong. A closed list: new types arrive only with a new API version.
Each type has a page at https://api.flow.engineer/docs/errors/<type>, given in
the error's doc_url; the error's hint says what to change for the case.
invalid_request(400, or 413 for a request body over the size limit): the request is malformed or a parameter is invalid. Fix the parameter named inparam; thehintsays what it must look like. Every endpoint answers it for aFlow-Versionheader that is not a version this API serves.authentication(401): the API key is missing, malformed, unknown, revoked or expired. AnAuthorization: Bearerheader with nothing after it usually means the environment variable holding the key was empty when the header was built (for an MCP client, when the server was added): thehintsays so. SendAuthorization: Bearer fk_test_...orfk_live_...with a current key; with no key at all, get a test key withPOST /v0/sandbox/keys. A sandbox key past itsexpires_athaschannel_codesandbox_key_expired: sign in to claim the app, or get a new key.permission(403): the key may not do this, for example a test key using a live sender, a sandbox contact who joined another app, a new conversation from an iMessage line that may only reply, or a send past the app's sandbox allowance (channel_codesandbox_allowance_used,sandbox_contact_limitorsandbox_channel_not_included). Use the key of the right mode, have the contact send your join message, wait for the contact to message the line first, or sign in through the device flow to lift the allowance. On SMS:contact_opted_out(the contact texted STOP; nothing more goes to them until they text START),consent_required(a start to someone who never texted your numbers: record their consent withconsenton the start). In a HIPAA account:hipaa_channel_not_covered(the channel has no business associate agreement; use SMS). Simulated conversations:simulated_contact(a real sender was pointed at a simulated contact; reply in the contact's simulated conversation),not_simulated(a simulated sender or test endpoint was pointed at a real contact),test_mode_only,simulated_conversation_limit. Inmessage.failed:sandbox_contact_left(the contact joined another app on the sandbox sender while the message was queued; have them send your join message again).not_found(404): no such object for this app and mode. Check the ID's prefix and that it was made with a key of the same mode (test and live data are separate).idempotency_conflict(409): the idempotency key was used for a different request (channel_codebody_mismatch: use a new key), or that request is still running (in_progress: waitretry_afterseconds and repeat it with the same key), or it already created a secret that is shown only once (secret_not_kept: creating a webhook endpoint, rotating its secret, connecting a WhatsApp number; a secret that was lost must be rotated again, and a WhatsApp number connected again to see its verify token).outside_window(409, or inmessage.failed): the channel will not deliver outside its conversation window. On WhatsApp the 24-hour window is closed: send a template (POST /v0/conversations/{conversation_id}/template, orPOST /v0/messageswithkindtemplateto start), or wait for the contact to write. On iMessage the contact has not messaged the line (or opted in) yet, or not recently, so the failure arrives as amessage.failedevent: wait for the contact to write, then reply in that conversation. Typing and read receipts answer409 outside_windowdirectly when the channel's window for them is closed (iMessage typing works only within 5 minutes of the contact's last message; WhatsApp read receipts go at any time); ignore it and send your reply. On SMS,channel_codequiet_hours: the contact has not texted in the last hour and it is inside the sender's quiet hours in their time zone; retry afterretry_afterseconds, or wait for them to text. Inmessage.failed:window_closed(WhatsApp's 24-hour window closed while the message was queued; send a template) andqueued_too_long(the message waited in the queue longer than its sender allows, for example while the sender was paused; send it again if it still applies).unsupported_content(422): the conversation's channel does not take this kind, field, block or value, and nothing was sent; Flow never converts a message.paramnames what was refused:kind(the endpoint's kind is not on this channel, such as a carousel on Telegram), a field or block (reply_toon SMS,telegramon WhatsApp,telegram.is_big), or the field whose value is not taken (emojioutside Telegram's reaction emoji,buttonsfor more than 3 WhatsApp buttons).channelis the conversation's channel andhintsays what to send instead over plain HTTP; each endpoint's page lists its channels. In an iMessage group conversation, a reaction, typing or a read receipt answers it withparamkindandchannel_codegroup_unsupported: leave it out. Media to an SMS number without MMS answers it withparammediaandchannel_codeno_mms. An edit or unsend of an iMessage message that went by SMS or RCS (itsnetwork) answers it withchannel_codenot_on_sms: send a new message instead. An edit or unsend past the channel's time limit answers it withchannel_codeedit_window_closedorunsend_window_closed(iMessage: edits within 15 minutes of sending, unsends within 2 minutes; Telegram: unsends within 48 hours), and an iMessage edit of the message that started the conversation withfirst_message_not_editable;messagestates the limit and how long ago the message was sent. Send a new message instead.POST /v0/filesanswers it for a type Flow does not take (programs, web pages, unknown types). A send byfile_idof a document a contact sent before Flow malware-scanned documents answers it too: Flow sends only documents it scanned.new_contact_limit(429): the sender has used its budget for starting conversations. Waitretry_afterseconds; replies into existing conversations still go.sender_throttled(429): abuse signals tripped (the same text to many new contacts, many starts with no reply, blocks), so the sender may not start conversations untilretry_after; replies into existing conversations still go. Personalise first messages and start only conversations people expect.file_blocked(422): the file failed the malware scan and was not stored. Send a different file. (When the scan cannot run at all, the upload answers a retryable500 api_errorwithRetry-Afterinstead.)rate_limited(429): either too many requests for this key (the per-key request limit, reported in theRateLimit-*headers) or sends faster than the sender's sending rate (pacing). WaitRetry-After(retry_after) seconds and retry with the sameIdempotency-Key.channel_error(502, or inmessage.failed): the channel refused or failed the message, or timed out;channel_codecarries its own code. Readmessage, change what the channel objected to, and send again. From typing and read receipts, which call the channel at once, it is safe to ignore. SMS codes are Flow's, the same whichever carrier network carries the message:carrier_filtered(a carrier's spam filter blocked it: make it read like a person's message, no shortened links),unreachable,invalid_number,landline,registration_required(the number's 10DLC or toll-free registration is not approved yet),carrier_error, andprovider_not_configured(the SMS sender is not fully set up on Flow's side, so nothing was sent; contact the Flow team). Flow's own codes on every channel:channel_timeout_unknown_outcome(inmessage.failed: the channel did not answer after the message was sent, and the resends its policy allows did not settle it, so the message may have arrived or not; check with the contact before sending it again) andunknown_outcome(from typing and read receipts: the call timed out after it was sent).not_implemented(501): this endpoint or channel is not live yet during the beta. Use a channel that is live (Telegram, iMessage), or check the changelog.api_error(500, 503): something went wrong on Flow's side. Retry with the same idempotency key afterretry_afterseconds, and quoterequest_idif it persists.
message string required A sentence for a developer saying what went wrong. Do not parse it; switch on type.
hint string required One sentence saying what to change to make the request succeed, specific
to this case, for example "Send a template instead: POST
/v0/conversations/{conversation_id}/template." For unsupported_content
it says what to send instead over plain HTTP. Written for developers and
coding agents alike. Do not parse it; it may be reworded at any time.
doc_url string<uri> required The documentation page for this error's type, https://api.flow.engineer/docs/errors/<type>. It says what the error means, why it happens and how to fix it, with code.
param string The request parameter the error is about, as a dotted path (kind, telegram.is_big, buttons, cards[1].header.type, limit).
retry_after integer Seconds to wait before retrying, for errors that clear by themselves.
channel enum<string> The conversation's channel, on errors about what it takes (unsupported_content) and its windows (outside_window).
conversation string A conversation ID, conv_ and a ULID.
sender string A sender ID, snd_ and a ULID.
channel_code string For channel_error, the channel's own error code, as given by the
channel. For some other errors, Flow's own code naming the case, for
example sandbox_allowance_used (permission: the sandbox allowance is
used up), sandbox_contact_limit (permission: the allowance has no
room for another contact), sandbox_channel_not_included (permission:
the allowance does not cover this sandbox channel), sign_in_required
(permission: an app made without an account cannot do this) or
sandbox_key_expired (authentication: a key from POST /v0/sandbox/keys passed its expires_at; permission, for a send
that carries no key, such as a reply in a webhook answer, from such an
app). For idempotency_conflict, always one of body_mismatch (the
key was used for a different request), in_progress (the first
request is still running; retry_after says when to repeat it) or
secret_not_kept (its answer carried a secret shown only once).
SMS: contact_opted_out and consent_required (permission),
quiet_hours (outside_window, with retry_after), and
carrier_filtered, unreachable, invalid_number, landline,
registration_required, carrier_error and provider_not_configured
(channel_error). HIPAA accounts: hipaa_channel_not_covered
(permission). Connecting your own WhatsApp number (POST /v0/senders): whatsapp_token_malformed, whatsapp_token_rejected,
whatsapp_app_secret_malformed and whatsapp_number_not_in_account
(invalid_request), and whatsapp_number_taken,
whatsapp_account_taken and whatsapp_number_managed (permission).
Simulated conversations (permission): test_mode_only,
not_simulated, simulated_contact and simulated_conversation_limit.
For a message that failed after it was queued (message.failed):
sandbox_contact_left (permission), window_closed and
queued_too_long (outside_window), and
channel_timeout_unknown_outcome (channel_error: the channel never
confirmed the send, so it may or may not have arrived). iMessage
(unsupported_content): group_unsupported (a reaction, typing or a
read receipt in a group conversation; param kind) and
not_on_sms (an edit or unsend of a message that went by SMS or RCS;
param kind). SMS (unsupported_content): no_mms (media to a
number without MMS; param media).
Edits and unsends (unsupported_content): edit_window_closed and
unsend_window_closed (the message is older than the channel allows
for the change) and first_message_not_editable (iMessage: the message
that started the conversation). From typing and
read receipts, unknown_outcome (channel_error: the call timed out after
it was sent).
request_id string Flow's ID for this request. Quote it when asking for help.
401 application/json
The API key is missing, malformed, unknown, revoked or expired (authentication).
error ErrorBody required The details of an error.
Show child attributes
type enum<string> required What went wrong. A closed list: new types arrive only with a new API version.
Each type has a page at https://api.flow.engineer/docs/errors/<type>, given in
the error's doc_url; the error's hint says what to change for the case.
invalid_request(400, or 413 for a request body over the size limit): the request is malformed or a parameter is invalid. Fix the parameter named inparam; thehintsays what it must look like. Every endpoint answers it for aFlow-Versionheader that is not a version this API serves.authentication(401): the API key is missing, malformed, unknown, revoked or expired. AnAuthorization: Bearerheader with nothing after it usually means the environment variable holding the key was empty when the header was built (for an MCP client, when the server was added): thehintsays so. SendAuthorization: Bearer fk_test_...orfk_live_...with a current key; with no key at all, get a test key withPOST /v0/sandbox/keys. A sandbox key past itsexpires_athaschannel_codesandbox_key_expired: sign in to claim the app, or get a new key.permission(403): the key may not do this, for example a test key using a live sender, a sandbox contact who joined another app, a new conversation from an iMessage line that may only reply, or a send past the app's sandbox allowance (channel_codesandbox_allowance_used,sandbox_contact_limitorsandbox_channel_not_included). Use the key of the right mode, have the contact send your join message, wait for the contact to message the line first, or sign in through the device flow to lift the allowance. On SMS:contact_opted_out(the contact texted STOP; nothing more goes to them until they text START),consent_required(a start to someone who never texted your numbers: record their consent withconsenton the start). In a HIPAA account:hipaa_channel_not_covered(the channel has no business associate agreement; use SMS). Simulated conversations:simulated_contact(a real sender was pointed at a simulated contact; reply in the contact's simulated conversation),not_simulated(a simulated sender or test endpoint was pointed at a real contact),test_mode_only,simulated_conversation_limit. Inmessage.failed:sandbox_contact_left(the contact joined another app on the sandbox sender while the message was queued; have them send your join message again).not_found(404): no such object for this app and mode. Check the ID's prefix and that it was made with a key of the same mode (test and live data are separate).idempotency_conflict(409): the idempotency key was used for a different request (channel_codebody_mismatch: use a new key), or that request is still running (in_progress: waitretry_afterseconds and repeat it with the same key), or it already created a secret that is shown only once (secret_not_kept: creating a webhook endpoint, rotating its secret, connecting a WhatsApp number; a secret that was lost must be rotated again, and a WhatsApp number connected again to see its verify token).outside_window(409, or inmessage.failed): the channel will not deliver outside its conversation window. On WhatsApp the 24-hour window is closed: send a template (POST /v0/conversations/{conversation_id}/template, orPOST /v0/messageswithkindtemplateto start), or wait for the contact to write. On iMessage the contact has not messaged the line (or opted in) yet, or not recently, so the failure arrives as amessage.failedevent: wait for the contact to write, then reply in that conversation. Typing and read receipts answer409 outside_windowdirectly when the channel's window for them is closed (iMessage typing works only within 5 minutes of the contact's last message; WhatsApp read receipts go at any time); ignore it and send your reply. On SMS,channel_codequiet_hours: the contact has not texted in the last hour and it is inside the sender's quiet hours in their time zone; retry afterretry_afterseconds, or wait for them to text. Inmessage.failed:window_closed(WhatsApp's 24-hour window closed while the message was queued; send a template) andqueued_too_long(the message waited in the queue longer than its sender allows, for example while the sender was paused; send it again if it still applies).unsupported_content(422): the conversation's channel does not take this kind, field, block or value, and nothing was sent; Flow never converts a message.paramnames what was refused:kind(the endpoint's kind is not on this channel, such as a carousel on Telegram), a field or block (reply_toon SMS,telegramon WhatsApp,telegram.is_big), or the field whose value is not taken (emojioutside Telegram's reaction emoji,buttonsfor more than 3 WhatsApp buttons).channelis the conversation's channel andhintsays what to send instead over plain HTTP; each endpoint's page lists its channels. In an iMessage group conversation, a reaction, typing or a read receipt answers it withparamkindandchannel_codegroup_unsupported: leave it out. Media to an SMS number without MMS answers it withparammediaandchannel_codeno_mms. An edit or unsend of an iMessage message that went by SMS or RCS (itsnetwork) answers it withchannel_codenot_on_sms: send a new message instead. An edit or unsend past the channel's time limit answers it withchannel_codeedit_window_closedorunsend_window_closed(iMessage: edits within 15 minutes of sending, unsends within 2 minutes; Telegram: unsends within 48 hours), and an iMessage edit of the message that started the conversation withfirst_message_not_editable;messagestates the limit and how long ago the message was sent. Send a new message instead.POST /v0/filesanswers it for a type Flow does not take (programs, web pages, unknown types). A send byfile_idof a document a contact sent before Flow malware-scanned documents answers it too: Flow sends only documents it scanned.new_contact_limit(429): the sender has used its budget for starting conversations. Waitretry_afterseconds; replies into existing conversations still go.sender_throttled(429): abuse signals tripped (the same text to many new contacts, many starts with no reply, blocks), so the sender may not start conversations untilretry_after; replies into existing conversations still go. Personalise first messages and start only conversations people expect.file_blocked(422): the file failed the malware scan and was not stored. Send a different file. (When the scan cannot run at all, the upload answers a retryable500 api_errorwithRetry-Afterinstead.)rate_limited(429): either too many requests for this key (the per-key request limit, reported in theRateLimit-*headers) or sends faster than the sender's sending rate (pacing). WaitRetry-After(retry_after) seconds and retry with the sameIdempotency-Key.channel_error(502, or inmessage.failed): the channel refused or failed the message, or timed out;channel_codecarries its own code. Readmessage, change what the channel objected to, and send again. From typing and read receipts, which call the channel at once, it is safe to ignore. SMS codes are Flow's, the same whichever carrier network carries the message:carrier_filtered(a carrier's spam filter blocked it: make it read like a person's message, no shortened links),unreachable,invalid_number,landline,registration_required(the number's 10DLC or toll-free registration is not approved yet),carrier_error, andprovider_not_configured(the SMS sender is not fully set up on Flow's side, so nothing was sent; contact the Flow team). Flow's own codes on every channel:channel_timeout_unknown_outcome(inmessage.failed: the channel did not answer after the message was sent, and the resends its policy allows did not settle it, so the message may have arrived or not; check with the contact before sending it again) andunknown_outcome(from typing and read receipts: the call timed out after it was sent).not_implemented(501): this endpoint or channel is not live yet during the beta. Use a channel that is live (Telegram, iMessage), or check the changelog.api_error(500, 503): something went wrong on Flow's side. Retry with the same idempotency key afterretry_afterseconds, and quoterequest_idif it persists.
message string required A sentence for a developer saying what went wrong. Do not parse it; switch on type.
hint string required One sentence saying what to change to make the request succeed, specific
to this case, for example "Send a template instead: POST
/v0/conversations/{conversation_id}/template." For unsupported_content
it says what to send instead over plain HTTP. Written for developers and
coding agents alike. Do not parse it; it may be reworded at any time.
doc_url string<uri> required The documentation page for this error's type, https://api.flow.engineer/docs/errors/<type>. It says what the error means, why it happens and how to fix it, with code.
param string The request parameter the error is about, as a dotted path (kind, telegram.is_big, buttons, cards[1].header.type, limit).
retry_after integer Seconds to wait before retrying, for errors that clear by themselves.
channel enum<string> The conversation's channel, on errors about what it takes (unsupported_content) and its windows (outside_window).
conversation string A conversation ID, conv_ and a ULID.
sender string A sender ID, snd_ and a ULID.
channel_code string For channel_error, the channel's own error code, as given by the
channel. For some other errors, Flow's own code naming the case, for
example sandbox_allowance_used (permission: the sandbox allowance is
used up), sandbox_contact_limit (permission: the allowance has no
room for another contact), sandbox_channel_not_included (permission:
the allowance does not cover this sandbox channel), sign_in_required
(permission: an app made without an account cannot do this) or
sandbox_key_expired (authentication: a key from POST /v0/sandbox/keys passed its expires_at; permission, for a send
that carries no key, such as a reply in a webhook answer, from such an
app). For idempotency_conflict, always one of body_mismatch (the
key was used for a different request), in_progress (the first
request is still running; retry_after says when to repeat it) or
secret_not_kept (its answer carried a secret shown only once).
SMS: contact_opted_out and consent_required (permission),
quiet_hours (outside_window, with retry_after), and
carrier_filtered, unreachable, invalid_number, landline,
registration_required, carrier_error and provider_not_configured
(channel_error). HIPAA accounts: hipaa_channel_not_covered
(permission). Connecting your own WhatsApp number (POST /v0/senders): whatsapp_token_malformed, whatsapp_token_rejected,
whatsapp_app_secret_malformed and whatsapp_number_not_in_account
(invalid_request), and whatsapp_number_taken,
whatsapp_account_taken and whatsapp_number_managed (permission).
Simulated conversations (permission): test_mode_only,
not_simulated, simulated_contact and simulated_conversation_limit.
For a message that failed after it was queued (message.failed):
sandbox_contact_left (permission), window_closed and
queued_too_long (outside_window), and
channel_timeout_unknown_outcome (channel_error: the channel never
confirmed the send, so it may or may not have arrived). iMessage
(unsupported_content): group_unsupported (a reaction, typing or a
read receipt in a group conversation; param kind) and
not_on_sms (an edit or unsend of a message that went by SMS or RCS;
param kind). SMS (unsupported_content): no_mms (media to a
number without MMS; param media).
Edits and unsends (unsupported_content): edit_window_closed and
unsend_window_closed (the message is older than the channel allows
for the change) and first_message_not_editable (iMessage: the message
that started the conversation). From typing and
read receipts, unknown_outcome (channel_error: the call timed out after
it was sent).
request_id string Flow's ID for this request. Quote it when asking for help.
429 application/json
Too many requests (rate_limited): either this key's request limit (see the
RateLimit-* headers) or the sender's sending rate (pacing) was exceeded;
retry after Retry-After seconds (also error.retry_after) with the same
Idempotency-Key. Or the sender has used its budget for starting
conversations (new_contact_limit), or the sender is throttled after abuse
signals (sender_throttled); wait retry_after seconds.
error ErrorBody required The details of an error.
Show child attributes
type enum<string> required What went wrong. A closed list: new types arrive only with a new API version.
Each type has a page at https://api.flow.engineer/docs/errors/<type>, given in
the error's doc_url; the error's hint says what to change for the case.
invalid_request(400, or 413 for a request body over the size limit): the request is malformed or a parameter is invalid. Fix the parameter named inparam; thehintsays what it must look like. Every endpoint answers it for aFlow-Versionheader that is not a version this API serves.authentication(401): the API key is missing, malformed, unknown, revoked or expired. AnAuthorization: Bearerheader with nothing after it usually means the environment variable holding the key was empty when the header was built (for an MCP client, when the server was added): thehintsays so. SendAuthorization: Bearer fk_test_...orfk_live_...with a current key; with no key at all, get a test key withPOST /v0/sandbox/keys. A sandbox key past itsexpires_athaschannel_codesandbox_key_expired: sign in to claim the app, or get a new key.permission(403): the key may not do this, for example a test key using a live sender, a sandbox contact who joined another app, a new conversation from an iMessage line that may only reply, or a send past the app's sandbox allowance (channel_codesandbox_allowance_used,sandbox_contact_limitorsandbox_channel_not_included). Use the key of the right mode, have the contact send your join message, wait for the contact to message the line first, or sign in through the device flow to lift the allowance. On SMS:contact_opted_out(the contact texted STOP; nothing more goes to them until they text START),consent_required(a start to someone who never texted your numbers: record their consent withconsenton the start). In a HIPAA account:hipaa_channel_not_covered(the channel has no business associate agreement; use SMS). Simulated conversations:simulated_contact(a real sender was pointed at a simulated contact; reply in the contact's simulated conversation),not_simulated(a simulated sender or test endpoint was pointed at a real contact),test_mode_only,simulated_conversation_limit. Inmessage.failed:sandbox_contact_left(the contact joined another app on the sandbox sender while the message was queued; have them send your join message again).not_found(404): no such object for this app and mode. Check the ID's prefix and that it was made with a key of the same mode (test and live data are separate).idempotency_conflict(409): the idempotency key was used for a different request (channel_codebody_mismatch: use a new key), or that request is still running (in_progress: waitretry_afterseconds and repeat it with the same key), or it already created a secret that is shown only once (secret_not_kept: creating a webhook endpoint, rotating its secret, connecting a WhatsApp number; a secret that was lost must be rotated again, and a WhatsApp number connected again to see its verify token).outside_window(409, or inmessage.failed): the channel will not deliver outside its conversation window. On WhatsApp the 24-hour window is closed: send a template (POST /v0/conversations/{conversation_id}/template, orPOST /v0/messageswithkindtemplateto start), or wait for the contact to write. On iMessage the contact has not messaged the line (or opted in) yet, or not recently, so the failure arrives as amessage.failedevent: wait for the contact to write, then reply in that conversation. Typing and read receipts answer409 outside_windowdirectly when the channel's window for them is closed (iMessage typing works only within 5 minutes of the contact's last message; WhatsApp read receipts go at any time); ignore it and send your reply. On SMS,channel_codequiet_hours: the contact has not texted in the last hour and it is inside the sender's quiet hours in their time zone; retry afterretry_afterseconds, or wait for them to text. Inmessage.failed:window_closed(WhatsApp's 24-hour window closed while the message was queued; send a template) andqueued_too_long(the message waited in the queue longer than its sender allows, for example while the sender was paused; send it again if it still applies).unsupported_content(422): the conversation's channel does not take this kind, field, block or value, and nothing was sent; Flow never converts a message.paramnames what was refused:kind(the endpoint's kind is not on this channel, such as a carousel on Telegram), a field or block (reply_toon SMS,telegramon WhatsApp,telegram.is_big), or the field whose value is not taken (emojioutside Telegram's reaction emoji,buttonsfor more than 3 WhatsApp buttons).channelis the conversation's channel andhintsays what to send instead over plain HTTP; each endpoint's page lists its channels. In an iMessage group conversation, a reaction, typing or a read receipt answers it withparamkindandchannel_codegroup_unsupported: leave it out. Media to an SMS number without MMS answers it withparammediaandchannel_codeno_mms. An edit or unsend of an iMessage message that went by SMS or RCS (itsnetwork) answers it withchannel_codenot_on_sms: send a new message instead. An edit or unsend past the channel's time limit answers it withchannel_codeedit_window_closedorunsend_window_closed(iMessage: edits within 15 minutes of sending, unsends within 2 minutes; Telegram: unsends within 48 hours), and an iMessage edit of the message that started the conversation withfirst_message_not_editable;messagestates the limit and how long ago the message was sent. Send a new message instead.POST /v0/filesanswers it for a type Flow does not take (programs, web pages, unknown types). A send byfile_idof a document a contact sent before Flow malware-scanned documents answers it too: Flow sends only documents it scanned.new_contact_limit(429): the sender has used its budget for starting conversations. Waitretry_afterseconds; replies into existing conversations still go.sender_throttled(429): abuse signals tripped (the same text to many new contacts, many starts with no reply, blocks), so the sender may not start conversations untilretry_after; replies into existing conversations still go. Personalise first messages and start only conversations people expect.file_blocked(422): the file failed the malware scan and was not stored. Send a different file. (When the scan cannot run at all, the upload answers a retryable500 api_errorwithRetry-Afterinstead.)rate_limited(429): either too many requests for this key (the per-key request limit, reported in theRateLimit-*headers) or sends faster than the sender's sending rate (pacing). WaitRetry-After(retry_after) seconds and retry with the sameIdempotency-Key.channel_error(502, or inmessage.failed): the channel refused or failed the message, or timed out;channel_codecarries its own code. Readmessage, change what the channel objected to, and send again. From typing and read receipts, which call the channel at once, it is safe to ignore. SMS codes are Flow's, the same whichever carrier network carries the message:carrier_filtered(a carrier's spam filter blocked it: make it read like a person's message, no shortened links),unreachable,invalid_number,landline,registration_required(the number's 10DLC or toll-free registration is not approved yet),carrier_error, andprovider_not_configured(the SMS sender is not fully set up on Flow's side, so nothing was sent; contact the Flow team). Flow's own codes on every channel:channel_timeout_unknown_outcome(inmessage.failed: the channel did not answer after the message was sent, and the resends its policy allows did not settle it, so the message may have arrived or not; check with the contact before sending it again) andunknown_outcome(from typing and read receipts: the call timed out after it was sent).not_implemented(501): this endpoint or channel is not live yet during the beta. Use a channel that is live (Telegram, iMessage), or check the changelog.api_error(500, 503): something went wrong on Flow's side. Retry with the same idempotency key afterretry_afterseconds, and quoterequest_idif it persists.
message string required A sentence for a developer saying what went wrong. Do not parse it; switch on type.
hint string required One sentence saying what to change to make the request succeed, specific
to this case, for example "Send a template instead: POST
/v0/conversations/{conversation_id}/template." For unsupported_content
it says what to send instead over plain HTTP. Written for developers and
coding agents alike. Do not parse it; it may be reworded at any time.
doc_url string<uri> required The documentation page for this error's type, https://api.flow.engineer/docs/errors/<type>. It says what the error means, why it happens and how to fix it, with code.
param string The request parameter the error is about, as a dotted path (kind, telegram.is_big, buttons, cards[1].header.type, limit).
retry_after integer Seconds to wait before retrying, for errors that clear by themselves.
channel enum<string> The conversation's channel, on errors about what it takes (unsupported_content) and its windows (outside_window).
conversation string A conversation ID, conv_ and a ULID.
sender string A sender ID, snd_ and a ULID.
channel_code string For channel_error, the channel's own error code, as given by the
channel. For some other errors, Flow's own code naming the case, for
example sandbox_allowance_used (permission: the sandbox allowance is
used up), sandbox_contact_limit (permission: the allowance has no
room for another contact), sandbox_channel_not_included (permission:
the allowance does not cover this sandbox channel), sign_in_required
(permission: an app made without an account cannot do this) or
sandbox_key_expired (authentication: a key from POST /v0/sandbox/keys passed its expires_at; permission, for a send
that carries no key, such as a reply in a webhook answer, from such an
app). For idempotency_conflict, always one of body_mismatch (the
key was used for a different request), in_progress (the first
request is still running; retry_after says when to repeat it) or
secret_not_kept (its answer carried a secret shown only once).
SMS: contact_opted_out and consent_required (permission),
quiet_hours (outside_window, with retry_after), and
carrier_filtered, unreachable, invalid_number, landline,
registration_required, carrier_error and provider_not_configured
(channel_error). HIPAA accounts: hipaa_channel_not_covered
(permission). Connecting your own WhatsApp number (POST /v0/senders): whatsapp_token_malformed, whatsapp_token_rejected,
whatsapp_app_secret_malformed and whatsapp_number_not_in_account
(invalid_request), and whatsapp_number_taken,
whatsapp_account_taken and whatsapp_number_managed (permission).
Simulated conversations (permission): test_mode_only,
not_simulated, simulated_contact and simulated_conversation_limit.
For a message that failed after it was queued (message.failed):
sandbox_contact_left (permission), window_closed and
queued_too_long (outside_window), and
channel_timeout_unknown_outcome (channel_error: the channel never
confirmed the send, so it may or may not have arrived). iMessage
(unsupported_content): group_unsupported (a reaction, typing or a
read receipt in a group conversation; param kind) and
not_on_sms (an edit or unsend of a message that went by SMS or RCS;
param kind). SMS (unsupported_content): no_mms (media to a
number without MMS; param media).
Edits and unsends (unsupported_content): edit_window_closed and
unsend_window_closed (the message is older than the channel allows
for the change) and first_message_not_editable (iMessage: the message
that started the conversation). From typing and
read receipts, unknown_outcome (channel_error: the call timed out after
it was sent).
request_id string Flow's ID for this request. Quote it when asking for help.
default application/json
An error. error.type says which (see ErrorType).
error ErrorBody required The details of an error.
Show child attributes
type enum<string> required What went wrong. A closed list: new types arrive only with a new API version.
Each type has a page at https://api.flow.engineer/docs/errors/<type>, given in
the error's doc_url; the error's hint says what to change for the case.
invalid_request(400, or 413 for a request body over the size limit): the request is malformed or a parameter is invalid. Fix the parameter named inparam; thehintsays what it must look like. Every endpoint answers it for aFlow-Versionheader that is not a version this API serves.authentication(401): the API key is missing, malformed, unknown, revoked or expired. AnAuthorization: Bearerheader with nothing after it usually means the environment variable holding the key was empty when the header was built (for an MCP client, when the server was added): thehintsays so. SendAuthorization: Bearer fk_test_...orfk_live_...with a current key; with no key at all, get a test key withPOST /v0/sandbox/keys. A sandbox key past itsexpires_athaschannel_codesandbox_key_expired: sign in to claim the app, or get a new key.permission(403): the key may not do this, for example a test key using a live sender, a sandbox contact who joined another app, a new conversation from an iMessage line that may only reply, or a send past the app's sandbox allowance (channel_codesandbox_allowance_used,sandbox_contact_limitorsandbox_channel_not_included). Use the key of the right mode, have the contact send your join message, wait for the contact to message the line first, or sign in through the device flow to lift the allowance. On SMS:contact_opted_out(the contact texted STOP; nothing more goes to them until they text START),consent_required(a start to someone who never texted your numbers: record their consent withconsenton the start). In a HIPAA account:hipaa_channel_not_covered(the channel has no business associate agreement; use SMS). Simulated conversations:simulated_contact(a real sender was pointed at a simulated contact; reply in the contact's simulated conversation),not_simulated(a simulated sender or test endpoint was pointed at a real contact),test_mode_only,simulated_conversation_limit. Inmessage.failed:sandbox_contact_left(the contact joined another app on the sandbox sender while the message was queued; have them send your join message again).not_found(404): no such object for this app and mode. Check the ID's prefix and that it was made with a key of the same mode (test and live data are separate).idempotency_conflict(409): the idempotency key was used for a different request (channel_codebody_mismatch: use a new key), or that request is still running (in_progress: waitretry_afterseconds and repeat it with the same key), or it already created a secret that is shown only once (secret_not_kept: creating a webhook endpoint, rotating its secret, connecting a WhatsApp number; a secret that was lost must be rotated again, and a WhatsApp number connected again to see its verify token).outside_window(409, or inmessage.failed): the channel will not deliver outside its conversation window. On WhatsApp the 24-hour window is closed: send a template (POST /v0/conversations/{conversation_id}/template, orPOST /v0/messageswithkindtemplateto start), or wait for the contact to write. On iMessage the contact has not messaged the line (or opted in) yet, or not recently, so the failure arrives as amessage.failedevent: wait for the contact to write, then reply in that conversation. Typing and read receipts answer409 outside_windowdirectly when the channel's window for them is closed (iMessage typing works only within 5 minutes of the contact's last message; WhatsApp read receipts go at any time); ignore it and send your reply. On SMS,channel_codequiet_hours: the contact has not texted in the last hour and it is inside the sender's quiet hours in their time zone; retry afterretry_afterseconds, or wait for them to text. Inmessage.failed:window_closed(WhatsApp's 24-hour window closed while the message was queued; send a template) andqueued_too_long(the message waited in the queue longer than its sender allows, for example while the sender was paused; send it again if it still applies).unsupported_content(422): the conversation's channel does not take this kind, field, block or value, and nothing was sent; Flow never converts a message.paramnames what was refused:kind(the endpoint's kind is not on this channel, such as a carousel on Telegram), a field or block (reply_toon SMS,telegramon WhatsApp,telegram.is_big), or the field whose value is not taken (emojioutside Telegram's reaction emoji,buttonsfor more than 3 WhatsApp buttons).channelis the conversation's channel andhintsays what to send instead over plain HTTP; each endpoint's page lists its channels. In an iMessage group conversation, a reaction, typing or a read receipt answers it withparamkindandchannel_codegroup_unsupported: leave it out. Media to an SMS number without MMS answers it withparammediaandchannel_codeno_mms. An edit or unsend of an iMessage message that went by SMS or RCS (itsnetwork) answers it withchannel_codenot_on_sms: send a new message instead. An edit or unsend past the channel's time limit answers it withchannel_codeedit_window_closedorunsend_window_closed(iMessage: edits within 15 minutes of sending, unsends within 2 minutes; Telegram: unsends within 48 hours), and an iMessage edit of the message that started the conversation withfirst_message_not_editable;messagestates the limit and how long ago the message was sent. Send a new message instead.POST /v0/filesanswers it for a type Flow does not take (programs, web pages, unknown types). A send byfile_idof a document a contact sent before Flow malware-scanned documents answers it too: Flow sends only documents it scanned.new_contact_limit(429): the sender has used its budget for starting conversations. Waitretry_afterseconds; replies into existing conversations still go.sender_throttled(429): abuse signals tripped (the same text to many new contacts, many starts with no reply, blocks), so the sender may not start conversations untilretry_after; replies into existing conversations still go. Personalise first messages and start only conversations people expect.file_blocked(422): the file failed the malware scan and was not stored. Send a different file. (When the scan cannot run at all, the upload answers a retryable500 api_errorwithRetry-Afterinstead.)rate_limited(429): either too many requests for this key (the per-key request limit, reported in theRateLimit-*headers) or sends faster than the sender's sending rate (pacing). WaitRetry-After(retry_after) seconds and retry with the sameIdempotency-Key.channel_error(502, or inmessage.failed): the channel refused or failed the message, or timed out;channel_codecarries its own code. Readmessage, change what the channel objected to, and send again. From typing and read receipts, which call the channel at once, it is safe to ignore. SMS codes are Flow's, the same whichever carrier network carries the message:carrier_filtered(a carrier's spam filter blocked it: make it read like a person's message, no shortened links),unreachable,invalid_number,landline,registration_required(the number's 10DLC or toll-free registration is not approved yet),carrier_error, andprovider_not_configured(the SMS sender is not fully set up on Flow's side, so nothing was sent; contact the Flow team). Flow's own codes on every channel:channel_timeout_unknown_outcome(inmessage.failed: the channel did not answer after the message was sent, and the resends its policy allows did not settle it, so the message may have arrived or not; check with the contact before sending it again) andunknown_outcome(from typing and read receipts: the call timed out after it was sent).not_implemented(501): this endpoint or channel is not live yet during the beta. Use a channel that is live (Telegram, iMessage), or check the changelog.api_error(500, 503): something went wrong on Flow's side. Retry with the same idempotency key afterretry_afterseconds, and quoterequest_idif it persists.
message string required A sentence for a developer saying what went wrong. Do not parse it; switch on type.
hint string required One sentence saying what to change to make the request succeed, specific
to this case, for example "Send a template instead: POST
/v0/conversations/{conversation_id}/template." For unsupported_content
it says what to send instead over plain HTTP. Written for developers and
coding agents alike. Do not parse it; it may be reworded at any time.
doc_url string<uri> required The documentation page for this error's type, https://api.flow.engineer/docs/errors/<type>. It says what the error means, why it happens and how to fix it, with code.
param string The request parameter the error is about, as a dotted path (kind, telegram.is_big, buttons, cards[1].header.type, limit).
retry_after integer Seconds to wait before retrying, for errors that clear by themselves.
channel enum<string> The conversation's channel, on errors about what it takes (unsupported_content) and its windows (outside_window).
conversation string A conversation ID, conv_ and a ULID.
sender string A sender ID, snd_ and a ULID.
channel_code string For channel_error, the channel's own error code, as given by the
channel. For some other errors, Flow's own code naming the case, for
example sandbox_allowance_used (permission: the sandbox allowance is
used up), sandbox_contact_limit (permission: the allowance has no
room for another contact), sandbox_channel_not_included (permission:
the allowance does not cover this sandbox channel), sign_in_required
(permission: an app made without an account cannot do this) or
sandbox_key_expired (authentication: a key from POST /v0/sandbox/keys passed its expires_at; permission, for a send
that carries no key, such as a reply in a webhook answer, from such an
app). For idempotency_conflict, always one of body_mismatch (the
key was used for a different request), in_progress (the first
request is still running; retry_after says when to repeat it) or
secret_not_kept (its answer carried a secret shown only once).
SMS: contact_opted_out and consent_required (permission),
quiet_hours (outside_window, with retry_after), and
carrier_filtered, unreachable, invalid_number, landline,
registration_required, carrier_error and provider_not_configured
(channel_error). HIPAA accounts: hipaa_channel_not_covered
(permission). Connecting your own WhatsApp number (POST /v0/senders): whatsapp_token_malformed, whatsapp_token_rejected,
whatsapp_app_secret_malformed and whatsapp_number_not_in_account
(invalid_request), and whatsapp_number_taken,
whatsapp_account_taken and whatsapp_number_managed (permission).
Simulated conversations (permission): test_mode_only,
not_simulated, simulated_contact and simulated_conversation_limit.
For a message that failed after it was queued (message.failed):
sandbox_contact_left (permission), window_closed and
queued_too_long (outside_window), and
channel_timeout_unknown_outcome (channel_error: the channel never
confirmed the send, so it may or may not have arrived). iMessage
(unsupported_content): group_unsupported (a reaction, typing or a
read receipt in a group conversation; param kind) and
not_on_sms (an edit or unsend of a message that went by SMS or RCS;
param kind). SMS (unsupported_content): no_mms (media to a
number without MMS; param media).
Edits and unsends (unsupported_content): edit_window_closed and
unsend_window_closed (the message is older than the channel allows
for the change) and first_message_not_editable (iMessage: the message
that started the conversation). From typing and
read receipts, unknown_outcome (channel_error: the call timed out after
it was sent).
request_id string Flow's ID for this request. Quote it when asking for help.
Thanks, we read every comment.