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.

Poll a device sign-in for its key

POST /v0/device/token
POST https://api.flow.engineer/v0/device/token Try it

Answers the state of a sign-in started with POST /v0/device/authorizations. While the person has not finished, status is pending: wait interval seconds and poll again. Polling faster answers 429 rate_limited with retry_after. Once they approve, status is approved and the answer holds a new fk_test_ key, shown once; the device code is then used up, and later polls answer expired. If the sign-in claimed a sandbox app, that app's sandbox keys stop working when this key is handed out: replace FLOW_MESSAGING_KEY with it. (A claim through claim_url in a browser hands out no key, and revokes the sandbox key unless the person chooses to keep their agent's key working.) denied means the person refused, and expired that the code ran out (after expires_in seconds): start again. Polls are also limited per client network, unknown codes included.

Body application/json

device_code string required

The device_code from POST /v0/device/authorizations.

Response

200 application/json

The sign-in's state, with the key once it is approved.

status enum<string> required
  • pending: the person has not finished; poll again after interval seconds.
  • approved: signed in; key is set. The device code is now used up.
  • denied: the person refused. Stop polling.
  • expired: the codes ran out or were already used. Start again.

Available options: pending, approved, denied, expired

interval integer required

Seconds to wait before the next poll.

Range: 1 to any

key string

approved only. A new API key (fk_test_...), shown once; store it as FLOW_MESSAGING_KEY in place of the sandbox key. When the sign-in claimed a sandbox app, that app's sandbox keys stop working as this key is handed out.

api_key ApiKey

An API key's record. The key itself is shown once, at creation, and stored only as a hash.

Show child attributes
id string required

An API key's ID, key_ and a ULID. Not the secret key itself.

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

mode enum<string> required

test (sandbox senders and test data only) or live.

Available options: test, live

last4 string required

The key's last four characters, to tell keys apart.

Length: 4 to 4

created_at string<date-time> required

When the key was created.

expires_at string<date-time>

When the key stops working. Set only on keys of an unclaimed app made with POST /v0/sandbox/keys (7 days after it was made); signing in through the device flow claims the app and removes it. An expired key can still start that sign-in for 30 days after this time, and claiming the app within those 30 days makes the key work again.

account Account

A customer company. Holds the plan, billing and members. Every app belongs to one account.

Show child attributes
id string required

An account ID, acct_ and a ULID.

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

name string required

The company's name.

plan enum<string> required

The account's plan, set by the Flow team (there is no self-serve checkout). Each plan caps how many people your apps may talk with per channel; people shows the caps and what is used.

  • free: a small number of people per channel in total (25 today).
  • shared: Flow's shared senders, with caps per channel a day and a month (200 a day and 300 a month today).
  • dedicated: your own iMessage line, with a daily cap on it and no monthly cap (1,000 a day today); on Flow's shared senders it has shared's caps, counted apart (people shows each pool).

The numbers are settings and may change; read the caps that apply now from people, not from this list.

  • pro, business, enterprise: older plans, kept for accounts that have them; they have no people caps.

New values may be added; treat one you do not know like free.

Available options: free, shared, dedicated, pro, business, enterprise

people PeopleLimits read-only

The account's plan caps on people, and what is used now. A person counts once in a window, with their first inbound message in it on that channel's sender, whether one-to-one or in a group chat; group members who never write, calls, and your own sends do not count. A person past a cap is held: their messages are kept but not delivered to your app, and are delivered once a place frees or the window rolls over. Absent when the account's plan has no people caps. These numbers are the source of truth for the caps; they are settings and can change.

Show child attributes
per enum<string> required

channel: each channel has its own caps (every plan today). account: one set of caps over all channels, with no channel on its entry.

Available options: channel, account

channels ChannelPeople[] required

One entry per channel the caps apply to (one entry without channel when per is account).

Show child attributes
channel enum<string>

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

Available options: telegram, whatsapp, imessage, sms

pool enum<string> required

shared: Flow's shared senders on this channel. own: the account's own senders on it (a dedicated line, its own bot or number). Counted apart.

Available options: shared, own

limits PeopleWindow[] required

Each window the plan caps (free: total; shared: day and month; dedicated: day).

Show child attributes
window enum<string> required

total: since the account began (never resets). day: the UTC day. month: the UTC calendar month.

Available options: total, day, month

limit integer required

How many people may write in the window.

Range: 0 to any

used integer required

How many distinct people wrote in the window so far.

Range: 0 to any

resets_at string<date-time>

When the window rolls over and used starts again. Absent for total.

held integer required

People held now because a cap is reached; their messages wait and are delivered when a place frees or the window rolls over.

Range: 0 to any

hipaa boolean read-only

true when the account handles protected health information under a business associate agreement with Flow. Its messages then go only through channels Flow holds such an agreement for (SMS): sends, typing, read receipts and sender connections on Telegram, WhatsApp and iMessage answer 403 permission with channel_code hipaa_channel_not_covered, and messages already queued there fail the same way. The sandbox senders answer the account's contacts with a fixed refusal and store and deliver nothing they send; turning the flag on ends the account's sandbox joins. Only SMS can be simulated (POST /v0/test/conversations): creating a simulated Telegram, WhatsApp or iMessage conversation, or posting a turn to one made before the flag was set, answers the same refusal. Set by the Flow team.

created_at string<date-time> required

When the account was created.

app App

One agent integration. Owns API keys (per mode), webhook endpoints and settings.

Show child attributes
id string required

An app ID, app_ and a ULID.

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

account string required

An account ID, acct_ and a ULID.

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

name string required

The app's name.

api_version string<date> required

The API version pinned to the app when it was created, used when a request sends no Flow-Version.

settings AppSettings required

Per-app behaviour. The Flow team sets it for now; ask them to change it.

Show child attributes
transcription boolean required

Not used yet. Flow makes no transcripts of its own on any channel; on iMessage, a received voice note carries the transcript iMessage made of it, whatever this setting says.

auto_read boolean required

When on, Flow marks inbound messages as read as soon as your endpoint accepts them.

sandbox_join_code string

The app's sandbox join code on its own: two words and eight digits, for example brave-otter-40718263. A contact joins this app by sending a shared sandbox sender the join message, which ends with this code in brackets after ref (Hi Sandbox app (ref brave-otter-40718263)), or on Telegram by opening the bot's link with the code (https://t.me/<bot>?start=brave-otter-40718263, which sends /start brave-otter-40718263). Each shared sender's join_code gives the whole message: show that to testers, not this code.

created_at string<date-time> required

When the app was created.

claimed boolean

approved only. true when the sign-in claimed the app of the claim_token or key it was started with.

allowance SandboxAllowance

What the app may still send on the shared sandbox senders for free. Present only on apps that have one: apps made with POST /v0/sandbox/keys (anonymous, one allowance per app), and apps of people who signed in (signed_in, one allowance per person: every app the person owns or claimed draws on the same contacts and messages, so the counts here are the person's, over all those apps). Only messages your agent sends count, on the channels in channels; inbound messages are free. A contact counts once it joins an app on a sandbox sender, and keeps counting after it leaves. Sends past the allowance answer 403 permission with channel_code sandbox_allowance_used; a join past contacts.limit is refused in the chat. Absent on an account with the free trial (your own account, from your first sign-in), which replaces it, and on paid plans.

Show child attributes
tier enum<string> required

anonymous: an app made without an account, whose keys expire. signed_in: an app of a person who signed in (with a Flow account).

Available options: anonymous, signed_in

scope enum<string> required

Who the counts belong to. app: this app alone (anonymous). person: the signed-in person, shared by every app they own or claimed, so sends from their other apps use the same contacts and messages.

Available options: app, person

channels enum<string>[] required

The sandbox channels the allowance covers whose shared sandbox is open now (Telegram today; WhatsApp when its sandbox opens). Shared senders of other channels (iMessage) are not part of it and refuse its sends.

contacts AllowanceCount required

Contacts the allowance has room for, and how many have joined (with scope person, over all the person's apps).

Show child attributes
limit integer required

How many contacts may join on the sandbox (1 for an anonymous app; 3 for a signed-in person, over all their apps).

Range: 0 to any

used integer required

How many have joined so far.

Range: 0 to any

messages_per_contact integer required

How many messages may be sent to each contact (50 anonymous, so 50 in total; 100 signed in).

Range: 0 to any

messages AllowanceMessages required

Messages sent against the allowance, over all its contacts (with scope person, from all the person's apps).

Show child attributes
limit integer required

contacts.limit times messages_per_contact.

Range: 0 to any

used integer required

Messages sent so far on the allowance's sandbox senders.

Range: 0 to any

remaining integer required

Messages still allowed, counting each contact's own cap and the contacts that may still join.

Range: 0 to any

expires_at string<date-time>

anonymous only: when the app's keys expire. Signing in removes it.

upgrade string required

One sentence saying how to raise the allowance, for example by signing in with npx @flow-engineer/messaging login.

user SignedInUser

The person who approved a device sign-in.

Show child attributes
name string required

Their name or login at the provider.

email string<email>

Their verified email address, where the provider gave one.

provider enum<string> required

Where they signed in: flow (a Flow account, flow.engineer: every sign-in today). github and google are sign-ins made on the Messaging API itself before Flow accounts. More may be added later, so treat a value you do not know as a sign-in elsewhere rather than an error.

Available options: flow, github, google

400 application/json

The request is malformed or a parameter is invalid (invalid_request). error.param names the parameter. Every endpoint answers this for a Flow-Version header that is not a date this API serves (param Flow-Version), and every POST that takes an Idempotency-Key for a key over 255 characters (param Idempotency-Key).

error ErrorBody required

The details of an error.

Show child attributes
type enum<string> required

What went wrong. A closed list: new types arrive only with a new API version. Each type has a page at https://api.flow.engineer/docs/errors/<type>, given in the error's doc_url; the error's hint says what to change for the case.

  • invalid_request (400, or 413 for a request body over the size limit): the request is malformed or a parameter is invalid. Fix the parameter named in param; the hint says what it must look like. Every endpoint answers it for a Flow-Version header that is not a version this API serves.
  • authentication (401): the API key is missing, malformed, unknown, revoked or expired. An Authorization: Bearer header with nothing after it usually means the environment variable holding the key was empty when the header was built (for an MCP client, when the server was added): the hint says so. Send Authorization: Bearer fk_test_... or fk_live_... with a current key; with no key at all, get a test key with POST /v0/sandbox/keys. A sandbox key past its expires_at has channel_code sandbox_key_expired: sign in to claim the app, or get a new key.
  • permission (403): the key may not do this, for example a test key using a live sender, a sandbox contact who joined another app, a new conversation from an iMessage line that may only reply, or a send past the app's sandbox allowance (channel_code sandbox_allowance_used, sandbox_contact_limit or sandbox_channel_not_included). Use the key of the right mode, have the contact send your join message, wait for the contact to message the line first, or sign in through the device flow to lift the allowance. On SMS: contact_opted_out (the contact texted STOP; nothing more goes to them until they text START), consent_required (a start to someone who never texted your numbers: record their consent with consent on the start). In a HIPAA account: hipaa_channel_not_covered (the channel has no business associate agreement; use SMS). Simulated conversations: simulated_contact (a real sender was pointed at a simulated contact; reply in the contact's simulated conversation), not_simulated (a simulated sender or test endpoint was pointed at a real contact), test_mode_only, simulated_conversation_limit. In message.failed: sandbox_contact_left (the contact joined another app on the sandbox sender while the message was queued; have them send your join message again).
  • not_found (404): no such object for this app and mode. Check the ID's prefix and that it was made with a key of the same mode (test and live data are separate).
  • idempotency_conflict (409): the idempotency key was used for a different request (channel_code body_mismatch: use a new key), or that request is still running (in_progress: wait retry_after seconds and repeat it with the same key), or it already created a secret that is shown only once (secret_not_kept: creating a webhook endpoint, rotating its secret, connecting a WhatsApp number; a secret that was lost must be rotated again, and a WhatsApp number connected again to see its verify token).
  • outside_window (409, or in message.failed): the channel will not deliver outside its conversation window. On WhatsApp the 24-hour window is closed: send a template (POST /v0/conversations/{conversation_id}/template, or POST /v0/messages with kind template to start), or wait for the contact to write. On iMessage the contact has not messaged the line (or opted in) yet, or not recently, so the failure arrives as a message.failed event: wait for the contact to write, then reply in that conversation. Typing and read receipts answer 409 outside_window directly when the channel's window for them is closed (iMessage typing works only within 5 minutes of the contact's last message; WhatsApp read receipts go at any time); ignore it and send your reply. On SMS, channel_code quiet_hours: the contact has not texted in the last hour and it is inside the sender's quiet hours in their time zone; retry after retry_after seconds, or wait for them to text. In message.failed: window_closed (WhatsApp's 24-hour window closed while the message was queued; send a template) and queued_too_long (the message waited in the queue longer than its sender allows, for example while the sender was paused; send it again if it still applies).
  • unsupported_content (422): the conversation's channel does not take this kind, field, block or value, and nothing was sent; Flow never converts a message. param names what was refused: kind (the endpoint's kind is not on this channel, such as a carousel on Telegram), a field or block (reply_to on SMS, telegram on WhatsApp, telegram.is_big), or the field whose value is not taken (emoji outside Telegram's reaction emoji, buttons for more than 3 WhatsApp buttons). channel is the conversation's channel and hint says what to send instead over plain HTTP; each endpoint's page lists its channels. In an iMessage group conversation, a reaction, typing or a read receipt answers it with param kind and channel_code group_unsupported: leave it out. Media to an SMS number without MMS answers it with param media and channel_code no_mms. An edit or unsend of an iMessage message that went by SMS or RCS (its network) answers it with channel_code not_on_sms: send a new message instead. An edit or unsend past the channel's time limit answers it with channel_code edit_window_closed or unsend_window_closed (iMessage: edits within 15 minutes of sending, unsends within 2 minutes; Telegram: unsends within 48 hours), and an iMessage edit of the message that started the conversation with first_message_not_editable; message states the limit and how long ago the message was sent. Send a new message instead. POST /v0/files answers it for a type Flow does not take (programs, web pages, unknown types). A send by file_id of a document a contact sent before Flow malware-scanned documents answers it too: Flow sends only documents it scanned.
  • new_contact_limit (429): the sender has used its budget for starting conversations. Wait retry_after seconds; replies into existing conversations still go.
  • sender_throttled (429): abuse signals tripped (the same text to many new contacts, many starts with no reply, blocks), so the sender may not start conversations until retry_after; replies into existing conversations still go. Personalise first messages and start only conversations people expect.
  • file_blocked (422): the file failed the malware scan and was not stored. Send a different file. (When the scan cannot run at all, the upload answers a retryable 500 api_error with Retry-After instead.)
  • rate_limited (429): either too many requests for this key (the per-key request limit, reported in the RateLimit-* headers) or sends faster than the sender's sending rate (pacing). Wait Retry-After (retry_after) seconds and retry with the same Idempotency-Key.
  • channel_error (502, or in message.failed): the channel refused or failed the message, or timed out; channel_code carries its own code. Read message, change what the channel objected to, and send again. From typing and read receipts, which call the channel at once, it is safe to ignore. SMS codes are Flow's, the same whichever carrier network carries the message: carrier_filtered (a carrier's spam filter blocked it: make it read like a person's message, no shortened links), unreachable, invalid_number, landline, registration_required (the number's 10DLC or toll-free registration is not approved yet), carrier_error, and provider_not_configured (the SMS sender is not fully set up on Flow's side, so nothing was sent; contact the Flow team). Flow's own codes on every channel: channel_timeout_unknown_outcome (in message.failed: the channel did not answer after the message was sent, and the resends its policy allows did not settle it, so the message may have arrived or not; check with the contact before sending it again) and unknown_outcome (from typing and read receipts: the call timed out after it was sent).
  • not_implemented (501): this endpoint or channel is not live yet during the beta. Use a channel that is live (Telegram, iMessage), or check the changelog.
  • api_error (500, 503): something went wrong on Flow's side. Retry with the same idempotency key after retry_after seconds, and quote request_id if it persists.

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

message string required

A sentence for a developer saying what went wrong. Do not parse it; switch on type.

hint string required

One sentence saying what to change to make the request succeed, specific to this case, for example "Send a template instead: POST /v0/conversations/{conversation_id}/template." For unsupported_content it says what to send instead over plain HTTP. Written for developers and coding agents alike. Do not parse it; it may be reworded at any time.

doc_url string<uri> required

The documentation page for this error's type, https://api.flow.engineer/docs/errors/<type>. It says what the error means, why it happens and how to fix it, with code.

param string

The request parameter the error is about, as a dotted path (kind, telegram.is_big, buttons, cards[1].header.type, limit).

retry_after integer

Seconds to wait before retrying, for errors that clear by themselves.

Range: 0 to any

channel enum<string>

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

Available options: telegram, whatsapp, imessage, sms

conversation string

A conversation ID, conv_ and a ULID.

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

sender string

A sender ID, snd_ and a ULID.

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

channel_code string

For channel_error, the channel's own error code, as given by the channel. For some other errors, Flow's own code naming the case, for example sandbox_allowance_used (permission: the sandbox allowance is used up), sandbox_contact_limit (permission: the allowance has no room for another contact), sandbox_channel_not_included (permission: the allowance does not cover this sandbox channel), sign_in_required (permission: an app made without an account cannot do this) or sandbox_key_expired (authentication: a key from POST /v0/sandbox/keys passed its expires_at; permission, for a send that carries no key, such as a reply in a webhook answer, from such an app). For idempotency_conflict, always one of body_mismatch (the key was used for a different request), in_progress (the first request is still running; retry_after says when to repeat it) or secret_not_kept (its answer carried a secret shown only once). SMS: contact_opted_out and consent_required (permission), quiet_hours (outside_window, with retry_after), and carrier_filtered, unreachable, invalid_number, landline, registration_required, carrier_error and provider_not_configured (channel_error). HIPAA accounts: hipaa_channel_not_covered (permission). Connecting your own WhatsApp number (POST /v0/senders): whatsapp_token_malformed, whatsapp_token_rejected, whatsapp_app_secret_malformed and whatsapp_number_not_in_account (invalid_request), and whatsapp_number_taken, whatsapp_account_taken and whatsapp_number_managed (permission). Simulated conversations (permission): test_mode_only, not_simulated, simulated_contact and simulated_conversation_limit. For a message that failed after it was queued (message.failed): sandbox_contact_left (permission), window_closed and queued_too_long (outside_window), and channel_timeout_unknown_outcome (channel_error: the channel never confirmed the send, so it may or may not have arrived). iMessage (unsupported_content): group_unsupported (a reaction, typing or a read receipt in a group conversation; param kind) and not_on_sms (an edit or unsend of a message that went by SMS or RCS; param kind). SMS (unsupported_content): no_mms (media to a number without MMS; param media). Edits and unsends (unsupported_content): edit_window_closed and unsend_window_closed (the message is older than the channel allows for the change) and first_message_not_editable (iMessage: the message that started the conversation). From typing and read receipts, unknown_outcome (channel_error: the call timed out after it was sent).

request_id string

Flow's ID for this request. Quote it when asking for help.

404 application/json

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

error ErrorBody required

The details of an error.

Show child attributes
type enum<string> required

What went wrong. A closed list: new types arrive only with a new API version. Each type has a page at https://api.flow.engineer/docs/errors/<type>, given in the error's doc_url; the error's hint says what to change for the case.

  • invalid_request (400, or 413 for a request body over the size limit): the request is malformed or a parameter is invalid. Fix the parameter named in param; the hint says what it must look like. Every endpoint answers it for a Flow-Version header that is not a version this API serves.
  • authentication (401): the API key is missing, malformed, unknown, revoked or expired. An Authorization: Bearer header with nothing after it usually means the environment variable holding the key was empty when the header was built (for an MCP client, when the server was added): the hint says so. Send Authorization: Bearer fk_test_... or fk_live_... with a current key; with no key at all, get a test key with POST /v0/sandbox/keys. A sandbox key past its expires_at has channel_code sandbox_key_expired: sign in to claim the app, or get a new key.
  • permission (403): the key may not do this, for example a test key using a live sender, a sandbox contact who joined another app, a new conversation from an iMessage line that may only reply, or a send past the app's sandbox allowance (channel_code sandbox_allowance_used, sandbox_contact_limit or sandbox_channel_not_included). Use the key of the right mode, have the contact send your join message, wait for the contact to message the line first, or sign in through the device flow to lift the allowance. On SMS: contact_opted_out (the contact texted STOP; nothing more goes to them until they text START), consent_required (a start to someone who never texted your numbers: record their consent with consent on the start). In a HIPAA account: hipaa_channel_not_covered (the channel has no business associate agreement; use SMS). Simulated conversations: simulated_contact (a real sender was pointed at a simulated contact; reply in the contact's simulated conversation), not_simulated (a simulated sender or test endpoint was pointed at a real contact), test_mode_only, simulated_conversation_limit. In message.failed: sandbox_contact_left (the contact joined another app on the sandbox sender while the message was queued; have them send your join message again).
  • not_found (404): no such object for this app and mode. Check the ID's prefix and that it was made with a key of the same mode (test and live data are separate).
  • idempotency_conflict (409): the idempotency key was used for a different request (channel_code body_mismatch: use a new key), or that request is still running (in_progress: wait retry_after seconds and repeat it with the same key), or it already created a secret that is shown only once (secret_not_kept: creating a webhook endpoint, rotating its secret, connecting a WhatsApp number; a secret that was lost must be rotated again, and a WhatsApp number connected again to see its verify token).
  • outside_window (409, or in message.failed): the channel will not deliver outside its conversation window. On WhatsApp the 24-hour window is closed: send a template (POST /v0/conversations/{conversation_id}/template, or POST /v0/messages with kind template to start), or wait for the contact to write. On iMessage the contact has not messaged the line (or opted in) yet, or not recently, so the failure arrives as a message.failed event: wait for the contact to write, then reply in that conversation. Typing and read receipts answer 409 outside_window directly when the channel's window for them is closed (iMessage typing works only within 5 minutes of the contact's last message; WhatsApp read receipts go at any time); ignore it and send your reply. On SMS, channel_code quiet_hours: the contact has not texted in the last hour and it is inside the sender's quiet hours in their time zone; retry after retry_after seconds, or wait for them to text. In message.failed: window_closed (WhatsApp's 24-hour window closed while the message was queued; send a template) and queued_too_long (the message waited in the queue longer than its sender allows, for example while the sender was paused; send it again if it still applies).
  • unsupported_content (422): the conversation's channel does not take this kind, field, block or value, and nothing was sent; Flow never converts a message. param names what was refused: kind (the endpoint's kind is not on this channel, such as a carousel on Telegram), a field or block (reply_to on SMS, telegram on WhatsApp, telegram.is_big), or the field whose value is not taken (emoji outside Telegram's reaction emoji, buttons for more than 3 WhatsApp buttons). channel is the conversation's channel and hint says what to send instead over plain HTTP; each endpoint's page lists its channels. In an iMessage group conversation, a reaction, typing or a read receipt answers it with param kind and channel_code group_unsupported: leave it out. Media to an SMS number without MMS answers it with param media and channel_code no_mms. An edit or unsend of an iMessage message that went by SMS or RCS (its network) answers it with channel_code not_on_sms: send a new message instead. An edit or unsend past the channel's time limit answers it with channel_code edit_window_closed or unsend_window_closed (iMessage: edits within 15 minutes of sending, unsends within 2 minutes; Telegram: unsends within 48 hours), and an iMessage edit of the message that started the conversation with first_message_not_editable; message states the limit and how long ago the message was sent. Send a new message instead. POST /v0/files answers it for a type Flow does not take (programs, web pages, unknown types). A send by file_id of a document a contact sent before Flow malware-scanned documents answers it too: Flow sends only documents it scanned.
  • new_contact_limit (429): the sender has used its budget for starting conversations. Wait retry_after seconds; replies into existing conversations still go.
  • sender_throttled (429): abuse signals tripped (the same text to many new contacts, many starts with no reply, blocks), so the sender may not start conversations until retry_after; replies into existing conversations still go. Personalise first messages and start only conversations people expect.
  • file_blocked (422): the file failed the malware scan and was not stored. Send a different file. (When the scan cannot run at all, the upload answers a retryable 500 api_error with Retry-After instead.)
  • rate_limited (429): either too many requests for this key (the per-key request limit, reported in the RateLimit-* headers) or sends faster than the sender's sending rate (pacing). Wait Retry-After (retry_after) seconds and retry with the same Idempotency-Key.
  • channel_error (502, or in message.failed): the channel refused or failed the message, or timed out; channel_code carries its own code. Read message, change what the channel objected to, and send again. From typing and read receipts, which call the channel at once, it is safe to ignore. SMS codes are Flow's, the same whichever carrier network carries the message: carrier_filtered (a carrier's spam filter blocked it: make it read like a person's message, no shortened links), unreachable, invalid_number, landline, registration_required (the number's 10DLC or toll-free registration is not approved yet), carrier_error, and provider_not_configured (the SMS sender is not fully set up on Flow's side, so nothing was sent; contact the Flow team). Flow's own codes on every channel: channel_timeout_unknown_outcome (in message.failed: the channel did not answer after the message was sent, and the resends its policy allows did not settle it, so the message may have arrived or not; check with the contact before sending it again) and unknown_outcome (from typing and read receipts: the call timed out after it was sent).
  • not_implemented (501): this endpoint or channel is not live yet during the beta. Use a channel that is live (Telegram, iMessage), or check the changelog.
  • api_error (500, 503): something went wrong on Flow's side. Retry with the same idempotency key after retry_after seconds, and quote request_id if it persists.

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

message string required

A sentence for a developer saying what went wrong. Do not parse it; switch on type.

hint string required

One sentence saying what to change to make the request succeed, specific to this case, for example "Send a template instead: POST /v0/conversations/{conversation_id}/template." For unsupported_content it says what to send instead over plain HTTP. Written for developers and coding agents alike. Do not parse it; it may be reworded at any time.

doc_url string<uri> required

The documentation page for this error's type, https://api.flow.engineer/docs/errors/<type>. It says what the error means, why it happens and how to fix it, with code.

param string

The request parameter the error is about, as a dotted path (kind, telegram.is_big, buttons, cards[1].header.type, limit).

retry_after integer

Seconds to wait before retrying, for errors that clear by themselves.

Range: 0 to any

channel enum<string>

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

Available options: telegram, whatsapp, imessage, sms

conversation string

A conversation ID, conv_ and a ULID.

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

sender string

A sender ID, snd_ and a ULID.

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

channel_code string

For channel_error, the channel's own error code, as given by the channel. For some other errors, Flow's own code naming the case, for example sandbox_allowance_used (permission: the sandbox allowance is used up), sandbox_contact_limit (permission: the allowance has no room for another contact), sandbox_channel_not_included (permission: the allowance does not cover this sandbox channel), sign_in_required (permission: an app made without an account cannot do this) or sandbox_key_expired (authentication: a key from POST /v0/sandbox/keys passed its expires_at; permission, for a send that carries no key, such as a reply in a webhook answer, from such an app). For idempotency_conflict, always one of body_mismatch (the key was used for a different request), in_progress (the first request is still running; retry_after says when to repeat it) or secret_not_kept (its answer carried a secret shown only once). SMS: contact_opted_out and consent_required (permission), quiet_hours (outside_window, with retry_after), and carrier_filtered, unreachable, invalid_number, landline, registration_required, carrier_error and provider_not_configured (channel_error). HIPAA accounts: hipaa_channel_not_covered (permission). Connecting your own WhatsApp number (POST /v0/senders): whatsapp_token_malformed, whatsapp_token_rejected, whatsapp_app_secret_malformed and whatsapp_number_not_in_account (invalid_request), and whatsapp_number_taken, whatsapp_account_taken and whatsapp_number_managed (permission). Simulated conversations (permission): test_mode_only, not_simulated, simulated_contact and simulated_conversation_limit. For a message that failed after it was queued (message.failed): sandbox_contact_left (permission), window_closed and queued_too_long (outside_window), and channel_timeout_unknown_outcome (channel_error: the channel never confirmed the send, so it may or may not have arrived). iMessage (unsupported_content): group_unsupported (a reaction, typing or a read receipt in a group conversation; param kind) and not_on_sms (an edit or unsend of a message that went by SMS or RCS; param kind). SMS (unsupported_content): no_mms (media to a number without MMS; param media). Edits and unsends (unsupported_content): edit_window_closed and unsend_window_closed (the message is older than the channel allows for the change) and first_message_not_editable (iMessage: the message that started the conversation). From typing and read receipts, unknown_outcome (channel_error: the call timed out after it was sent).

request_id string

Flow's ID for this request. Quote it when asking for help.

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 channel has no business associate agreement; use SMS). Simulated conversations: simulated_contact (a real sender was pointed at a simulated contact; reply in the contact's simulated conversation), not_simulated (a simulated sender or test endpoint was pointed at a real contact), test_mode_only, simulated_conversation_limit. In message.failed: sandbox_contact_left (the contact joined another app on the sandbox sender while the message was queued; have them send your join message again).
  • not_found (404): no such object for this app and mode. Check the ID's prefix and that it was made with a key of the same mode (test and live data are separate).
  • idempotency_conflict (409): the idempotency key was used for a different request (channel_code body_mismatch: use a new key), or that request is still running (in_progress: wait retry_after seconds and repeat it with the same key), or it already created a secret that is shown only once (secret_not_kept: creating a webhook endpoint, rotating its secret, connecting a WhatsApp number; a secret that was lost must be rotated again, and a WhatsApp number connected again to see its verify token).
  • outside_window (409, or in message.failed): the channel will not deliver outside its conversation window. On WhatsApp the 24-hour window is closed: send a template (POST /v0/conversations/{conversation_id}/template, or POST /v0/messages with kind template to start), or wait for the contact to write. On iMessage the contact has not messaged the line (or opted in) yet, or not recently, so the failure arrives as a message.failed event: wait for the contact to write, then reply in that conversation. Typing and read receipts answer 409 outside_window directly when the channel's window for them is closed (iMessage typing works only within 5 minutes of the contact's last message; WhatsApp read receipts go at any time); ignore it and send your reply. On SMS, channel_code quiet_hours: the contact has not texted in the last hour and it is inside the sender's quiet hours in their time zone; retry after retry_after seconds, or wait for them to text. In message.failed: window_closed (WhatsApp's 24-hour window closed while the message was queued; send a template) and queued_too_long (the message waited in the queue longer than its sender allows, for example while the sender was paused; send it again if it still applies).
  • unsupported_content (422): the conversation's channel does not take this kind, field, block or value, and nothing was sent; Flow never converts a message. param names what was refused: kind (the endpoint's kind is not on this channel, such as a carousel on Telegram), a field or block (reply_to on SMS, telegram on WhatsApp, telegram.is_big), or the field whose value is not taken (emoji outside Telegram's reaction emoji, buttons for more than 3 WhatsApp buttons). channel is the conversation's channel and hint says what to send instead over plain HTTP; each endpoint's page lists its channels. In an iMessage group conversation, a reaction, typing or a read receipt answers it with param kind and channel_code group_unsupported: leave it out. Media to an SMS number without MMS answers it with param media and channel_code no_mms. An edit or unsend of an iMessage message that went by SMS or RCS (its network) answers it with channel_code not_on_sms: send a new message instead. An edit or unsend past the channel's time limit answers it with channel_code edit_window_closed or unsend_window_closed (iMessage: edits within 15 minutes of sending, unsends within 2 minutes; Telegram: unsends within 48 hours), and an iMessage edit of the message that started the conversation with first_message_not_editable; message states the limit and how long ago the message was sent. Send a new message instead. POST /v0/files answers it for a type Flow does not take (programs, web pages, unknown types). A send by file_id of a document a contact sent before Flow malware-scanned documents answers it too: Flow sends only documents it scanned.
  • new_contact_limit (429): the sender has used its budget for starting conversations. Wait retry_after seconds; replies into existing conversations still go.
  • sender_throttled (429): abuse signals tripped (the same text to many new contacts, many starts with no reply, blocks), so the sender may not start conversations until retry_after; replies into existing conversations still go. Personalise first messages and start only conversations people expect.
  • file_blocked (422): the file failed the malware scan and was not stored. Send a different file. (When the scan cannot run at all, the upload answers a retryable 500 api_error with Retry-After instead.)
  • rate_limited (429): either too many requests for this key (the per-key request limit, reported in the RateLimit-* headers) or sends faster than the sender's sending rate (pacing). Wait Retry-After (retry_after) seconds and retry with the same Idempotency-Key.
  • channel_error (502, or in message.failed): the channel refused or failed the message, or timed out; channel_code carries its own code. Read message, change what the channel objected to, and send again. From typing and read receipts, which call the channel at once, it is safe to ignore. SMS codes are Flow's, the same whichever carrier network carries the message: carrier_filtered (a carrier's spam filter blocked it: make it read like a person's message, no shortened links), unreachable, invalid_number, landline, registration_required (the number's 10DLC or toll-free registration is not approved yet), carrier_error, and provider_not_configured (the SMS sender is not fully set up on Flow's side, so nothing was sent; contact the Flow team). Flow's own codes on every channel: channel_timeout_unknown_outcome (in message.failed: the channel did not answer after the message was sent, and the resends its policy allows did not settle it, so the message may have arrived or not; check with the contact before sending it again) and unknown_outcome (from typing and read receipts: the call timed out after it was sent).
  • not_implemented (501): this endpoint or channel is not live yet during the beta. Use a channel that is live (Telegram, iMessage), or check the changelog.
  • api_error (500, 503): something went wrong on Flow's side. Retry with the same idempotency key after retry_after seconds, and quote request_id if it persists.

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

message string required

A sentence for a developer saying what went wrong. Do not parse it; switch on type.

hint string required

One sentence saying what to change to make the request succeed, specific to this case, for example "Send a template instead: POST /v0/conversations/{conversation_id}/template." For unsupported_content it says what to send instead over plain HTTP. Written for developers and coding agents alike. Do not parse it; it may be reworded at any time.

doc_url string<uri> required

The documentation page for this error's type, https://api.flow.engineer/docs/errors/<type>. It says what the error means, why it happens and how to fix it, with code.

param string

The request parameter the error is about, as a dotted path (kind, telegram.is_big, buttons, cards[1].header.type, limit).

retry_after integer

Seconds to wait before retrying, for errors that clear by themselves.

Range: 0 to any

channel enum<string>

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

Available options: telegram, whatsapp, imessage, sms

conversation string

A conversation ID, conv_ and a ULID.

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

sender string

A sender ID, snd_ and a ULID.

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

channel_code string

For channel_error, the channel's own error code, as given by the channel. For some other errors, Flow's own code naming the case, for example sandbox_allowance_used (permission: the sandbox allowance is used up), sandbox_contact_limit (permission: the allowance has no room for another contact), sandbox_channel_not_included (permission: the allowance does not cover this sandbox channel), sign_in_required (permission: an app made without an account cannot do this) or sandbox_key_expired (authentication: a key from POST /v0/sandbox/keys passed its expires_at; permission, for a send that carries no key, such as a reply in a webhook answer, from such an app). For idempotency_conflict, always one of body_mismatch (the key was used for a different request), in_progress (the first request is still running; retry_after says when to repeat it) or secret_not_kept (its answer carried a secret shown only once). SMS: contact_opted_out and consent_required (permission), quiet_hours (outside_window, with retry_after), and carrier_filtered, unreachable, invalid_number, landline, registration_required, carrier_error and provider_not_configured (channel_error). HIPAA accounts: hipaa_channel_not_covered (permission). Connecting your own WhatsApp number (POST /v0/senders): whatsapp_token_malformed, whatsapp_token_rejected, whatsapp_app_secret_malformed and whatsapp_number_not_in_account (invalid_request), and whatsapp_number_taken, whatsapp_account_taken and whatsapp_number_managed (permission). Simulated conversations (permission): test_mode_only, not_simulated, simulated_contact and simulated_conversation_limit. For a message that failed after it was queued (message.failed): sandbox_contact_left (permission), window_closed and queued_too_long (outside_window), and channel_timeout_unknown_outcome (channel_error: the channel never confirmed the send, so it may or may not have arrived). iMessage (unsupported_content): group_unsupported (a reaction, typing or a read receipt in a group conversation; param kind) and not_on_sms (an edit or unsend of a message that went by SMS or RCS; param kind). SMS (unsupported_content): no_mms (media to a number without MMS; param media). Edits and unsends (unsupported_content): edit_window_closed and unsend_window_closed (the message is older than the channel allows for the change) and first_message_not_editable (iMessage: the message that started the conversation). From typing and read receipts, unknown_outcome (channel_error: the call timed out after it was sent).

request_id string

Flow's ID for this request. Quote it when asking for help.

429 application/json

Too many requests (rate_limited): either this key's request limit (see the RateLimit-* headers) or the sender's sending rate (pacing) was exceeded; retry after Retry-After seconds (also error.retry_after) with the same Idempotency-Key. Or the sender has used its budget for starting conversations (new_contact_limit), or the sender is throttled after abuse signals (sender_throttled); wait retry_after seconds.

error ErrorBody required

The details of an error.

Show child attributes
type enum<string> required

What went wrong. A closed list: new types arrive only with a new API version. Each type has a page at https://api.flow.engineer/docs/errors/<type>, given in the error's doc_url; the error's hint says what to change for the case.

  • invalid_request (400, or 413 for a request body over the size limit): the request is malformed or a parameter is invalid. Fix the parameter named in param; the hint says what it must look like. Every endpoint answers it for a Flow-Version header that is not a version this API serves.
  • authentication (401): the API key is missing, malformed, unknown, revoked or expired. An Authorization: Bearer header with nothing after it usually means the environment variable holding the key was empty when the header was built (for an MCP client, when the server was added): the hint says so. Send Authorization: Bearer fk_test_... or fk_live_... with a current key; with no key at all, get a test key with POST /v0/sandbox/keys. A sandbox key past its expires_at has channel_code sandbox_key_expired: sign in to claim the app, or get a new key.
  • permission (403): the key may not do this, for example a test key using a live sender, a sandbox contact who joined another app, a new conversation from an iMessage line that may only reply, or a send past the app's sandbox allowance (channel_code sandbox_allowance_used, sandbox_contact_limit or sandbox_channel_not_included). Use the key of the right mode, have the contact send your join message, wait for the contact to message the line first, or sign in through the device flow to lift the allowance. On SMS: contact_opted_out (the contact texted STOP; nothing more goes to them until they text START), consent_required (a start to someone who never texted your numbers: record their consent with consent on the start). In a HIPAA account: hipaa_channel_not_covered (the channel has no business associate agreement; use SMS). Simulated conversations: simulated_contact (a real sender was pointed at a simulated contact; reply in the contact's simulated conversation), not_simulated (a simulated sender or test endpoint was pointed at a real contact), test_mode_only, simulated_conversation_limit. In message.failed: sandbox_contact_left (the contact joined another app on the sandbox sender while the message was queued; have them send your join message again).
  • not_found (404): no such object for this app and mode. Check the ID's prefix and that it was made with a key of the same mode (test and live data are separate).
  • idempotency_conflict (409): the idempotency key was used for a different request (channel_code body_mismatch: use a new key), or that request is still running (in_progress: wait retry_after seconds and repeat it with the same key), or it already created a secret that is shown only once (secret_not_kept: creating a webhook endpoint, rotating its secret, connecting a WhatsApp number; a secret that was lost must be rotated again, and a WhatsApp number connected again to see its verify token).
  • outside_window (409, or in message.failed): the channel will not deliver outside its conversation window. On WhatsApp the 24-hour window is closed: send a template (POST /v0/conversations/{conversation_id}/template, or POST /v0/messages with kind template to start), or wait for the contact to write. On iMessage the contact has not messaged the line (or opted in) yet, or not recently, so the failure arrives as a message.failed event: wait for the contact to write, then reply in that conversation. Typing and read receipts answer 409 outside_window directly when the channel's window for them is closed (iMessage typing works only within 5 minutes of the contact's last message; WhatsApp read receipts go at any time); ignore it and send your reply. On SMS, channel_code quiet_hours: the contact has not texted in the last hour and it is inside the sender's quiet hours in their time zone; retry after retry_after seconds, or wait for them to text. In message.failed: window_closed (WhatsApp's 24-hour window closed while the message was queued; send a template) and queued_too_long (the message waited in the queue longer than its sender allows, for example while the sender was paused; send it again if it still applies).
  • unsupported_content (422): the conversation's channel does not take this kind, field, block or value, and nothing was sent; Flow never converts a message. param names what was refused: kind (the endpoint's kind is not on this channel, such as a carousel on Telegram), a field or block (reply_to on SMS, telegram on WhatsApp, telegram.is_big), or the field whose value is not taken (emoji outside Telegram's reaction emoji, buttons for more than 3 WhatsApp buttons). channel is the conversation's channel and hint says what to send instead over plain HTTP; each endpoint's page lists its channels. In an iMessage group conversation, a reaction, typing or a read receipt answers it with param kind and channel_code group_unsupported: leave it out. Media to an SMS number without MMS answers it with param media and channel_code no_mms. An edit or unsend of an iMessage message that went by SMS or RCS (its network) answers it with channel_code not_on_sms: send a new message instead. An edit or unsend past the channel's time limit answers it with channel_code edit_window_closed or unsend_window_closed (iMessage: edits within 15 minutes of sending, unsends within 2 minutes; Telegram: unsends within 48 hours), and an iMessage edit of the message that started the conversation with first_message_not_editable; message states the limit and how long ago the message was sent. Send a new message instead. POST /v0/files answers it for a type Flow does not take (programs, web pages, unknown types). A send by file_id of a document a contact sent before Flow malware-scanned documents answers it too: Flow sends only documents it scanned.
  • new_contact_limit (429): the sender has used its budget for starting conversations. Wait retry_after seconds; replies into existing conversations still go.
  • sender_throttled (429): abuse signals tripped (the same text to many new contacts, many starts with no reply, blocks), so the sender may not start conversations until retry_after; replies into existing conversations still go. Personalise first messages and start only conversations people expect.
  • file_blocked (422): the file failed the malware scan and was not stored. Send a different file. (When the scan cannot run at all, the upload answers a retryable 500 api_error with Retry-After instead.)
  • rate_limited (429): either too many requests for this key (the per-key request limit, reported in the RateLimit-* headers) or sends faster than the sender's sending rate (pacing). Wait Retry-After (retry_after) seconds and retry with the same Idempotency-Key.
  • channel_error (502, or in message.failed): the channel refused or failed the message, or timed out; channel_code carries its own code. Read message, change what the channel objected to, and send again. From typing and read receipts, which call the channel at once, it is safe to ignore. SMS codes are Flow's, the same whichever carrier network carries the message: carrier_filtered (a carrier's spam filter blocked it: make it read like a person's message, no shortened links), unreachable, invalid_number, landline, registration_required (the number's 10DLC or toll-free registration is not approved yet), carrier_error, and provider_not_configured (the SMS sender is not fully set up on Flow's side, so nothing was sent; contact the Flow team). Flow's own codes on every channel: channel_timeout_unknown_outcome (in message.failed: the channel did not answer after the message was sent, and the resends its policy allows did not settle it, so the message may have arrived or not; check with the contact before sending it again) and unknown_outcome (from typing and read receipts: the call timed out after it was sent).
  • not_implemented (501): this endpoint or channel is not live yet during the beta. Use a channel that is live (Telegram, iMessage), or check the changelog.
  • api_error (500, 503): something went wrong on Flow's side. Retry with the same idempotency key after retry_after seconds, and quote request_id if it persists.

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

message string required

A sentence for a developer saying what went wrong. Do not parse it; switch on type.

hint string required

One sentence saying what to change to make the request succeed, specific to this case, for example "Send a template instead: POST /v0/conversations/{conversation_id}/template." For unsupported_content it says what to send instead over plain HTTP. Written for developers and coding agents alike. Do not parse it; it may be reworded at any time.

doc_url string<uri> required

The documentation page for this error's type, https://api.flow.engineer/docs/errors/<type>. It says what the error means, why it happens and how to fix it, with code.

param string

The request parameter the error is about, as a dotted path (kind, telegram.is_big, buttons, cards[1].header.type, limit).

retry_after integer

Seconds to wait before retrying, for errors that clear by themselves.

Range: 0 to any

channel enum<string>

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

Available options: telegram, whatsapp, imessage, sms

conversation string

A conversation ID, conv_ and a ULID.

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

sender string

A sender ID, snd_ and a ULID.

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

channel_code string

For channel_error, the channel's own error code, as given by the channel. For some other errors, Flow's own code naming the case, for example sandbox_allowance_used (permission: the sandbox allowance is used up), sandbox_contact_limit (permission: the allowance has no room for another contact), sandbox_channel_not_included (permission: the allowance does not cover this sandbox channel), sign_in_required (permission: an app made without an account cannot do this) or sandbox_key_expired (authentication: a key from POST /v0/sandbox/keys passed its expires_at; permission, for a send that carries no key, such as a reply in a webhook answer, from such an app). For idempotency_conflict, always one of body_mismatch (the key was used for a different request), in_progress (the first request is still running; retry_after says when to repeat it) or secret_not_kept (its answer carried a secret shown only once). SMS: contact_opted_out and consent_required (permission), quiet_hours (outside_window, with retry_after), and carrier_filtered, unreachable, invalid_number, landline, registration_required, carrier_error and provider_not_configured (channel_error). HIPAA accounts: hipaa_channel_not_covered (permission). Connecting your own WhatsApp number (POST /v0/senders): whatsapp_token_malformed, whatsapp_token_rejected, whatsapp_app_secret_malformed and whatsapp_number_not_in_account (invalid_request), and whatsapp_number_taken, whatsapp_account_taken and whatsapp_number_managed (permission). Simulated conversations (permission): test_mode_only, not_simulated, simulated_contact and simulated_conversation_limit. For a message that failed after it was queued (message.failed): sandbox_contact_left (permission), window_closed and queued_too_long (outside_window), and channel_timeout_unknown_outcome (channel_error: the channel never confirmed the send, so it may or may not have arrived). iMessage (unsupported_content): group_unsupported (a reaction, typing or a read receipt in a group conversation; param kind) and not_on_sms (an edit or unsend of a message that went by SMS or RCS; param kind). SMS (unsupported_content): no_mms (media to a number without MMS; param media). Edits and unsends (unsupported_content): edit_window_closed and unsend_window_closed (the message is older than the channel allows for the change) and first_message_not_editable (iMessage: the message that started the conversation). From typing and read receipts, unknown_outcome (channel_error: the call timed out after it was sent).

request_id string

Flow's ID for this request. Quote it when asking for help.

default application/json

An error. error.type says which (see ErrorType).

error ErrorBody required

The details of an error.

Show child attributes
type enum<string> required

What went wrong. A closed list: new types arrive only with a new API version. Each type has a page at https://api.flow.engineer/docs/errors/<type>, given in the error's doc_url; the error's hint says what to change for the case.

  • invalid_request (400, or 413 for a request body over the size limit): the request is malformed or a parameter is invalid. Fix the parameter named in param; the hint says what it must look like. Every endpoint answers it for a Flow-Version header that is not a version this API serves.
  • authentication (401): the API key is missing, malformed, unknown, revoked or expired. An Authorization: Bearer header with nothing after it usually means the environment variable holding the key was empty when the header was built (for an MCP client, when the server was added): the hint says so. Send Authorization: Bearer fk_test_... or fk_live_... with a current key; with no key at all, get a test key with POST /v0/sandbox/keys. A sandbox key past its expires_at has channel_code sandbox_key_expired: sign in to claim the app, or get a new key.
  • permission (403): the key may not do this, for example a test key using a live sender, a sandbox contact who joined another app, a new conversation from an iMessage line that may only reply, or a send past the app's sandbox allowance (channel_code sandbox_allowance_used, sandbox_contact_limit or sandbox_channel_not_included). Use the key of the right mode, have the contact send your join message, wait for the contact to message the line first, or sign in through the device flow to lift the allowance. On SMS: contact_opted_out (the contact texted STOP; nothing more goes to them until they text START), consent_required (a start to someone who never texted your numbers: record their consent with consent on the start). In a HIPAA account: hipaa_channel_not_covered (the channel has no business associate agreement; use SMS). Simulated conversations: simulated_contact (a real sender was pointed at a simulated contact; reply in the contact's simulated conversation), not_simulated (a simulated sender or test endpoint was pointed at a real contact), test_mode_only, simulated_conversation_limit. In message.failed: sandbox_contact_left (the contact joined another app on the sandbox sender while the message was queued; have them send your join message again).
  • not_found (404): no such object for this app and mode. Check the ID's prefix and that it was made with a key of the same mode (test and live data are separate).
  • idempotency_conflict (409): the idempotency key was used for a different request (channel_code body_mismatch: use a new key), or that request is still running (in_progress: wait retry_after seconds and repeat it with the same key), or it already created a secret that is shown only once (secret_not_kept: creating a webhook endpoint, rotating its secret, connecting a WhatsApp number; a secret that was lost must be rotated again, and a WhatsApp number connected again to see its verify token).
  • outside_window (409, or in message.failed): the channel will not deliver outside its conversation window. On WhatsApp the 24-hour window is closed: send a template (POST /v0/conversations/{conversation_id}/template, or POST /v0/messages with kind template to start), or wait for the contact to write. On iMessage the contact has not messaged the line (or opted in) yet, or not recently, so the failure arrives as a message.failed event: wait for the contact to write, then reply in that conversation. Typing and read receipts answer 409 outside_window directly when the channel's window for them is closed (iMessage typing works only within 5 minutes of the contact's last message; WhatsApp read receipts go at any time); ignore it and send your reply. On SMS, channel_code quiet_hours: the contact has not texted in the last hour and it is inside the sender's quiet hours in their time zone; retry after retry_after seconds, or wait for them to text. In message.failed: window_closed (WhatsApp's 24-hour window closed while the message was queued; send a template) and queued_too_long (the message waited in the queue longer than its sender allows, for example while the sender was paused; send it again if it still applies).
  • unsupported_content (422): the conversation's channel does not take this kind, field, block or value, and nothing was sent; Flow never converts a message. param names what was refused: kind (the endpoint's kind is not on this channel, such as a carousel on Telegram), a field or block (reply_to on SMS, telegram on WhatsApp, telegram.is_big), or the field whose value is not taken (emoji outside Telegram's reaction emoji, buttons for more than 3 WhatsApp buttons). channel is the conversation's channel and hint says what to send instead over plain HTTP; each endpoint's page lists its channels. In an iMessage group conversation, a reaction, typing or a read receipt answers it with param kind and channel_code group_unsupported: leave it out. Media to an SMS number without MMS answers it with param media and channel_code no_mms. An edit or unsend of an iMessage message that went by SMS or RCS (its network) answers it with channel_code not_on_sms: send a new message instead. An edit or unsend past the channel's time limit answers it with channel_code edit_window_closed or unsend_window_closed (iMessage: edits within 15 minutes of sending, unsends within 2 minutes; Telegram: unsends within 48 hours), and an iMessage edit of the message that started the conversation with first_message_not_editable; message states the limit and how long ago the message was sent. Send a new message instead. POST /v0/files answers it for a type Flow does not take (programs, web pages, unknown types). A send by file_id of a document a contact sent before Flow malware-scanned documents answers it too: Flow sends only documents it scanned.
  • new_contact_limit (429): the sender has used its budget for starting conversations. Wait retry_after seconds; replies into existing conversations still go.
  • sender_throttled (429): abuse signals tripped (the same text to many new contacts, many starts with no reply, blocks), so the sender may not start conversations until retry_after; replies into existing conversations still go. Personalise first messages and start only conversations people expect.
  • file_blocked (422): the file failed the malware scan and was not stored. Send a different file. (When the scan cannot run at all, the upload answers a retryable 500 api_error with Retry-After instead.)
  • rate_limited (429): either too many requests for this key (the per-key request limit, reported in the RateLimit-* headers) or sends faster than the sender's sending rate (pacing). Wait Retry-After (retry_after) seconds and retry with the same Idempotency-Key.
  • channel_error (502, or in message.failed): the channel refused or failed the message, or timed out; channel_code carries its own code. Read message, change what the channel objected to, and send again. From typing and read receipts, which call the channel at once, it is safe to ignore. SMS codes are Flow's, the same whichever carrier network carries the message: carrier_filtered (a carrier's spam filter blocked it: make it read like a person's message, no shortened links), unreachable, invalid_number, landline, registration_required (the number's 10DLC or toll-free registration is not approved yet), carrier_error, and provider_not_configured (the SMS sender is not fully set up on Flow's side, so nothing was sent; contact the Flow team). Flow's own codes on every channel: channel_timeout_unknown_outcome (in message.failed: the channel did not answer after the message was sent, and the resends its policy allows did not settle it, so the message may have arrived or not; check with the contact before sending it again) and unknown_outcome (from typing and read receipts: the call timed out after it was sent).
  • not_implemented (501): this endpoint or channel is not live yet during the beta. Use a channel that is live (Telegram, iMessage), or check the changelog.
  • api_error (500, 503): something went wrong on Flow's side. Retry with the same idempotency key after retry_after seconds, and quote request_id if it persists.

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

message string required

A sentence for a developer saying what went wrong. Do not parse it; switch on type.

hint string required

One sentence saying what to change to make the request succeed, specific to this case, for example "Send a template instead: POST /v0/conversations/{conversation_id}/template." For unsupported_content it says what to send instead over plain HTTP. Written for developers and coding agents alike. Do not parse it; it may be reworded at any time.

doc_url string<uri> required

The documentation page for this error's type, https://api.flow.engineer/docs/errors/<type>. It says what the error means, why it happens and how to fix it, with code.

param string

The request parameter the error is about, as a dotted path (kind, telegram.is_big, buttons, cards[1].header.type, limit).

retry_after integer

Seconds to wait before retrying, for errors that clear by themselves.

Range: 0 to any

channel enum<string>

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

Available options: telegram, whatsapp, imessage, sms

conversation string

A conversation ID, conv_ and a ULID.

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

sender string

A sender ID, snd_ and a ULID.

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

channel_code string

For channel_error, the channel's own error code, as given by the channel. For some other errors, Flow's own code naming the case, for example sandbox_allowance_used (permission: the sandbox allowance is used up), sandbox_contact_limit (permission: the allowance has no room for another contact), sandbox_channel_not_included (permission: the allowance does not cover this sandbox channel), sign_in_required (permission: an app made without an account cannot do this) or sandbox_key_expired (authentication: a key from POST /v0/sandbox/keys passed its expires_at; permission, for a send that carries no key, such as a reply in a webhook answer, from such an app). For idempotency_conflict, always one of body_mismatch (the key was used for a different request), in_progress (the first request is still running; retry_after says when to repeat it) or secret_not_kept (its answer carried a secret shown only once). SMS: contact_opted_out and consent_required (permission), quiet_hours (outside_window, with retry_after), and carrier_filtered, unreachable, invalid_number, landline, registration_required, carrier_error and provider_not_configured (channel_error). HIPAA accounts: hipaa_channel_not_covered (permission). Connecting your own WhatsApp number (POST /v0/senders): whatsapp_token_malformed, whatsapp_token_rejected, whatsapp_app_secret_malformed and whatsapp_number_not_in_account (invalid_request), and whatsapp_number_taken, whatsapp_account_taken and whatsapp_number_managed (permission). Simulated conversations (permission): test_mode_only, not_simulated, simulated_contact and simulated_conversation_limit. For a message that failed after it was queued (message.failed): sandbox_contact_left (permission), window_closed and queued_too_long (outside_window), and channel_timeout_unknown_outcome (channel_error: the channel never confirmed the send, so it may or may not have arrived). iMessage (unsupported_content): group_unsupported (a reaction, typing or a read receipt in a group conversation; param kind) and not_on_sms (an edit or unsend of a message that went by SMS or RCS; param kind). SMS (unsupported_content): no_mms (media to a number without MMS; param media). Edits and unsends (unsupported_content): edit_window_closed and unsend_window_closed (the message is older than the channel allows for the change) and first_message_not_editable (iMessage: the message that started the conversation). From typing and read receipts, unknown_outcome (channel_error: the call timed out after it was sent).

request_id string

Flow's ID for this request. Quote it when asking for help.