Platform API errors
Every refusal on the messaging endpoints (/v1/messages, /v1/calls,
/v1/campaigns, /v1/senders, /v1/me, /v1/templates, /v1/agents,
/v1/lists, /v1/contacts, /v1/contact-fields, /v1/conversations,
/v1/segments, /v1/balance, /v1/compliance,
/v1/webhooks, /v1/events, /v1/media and /v1/recordings) is the same
envelope, and so is a /v1 path that does not exist (404 not_found). Only
the OpenAI-compatible endpoints, /v1/responses, /v1/chat/completions and
/v1/models, answer in the OpenAI format (Errors).
This page is about the messaging endpoints, and it is the one place that says
what each code means: the guides list only the codes you are most likely to
meet.
{ "error": { "type": "invalid_request_error", "code": "sender_not_cleared", "message": "This number isn't cleared to send texts yet.", "param": "from", "hint": "Register the number for business texting: in the dashboard open Phone numbers, pick the number, choose Request registration. Carriers usually take 1 to 3 weeks.", "docs_url": "https://docs.omniocto.com/platform-api/sms/#get-a-number-cleared-to-text", "request_id": "00000000-0000-4000-8000-000000000008", "sender_status": "receiving_only" } }- Branch on
code. It is a stable machine word. - Show
message. It is a sentence a person can read. paramnames what the error is about, in/v1names: a body field (to,template.id,audience.contacts.0.phone), a query parameter (status), a path segment (id) or a header (idempotency-key); ornull.hintsays what to do next anddocs_urlwhere to read more, when we know. Anunknown_fieldhint names the field you most likely meant at that place in the body (Did you mean `variables`?,Did you mean `agent`?insidecontent);unknown_parameterdoes the same for a query parameter, and aninvalid_numberhint gives the E.164 form (Did you mean +14155550100?).request_ididentifies the request. Quote it when you contact us.- Some codes carry details beside these, in snake_case. Every one is
declared on V1Error:
allowed,allowed_country,campaign_id,campaigns,candidates,cap,channel,channels,closes_at,contact,current_version,errors,field,fields,invalid_rows,issues,message_id,missing,part,problems,reason,received,remaining,resets_at,retry_after,sender_status,short_credits,skipped_by_reason,status,template_status,used,version. The tables below name the ones each code carries. - A validation failure (
400 invalid_request) also carriesissues, one per problem:[{ "path": ["name"], "code": "invalid_type", "message": "nameis required." }].messageis a sentence about the field; branch oncode.
The envelope’s fields, field by field: V1Error.
type groups codes by what you do about them. It follows the HTTP status:
| status | type | what to do |
|---|---|---|
| 400, 405, 413, 415, 422 | invalid_request_error | fix the request; retrying it unchanged fails again |
| 401 | invalid_request_error (code invalid_api_key) | check the key; the message says whether it was missing, sent without Bearer , or not valid |
| 402 | billing_error | add credits or a payment method |
| 403 | permission_error | use a key with the right role |
| 404 | not_found_error | check the id; another workspace’s ids answer 404 too |
| 409 | conflict_error | the resource’s state does not allow it, or the same key is still running |
| 429 | rate_limit_exceeded | wait Retry-After seconds, then retry |
| 500, 502, 503 | server_error | retry with the same Idempotency-Key |
A 500 is internal_error: something failed on our side. An unexpected
failure can also answer 500 with code: null; treat both the same way.
Retrying with the same Idempotency-Key is safe: a text is never sent twice
for one key, and the other keyed requests replay (see Idempotency).
A missing or bad key is 401 with code: "invalid_api_key", answered before
the request reaches the messaging routes; its type is
invalid_request_error, as OpenAI-compatible clients expect, and it carries
request_id like every other error. Its param is authorization, and its
message says whether the key was missing, sent without Bearer , or not
valid; it never repeats the key. Branch on code, not on type.
New codes may be added. Treat a code you do not know by its HTTP status and
type, and show its message. A refusal that carries no code of its own
answers the word for its status: unauthorized (401), payment_required
(402), forbidden (403), conflict (409), unprocessable (422) or
unavailable (503).
Codes by resource
Section titled “Codes by resource”Each table says what a code means and what to do about it. A code can appear in more than one table when more than one resource answers it.
Every request
Section titled “Every request”| status | code | means, and what to do |
|---|---|---|
| 400 | invalid_json | The body is not valid JSON. Send JSON. |
| 400 | invalid_request | A field or query parameter fails validation (param and issues say which), a parameter was sent twice, or an id is of the wrong kind: in a body field (a sg-… as audience.list), a path (/v1/campaigns/cl-…, param: "id") or a query parameter (?campaign=cl-…, param: "campaign"); message names the prefix it takes and hint the route that lists them. As from, another kind’s id is invalid_from. Fix the field. |
| 400 | unknown_field | The body has a field this route does not take (inside nested objects too). hint names the field you most likely meant. |
| 400 | unknown_parameter | The query string has a parameter this route does not take. hint names the closest one. |
| 400 | invalid_idempotency_key | The Idempotency-Key breaks the format. Use 1 to 255 printable ASCII characters. |
| 401 | invalid_api_key | The key is not usable, and param is authorization. The message names one of four causes: no key at all; the key sent in X-API-Key instead of Authorization; an Authorization header without Bearer (a bare key, or another scheme such as Basic); or a Bearer key that is mistyped, revoked or expired (these three answer alike). Send Authorization: Bearer <your key>. Its docs_url is Authentication. |
| 404 | not_found | No such path, or no such resource in this workspace: another workspace’s id answers the same. Check the id. |
| 405 | method_not_allowed | The path does not take this method. The Allow header lists the ones it does. |
| 413 | payload_too_large | The body is over the size limit. Send less, or a campaign audience as a list. |
| 415 | unsupported_media_type | The body was not sent as application/json. Set Content-Type. |
| 422 | idempotency_key_reuse | The same Idempotency-Key came with a different request. Use a new key for a new request. |
| 429 | rate_limited | Over a rate limit. Wait Retry-After seconds and retry with the same key. |
| 500 | internal_error | Something failed on our side. Retry with the same key. |
| 502 | upstream_error | A service we depend on failed. Retry with the same key. |
| status | code | means, and what to do |
|---|---|---|
| 400 | invalid_cursor | after is not a next_cursor of this list read with these filters. Cursors are opaque and belong to the list and filters that answered them; a cursor answered before 2026-10-17 is refused once. Start again without after. |
| 400 | invalid_request | limit is out of range, or a query parameter was sent twice. |
| 400 | invalid_request (messages, calls) | channel is missing, a status is not one the channel has, or a time is not ISO 8601. |
| 400 | invalid_number | The to filter is not E.164. In a query string write + as %2B; the hint says so when a bare + arrived as a space. |
| 400 | invalid_from | The from filter is not one of your numbers. |
| 404 | not_found | The campaign filter is not a campaign of this workspace. |
Contact lists
Section titled “Contact lists”/v1/lists (Make a list).
| status | code | means, and what to do |
|---|---|---|
| 400 | empty_audience_input | "contacts": []. Leave contacts out for an empty list, or send at least one row. Nothing is changed. |
| 400 | invalid_number | A row’s phone is not E.164; param names the row (contacts.0.phone). |
| 404 | not_found | param: "id": no such list in this workspace (or it was deleted). param: "contact": nobody in the list has that id or number. |
| 409 | list_in_use (with campaigns) | A draft, queued, running or paused campaign uses the list, so it was not deleted. PATCH the draft to another audience.list, delete it, or cancel the campaign. |
| 409 | ambiguous_contact (with candidates) | More than one contact in the list has that number; nobody was removed. Remove each by its ct-… id. |
| 409 | import_in_progress | The first request with this Idempotency-Key is still running. Retry with the same key after Retry-After. |
Insights
Section titled “Insights”/v1/insights, and insight on a voice campaign (Choose what calls find out).
| status | code | means, and what to do |
|---|---|---|
| 400 | insight_not_supported | insight on a voicemail-drop campaign: it has no conversation to read. |
| 404 | insight_not_found | param: "insight": no insight with that id in this workspace. GET /v1/insights lists them. |
| 409 | insight_owned_by_call_plan | The campaign is a Call plan, which keeps its own insight. Change what it asks in the dashboard or with Octo. |
| 409 | revision_conflict (with revision) | The insight changed since you read it; nothing was changed. Read it again and redo the edit on what it holds now. |
| 409 | field_in_use (with fields) | The edit renames or removes a field that has open requests, starts workflows or alerts live; nothing was changed. Keep its key, or send acknowledge_field_changes: true. |
Segments
Section titled “Segments”/v1/segments (Build a segment).
| status | code | means, and what to do |
|---|---|---|
| 400 | invalid_filter (with issues) | The filter has a problem: param names the first one (filter.all.1.value), the message says what that field takes, and issues lists every problem with its path. A field written the dashboard’s way (channelType, dnc:sms) gets a hint with the /v1 name. Nothing is saved. |
| 403 | insufficient_role | Creating, changing or deleting a segment needs a key created by an owner or admin. Reads work with any key. |
| 404 | not_found | No live segment with that id in this workspace (a deleted one too). |
| 409 | segment_in_use (with campaigns) | A queued, running or paused campaign uses the segment, so it was not deleted, or its filter was not changed (a PATCH changes nothing then, name included). Wait for it to finish, or cancel it. A draft does not block. |
| 409 | create_in_progress | The first POST with this Idempotency-Key is still running. Retry with the same key after Retry-After. |
| 422 | segment_filter_invalid | The segment’s saved filter can no longer be used, so its contacts cannot be listed. PATCH a new filter. |
| 503 | segment_too_slow | Reading the segment’s contacts took too long and was stopped. Narrow the filter (add a tag or channel condition) or ask for a smaller limit; retry after Retry-After. |
Contacts
Section titled “Contacts”/v1/contacts (Keep contacts).
| status | code | means, and what to do |
|---|---|---|
| 400 | invalid_request | POST with neither phone nor email; email is not an email address; attributes or tags are over their limits; query is not 3 to 100 characters, or came with phone or email; or a change would remove the contact’s last phone number and email (delete the contact instead). |
| 400 | invalid_number | phone (in the body, or the phone filter) is not E.164. In a query string write + as %2B. |
| 404 | not_found | No such contact in this workspace, or it was deleted. |
| 409 | contact_exists (with contact) | Another contact already has this phone number (on texts, calls or WhatsApp) or email, so nothing was created or changed. contact is its ct-… id: read it or PATCH it instead. |
| 409 | create_in_progress | The first POST with this Idempotency-Key is still running. Retry with the same key after Retry-After. |
| 409 | stopped_by_person (with channels) | An opt-in over an opt-out the person sent themselves, such as a STOP. Nothing was changed. Only the person can opt back in: on SMS, by texting START. |
| 409 | no_address (with channels) | An opt-out for a channel the contact has no phone number or email for. Nothing was changed. Give it one first, or leave that channel out. |
Balance and compliance
Section titled “Balance and compliance”/v1/balance (Credits and balance),
/v1/usage (Usage and spend)
and /v1/compliance
(Accept the outbound compliance acknowledgment).
| status | code | means, and what to do |
|---|---|---|
| 400 | invalid_request | POST /v1/compliance/accept without version. Send the current_version GET /v1/compliance returned. |
| 400 | unknown_field | The accept body takes version only. |
| 403 | insufficient_role | Only an owner or admin accepts the acknowledgment: use a key created by one. Nothing is accepted. |
| 409 | version_mismatch (with current_version) | The text changed since you read it, so nothing was accepted. Read it again with GET /v1/compliance and accept its current_version. |
| 400 | too_many_groups (with limit) | GET /v1/usage would answer more than 5,000 groups. Narrow from / to, or group by less (day,category instead of day,label). |
| 400 | export_too_large (with limit) | More than 250,000 charges match GET /v1/usage/export. Nothing was sent. Narrow from / to or filter by category, and export each part. |
| 429 | export_in_progress | Another usage export of the workspace is running. Retry after Retry-After seconds. |
SMS messages
Section titled “SMS messages”POST /v1/messages with "channel": "sms" (Send a text).
| status | code | means, and what to do |
|---|---|---|
| 400 | invalid_number | to is not E.164, or not a real number (param: "to"). The hint suggests the E.164 form when the number is clear; we never send to the suggestion. Nothing is sent or charged. |
| 400 | invalid_from | from is not a live number of this workspace. Pick one from GET /v1/senders. |
| 400 | from_not_sms_capable | from is yours but has no SMS channel (a voice-only number). Pick a number whose sms.ready is true. |
| 400 | no_sender | The number’s SMS channel has no sender set up yet. Finish its SMS setup in the dashboard under Phone numbers. |
| 402 | insufficient_credits | The balance cannot cover the send (short_credits says by how much). Nothing is sent. Read it with GET /v1/balance and add credits in the dashboard. |
| 402 | billing_not_eligible | The workspace’s billing does not allow outbound sends: there is no card on file, or the subscription is past due or canceled (reason says which). Add a card, or bring the subscription up to date, under Settings → Billing. |
| 402 | workspace_suspended | The workspace is suspended, usually for an unpaid balance. See Settings → Billing. |
| 402 | route_blocked | Your account’s sending rules refuse this destination. |
| 409 | opted_out | The number opted out (it texted STOP, or is on your do-not-contact list). Nothing was written. Do not resend. |
| 409 | send_in_progress | The first request with this Idempotency-Key is still being handed to the sending engine. Retry with the same key after Retry-After (Idempotency). |
| 422 | sender_not_cleared | The number is not registered with the carriers yet; sender_status says where it is (Get a number cleared to text). |
| 422 | trial_sender_not_allowed | from is the shared trial number, which only places calls to a phone number on your profile or a teammate’s (The trial number). |
| 422 | mms_unsupported_country | media was sent to a number outside +1. MMS goes to +1 numbers only. |
| 422 | media_not_owned | media is not a file this workspace uploaded. Upload it with POST /v1/media first and send the media it answers. |
| 422 | sms_no_rate | We have no price for this destination yet, so we cannot send to it. |
| 422 | empty_message | Neither text nor media was sent. Send one. |
| 429 | rate_limited | Over the send limit (Rate limits). Retry with the same key after Retry-After. |
| 503 | rate_limiter_unavailable | The send limiter is briefly unavailable. Retry with the same key after Retry-After. |
WhatsApp messages
Section titled “WhatsApp messages”POST /v1/messages with "channel": "whatsapp" (Send a WhatsApp template).
| status | code | means, and what to do |
|---|---|---|
| 400 | invalid_number | to is not E.164 or not a real number, checked before anything else; the hint suggests the E.164 form when it can. |
| 400 | invalid_from | from is not a WhatsApp number of this workspace. |
| 400 | invalid_request | The body breaks a rule: a name in several languages without template.language, variables that mix names and positions, or a template id with another template’s name or language. param names the field. |
| 400 | unknown_field | A field a WhatsApp message does not take. A text field gets a hint to use template: the API sends templates only. |
| 402 | billing_not_eligible | As for SMS: no card on file, or a past-due or canceled subscription (reason). |
| 402 | workspace_suspended | The workspace is suspended. |
| 402 | route_blocked | Your sending rules refuse this destination. |
| 404 | template_not_found | No template with that id, or that name and language, on this number. hint suggests the closest name, or the message lists the languages it exists in. |
| 409 | opted_out | The person opted out. The message is stored as blocked (message_id). Do not resend. |
| 409 | send_in_progress | The first request with this key is still sending. Retry with the same key after Retry-After. |
| 422 | trial_sender_not_allowed | from is the shared trial number, which only places calls. |
| 422 | template_not_on_sender | The template belongs to a different WhatsApp number than from. Send from the number GET /v1/templates lists. |
| 422 | template_not_approved | Meta has not approved the template; status is its current state. |
| 422 | template_components_mismatch | The values do not fit the approved template (a missing variable, a button it does not have). message names it; nothing was written. |
| 422 | channel_unavailable | The WhatsApp number is deleted or inactive. |
| 422 | channel_pending_verification | The WhatsApp number is still being verified. |
| 422 | number_disconnected | Meta disconnected this WhatsApp Business App number. Reconnect it in the dashboard. |
| 429 | workspace_rate_limited | The workspace’s rate for template sends through the API is used up. Nothing was written. |
| 429 | rate_limited | The number’s own sending budget for this second, minute or hour is full (shared with the inbox). |
| 429 | daily_limit_reached | The number’s daily template cap is used up (one cap per number, shared with campaigns). |
| 502 | send_failed | Meta rejected the send. The message is stored as failed (message_id). |
| 503 | idempotency_unavailable | We could not check the key just now; nothing was sent. Retry with the same key. |
Every WhatsApp 429 carries retry_after in error and the same number of
seconds in Retry-After.
Agents
Section titled “Agents”PATCH /v1/agents/{id} (Improve an agent between rounds). Nothing changes on any of these.
| status | code | means, and what to do |
|---|---|---|
| 400 | prompt_required | prompt is blank. Send the agent’s instructions. |
| 400 | invalid_request | A field breaks a rule (prompt over 50,000 or greeting over 2,000 characters); param names it. |
| 400 | field_not_supported | voicemail_message (or voicemail): voicemail is a setting of the voice campaign, not of the agent. |
| 400 | model_not_allowed | model.chat or model.voice (param) is not a model this agent can use; allowed lists the ones it can. |
| 400 | voice_not_available | voice is not a voice of this agent’s voice engine or one the workspace imported; allowed lists them (id, name, language). |
| 400 | language_not_supported | language is not a language calls support; allowed lists them. |
| 403 | insufficient_role | The key’s owner is not an owner or admin of the workspace. |
| 409 | version_conflict | The agent changed since you read it: it is no longer at expected_version. The error carries expected_version and current_version. Read it again, then send your change with the new version. |
A saved change can still carry warnings in the answer: model_replaced (a retired model was saved as its replacement) and voice_language_mismatch (the language changed and the voice speaks another one: send a voice of that language).
Rehearsals
Section titled “Rehearsals”POST /v1/agents/{id}/test-turns (Rehearse an agent). Nothing is run or billed on any of these.
| status | code | means, and what to do |
|---|---|---|
| 400 | message_required | A later turn without message, or a first turn of an agent with no greeting. Send what the person says. |
| 400 | session_setting_changed | channel, mission or variables (param) differ from the session’s. Leave them out, or start a new session. |
| 400 | variable_missing | The mission uses a placeholder (param: "variables.<name>") with no value and no fallback: a campaign would skip that person. Add it to variables. |
| 400 | voice_model_not_rehearsable | The agent’s calls run on a speech-to-speech model. Send channel: "chat". |
| 402 | workspace_suspended | The workspace is suspended (reason). Add credits or fix the payment method. |
| 404 | session_not_found | No session of this agent has that session_id, or it expired (an hour after its last request). Start a new one. |
| 409 | session_busy | Another request of the session is still running. Wait Retry-After, then send the turn again with its Idempotency-Key. |
| 409 | session_full | The session has 50 caller turns. Start a new one. |
| 409 | agent_changed | The agent changed since the session started (session_version, current_version). Start a new session. |
| 429 | rate_limited | Over 60 rehearsal requests a minute for the workspace. |
| 502 | upstream_error | The agent’s model did not answer; the session is unchanged. Retry with the same key. |
| 503 | unavailable | Sessions are unavailable just now. Retry with the same key. |
POST /v1/calls (Place a call).
| status | code | means, and what to do |
|---|---|---|
| 400 | invalid_number | to is not E.164 or not a real number; nothing was dialed. |
| 400 | invalid_from | from is not a number of this workspace that can place calls, or the trial number’s id is wrong. Copy it again from GET /v1/senders. |
| 400 | invalid_request | The body breaks a rule (mission over 4,000 characters, an agent that is another kind of id). param names the field. |
| 400 | agent_required | No agent was sent and the from number has no agent of its own (or its agent was deleted); nothing was dialed. Name one from GET /v1/agents. |
| 400 | daily_voice_minutes_exceeded | The workspace’s daily talk-minute cap is used up; the error carries cap, used and remaining. Wait for the reset at midnight in the workspace’s timezone, or raise the cap under Settings → Voice. |
| 402 | route_blocked | Your calling rules refuse this destination (allowed_country names where you can call). On the trial number: to is not a phone number on your profile or a teammate’s, or no profile has a number yet. |
| 402 | trial_daily_cap | The trial number’s calls for today are used up; resets_at says when it clears. |
| 402 | trial_lifetime_cap | The trial number’s calls are used up for good. Buy or bring a number. |
| 402 | trial_calling_off, trial_platform_cap | Trial calling is not available right now. Show message. |
| 402 | workspace_suspended | The workspace is suspended or cannot pay for calls. See Settings → Billing. |
| 404 | agent_not_found | agent is not an agent of this workspace, or it was deleted; nothing was dialed. List them with GET /v1/agents. |
| 404 | voicemail_recording_not_found | call_settings.voicemail.recording (on a call or a voice campaign) is not a voicemail recording of this workspace; nothing was dialed or saved. List them with GET /v1/voicemail-recordings. |
| 409 | agent_disabled | The agent is paused or archived. Enable it first. |
| 409 | call_in_progress | The first request with this key is still running. Retry with the same key after Retry-After. |
| 409 | calls_not_routed | The number was brought from your own carrier for texts only, so its calls do not come to us. |
| 429 | rate_limited | More than 10 calls a minute in this workspace. Wait retry_after seconds (also the Retry-After header). |
| 502 | dispatch_failed | The call could not be placed. Try again later. |
| 503 | trial_check_unavailable | We could not check a trial call just now. Try again in a moment. |
| 503 | idempotency_unavailable | We could not check the key just now; nothing was dialed. Retry with the same key. |
/v1/voicemail-recordings (Voicemail recordings).
| status | code | means, and what to do |
|---|---|---|
| 400 | no_file | No file part was sent (or the body was cut short). Send the audio as -F file=@message.mp3. |
| 413 | audio_too_large | The audio is over 5 MB. |
| 413 | storage_quota_exceeded | The workspace’s file storage is full. Delete recordings or files you no longer use. |
| 415 | unsupported_audio_format | Not an MP3, M4A or OGG file (WAV is not accepted). |
| 409 | voicemail_recording_in_use | A draft, queued, running or paused campaign plays this recording (campaigns names them). Point those campaigns at another recording, or wait until they finish. |
POST /v1/calls/{id}/end and GET /v1/calls/{id}/voicemail
(End a call or get its voicemail).
| status | code | means, and what to do |
|---|---|---|
| 404 | no_voicemail | The call has no voicemail: it never reached one, or nobody left a message. |
| 502 | hangup_failed | The hangup could not be sent, so the call may still be on. Try again; ending twice is safe. |
Conversations
Section titled “Conversations”/v1/conversations (Work the inbox).
| status | code | means, and what to do |
|---|---|---|
| 400 | invalid_request | A filter word that is not one (status, channel, include_campaigns), a reply with neither text, media nor template, a template sent with text or media, or a search query that is empty or over 200 characters. param names it. |
| 404 | not_found | No such conversation in this workspace (or contact, for the contact filter). |
| 404 | template_not_found | No approved template by that id or name on the conversation’s number. |
| 409 | opted_out | The person opted out of this channel, so the reply was not sent. The thread keeps it, marked not sent. Do not resend. |
| 409 | conversation_closed | A closed conversation reopens as active only. Send that first. |
| 409 | call_in_progress | PATCH cannot close a call still on the line. End it with POST /v1/calls/{id}/end. |
| 409 | status_changed | The status changed while your PATCH ran (the AI agent handed it off, a teammate closed it), so nothing was changed. Read it again and decide. |
| 409 | reply_in_progress | The first reply with this Idempotency-Key is still running. Retry with the same key after Retry-After. |
| 422 | reply_window_closed (with closes_at) | Meta’s 24 hours since the person last wrote are over. On WhatsApp send a template; elsewhere wait for them to write. |
| 422 | channel_not_supported | A call or an email thread takes no reply here. |
| 422 | no_delivery_route | The conversation’s channel can no longer send. Reconnect it in the dashboard. |
| 422 | media_not_owned | media is not a file this workspace uploaded. |
| 422 | template_not_supported | A template reply on a conversation that is not on WhatsApp. Send text instead. |
| 402 | insufficient_credits | An SMS reply the balance cannot cover (short_credits says by how much). Nothing is sent. |
| 422 | sms_no_rate | An SMS reply to a destination we have no price for yet. |
| 429 | rate_limited, daily_limit_reached | On SMS, this key’s send rate, shared with POST /v1/messages; on WhatsApp, the number’s sending budget. Nothing was sent. Retry with the same key after Retry-After. |
| 502 | dispatch_failed | The channel refused the reply. The thread keeps it, marked not sent. |
Events, media and recordings
Section titled “Events, media and recordings”GET /v1/events (The event stream),
GET /v1/media/{id} and GET /v1/recordings/{conversation_id}.
| status | code | means, and what to do |
|---|---|---|
| 400 | invalid_request | types names an event type that does not exist, api_version is not 2026-10-01 or v2, a parameter was sent twice, or the path id is malformed or of another kind (param says which). |
| 400 | invalid_cursor | after is not a whole number of 0 or more. Pass the id: of the last frame you handled, or leave it out. |
| 400 | unknown_parameter | The stream takes after, types and api_version; media and recordings take none. |
| 403 | insufficient_role | The stream is for owners and admins only. On an open stream this comes as an error frame, and the stream ends. |
| 404 | not_found | No such file or conversation in this workspace. |
| 404 | no_recording | The call has no recording: it did not connect, or recording was off for the agent. |
| 404 | recording_failed | Recording was attempted and failed; message says why. Do not retry. |
| 429 | too_many_streams (with retry_after) | This key (5) or workspace (20) has the most streams it may hold open. Close one. |
| 502 | upstream_error | The download link could not be made. Retry. |
Uploads
Section titled “Uploads”POST /v1/media (Send a picture (MMS)).
| status | code | means, and what to do |
|---|---|---|
| 400 | no_file | No file part, or the multipart body could not be read. Send the file as a part named file. |
| 400 | unknown_field | A form field other than file (param names it). |
| 400 | invalid_request | More than one file. Upload each file on its own. |
| 413 | image_too_large | The image is over 5 MB. |
| 413 | video_too_large | The video, or the request, is over 50 MB. |
| 413 | image_too_large_prepared | The image is still too large once prepared. Send a smaller one. |
| 413 | storage_quota_exceeded | The workspace’s storage is full. |
| 415 | unsupported_type | Not a JPEG, PNG or GIF up to 5 MB, or an MP4, MOV or WebM up to 50 MB. |
| 415 | unsupported_media_type | The request is not multipart/form-data. |
| 422 | invalid_image / invalid_video | The file could not be read as the type its bytes claim. |
| 422 | transcode_timeout | The video took too long to prepare. Send a shorter one. |
| 429 | upload_busy / transcoder_busy | Too many uploads, or another video is being prepared. Retry after Retry-After (retry_after: 5 or 10 seconds). |
| 500 | upload_failed | The file could not be stored. Retry. |
| 503 | image_unavailable / transcoder_unavailable | Files cannot be prepared right now. Retry after Retry-After. |
| 504 | image_timeout | The image took too long to prepare. Retry. |
Webhooks
Section titled “Webhooks”/v1/webhooks (Receive webhooks).
| status | code | means, and what to do |
|---|---|---|
| 400 | invalid_request | url, event_types or api_version breaks a rule, a deliveries list’s status is not one of its values, or an id is of another kind. param names the field. |
| 400 | invalid_cursor | A deliveries list’s after is from another endpoint or another status. Pass the next_cursor of the same list. |
| 403 | insufficient_role | The key’s owner is not an owner or admin of the workspace. Use such a key. |
| 404 | not_found | No such endpoint in this workspace, or no such delivery under it (deliveries are kept for 7 days). |
| 409 | webhook_endpoint_limit | The workspace has 500 endpoints. Delete one first. |
| 429 | webhook_test_rate_limited | More than 10 test events a minute. Wait Retry-After seconds. |
Campaigns
Section titled “Campaigns”/v1/campaigns and its actions (Run a campaign).
When a create that starts is refused after the campaign was created,
error.campaign_id names it: it is kept as a draft and nothing is sent.
| status | code | means, and what to do |
|---|---|---|
| 400 | start_required | A create without start (param: "start"). Add "start": "draft" to save it for preview, or "start": "auto" to create and start it now. Nothing is created. |
| 400 | invalid_from | from is not a number of this workspace that can send on the campaign’s channel (param: "from"). |
| 400 | from_not_sms_capable | An SMS campaign’s from (on create, or a draft’s new from on PATCH) is a number of this workspace with no SMS channel. |
| 400 | invalid_number | An inline contact’s phone is not E.164; param names the row (audience.contacts.0.phone) and hint gives the E.164 form. |
| 400 | invalid_variable (with problems, part) | A variables value is not one reference or plain text, names a field with a space or another character a field name can’t have, is too long, is {{contact.id}} on a WhatsApp or AI-call campaign (only SMS can send the contact’s id), or is a copy code over 15 characters. Every bad value is in problems; param is the first by param. A value over 200 characters says which part (part: text, field or fallback) and its length. On a start or resume: a copy code written longer than 15 characters. |
| 400 | unknown_variable (with fields, problems) | variables names something the content has no placeholder for, on create or PATCH (stored names included, after the merge). param is the first by param, fields every one, hint the closest name or how to remove them; each entry of problems has its own hint and, when there is one, suggestion. A voice campaign’s names are checked once it has an agent: the PATCH that sets content.agent checks the names stored before it. |
| 400 | invalid_schedule | tz is not an IANA zone; start_at or end_at has passed, falls in a skipped local hour or cannot be read; end_at is not after start_at; or a window in hours starts and ends at the same time. On a start: schedule.end_at has passed, so it would send nothing; PATCH a later one. |
| 400 | empty_audience_input (with errors, received, invalid_rows) | The audience has nobody to import: "contacts": [], or every row was dropped. Nothing is created, and the Idempotency-Key is free again. |
| 400 | field_not_editable | An edit names a field fixed at create (audience.contacts, audience.csv, a started SMS campaign’s from, a started campaign’s audience, …). An SMS draft takes a new from. |
| 400 | field_not_for_channel | A field of another channel’s campaigns (content.mission on SMS, retry on WhatsApp, limits.spend_cap_credits on a voicemail drop); param and fields name them. |
| 400 | voicemail_drop_unavailable | Voicemail-drop campaigns are not available yet. Nothing is created. |
| 400 | daily_voice_minutes_exceeded | The campaign does not fit its day’s calling minutes. The day is the campaign’s start day, in the workspace’s timezone (time_zone, day). metered is what that day already billed and held what other queued or scheduled campaigns hold that day (held_by names them); per_call_minutes is how long each call is projected at. Schedule it for another day, split the audience, or raise the cap. |
| 400 | audience_required | A clone without audience. Send "unreached" (only the people it did not reach) or "same" (everyone it had). |
| 402 | insufficient_credits (with short_credits) | The balance does not cover the whole campaign (GET /v1/balance shows it). A WhatsApp campaign needs 0.1 credits per recipient. |
| 402 | workspace_suspended | The workspace is suspended. See Settings → Billing. |
| 402 | billing_not_eligible | A voice campaign: no card on file, or a past-due or canceled subscription (reason). See Settings → Billing. |
| 403 | spend_cap_raise_needs_person (with cap, dashboard_url) | An API key raised or removed the spending limit of an AI-call campaign that has started. A key can set one where there is none, or lower one; a person raises it on the campaign’s page (dashboard_url), then resumes. |
| 404 | not_found | No such campaign, preview, contact list or segment in this workspace. A deleted contact list or segment answers the same. |
| 404 | template_not_found | An unknown template; hint suggests the closest name, or the languages it exists in (param: "content.template.language"). |
| 404 | agent_not_found | content.agent is not an agent of this workspace, or it was deleted. On a PATCH, a preview or a start of a voice campaign: the campaign’s agent was deleted after it was saved; set another content.agent with PATCH. A campaign whose agent is deleted while it runs skips its remaining calls as agent_disabled. |
| 404 | agent_not_found (clone) | The campaign’s agent was deleted, so its next round has nobody to talk. Send content.agent. |
| 409 | outbound_compliance_required | An owner or admin accepts the outbound compliance acknowledgment first, with POST /v1/compliance/accept. Nothing is created. |
| 409 | preview_required | An SMS draft starts only from a ready preview. Preview it, then start with its preview_id. |
| 409 | preview_stale | The preview is out of date: the campaign was edited after it, something it checked changed (the sender, the template, the agent, the compliance acknowledgment, the list or segment), a newer preview replaced it (reading that preview shows preview_expired), or it failed. A preview id made before previews recorded what they checked is stale too. A PATCH that changes nothing (every field as stored) does not make a preview stale. Preview again. |
| 409 | create_in_progress, start_in_progress, resume_in_progress | The first request with this key is still running. Retry with the same key. |
| 409 | campaign_already_started | A start of a campaign that is no longer a draft, started already or by a racing start. Read it with GET /v1/campaigns/{id}. |
| 409 | campaign_completed, campaign_canceled | A start of a campaign that finished, or was canceled. |
| 409 | campaign_not_pausable, campaign_not_resumable, campaign_not_cancellable (with status, allowed) | The campaign’s state does not allow the action. Canceling a draft is campaign_not_cancellable: delete it instead (a cancel’s allowed never lists draft). |
| 409 | campaign_not_editable (with status) | Only a draft or a paused campaign can be edited. Pause a running one first. |
| 409 | campaign_not_previewable (with status, allowed) | Only a draft or a paused campaign can be previewed (allowed: ["draft", "paused"]). |
| 409 | campaign_not_deletable (with status) | Only a draft, or a campaign canceled before it sent anything, can be deleted. A queued, running or paused campaign: cancel it to stop it. A finished one, or one canceled after it sent to someone, keeps its history. |
| 409 | campaign_not_finished (with status, allowed) | A clone of a campaign that is queued, running or paused: a next round would ring the same people at the same time. Wait for campaign.completed, or cancel it first. A draft may be copied with audience: "same". |
| 409 | round_in_progress (with campaign_id) | Another round of the same campaign family is still a draft or calling: a second would call the same people. Start, finish or delete it first. |
| 409 | clone_in_progress | The first clone with this Idempotency-Key is still running. Retry with the same key after Retry-After. |
| 409 | pause_in_progress | An SMS campaign just paused is still finishing its last texts. Preview again after Retry-After. |
| 409 | media_link_unavailable | Resume: the picture changed while its link was refreshed. The campaign is still paused; try again. |
| 409 | spend_cap_reached (with cap, used, remaining, in_flight, next_call, dashboard_url) | Resume of an AI-call campaign whose next call would not fit its spending limit: it would only pause again. It stays paused, nothing changes. A person raises the limit on the campaign’s page (dashboard_url), then resume it. |
| 413 | payload_too_large | The audience is over the size limit. |
| 422 | trial_sender_not_allowed | from is the shared trial number, which places single calls only. Nothing is created. |
| 422 | channel_not_supported | Results and clones are for AI-call campaigns; an SMS or WhatsApp campaign reports in GET /v1/campaigns/{id}. |
| 422 | campaign_not_clonable | The campaign is a Call plan: its next round is made from its results page in the dashboard. |
| 422 | no_unreached_recipients (with by_status) | A clone with audience: "unreached" and nobody left to call: everyone was reached, skipped, opted out or has no phone number. Nothing is created. Clone with audience: "same" to call everyone again. |
| 422 | no_recipients_left (with by_status) | A clone with audience: "same" and nobody it had can be called again: all asked to stop, opted out or have no phone number. Nothing is created. |
| 422 | sender_not_cleared (with sender_status), sender_missing, sms_no_billing_mode | The SMS sender cannot text yet (Get a number cleared to text). |
| 422 | sender_provider_unsupported | Texts and SMS campaigns send from Twilio numbers only for now; a Telnyx or Vonage from is refused (a single text answers 400 from_not_sms_capable). |
| 422 | template_not_on_sender | The template belongs to a different WhatsApp number than from. |
| 422 | template_not_approved (with template_status) | Create, PATCH or start: Meta has not approved the template. Set an approved one. |
| 422 | all_opted_out, no_recipients (with skipped_by_reason) | Everybody was skipped: all opted out, or each for the reason skipped_by_reason names (same_as_sender: the number is the campaign’s own sending number). A WhatsApp or voice start whose audience reaches nobody answers it too. The campaign is kept as a draft, on every channel and on a create with start: "auto" too (campaign_id names it; a retry with the same Idempotency-Key answers the same refusal). |
| 422 | unmapped_variable (with field, fields, problems, param: "variables") | A start, or a create that starts, has placeholders with no source and no fallback: field is the first, fields every one, problems each with its own hint. Nothing is created or sent. |
| 422 | unknown_variable (with fields, problems) | A WhatsApp or voice start or resume: a stored variables name fills nothing. Remove it with PATCH and "<name>": null. |
| 422 | agent_required | An AI-call campaign has no content.agent. |
| 422 | pricing_unavailable, preview_failed, mapping_too_long, media_not_owned, too_many_recipients | The preview could not be built. message says why. |
| 422 | launch_fields_missing (with missing) | The campaign cannot start until the /v1 fields in missing are set; hint names the PATCH. |
| 422 | not_launchable | The campaign cannot start as it is; message says why. |
| 502 | start_failed, campaign_restart_failed, upstream_error | A problem on our side kept it from starting or resuming; nothing changed. After a failed SMS start, preview again (its preview is out of date), then retry. |
| 503 | idempotency_unavailable | A WhatsApp or voice start could not check its key; nothing was started. Retry with the same key. |
A preview that runs but cannot start the campaign is not a refusal: it
answers status: "failed" with one of these codes in error, or with
preview_expired for an SMS preview older than 30 minutes or replaced by a
newer one; a start from such a preview is refused 409 preview_stale
(the preview rules).
Idempotency
Section titled “Idempotency”Moved to its own page: Retries, idempotency and rate limits.
It covers which routes take an Idempotency-Key, what a retry and a replay answer, and the
*_in_progress and idempotency_key_reuse codes.
Rate limits
Section titled “Rate limits”Moved to the same page: Rate limits (the request
and sending limits, 429 rate_limited and Retry-After).
Request ids
Section titled “Request ids”Every /v1 response carries an X-Request-Id header, errors and unknown
paths included, and a messaging endpoint’s error repeats it as request_id
(a 413 payload_too_large and a 401 too). Send your own X-Request-Id (letters, digits, -, _,
=) and we use it, so your logs and ours share one id.