Skip to content

Ask the docs

Answers come only from these docs and are written by an AI model (Gemini), so check the linked pages. Your question is not kept. Do not include personal details or keys.

GET https://api.flow.engineer/v0/events Try it

Reads your app's event log in this mode, oldest first. Pass the ID of the last event you processed as after to catch up after downtime, or to replay from any point. The log is the same one webhooks and the stream deliver from, so it is also how you recover events whose webhook deliveries failed.

The log only returns events once every event before them is committed, so reading forward with after never skips an event.

Authorizations

Authorizationstringheaderrequired

An 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

afterstring

An item ID. Returns the items that come after it in the list's order.

Length: 0 to 64

beforestring

An item ID. Returns the items that come before it in the list's order.

Length: 0 to 64

limitinteger

How many items to return, 1 to 100.

Default: 20 · Range: 1 to 100

typeenum<string>[]

Only events of these types. Repeat the parameter for several (type=message.received&type=reaction.added).

conversationstring

Only events in this conversation. An ID with no conversation of this app and mode answers 404 not_found.

Response

200 application/json

A page of events, oldest first.

data Event[] required
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.

Pattern: ^evt_[0-9A-HJKMNP-TV-Z]{26}$

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.

Pattern: ^app_[0-9A-HJKMNP-TV-Z]{26}$

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.

Pattern: ^conv_[0-9A-HJKMNP-TV-Z]{26}$

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.

Available options: direct, group · Default: "direct"

channel enum<string> required

A messaging channel. sms is US SMS and MMS (in pilot), from dedicated numbers only.

Available options: telegram, whatsapp, imessage, sms

sender string required

A sender ID, snd_ and a ULID.

Pattern: ^snd_[0-9A-HJKMNP-TV-Z]{26}$

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).

Pattern: ^ct_[0-9A-HJKMNP-TV-Z]{26}$

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.

Available options: 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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

conversation string required

A conversation ID, conv_ and a ULID.

Pattern: ^conv_[0-9A-HJKMNP-TV-Z]{26}$

direction enum<string> required

in from the contact, out from your app.

Available options: in, out

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.

Pattern: ^ct_[0-9A-HJKMNP-TV-Z]{26}$

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; see error.
  • unsent: you unsent it.

Available options: received, queued, sent, delivered, read, failed, unsent

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.

Available options: basic, voice, buttons, list, carousel, template, contact_card, location, effect, reaction, typing, read, edit, unsend, button_reply, 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).

Default: "basic"

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.

Length: 1 to 9999

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).

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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

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.

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.

Default: "voice"

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.

Pattern: ^file_[0-9A-HJKMNP-TV-Z]{26}$

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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Default: "buttons"

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.

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.

Length: 1 to 4096

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).

Items: 1 to 10

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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

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.

Default: "list"

header string

A title shown above the text, 1 to 60 characters.

Length: 1 to 60

text string required

The message's text, sent exactly as written, 1 to 4096 characters.

Length: 1 to 4096

button string required

The label of the button that opens the list, 1 to 20 characters.

Length: 1 to 20

sections ListSection[] required

1 to 10 sections, in order, with at most 10 rows in all.

Items: 1 to 10

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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Default: "carousel"

text string required

The message text shown above the cards, 1 to 1024 characters. Longer text is refused with 400 invalid_request.

Length: 1 to 1024

cards CarouselCard[] required

2 to 10 cards, in the order shown (left to right).

Items: 2 to 10

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).

Default: "template"

template_id string required

A template ID, tpl_ and a ULID.

Pattern: ^tpl_[0-9A-HJKMNP-TV-Z]{26}$

language string required

The template language to use, as Meta names it (en, en_US, hi).

params TemplateParams

Values for the template's placeholders.

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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Default: "contact_card"

name string required

The person's or business's name.

Length: 1 to any

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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Default: "location"

lat number required

Latitude in degrees.

Range: -90 to 90

lng number required

Longitude in degrees.

Range: -180 to 180

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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Default: "effect"

text string required

The text, sent exactly as written, at most 9999 characters. Longer text is refused with 400 invalid_request.

Length: 1 to 9999

effect enum<string> required

The effect.

Available options: slam, loud, gentle, invisible_ink, echo, spotlight, balloons, confetti, love, lasers, fireworks, celebration

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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Default: "reaction"

message_id string required

A message ID, msg_ and a ULID.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Length: 0 to 16

telegram ReactionTelegramOptions

Telegram only. On another channel the block answers 422 unsupported_content with param telegram; an unknown key 400 invalid_request.

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.

Default: "typing"

imessage TypingImessageOptions

iMessage only. On another channel the block answers 422 unsupported_content with param imessage; an unknown key 400 invalid_request.

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.

Default: "read"

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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Length: 1 to 9999

telegram EditTelegramOptions

Telegram only. How Telegram reads the new text. An unknown key is refused with 400 invalid_request.

imessage EditImessageOptions

iMessage only. Styles for the new text. An unknown key is refused with 400 invalid_request.

kind string required

Always edit.

message_id string required

A message ID, msg_ and a ULID.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Available options: malware, type_not_allowed, scan_failed, too_large

media_type enum<string>

What kind of file the channel said it was.

Available options: image, video, document, audio

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.

Available options: imessage, sms, rcs

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 in param; the hint says what it must look like. Every endpoint answers it for a Flow-Version header that is not a version this API serves.
  • authentication (401): the API key is missing, malformed, unknown, revoked or expired. An Authorization: Bearer header 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): the hint says so. Send Authorization: Bearer fk_test_... or fk_live_... with a current key; with no key at all, get a test key with POST /v0/sandbox/keys. A sandbox key past its expires_at has channel_code sandbox_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_code sandbox_allowance_used, sandbox_contact_limit or sandbox_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 with consent on 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. In message.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_code body_mismatch: use a new key), or that request is still running (in_progress: wait retry_after seconds 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 in message.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, or POST /v0/messages with kind template to 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 a message.failed event: wait for the contact to write, then reply in that conversation. Typing and read receipts answer 409 outside_window directly 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_code quiet_hours: the contact has not texted in the last hour and it is inside the sender's quiet hours in their time zone; retry after retry_after seconds, or wait for them to text. In message.failed: window_closed (WhatsApp's 24-hour window closed while the message was queued; send a template) and queued_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. param names what was refused: kind (the endpoint's kind is not on this channel, such as a carousel on Telegram), a field or block (reply_to on SMS, telegram on WhatsApp, telegram.is_big), or the field whose value is not taken (emoji outside Telegram's reaction emoji, buttons for more than 3 WhatsApp buttons). channel is the conversation's channel and hint says 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 with param kind and channel_code group_unsupported: leave it out. Media to an SMS number without MMS answers it with param media and channel_code no_mms. An edit or unsend of an iMessage message that went by SMS or RCS (its network) answers it with channel_code not_on_sms: send a new message instead. An edit or unsend past the channel's time limit answers it with channel_code edit_window_closed or unsend_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 with first_message_not_editable; message states the limit and how long ago the message was sent. Send a new message instead. POST /v0/files answers it for a type Flow does not take (programs, web pages, unknown types). A send by file_id of 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. Wait retry_after seconds; 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 until retry_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 retryable 500 api_error with Retry-After instead.)
  • rate_limited (429): either too many requests for this key (the per-key request limit, reported in the RateLimit-* headers) or sends faster than the sender's sending rate (pacing). Wait Retry-After (retry_after) seconds and retry with the same Idempotency-Key.
  • channel_error (502, or in message.failed): the channel refused or failed the message, or timed out; channel_code carries its own code. Read message, 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, and provider_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 (in message.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) and unknown_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 after retry_after seconds, and quote request_id if it persists.

Available options: invalid_request, authentication, permission, not_found, idempotency_conflict, outside_window, unsupported_content, new_contact_limit, sender_throttled, file_blocked, rate_limited, channel_error, not_implemented, api_error

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.

Range: 0 to any

channel enum<string>

The conversation's channel, on errors about what it takes (unsupported_content) and its windows (outside_window).

Available options: telegram, whatsapp, imessage, sms

conversation string

A conversation ID, conv_ and a ULID.

Pattern: ^conv_[0-9A-HJKMNP-TV-Z]{26}$

sender string

A sender ID, snd_ and a ULID.

Pattern: ^snd_[0-9A-HJKMNP-TV-Z]{26}$

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.

Range: 1 to any

encoding enum<string> required

gsm7 or ucs2.

Available options: gsm7, 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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Pattern: ^evt_[0-9A-HJKMNP-TV-Z]{26}$

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.

Pattern: ^app_[0-9A-HJKMNP-TV-Z]{26}$

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.

Pattern: ^conv_[0-9A-HJKMNP-TV-Z]{26}$

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.

Available options: direct, group · Default: "direct"

channel enum<string> required

A messaging channel. sms is US SMS and MMS (in pilot), from dedicated numbers only.

Available options: telegram, whatsapp, imessage, sms

sender string required

A sender ID, snd_ and a ULID.

Pattern: ^snd_[0-9A-HJKMNP-TV-Z]{26}$

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).

Pattern: ^ct_[0-9A-HJKMNP-TV-Z]{26}$

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.

Available options: 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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

conversation string required

A conversation ID, conv_ and a ULID.

Pattern: ^conv_[0-9A-HJKMNP-TV-Z]{26}$

direction enum<string> required

in from the contact, out from your app.

Available options: in, out

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.

Pattern: ^ct_[0-9A-HJKMNP-TV-Z]{26}$

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; see error.
  • unsent: you unsent it.

Available options: received, queued, sent, delivered, read, failed, unsent

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.

Available options: basic, voice, buttons, list, carousel, template, contact_card, location, effect, reaction, typing, read, edit, unsend, button_reply, 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).

Default: "basic"

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.

Length: 1 to 9999

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).

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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

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.

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.

Default: "voice"

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.

Pattern: ^file_[0-9A-HJKMNP-TV-Z]{26}$

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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Default: "buttons"

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.

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.

Length: 1 to 4096

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).

Items: 1 to 10

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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

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.

Default: "list"

header string

A title shown above the text, 1 to 60 characters.

Length: 1 to 60

text string required

The message's text, sent exactly as written, 1 to 4096 characters.

Length: 1 to 4096

button string required

The label of the button that opens the list, 1 to 20 characters.

Length: 1 to 20

sections ListSection[] required

1 to 10 sections, in order, with at most 10 rows in all.

Items: 1 to 10

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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Default: "carousel"

text string required

The message text shown above the cards, 1 to 1024 characters. Longer text is refused with 400 invalid_request.

Length: 1 to 1024

cards CarouselCard[] required

2 to 10 cards, in the order shown (left to right).

Items: 2 to 10

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).

Default: "template"

template_id string required

A template ID, tpl_ and a ULID.

Pattern: ^tpl_[0-9A-HJKMNP-TV-Z]{26}$

language string required

The template language to use, as Meta names it (en, en_US, hi).

params TemplateParams

Values for the template's placeholders.

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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Default: "contact_card"

name string required

The person's or business's name.

Length: 1 to any

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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Default: "location"

lat number required

Latitude in degrees.

Range: -90 to 90

lng number required

Longitude in degrees.

Range: -180 to 180

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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Default: "effect"

text string required

The text, sent exactly as written, at most 9999 characters. Longer text is refused with 400 invalid_request.

Length: 1 to 9999

effect enum<string> required

The effect.

Available options: slam, loud, gentle, invisible_ink, echo, spotlight, balloons, confetti, love, lasers, fireworks, celebration

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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Default: "reaction"

message_id string required

A message ID, msg_ and a ULID.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Length: 0 to 16

telegram ReactionTelegramOptions

Telegram only. On another channel the block answers 422 unsupported_content with param telegram; an unknown key 400 invalid_request.

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.

Default: "typing"

imessage TypingImessageOptions

iMessage only. On another channel the block answers 422 unsupported_content with param imessage; an unknown key 400 invalid_request.

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.

Default: "read"

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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Length: 1 to 9999

telegram EditTelegramOptions

Telegram only. How Telegram reads the new text. An unknown key is refused with 400 invalid_request.

imessage EditImessageOptions

iMessage only. Styles for the new text. An unknown key is refused with 400 invalid_request.

kind string required

Always edit.

message_id string required

A message ID, msg_ and a ULID.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Available options: malware, type_not_allowed, scan_failed, too_large

media_type enum<string>

What kind of file the channel said it was.

Available options: image, video, document, audio

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.

Available options: imessage, sms, rcs

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 in param; the hint says what it must look like. Every endpoint answers it for a Flow-Version header that is not a version this API serves.
  • authentication (401): the API key is missing, malformed, unknown, revoked or expired. An Authorization: Bearer header 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): the hint says so. Send Authorization: Bearer fk_test_... or fk_live_... with a current key; with no key at all, get a test key with POST /v0/sandbox/keys. A sandbox key past its expires_at has channel_code sandbox_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_code sandbox_allowance_used, sandbox_contact_limit or sandbox_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 with consent on 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. In message.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_code body_mismatch: use a new key), or that request is still running (in_progress: wait retry_after seconds 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 in message.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, or POST /v0/messages with kind template to 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 a message.failed event: wait for the contact to write, then reply in that conversation. Typing and read receipts answer 409 outside_window directly 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_code quiet_hours: the contact has not texted in the last hour and it is inside the sender's quiet hours in their time zone; retry after retry_after seconds, or wait for them to text. In message.failed: window_closed (WhatsApp's 24-hour window closed while the message was queued; send a template) and queued_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. param names what was refused: kind (the endpoint's kind is not on this channel, such as a carousel on Telegram), a field or block (reply_to on SMS, telegram on WhatsApp, telegram.is_big), or the field whose value is not taken (emoji outside Telegram's reaction emoji, buttons for more than 3 WhatsApp buttons). channel is the conversation's channel and hint says 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 with param kind and channel_code group_unsupported: leave it out. Media to an SMS number without MMS answers it with param media and channel_code no_mms. An edit or unsend of an iMessage message that went by SMS or RCS (its network) answers it with channel_code not_on_sms: send a new message instead. An edit or unsend past the channel's time limit answers it with channel_code edit_window_closed or unsend_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 with first_message_not_editable; message states the limit and how long ago the message was sent. Send a new message instead. POST /v0/files answers it for a type Flow does not take (programs, web pages, unknown types). A send by file_id of 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. Wait retry_after seconds; 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 until retry_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 retryable 500 api_error with Retry-After instead.)
  • rate_limited (429): either too many requests for this key (the per-key request limit, reported in the RateLimit-* headers) or sends faster than the sender's sending rate (pacing). Wait Retry-After (retry_after) seconds and retry with the same Idempotency-Key.
  • channel_error (502, or in message.failed): the channel refused or failed the message, or timed out; channel_code carries its own code. Read message, 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, and provider_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 (in message.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) and unknown_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 after retry_after seconds, and quote request_id if it persists.

Available options: invalid_request, authentication, permission, not_found, idempotency_conflict, outside_window, unsupported_content, new_contact_limit, sender_throttled, file_blocked, rate_limited, channel_error, not_implemented, api_error

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.

Range: 0 to any

channel enum<string>

The conversation's channel, on errors about what it takes (unsupported_content) and its windows (outside_window).

Available options: telegram, whatsapp, imessage, sms

conversation string

A conversation ID, conv_ and a ULID.

Pattern: ^conv_[0-9A-HJKMNP-TV-Z]{26}$

sender string

A sender ID, snd_ and a ULID.

Pattern: ^snd_[0-9A-HJKMNP-TV-Z]{26}$

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.

Range: 1 to any

encoding enum<string> required

gsm7 or ucs2.

Available options: gsm7, 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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Pattern: ^evt_[0-9A-HJKMNP-TV-Z]{26}$

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.

Pattern: ^app_[0-9A-HJKMNP-TV-Z]{26}$

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.

Pattern: ^conv_[0-9A-HJKMNP-TV-Z]{26}$

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.

Available options: direct, group · Default: "direct"

channel enum<string> required

A messaging channel. sms is US SMS and MMS (in pilot), from dedicated numbers only.

Available options: telegram, whatsapp, imessage, sms

sender string required

A sender ID, snd_ and a ULID.

Pattern: ^snd_[0-9A-HJKMNP-TV-Z]{26}$

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).

Pattern: ^ct_[0-9A-HJKMNP-TV-Z]{26}$

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.

Available options: 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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

conversation string required

A conversation ID, conv_ and a ULID.

Pattern: ^conv_[0-9A-HJKMNP-TV-Z]{26}$

direction enum<string> required

in from the contact, out from your app.

Available options: in, out

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.

Pattern: ^ct_[0-9A-HJKMNP-TV-Z]{26}$

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; see error.
  • unsent: you unsent it.

Available options: received, queued, sent, delivered, read, failed, unsent

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.

Available options: basic, voice, buttons, list, carousel, template, contact_card, location, effect, reaction, typing, read, edit, unsend, button_reply, 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).

Default: "basic"

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.

Length: 1 to 9999

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).

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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

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.

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.

Default: "voice"

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.

Pattern: ^file_[0-9A-HJKMNP-TV-Z]{26}$

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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Default: "buttons"

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.

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.

Length: 1 to 4096

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).

Items: 1 to 10

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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

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.

Default: "list"

header string

A title shown above the text, 1 to 60 characters.

Length: 1 to 60

text string required

The message's text, sent exactly as written, 1 to 4096 characters.

Length: 1 to 4096

button string required

The label of the button that opens the list, 1 to 20 characters.

Length: 1 to 20

sections ListSection[] required

1 to 10 sections, in order, with at most 10 rows in all.

Items: 1 to 10

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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Default: "carousel"

text string required

The message text shown above the cards, 1 to 1024 characters. Longer text is refused with 400 invalid_request.

Length: 1 to 1024

cards CarouselCard[] required

2 to 10 cards, in the order shown (left to right).

Items: 2 to 10

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).

Default: "template"

template_id string required

A template ID, tpl_ and a ULID.

Pattern: ^tpl_[0-9A-HJKMNP-TV-Z]{26}$

language string required

The template language to use, as Meta names it (en, en_US, hi).

params TemplateParams

Values for the template's placeholders.

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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Default: "contact_card"

name string required

The person's or business's name.

Length: 1 to any

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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Default: "location"

lat number required

Latitude in degrees.

Range: -90 to 90

lng number required

Longitude in degrees.

Range: -180 to 180

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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Default: "effect"

text string required

The text, sent exactly as written, at most 9999 characters. Longer text is refused with 400 invalid_request.

Length: 1 to 9999

effect enum<string> required

The effect.

Available options: slam, loud, gentle, invisible_ink, echo, spotlight, balloons, confetti, love, lasers, fireworks, celebration

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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Default: "reaction"

message_id string required

A message ID, msg_ and a ULID.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Length: 0 to 16

telegram ReactionTelegramOptions

Telegram only. On another channel the block answers 422 unsupported_content with param telegram; an unknown key 400 invalid_request.

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.

Default: "typing"

imessage TypingImessageOptions

iMessage only. On another channel the block answers 422 unsupported_content with param imessage; an unknown key 400 invalid_request.

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.

Default: "read"

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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Length: 1 to 9999

telegram EditTelegramOptions

Telegram only. How Telegram reads the new text. An unknown key is refused with 400 invalid_request.

imessage EditImessageOptions

iMessage only. Styles for the new text. An unknown key is refused with 400 invalid_request.

kind string required

Always edit.

message_id string required

A message ID, msg_ and a ULID.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Available options: malware, type_not_allowed, scan_failed, too_large

media_type enum<string>

What kind of file the channel said it was.

Available options: image, video, document, audio

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.

Available options: imessage, sms, rcs

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 in param; the hint says what it must look like. Every endpoint answers it for a Flow-Version header that is not a version this API serves.
  • authentication (401): the API key is missing, malformed, unknown, revoked or expired. An Authorization: Bearer header 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): the hint says so. Send Authorization: Bearer fk_test_... or fk_live_... with a current key; with no key at all, get a test key with POST /v0/sandbox/keys. A sandbox key past its expires_at has channel_code sandbox_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_code sandbox_allowance_used, sandbox_contact_limit or sandbox_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 with consent on 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. In message.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_code body_mismatch: use a new key), or that request is still running (in_progress: wait retry_after seconds 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 in message.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, or POST /v0/messages with kind template to 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 a message.failed event: wait for the contact to write, then reply in that conversation. Typing and read receipts answer 409 outside_window directly 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_code quiet_hours: the contact has not texted in the last hour and it is inside the sender's quiet hours in their time zone; retry after retry_after seconds, or wait for them to text. In message.failed: window_closed (WhatsApp's 24-hour window closed while the message was queued; send a template) and queued_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. param names what was refused: kind (the endpoint's kind is not on this channel, such as a carousel on Telegram), a field or block (reply_to on SMS, telegram on WhatsApp, telegram.is_big), or the field whose value is not taken (emoji outside Telegram's reaction emoji, buttons for more than 3 WhatsApp buttons). channel is the conversation's channel and hint says 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 with param kind and channel_code group_unsupported: leave it out. Media to an SMS number without MMS answers it with param media and channel_code no_mms. An edit or unsend of an iMessage message that went by SMS or RCS (its network) answers it with channel_code not_on_sms: send a new message instead. An edit or unsend past the channel's time limit answers it with channel_code edit_window_closed or unsend_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 with first_message_not_editable; message states the limit and how long ago the message was sent. Send a new message instead. POST /v0/files answers it for a type Flow does not take (programs, web pages, unknown types). A send by file_id of 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. Wait retry_after seconds; 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 until retry_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 retryable 500 api_error with Retry-After instead.)
  • rate_limited (429): either too many requests for this key (the per-key request limit, reported in the RateLimit-* headers) or sends faster than the sender's sending rate (pacing). Wait Retry-After (retry_after) seconds and retry with the same Idempotency-Key.
  • channel_error (502, or in message.failed): the channel refused or failed the message, or timed out; channel_code carries its own code. Read message, 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, and provider_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 (in message.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) and unknown_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 after retry_after seconds, and quote request_id if it persists.

Available options: invalid_request, authentication, permission, not_found, idempotency_conflict, outside_window, unsupported_content, new_contact_limit, sender_throttled, file_blocked, rate_limited, channel_error, not_implemented, api_error

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.

Range: 0 to any

channel enum<string>

The conversation's channel, on errors about what it takes (unsupported_content) and its windows (outside_window).

Available options: telegram, whatsapp, imessage, sms

conversation string

A conversation ID, conv_ and a ULID.

Pattern: ^conv_[0-9A-HJKMNP-TV-Z]{26}$

sender string

A sender ID, snd_ and a ULID.

Pattern: ^snd_[0-9A-HJKMNP-TV-Z]{26}$

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.

Range: 1 to any

encoding enum<string> required

gsm7 or ucs2.

Available options: gsm7, 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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Pattern: ^evt_[0-9A-HJKMNP-TV-Z]{26}$

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.

Pattern: ^app_[0-9A-HJKMNP-TV-Z]{26}$

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.

Pattern: ^conv_[0-9A-HJKMNP-TV-Z]{26}$

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.

Available options: direct, group · Default: "direct"

channel enum<string> required

A messaging channel. sms is US SMS and MMS (in pilot), from dedicated numbers only.

Available options: telegram, whatsapp, imessage, sms

sender string required

A sender ID, snd_ and a ULID.

Pattern: ^snd_[0-9A-HJKMNP-TV-Z]{26}$

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).

Pattern: ^ct_[0-9A-HJKMNP-TV-Z]{26}$

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.

Available options: 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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

conversation string required

A conversation ID, conv_ and a ULID.

Pattern: ^conv_[0-9A-HJKMNP-TV-Z]{26}$

direction enum<string> required

in from the contact, out from your app.

Available options: in, out

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.

Pattern: ^ct_[0-9A-HJKMNP-TV-Z]{26}$

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; see error.
  • unsent: you unsent it.

Available options: received, queued, sent, delivered, read, failed, unsent

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.

Available options: basic, voice, buttons, list, carousel, template, contact_card, location, effect, reaction, typing, read, edit, unsend, button_reply, 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).

Default: "basic"

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.

Length: 1 to 9999

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).

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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

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.

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.

Default: "voice"

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.

Pattern: ^file_[0-9A-HJKMNP-TV-Z]{26}$

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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Default: "buttons"

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.

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.

Length: 1 to 4096

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).

Items: 1 to 10

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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

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.

Default: "list"

header string

A title shown above the text, 1 to 60 characters.

Length: 1 to 60

text string required

The message's text, sent exactly as written, 1 to 4096 characters.

Length: 1 to 4096

button string required

The label of the button that opens the list, 1 to 20 characters.

Length: 1 to 20

sections ListSection[] required

1 to 10 sections, in order, with at most 10 rows in all.

Items: 1 to 10

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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Default: "carousel"

text string required

The message text shown above the cards, 1 to 1024 characters. Longer text is refused with 400 invalid_request.

Length: 1 to 1024

cards CarouselCard[] required

2 to 10 cards, in the order shown (left to right).

Items: 2 to 10

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).

Default: "template"

template_id string required

A template ID, tpl_ and a ULID.

Pattern: ^tpl_[0-9A-HJKMNP-TV-Z]{26}$

language string required

The template language to use, as Meta names it (en, en_US, hi).

params TemplateParams

Values for the template's placeholders.

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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Default: "contact_card"

name string required

The person's or business's name.

Length: 1 to any

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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Default: "location"

lat number required

Latitude in degrees.

Range: -90 to 90

lng number required

Longitude in degrees.

Range: -180 to 180

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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Default: "effect"

text string required

The text, sent exactly as written, at most 9999 characters. Longer text is refused with 400 invalid_request.

Length: 1 to 9999

effect enum<string> required

The effect.

Available options: slam, loud, gentle, invisible_ink, echo, spotlight, balloons, confetti, love, lasers, fireworks, celebration

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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Default: "reaction"

message_id string required

A message ID, msg_ and a ULID.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Length: 0 to 16

telegram ReactionTelegramOptions

Telegram only. On another channel the block answers 422 unsupported_content with param telegram; an unknown key 400 invalid_request.

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.

Default: "typing"

imessage TypingImessageOptions

iMessage only. On another channel the block answers 422 unsupported_content with param imessage; an unknown key 400 invalid_request.

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.

Default: "read"

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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Length: 1 to 9999

telegram EditTelegramOptions

Telegram only. How Telegram reads the new text. An unknown key is refused with 400 invalid_request.

imessage EditImessageOptions

iMessage only. Styles for the new text. An unknown key is refused with 400 invalid_request.

kind string required

Always edit.

message_id string required

A message ID, msg_ and a ULID.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Available options: malware, type_not_allowed, scan_failed, too_large

media_type enum<string>

What kind of file the channel said it was.

Available options: image, video, document, audio

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.

Available options: imessage, sms, rcs

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 in param; the hint says what it must look like. Every endpoint answers it for a Flow-Version header that is not a version this API serves.
  • authentication (401): the API key is missing, malformed, unknown, revoked or expired. An Authorization: Bearer header 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): the hint says so. Send Authorization: Bearer fk_test_... or fk_live_... with a current key; with no key at all, get a test key with POST /v0/sandbox/keys. A sandbox key past its expires_at has channel_code sandbox_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_code sandbox_allowance_used, sandbox_contact_limit or sandbox_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 with consent on 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. In message.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_code body_mismatch: use a new key), or that request is still running (in_progress: wait retry_after seconds 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 in message.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, or POST /v0/messages with kind template to 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 a message.failed event: wait for the contact to write, then reply in that conversation. Typing and read receipts answer 409 outside_window directly 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_code quiet_hours: the contact has not texted in the last hour and it is inside the sender's quiet hours in their time zone; retry after retry_after seconds, or wait for them to text. In message.failed: window_closed (WhatsApp's 24-hour window closed while the message was queued; send a template) and queued_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. param names what was refused: kind (the endpoint's kind is not on this channel, such as a carousel on Telegram), a field or block (reply_to on SMS, telegram on WhatsApp, telegram.is_big), or the field whose value is not taken (emoji outside Telegram's reaction emoji, buttons for more than 3 WhatsApp buttons). channel is the conversation's channel and hint says 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 with param kind and channel_code group_unsupported: leave it out. Media to an SMS number without MMS answers it with param media and channel_code no_mms. An edit or unsend of an iMessage message that went by SMS or RCS (its network) answers it with channel_code not_on_sms: send a new message instead. An edit or unsend past the channel's time limit answers it with channel_code edit_window_closed or unsend_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 with first_message_not_editable; message states the limit and how long ago the message was sent. Send a new message instead. POST /v0/files answers it for a type Flow does not take (programs, web pages, unknown types). A send by file_id of 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. Wait retry_after seconds; 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 until retry_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 retryable 500 api_error with Retry-After instead.)
  • rate_limited (429): either too many requests for this key (the per-key request limit, reported in the RateLimit-* headers) or sends faster than the sender's sending rate (pacing). Wait Retry-After (retry_after) seconds and retry with the same Idempotency-Key.
  • channel_error (502, or in message.failed): the channel refused or failed the message, or timed out; channel_code carries its own code. Read message, 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, and provider_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 (in message.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) and unknown_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 after retry_after seconds, and quote request_id if it persists.

Available options: invalid_request, authentication, permission, not_found, idempotency_conflict, outside_window, unsupported_content, new_contact_limit, sender_throttled, file_blocked, rate_limited, channel_error, not_implemented, api_error

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.

Range: 0 to any

channel enum<string>

The conversation's channel, on errors about what it takes (unsupported_content) and its windows (outside_window).

Available options: telegram, whatsapp, imessage, sms

conversation string

A conversation ID, conv_ and a ULID.

Pattern: ^conv_[0-9A-HJKMNP-TV-Z]{26}$

sender string

A sender ID, snd_ and a ULID.

Pattern: ^snd_[0-9A-HJKMNP-TV-Z]{26}$

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.

Range: 1 to any

encoding enum<string> required

gsm7 or ucs2.

Available options: gsm7, 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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Pattern: ^evt_[0-9A-HJKMNP-TV-Z]{26}$

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.

Pattern: ^app_[0-9A-HJKMNP-TV-Z]{26}$

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.

Pattern: ^conv_[0-9A-HJKMNP-TV-Z]{26}$

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.

Available options: direct, group · Default: "direct"

channel enum<string> required

A messaging channel. sms is US SMS and MMS (in pilot), from dedicated numbers only.

Available options: telegram, whatsapp, imessage, sms

sender string required

A sender ID, snd_ and a ULID.

Pattern: ^snd_[0-9A-HJKMNP-TV-Z]{26}$

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).

Pattern: ^ct_[0-9A-HJKMNP-TV-Z]{26}$

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.

Available options: 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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

conversation string required

A conversation ID, conv_ and a ULID.

Pattern: ^conv_[0-9A-HJKMNP-TV-Z]{26}$

direction enum<string> required

in from the contact, out from your app.

Available options: in, out

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.

Pattern: ^ct_[0-9A-HJKMNP-TV-Z]{26}$

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; see error.
  • unsent: you unsent it.

Available options: received, queued, sent, delivered, read, failed, unsent

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.

Available options: basic, voice, buttons, list, carousel, template, contact_card, location, effect, reaction, typing, read, edit, unsend, button_reply, 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).

Default: "basic"

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.

Length: 1 to 9999

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).

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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

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.

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.

Default: "voice"

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.

Pattern: ^file_[0-9A-HJKMNP-TV-Z]{26}$

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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Default: "buttons"

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.

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.

Length: 1 to 4096

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).

Items: 1 to 10

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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

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.

Default: "list"

header string

A title shown above the text, 1 to 60 characters.

Length: 1 to 60

text string required

The message's text, sent exactly as written, 1 to 4096 characters.

Length: 1 to 4096

button string required

The label of the button that opens the list, 1 to 20 characters.

Length: 1 to 20

sections ListSection[] required

1 to 10 sections, in order, with at most 10 rows in all.

Items: 1 to 10

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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Default: "carousel"

text string required

The message text shown above the cards, 1 to 1024 characters. Longer text is refused with 400 invalid_request.

Length: 1 to 1024

cards CarouselCard[] required

2 to 10 cards, in the order shown (left to right).

Items: 2 to 10

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).

Default: "template"

template_id string required

A template ID, tpl_ and a ULID.

Pattern: ^tpl_[0-9A-HJKMNP-TV-Z]{26}$

language string required

The template language to use, as Meta names it (en, en_US, hi).

params TemplateParams

Values for the template's placeholders.

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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Default: "contact_card"

name string required

The person's or business's name.

Length: 1 to any

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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Default: "location"

lat number required

Latitude in degrees.

Range: -90 to 90

lng number required

Longitude in degrees.

Range: -180 to 180

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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Default: "effect"

text string required

The text, sent exactly as written, at most 9999 characters. Longer text is refused with 400 invalid_request.

Length: 1 to 9999

effect enum<string> required

The effect.

Available options: slam, loud, gentle, invisible_ink, echo, spotlight, balloons, confetti, love, lasers, fireworks, celebration

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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Default: "reaction"

message_id string required

A message ID, msg_ and a ULID.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Length: 0 to 16

telegram ReactionTelegramOptions

Telegram only. On another channel the block answers 422 unsupported_content with param telegram; an unknown key 400 invalid_request.

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.

Default: "typing"

imessage TypingImessageOptions

iMessage only. On another channel the block answers 422 unsupported_content with param imessage; an unknown key 400 invalid_request.

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.

Default: "read"

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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Length: 1 to 9999

telegram EditTelegramOptions

Telegram only. How Telegram reads the new text. An unknown key is refused with 400 invalid_request.

imessage EditImessageOptions

iMessage only. Styles for the new text. An unknown key is refused with 400 invalid_request.

kind string required

Always edit.

message_id string required

A message ID, msg_ and a ULID.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Available options: malware, type_not_allowed, scan_failed, too_large

media_type enum<string>

What kind of file the channel said it was.

Available options: image, video, document, audio

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.

Available options: imessage, sms, rcs

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 in param; the hint says what it must look like. Every endpoint answers it for a Flow-Version header that is not a version this API serves.
  • authentication (401): the API key is missing, malformed, unknown, revoked or expired. An Authorization: Bearer header 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): the hint says so. Send Authorization: Bearer fk_test_... or fk_live_... with a current key; with no key at all, get a test key with POST /v0/sandbox/keys. A sandbox key past its expires_at has channel_code sandbox_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_code sandbox_allowance_used, sandbox_contact_limit or sandbox_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 with consent on 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. In message.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_code body_mismatch: use a new key), or that request is still running (in_progress: wait retry_after seconds 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 in message.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, or POST /v0/messages with kind template to 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 a message.failed event: wait for the contact to write, then reply in that conversation. Typing and read receipts answer 409 outside_window directly 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_code quiet_hours: the contact has not texted in the last hour and it is inside the sender's quiet hours in their time zone; retry after retry_after seconds, or wait for them to text. In message.failed: window_closed (WhatsApp's 24-hour window closed while the message was queued; send a template) and queued_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. param names what was refused: kind (the endpoint's kind is not on this channel, such as a carousel on Telegram), a field or block (reply_to on SMS, telegram on WhatsApp, telegram.is_big), or the field whose value is not taken (emoji outside Telegram's reaction emoji, buttons for more than 3 WhatsApp buttons). channel is the conversation's channel and hint says 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 with param kind and channel_code group_unsupported: leave it out. Media to an SMS number without MMS answers it with param media and channel_code no_mms. An edit or unsend of an iMessage message that went by SMS or RCS (its network) answers it with channel_code not_on_sms: send a new message instead. An edit or unsend past the channel's time limit answers it with channel_code edit_window_closed or unsend_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 with first_message_not_editable; message states the limit and how long ago the message was sent. Send a new message instead. POST /v0/files answers it for a type Flow does not take (programs, web pages, unknown types). A send by file_id of 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. Wait retry_after seconds; 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 until retry_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 retryable 500 api_error with Retry-After instead.)
  • rate_limited (429): either too many requests for this key (the per-key request limit, reported in the RateLimit-* headers) or sends faster than the sender's sending rate (pacing). Wait Retry-After (retry_after) seconds and retry with the same Idempotency-Key.
  • channel_error (502, or in message.failed): the channel refused or failed the message, or timed out; channel_code carries its own code. Read message, 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, and provider_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 (in message.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) and unknown_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 after retry_after seconds, and quote request_id if it persists.

Available options: invalid_request, authentication, permission, not_found, idempotency_conflict, outside_window, unsupported_content, new_contact_limit, sender_throttled, file_blocked, rate_limited, channel_error, not_implemented, api_error

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.

Range: 0 to any

channel enum<string>

The conversation's channel, on errors about what it takes (unsupported_content) and its windows (outside_window).

Available options: telegram, whatsapp, imessage, sms

conversation string

A conversation ID, conv_ and a ULID.

Pattern: ^conv_[0-9A-HJKMNP-TV-Z]{26}$

sender string

A sender ID, snd_ and a ULID.

Pattern: ^snd_[0-9A-HJKMNP-TV-Z]{26}$

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.

Range: 1 to any

encoding enum<string> required

gsm7 or ucs2.

Available options: gsm7, 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.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Pattern: ^evt_[0-9A-HJKMNP-TV-Z]{26}$

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.

Pattern: ^app_[0-9A-HJKMNP-TV-Z]{26}$

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.

Pattern: ^conv_[0-9A-HJKMNP-TV-Z]{26}$

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.

Available options: direct, group · Default: "direct"

channel enum<string> required

A messaging channel. sms is US SMS and MMS (in pilot), from dedicated numbers only.

Available options: telegram, whatsapp, imessage, sms

sender string required

A sender ID, snd_ and a ULID.

Pattern: ^snd_[0-9A-HJKMNP-TV-Z]{26}$

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).

Pattern: ^ct_[0-9A-HJKMNP-TV-Z]{26}$

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.

Available options: 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).

Pattern: ^ct_[0-9A-HJKMNP-TV-Z]{26}$

message_id string required

A message ID, msg_ and a ULID.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Pattern: ^evt_[0-9A-HJKMNP-TV-Z]{26}$

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.

Pattern: ^app_[0-9A-HJKMNP-TV-Z]{26}$

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.

Pattern: ^conv_[0-9A-HJKMNP-TV-Z]{26}$

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.

Available options: direct, group · Default: "direct"

channel enum<string> required

A messaging channel. sms is US SMS and MMS (in pilot), from dedicated numbers only.

Available options: telegram, whatsapp, imessage, sms

sender string required

A sender ID, snd_ and a ULID.

Pattern: ^snd_[0-9A-HJKMNP-TV-Z]{26}$

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).

Pattern: ^ct_[0-9A-HJKMNP-TV-Z]{26}$

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.

Available options: 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).

Pattern: ^ct_[0-9A-HJKMNP-TV-Z]{26}$

message_id string required

A message ID, msg_ and a ULID.

Pattern: ^msg_[0-9A-HJKMNP-TV-Z]{26}$

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.

Pattern: ^evt_[0-9A-HJKMNP-TV-Z]{26}$

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.

Pattern: ^app_[0-9A-HJKMNP-TV-Z]{26}$

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.

Pattern: ^conv_[0-9A-HJKMNP-TV-Z]{26}$

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.

Available options: direct, group · Default: "direct"

channel enum<string> required

A messaging channel. sms is US SMS and MMS (in pilot), from dedicated numbers only.

Available options: telegram, whatsapp, imessage, sms

sender string required

A sender ID, snd_ and a ULID.

Pattern: ^snd_[0-9A-HJKMNP-TV-Z]{26}$

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).

Pattern: ^ct_[0-9A-HJKMNP-TV-Z]{26}$

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.

Available options: typing.started

data TypingEventData required

Who is typing.

Show child attributes
contact string required

A contact ID, ct_ and a ULID.

Pattern: ^ct_[0-9A-HJKMNP-TV-Z]{26}$

type: typing.stopped

The contact stopped typing, where the channel reports it.

id string required

An event ID, evt_ and a ULID.

Pattern: ^evt_[0-9A-HJKMNP-TV-Z]{26}$

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.

Pattern: ^app_[0-9A-HJKMNP-TV-Z]{26}$

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.

Pattern: ^conv_[0-9A-HJKMNP-TV-Z]{26}$

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.

Available options: direct, group · Default: "direct"

channel enum<string> required

A messaging channel. sms is US SMS and MMS (in pilot), from dedicated numbers only.

Available options: telegram, whatsapp, imessage, sms

sender string required

A sender ID, snd_ and a ULID.

Pattern: ^snd_[0-9A-HJKMNP-TV-Z]{26}$

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).

Pattern: ^ct_[0-9A-HJKMNP-TV-Z]{26}$

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.

Available options: typing.stopped

data TypingEventData required

Who is typing.

Show child attributes
contact string required

A contact ID, ct_ and a ULID.

Pattern: ^ct_[0-9A-HJKMNP-TV-Z]{26}$

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.

Pattern: ^evt_[0-9A-HJKMNP-TV-Z]{26}$

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.

Pattern: ^app_[0-9A-HJKMNP-TV-Z]{26}$

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.

Pattern: ^conv_[0-9A-HJKMNP-TV-Z]{26}$

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.

Available options: direct, group · Default: "direct"

channel enum<string> required

A messaging channel. sms is US SMS and MMS (in pilot), from dedicated numbers only.

Available options: telegram, whatsapp, imessage, sms

sender string required

A sender ID, snd_ and a ULID.

Pattern: ^snd_[0-9A-HJKMNP-TV-Z]{26}$

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).

Pattern: ^ct_[0-9A-HJKMNP-TV-Z]{26}$

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.

Available options: 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.

Pattern: ^conv_[0-9A-HJKMNP-TV-Z]{26}$

app string required

An app ID, app_ and a ULID.

Pattern: ^app_[0-9A-HJKMNP-TV-Z]{26}$

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.

Available options: direct, group · Default: "direct"

channel enum<string> required

A messaging channel. sms is US SMS and MMS (in pilot), from dedicated numbers only.

Available options: telegram, whatsapp, imessage, sms

sender string required

A sender ID, snd_ and a ULID.

Pattern: ^snd_[0-9A-HJKMNP-TV-Z]{26}$

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).

Pattern: ^ct_[0-9A-HJKMNP-TV-Z]{26}$

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.

Show child attributes
name string

The group chat's name, when it has one.

participants GroupParticipant[] required

The people in the group other than your line, each a contact on the channel (made the first time Flow sees them, like a one-to-one contact).

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).

Available options: inbound, sandbox_join, outbound

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.

Pattern: ^evt_[0-9A-HJKMNP-TV-Z]{26}$

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.

Pattern: ^app_[0-9A-HJKMNP-TV-Z]{26}$

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.

Pattern: ^conv_[0-9A-HJKMNP-TV-Z]{26}$

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.

Available options: direct, group · Default: "direct"

channel enum<string> required

A messaging channel. sms is US SMS and MMS (in pilot), from dedicated numbers only.

Available options: telegram, whatsapp, imessage, sms

sender string required

A sender ID, snd_ and a ULID.

Pattern: ^snd_[0-9A-HJKMNP-TV-Z]{26}$

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).

Pattern: ^ct_[0-9A-HJKMNP-TV-Z]{26}$

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.

Available options: 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.

Pattern: ^evt_[0-9A-HJKMNP-TV-Z]{26}$

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.

Pattern: ^app_[0-9A-HJKMNP-TV-Z]{26}$

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.

Pattern: ^conv_[0-9A-HJKMNP-TV-Z]{26}$

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.

Available options: direct, group · Default: "direct"

channel enum<string> required

A messaging channel. sms is US SMS and MMS (in pilot), from dedicated numbers only.

Available options: telegram, whatsapp, imessage, sms

sender string required

A sender ID, snd_ and a ULID.

Pattern: ^snd_[0-9A-HJKMNP-TV-Z]{26}$

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).

Pattern: ^ct_[0-9A-HJKMNP-TV-Z]{26}$

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.

Available options: 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.

Pattern: ^snd_[0-9A-HJKMNP-TV-Z]{26}$

channel enum<string> required

A messaging channel. sms is US SMS and MMS (in pilot), from dedicated numbers only.

Available options: telegram, whatsapp, imessage, sms

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).

Available options: shared, dedicated, simulated

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 also flagged when Telegram rejects its token (revoked in @BotFather): it then sends nothing, new sends answer 403 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 with outside_window (channel_code queued_too_long) in message.failed instead 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.

Available options: pending, active, warming_up, throttled, flagged, banned

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.

Show child attributes
phone string

WhatsApp, iMessage and SMS. E.164 phone number.

username string

Telegram. The bot's username, without @.

handle string

iMessage. The line's handle (phone number or email address).

link string<uri>

A link that opens a chat with the sender: https://t.me/... for a Telegram bot, https://wa.me/... for a WhatsApp number. For an iMessage line it is the line's opt-in link, which opens Messages with the line and a prefilled text the person sends to start the conversation; it is set only when the line has one configured.

limits SenderLimits required

The sender's current budget for starting conversations, and how fast it sends.

Show child attributes
new_contacts_per_day integer required

New conversations the sender may start per day today (grows during warm-up).

new_contacts_per_hour integer required

New conversations the sender may start per hour.

messages_per_second number

The sender's pacing: how many messages a second it takes on average, replies included, with short bursts allowed above it. A send past it answers 429 rate_limited with retry_after. A shared sandbox sender's pacing is shared by every app that uses it. Absent for simulated senders, which are not paced.

whatsapp_tier string

WhatsApp only. Meta's messaging tier for the number.

quality_rating enum<string>

WhatsApp only. Meta's quality rating for the number.

Available options: green, yellow, red, unknown

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).

Show child attributes
number_type enum<string> required

10dlc (a local 10-digit number, registered as a brand and campaign) or toll_free (a verified toll-free number).

Available options: 10dlc, toll_free

registration_status enum<string> required

The number's carrier registration (the 10DLC campaign, or toll-free verification): pending (submitted, not approved yet; the sender is pending and sends nothing), approved, or rejected (the sender is flagged; the Flow team resubmits).

Available options: pending, approved, rejected

two_way boolean required

Whether the number receives texts. Always true for senders Flow connects today.

mms boolean required

Whether the number sends and receives MMS (images, audio and video). Without it, a basic message with media answers 422 unsupported_content with param media and channel_code no_mms.

quiet_hours QuietHours

The hours, in the contact's local time zone (worked out from their area code; when it is not known, every US time zone must be outside the hours), in which the sender does not message contacts who have not texted it in the last hour. Absent when the sender has no quiet hours. A send inside them answers 409 outside_window with channel_code quiet_hours and retry_after; a message that waited in the queue into them is held until they end. Replies to a contact who texted within the hour always go.

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.

Show child attributes
fallback boolean required

Whether messages to a contact who cannot get iMessage go by SMS or RCS instead. The network is chosen for each message when it is sent, never by your app; the message's network says which one carried it. Included on Flow's shared lines; on a dedicated line, an add-on arranged with the Flow team. Off, a message to a contact who is not on iMessage fails (message.failed). A message that goes by SMS or RCS shows no imessage.styles, imessage.subject or effect, and edits and unsends of it answer 422 unsupported_content with channel_code not_on_sms (see Message.network).

call_forwarding CallForwarding

Dedicated lines only: where phone calls to the line's number are forwarded, set up with the Flow team. Forwarded calls send no event. FaceTime calls are not forwarded; they arrive as call.received. Absent when calls are not forwarded (always on shared lines).

can_initiate boolean required

Whether the line may write first: start a conversation with POST /v0/messages with a contact who has not messaged it. Off (the default), the line only replies and a start answers 403 permission. Writing first is available on request from the Flow team, on dedicated lines only (always false on shared lines); a line that starts conversations spends its new-contact budget (limits), which grows during warm-up.

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.

Show child attributes
callback_url string<uri> required

The callback URL to enter in your Meta app (App Dashboard > WhatsApp > Configuration > Webhook).

verify_token string required

The verify token to enter with the callback URL.

webhook_fields string[] required

The webhook fields to subscribe in the same place.

steps string[] required

The steps in your Meta app, in order, as plain sentences to show a person.

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 also flagged when Telegram rejects its token (revoked in @BotFather): it then sends nothing, new sends answer 403 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 with outside_window (channel_code queued_too_long) in message.failed instead 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.

Available options: pending, active, warming_up, throttled, flagged, banned

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.

Pattern: ^evt_[0-9A-HJKMNP-TV-Z]{26}$

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.

Pattern: ^app_[0-9A-HJKMNP-TV-Z]{26}$

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.

Pattern: ^conv_[0-9A-HJKMNP-TV-Z]{26}$

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.

Available options: direct, group · Default: "direct"

channel enum<string> required

A messaging channel. sms is US SMS and MMS (in pilot), from dedicated numbers only.

Available options: telegram, whatsapp, imessage, sms

sender string required

A sender ID, snd_ and a ULID.

Pattern: ^snd_[0-9A-HJKMNP-TV-Z]{26}$

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).

Pattern: ^ct_[0-9A-HJKMNP-TV-Z]{26}$

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.

Available options: 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.

Pattern: ^tpl_[0-9A-HJKMNP-TV-Z]{26}$

sender string required

A sender ID, snd_ and a ULID.

Pattern: ^snd_[0-9A-HJKMNP-TV-Z]{26}$

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.

Available options: marketing, utility, authentication

status enum<string> required

A template's approval status at Meta.

Available options: pending, approved, rejected, paused, disabled

components TemplateComponent[] required

The template's parts, as submitted.

Show child attributes
type enum<string> required

Which part this is.

Available options: header, body, footer, buttons

format enum<string>

For headers, what the header holds.

Available options: text, image, video, document

text string

The part's text, with placeholders.

examples string[]

For a body or a text header with placeholders, a realistic sample value for each placeholder, in order ({{1}} first). WhatsApp's reviewers read the template with these values, and a template whose samples do not look real is often rejected or moved to another category, so give them for every new template. Exactly one per placeholder; each 1 to 60 characters in a header and 1 to 1024 in a body, with no line breaks, tabs or more than four spaces in a row. Left out, Flow sends WhatsApp placeholder samples (x1, x2, ...) instead. Not taken on footers, buttons parts, media headers or text without placeholders.

Items: 0 to 100

buttons TemplateButton[]

For buttons, the buttons in order.

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.

Available options: pending, approved, rejected, paused, disabled

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.

Pattern: ^evt_[0-9A-HJKMNP-TV-Z]{26}$

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.

Pattern: ^app_[0-9A-HJKMNP-TV-Z]{26}$

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.

Pattern: ^conv_[0-9A-HJKMNP-TV-Z]{26}$

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.

Available options: direct, group · Default: "direct"

channel enum<string> required

A messaging channel. sms is US SMS and MMS (in pilot), from dedicated numbers only.

Available options: telegram, whatsapp, imessage, sms

sender string required

A sender ID, snd_ and a ULID.

Pattern: ^snd_[0-9A-HJKMNP-TV-Z]{26}$

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).

Pattern: ^ct_[0-9A-HJKMNP-TV-Z]{26}$

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.

Available options: 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.

Pattern: ^ct_[0-9A-HJKMNP-TV-Z]{26}$

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.

Pattern: ^evt_[0-9A-HJKMNP-TV-Z]{26}$

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.

Pattern: ^app_[0-9A-HJKMNP-TV-Z]{26}$

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.

Pattern: ^conv_[0-9A-HJKMNP-TV-Z]{26}$

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.

Available options: direct, group · Default: "direct"

channel enum<string> required

A messaging channel. sms is US SMS and MMS (in pilot), from dedicated numbers only.

Available options: telegram, whatsapp, imessage, sms

sender string required

A sender ID, snd_ and a ULID.

Pattern: ^snd_[0-9A-HJKMNP-TV-Z]{26}$

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).

Pattern: ^ct_[0-9A-HJKMNP-TV-Z]{26}$

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.

Available options: 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.

Pattern: ^ct_[0-9A-HJKMNP-TV-Z]{26}$

sender string required

A sender ID, snd_ and a ULID.

Pattern: ^snd_[0-9A-HJKMNP-TV-Z]{26}$

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).

Available options: joined_other_app

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.

Pattern: ^evt_[0-9A-HJKMNP-TV-Z]{26}$

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.

Pattern: ^app_[0-9A-HJKMNP-TV-Z]{26}$

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.

Pattern: ^conv_[0-9A-HJKMNP-TV-Z]{26}$

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.

Available options: direct, group · Default: "direct"

channel enum<string> required

A messaging channel. sms is US SMS and MMS (in pilot), from dedicated numbers only.

Available options: telegram, whatsapp, imessage, sms

sender string required

A sender ID, snd_ and a ULID.

Pattern: ^snd_[0-9A-HJKMNP-TV-Z]{26}$

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).

Pattern: ^ct_[0-9A-HJKMNP-TV-Z]{26}$

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.

Available options: 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.

Pattern: ^ct_[0-9A-HJKMNP-TV-Z]{26}$

network enum<string> required

How they called. facetime (a FaceTime call) today.

Available options: facetime

has_more boolean required

Whether more committed events follow this page.

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 in param; the hint says what it must look like. Every endpoint answers it for a Flow-Version header that is not a version this API serves.
  • authentication (401): the API key is missing, malformed, unknown, revoked or expired. An Authorization: Bearer header 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): the hint says so. Send Authorization: Bearer fk_test_... or fk_live_... with a current key; with no key at all, get a test key with POST /v0/sandbox/keys. A sandbox key past its expires_at has channel_code sandbox_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_code sandbox_allowance_used, sandbox_contact_limit or sandbox_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 with consent on 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. In message.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_code body_mismatch: use a new key), or that request is still running (in_progress: wait retry_after seconds 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 in message.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, or POST /v0/messages with kind template to 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 a message.failed event: wait for the contact to write, then reply in that conversation. Typing and read receipts answer 409 outside_window directly 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_code quiet_hours: the contact has not texted in the last hour and it is inside the sender's quiet hours in their time zone; retry after retry_after seconds, or wait for them to text. In message.failed: window_closed (WhatsApp's 24-hour window closed while the message was queued; send a template) and queued_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. param names what was refused: kind (the endpoint's kind is not on this channel, such as a carousel on Telegram), a field or block (reply_to on SMS, telegram on WhatsApp, telegram.is_big), or the field whose value is not taken (emoji outside Telegram's reaction emoji, buttons for more than 3 WhatsApp buttons). channel is the conversation's channel and hint says 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 with param kind and channel_code group_unsupported: leave it out. Media to an SMS number without MMS answers it with param media and channel_code no_mms. An edit or unsend of an iMessage message that went by SMS or RCS (its network) answers it with channel_code not_on_sms: send a new message instead. An edit or unsend past the channel's time limit answers it with channel_code edit_window_closed or unsend_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 with first_message_not_editable; message states the limit and how long ago the message was sent. Send a new message instead. POST /v0/files answers it for a type Flow does not take (programs, web pages, unknown types). A send by file_id of 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. Wait retry_after seconds; 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 until retry_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 retryable 500 api_error with Retry-After instead.)
  • rate_limited (429): either too many requests for this key (the per-key request limit, reported in the RateLimit-* headers) or sends faster than the sender's sending rate (pacing). Wait Retry-After (retry_after) seconds and retry with the same Idempotency-Key.
  • channel_error (502, or in message.failed): the channel refused or failed the message, or timed out; channel_code carries its own code. Read message, 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, and provider_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 (in message.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) and unknown_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 after retry_after seconds, and quote request_id if it persists.

Available options: invalid_request, authentication, permission, not_found, idempotency_conflict, outside_window, unsupported_content, new_contact_limit, sender_throttled, file_blocked, rate_limited, channel_error, not_implemented, api_error

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.

Range: 0 to any

channel enum<string>

The conversation's channel, on errors about what it takes (unsupported_content) and its windows (outside_window).

Available options: telegram, whatsapp, imessage, sms

conversation string

A conversation ID, conv_ and a ULID.

Pattern: ^conv_[0-9A-HJKMNP-TV-Z]{26}$

sender string

A sender ID, snd_ and a ULID.

Pattern: ^snd_[0-9A-HJKMNP-TV-Z]{26}$

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 in param; the hint says what it must look like. Every endpoint answers it for a Flow-Version header that is not a version this API serves.
  • authentication (401): the API key is missing, malformed, unknown, revoked or expired. An Authorization: Bearer header 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): the hint says so. Send Authorization: Bearer fk_test_... or fk_live_... with a current key; with no key at all, get a test key with POST /v0/sandbox/keys. A sandbox key past its expires_at has channel_code sandbox_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_code sandbox_allowance_used, sandbox_contact_limit or sandbox_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 with consent on 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. In message.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_code body_mismatch: use a new key), or that request is still running (in_progress: wait retry_after seconds 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 in message.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, or POST /v0/messages with kind template to 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 a message.failed event: wait for the contact to write, then reply in that conversation. Typing and read receipts answer 409 outside_window directly 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_code quiet_hours: the contact has not texted in the last hour and it is inside the sender's quiet hours in their time zone; retry after retry_after seconds, or wait for them to text. In message.failed: window_closed (WhatsApp's 24-hour window closed while the message was queued; send a template) and queued_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. param names what was refused: kind (the endpoint's kind is not on this channel, such as a carousel on Telegram), a field or block (reply_to on SMS, telegram on WhatsApp, telegram.is_big), or the field whose value is not taken (emoji outside Telegram's reaction emoji, buttons for more than 3 WhatsApp buttons). channel is the conversation's channel and hint says 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 with param kind and channel_code group_unsupported: leave it out. Media to an SMS number without MMS answers it with param media and channel_code no_mms. An edit or unsend of an iMessage message that went by SMS or RCS (its network) answers it with channel_code not_on_sms: send a new message instead. An edit or unsend past the channel's time limit answers it with channel_code edit_window_closed or unsend_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 with first_message_not_editable; message states the limit and how long ago the message was sent. Send a new message instead. POST /v0/files answers it for a type Flow does not take (programs, web pages, unknown types). A send by file_id of 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. Wait retry_after seconds; 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 until retry_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 retryable 500 api_error with Retry-After instead.)
  • rate_limited (429): either too many requests for this key (the per-key request limit, reported in the RateLimit-* headers) or sends faster than the sender's sending rate (pacing). Wait Retry-After (retry_after) seconds and retry with the same Idempotency-Key.
  • channel_error (502, or in message.failed): the channel refused or failed the message, or timed out; channel_code carries its own code. Read message, 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, and provider_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 (in message.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) and unknown_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 after retry_after seconds, and quote request_id if it persists.

Available options: invalid_request, authentication, permission, not_found, idempotency_conflict, outside_window, unsupported_content, new_contact_limit, sender_throttled, file_blocked, rate_limited, channel_error, not_implemented, api_error

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.

Range: 0 to any

channel enum<string>

The conversation's channel, on errors about what it takes (unsupported_content) and its windows (outside_window).

Available options: telegram, whatsapp, imessage, sms

conversation string

A conversation ID, conv_ and a ULID.

Pattern: ^conv_[0-9A-HJKMNP-TV-Z]{26}$

sender string

A sender ID, snd_ and a ULID.

Pattern: ^snd_[0-9A-HJKMNP-TV-Z]{26}$

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.

404 application/json

No such object for this app and mode (not_found).

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 in param; the hint says what it must look like. Every endpoint answers it for a Flow-Version header that is not a version this API serves.
  • authentication (401): the API key is missing, malformed, unknown, revoked or expired. An Authorization: Bearer header 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): the hint says so. Send Authorization: Bearer fk_test_... or fk_live_... with a current key; with no key at all, get a test key with POST /v0/sandbox/keys. A sandbox key past its expires_at has channel_code sandbox_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_code sandbox_allowance_used, sandbox_contact_limit or sandbox_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 with consent on 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. In message.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_code body_mismatch: use a new key), or that request is still running (in_progress: wait retry_after seconds 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 in message.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, or POST /v0/messages with kind template to 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 a message.failed event: wait for the contact to write, then reply in that conversation. Typing and read receipts answer 409 outside_window directly 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_code quiet_hours: the contact has not texted in the last hour and it is inside the sender's quiet hours in their time zone; retry after retry_after seconds, or wait for them to text. In message.failed: window_closed (WhatsApp's 24-hour window closed while the message was queued; send a template) and queued_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. param names what was refused: kind (the endpoint's kind is not on this channel, such as a carousel on Telegram), a field or block (reply_to on SMS, telegram on WhatsApp, telegram.is_big), or the field whose value is not taken (emoji outside Telegram's reaction emoji, buttons for more than 3 WhatsApp buttons). channel is the conversation's channel and hint says 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 with param kind and channel_code group_unsupported: leave it out. Media to an SMS number without MMS answers it with param media and channel_code no_mms. An edit or unsend of an iMessage message that went by SMS or RCS (its network) answers it with channel_code not_on_sms: send a new message instead. An edit or unsend past the channel's time limit answers it with channel_code edit_window_closed or unsend_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 with first_message_not_editable; message states the limit and how long ago the message was sent. Send a new message instead. POST /v0/files answers it for a type Flow does not take (programs, web pages, unknown types). A send by file_id of 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. Wait retry_after seconds; 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 until retry_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 retryable 500 api_error with Retry-After instead.)
  • rate_limited (429): either too many requests for this key (the per-key request limit, reported in the RateLimit-* headers) or sends faster than the sender's sending rate (pacing). Wait Retry-After (retry_after) seconds and retry with the same Idempotency-Key.
  • channel_error (502, or in message.failed): the channel refused or failed the message, or timed out; channel_code carries its own code. Read message, 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, and provider_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 (in message.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) and unknown_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 after retry_after seconds, and quote request_id if it persists.

Available options: invalid_request, authentication, permission, not_found, idempotency_conflict, outside_window, unsupported_content, new_contact_limit, sender_throttled, file_blocked, rate_limited, channel_error, not_implemented, api_error

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.

Range: 0 to any

channel enum<string>

The conversation's channel, on errors about what it takes (unsupported_content) and its windows (outside_window).

Available options: telegram, whatsapp, imessage, sms

conversation string

A conversation ID, conv_ and a ULID.

Pattern: ^conv_[0-9A-HJKMNP-TV-Z]{26}$

sender string

A sender ID, snd_ and a ULID.

Pattern: ^snd_[0-9A-HJKMNP-TV-Z]{26}$

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 in param; the hint says what it must look like. Every endpoint answers it for a Flow-Version header that is not a version this API serves.
  • authentication (401): the API key is missing, malformed, unknown, revoked or expired. An Authorization: Bearer header 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): the hint says so. Send Authorization: Bearer fk_test_... or fk_live_... with a current key; with no key at all, get a test key with POST /v0/sandbox/keys. A sandbox key past its expires_at has channel_code sandbox_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_code sandbox_allowance_used, sandbox_contact_limit or sandbox_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 with consent on 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. In message.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_code body_mismatch: use a new key), or that request is still running (in_progress: wait retry_after seconds 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 in message.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, or POST /v0/messages with kind template to 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 a message.failed event: wait for the contact to write, then reply in that conversation. Typing and read receipts answer 409 outside_window directly 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_code quiet_hours: the contact has not texted in the last hour and it is inside the sender's quiet hours in their time zone; retry after retry_after seconds, or wait for them to text. In message.failed: window_closed (WhatsApp's 24-hour window closed while the message was queued; send a template) and queued_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. param names what was refused: kind (the endpoint's kind is not on this channel, such as a carousel on Telegram), a field or block (reply_to on SMS, telegram on WhatsApp, telegram.is_big), or the field whose value is not taken (emoji outside Telegram's reaction emoji, buttons for more than 3 WhatsApp buttons). channel is the conversation's channel and hint says 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 with param kind and channel_code group_unsupported: leave it out. Media to an SMS number without MMS answers it with param media and channel_code no_mms. An edit or unsend of an iMessage message that went by SMS or RCS (its network) answers it with channel_code not_on_sms: send a new message instead. An edit or unsend past the channel's time limit answers it with channel_code edit_window_closed or unsend_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 with first_message_not_editable; message states the limit and how long ago the message was sent. Send a new message instead. POST /v0/files answers it for a type Flow does not take (programs, web pages, unknown types). A send by file_id of 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. Wait retry_after seconds; 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 until retry_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 retryable 500 api_error with Retry-After instead.)
  • rate_limited (429): either too many requests for this key (the per-key request limit, reported in the RateLimit-* headers) or sends faster than the sender's sending rate (pacing). Wait Retry-After (retry_after) seconds and retry with the same Idempotency-Key.
  • channel_error (502, or in message.failed): the channel refused or failed the message, or timed out; channel_code carries its own code. Read message, 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, and provider_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 (in message.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) and unknown_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 after retry_after seconds, and quote request_id if it persists.

Available options: invalid_request, authentication, permission, not_found, idempotency_conflict, outside_window, unsupported_content, new_contact_limit, sender_throttled, file_blocked, rate_limited, channel_error, not_implemented, api_error

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.

Range: 0 to any

channel enum<string>

The conversation's channel, on errors about what it takes (unsupported_content) and its windows (outside_window).

Available options: telegram, whatsapp, imessage, sms

conversation string

A conversation ID, conv_ and a ULID.

Pattern: ^conv_[0-9A-HJKMNP-TV-Z]{26}$

sender string

A sender ID, snd_ and a ULID.

Pattern: ^snd_[0-9A-HJKMNP-TV-Z]{26}$

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 in param; the hint says what it must look like. Every endpoint answers it for a Flow-Version header that is not a version this API serves.
  • authentication (401): the API key is missing, malformed, unknown, revoked or expired. An Authorization: Bearer header 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): the hint says so. Send Authorization: Bearer fk_test_... or fk_live_... with a current key; with no key at all, get a test key with POST /v0/sandbox/keys. A sandbox key past its expires_at has channel_code sandbox_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_code sandbox_allowance_used, sandbox_contact_limit or sandbox_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 with consent on 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. In message.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_code body_mismatch: use a new key), or that request is still running (in_progress: wait retry_after seconds 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 in message.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, or POST /v0/messages with kind template to 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 a message.failed event: wait for the contact to write, then reply in that conversation. Typing and read receipts answer 409 outside_window directly 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_code quiet_hours: the contact has not texted in the last hour and it is inside the sender's quiet hours in their time zone; retry after retry_after seconds, or wait for them to text. In message.failed: window_closed (WhatsApp's 24-hour window closed while the message was queued; send a template) and queued_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. param names what was refused: kind (the endpoint's kind is not on this channel, such as a carousel on Telegram), a field or block (reply_to on SMS, telegram on WhatsApp, telegram.is_big), or the field whose value is not taken (emoji outside Telegram's reaction emoji, buttons for more than 3 WhatsApp buttons). channel is the conversation's channel and hint says 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 with param kind and channel_code group_unsupported: leave it out. Media to an SMS number without MMS answers it with param media and channel_code no_mms. An edit or unsend of an iMessage message that went by SMS or RCS (its network) answers it with channel_code not_on_sms: send a new message instead. An edit or unsend past the channel's time limit answers it with channel_code edit_window_closed or unsend_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 with first_message_not_editable; message states the limit and how long ago the message was sent. Send a new message instead. POST /v0/files answers it for a type Flow does not take (programs, web pages, unknown types). A send by file_id of 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. Wait retry_after seconds; 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 until retry_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 retryable 500 api_error with Retry-After instead.)
  • rate_limited (429): either too many requests for this key (the per-key request limit, reported in the RateLimit-* headers) or sends faster than the sender's sending rate (pacing). Wait Retry-After (retry_after) seconds and retry with the same Idempotency-Key.
  • channel_error (502, or in message.failed): the channel refused or failed the message, or timed out; channel_code carries its own code. Read message, 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, and provider_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 (in message.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) and unknown_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 after retry_after seconds, and quote request_id if it persists.

Available options: invalid_request, authentication, permission, not_found, idempotency_conflict, outside_window, unsupported_content, new_contact_limit, sender_throttled, file_blocked, rate_limited, channel_error, not_implemented, api_error

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.

Range: 0 to any

channel enum<string>

The conversation's channel, on errors about what it takes (unsupported_content) and its windows (outside_window).

Available options: telegram, whatsapp, imessage, sms

conversation string

A conversation ID, conv_ and a ULID.

Pattern: ^conv_[0-9A-HJKMNP-TV-Z]{26}$

sender string

A sender ID, snd_ and a ULID.

Pattern: ^snd_[0-9A-HJKMNP-TV-Z]{26}$

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.