Request a dedicated sender
POST /v0/sendersConnects a dedicated sender to your app. Telegram bots and WhatsApp numbers
in your own WhatsApp Business Account are connected here, self-serve, with
a live key (below). iMessage lines and US SMS numbers are connected by the
Flow team, not by API: a request with channel imessage or sms answers
501 not_implemented before any other check, with any key (test or live,
claimed app or not): ask the Flow team, and the sender appears in
GET /v0/senders once it is connected. Its hint says what to build with
meanwhile: a Telegram bot, or simulated conversations of that channel
(POST /v0/test/conversations, test keys). Never send API keys, tokens or
passwords in a chat or email, to the Flow team or anyone else.
- iMessage: a dedicated line attached to your app.
- US SMS (in pilot): carriers require each number's brand and campaign to be registered first, so the Flow team registers and connects the number.
To get a live key (fk_live_...), a person signs in to the dashboard at
https://api.flow.engineer/admin (with a Flow account), switches to Live,
and clicks Create live key on the Keys page
(https://api.flow.engineer/admin/keys?mode=live). An app made without an
account (POST /v0/sandbox/keys) is claimed first, by signing in through the
device flow or its claim_url. For a Telegram bot or a WhatsApp number, an
unclaimed app's key gets 403 permission (channel_code
sign_in_required) and a test key gets 403 permission, each with a hint
naming these steps. Telegram bots and your own WhatsApp numbers are
self-serve; iMessage lines and SMS numbers are arranged with the Flow team,
and the Flow team can also help you connect a WhatsApp number.
A Telegram bot is connected at once, with a live key: give the token
BotFather issued as telegram_bot_token. Flow checks it, keeps it encrypted, points the bot's
webhook at Flow, and answers 200 with the sender active. The token is
never returned. One bot is one sender:
- Connecting a bot that is already a sender of this app updates its token in
place and answers with the same sender (use it after revoking a token in
@BotFather; a sender
flaggedbecause Telegram rejected its old token becomesactiveagain, orthrottledwhile an abuse throttle still runs, and its queued messages go out). - Connecting a bot that is a sender of another app moves it here: holding
the token proves control of the bot. The old sender is retired (
banned) and its app receivessender.status_changed. - A bot that is one of Flow's sandbox senders is refused with
403 permission.
To disconnect a bot, call DELETE /v0/senders/{sender_id}.
A WhatsApp number in your own WhatsApp Business Account, under your own
Meta app, is connected at once with a live key. In Meta Business Manager,
first: create a Meta app of type Business with the WhatsApp product, and
switch it to Live; add the phone number to your WhatsApp Business Account
with an approved display name, and a payment method (Meta bills its fees
to you directly); create a system user (Admin) with your WhatsApp
Business Account (full control) and the app assigned, and generate a
token that never expires with the permissions
whatsapp_business_messaging and whatsapp_business_management. Then
send whatsapp_phone_number_id, whatsapp_business_account_id,
whatsapp_access_token (that system user token) and
whatsapp_app_secret (App settings > Basic), and display_name if you
like. Add whatsapp_registration_pin only when the number is not
registered for the Cloud API yet: Flow then registers it with that
six-digit two-step verification PIN (Meta allows 10 registrations per
number in 72 hours).
Flow checks that the number belongs to that business account, subscribes
your app to the business account's webhooks (an existing subscription is
left as it is), and points only this number's webhooks at Flow (Meta's
phone-number-level override; your other numbers keep theirs). It keeps
the token and the app secret encrypted and never returns them. The answer
is 200 with the sender active and, only in this answer, setup: the
callback URL and verify token to enter in your Meta app (App Dashboard >
WhatsApp > Configuration) and the webhook fields to subscribe there, with
the steps. One number is one sender:
- Connecting a number that is already a sender of this app updates its
token and app secret in place and answers with the same sender and the
same verify token (use it to rotate the token; a sender
flaggedbecause Meta rejected its old token becomesactiveagain, and its queued messages go out). - A number that is a sender of another app, or in a WhatsApp Business
Account whose numbers are connected to another account, is refused
with
403 permission(channel_codewhatsapp_number_takenorwhatsapp_account_taken): disconnect it there first. - A number the Flow team connected for you is updated by the Flow team
(
403 permission,channel_codewhatsapp_number_managed). - A token that is not a Meta access token answers
400 invalid_request(channel_codewhatsapp_token_malformed), a token Meta refuses (expired, revoked, or without the permissions or assets above)400 invalid_request(channel_codewhatsapp_token_rejected), and a number that is not in that business account400 invalid_request(channel_codewhatsapp_number_not_in_account). Other refusals from Meta answer502 channel_errorwith Meta's code aschannel_code.
To disconnect a number, call DELETE /v0/senders/{sender_id}.
Authorizations
AuthorizationstringheaderrequiredAn API key of one app, sent as Authorization: Bearer <key>. Keys start with
fk_test_ (test mode: sandbox senders and test data only) or fk_live_
(live mode). Keep live keys on your server; never ship them in an app or page.
Headers
Flow-Versionstring<date>The API version to use, as a date. Without it, the version pinned to your app when it was created is used.
Idempotency-KeystringA 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.
Body application/json
channel enum<string> required A messaging channel. sms is US SMS and MMS (in pilot), from dedicated numbers only.
display_name string The name contacts should see.
country string WhatsApp and iMessage. ISO 3166-1 alpha-2 country for the number, for example IN.
telegram_bot_token string Telegram only, and required there. The bot token from BotFather. Stored encrypted; never returned.
whatsapp_phone_number_id string WhatsApp only, and required there. The phone number ID of your number (WhatsApp Manager, or App Dashboard > WhatsApp > API Setup), not the phone number itself.
whatsapp_business_account_id string WhatsApp only, and required there. The ID of the WhatsApp Business Account the number is in.
whatsapp_access_token string WhatsApp only, and required there. A system user access token of your
business (it starts with EAA) that never expires, with
whatsapp_business_messaging and whatsapp_business_management, and
your WhatsApp Business Account and Meta app assigned to the system
user. Stored encrypted; never returned.
whatsapp_app_secret string WhatsApp only, and required there. The App secret of your Meta app (App settings > Basic, 32 hexadecimal characters); Meta signs the number's webhooks with it. Stored encrypted; never returned.
whatsapp_registration_pin string WhatsApp only, optional. Give it only when the number is not registered for the Cloud API yet: Flow registers the number with this six-digit two-step verification PIN (the number's current PIN, if it has one). Meta allows 10 registrations per number in 72 hours. Stored encrypted; never returned.
Response
200 application/json
The sender is connected and active, or an already connected bot's token, or number's token and app secret, were updated in place. A WhatsApp number's answer carries setup.
id string required A sender ID, snd_ and a ULID.
channel enum<string> required A messaging channel. sms is US SMS and MMS (in pilot), from dedicated numbers only.
kind enum<string> required shared (the sandbox), dedicated (yours) or simulated (test mode only:
your app's stand-in for a channel, used by simulated conversations from
POST /v0/test/conversations; it never touches a network).
livemode boolean required false for sandbox and simulated senders, true for dedicated senders used in live mode.
display_name string The name contacts see, where the channel shows one.
status enum<string> required pending: requested, being provisioned.active: sending normally.warming_up: active, with a new-contact budget that grows day by day.throttled: the gate slowed it after an abuse signal; it recovers by itself.flagged: the channel or Flow flagged it; starts are paused. A Telegram bot is alsoflaggedwhen Telegram rejects its token (revoked in @BotFather): it then sends nothing, new sends answer403 permission, and queued messages wait until you connect the bot again with its new token (POST /v0/senders). They wait at most 72 hours after Flow accepted them; older ones fail withoutside_window(channel_codequeued_too_long) inmessage.failedinstead of going out late.banned: it cannot send or receive: the channel banned it, you disconnected it (DELETE /v0/senders/{sender_id}), or its bot or line was connected to another app.
throttled_until string<date-time> With status throttled, when starts are allowed again. Recovery is automatic.
address SenderAddress required How contacts reach the sender. Which fields are set depends on the channel.
Show child attributes
phone string WhatsApp, iMessage and SMS. E.164 phone number.
username string Telegram. The bot's username, without @.
handle string iMessage. The line's handle (phone number or email address).
link string<uri> A link that opens a chat with the sender: https://t.me/... for a Telegram
bot, https://wa.me/... for a WhatsApp number. For an iMessage line it is
the line's opt-in link, which opens Messages with the line and a prefilled
text the person sends to start the conversation; it is set only when the
line has one configured.
limits SenderLimits required The sender's current budget for starting conversations, and how fast it sends.
Show child attributes
new_contacts_per_day integer required New conversations the sender may start per day today (grows during warm-up).
new_contacts_per_hour integer required New conversations the sender may start per hour.
messages_per_second number The sender's pacing: how many messages a second it takes on average,
replies included, with short bursts allowed above it. A send past it
answers 429 rate_limited with retry_after. A shared sandbox
sender's pacing is shared by every app that uses it. Absent for
simulated senders, which are not paced.
whatsapp_tier string WhatsApp only. Meta's messaging tier for the number.
quality_rating enum<string> WhatsApp only. Meta's quality rating for the number.
sms SmsSender read-only SMS senders only: how the number is registered with US carriers and what it
can do. An SMS number stays pending until its registration is approved
(carriers block traffic from unregistered numbers).
Show child attributes
number_type enum<string> required 10dlc (a local 10-digit number, registered as a brand and campaign) or toll_free (a verified toll-free number).
registration_status enum<string> required The number's carrier registration (the 10DLC campaign, or toll-free
verification): pending (submitted, not approved yet; the sender is
pending and sends nothing), approved, or rejected (the sender is
flagged; the Flow team resubmits).
two_way boolean required Whether the number receives texts. Always true for senders Flow connects today.
mms boolean required Whether the number sends and receives MMS (images, audio and video). Without it, a basic message with media answers 422 unsupported_content with param media and channel_code no_mms.
quiet_hours QuietHours The hours, in the contact's local time zone (worked out from their area
code; when it is not known, every US time zone must be outside the
hours), in which the sender does not message contacts who have not texted
it in the last hour. Absent when the sender has no quiet hours. A send
inside them answers 409 outside_window with channel_code quiet_hours
and retry_after; a message that waited in the queue into them is held
until they end. Replies to a contact who texted within the hour always
go.
Show child attributes
start string required When the quiet hours begin, HH:MM on a 24-hour clock.
end string required When they end, HH:MM on a 24-hour clock (earlier than start when they span midnight).
imessage ImessageSender read-only iMessage senders only: what the line can do beyond replying in iMessage. Set by the Flow team (ask them to change it); absent on other channels.
Show child attributes
fallback boolean required Whether messages to a contact who cannot get iMessage go by SMS or RCS
instead. The network is chosen for each message when it is sent, never
by your app; the message's network says which one carried it.
Included on Flow's shared lines; on a dedicated line, an add-on
arranged with the Flow team. Off, a message to a contact who is not on
iMessage fails (message.failed). A message that goes by SMS or RCS
shows no imessage.styles, imessage.subject or effect, and edits
and unsends of it answer 422 unsupported_content with
channel_code not_on_sms (see Message.network).
call_forwarding CallForwarding Dedicated lines only: where phone calls to the line's number are
forwarded, set up with the Flow team. Forwarded calls send no event.
FaceTime calls are not forwarded; they arrive as call.received. Absent
when calls are not forwarded (always on shared lines).
Show child attributes
phone string required The E.164 number calls are forwarded to.
can_initiate boolean required Whether the line may write first: start a conversation with
POST /v0/messages with a contact who has not messaged it. Off (the
default), the line only replies and a start answers 403 permission.
Writing first is available on request from the Flow team, on dedicated
lines only (always false on shared lines); a line that starts
conversations spends its new-contact budget (limits), which grows
during warm-up.
setup SenderSetup read-only Only in the answer to POST /v0/senders that connected a WhatsApp number:
what you still set in your own Meta app, which Flow cannot set for you.
Flow already points the number's webhooks at callback_url (Meta's
phone-number-level override); entering the same URL in your app makes its
field subscriptions apply. The verify token is shown only in this answer
(connecting the number again shows it again); keep it like a password.
Show child attributes
callback_url string<uri> required The callback URL to enter in your Meta app (App Dashboard > WhatsApp > Configuration > Webhook).
verify_token string required The verify token to enter with the callback URL.
webhook_fields string[] required The webhook fields to subscribe in the same place.
steps string[] required The steps in your Meta app, in order, as plain sentences to show a person.
join_code string Shared senders only. The whole message a contact sends to this sender to join your app: Hi , the app's name (there when it has none), then the app's sandbox_join_code (from GET /v0/app) in brackets after ref, for example Hi Sandbox app (ref brave-otter-40718263). Show it to testers as is; a link that fills it in (wa.me, sms:) carries it URL-encoded. Any message of at most 160 characters that ends with (ref <code>) joins, so a tester may change the words before the brackets.
created_at string<date-time> required When the sender was created.
202 application/json
The request was accepted. The sender is pending, and you receive sender.status_changed when it is ready. No request answers this way today: Telegram bots and WhatsApp numbers answer 200, and senders of other channels are connected by the Flow team.
id string required A sender ID, snd_ and a ULID.
channel enum<string> required A messaging channel. sms is US SMS and MMS (in pilot), from dedicated numbers only.
kind enum<string> required shared (the sandbox), dedicated (yours) or simulated (test mode only:
your app's stand-in for a channel, used by simulated conversations from
POST /v0/test/conversations; it never touches a network).
livemode boolean required false for sandbox and simulated senders, true for dedicated senders used in live mode.
display_name string The name contacts see, where the channel shows one.
status enum<string> required pending: requested, being provisioned.active: sending normally.warming_up: active, with a new-contact budget that grows day by day.throttled: the gate slowed it after an abuse signal; it recovers by itself.flagged: the channel or Flow flagged it; starts are paused. A Telegram bot is alsoflaggedwhen Telegram rejects its token (revoked in @BotFather): it then sends nothing, new sends answer403 permission, and queued messages wait until you connect the bot again with its new token (POST /v0/senders). They wait at most 72 hours after Flow accepted them; older ones fail withoutside_window(channel_codequeued_too_long) inmessage.failedinstead of going out late.banned: it cannot send or receive: the channel banned it, you disconnected it (DELETE /v0/senders/{sender_id}), or its bot or line was connected to another app.
throttled_until string<date-time> With status throttled, when starts are allowed again. Recovery is automatic.
address SenderAddress required How contacts reach the sender. Which fields are set depends on the channel.
Show child attributes
phone string WhatsApp, iMessage and SMS. E.164 phone number.
username string Telegram. The bot's username, without @.
handle string iMessage. The line's handle (phone number or email address).
link string<uri> A link that opens a chat with the sender: https://t.me/... for a Telegram
bot, https://wa.me/... for a WhatsApp number. For an iMessage line it is
the line's opt-in link, which opens Messages with the line and a prefilled
text the person sends to start the conversation; it is set only when the
line has one configured.
limits SenderLimits required The sender's current budget for starting conversations, and how fast it sends.
Show child attributes
new_contacts_per_day integer required New conversations the sender may start per day today (grows during warm-up).
new_contacts_per_hour integer required New conversations the sender may start per hour.
messages_per_second number The sender's pacing: how many messages a second it takes on average,
replies included, with short bursts allowed above it. A send past it
answers 429 rate_limited with retry_after. A shared sandbox
sender's pacing is shared by every app that uses it. Absent for
simulated senders, which are not paced.
whatsapp_tier string WhatsApp only. Meta's messaging tier for the number.
quality_rating enum<string> WhatsApp only. Meta's quality rating for the number.
sms SmsSender read-only SMS senders only: how the number is registered with US carriers and what it
can do. An SMS number stays pending until its registration is approved
(carriers block traffic from unregistered numbers).
Show child attributes
number_type enum<string> required 10dlc (a local 10-digit number, registered as a brand and campaign) or toll_free (a verified toll-free number).
registration_status enum<string> required The number's carrier registration (the 10DLC campaign, or toll-free
verification): pending (submitted, not approved yet; the sender is
pending and sends nothing), approved, or rejected (the sender is
flagged; the Flow team resubmits).
two_way boolean required Whether the number receives texts. Always true for senders Flow connects today.
mms boolean required Whether the number sends and receives MMS (images, audio and video). Without it, a basic message with media answers 422 unsupported_content with param media and channel_code no_mms.
quiet_hours QuietHours The hours, in the contact's local time zone (worked out from their area
code; when it is not known, every US time zone must be outside the
hours), in which the sender does not message contacts who have not texted
it in the last hour. Absent when the sender has no quiet hours. A send
inside them answers 409 outside_window with channel_code quiet_hours
and retry_after; a message that waited in the queue into them is held
until they end. Replies to a contact who texted within the hour always
go.
Show child attributes
start string required When the quiet hours begin, HH:MM on a 24-hour clock.
end string required When they end, HH:MM on a 24-hour clock (earlier than start when they span midnight).
imessage ImessageSender read-only iMessage senders only: what the line can do beyond replying in iMessage. Set by the Flow team (ask them to change it); absent on other channels.
Show child attributes
fallback boolean required Whether messages to a contact who cannot get iMessage go by SMS or RCS
instead. The network is chosen for each message when it is sent, never
by your app; the message's network says which one carried it.
Included on Flow's shared lines; on a dedicated line, an add-on
arranged with the Flow team. Off, a message to a contact who is not on
iMessage fails (message.failed). A message that goes by SMS or RCS
shows no imessage.styles, imessage.subject or effect, and edits
and unsends of it answer 422 unsupported_content with
channel_code not_on_sms (see Message.network).
call_forwarding CallForwarding Dedicated lines only: where phone calls to the line's number are
forwarded, set up with the Flow team. Forwarded calls send no event.
FaceTime calls are not forwarded; they arrive as call.received. Absent
when calls are not forwarded (always on shared lines).
Show child attributes
phone string required The E.164 number calls are forwarded to.
can_initiate boolean required Whether the line may write first: start a conversation with
POST /v0/messages with a contact who has not messaged it. Off (the
default), the line only replies and a start answers 403 permission.
Writing first is available on request from the Flow team, on dedicated
lines only (always false on shared lines); a line that starts
conversations spends its new-contact budget (limits), which grows
during warm-up.
setup SenderSetup read-only Only in the answer to POST /v0/senders that connected a WhatsApp number:
what you still set in your own Meta app, which Flow cannot set for you.
Flow already points the number's webhooks at callback_url (Meta's
phone-number-level override); entering the same URL in your app makes its
field subscriptions apply. The verify token is shown only in this answer
(connecting the number again shows it again); keep it like a password.
Show child attributes
callback_url string<uri> required The callback URL to enter in your Meta app (App Dashboard > WhatsApp > Configuration > Webhook).
verify_token string required The verify token to enter with the callback URL.
webhook_fields string[] required The webhook fields to subscribe in the same place.
steps string[] required The steps in your Meta app, in order, as plain sentences to show a person.
join_code string Shared senders only. The whole message a contact sends to this sender to join your app: Hi , the app's name (there when it has none), then the app's sandbox_join_code (from GET /v0/app) in brackets after ref, for example Hi Sandbox app (ref brave-otter-40718263). Show it to testers as is; a link that fills it in (wa.me, sms:) carries it URL-encoded. Any message of at most 160 characters that ends with (ref <code>) joins, so a tester may change the words before the brackets.
created_at string<date-time> required When the sender was created.
400 application/json
The request is malformed or a parameter is invalid (invalid_request).
error.param names the parameter. Every endpoint answers this for a
Flow-Version header that is not a date this API serves (param
Flow-Version), and every POST that takes an Idempotency-Key for a key
over 255 characters (param Idempotency-Key).
error ErrorBody required The details of an error.
Show child attributes
type enum<string> required What went wrong. A closed list: new types arrive only with a new API version.
Each type has a page at https://api.flow.engineer/docs/errors/<type>, given in
the error's doc_url; the error's hint says what to change for the case.
invalid_request(400, or 413 for a request body over the size limit): the request is malformed or a parameter is invalid. Fix the parameter named inparam; thehintsays what it must look like. Every endpoint answers it for aFlow-Versionheader that is not a version this API serves.authentication(401): the API key is missing, malformed, unknown, revoked or expired. AnAuthorization: Bearerheader with nothing after it usually means the environment variable holding the key was empty when the header was built (for an MCP client, when the server was added): thehintsays so. SendAuthorization: Bearer fk_test_...orfk_live_...with a current key; with no key at all, get a test key withPOST /v0/sandbox/keys. A sandbox key past itsexpires_athaschannel_codesandbox_key_expired: sign in to claim the app, or get a new key.permission(403): the key may not do this, for example a test key using a live sender, a sandbox contact who joined another app, a new conversation from an iMessage line that may only reply, or a send past the app's sandbox allowance (channel_codesandbox_allowance_used,sandbox_contact_limitorsandbox_channel_not_included). Use the key of the right mode, have the contact send your join message, wait for the contact to message the line first, or sign in through the device flow to lift the allowance. On SMS:contact_opted_out(the contact texted STOP; nothing more goes to them until they text START),consent_required(a start to someone who never texted your numbers: record their consent withconsenton the start). In a HIPAA account:hipaa_channel_not_covered(the channel has no business associate agreement; use SMS). Simulated conversations:simulated_contact(a real sender was pointed at a simulated contact; reply in the contact's simulated conversation),not_simulated(a simulated sender or test endpoint was pointed at a real contact),test_mode_only,simulated_conversation_limit. Inmessage.failed:sandbox_contact_left(the contact joined another app on the sandbox sender while the message was queued; have them send your join message again).not_found(404): no such object for this app and mode. Check the ID's prefix and that it was made with a key of the same mode (test and live data are separate).idempotency_conflict(409): the idempotency key was used for a different request (channel_codebody_mismatch: use a new key), or that request is still running (in_progress: waitretry_afterseconds and repeat it with the same key), or it already created a secret that is shown only once (secret_not_kept: creating a webhook endpoint, rotating its secret, connecting a WhatsApp number; a secret that was lost must be rotated again, and a WhatsApp number connected again to see its verify token).outside_window(409, or inmessage.failed): the channel will not deliver outside its conversation window. On WhatsApp the 24-hour window is closed: send a template (POST /v0/conversations/{conversation_id}/template, orPOST /v0/messageswithkindtemplateto start), or wait for the contact to write. On iMessage the contact has not messaged the line (or opted in) yet, or not recently, so the failure arrives as amessage.failedevent: wait for the contact to write, then reply in that conversation. Typing and read receipts answer409 outside_windowdirectly when the channel's window for them is closed (iMessage typing works only within 5 minutes of the contact's last message; WhatsApp read receipts go at any time); ignore it and send your reply. On SMS,channel_codequiet_hours: the contact has not texted in the last hour and it is inside the sender's quiet hours in their time zone; retry afterretry_afterseconds, or wait for them to text. Inmessage.failed:window_closed(WhatsApp's 24-hour window closed while the message was queued; send a template) andqueued_too_long(the message waited in the queue longer than its sender allows, for example while the sender was paused; send it again if it still applies).unsupported_content(422): the conversation's channel does not take this kind, field, block or value, and nothing was sent; Flow never converts a message.paramnames what was refused:kind(the endpoint's kind is not on this channel, such as a carousel on Telegram), a field or block (reply_toon SMS,telegramon WhatsApp,telegram.is_big), or the field whose value is not taken (emojioutside Telegram's reaction emoji,buttonsfor more than 3 WhatsApp buttons).channelis the conversation's channel andhintsays what to send instead over plain HTTP; each endpoint's page lists its channels. In an iMessage group conversation, a reaction, typing or a read receipt answers it withparamkindandchannel_codegroup_unsupported: leave it out. Media to an SMS number without MMS answers it withparammediaandchannel_codeno_mms. An edit or unsend of an iMessage message that went by SMS or RCS (itsnetwork) answers it withchannel_codenot_on_sms: send a new message instead. An edit or unsend past the channel's time limit answers it withchannel_codeedit_window_closedorunsend_window_closed(iMessage: edits within 15 minutes of sending, unsends within 2 minutes; Telegram: unsends within 48 hours), and an iMessage edit of the message that started the conversation withfirst_message_not_editable;messagestates the limit and how long ago the message was sent. Send a new message instead.POST /v0/filesanswers it for a type Flow does not take (programs, web pages, unknown types). A send byfile_idof a document a contact sent before Flow malware-scanned documents answers it too: Flow sends only documents it scanned.new_contact_limit(429): the sender has used its budget for starting conversations. Waitretry_afterseconds; replies into existing conversations still go.sender_throttled(429): abuse signals tripped (the same text to many new contacts, many starts with no reply, blocks), so the sender may not start conversations untilretry_after; replies into existing conversations still go. Personalise first messages and start only conversations people expect.file_blocked(422): the file failed the malware scan and was not stored. Send a different file. (When the scan cannot run at all, the upload answers a retryable500 api_errorwithRetry-Afterinstead.)rate_limited(429): either too many requests for this key (the per-key request limit, reported in theRateLimit-*headers) or sends faster than the sender's sending rate (pacing). WaitRetry-After(retry_after) seconds and retry with the sameIdempotency-Key.channel_error(502, or inmessage.failed): the channel refused or failed the message, or timed out;channel_codecarries its own code. Readmessage, change what the channel objected to, and send again. From typing and read receipts, which call the channel at once, it is safe to ignore. SMS codes are Flow's, the same whichever carrier network carries the message:carrier_filtered(a carrier's spam filter blocked it: make it read like a person's message, no shortened links),unreachable,invalid_number,landline,registration_required(the number's 10DLC or toll-free registration is not approved yet),carrier_error, andprovider_not_configured(the SMS sender is not fully set up on Flow's side, so nothing was sent; contact the Flow team). Flow's own codes on every channel:channel_timeout_unknown_outcome(inmessage.failed: the channel did not answer after the message was sent, and the resends its policy allows did not settle it, so the message may have arrived or not; check with the contact before sending it again) andunknown_outcome(from typing and read receipts: the call timed out after it was sent).not_implemented(501): this endpoint or channel is not live yet during the beta. Use a channel that is live (Telegram, iMessage), or check the changelog.api_error(500, 503): something went wrong on Flow's side. Retry with the same idempotency key afterretry_afterseconds, and quoterequest_idif it persists.
message string required A sentence for a developer saying what went wrong. Do not parse it; switch on type.
hint string required One sentence saying what to change to make the request succeed, specific
to this case, for example "Send a template instead: POST
/v0/conversations/{conversation_id}/template." For unsupported_content
it says what to send instead over plain HTTP. Written for developers and
coding agents alike. Do not parse it; it may be reworded at any time.
doc_url string<uri> required The documentation page for this error's type, https://api.flow.engineer/docs/errors/<type>. It says what the error means, why it happens and how to fix it, with code.
param string The request parameter the error is about, as a dotted path (kind, telegram.is_big, buttons, cards[1].header.type, limit).
retry_after integer Seconds to wait before retrying, for errors that clear by themselves.
channel enum<string> The conversation's channel, on errors about what it takes (unsupported_content) and its windows (outside_window).
conversation string A conversation ID, conv_ and a ULID.
sender string A sender ID, snd_ and a ULID.
channel_code string For channel_error, the channel's own error code, as given by the
channel. For some other errors, Flow's own code naming the case, for
example sandbox_allowance_used (permission: the sandbox allowance is
used up), sandbox_contact_limit (permission: the allowance has no
room for another contact), sandbox_channel_not_included (permission:
the allowance does not cover this sandbox channel), sign_in_required
(permission: an app made without an account cannot do this) or
sandbox_key_expired (authentication: a key from POST /v0/sandbox/keys passed its expires_at; permission, for a send
that carries no key, such as a reply in a webhook answer, from such an
app). For idempotency_conflict, always one of body_mismatch (the
key was used for a different request), in_progress (the first
request is still running; retry_after says when to repeat it) or
secret_not_kept (its answer carried a secret shown only once).
SMS: contact_opted_out and consent_required (permission),
quiet_hours (outside_window, with retry_after), and
carrier_filtered, unreachable, invalid_number, landline,
registration_required, carrier_error and provider_not_configured
(channel_error). HIPAA accounts: hipaa_channel_not_covered
(permission). Connecting your own WhatsApp number (POST /v0/senders): whatsapp_token_malformed, whatsapp_token_rejected,
whatsapp_app_secret_malformed and whatsapp_number_not_in_account
(invalid_request), and whatsapp_number_taken,
whatsapp_account_taken and whatsapp_number_managed (permission).
Simulated conversations (permission): test_mode_only,
not_simulated, simulated_contact and simulated_conversation_limit.
For a message that failed after it was queued (message.failed):
sandbox_contact_left (permission), window_closed and
queued_too_long (outside_window), and
channel_timeout_unknown_outcome (channel_error: the channel never
confirmed the send, so it may or may not have arrived). iMessage
(unsupported_content): group_unsupported (a reaction, typing or a
read receipt in a group conversation; param kind) and
not_on_sms (an edit or unsend of a message that went by SMS or RCS;
param kind). SMS (unsupported_content): no_mms (media to a
number without MMS; param media).
Edits and unsends (unsupported_content): edit_window_closed and
unsend_window_closed (the message is older than the channel allows
for the change) and first_message_not_editable (iMessage: the message
that started the conversation). From typing and
read receipts, unknown_outcome (channel_error: the call timed out after
it was sent).
request_id string Flow's ID for this request. Quote it when asking for help.
401 application/json
The API key is missing, malformed, unknown, revoked or expired (authentication).
error ErrorBody required The details of an error.
Show child attributes
type enum<string> required What went wrong. A closed list: new types arrive only with a new API version.
Each type has a page at https://api.flow.engineer/docs/errors/<type>, given in
the error's doc_url; the error's hint says what to change for the case.
invalid_request(400, or 413 for a request body over the size limit): the request is malformed or a parameter is invalid. Fix the parameter named inparam; thehintsays what it must look like. Every endpoint answers it for aFlow-Versionheader that is not a version this API serves.authentication(401): the API key is missing, malformed, unknown, revoked or expired. AnAuthorization: Bearerheader with nothing after it usually means the environment variable holding the key was empty when the header was built (for an MCP client, when the server was added): thehintsays so. SendAuthorization: Bearer fk_test_...orfk_live_...with a current key; with no key at all, get a test key withPOST /v0/sandbox/keys. A sandbox key past itsexpires_athaschannel_codesandbox_key_expired: sign in to claim the app, or get a new key.permission(403): the key may not do this, for example a test key using a live sender, a sandbox contact who joined another app, a new conversation from an iMessage line that may only reply, or a send past the app's sandbox allowance (channel_codesandbox_allowance_used,sandbox_contact_limitorsandbox_channel_not_included). Use the key of the right mode, have the contact send your join message, wait for the contact to message the line first, or sign in through the device flow to lift the allowance. On SMS:contact_opted_out(the contact texted STOP; nothing more goes to them until they text START),consent_required(a start to someone who never texted your numbers: record their consent withconsenton the start). In a HIPAA account:hipaa_channel_not_covered(the channel has no business associate agreement; use SMS). Simulated conversations:simulated_contact(a real sender was pointed at a simulated contact; reply in the contact's simulated conversation),not_simulated(a simulated sender or test endpoint was pointed at a real contact),test_mode_only,simulated_conversation_limit. Inmessage.failed:sandbox_contact_left(the contact joined another app on the sandbox sender while the message was queued; have them send your join message again).not_found(404): no such object for this app and mode. Check the ID's prefix and that it was made with a key of the same mode (test and live data are separate).idempotency_conflict(409): the idempotency key was used for a different request (channel_codebody_mismatch: use a new key), or that request is still running (in_progress: waitretry_afterseconds and repeat it with the same key), or it already created a secret that is shown only once (secret_not_kept: creating a webhook endpoint, rotating its secret, connecting a WhatsApp number; a secret that was lost must be rotated again, and a WhatsApp number connected again to see its verify token).outside_window(409, or inmessage.failed): the channel will not deliver outside its conversation window. On WhatsApp the 24-hour window is closed: send a template (POST /v0/conversations/{conversation_id}/template, orPOST /v0/messageswithkindtemplateto start), or wait for the contact to write. On iMessage the contact has not messaged the line (or opted in) yet, or not recently, so the failure arrives as amessage.failedevent: wait for the contact to write, then reply in that conversation. Typing and read receipts answer409 outside_windowdirectly when the channel's window for them is closed (iMessage typing works only within 5 minutes of the contact's last message; WhatsApp read receipts go at any time); ignore it and send your reply. On SMS,channel_codequiet_hours: the contact has not texted in the last hour and it is inside the sender's quiet hours in their time zone; retry afterretry_afterseconds, or wait for them to text. Inmessage.failed:window_closed(WhatsApp's 24-hour window closed while the message was queued; send a template) andqueued_too_long(the message waited in the queue longer than its sender allows, for example while the sender was paused; send it again if it still applies).unsupported_content(422): the conversation's channel does not take this kind, field, block or value, and nothing was sent; Flow never converts a message.paramnames what was refused:kind(the endpoint's kind is not on this channel, such as a carousel on Telegram), a field or block (reply_toon SMS,telegramon WhatsApp,telegram.is_big), or the field whose value is not taken (emojioutside Telegram's reaction emoji,buttonsfor more than 3 WhatsApp buttons).channelis the conversation's channel andhintsays what to send instead over plain HTTP; each endpoint's page lists its channels. In an iMessage group conversation, a reaction, typing or a read receipt answers it withparamkindandchannel_codegroup_unsupported: leave it out. Media to an SMS number without MMS answers it withparammediaandchannel_codeno_mms. An edit or unsend of an iMessage message that went by SMS or RCS (itsnetwork) answers it withchannel_codenot_on_sms: send a new message instead. An edit or unsend past the channel's time limit answers it withchannel_codeedit_window_closedorunsend_window_closed(iMessage: edits within 15 minutes of sending, unsends within 2 minutes; Telegram: unsends within 48 hours), and an iMessage edit of the message that started the conversation withfirst_message_not_editable;messagestates the limit and how long ago the message was sent. Send a new message instead.POST /v0/filesanswers it for a type Flow does not take (programs, web pages, unknown types). A send byfile_idof a document a contact sent before Flow malware-scanned documents answers it too: Flow sends only documents it scanned.new_contact_limit(429): the sender has used its budget for starting conversations. Waitretry_afterseconds; replies into existing conversations still go.sender_throttled(429): abuse signals tripped (the same text to many new contacts, many starts with no reply, blocks), so the sender may not start conversations untilretry_after; replies into existing conversations still go. Personalise first messages and start only conversations people expect.file_blocked(422): the file failed the malware scan and was not stored. Send a different file. (When the scan cannot run at all, the upload answers a retryable500 api_errorwithRetry-Afterinstead.)rate_limited(429): either too many requests for this key (the per-key request limit, reported in theRateLimit-*headers) or sends faster than the sender's sending rate (pacing). WaitRetry-After(retry_after) seconds and retry with the sameIdempotency-Key.channel_error(502, or inmessage.failed): the channel refused or failed the message, or timed out;channel_codecarries its own code. Readmessage, change what the channel objected to, and send again. From typing and read receipts, which call the channel at once, it is safe to ignore. SMS codes are Flow's, the same whichever carrier network carries the message:carrier_filtered(a carrier's spam filter blocked it: make it read like a person's message, no shortened links),unreachable,invalid_number,landline,registration_required(the number's 10DLC or toll-free registration is not approved yet),carrier_error, andprovider_not_configured(the SMS sender is not fully set up on Flow's side, so nothing was sent; contact the Flow team). Flow's own codes on every channel:channel_timeout_unknown_outcome(inmessage.failed: the channel did not answer after the message was sent, and the resends its policy allows did not settle it, so the message may have arrived or not; check with the contact before sending it again) andunknown_outcome(from typing and read receipts: the call timed out after it was sent).not_implemented(501): this endpoint or channel is not live yet during the beta. Use a channel that is live (Telegram, iMessage), or check the changelog.api_error(500, 503): something went wrong on Flow's side. Retry with the same idempotency key afterretry_afterseconds, and quoterequest_idif it persists.
message string required A sentence for a developer saying what went wrong. Do not parse it; switch on type.
hint string required One sentence saying what to change to make the request succeed, specific
to this case, for example "Send a template instead: POST
/v0/conversations/{conversation_id}/template." For unsupported_content
it says what to send instead over plain HTTP. Written for developers and
coding agents alike. Do not parse it; it may be reworded at any time.
doc_url string<uri> required The documentation page for this error's type, https://api.flow.engineer/docs/errors/<type>. It says what the error means, why it happens and how to fix it, with code.
param string The request parameter the error is about, as a dotted path (kind, telegram.is_big, buttons, cards[1].header.type, limit).
retry_after integer Seconds to wait before retrying, for errors that clear by themselves.
channel enum<string> The conversation's channel, on errors about what it takes (unsupported_content) and its windows (outside_window).
conversation string A conversation ID, conv_ and a ULID.
sender string A sender ID, snd_ and a ULID.
channel_code string For channel_error, the channel's own error code, as given by the
channel. For some other errors, Flow's own code naming the case, for
example sandbox_allowance_used (permission: the sandbox allowance is
used up), sandbox_contact_limit (permission: the allowance has no
room for another contact), sandbox_channel_not_included (permission:
the allowance does not cover this sandbox channel), sign_in_required
(permission: an app made without an account cannot do this) or
sandbox_key_expired (authentication: a key from POST /v0/sandbox/keys passed its expires_at; permission, for a send
that carries no key, such as a reply in a webhook answer, from such an
app). For idempotency_conflict, always one of body_mismatch (the
key was used for a different request), in_progress (the first
request is still running; retry_after says when to repeat it) or
secret_not_kept (its answer carried a secret shown only once).
SMS: contact_opted_out and consent_required (permission),
quiet_hours (outside_window, with retry_after), and
carrier_filtered, unreachable, invalid_number, landline,
registration_required, carrier_error and provider_not_configured
(channel_error). HIPAA accounts: hipaa_channel_not_covered
(permission). Connecting your own WhatsApp number (POST /v0/senders): whatsapp_token_malformed, whatsapp_token_rejected,
whatsapp_app_secret_malformed and whatsapp_number_not_in_account
(invalid_request), and whatsapp_number_taken,
whatsapp_account_taken and whatsapp_number_managed (permission).
Simulated conversations (permission): test_mode_only,
not_simulated, simulated_contact and simulated_conversation_limit.
For a message that failed after it was queued (message.failed):
sandbox_contact_left (permission), window_closed and
queued_too_long (outside_window), and
channel_timeout_unknown_outcome (channel_error: the channel never
confirmed the send, so it may or may not have arrived). iMessage
(unsupported_content): group_unsupported (a reaction, typing or a
read receipt in a group conversation; param kind) and
not_on_sms (an edit or unsend of a message that went by SMS or RCS;
param kind). SMS (unsupported_content): no_mms (media to a
number without MMS; param media).
Edits and unsends (unsupported_content): edit_window_closed and
unsend_window_closed (the message is older than the channel allows
for the change) and first_message_not_editable (iMessage: the message
that started the conversation). From typing and
read receipts, unknown_outcome (channel_error: the call timed out after
it was sent).
request_id string Flow's ID for this request. Quote it when asking for help.
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 inparam; thehintsays what it must look like. Every endpoint answers it for aFlow-Versionheader that is not a version this API serves.authentication(401): the API key is missing, malformed, unknown, revoked or expired. AnAuthorization: Bearerheader with nothing after it usually means the environment variable holding the key was empty when the header was built (for an MCP client, when the server was added): thehintsays so. SendAuthorization: Bearer fk_test_...orfk_live_...with a current key; with no key at all, get a test key withPOST /v0/sandbox/keys. A sandbox key past itsexpires_athaschannel_codesandbox_key_expired: sign in to claim the app, or get a new key.permission(403): the key may not do this, for example a test key using a live sender, a sandbox contact who joined another app, a new conversation from an iMessage line that may only reply, or a send past the app's sandbox allowance (channel_codesandbox_allowance_used,sandbox_contact_limitorsandbox_channel_not_included). Use the key of the right mode, have the contact send your join message, wait for the contact to message the line first, or sign in through the device flow to lift the allowance. On SMS:contact_opted_out(the contact texted STOP; nothing more goes to them until they text START),consent_required(a start to someone who never texted your numbers: record their consent withconsenton the start). In a HIPAA account:hipaa_channel_not_covered(the channel has no business associate agreement; use SMS). Simulated conversations:simulated_contact(a real sender was pointed at a simulated contact; reply in the contact's simulated conversation),not_simulated(a simulated sender or test endpoint was pointed at a real contact),test_mode_only,simulated_conversation_limit. Inmessage.failed:sandbox_contact_left(the contact joined another app on the sandbox sender while the message was queued; have them send your join message again).not_found(404): no such object for this app and mode. Check the ID's prefix and that it was made with a key of the same mode (test and live data are separate).idempotency_conflict(409): the idempotency key was used for a different request (channel_codebody_mismatch: use a new key), or that request is still running (in_progress: waitretry_afterseconds and repeat it with the same key), or it already created a secret that is shown only once (secret_not_kept: creating a webhook endpoint, rotating its secret, connecting a WhatsApp number; a secret that was lost must be rotated again, and a WhatsApp number connected again to see its verify token).outside_window(409, or inmessage.failed): the channel will not deliver outside its conversation window. On WhatsApp the 24-hour window is closed: send a template (POST /v0/conversations/{conversation_id}/template, orPOST /v0/messageswithkindtemplateto start), or wait for the contact to write. On iMessage the contact has not messaged the line (or opted in) yet, or not recently, so the failure arrives as amessage.failedevent: wait for the contact to write, then reply in that conversation. Typing and read receipts answer409 outside_windowdirectly when the channel's window for them is closed (iMessage typing works only within 5 minutes of the contact's last message; WhatsApp read receipts go at any time); ignore it and send your reply. On SMS,channel_codequiet_hours: the contact has not texted in the last hour and it is inside the sender's quiet hours in their time zone; retry afterretry_afterseconds, or wait for them to text. Inmessage.failed:window_closed(WhatsApp's 24-hour window closed while the message was queued; send a template) andqueued_too_long(the message waited in the queue longer than its sender allows, for example while the sender was paused; send it again if it still applies).unsupported_content(422): the conversation's channel does not take this kind, field, block or value, and nothing was sent; Flow never converts a message.paramnames what was refused:kind(the endpoint's kind is not on this channel, such as a carousel on Telegram), a field or block (reply_toon SMS,telegramon WhatsApp,telegram.is_big), or the field whose value is not taken (emojioutside Telegram's reaction emoji,buttonsfor more than 3 WhatsApp buttons).channelis the conversation's channel andhintsays what to send instead over plain HTTP; each endpoint's page lists its channels. In an iMessage group conversation, a reaction, typing or a read receipt answers it withparamkindandchannel_codegroup_unsupported: leave it out. Media to an SMS number without MMS answers it withparammediaandchannel_codeno_mms. An edit or unsend of an iMessage message that went by SMS or RCS (itsnetwork) answers it withchannel_codenot_on_sms: send a new message instead. An edit or unsend past the channel's time limit answers it withchannel_codeedit_window_closedorunsend_window_closed(iMessage: edits within 15 minutes of sending, unsends within 2 minutes; Telegram: unsends within 48 hours), and an iMessage edit of the message that started the conversation withfirst_message_not_editable;messagestates the limit and how long ago the message was sent. Send a new message instead.POST /v0/filesanswers it for a type Flow does not take (programs, web pages, unknown types). A send byfile_idof a document a contact sent before Flow malware-scanned documents answers it too: Flow sends only documents it scanned.new_contact_limit(429): the sender has used its budget for starting conversations. Waitretry_afterseconds; replies into existing conversations still go.sender_throttled(429): abuse signals tripped (the same text to many new contacts, many starts with no reply, blocks), so the sender may not start conversations untilretry_after; replies into existing conversations still go. Personalise first messages and start only conversations people expect.file_blocked(422): the file failed the malware scan and was not stored. Send a different file. (When the scan cannot run at all, the upload answers a retryable500 api_errorwithRetry-Afterinstead.)rate_limited(429): either too many requests for this key (the per-key request limit, reported in theRateLimit-*headers) or sends faster than the sender's sending rate (pacing). WaitRetry-After(retry_after) seconds and retry with the sameIdempotency-Key.channel_error(502, or inmessage.failed): the channel refused or failed the message, or timed out;channel_codecarries its own code. Readmessage, change what the channel objected to, and send again. From typing and read receipts, which call the channel at once, it is safe to ignore. SMS codes are Flow's, the same whichever carrier network carries the message:carrier_filtered(a carrier's spam filter blocked it: make it read like a person's message, no shortened links),unreachable,invalid_number,landline,registration_required(the number's 10DLC or toll-free registration is not approved yet),carrier_error, andprovider_not_configured(the SMS sender is not fully set up on Flow's side, so nothing was sent; contact the Flow team). Flow's own codes on every channel:channel_timeout_unknown_outcome(inmessage.failed: the channel did not answer after the message was sent, and the resends its policy allows did not settle it, so the message may have arrived or not; check with the contact before sending it again) andunknown_outcome(from typing and read receipts: the call timed out after it was sent).not_implemented(501): this endpoint or channel is not live yet during the beta. Use a channel that is live (Telegram, iMessage), or check the changelog.api_error(500, 503): something went wrong on Flow's side. Retry with the same idempotency key afterretry_afterseconds, and quoterequest_idif it persists.
message string required A sentence for a developer saying what went wrong. Do not parse it; switch on type.
hint string required One sentence saying what to change to make the request succeed, specific
to this case, for example "Send a template instead: POST
/v0/conversations/{conversation_id}/template." For unsupported_content
it says what to send instead over plain HTTP. Written for developers and
coding agents alike. Do not parse it; it may be reworded at any time.
doc_url string<uri> required The documentation page for this error's type, https://api.flow.engineer/docs/errors/<type>. It says what the error means, why it happens and how to fix it, with code.
param string The request parameter the error is about, as a dotted path (kind, telegram.is_big, buttons, cards[1].header.type, limit).
retry_after integer Seconds to wait before retrying, for errors that clear by themselves.
channel enum<string> The conversation's channel, on errors about what it takes (unsupported_content) and its windows (outside_window).
conversation string A conversation ID, conv_ and a ULID.
sender string A sender ID, snd_ and a ULID.
channel_code string For channel_error, the channel's own error code, as given by the
channel. For some other errors, Flow's own code naming the case, for
example sandbox_allowance_used (permission: the sandbox allowance is
used up), sandbox_contact_limit (permission: the allowance has no
room for another contact), sandbox_channel_not_included (permission:
the allowance does not cover this sandbox channel), sign_in_required
(permission: an app made without an account cannot do this) or
sandbox_key_expired (authentication: a key from POST /v0/sandbox/keys passed its expires_at; permission, for a send
that carries no key, such as a reply in a webhook answer, from such an
app). For idempotency_conflict, always one of body_mismatch (the
key was used for a different request), in_progress (the first
request is still running; retry_after says when to repeat it) or
secret_not_kept (its answer carried a secret shown only once).
SMS: contact_opted_out and consent_required (permission),
quiet_hours (outside_window, with retry_after), and
carrier_filtered, unreachable, invalid_number, landline,
registration_required, carrier_error and provider_not_configured
(channel_error). HIPAA accounts: hipaa_channel_not_covered
(permission). Connecting your own WhatsApp number (POST /v0/senders): whatsapp_token_malformed, whatsapp_token_rejected,
whatsapp_app_secret_malformed and whatsapp_number_not_in_account
(invalid_request), and whatsapp_number_taken,
whatsapp_account_taken and whatsapp_number_managed (permission).
Simulated conversations (permission): test_mode_only,
not_simulated, simulated_contact and simulated_conversation_limit.
For a message that failed after it was queued (message.failed):
sandbox_contact_left (permission), window_closed and
queued_too_long (outside_window), and
channel_timeout_unknown_outcome (channel_error: the channel never
confirmed the send, so it may or may not have arrived). iMessage
(unsupported_content): group_unsupported (a reaction, typing or a
read receipt in a group conversation; param kind) and
not_on_sms (an edit or unsend of a message that went by SMS or RCS;
param kind). SMS (unsupported_content): no_mms (media to a
number without MMS; param media).
Edits and unsends (unsupported_content): edit_window_closed and
unsend_window_closed (the message is older than the channel allows
for the change) and first_message_not_editable (iMessage: the message
that started the conversation). From typing and
read receipts, unknown_outcome (channel_error: the call timed out after
it was sent).
request_id string Flow's ID for this request. Quote it when asking for help.
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 inparam; thehintsays what it must look like. Every endpoint answers it for aFlow-Versionheader that is not a version this API serves.authentication(401): the API key is missing, malformed, unknown, revoked or expired. AnAuthorization: Bearerheader with nothing after it usually means the environment variable holding the key was empty when the header was built (for an MCP client, when the server was added): thehintsays so. SendAuthorization: Bearer fk_test_...orfk_live_...with a current key; with no key at all, get a test key withPOST /v0/sandbox/keys. A sandbox key past itsexpires_athaschannel_codesandbox_key_expired: sign in to claim the app, or get a new key.permission(403): the key may not do this, for example a test key using a live sender, a sandbox contact who joined another app, a new conversation from an iMessage line that may only reply, or a send past the app's sandbox allowance (channel_codesandbox_allowance_used,sandbox_contact_limitorsandbox_channel_not_included). Use the key of the right mode, have the contact send your join message, wait for the contact to message the line first, or sign in through the device flow to lift the allowance. On SMS:contact_opted_out(the contact texted STOP; nothing more goes to them until they text START),consent_required(a start to someone who never texted your numbers: record their consent withconsenton the start). In a HIPAA account:hipaa_channel_not_covered(the channel has no business associate agreement; use SMS). Simulated conversations:simulated_contact(a real sender was pointed at a simulated contact; reply in the contact's simulated conversation),not_simulated(a simulated sender or test endpoint was pointed at a real contact),test_mode_only,simulated_conversation_limit. Inmessage.failed:sandbox_contact_left(the contact joined another app on the sandbox sender while the message was queued; have them send your join message again).not_found(404): no such object for this app and mode. Check the ID's prefix and that it was made with a key of the same mode (test and live data are separate).idempotency_conflict(409): the idempotency key was used for a different request (channel_codebody_mismatch: use a new key), or that request is still running (in_progress: waitretry_afterseconds and repeat it with the same key), or it already created a secret that is shown only once (secret_not_kept: creating a webhook endpoint, rotating its secret, connecting a WhatsApp number; a secret that was lost must be rotated again, and a WhatsApp number connected again to see its verify token).outside_window(409, or inmessage.failed): the channel will not deliver outside its conversation window. On WhatsApp the 24-hour window is closed: send a template (POST /v0/conversations/{conversation_id}/template, orPOST /v0/messageswithkindtemplateto start), or wait for the contact to write. On iMessage the contact has not messaged the line (or opted in) yet, or not recently, so the failure arrives as amessage.failedevent: wait for the contact to write, then reply in that conversation. Typing and read receipts answer409 outside_windowdirectly when the channel's window for them is closed (iMessage typing works only within 5 minutes of the contact's last message; WhatsApp read receipts go at any time); ignore it and send your reply. On SMS,channel_codequiet_hours: the contact has not texted in the last hour and it is inside the sender's quiet hours in their time zone; retry afterretry_afterseconds, or wait for them to text. Inmessage.failed:window_closed(WhatsApp's 24-hour window closed while the message was queued; send a template) andqueued_too_long(the message waited in the queue longer than its sender allows, for example while the sender was paused; send it again if it still applies).unsupported_content(422): the conversation's channel does not take this kind, field, block or value, and nothing was sent; Flow never converts a message.paramnames what was refused:kind(the endpoint's kind is not on this channel, such as a carousel on Telegram), a field or block (reply_toon SMS,telegramon WhatsApp,telegram.is_big), or the field whose value is not taken (emojioutside Telegram's reaction emoji,buttonsfor more than 3 WhatsApp buttons).channelis the conversation's channel andhintsays what to send instead over plain HTTP; each endpoint's page lists its channels. In an iMessage group conversation, a reaction, typing or a read receipt answers it withparamkindandchannel_codegroup_unsupported: leave it out. Media to an SMS number without MMS answers it withparammediaandchannel_codeno_mms. An edit or unsend of an iMessage message that went by SMS or RCS (itsnetwork) answers it withchannel_codenot_on_sms: send a new message instead. An edit or unsend past the channel's time limit answers it withchannel_codeedit_window_closedorunsend_window_closed(iMessage: edits within 15 minutes of sending, unsends within 2 minutes; Telegram: unsends within 48 hours), and an iMessage edit of the message that started the conversation withfirst_message_not_editable;messagestates the limit and how long ago the message was sent. Send a new message instead.POST /v0/filesanswers it for a type Flow does not take (programs, web pages, unknown types). A send byfile_idof a document a contact sent before Flow malware-scanned documents answers it too: Flow sends only documents it scanned.new_contact_limit(429): the sender has used its budget for starting conversations. Waitretry_afterseconds; replies into existing conversations still go.sender_throttled(429): abuse signals tripped (the same text to many new contacts, many starts with no reply, blocks), so the sender may not start conversations untilretry_after; replies into existing conversations still go. Personalise first messages and start only conversations people expect.file_blocked(422): the file failed the malware scan and was not stored. Send a different file. (When the scan cannot run at all, the upload answers a retryable500 api_errorwithRetry-Afterinstead.)rate_limited(429): either too many requests for this key (the per-key request limit, reported in theRateLimit-*headers) or sends faster than the sender's sending rate (pacing). WaitRetry-After(retry_after) seconds and retry with the sameIdempotency-Key.channel_error(502, or inmessage.failed): the channel refused or failed the message, or timed out;channel_codecarries its own code. Readmessage, change what the channel objected to, and send again. From typing and read receipts, which call the channel at once, it is safe to ignore. SMS codes are Flow's, the same whichever carrier network carries the message:carrier_filtered(a carrier's spam filter blocked it: make it read like a person's message, no shortened links),unreachable,invalid_number,landline,registration_required(the number's 10DLC or toll-free registration is not approved yet),carrier_error, andprovider_not_configured(the SMS sender is not fully set up on Flow's side, so nothing was sent; contact the Flow team). Flow's own codes on every channel:channel_timeout_unknown_outcome(inmessage.failed: the channel did not answer after the message was sent, and the resends its policy allows did not settle it, so the message may have arrived or not; check with the contact before sending it again) andunknown_outcome(from typing and read receipts: the call timed out after it was sent).not_implemented(501): this endpoint or channel is not live yet during the beta. Use a channel that is live (Telegram, iMessage), or check the changelog.api_error(500, 503): something went wrong on Flow's side. Retry with the same idempotency key afterretry_afterseconds, and quoterequest_idif it persists.
message string required A sentence for a developer saying what went wrong. Do not parse it; switch on type.
hint string required One sentence saying what to change to make the request succeed, specific
to this case, for example "Send a template instead: POST
/v0/conversations/{conversation_id}/template." For unsupported_content
it says what to send instead over plain HTTP. Written for developers and
coding agents alike. Do not parse it; it may be reworded at any time.
doc_url string<uri> required The documentation page for this error's type, https://api.flow.engineer/docs/errors/<type>. It says what the error means, why it happens and how to fix it, with code.
param string The request parameter the error is about, as a dotted path (kind, telegram.is_big, buttons, cards[1].header.type, limit).
retry_after integer Seconds to wait before retrying, for errors that clear by themselves.
channel enum<string> The conversation's channel, on errors about what it takes (unsupported_content) and its windows (outside_window).
conversation string A conversation ID, conv_ and a ULID.
sender string A sender ID, snd_ and a ULID.
channel_code string For channel_error, the channel's own error code, as given by the
channel. For some other errors, Flow's own code naming the case, for
example sandbox_allowance_used (permission: the sandbox allowance is
used up), sandbox_contact_limit (permission: the allowance has no
room for another contact), sandbox_channel_not_included (permission:
the allowance does not cover this sandbox channel), sign_in_required
(permission: an app made without an account cannot do this) or
sandbox_key_expired (authentication: a key from POST /v0/sandbox/keys passed its expires_at; permission, for a send
that carries no key, such as a reply in a webhook answer, from such an
app). For idempotency_conflict, always one of body_mismatch (the
key was used for a different request), in_progress (the first
request is still running; retry_after says when to repeat it) or
secret_not_kept (its answer carried a secret shown only once).
SMS: contact_opted_out and consent_required (permission),
quiet_hours (outside_window, with retry_after), and
carrier_filtered, unreachable, invalid_number, landline,
registration_required, carrier_error and provider_not_configured
(channel_error). HIPAA accounts: hipaa_channel_not_covered
(permission). Connecting your own WhatsApp number (POST /v0/senders): whatsapp_token_malformed, whatsapp_token_rejected,
whatsapp_app_secret_malformed and whatsapp_number_not_in_account
(invalid_request), and whatsapp_number_taken,
whatsapp_account_taken and whatsapp_number_managed (permission).
Simulated conversations (permission): test_mode_only,
not_simulated, simulated_contact and simulated_conversation_limit.
For a message that failed after it was queued (message.failed):
sandbox_contact_left (permission), window_closed and
queued_too_long (outside_window), and
channel_timeout_unknown_outcome (channel_error: the channel never
confirmed the send, so it may or may not have arrived). iMessage
(unsupported_content): group_unsupported (a reaction, typing or a
read receipt in a group conversation; param kind) and
not_on_sms (an edit or unsend of a message that went by SMS or RCS;
param kind). SMS (unsupported_content): no_mms (media to a
number without MMS; param media).
Edits and unsends (unsupported_content): edit_window_closed and
unsend_window_closed (the message is older than the channel allows
for the change) and first_message_not_editable (iMessage: the message
that started the conversation). From typing and
read receipts, unknown_outcome (channel_error: the call timed out after
it was sent).
request_id string Flow's ID for this request. Quote it when asking for help.
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 inparam; thehintsays what it must look like. Every endpoint answers it for aFlow-Versionheader that is not a version this API serves.authentication(401): the API key is missing, malformed, unknown, revoked or expired. AnAuthorization: Bearerheader with nothing after it usually means the environment variable holding the key was empty when the header was built (for an MCP client, when the server was added): thehintsays so. SendAuthorization: Bearer fk_test_...orfk_live_...with a current key; with no key at all, get a test key withPOST /v0/sandbox/keys. A sandbox key past itsexpires_athaschannel_codesandbox_key_expired: sign in to claim the app, or get a new key.permission(403): the key may not do this, for example a test key using a live sender, a sandbox contact who joined another app, a new conversation from an iMessage line that may only reply, or a send past the app's sandbox allowance (channel_codesandbox_allowance_used,sandbox_contact_limitorsandbox_channel_not_included). Use the key of the right mode, have the contact send your join message, wait for the contact to message the line first, or sign in through the device flow to lift the allowance. On SMS:contact_opted_out(the contact texted STOP; nothing more goes to them until they text START),consent_required(a start to someone who never texted your numbers: record their consent withconsenton the start). In a HIPAA account:hipaa_channel_not_covered(the channel has no business associate agreement; use SMS). Simulated conversations:simulated_contact(a real sender was pointed at a simulated contact; reply in the contact's simulated conversation),not_simulated(a simulated sender or test endpoint was pointed at a real contact),test_mode_only,simulated_conversation_limit. Inmessage.failed:sandbox_contact_left(the contact joined another app on the sandbox sender while the message was queued; have them send your join message again).not_found(404): no such object for this app and mode. Check the ID's prefix and that it was made with a key of the same mode (test and live data are separate).idempotency_conflict(409): the idempotency key was used for a different request (channel_codebody_mismatch: use a new key), or that request is still running (in_progress: waitretry_afterseconds and repeat it with the same key), or it already created a secret that is shown only once (secret_not_kept: creating a webhook endpoint, rotating its secret, connecting a WhatsApp number; a secret that was lost must be rotated again, and a WhatsApp number connected again to see its verify token).outside_window(409, or inmessage.failed): the channel will not deliver outside its conversation window. On WhatsApp the 24-hour window is closed: send a template (POST /v0/conversations/{conversation_id}/template, orPOST /v0/messageswithkindtemplateto start), or wait for the contact to write. On iMessage the contact has not messaged the line (or opted in) yet, or not recently, so the failure arrives as amessage.failedevent: wait for the contact to write, then reply in that conversation. Typing and read receipts answer409 outside_windowdirectly when the channel's window for them is closed (iMessage typing works only within 5 minutes of the contact's last message; WhatsApp read receipts go at any time); ignore it and send your reply. On SMS,channel_codequiet_hours: the contact has not texted in the last hour and it is inside the sender's quiet hours in their time zone; retry afterretry_afterseconds, or wait for them to text. Inmessage.failed:window_closed(WhatsApp's 24-hour window closed while the message was queued; send a template) andqueued_too_long(the message waited in the queue longer than its sender allows, for example while the sender was paused; send it again if it still applies).unsupported_content(422): the conversation's channel does not take this kind, field, block or value, and nothing was sent; Flow never converts a message.paramnames what was refused:kind(the endpoint's kind is not on this channel, such as a carousel on Telegram), a field or block (reply_toon SMS,telegramon WhatsApp,telegram.is_big), or the field whose value is not taken (emojioutside Telegram's reaction emoji,buttonsfor more than 3 WhatsApp buttons).channelis the conversation's channel andhintsays what to send instead over plain HTTP; each endpoint's page lists its channels. In an iMessage group conversation, a reaction, typing or a read receipt answers it withparamkindandchannel_codegroup_unsupported: leave it out. Media to an SMS number without MMS answers it withparammediaandchannel_codeno_mms. An edit or unsend of an iMessage message that went by SMS or RCS (itsnetwork) answers it withchannel_codenot_on_sms: send a new message instead. An edit or unsend past the channel's time limit answers it withchannel_codeedit_window_closedorunsend_window_closed(iMessage: edits within 15 minutes of sending, unsends within 2 minutes; Telegram: unsends within 48 hours), and an iMessage edit of the message that started the conversation withfirst_message_not_editable;messagestates the limit and how long ago the message was sent. Send a new message instead.POST /v0/filesanswers it for a type Flow does not take (programs, web pages, unknown types). A send byfile_idof a document a contact sent before Flow malware-scanned documents answers it too: Flow sends only documents it scanned.new_contact_limit(429): the sender has used its budget for starting conversations. Waitretry_afterseconds; replies into existing conversations still go.sender_throttled(429): abuse signals tripped (the same text to many new contacts, many starts with no reply, blocks), so the sender may not start conversations untilretry_after; replies into existing conversations still go. Personalise first messages and start only conversations people expect.file_blocked(422): the file failed the malware scan and was not stored. Send a different file. (When the scan cannot run at all, the upload answers a retryable500 api_errorwithRetry-Afterinstead.)rate_limited(429): either too many requests for this key (the per-key request limit, reported in theRateLimit-*headers) or sends faster than the sender's sending rate (pacing). WaitRetry-After(retry_after) seconds and retry with the sameIdempotency-Key.channel_error(502, or inmessage.failed): the channel refused or failed the message, or timed out;channel_codecarries its own code. Readmessage, change what the channel objected to, and send again. From typing and read receipts, which call the channel at once, it is safe to ignore. SMS codes are Flow's, the same whichever carrier network carries the message:carrier_filtered(a carrier's spam filter blocked it: make it read like a person's message, no shortened links),unreachable,invalid_number,landline,registration_required(the number's 10DLC or toll-free registration is not approved yet),carrier_error, andprovider_not_configured(the SMS sender is not fully set up on Flow's side, so nothing was sent; contact the Flow team). Flow's own codes on every channel:channel_timeout_unknown_outcome(inmessage.failed: the channel did not answer after the message was sent, and the resends its policy allows did not settle it, so the message may have arrived or not; check with the contact before sending it again) andunknown_outcome(from typing and read receipts: the call timed out after it was sent).not_implemented(501): this endpoint or channel is not live yet during the beta. Use a channel that is live (Telegram, iMessage), or check the changelog.api_error(500, 503): something went wrong on Flow's side. Retry with the same idempotency key afterretry_afterseconds, and quoterequest_idif it persists.
message string required A sentence for a developer saying what went wrong. Do not parse it; switch on type.
hint string required One sentence saying what to change to make the request succeed, specific
to this case, for example "Send a template instead: POST
/v0/conversations/{conversation_id}/template." For unsupported_content
it says what to send instead over plain HTTP. Written for developers and
coding agents alike. Do not parse it; it may be reworded at any time.
doc_url string<uri> required The documentation page for this error's type, https://api.flow.engineer/docs/errors/<type>. It says what the error means, why it happens and how to fix it, with code.
param string The request parameter the error is about, as a dotted path (kind, telegram.is_big, buttons, cards[1].header.type, limit).
retry_after integer Seconds to wait before retrying, for errors that clear by themselves.
channel enum<string> The conversation's channel, on errors about what it takes (unsupported_content) and its windows (outside_window).
conversation string A conversation ID, conv_ and a ULID.
sender string A sender ID, snd_ and a ULID.
channel_code string For channel_error, the channel's own error code, as given by the
channel. For some other errors, Flow's own code naming the case, for
example sandbox_allowance_used (permission: the sandbox allowance is
used up), sandbox_contact_limit (permission: the allowance has no
room for another contact), sandbox_channel_not_included (permission:
the allowance does not cover this sandbox channel), sign_in_required
(permission: an app made without an account cannot do this) or
sandbox_key_expired (authentication: a key from POST /v0/sandbox/keys passed its expires_at; permission, for a send
that carries no key, such as a reply in a webhook answer, from such an
app). For idempotency_conflict, always one of body_mismatch (the
key was used for a different request), in_progress (the first
request is still running; retry_after says when to repeat it) or
secret_not_kept (its answer carried a secret shown only once).
SMS: contact_opted_out and consent_required (permission),
quiet_hours (outside_window, with retry_after), and
carrier_filtered, unreachable, invalid_number, landline,
registration_required, carrier_error and provider_not_configured
(channel_error). HIPAA accounts: hipaa_channel_not_covered
(permission). Connecting your own WhatsApp number (POST /v0/senders): whatsapp_token_malformed, whatsapp_token_rejected,
whatsapp_app_secret_malformed and whatsapp_number_not_in_account
(invalid_request), and whatsapp_number_taken,
whatsapp_account_taken and whatsapp_number_managed (permission).
Simulated conversations (permission): test_mode_only,
not_simulated, simulated_contact and simulated_conversation_limit.
For a message that failed after it was queued (message.failed):
sandbox_contact_left (permission), window_closed and
queued_too_long (outside_window), and
channel_timeout_unknown_outcome (channel_error: the channel never
confirmed the send, so it may or may not have arrived). iMessage
(unsupported_content): group_unsupported (a reaction, typing or a
read receipt in a group conversation; param kind) and
not_on_sms (an edit or unsend of a message that went by SMS or RCS;
param kind). SMS (unsupported_content): no_mms (media to a
number without MMS; param media).
Edits and unsends (unsupported_content): edit_window_closed and
unsend_window_closed (the message is older than the channel allows
for the change) and first_message_not_editable (iMessage: the message
that started the conversation). From typing and
read receipts, unknown_outcome (channel_error: the call timed out after
it was sent).
request_id string Flow's ID for this request. Quote it when asking for help.
429 application/json
Too many requests (rate_limited): either this key's request limit (see the
RateLimit-* headers) or the sender's sending rate (pacing) was exceeded;
retry after Retry-After seconds (also error.retry_after) with the same
Idempotency-Key. Or the sender has used its budget for starting
conversations (new_contact_limit), or the sender is throttled after abuse
signals (sender_throttled); wait retry_after seconds.
error ErrorBody required The details of an error.
Show child attributes
type enum<string> required What went wrong. A closed list: new types arrive only with a new API version.
Each type has a page at https://api.flow.engineer/docs/errors/<type>, given in
the error's doc_url; the error's hint says what to change for the case.
invalid_request(400, or 413 for a request body over the size limit): the request is malformed or a parameter is invalid. Fix the parameter named inparam; thehintsays what it must look like. Every endpoint answers it for aFlow-Versionheader that is not a version this API serves.authentication(401): the API key is missing, malformed, unknown, revoked or expired. AnAuthorization: Bearerheader with nothing after it usually means the environment variable holding the key was empty when the header was built (for an MCP client, when the server was added): thehintsays so. SendAuthorization: Bearer fk_test_...orfk_live_...with a current key; with no key at all, get a test key withPOST /v0/sandbox/keys. A sandbox key past itsexpires_athaschannel_codesandbox_key_expired: sign in to claim the app, or get a new key.permission(403): the key may not do this, for example a test key using a live sender, a sandbox contact who joined another app, a new conversation from an iMessage line that may only reply, or a send past the app's sandbox allowance (channel_codesandbox_allowance_used,sandbox_contact_limitorsandbox_channel_not_included). Use the key of the right mode, have the contact send your join message, wait for the contact to message the line first, or sign in through the device flow to lift the allowance. On SMS:contact_opted_out(the contact texted STOP; nothing more goes to them until they text START),consent_required(a start to someone who never texted your numbers: record their consent withconsenton the start). In a HIPAA account:hipaa_channel_not_covered(the channel has no business associate agreement; use SMS). Simulated conversations:simulated_contact(a real sender was pointed at a simulated contact; reply in the contact's simulated conversation),not_simulated(a simulated sender or test endpoint was pointed at a real contact),test_mode_only,simulated_conversation_limit. Inmessage.failed:sandbox_contact_left(the contact joined another app on the sandbox sender while the message was queued; have them send your join message again).not_found(404): no such object for this app and mode. Check the ID's prefix and that it was made with a key of the same mode (test and live data are separate).idempotency_conflict(409): the idempotency key was used for a different request (channel_codebody_mismatch: use a new key), or that request is still running (in_progress: waitretry_afterseconds and repeat it with the same key), or it already created a secret that is shown only once (secret_not_kept: creating a webhook endpoint, rotating its secret, connecting a WhatsApp number; a secret that was lost must be rotated again, and a WhatsApp number connected again to see its verify token).outside_window(409, or inmessage.failed): the channel will not deliver outside its conversation window. On WhatsApp the 24-hour window is closed: send a template (POST /v0/conversations/{conversation_id}/template, orPOST /v0/messageswithkindtemplateto start), or wait for the contact to write. On iMessage the contact has not messaged the line (or opted in) yet, or not recently, so the failure arrives as amessage.failedevent: wait for the contact to write, then reply in that conversation. Typing and read receipts answer409 outside_windowdirectly when the channel's window for them is closed (iMessage typing works only within 5 minutes of the contact's last message; WhatsApp read receipts go at any time); ignore it and send your reply. On SMS,channel_codequiet_hours: the contact has not texted in the last hour and it is inside the sender's quiet hours in their time zone; retry afterretry_afterseconds, or wait for them to text. Inmessage.failed:window_closed(WhatsApp's 24-hour window closed while the message was queued; send a template) andqueued_too_long(the message waited in the queue longer than its sender allows, for example while the sender was paused; send it again if it still applies).unsupported_content(422): the conversation's channel does not take this kind, field, block or value, and nothing was sent; Flow never converts a message.paramnames what was refused:kind(the endpoint's kind is not on this channel, such as a carousel on Telegram), a field or block (reply_toon SMS,telegramon WhatsApp,telegram.is_big), or the field whose value is not taken (emojioutside Telegram's reaction emoji,buttonsfor more than 3 WhatsApp buttons).channelis the conversation's channel andhintsays what to send instead over plain HTTP; each endpoint's page lists its channels. In an iMessage group conversation, a reaction, typing or a read receipt answers it withparamkindandchannel_codegroup_unsupported: leave it out. Media to an SMS number without MMS answers it withparammediaandchannel_codeno_mms. An edit or unsend of an iMessage message that went by SMS or RCS (itsnetwork) answers it withchannel_codenot_on_sms: send a new message instead. An edit or unsend past the channel's time limit answers it withchannel_codeedit_window_closedorunsend_window_closed(iMessage: edits within 15 minutes of sending, unsends within 2 minutes; Telegram: unsends within 48 hours), and an iMessage edit of the message that started the conversation withfirst_message_not_editable;messagestates the limit and how long ago the message was sent. Send a new message instead.POST /v0/filesanswers it for a type Flow does not take (programs, web pages, unknown types). A send byfile_idof a document a contact sent before Flow malware-scanned documents answers it too: Flow sends only documents it scanned.new_contact_limit(429): the sender has used its budget for starting conversations. Waitretry_afterseconds; replies into existing conversations still go.sender_throttled(429): abuse signals tripped (the same text to many new contacts, many starts with no reply, blocks), so the sender may not start conversations untilretry_after; replies into existing conversations still go. Personalise first messages and start only conversations people expect.file_blocked(422): the file failed the malware scan and was not stored. Send a different file. (When the scan cannot run at all, the upload answers a retryable500 api_errorwithRetry-Afterinstead.)rate_limited(429): either too many requests for this key (the per-key request limit, reported in theRateLimit-*headers) or sends faster than the sender's sending rate (pacing). WaitRetry-After(retry_after) seconds and retry with the sameIdempotency-Key.channel_error(502, or inmessage.failed): the channel refused or failed the message, or timed out;channel_codecarries its own code. Readmessage, change what the channel objected to, and send again. From typing and read receipts, which call the channel at once, it is safe to ignore. SMS codes are Flow's, the same whichever carrier network carries the message:carrier_filtered(a carrier's spam filter blocked it: make it read like a person's message, no shortened links),unreachable,invalid_number,landline,registration_required(the number's 10DLC or toll-free registration is not approved yet),carrier_error, andprovider_not_configured(the SMS sender is not fully set up on Flow's side, so nothing was sent; contact the Flow team). Flow's own codes on every channel:channel_timeout_unknown_outcome(inmessage.failed: the channel did not answer after the message was sent, and the resends its policy allows did not settle it, so the message may have arrived or not; check with the contact before sending it again) andunknown_outcome(from typing and read receipts: the call timed out after it was sent).not_implemented(501): this endpoint or channel is not live yet during the beta. Use a channel that is live (Telegram, iMessage), or check the changelog.api_error(500, 503): something went wrong on Flow's side. Retry with the same idempotency key afterretry_afterseconds, and quoterequest_idif it persists.
message string required A sentence for a developer saying what went wrong. Do not parse it; switch on type.
hint string required One sentence saying what to change to make the request succeed, specific
to this case, for example "Send a template instead: POST
/v0/conversations/{conversation_id}/template." For unsupported_content
it says what to send instead over plain HTTP. Written for developers and
coding agents alike. Do not parse it; it may be reworded at any time.
doc_url string<uri> required The documentation page for this error's type, https://api.flow.engineer/docs/errors/<type>. It says what the error means, why it happens and how to fix it, with code.
param string The request parameter the error is about, as a dotted path (kind, telegram.is_big, buttons, cards[1].header.type, limit).
retry_after integer Seconds to wait before retrying, for errors that clear by themselves.
channel enum<string> The conversation's channel, on errors about what it takes (unsupported_content) and its windows (outside_window).
conversation string A conversation ID, conv_ and a ULID.
sender string A sender ID, snd_ and a ULID.
channel_code string For channel_error, the channel's own error code, as given by the
channel. For some other errors, Flow's own code naming the case, for
example sandbox_allowance_used (permission: the sandbox allowance is
used up), sandbox_contact_limit (permission: the allowance has no
room for another contact), sandbox_channel_not_included (permission:
the allowance does not cover this sandbox channel), sign_in_required
(permission: an app made without an account cannot do this) or
sandbox_key_expired (authentication: a key from POST /v0/sandbox/keys passed its expires_at; permission, for a send
that carries no key, such as a reply in a webhook answer, from such an
app). For idempotency_conflict, always one of body_mismatch (the
key was used for a different request), in_progress (the first
request is still running; retry_after says when to repeat it) or
secret_not_kept (its answer carried a secret shown only once).
SMS: contact_opted_out and consent_required (permission),
quiet_hours (outside_window, with retry_after), and
carrier_filtered, unreachable, invalid_number, landline,
registration_required, carrier_error and provider_not_configured
(channel_error). HIPAA accounts: hipaa_channel_not_covered
(permission). Connecting your own WhatsApp number (POST /v0/senders): whatsapp_token_malformed, whatsapp_token_rejected,
whatsapp_app_secret_malformed and whatsapp_number_not_in_account
(invalid_request), and whatsapp_number_taken,
whatsapp_account_taken and whatsapp_number_managed (permission).
Simulated conversations (permission): test_mode_only,
not_simulated, simulated_contact and simulated_conversation_limit.
For a message that failed after it was queued (message.failed):
sandbox_contact_left (permission), window_closed and
queued_too_long (outside_window), and
channel_timeout_unknown_outcome (channel_error: the channel never
confirmed the send, so it may or may not have arrived). iMessage
(unsupported_content): group_unsupported (a reaction, typing or a
read receipt in a group conversation; param kind) and
not_on_sms (an edit or unsend of a message that went by SMS or RCS;
param kind). SMS (unsupported_content): no_mms (media to a
number without MMS; param media).
Edits and unsends (unsupported_content): edit_window_closed and
unsend_window_closed (the message is older than the channel allows
for the change) and first_message_not_editable (iMessage: the message
that started the conversation). From typing and
read receipts, unknown_outcome (channel_error: the call timed out after
it was sent).
request_id string Flow's ID for this request. Quote it when asking for help.
501 application/json
channel is imessage or sms: the Flow team connects these senders
(not_implemented, with any key). The hint says how to get one and
what to build with meanwhile.
error ErrorBody required The details of an error.
Show child attributes
type enum<string> required What went wrong. A closed list: new types arrive only with a new API version.
Each type has a page at https://api.flow.engineer/docs/errors/<type>, given in
the error's doc_url; the error's hint says what to change for the case.
invalid_request(400, or 413 for a request body over the size limit): the request is malformed or a parameter is invalid. Fix the parameter named inparam; thehintsays what it must look like. Every endpoint answers it for aFlow-Versionheader that is not a version this API serves.authentication(401): the API key is missing, malformed, unknown, revoked or expired. AnAuthorization: Bearerheader with nothing after it usually means the environment variable holding the key was empty when the header was built (for an MCP client, when the server was added): thehintsays so. SendAuthorization: Bearer fk_test_...orfk_live_...with a current key; with no key at all, get a test key withPOST /v0/sandbox/keys. A sandbox key past itsexpires_athaschannel_codesandbox_key_expired: sign in to claim the app, or get a new key.permission(403): the key may not do this, for example a test key using a live sender, a sandbox contact who joined another app, a new conversation from an iMessage line that may only reply, or a send past the app's sandbox allowance (channel_codesandbox_allowance_used,sandbox_contact_limitorsandbox_channel_not_included). Use the key of the right mode, have the contact send your join message, wait for the contact to message the line first, or sign in through the device flow to lift the allowance. On SMS:contact_opted_out(the contact texted STOP; nothing more goes to them until they text START),consent_required(a start to someone who never texted your numbers: record their consent withconsenton the start). In a HIPAA account:hipaa_channel_not_covered(the channel has no business associate agreement; use SMS). Simulated conversations:simulated_contact(a real sender was pointed at a simulated contact; reply in the contact's simulated conversation),not_simulated(a simulated sender or test endpoint was pointed at a real contact),test_mode_only,simulated_conversation_limit. Inmessage.failed:sandbox_contact_left(the contact joined another app on the sandbox sender while the message was queued; have them send your join message again).not_found(404): no such object for this app and mode. Check the ID's prefix and that it was made with a key of the same mode (test and live data are separate).idempotency_conflict(409): the idempotency key was used for a different request (channel_codebody_mismatch: use a new key), or that request is still running (in_progress: waitretry_afterseconds and repeat it with the same key), or it already created a secret that is shown only once (secret_not_kept: creating a webhook endpoint, rotating its secret, connecting a WhatsApp number; a secret that was lost must be rotated again, and a WhatsApp number connected again to see its verify token).outside_window(409, or inmessage.failed): the channel will not deliver outside its conversation window. On WhatsApp the 24-hour window is closed: send a template (POST /v0/conversations/{conversation_id}/template, orPOST /v0/messageswithkindtemplateto start), or wait for the contact to write. On iMessage the contact has not messaged the line (or opted in) yet, or not recently, so the failure arrives as amessage.failedevent: wait for the contact to write, then reply in that conversation. Typing and read receipts answer409 outside_windowdirectly when the channel's window for them is closed (iMessage typing works only within 5 minutes of the contact's last message; WhatsApp read receipts go at any time); ignore it and send your reply. On SMS,channel_codequiet_hours: the contact has not texted in the last hour and it is inside the sender's quiet hours in their time zone; retry afterretry_afterseconds, or wait for them to text. Inmessage.failed:window_closed(WhatsApp's 24-hour window closed while the message was queued; send a template) andqueued_too_long(the message waited in the queue longer than its sender allows, for example while the sender was paused; send it again if it still applies).unsupported_content(422): the conversation's channel does not take this kind, field, block or value, and nothing was sent; Flow never converts a message.paramnames what was refused:kind(the endpoint's kind is not on this channel, such as a carousel on Telegram), a field or block (reply_toon SMS,telegramon WhatsApp,telegram.is_big), or the field whose value is not taken (emojioutside Telegram's reaction emoji,buttonsfor more than 3 WhatsApp buttons).channelis the conversation's channel andhintsays what to send instead over plain HTTP; each endpoint's page lists its channels. In an iMessage group conversation, a reaction, typing or a read receipt answers it withparamkindandchannel_codegroup_unsupported: leave it out. Media to an SMS number without MMS answers it withparammediaandchannel_codeno_mms. An edit or unsend of an iMessage message that went by SMS or RCS (itsnetwork) answers it withchannel_codenot_on_sms: send a new message instead. An edit or unsend past the channel's time limit answers it withchannel_codeedit_window_closedorunsend_window_closed(iMessage: edits within 15 minutes of sending, unsends within 2 minutes; Telegram: unsends within 48 hours), and an iMessage edit of the message that started the conversation withfirst_message_not_editable;messagestates the limit and how long ago the message was sent. Send a new message instead.POST /v0/filesanswers it for a type Flow does not take (programs, web pages, unknown types). A send byfile_idof a document a contact sent before Flow malware-scanned documents answers it too: Flow sends only documents it scanned.new_contact_limit(429): the sender has used its budget for starting conversations. Waitretry_afterseconds; replies into existing conversations still go.sender_throttled(429): abuse signals tripped (the same text to many new contacts, many starts with no reply, blocks), so the sender may not start conversations untilretry_after; replies into existing conversations still go. Personalise first messages and start only conversations people expect.file_blocked(422): the file failed the malware scan and was not stored. Send a different file. (When the scan cannot run at all, the upload answers a retryable500 api_errorwithRetry-Afterinstead.)rate_limited(429): either too many requests for this key (the per-key request limit, reported in theRateLimit-*headers) or sends faster than the sender's sending rate (pacing). WaitRetry-After(retry_after) seconds and retry with the sameIdempotency-Key.channel_error(502, or inmessage.failed): the channel refused or failed the message, or timed out;channel_codecarries its own code. Readmessage, change what the channel objected to, and send again. From typing and read receipts, which call the channel at once, it is safe to ignore. SMS codes are Flow's, the same whichever carrier network carries the message:carrier_filtered(a carrier's spam filter blocked it: make it read like a person's message, no shortened links),unreachable,invalid_number,landline,registration_required(the number's 10DLC or toll-free registration is not approved yet),carrier_error, andprovider_not_configured(the SMS sender is not fully set up on Flow's side, so nothing was sent; contact the Flow team). Flow's own codes on every channel:channel_timeout_unknown_outcome(inmessage.failed: the channel did not answer after the message was sent, and the resends its policy allows did not settle it, so the message may have arrived or not; check with the contact before sending it again) andunknown_outcome(from typing and read receipts: the call timed out after it was sent).not_implemented(501): this endpoint or channel is not live yet during the beta. Use a channel that is live (Telegram, iMessage), or check the changelog.api_error(500, 503): something went wrong on Flow's side. Retry with the same idempotency key afterretry_afterseconds, and quoterequest_idif it persists.
message string required A sentence for a developer saying what went wrong. Do not parse it; switch on type.
hint string required One sentence saying what to change to make the request succeed, specific
to this case, for example "Send a template instead: POST
/v0/conversations/{conversation_id}/template." For unsupported_content
it says what to send instead over plain HTTP. Written for developers and
coding agents alike. Do not parse it; it may be reworded at any time.
doc_url string<uri> required The documentation page for this error's type, https://api.flow.engineer/docs/errors/<type>. It says what the error means, why it happens and how to fix it, with code.
param string The request parameter the error is about, as a dotted path (kind, telegram.is_big, buttons, cards[1].header.type, limit).
retry_after integer Seconds to wait before retrying, for errors that clear by themselves.
channel enum<string> The conversation's channel, on errors about what it takes (unsupported_content) and its windows (outside_window).
conversation string A conversation ID, conv_ and a ULID.
sender string A sender ID, snd_ and a ULID.
channel_code string For channel_error, the channel's own error code, as given by the
channel. For some other errors, Flow's own code naming the case, for
example sandbox_allowance_used (permission: the sandbox allowance is
used up), sandbox_contact_limit (permission: the allowance has no
room for another contact), sandbox_channel_not_included (permission:
the allowance does not cover this sandbox channel), sign_in_required
(permission: an app made without an account cannot do this) or
sandbox_key_expired (authentication: a key from POST /v0/sandbox/keys passed its expires_at; permission, for a send
that carries no key, such as a reply in a webhook answer, from such an
app). For idempotency_conflict, always one of body_mismatch (the
key was used for a different request), in_progress (the first
request is still running; retry_after says when to repeat it) or
secret_not_kept (its answer carried a secret shown only once).
SMS: contact_opted_out and consent_required (permission),
quiet_hours (outside_window, with retry_after), and
carrier_filtered, unreachable, invalid_number, landline,
registration_required, carrier_error and provider_not_configured
(channel_error). HIPAA accounts: hipaa_channel_not_covered
(permission). Connecting your own WhatsApp number (POST /v0/senders): whatsapp_token_malformed, whatsapp_token_rejected,
whatsapp_app_secret_malformed and whatsapp_number_not_in_account
(invalid_request), and whatsapp_number_taken,
whatsapp_account_taken and whatsapp_number_managed (permission).
Simulated conversations (permission): test_mode_only,
not_simulated, simulated_contact and simulated_conversation_limit.
For a message that failed after it was queued (message.failed):
sandbox_contact_left (permission), window_closed and
queued_too_long (outside_window), and
channel_timeout_unknown_outcome (channel_error: the channel never
confirmed the send, so it may or may not have arrived). iMessage
(unsupported_content): group_unsupported (a reaction, typing or a
read receipt in a group conversation; param kind) and
not_on_sms (an edit or unsend of a message that went by SMS or RCS;
param kind). SMS (unsupported_content): no_mms (media to a
number without MMS; param media).
Edits and unsends (unsupported_content): edit_window_closed and
unsend_window_closed (the message is older than the channel allows
for the change) and first_message_not_editable (iMessage: the message
that started the conversation). From typing and
read receipts, unknown_outcome (channel_error: the call timed out after
it was sent).
request_id string Flow's ID for this request. Quote it when asking for help.
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 inparam; thehintsays what it must look like. Every endpoint answers it for aFlow-Versionheader that is not a version this API serves.authentication(401): the API key is missing, malformed, unknown, revoked or expired. AnAuthorization: Bearerheader with nothing after it usually means the environment variable holding the key was empty when the header was built (for an MCP client, when the server was added): thehintsays so. SendAuthorization: Bearer fk_test_...orfk_live_...with a current key; with no key at all, get a test key withPOST /v0/sandbox/keys. A sandbox key past itsexpires_athaschannel_codesandbox_key_expired: sign in to claim the app, or get a new key.permission(403): the key may not do this, for example a test key using a live sender, a sandbox contact who joined another app, a new conversation from an iMessage line that may only reply, or a send past the app's sandbox allowance (channel_codesandbox_allowance_used,sandbox_contact_limitorsandbox_channel_not_included). Use the key of the right mode, have the contact send your join message, wait for the contact to message the line first, or sign in through the device flow to lift the allowance. On SMS:contact_opted_out(the contact texted STOP; nothing more goes to them until they text START),consent_required(a start to someone who never texted your numbers: record their consent withconsenton the start). In a HIPAA account:hipaa_channel_not_covered(the channel has no business associate agreement; use SMS). Simulated conversations:simulated_contact(a real sender was pointed at a simulated contact; reply in the contact's simulated conversation),not_simulated(a simulated sender or test endpoint was pointed at a real contact),test_mode_only,simulated_conversation_limit. Inmessage.failed:sandbox_contact_left(the contact joined another app on the sandbox sender while the message was queued; have them send your join message again).not_found(404): no such object for this app and mode. Check the ID's prefix and that it was made with a key of the same mode (test and live data are separate).idempotency_conflict(409): the idempotency key was used for a different request (channel_codebody_mismatch: use a new key), or that request is still running (in_progress: waitretry_afterseconds and repeat it with the same key), or it already created a secret that is shown only once (secret_not_kept: creating a webhook endpoint, rotating its secret, connecting a WhatsApp number; a secret that was lost must be rotated again, and a WhatsApp number connected again to see its verify token).outside_window(409, or inmessage.failed): the channel will not deliver outside its conversation window. On WhatsApp the 24-hour window is closed: send a template (POST /v0/conversations/{conversation_id}/template, orPOST /v0/messageswithkindtemplateto start), or wait for the contact to write. On iMessage the contact has not messaged the line (or opted in) yet, or not recently, so the failure arrives as amessage.failedevent: wait for the contact to write, then reply in that conversation. Typing and read receipts answer409 outside_windowdirectly when the channel's window for them is closed (iMessage typing works only within 5 minutes of the contact's last message; WhatsApp read receipts go at any time); ignore it and send your reply. On SMS,channel_codequiet_hours: the contact has not texted in the last hour and it is inside the sender's quiet hours in their time zone; retry afterretry_afterseconds, or wait for them to text. Inmessage.failed:window_closed(WhatsApp's 24-hour window closed while the message was queued; send a template) andqueued_too_long(the message waited in the queue longer than its sender allows, for example while the sender was paused; send it again if it still applies).unsupported_content(422): the conversation's channel does not take this kind, field, block or value, and nothing was sent; Flow never converts a message.paramnames what was refused:kind(the endpoint's kind is not on this channel, such as a carousel on Telegram), a field or block (reply_toon SMS,telegramon WhatsApp,telegram.is_big), or the field whose value is not taken (emojioutside Telegram's reaction emoji,buttonsfor more than 3 WhatsApp buttons).channelis the conversation's channel andhintsays what to send instead over plain HTTP; each endpoint's page lists its channels. In an iMessage group conversation, a reaction, typing or a read receipt answers it withparamkindandchannel_codegroup_unsupported: leave it out. Media to an SMS number without MMS answers it withparammediaandchannel_codeno_mms. An edit or unsend of an iMessage message that went by SMS or RCS (itsnetwork) answers it withchannel_codenot_on_sms: send a new message instead. An edit or unsend past the channel's time limit answers it withchannel_codeedit_window_closedorunsend_window_closed(iMessage: edits within 15 minutes of sending, unsends within 2 minutes; Telegram: unsends within 48 hours), and an iMessage edit of the message that started the conversation withfirst_message_not_editable;messagestates the limit and how long ago the message was sent. Send a new message instead.POST /v0/filesanswers it for a type Flow does not take (programs, web pages, unknown types). A send byfile_idof a document a contact sent before Flow malware-scanned documents answers it too: Flow sends only documents it scanned.new_contact_limit(429): the sender has used its budget for starting conversations. Waitretry_afterseconds; replies into existing conversations still go.sender_throttled(429): abuse signals tripped (the same text to many new contacts, many starts with no reply, blocks), so the sender may not start conversations untilretry_after; replies into existing conversations still go. Personalise first messages and start only conversations people expect.file_blocked(422): the file failed the malware scan and was not stored. Send a different file. (When the scan cannot run at all, the upload answers a retryable500 api_errorwithRetry-Afterinstead.)rate_limited(429): either too many requests for this key (the per-key request limit, reported in theRateLimit-*headers) or sends faster than the sender's sending rate (pacing). WaitRetry-After(retry_after) seconds and retry with the sameIdempotency-Key.channel_error(502, or inmessage.failed): the channel refused or failed the message, or timed out;channel_codecarries its own code. Readmessage, change what the channel objected to, and send again. From typing and read receipts, which call the channel at once, it is safe to ignore. SMS codes are Flow's, the same whichever carrier network carries the message:carrier_filtered(a carrier's spam filter blocked it: make it read like a person's message, no shortened links),unreachable,invalid_number,landline,registration_required(the number's 10DLC or toll-free registration is not approved yet),carrier_error, andprovider_not_configured(the SMS sender is not fully set up on Flow's side, so nothing was sent; contact the Flow team). Flow's own codes on every channel:channel_timeout_unknown_outcome(inmessage.failed: the channel did not answer after the message was sent, and the resends its policy allows did not settle it, so the message may have arrived or not; check with the contact before sending it again) andunknown_outcome(from typing and read receipts: the call timed out after it was sent).not_implemented(501): this endpoint or channel is not live yet during the beta. Use a channel that is live (Telegram, iMessage), or check the changelog.api_error(500, 503): something went wrong on Flow's side. Retry with the same idempotency key afterretry_afterseconds, and quoterequest_idif it persists.
message string required A sentence for a developer saying what went wrong. Do not parse it; switch on type.
hint string required One sentence saying what to change to make the request succeed, specific
to this case, for example "Send a template instead: POST
/v0/conversations/{conversation_id}/template." For unsupported_content
it says what to send instead over plain HTTP. Written for developers and
coding agents alike. Do not parse it; it may be reworded at any time.
doc_url string<uri> required The documentation page for this error's type, https://api.flow.engineer/docs/errors/<type>. It says what the error means, why it happens and how to fix it, with code.
param string The request parameter the error is about, as a dotted path (kind, telegram.is_big, buttons, cards[1].header.type, limit).
retry_after integer Seconds to wait before retrying, for errors that clear by themselves.
channel enum<string> The conversation's channel, on errors about what it takes (unsupported_content) and its windows (outside_window).
conversation string A conversation ID, conv_ and a ULID.
sender string A sender ID, snd_ and a ULID.
channel_code string For channel_error, the channel's own error code, as given by the
channel. For some other errors, Flow's own code naming the case, for
example sandbox_allowance_used (permission: the sandbox allowance is
used up), sandbox_contact_limit (permission: the allowance has no
room for another contact), sandbox_channel_not_included (permission:
the allowance does not cover this sandbox channel), sign_in_required
(permission: an app made without an account cannot do this) or
sandbox_key_expired (authentication: a key from POST /v0/sandbox/keys passed its expires_at; permission, for a send
that carries no key, such as a reply in a webhook answer, from such an
app). For idempotency_conflict, always one of body_mismatch (the
key was used for a different request), in_progress (the first
request is still running; retry_after says when to repeat it) or
secret_not_kept (its answer carried a secret shown only once).
SMS: contact_opted_out and consent_required (permission),
quiet_hours (outside_window, with retry_after), and
carrier_filtered, unreachable, invalid_number, landline,
registration_required, carrier_error and provider_not_configured
(channel_error). HIPAA accounts: hipaa_channel_not_covered
(permission). Connecting your own WhatsApp number (POST /v0/senders): whatsapp_token_malformed, whatsapp_token_rejected,
whatsapp_app_secret_malformed and whatsapp_number_not_in_account
(invalid_request), and whatsapp_number_taken,
whatsapp_account_taken and whatsapp_number_managed (permission).
Simulated conversations (permission): test_mode_only,
not_simulated, simulated_contact and simulated_conversation_limit.
For a message that failed after it was queued (message.failed):
sandbox_contact_left (permission), window_closed and
queued_too_long (outside_window), and
channel_timeout_unknown_outcome (channel_error: the channel never
confirmed the send, so it may or may not have arrived). iMessage
(unsupported_content): group_unsupported (a reaction, typing or a
read receipt in a group conversation; param kind) and
not_on_sms (an edit or unsend of a message that went by SMS or RCS;
param kind). SMS (unsupported_content): no_mms (media to a
number without MMS; param media).
Edits and unsends (unsupported_content): edit_window_closed and
unsend_window_closed (the message is older than the channel allows
for the change) and first_message_not_editable (iMessage: the message
that started the conversation). From typing and
read receipts, unknown_outcome (channel_error: the call timed out after
it was sent).
request_id string Flow's ID for this request. Quote it when asking for help.
default application/json
An error. error.type says which (see ErrorType).
error ErrorBody required The details of an error.
Show child attributes
type enum<string> required What went wrong. A closed list: new types arrive only with a new API version.
Each type has a page at https://api.flow.engineer/docs/errors/<type>, given in
the error's doc_url; the error's hint says what to change for the case.
invalid_request(400, or 413 for a request body over the size limit): the request is malformed or a parameter is invalid. Fix the parameter named inparam; thehintsays what it must look like. Every endpoint answers it for aFlow-Versionheader that is not a version this API serves.authentication(401): the API key is missing, malformed, unknown, revoked or expired. AnAuthorization: Bearerheader with nothing after it usually means the environment variable holding the key was empty when the header was built (for an MCP client, when the server was added): thehintsays so. SendAuthorization: Bearer fk_test_...orfk_live_...with a current key; with no key at all, get a test key withPOST /v0/sandbox/keys. A sandbox key past itsexpires_athaschannel_codesandbox_key_expired: sign in to claim the app, or get a new key.permission(403): the key may not do this, for example a test key using a live sender, a sandbox contact who joined another app, a new conversation from an iMessage line that may only reply, or a send past the app's sandbox allowance (channel_codesandbox_allowance_used,sandbox_contact_limitorsandbox_channel_not_included). Use the key of the right mode, have the contact send your join message, wait for the contact to message the line first, or sign in through the device flow to lift the allowance. On SMS:contact_opted_out(the contact texted STOP; nothing more goes to them until they text START),consent_required(a start to someone who never texted your numbers: record their consent withconsenton the start). In a HIPAA account:hipaa_channel_not_covered(the channel has no business associate agreement; use SMS). Simulated conversations:simulated_contact(a real sender was pointed at a simulated contact; reply in the contact's simulated conversation),not_simulated(a simulated sender or test endpoint was pointed at a real contact),test_mode_only,simulated_conversation_limit. Inmessage.failed:sandbox_contact_left(the contact joined another app on the sandbox sender while the message was queued; have them send your join message again).not_found(404): no such object for this app and mode. Check the ID's prefix and that it was made with a key of the same mode (test and live data are separate).idempotency_conflict(409): the idempotency key was used for a different request (channel_codebody_mismatch: use a new key), or that request is still running (in_progress: waitretry_afterseconds and repeat it with the same key), or it already created a secret that is shown only once (secret_not_kept: creating a webhook endpoint, rotating its secret, connecting a WhatsApp number; a secret that was lost must be rotated again, and a WhatsApp number connected again to see its verify token).outside_window(409, or inmessage.failed): the channel will not deliver outside its conversation window. On WhatsApp the 24-hour window is closed: send a template (POST /v0/conversations/{conversation_id}/template, orPOST /v0/messageswithkindtemplateto start), or wait for the contact to write. On iMessage the contact has not messaged the line (or opted in) yet, or not recently, so the failure arrives as amessage.failedevent: wait for the contact to write, then reply in that conversation. Typing and read receipts answer409 outside_windowdirectly when the channel's window for them is closed (iMessage typing works only within 5 minutes of the contact's last message; WhatsApp read receipts go at any time); ignore it and send your reply. On SMS,channel_codequiet_hours: the contact has not texted in the last hour and it is inside the sender's quiet hours in their time zone; retry afterretry_afterseconds, or wait for them to text. Inmessage.failed:window_closed(WhatsApp's 24-hour window closed while the message was queued; send a template) andqueued_too_long(the message waited in the queue longer than its sender allows, for example while the sender was paused; send it again if it still applies).unsupported_content(422): the conversation's channel does not take this kind, field, block or value, and nothing was sent; Flow never converts a message.paramnames what was refused:kind(the endpoint's kind is not on this channel, such as a carousel on Telegram), a field or block (reply_toon SMS,telegramon WhatsApp,telegram.is_big), or the field whose value is not taken (emojioutside Telegram's reaction emoji,buttonsfor more than 3 WhatsApp buttons).channelis the conversation's channel andhintsays what to send instead over plain HTTP; each endpoint's page lists its channels. In an iMessage group conversation, a reaction, typing or a read receipt answers it withparamkindandchannel_codegroup_unsupported: leave it out. Media to an SMS number without MMS answers it withparammediaandchannel_codeno_mms. An edit or unsend of an iMessage message that went by SMS or RCS (itsnetwork) answers it withchannel_codenot_on_sms: send a new message instead. An edit or unsend past the channel's time limit answers it withchannel_codeedit_window_closedorunsend_window_closed(iMessage: edits within 15 minutes of sending, unsends within 2 minutes; Telegram: unsends within 48 hours), and an iMessage edit of the message that started the conversation withfirst_message_not_editable;messagestates the limit and how long ago the message was sent. Send a new message instead.POST /v0/filesanswers it for a type Flow does not take (programs, web pages, unknown types). A send byfile_idof a document a contact sent before Flow malware-scanned documents answers it too: Flow sends only documents it scanned.new_contact_limit(429): the sender has used its budget for starting conversations. Waitretry_afterseconds; replies into existing conversations still go.sender_throttled(429): abuse signals tripped (the same text to many new contacts, many starts with no reply, blocks), so the sender may not start conversations untilretry_after; replies into existing conversations still go. Personalise first messages and start only conversations people expect.file_blocked(422): the file failed the malware scan and was not stored. Send a different file. (When the scan cannot run at all, the upload answers a retryable500 api_errorwithRetry-Afterinstead.)rate_limited(429): either too many requests for this key (the per-key request limit, reported in theRateLimit-*headers) or sends faster than the sender's sending rate (pacing). WaitRetry-After(retry_after) seconds and retry with the sameIdempotency-Key.channel_error(502, or inmessage.failed): the channel refused or failed the message, or timed out;channel_codecarries its own code. Readmessage, change what the channel objected to, and send again. From typing and read receipts, which call the channel at once, it is safe to ignore. SMS codes are Flow's, the same whichever carrier network carries the message:carrier_filtered(a carrier's spam filter blocked it: make it read like a person's message, no shortened links),unreachable,invalid_number,landline,registration_required(the number's 10DLC or toll-free registration is not approved yet),carrier_error, andprovider_not_configured(the SMS sender is not fully set up on Flow's side, so nothing was sent; contact the Flow team). Flow's own codes on every channel:channel_timeout_unknown_outcome(inmessage.failed: the channel did not answer after the message was sent, and the resends its policy allows did not settle it, so the message may have arrived or not; check with the contact before sending it again) andunknown_outcome(from typing and read receipts: the call timed out after it was sent).not_implemented(501): this endpoint or channel is not live yet during the beta. Use a channel that is live (Telegram, iMessage), or check the changelog.api_error(500, 503): something went wrong on Flow's side. Retry with the same idempotency key afterretry_afterseconds, and quoterequest_idif it persists.
message string required A sentence for a developer saying what went wrong. Do not parse it; switch on type.
hint string required One sentence saying what to change to make the request succeed, specific
to this case, for example "Send a template instead: POST
/v0/conversations/{conversation_id}/template." For unsupported_content
it says what to send instead over plain HTTP. Written for developers and
coding agents alike. Do not parse it; it may be reworded at any time.
doc_url string<uri> required The documentation page for this error's type, https://api.flow.engineer/docs/errors/<type>. It says what the error means, why it happens and how to fix it, with code.
param string The request parameter the error is about, as a dotted path (kind, telegram.is_big, buttons, cards[1].header.type, limit).
retry_after integer Seconds to wait before retrying, for errors that clear by themselves.
channel enum<string> The conversation's channel, on errors about what it takes (unsupported_content) and its windows (outside_window).
conversation string A conversation ID, conv_ and a ULID.
sender string A sender ID, snd_ and a ULID.
channel_code string For channel_error, the channel's own error code, as given by the
channel. For some other errors, Flow's own code naming the case, for
example sandbox_allowance_used (permission: the sandbox allowance is
used up), sandbox_contact_limit (permission: the allowance has no
room for another contact), sandbox_channel_not_included (permission:
the allowance does not cover this sandbox channel), sign_in_required
(permission: an app made without an account cannot do this) or
sandbox_key_expired (authentication: a key from POST /v0/sandbox/keys passed its expires_at; permission, for a send
that carries no key, such as a reply in a webhook answer, from such an
app). For idempotency_conflict, always one of body_mismatch (the
key was used for a different request), in_progress (the first
request is still running; retry_after says when to repeat it) or
secret_not_kept (its answer carried a secret shown only once).
SMS: contact_opted_out and consent_required (permission),
quiet_hours (outside_window, with retry_after), and
carrier_filtered, unreachable, invalid_number, landline,
registration_required, carrier_error and provider_not_configured
(channel_error). HIPAA accounts: hipaa_channel_not_covered
(permission). Connecting your own WhatsApp number (POST /v0/senders): whatsapp_token_malformed, whatsapp_token_rejected,
whatsapp_app_secret_malformed and whatsapp_number_not_in_account
(invalid_request), and whatsapp_number_taken,
whatsapp_account_taken and whatsapp_number_managed (permission).
Simulated conversations (permission): test_mode_only,
not_simulated, simulated_contact and simulated_conversation_limit.
For a message that failed after it was queued (message.failed):
sandbox_contact_left (permission), window_closed and
queued_too_long (outside_window), and
channel_timeout_unknown_outcome (channel_error: the channel never
confirmed the send, so it may or may not have arrived). iMessage
(unsupported_content): group_unsupported (a reaction, typing or a
read receipt in a group conversation; param kind) and
not_on_sms (an edit or unsend of a message that went by SMS or RCS;
param kind). SMS (unsupported_content): no_mms (media to a
number without MMS; param media).
Edits and unsends (unsupported_content): edit_window_closed and
unsend_window_closed (the message is older than the channel allows
for the change) and first_message_not_editable (iMessage: the message
that started the conversation). From typing and
read receipts, unknown_outcome (channel_error: the call timed out after
it was sent).
request_id string Flow's ID for this request. Quote it when asking for help.
Thanks, we read every comment.