Skip to content

Platform API errors

Download .md

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.
  • param names what the error is about, in /v1 names: a body field (to, template.id, audience.contacts.0.phone), a query parameter (status), a path segment (id) or a header (idempotency-key); or null.
  • hint says what to do next and docs_url where to read more, when we know. An unknown_field hint names the field you most likely meant at that place in the body (Did you mean `variables`?, Did you mean `agent`? inside content); unknown_parameter does the same for a query parameter, and an invalid_number hint gives the E.164 form (Did you mean +14155550100?).
  • request_id identifies 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 carries issues, one per problem: [{ "path": ["name"], "code": "invalid_type", "message": "name is required." }]. message is a sentence about the field; branch on code.

The envelope’s fields, field by field: V1Error.

type groups codes by what you do about them. It follows the HTTP status:

statustypewhat to do
400, 405, 413, 415, 422invalid_request_errorfix the request; retrying it unchanged fails again
401invalid_request_error (code invalid_api_key)check the key; the message says whether it was missing, sent without Bearer , or not valid
402billing_erroradd credits or a payment method
403permission_erroruse a key with the right role
404not_found_errorcheck the id; another workspace’s ids answer 404 too
409conflict_errorthe resource’s state does not allow it, or the same key is still running
429rate_limit_exceededwait Retry-After seconds, then retry
500, 502, 503server_errorretry 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).

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.

statuscodemeans, and what to do
400invalid_jsonThe body is not valid JSON. Send JSON.
400invalid_requestA 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.
400unknown_fieldThe body has a field this route does not take (inside nested objects too). hint names the field you most likely meant.
400unknown_parameterThe query string has a parameter this route does not take. hint names the closest one.
400invalid_idempotency_keyThe Idempotency-Key breaks the format. Use 1 to 255 printable ASCII characters.
401invalid_api_keyThe 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.
404not_foundNo such path, or no such resource in this workspace: another workspace’s id answers the same. Check the id.
405method_not_allowedThe path does not take this method. The Allow header lists the ones it does.
413payload_too_largeThe body is over the size limit. Send less, or a campaign audience as a list.
415unsupported_media_typeThe body was not sent as application/json. Set Content-Type.
422idempotency_key_reuseThe same Idempotency-Key came with a different request. Use a new key for a new request.
429rate_limitedOver a rate limit. Wait Retry-After seconds and retry with the same key.
500internal_errorSomething failed on our side. Retry with the same key.
502upstream_errorA service we depend on failed. Retry with the same key.
statuscodemeans, and what to do
400invalid_cursorafter 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.
400invalid_requestlimit is out of range, or a query parameter was sent twice.
400invalid_request (messages, calls)channel is missing, a status is not one the channel has, or a time is not ISO 8601.
400invalid_numberThe to filter is not E.164. In a query string write + as %2B; the hint says so when a bare + arrived as a space.
400invalid_fromThe from filter is not one of your numbers.
404not_foundThe campaign filter is not a campaign of this workspace.

/v1/lists (Make a list).

statuscodemeans, and what to do
400empty_audience_input"contacts": []. Leave contacts out for an empty list, or send at least one row. Nothing is changed.
400invalid_numberA row’s phone is not E.164; param names the row (contacts.0.phone).
404not_foundparam: "id": no such list in this workspace (or it was deleted). param: "contact": nobody in the list has that id or number.
409list_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.
409ambiguous_contact (with candidates)More than one contact in the list has that number; nobody was removed. Remove each by its ct-… id.
409import_in_progressThe first request with this Idempotency-Key is still running. Retry with the same key after Retry-After.

/v1/insights, and insight on a voice campaign (Choose what calls find out).

statuscodemeans, and what to do
400insight_not_supportedinsight on a voicemail-drop campaign: it has no conversation to read.
404insight_not_foundparam: "insight": no insight with that id in this workspace. GET /v1/insights lists them.
409insight_owned_by_call_planThe campaign is a Call plan, which keeps its own insight. Change what it asks in the dashboard or with Octo.
409revision_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.
409field_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.

/v1/segments (Build a segment).

statuscodemeans, and what to do
400invalid_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.
403insufficient_roleCreating, changing or deleting a segment needs a key created by an owner or admin. Reads work with any key.
404not_foundNo live segment with that id in this workspace (a deleted one too).
409segment_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.
409create_in_progressThe first POST with this Idempotency-Key is still running. Retry with the same key after Retry-After.
422segment_filter_invalidThe segment’s saved filter can no longer be used, so its contacts cannot be listed. PATCH a new filter.
503segment_too_slowReading 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.

/v1/contacts (Keep contacts).

statuscodemeans, and what to do
400invalid_requestPOST 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).
400invalid_numberphone (in the body, or the phone filter) is not E.164. In a query string write + as %2B.
404not_foundNo such contact in this workspace, or it was deleted.
409contact_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.
409create_in_progressThe first POST with this Idempotency-Key is still running. Retry with the same key after Retry-After.
409stopped_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.
409no_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.

/v1/balance (Credits and balance), /v1/usage (Usage and spend) and /v1/compliance (Accept the outbound compliance acknowledgment).

statuscodemeans, and what to do
400invalid_requestPOST /v1/compliance/accept without version. Send the current_version GET /v1/compliance returned.
400unknown_fieldThe accept body takes version only.
403insufficient_roleOnly an owner or admin accepts the acknowledgment: use a key created by one. Nothing is accepted.
409version_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.
400too_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).
400export_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.
429export_in_progressAnother usage export of the workspace is running. Retry after Retry-After seconds.

POST /v1/messages with "channel": "sms" (Send a text).

statuscodemeans, and what to do
400invalid_numberto 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.
400invalid_fromfrom is not a live number of this workspace. Pick one from GET /v1/senders.
400from_not_sms_capablefrom is yours but has no SMS channel (a voice-only number). Pick a number whose sms.ready is true.
400no_senderThe number’s SMS channel has no sender set up yet. Finish its SMS setup in the dashboard under Phone numbers.
402insufficient_creditsThe 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.
402billing_not_eligibleThe 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.
402workspace_suspendedThe workspace is suspended, usually for an unpaid balance. See Settings → Billing.
402route_blockedYour account’s sending rules refuse this destination.
409opted_outThe number opted out (it texted STOP, or is on your do-not-contact list). Nothing was written. Do not resend.
409send_in_progressThe first request with this Idempotency-Key is still being handed to the sending engine. Retry with the same key after Retry-After (Idempotency).
422sender_not_clearedThe number is not registered with the carriers yet; sender_status says where it is (Get a number cleared to text).
422trial_sender_not_allowedfrom is the shared trial number, which only places calls to a phone number on your profile or a teammate’s (The trial number).
422mms_unsupported_countrymedia was sent to a number outside +1. MMS goes to +1 numbers only.
422media_not_ownedmedia is not a file this workspace uploaded. Upload it with POST /v1/media first and send the media it answers.
422sms_no_rateWe have no price for this destination yet, so we cannot send to it.
422empty_messageNeither text nor media was sent. Send one.
429rate_limitedOver the send limit (Rate limits). Retry with the same key after Retry-After.
503rate_limiter_unavailableThe send limiter is briefly unavailable. Retry with the same key after Retry-After.

POST /v1/messages with "channel": "whatsapp" (Send a WhatsApp template).

statuscodemeans, and what to do
400invalid_numberto is not E.164 or not a real number, checked before anything else; the hint suggests the E.164 form when it can.
400invalid_fromfrom is not a WhatsApp number of this workspace.
400invalid_requestThe 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.
400unknown_fieldA field a WhatsApp message does not take. A text field gets a hint to use template: the API sends templates only.
402billing_not_eligibleAs for SMS: no card on file, or a past-due or canceled subscription (reason).
402workspace_suspendedThe workspace is suspended.
402route_blockedYour sending rules refuse this destination.
404template_not_foundNo 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.
409opted_outThe person opted out. The message is stored as blocked (message_id). Do not resend.
409send_in_progressThe first request with this key is still sending. Retry with the same key after Retry-After.
422trial_sender_not_allowedfrom is the shared trial number, which only places calls.
422template_not_on_senderThe template belongs to a different WhatsApp number than from. Send from the number GET /v1/templates lists.
422template_not_approvedMeta has not approved the template; status is its current state.
422template_components_mismatchThe values do not fit the approved template (a missing variable, a button it does not have). message names it; nothing was written.
422channel_unavailableThe WhatsApp number is deleted or inactive.
422channel_pending_verificationThe WhatsApp number is still being verified.
422number_disconnectedMeta disconnected this WhatsApp Business App number. Reconnect it in the dashboard.
429workspace_rate_limitedThe workspace’s rate for template sends through the API is used up. Nothing was written.
429rate_limitedThe number’s own sending budget for this second, minute or hour is full (shared with the inbox).
429daily_limit_reachedThe number’s daily template cap is used up (one cap per number, shared with campaigns).
502send_failedMeta rejected the send. The message is stored as failed (message_id).
503idempotency_unavailableWe 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.

PATCH /v1/agents/{id} (Improve an agent between rounds). Nothing changes on any of these.

statuscodemeans, and what to do
400prompt_requiredprompt is blank. Send the agent’s instructions.
400invalid_requestA field breaks a rule (prompt over 50,000 or greeting over 2,000 characters); param names it.
400field_not_supportedvoicemail_message (or voicemail): voicemail is a setting of the voice campaign, not of the agent.
400model_not_allowedmodel.chat or model.voice (param) is not a model this agent can use; allowed lists the ones it can.
400voice_not_availablevoice is not a voice of this agent’s voice engine or one the workspace imported; allowed lists them (id, name, language).
400language_not_supportedlanguage is not a language calls support; allowed lists them.
403insufficient_roleThe key’s owner is not an owner or admin of the workspace.
409version_conflictThe 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).

POST /v1/agents/{id}/test-turns (Rehearse an agent). Nothing is run or billed on any of these.

statuscodemeans, and what to do
400message_requiredA later turn without message, or a first turn of an agent with no greeting. Send what the person says.
400session_setting_changedchannel, mission or variables (param) differ from the session’s. Leave them out, or start a new session.
400variable_missingThe mission uses a placeholder (param: "variables.<name>") with no value and no fallback: a campaign would skip that person. Add it to variables.
400voice_model_not_rehearsableThe agent’s calls run on a speech-to-speech model. Send channel: "chat".
402workspace_suspendedThe workspace is suspended (reason). Add credits or fix the payment method.
404session_not_foundNo session of this agent has that session_id, or it expired (an hour after its last request). Start a new one.
409session_busyAnother request of the session is still running. Wait Retry-After, then send the turn again with its Idempotency-Key.
409session_fullThe session has 50 caller turns. Start a new one.
409agent_changedThe agent changed since the session started (session_version, current_version). Start a new session.
429rate_limitedOver 60 rehearsal requests a minute for the workspace.
502upstream_errorThe agent’s model did not answer; the session is unchanged. Retry with the same key.
503unavailableSessions are unavailable just now. Retry with the same key.

POST /v1/calls (Place a call).

statuscodemeans, and what to do
400invalid_numberto is not E.164 or not a real number; nothing was dialed.
400invalid_fromfrom 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.
400invalid_requestThe body breaks a rule (mission over 4,000 characters, an agent that is another kind of id). param names the field.
400agent_requiredNo 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.
400daily_voice_minutes_exceededThe 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.
402route_blockedYour 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.
402trial_daily_capThe trial number’s calls for today are used up; resets_at says when it clears.
402trial_lifetime_capThe trial number’s calls are used up for good. Buy or bring a number.
402trial_calling_off, trial_platform_capTrial calling is not available right now. Show message.
402workspace_suspendedThe workspace is suspended or cannot pay for calls. See Settings → Billing.
404agent_not_foundagent is not an agent of this workspace, or it was deleted; nothing was dialed. List them with GET /v1/agents.
404voicemail_recording_not_foundcall_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.
409agent_disabledThe agent is paused or archived. Enable it first.
409call_in_progressThe first request with this key is still running. Retry with the same key after Retry-After.
409calls_not_routedThe number was brought from your own carrier for texts only, so its calls do not come to us.
429rate_limitedMore than 10 calls a minute in this workspace. Wait retry_after seconds (also the Retry-After header).
502dispatch_failedThe call could not be placed. Try again later.
503trial_check_unavailableWe could not check a trial call just now. Try again in a moment.
503idempotency_unavailableWe could not check the key just now; nothing was dialed. Retry with the same key.

/v1/voicemail-recordings (Voicemail recordings).

statuscodemeans, and what to do
400no_fileNo file part was sent (or the body was cut short). Send the audio as -F file=@message.mp3.
413audio_too_largeThe audio is over 5 MB.
413storage_quota_exceededThe workspace’s file storage is full. Delete recordings or files you no longer use.
415unsupported_audio_formatNot an MP3, M4A or OGG file (WAV is not accepted).
409voicemail_recording_in_useA 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).

statuscodemeans, and what to do
404no_voicemailThe call has no voicemail: it never reached one, or nobody left a message.
502hangup_failedThe hangup could not be sent, so the call may still be on. Try again; ending twice is safe.

/v1/conversations (Work the inbox).

statuscodemeans, and what to do
400invalid_requestA 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.
404not_foundNo such conversation in this workspace (or contact, for the contact filter).
404template_not_foundNo approved template by that id or name on the conversation’s number.
409opted_outThe person opted out of this channel, so the reply was not sent. The thread keeps it, marked not sent. Do not resend.
409conversation_closedA closed conversation reopens as active only. Send that first.
409call_in_progressPATCH cannot close a call still on the line. End it with POST /v1/calls/{id}/end.
409status_changedThe 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.
409reply_in_progressThe first reply with this Idempotency-Key is still running. Retry with the same key after Retry-After.
422reply_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.
422channel_not_supportedA call or an email thread takes no reply here.
422no_delivery_routeThe conversation’s channel can no longer send. Reconnect it in the dashboard.
422media_not_ownedmedia is not a file this workspace uploaded.
422template_not_supportedA template reply on a conversation that is not on WhatsApp. Send text instead.
402insufficient_creditsAn SMS reply the balance cannot cover (short_credits says by how much). Nothing is sent.
422sms_no_rateAn SMS reply to a destination we have no price for yet.
429rate_limited, daily_limit_reachedOn 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.
502dispatch_failedThe channel refused the reply. The thread keeps it, marked not sent.

GET /v1/events (The event stream), GET /v1/media/{id} and GET /v1/recordings/{conversation_id}.

statuscodemeans, and what to do
400invalid_requesttypes 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).
400invalid_cursorafter is not a whole number of 0 or more. Pass the id: of the last frame you handled, or leave it out.
400unknown_parameterThe stream takes after, types and api_version; media and recordings take none.
403insufficient_roleThe stream is for owners and admins only. On an open stream this comes as an error frame, and the stream ends.
404not_foundNo such file or conversation in this workspace.
404no_recordingThe call has no recording: it did not connect, or recording was off for the agent.
404recording_failedRecording was attempted and failed; message says why. Do not retry.
429too_many_streams (with retry_after)This key (5) or workspace (20) has the most streams it may hold open. Close one.
502upstream_errorThe download link could not be made. Retry.

POST /v1/media (Send a picture (MMS)).

statuscodemeans, and what to do
400no_fileNo file part, or the multipart body could not be read. Send the file as a part named file.
400unknown_fieldA form field other than file (param names it).
400invalid_requestMore than one file. Upload each file on its own.
413image_too_largeThe image is over 5 MB.
413video_too_largeThe video, or the request, is over 50 MB.
413image_too_large_preparedThe image is still too large once prepared. Send a smaller one.
413storage_quota_exceededThe workspace’s storage is full.
415unsupported_typeNot a JPEG, PNG or GIF up to 5 MB, or an MP4, MOV or WebM up to 50 MB.
415unsupported_media_typeThe request is not multipart/form-data.
422invalid_image / invalid_videoThe file could not be read as the type its bytes claim.
422transcode_timeoutThe video took too long to prepare. Send a shorter one.
429upload_busy / transcoder_busyToo many uploads, or another video is being prepared. Retry after Retry-After (retry_after: 5 or 10 seconds).
500upload_failedThe file could not be stored. Retry.
503image_unavailable / transcoder_unavailableFiles cannot be prepared right now. Retry after Retry-After.
504image_timeoutThe image took too long to prepare. Retry.

/v1/webhooks (Receive webhooks).

statuscodemeans, and what to do
400invalid_requesturl, 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.
400invalid_cursorA deliveries list’s after is from another endpoint or another status. Pass the next_cursor of the same list.
403insufficient_roleThe key’s owner is not an owner or admin of the workspace. Use such a key.
404not_foundNo such endpoint in this workspace, or no such delivery under it (deliveries are kept for 7 days).
409webhook_endpoint_limitThe workspace has 500 endpoints. Delete one first.
429webhook_test_rate_limitedMore than 10 test events a minute. Wait Retry-After seconds.

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

statuscodemeans, and what to do
400start_requiredA 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.
400invalid_fromfrom is not a number of this workspace that can send on the campaign’s channel (param: "from").
400from_not_sms_capableAn SMS campaign’s from (on create, or a draft’s new from on PATCH) is a number of this workspace with no SMS channel.
400invalid_numberAn inline contact’s phone is not E.164; param names the row (audience.contacts.0.phone) and hint gives the E.164 form.
400invalid_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.
400unknown_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.
400invalid_scheduletz 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.
400empty_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.
400field_not_editableAn 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.
400field_not_for_channelA 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.
400voicemail_drop_unavailableVoicemail-drop campaigns are not available yet. Nothing is created.
400daily_voice_minutes_exceededThe 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.
400audience_requiredA clone without audience. Send "unreached" (only the people it did not reach) or "same" (everyone it had).
402insufficient_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.
402workspace_suspendedThe workspace is suspended. See Settings → Billing.
402billing_not_eligibleA voice campaign: no card on file, or a past-due or canceled subscription (reason). See Settings → Billing.
403spend_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.
404not_foundNo such campaign, preview, contact list or segment in this workspace. A deleted contact list or segment answers the same.
404template_not_foundAn unknown template; hint suggests the closest name, or the languages it exists in (param: "content.template.language").
404agent_not_foundcontent.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.
404agent_not_found (clone)The campaign’s agent was deleted, so its next round has nobody to talk. Send content.agent.
409outbound_compliance_requiredAn owner or admin accepts the outbound compliance acknowledgment first, with POST /v1/compliance/accept. Nothing is created.
409preview_requiredAn SMS draft starts only from a ready preview. Preview it, then start with its preview_id.
409preview_staleThe 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.
409create_in_progress, start_in_progress, resume_in_progressThe first request with this key is still running. Retry with the same key.
409campaign_already_startedA start of a campaign that is no longer a draft, started already or by a racing start. Read it with GET /v1/campaigns/{id}.
409campaign_completed, campaign_canceledA start of a campaign that finished, or was canceled.
409campaign_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).
409campaign_not_editable (with status)Only a draft or a paused campaign can be edited. Pause a running one first.
409campaign_not_previewable (with status, allowed)Only a draft or a paused campaign can be previewed (allowed: ["draft", "paused"]).
409campaign_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.
409campaign_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".
409round_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.
409clone_in_progressThe first clone with this Idempotency-Key is still running. Retry with the same key after Retry-After.
409pause_in_progressAn SMS campaign just paused is still finishing its last texts. Preview again after Retry-After.
409media_link_unavailableResume: the picture changed while its link was refreshed. The campaign is still paused; try again.
409spend_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.
413payload_too_largeThe audience is over the size limit.
422trial_sender_not_allowedfrom is the shared trial number, which places single calls only. Nothing is created.
422channel_not_supportedResults and clones are for AI-call campaigns; an SMS or WhatsApp campaign reports in GET /v1/campaigns/{id}.
422campaign_not_clonableThe campaign is a Call plan: its next round is made from its results page in the dashboard.
422no_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.
422no_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.
422sender_not_cleared (with sender_status), sender_missing, sms_no_billing_modeThe SMS sender cannot text yet (Get a number cleared to text).
422sender_provider_unsupportedTexts 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).
422template_not_on_senderThe template belongs to a different WhatsApp number than from.
422template_not_approved (with template_status)Create, PATCH or start: Meta has not approved the template. Set an approved one.
422all_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).
422unmapped_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.
422unknown_variable (with fields, problems)A WhatsApp or voice start or resume: a stored variables name fills nothing. Remove it with PATCH and "<name>": null.
422agent_requiredAn AI-call campaign has no content.agent.
422pricing_unavailable, preview_failed, mapping_too_long, media_not_owned, too_many_recipientsThe preview could not be built. message says why.
422launch_fields_missing (with missing)The campaign cannot start until the /v1 fields in missing are set; hint names the PATCH.
422not_launchableThe campaign cannot start as it is; message says why.
502start_failed, campaign_restart_failed, upstream_errorA 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.
503idempotency_unavailableA 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).

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.

Moved to the same page: Rate limits (the request and sending limits, 429 rate_limited and Retry-After).

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.