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.

POST https://api.flow.engineer/v0/templates Try it

Submits a WhatsApp message template for review for one of your dedicated WhatsApp senders. It starts as pending; you receive template.status_changed when WhatsApp approves, rejects or pauses it. Templates are WhatsApp only: a Telegram or iMessage sender gets 422 unsupported_content, and a name and language the sender already has gets 400 invalid_request. Give a realistic sample for every placeholder (examples on the body and a text header, example on a url button whose link ends in {{1}}): WhatsApp reviews the template with them. A body that starts or ends with a placeholder, or has two placeholders side by side, gets 400 invalid_request (param the body's text), since Meta rejects such templates in review.

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.

Idempotency-Keystring

A unique string (up to 255 characters) that makes this request safe to retry. A repeat with the same key within 24 hours returns the first answer instead of acting again. See "Idempotency" in the introduction.

Length: 1 to 255

Body application/json

sender string required

A sender ID, snd_ and a ULID.

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

name string required

Lowercase letters, digits and underscores.

Pattern: ^[a-z0-9_]{1,512}$

language string required

The template's language, as Meta names it (en, en_US, hi).

category enum<string> required

Meta's category for the template, which sets its price.

Available options: marketing, utility, authentication

components TemplateComponent[] required

The template's parts. A body is required.

Items: 1 to any

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.

Show child attributes
type enum<string> required

What the button does.

Available options: quick_reply, url, phone_number

text string required

The button's label.

url string

For url buttons, the link, optionally ending in a {{1}} placeholder.

example string

For a url button whose link ends in {{1}}, a realistic sample value for the end of the link (what you will pass in params.buttons when sending), for WhatsApp's review: Flow shows the reviewers the link with the sample in place. That link is at most 2000 characters, and the sample has no spaces or line breaks. Left out, Flow sends a placeholder sample instead. Not taken on other buttons.

Length: 1 to 2000

phone string

For phone_number buttons, the E.164 number.

Response

201 application/json

The template was submitted.

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.

Show child attributes
type enum<string> required

What the button does.

Available options: quick_reply, url, phone_number

text string required

The button's label.

url string

For url buttons, the link, optionally ending in a {{1}} placeholder.

example string

For a url button whose link ends in {{1}}, a realistic sample value for the end of the link (what you will pass in params.buttons when sending), for WhatsApp's review: Flow shows the reviewers the link with the sample in place. That link is at most 2000 characters, and the sample has no spaces or line breaks. Left out, Flow sends a placeholder sample instead. Not taken on other buttons.

Length: 1 to 2000

phone string

For phone_number buttons, the E.164 number.

rejection_reason string

Meta's reason, when the template was rejected: its code, then : and Meta's words when it sent some, such as INVALID_FORMAT: The template has placeholders next to each other. rejection holds the same in parts, with Meta's recommendation.

rejection TemplateRejection

Why Meta rejected the template, as Meta's template status update said it. Present only while the template is rejected; each field only when Meta sent it.

Show child attributes
code string

Meta's reason code, such as INVALID_FORMAT, INCORRECT_CATEGORY or ABUSIVE_CONTENT.

title string

Meta's heading for the problem.

description string

Meta's words on what is wrong with the template.

recommendation string

Meta's suggestion for fixing the template before submitting it again.

created_at string<date-time> required

When the template was submitted, or when Flow first read it from Meta.

source enum<string>

Where the template was made: flow through this API or the dashboard, whatsapp_manager in Meta's WhatsApp Manager (Flow synced it). Both are sent the same way; Flow never deletes a whatsapp_manager template at Meta.

Available options: flow, whatsapp_manager

synced_at string<date-time> | null<date-time>

When Flow last read this template from Meta; null when it never did (a template made through Flow and not synced since).

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 account's HIPAA setting does not allow the channel; 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, so the failure arrives as a message.failed event: wait for the contact to write, then reply in that conversation. iMessage has no 24-hour window: once someone has texted your line, your agent can message them any time. Typing and read receipts answer 409 outside_window directly when the channel's window for them is closed; ignore it and send your reply. On iMessage typing and read receipts work only in a two-way conversation, for 5 minutes after your line's last message to the contact, so send your reply first. On WhatsApp read receipts go at any time. 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. Removing a reaction on iMessage (emoji null) when you have none on the message answers it with param emoji and channel_code no_reaction_to_remove: iMessage removes a reaction by naming it. 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. Flow's template on the shared WhatsApp sandbox sender answers it with param params.body[0] and channel_code template_value_not_taken when your message has a line break or tab, more than four spaces in a row, or makes the template longer than WhatsApp's 1024 characters: write it as one shorter line.
  • 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. On Flow's shared WhatsApp sandbox sender, channel_code flow_template_limit: your app already sent Flow's template to this contact in the last 24 hours; wait for them to reply, or send it after retry_after seconds.
  • 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 (permission): imessage_not_granted (the account has no access to Flow's shared line: sign in at flow.engineer to start the free trial). Paid plans (permission): plan_people_limit (a send to a person the account's plan holds: the plan had no room for new people on that channel when they wrote; see Account.people, and GET /v0/app's plans for the bigger plan). plan_ended (authentication for a live key, permission for a live send: the account's paid plan ended, so its live keys and sends are paused until the workspace owner chooses a plan again; test keys keep working). imessage_only (retired 27 Oct 2026; no longer sent: shared lines fall back to SMS/RCS). 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) and line_contact_card_dedicated_only (imessage.line_contact_card on a shared line; param imessage.line_contact_card). 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 account's HIPAA setting does not allow the channel; 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, so the failure arrives as a message.failed event: wait for the contact to write, then reply in that conversation. iMessage has no 24-hour window: once someone has texted your line, your agent can message them any time. Typing and read receipts answer 409 outside_window directly when the channel's window for them is closed; ignore it and send your reply. On iMessage typing and read receipts work only in a two-way conversation, for 5 minutes after your line's last message to the contact, so send your reply first. On WhatsApp read receipts go at any time. 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. Removing a reaction on iMessage (emoji null) when you have none on the message answers it with param emoji and channel_code no_reaction_to_remove: iMessage removes a reaction by naming it. 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. Flow's template on the shared WhatsApp sandbox sender answers it with param params.body[0] and channel_code template_value_not_taken when your message has a line break or tab, more than four spaces in a row, or makes the template longer than WhatsApp's 1024 characters: write it as one shorter line.
  • 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. On Flow's shared WhatsApp sandbox sender, channel_code flow_template_limit: your app already sent Flow's template to this contact in the last 24 hours; wait for them to reply, or send it after retry_after seconds.
  • 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 (permission): imessage_not_granted (the account has no access to Flow's shared line: sign in at flow.engineer to start the free trial). Paid plans (permission): plan_people_limit (a send to a person the account's plan holds: the plan had no room for new people on that channel when they wrote; see Account.people, and GET /v0/app's plans for the bigger plan). plan_ended (authentication for a live key, permission for a live send: the account's paid plan ended, so its live keys and sends are paused until the workspace owner chooses a plan again; test keys keep working). imessage_only (retired 27 Oct 2026; no longer sent: shared lines fall back to SMS/RCS). 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) and line_contact_card_dedicated_only (imessage.line_contact_card on a shared line; param imessage.line_contact_card). 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.

403 application/json

The key may not do this, for example a test key using a live sender (permission).

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 account's HIPAA setting does not allow the channel; 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, so the failure arrives as a message.failed event: wait for the contact to write, then reply in that conversation. iMessage has no 24-hour window: once someone has texted your line, your agent can message them any time. Typing and read receipts answer 409 outside_window directly when the channel's window for them is closed; ignore it and send your reply. On iMessage typing and read receipts work only in a two-way conversation, for 5 minutes after your line's last message to the contact, so send your reply first. On WhatsApp read receipts go at any time. 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. Removing a reaction on iMessage (emoji null) when you have none on the message answers it with param emoji and channel_code no_reaction_to_remove: iMessage removes a reaction by naming it. 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. Flow's template on the shared WhatsApp sandbox sender answers it with param params.body[0] and channel_code template_value_not_taken when your message has a line break or tab, more than four spaces in a row, or makes the template longer than WhatsApp's 1024 characters: write it as one shorter line.
  • 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. On Flow's shared WhatsApp sandbox sender, channel_code flow_template_limit: your app already sent Flow's template to this contact in the last 24 hours; wait for them to reply, or send it after retry_after seconds.
  • 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 (permission): imessage_not_granted (the account has no access to Flow's shared line: sign in at flow.engineer to start the free trial). Paid plans (permission): plan_people_limit (a send to a person the account's plan holds: the plan had no room for new people on that channel when they wrote; see Account.people, and GET /v0/app's plans for the bigger plan). plan_ended (authentication for a live key, permission for a live send: the account's paid plan ended, so its live keys and sends are paused until the workspace owner chooses a plan again; test keys keep working). imessage_only (retired 27 Oct 2026; no longer sent: shared lines fall back to SMS/RCS). 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) and line_contact_card_dedicated_only (imessage.line_contact_card on a shared line; param imessage.line_contact_card). 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 account's HIPAA setting does not allow the channel; 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, so the failure arrives as a message.failed event: wait for the contact to write, then reply in that conversation. iMessage has no 24-hour window: once someone has texted your line, your agent can message them any time. Typing and read receipts answer 409 outside_window directly when the channel's window for them is closed; ignore it and send your reply. On iMessage typing and read receipts work only in a two-way conversation, for 5 minutes after your line's last message to the contact, so send your reply first. On WhatsApp read receipts go at any time. 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. Removing a reaction on iMessage (emoji null) when you have none on the message answers it with param emoji and channel_code no_reaction_to_remove: iMessage removes a reaction by naming it. 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. Flow's template on the shared WhatsApp sandbox sender answers it with param params.body[0] and channel_code template_value_not_taken when your message has a line break or tab, more than four spaces in a row, or makes the template longer than WhatsApp's 1024 characters: write it as one shorter line.
  • 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. On Flow's shared WhatsApp sandbox sender, channel_code flow_template_limit: your app already sent Flow's template to this contact in the last 24 hours; wait for them to reply, or send it after retry_after seconds.
  • 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 (permission): imessage_not_granted (the account has no access to Flow's shared line: sign in at flow.engineer to start the free trial). Paid plans (permission): plan_people_limit (a send to a person the account's plan holds: the plan had no room for new people on that channel when they wrote; see Account.people, and GET /v0/app's plans for the bigger plan). plan_ended (authentication for a live key, permission for a live send: the account's paid plan ended, so its live keys and sends are paused until the workspace owner chooses a plan again; test keys keep working). imessage_only (retired 27 Oct 2026; no longer sent: shared lines fall back to SMS/RCS). 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) and line_contact_card_dedicated_only (imessage.line_contact_card on a shared line; param imessage.line_contact_card). 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.

409 application/json

The Idempotency-Key was already used (idempotency_conflict): channel_code body_mismatch (a different method, path or body; use a new key), in_progress (the first request with the key is still running; repeat it after retry_after seconds), or secret_not_kept (the first request went through and its answer carried a secret shown only once). See "Idempotency" in the introduction.

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 account's HIPAA setting does not allow the channel; 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, so the failure arrives as a message.failed event: wait for the contact to write, then reply in that conversation. iMessage has no 24-hour window: once someone has texted your line, your agent can message them any time. Typing and read receipts answer 409 outside_window directly when the channel's window for them is closed; ignore it and send your reply. On iMessage typing and read receipts work only in a two-way conversation, for 5 minutes after your line's last message to the contact, so send your reply first. On WhatsApp read receipts go at any time. 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. Removing a reaction on iMessage (emoji null) when you have none on the message answers it with param emoji and channel_code no_reaction_to_remove: iMessage removes a reaction by naming it. 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. Flow's template on the shared WhatsApp sandbox sender answers it with param params.body[0] and channel_code template_value_not_taken when your message has a line break or tab, more than four spaces in a row, or makes the template longer than WhatsApp's 1024 characters: write it as one shorter line.
  • 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. On Flow's shared WhatsApp sandbox sender, channel_code flow_template_limit: your app already sent Flow's template to this contact in the last 24 hours; wait for them to reply, or send it after retry_after seconds.
  • 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 (permission): imessage_not_granted (the account has no access to Flow's shared line: sign in at flow.engineer to start the free trial). Paid plans (permission): plan_people_limit (a send to a person the account's plan holds: the plan had no room for new people on that channel when they wrote; see Account.people, and GET /v0/app's plans for the bigger plan). plan_ended (authentication for a live key, permission for a live send: the account's paid plan ended, so its live keys and sends are paused until the workspace owner chooses a plan again; test keys keep working). imessage_only (retired 27 Oct 2026; no longer sent: shared lines fall back to SMS/RCS). 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) and line_contact_card_dedicated_only (imessage.line_contact_card on a shared line; param imessage.line_contact_card). 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.

413 application/json

The request body is larger than Flow takes (invalid_request): 32 MB, or 101 MB for POST /v0/files (a 100 MB file and its multipart framing). A body that declares a larger Content-Length is refused before it is read. Send media as a file_id from POST /v0/files or by url instead of inlining it.

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 account's HIPAA setting does not allow the channel; 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, so the failure arrives as a message.failed event: wait for the contact to write, then reply in that conversation. iMessage has no 24-hour window: once someone has texted your line, your agent can message them any time. Typing and read receipts answer 409 outside_window directly when the channel's window for them is closed; ignore it and send your reply. On iMessage typing and read receipts work only in a two-way conversation, for 5 minutes after your line's last message to the contact, so send your reply first. On WhatsApp read receipts go at any time. 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. Removing a reaction on iMessage (emoji null) when you have none on the message answers it with param emoji and channel_code no_reaction_to_remove: iMessage removes a reaction by naming it. 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. Flow's template on the shared WhatsApp sandbox sender answers it with param params.body[0] and channel_code template_value_not_taken when your message has a line break or tab, more than four spaces in a row, or makes the template longer than WhatsApp's 1024 characters: write it as one shorter line.
  • 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. On Flow's shared WhatsApp sandbox sender, channel_code flow_template_limit: your app already sent Flow's template to this contact in the last 24 hours; wait for them to reply, or send it after retry_after seconds.
  • 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 (permission): imessage_not_granted (the account has no access to Flow's shared line: sign in at flow.engineer to start the free trial). Paid plans (permission): plan_people_limit (a send to a person the account's plan holds: the plan had no room for new people on that channel when they wrote; see Account.people, and GET /v0/app's plans for the bigger plan). plan_ended (authentication for a live key, permission for a live send: the account's paid plan ended, so its live keys and sends are paused until the workspace owner chooses a plan again; test keys keep working). imessage_only (retired 27 Oct 2026; no longer sent: shared lines fall back to SMS/RCS). 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) and line_contact_card_dedicated_only (imessage.line_contact_card on a shared line; param imessage.line_contact_card). 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.

422 application/json

The conversation's channel does not take this kind, field, block or value, so nothing was sent (unsupported_content). param names what was refused, channel the channel, and hint what to send instead.

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 account's HIPAA setting does not allow the channel; 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, so the failure arrives as a message.failed event: wait for the contact to write, then reply in that conversation. iMessage has no 24-hour window: once someone has texted your line, your agent can message them any time. Typing and read receipts answer 409 outside_window directly when the channel's window for them is closed; ignore it and send your reply. On iMessage typing and read receipts work only in a two-way conversation, for 5 minutes after your line's last message to the contact, so send your reply first. On WhatsApp read receipts go at any time. 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. Removing a reaction on iMessage (emoji null) when you have none on the message answers it with param emoji and channel_code no_reaction_to_remove: iMessage removes a reaction by naming it. 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. Flow's template on the shared WhatsApp sandbox sender answers it with param params.body[0] and channel_code template_value_not_taken when your message has a line break or tab, more than four spaces in a row, or makes the template longer than WhatsApp's 1024 characters: write it as one shorter line.
  • 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. On Flow's shared WhatsApp sandbox sender, channel_code flow_template_limit: your app already sent Flow's template to this contact in the last 24 hours; wait for them to reply, or send it after retry_after seconds.
  • 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 (permission): imessage_not_granted (the account has no access to Flow's shared line: sign in at flow.engineer to start the free trial). Paid plans (permission): plan_people_limit (a send to a person the account's plan holds: the plan had no room for new people on that channel when they wrote; see Account.people, and GET /v0/app's plans for the bigger plan). plan_ended (authentication for a live key, permission for a live send: the account's paid plan ended, so its live keys and sends are paused until the workspace owner chooses a plan again; test keys keep working). imessage_only (retired 27 Oct 2026; no longer sent: shared lines fall back to SMS/RCS). 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) and line_contact_card_dedicated_only (imessage.line_contact_card on a shared line; param imessage.line_contact_card). 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 account's HIPAA setting does not allow the channel; 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, so the failure arrives as a message.failed event: wait for the contact to write, then reply in that conversation. iMessage has no 24-hour window: once someone has texted your line, your agent can message them any time. Typing and read receipts answer 409 outside_window directly when the channel's window for them is closed; ignore it and send your reply. On iMessage typing and read receipts work only in a two-way conversation, for 5 minutes after your line's last message to the contact, so send your reply first. On WhatsApp read receipts go at any time. 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. Removing a reaction on iMessage (emoji null) when you have none on the message answers it with param emoji and channel_code no_reaction_to_remove: iMessage removes a reaction by naming it. 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. Flow's template on the shared WhatsApp sandbox sender answers it with param params.body[0] and channel_code template_value_not_taken when your message has a line break or tab, more than four spaces in a row, or makes the template longer than WhatsApp's 1024 characters: write it as one shorter line.
  • 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. On Flow's shared WhatsApp sandbox sender, channel_code flow_template_limit: your app already sent Flow's template to this contact in the last 24 hours; wait for them to reply, or send it after retry_after seconds.
  • 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 (permission): imessage_not_granted (the account has no access to Flow's shared line: sign in at flow.engineer to start the free trial). Paid plans (permission): plan_people_limit (a send to a person the account's plan holds: the plan had no room for new people on that channel when they wrote; see Account.people, and GET /v0/app's plans for the bigger plan). plan_ended (authentication for a live key, permission for a live send: the account's paid plan ended, so its live keys and sends are paused until the workspace owner chooses a plan again; test keys keep working). imessage_only (retired 27 Oct 2026; no longer sent: shared lines fall back to SMS/RCS). 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) and line_contact_card_dedicated_only (imessage.line_contact_card on a shared line; param imessage.line_contact_card). 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.

502 application/json

The channel refused the call, failed or timed out (channel_error). error.channel_code carries the channel's own code where it gave one.

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 account's HIPAA setting does not allow the channel; 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, so the failure arrives as a message.failed event: wait for the contact to write, then reply in that conversation. iMessage has no 24-hour window: once someone has texted your line, your agent can message them any time. Typing and read receipts answer 409 outside_window directly when the channel's window for them is closed; ignore it and send your reply. On iMessage typing and read receipts work only in a two-way conversation, for 5 minutes after your line's last message to the contact, so send your reply first. On WhatsApp read receipts go at any time. 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. Removing a reaction on iMessage (emoji null) when you have none on the message answers it with param emoji and channel_code no_reaction_to_remove: iMessage removes a reaction by naming it. 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. Flow's template on the shared WhatsApp sandbox sender answers it with param params.body[0] and channel_code template_value_not_taken when your message has a line break or tab, more than four spaces in a row, or makes the template longer than WhatsApp's 1024 characters: write it as one shorter line.
  • 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. On Flow's shared WhatsApp sandbox sender, channel_code flow_template_limit: your app already sent Flow's template to this contact in the last 24 hours; wait for them to reply, or send it after retry_after seconds.
  • 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 (permission): imessage_not_granted (the account has no access to Flow's shared line: sign in at flow.engineer to start the free trial). Paid plans (permission): plan_people_limit (a send to a person the account's plan holds: the plan had no room for new people on that channel when they wrote; see Account.people, and GET /v0/app's plans for the bigger plan). plan_ended (authentication for a live key, permission for a live send: the account's paid plan ended, so its live keys and sends are paused until the workspace owner chooses a plan again; test keys keep working). imessage_only (retired 27 Oct 2026; no longer sent: shared lines fall back to SMS/RCS). 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) and line_contact_card_dedicated_only (imessage.line_contact_card on a shared line; param imessage.line_contact_card). 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 account's HIPAA setting does not allow the channel; 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, so the failure arrives as a message.failed event: wait for the contact to write, then reply in that conversation. iMessage has no 24-hour window: once someone has texted your line, your agent can message them any time. Typing and read receipts answer 409 outside_window directly when the channel's window for them is closed; ignore it and send your reply. On iMessage typing and read receipts work only in a two-way conversation, for 5 minutes after your line's last message to the contact, so send your reply first. On WhatsApp read receipts go at any time. 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. Removing a reaction on iMessage (emoji null) when you have none on the message answers it with param emoji and channel_code no_reaction_to_remove: iMessage removes a reaction by naming it. 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. Flow's template on the shared WhatsApp sandbox sender answers it with param params.body[0] and channel_code template_value_not_taken when your message has a line break or tab, more than four spaces in a row, or makes the template longer than WhatsApp's 1024 characters: write it as one shorter line.
  • 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. On Flow's shared WhatsApp sandbox sender, channel_code flow_template_limit: your app already sent Flow's template to this contact in the last 24 hours; wait for them to reply, or send it after retry_after seconds.
  • 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 (permission): imessage_not_granted (the account has no access to Flow's shared line: sign in at flow.engineer to start the free trial). Paid plans (permission): plan_people_limit (a send to a person the account's plan holds: the plan had no room for new people on that channel when they wrote; see Account.people, and GET /v0/app's plans for the bigger plan). plan_ended (authentication for a live key, permission for a live send: the account's paid plan ended, so its live keys and sends are paused until the workspace owner chooses a plan again; test keys keep working). imessage_only (retired 27 Oct 2026; no longer sent: shared lines fall back to SMS/RCS). 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) and line_contact_card_dedicated_only (imessage.line_contact_card on a shared line; param imessage.line_contact_card). 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.