Skip to content
Linq Copy agent prompt
Overview

Error Codes

Complete reference of WhatsApp API error codes with troubleshooting guides.

Every error returns the same JSON envelope: a success: false flag, a nested error object, and a request_id naming the request that failed. The error object always carries the HTTP status the response was written with, a numeric code, and a human-readable message.

{
"success": false,
"error": {
"status": 422,
"code": 1001,
"message": "Field required.",
"doc_url": "https://docs.linqapp.com/channel/whatsapp/error/codes/1xxx/1001/",
"param": "/parts/0/body",
"details": [
{
"code": "field_required",
"param": "/parts/0/body",
"message": "Body is required."
}
]
},
"request_id": "req_01JQZ8X4K2N3P5R7T9V1W3Y5Z7"
}

error.code is a number and is the field to branch on. error.message is written for a human reading a log and may be reworded at any time, so matching on it will break. A code is never removed and never reassigned to a different failure: once a number means something, it means that forever.

The optional members carry the specifics. param is present only when the response is about exactly one field and is that field’s JSON Pointer; with zero or several faults it is absent. details lists every field-level finding on a validation failure, each with a string code, a JSON Pointer param and a message. retry_after is present exactly when the response also carries a Retry-After header, with the same value in seconds. trace_id appears when a valid trace context exists. doc_url is present on every response whose code has a published page and is the absolute address of that page, https://docs.linqapp.com/channel/whatsapp/error/codes/<N>xxx/<code>/, so a client can link straight to it; it is derived from the code alone, and a code reachable only on a surface that has not been released yet carries no doc_url.

These codes belong to WhatsApp. Every channel numbers its own errors, which is why this reference is namespaced to /channel/whatsapp/ — the same integer means something different on another channel, so branch on the code only in code paths you know are talking to this API.

The set of codes grows over time. Branch on the integers you know, and treat an integer you do not know by its range plus the HTTP status that came with it — the range says what kind of failure it is, and the status says whether the request or the service was at fault.

Range Category Retry?
1xxx Request Errors No — correct the request first
2xxx Resource, Permission and State Errors No — inspect or reconcile that state
3xxx Service Errors Yes — bounded backoff, unless the code’s page says otherwise
4xxx Operational and Send Refusals Sometimes — follow the code’s remedy
5xxx Attachment and File Errors Sometimes — follow the code’s remedy

When retry_after is present, it overrides the guidance above: wait that many seconds, then retry.

The request never reached the thing it was about. Correct the request and send it again; retrying it unchanged fails identically. Validation rejects and points at the most specific location it can identify — it never coerces a value into something acceptable.

Code Message HTTP Troubleshooting
1001 Field required. 422 Inspect error.param (or details[].param) for the exact JSON Pointer to the missing field.
1002 Body missing. 400 Check that your client actually transmits the body (a GET-style call with no body on a POST route is the usual cause).
1003 Body unreadable. 400 Validate the body with a JSON linter and confirm it is a single object with no trailing bytes.
1004 Invalid content. 422 Read details[] for the exact param (a JSON Pointer into your request) and each field’s own fault code.
1005 Value invalid. 422 Inspect error.param / details[].param and the accompanying message, which names the rule.
1006 Channel message id inbound only. 422 Address a reply with reply_to_message_id, using the message id this API returned or delivered in an event.
1007 Duplicate value. 422 Inspect details[].param for the second occurrence.
1008 Event kind unknown. 422 Inspect details[].param for the offending index in event_kinds and compare it with the event kinds listed on the webhook endpoint operation.
1009 Field not allowed. 422 Inspect details[].param; GET /v1/parts/{type} documents which fields each shape accepts.
1010 Field type. 400 / 422 Inspect error.param / details[].param and the message, which names the expected type.
1012 From ambiguous. 503 Resend with from set to the E.164 number you want this conversation to use; it becomes the pinned sender.
1013 From not available. 422 Call GET /v1/phone_numbers to list the numbers this account may send from, and use one of them.
1014 Idempotency key invalid. 400 / 422 Send one Idempotency-Key header per request, generated per logical operation, and reuse it only to replay that same request.
1016 Invalid paging. 400 / 422 Inspect details[].param (/query/limit, /query/cursor, /query/order or /headers/Last-Event-ID) and resend with a valid value; drop the cursor to start from the end the operation reads from.
1017 Invalid template. 422 Read details[] for the exact param (a JSON Pointer into your request) and each field’s own fault code.
1018 Method not allowed. 405 Read the Allow header on this response and GET /v1/openapi.yaml for the operation’s method.
1019 Multiple parts not available. 422 Send one request per part; separate requests are independent and no cross-request order is promised.
1020 Mutually exclusive. 422 Inspect details[].param and GET /v1/parts/{type} for which fields form the choice.
1021 Not available. 422 Remove the reserved field or part named by details[].param.
1022 Part not supported. 422 Use a supported part type; GET /v1/openapi.yaml lists the sendable union.
1023 Reply target wrong chat. 422 Use a message id from the same chat, or address the send to the chat the message belongs to.
1024 Template refused. 422 Revise the template per the message and submit again under a new name if the refusal was permanent.
1025 Too few items. 422 Inspect details[].param; GET /v1/parts/{type} states each array’s minimum.
1026 Too many items. 422 Inspect details[].param; GET /v1/parts/{type} states each array’s maximum.
1027 Unknown field. 422 Inspect details[].param and remove or correct the field; compare against GET /v1/openapi.yaml or GET /v1/parts/{type}.
1028 Unknown part type. 422 GET /v1/openapi.yaml lists the part union; each member’s schema is at GET /v1/parts/{type}.
1029 Value too long. 422 Inspect details[].param and shorten the value; limits are counted in characters, not bytes.
1030 Webhook field invalid. 422 Read details[] for the exact param (a JSON Pointer into your request) and each field’s own fault code.
1031 Webhook status invalid. 422 Read the endpoint with GET /v1/webhook_endpoints/{endpoint} and choose a documented status.
1032 Webhook url invalid. 422 Inspect details[].param (/url) and supply an https URL on a public host.
1033 Parameter invalid. 400 Compare the named parameter against its schema in GET /v1/openapi.yaml.
1034 Parameter missing. 400 Add the named parameter or header and resend.
1036 Replay range too large. 422 Narrow after_cursor / through_cursor or the event kinds and request the replay in slices.
1037 Cursor invalid. 400 / 422 Take cursors from this collection’s own responses and restart the listing without one when in doubt.
1038 Part not sendable. 422 Use a sendable part type; GET /v1/openapi.yaml marks which are outbound.
1039 Send at not available. 422 Omit send_at and send when you want the message delivered.
1040 Reaction target wrong chat. 422 Use a message id from the same chat, or address the send to the chat the message belongs to.
1041 Webhook phone number not found. 422 Choose ids returned by GET /v1/phone_numbers using the same credential and mode.

2xxx — Resource, Permission and State Errors

Section titled “2xxx — Resource, Permission and State Errors”

The request was well formed and the thing it named was missing, not yours, or not in a state that allows this. Inspect or reconcile that state before sending again.

An id that belongs to another account answers exactly like one that does not exist. That is deliberate: it keeps one account from probing another’s ids.

Code Message HTTP Troubleshooting
2001 Chat not found. 404 Verify the chat id came from GET /v1/chats or a message this account sent or received, in the same livemode.
2002 Credential not permitted. 403 Use a partner API key for this call; rotating the key you sent will not help.
2003 Attachment not found. 404 Take attachment ids from the message or event that delivered them, in the same livemode.
2004 Credential invalid. 401 Send exactly one Authorization: Bearer header with a current partner API key or capability credential.
2005 Not permitted. 403 Confirm the key’s livemode matches the resource and that it belongs to the account that owns the resource.
2007 Attachment not ready. 409 Wait error.retry_after seconds and retry the same GET /v1/attachments/{attachment}/content.
2009 Idempotency conflict. 409 Use a fresh Idempotency-Key for a new operation.
2010 Webhook endpoint not found. 404 List endpoints with GET /v1/webhook_endpoints and use an id from that account.
2011 Not found. 404 Verify the id came from this account’s own reads or events, in the same livemode.
2012 Operation forbidden. 403 Mint a capability credential whose operation class covers this operation, or use the account’s partner API key.
2013 Part type not found. 404 GET /v1/openapi.yaml lists every part type.
2014 Reply target not found. 422 Use a message id from this chat’s transcript (GET /v1/chats/{chat}/events).
2015 Route not found. 404 GET /v1/openapi.yaml is the API’s own map of what exists.
2029 Webhook replay not found. 404 Use the replay id returned by POST /v1/webhook_endpoints/{endpoint}/replays and the endpoint it was created on.
2030 Replay in progress. 409 Poll GET /v1/webhook_endpoints/{endpoint}/replays/{replay} until the running replay completes, then request the next one.
2031 Capability outcome indeterminate. 409 Do not retry blindly; read the affected resource (the chat, template or endpoint) to learn its current state first.
2032 Cursor expired. 410 Use the chat and transcript reads (GET /v1/chats/{chat}/events) to backfill anything you missed before resuming.
2033 Last active secret. 409 Add a new secret with POST /v1/webhook_endpoints/{endpoint}/secrets first, then retire the old one.
2034 No channel account. 503 Complete channel onboarding for the account, then retry.
2035 Nothing unread. 409 Read the chat’s transcript (GET /v1/chats/{chat}/events) to confirm its state before sending another read command.
2036 Outside customer window. 422 Read error.context.expires_at: a timestamp means the window lapsed, null means the customer has never messaged this number.
2037 Reply target not ready. 422 Read the target message until its status is sent (its channel_message_id appears at the same moment), then resend the reply. message.sent is journaled when the send is accepted, not when the channel acknowledges it, so it is not the signal to wait for.
2038 Template exists. 409 List templates with GET /v1/templates and reuse the existing one, or submit under a new name.
2039 Template outcome indeterminate. 409 Do not resubmit under the same name; read GET /v1/templates after registry reconciliation, or contact support with the request_id.
2040 Too many active secrets. 409 Retire a secret with the retire operation, then add the new one; a rotation needs two active secrets at most.
2041 Webhook endpoint exists. 409 List endpoints with GET /v1/webhook_endpoints and update the existing one instead of creating a duplicate.
2042 Webhook endpoint limit. 422 Remove an unused endpoint with DELETE /v1/webhook_endpoints/{endpoint}, then retry.
2043 Attachment unavailable. 410 Download attachments promptly after the event that delivers them; retention is documented on the attachment operation.
2047 No sending number. 503 Complete number provisioning, then retry; contact support if a number should already be attached.
2049 Reaction target not found. 422 Use a message id from this chat’s transcript (GET /v1/chats/{chat}/events).
2050 Reaction target not ready. 422 Read the target message until its status is sent (its channel_message_id appears at the same moment), then resend the reaction. message.sent is journaled when the send is accepted, not when the channel acknowledges it, so it is not the signal to wait for.
2051 Webhook delivery in progress. 409 Read the delivery with GET /v1/webhook_deliveries?delivery_id=… and retry once its state is pending again.
2052 Webhook delivery terminal. 409 For a retry_exhausted or permanent_refusal delivery, replay its event with POST /v1/webhook_endpoints/{endpoint}/replays.
2053 Webhook delivery not found. 404 Use a delivery id from GET /v1/webhook_deliveries under the same key and mode.
2054 Environment has no sending line. 422 Move a number into this environment where the account’s numbers are managed, then send again.
2055 Environment stream unavailable. 403 Register a webhook endpoint with POST /v1/webhook_endpoints under this key; it receives exactly this environment’s events.
2056 Nothing readable. 409 Nothing is owed on these messages; send a read command again after the customer’s next supported message, which it will target.
2057 Webhook account not connected. 409 Finish connecting the account’s messaging account, then repeat the request.

Transient, and ours. Retry with bounded exponential backoff, starting at 1 second and capping at 30, unless the code’s own page says otherwise. Sends are idempotent on Idempotency-Key, so a retry of a send that actually succeeded returns the original result rather than sending twice.

Code Message HTTP Troubleshooting
3001 Attachment read unavailable. 503 Wait error.retry_after seconds and retry the same download.
3002 Authentication unavailable. 503 Retry the same request with bounded backoff; nothing was recorded.
3003 Capability audit unavailable. 503 Retry the same request with bounded backoff; nothing was recorded.
3004 Entitlement unavailable. 503 Retry the same request with bounded backoff; nothing was recorded.
3006 Internal error. 500 Retry with bounded backoff.
3008 Response failed. 500 Read the affected resource before retrying a mutation.
3009 Sending guard unavailable. 503 Retry the same request with bounded backoff; nothing was recorded.
3010 Stream unavailable. 503 Use GET /v1/event_log if it is available, or contact support.
3011 Template asset unavailable. 503 Retry the unchanged submission later; if it persists, upload the asset again and contact support with the request_id.
3012 Template channel unavailable. 503 Retry the same request with bounded backoff; nothing was recorded.
3013 Webhook registration unavailable. 503 Retry later; if it persists, contact support.
3014 Webhook replay unavailable. 503 Contact support if you need a replay.
3015 Event log unavailable. 503 Use GET /v1/streams/events if it is available, or contact support.
3016 Event payload invalid. 500 Retry the same read; if it persists, contact support and quote the request_id.
3017 Webhook replay failed. 500 Retry with the same Idempotency-Key.
3019 Not implemented. 501 Check the operation’s first description line in GET /v1/openapi.yaml; it says Not yet available until the operation is live.
3020 Rate not configured. 503 Contact support rather than retrying; retries return the same answer until a rate is assigned.
3021 Template submission in progress. 503 Resend the identical request after a short wait; a changed body starts a new submission instead.
3022 Unavailable. 503 Retry the same request with bounded backoff; nothing was recorded.

The send was understood and refused for an operational reason: a sending number that cannot carry it, a paused account, or a limit that was reached. Each code names its own remedy; a retry without that remedy is refused the same way.

Code Message HTTP Troubleshooting
4001 Exclude leaves no sender. 422 Read error.context.pinned_from when present: your exclude named the number this conversation already uses.
4003 From not sendable. 422 Call GET /v1/phone_numbers and choose a number whose status admits sending.
4004 Pinned line unsendable. 422 Read error.context.pinned_from (the current number, absent when it is no longer yours) and error.context.available_from (a number that could serve instead).
4005 Sending paused. 503 Retry later with bounded backoff.
4006 Spend limit exceeded. 422 / 429 When error.retry_after is present, wait that many seconds; the reset is the UTC day or month boundary.
4007 Template not sendable. 422 Read the template with GET /v1/templates to confirm its status and send_schema; details[].param names the discriminating field.
4008 Tier quota exceeded. 422 / 429 Wait at least error.retry_after seconds when present; replies inside an open customer window are not counted.
4009 Replay capacity reached. 429 Wait error.retry_after seconds and request the replay again.
4010 Pinned line outside environment. 422 Read error.context.pinned_from (the conversation’s current number) and error.context.available_from (a number in this environment that could serve, when one exists).
4011 Recipient phone number required. 422 Deliver the one-time code another way; every other message type, including templates without a one-time-code button, can still be sent to this customer.
4012 Request rate exceeded. 429 Wait error.retry_after seconds, then retry; back off further if the refusal repeats.
4013 Event stream limit reached. 429 Reuse one stream per consumer rather than opening a new stream per task, and close streams you no longer read.

The file’s bytes: its kind, its size, where it came from, or storing it. An attachment that is merely missing or not yet readable is a 2xxx resource error instead.

Code Message HTTP Troubleshooting
5001 Download capacity exhausted. 503 Wait error.retry_after seconds and retry the same download; nothing about the attachment changed.
5002 Media id inbound only. 422 Upload outbound bytes with POST /v1/media and reference the returned upload_ref in the part.
5003 Media invalid. 422 Read details[] for the exact param (a JSON Pointer into your request) and each field’s own fault code.
5004 Upload capacity exhausted. 503 Wait error.retry_after seconds and retry the same upload.
5005 Upload ref invalid. 422 Use the upload_ref exactly as returned by POST /v1/media, in the same livemode.
5006 Upload ref not found. 422 Upload the bytes again with POST /v1/media and send promptly with the new upload_ref.
5007 Media source invalid. 422 Serve the file from a public https host on port 443 and supply that address without credentials.
5008 Media source unavailable. 422 Confirm the address serves the file directly with a 200 answer, without authentication or a redirect to a page.
5009 Media source unsupported. 422 Set kind to match the file the address serves, and serve it under a media type the upload operation documents for that kind.
5010 Media source capacity exhausted. 503 Retry the same request shortly.