API errors and retries
Interpret API errors and handle uncertain outcomes without duplicate sends.
Reviewed
Send HTTPS requests with a Bearer key. For message writes use Content-Type: application/json and the supported message payload. Check the HTTP status before interpreting the body: message acceptance is 202, reads are 200, and successful media reads contain bytes.
Read the error envelope
Errors include a stable code and a fixed, public-safe message. Some include explicitly safe fields such as retryAfterSeconds. Internal exception messages, stack traces, provider bodies and diagnostic details are not part of the response contract. Unexpected failures return a generic 500 response. Gateways can return non-JSON errors, so check the content type too. Branch on the status and code rather than matching the message text.
{
"code": "service_window_closed",
"message": "The customer-service window is closed. Use an approved template."
}400 invalid_message: Correct the payload shape, recipient, or field limit. Unknown message fields are rejected.400 invalid_paginationorinvalid_cursor: Use only the supported list parameters and the returned opaque cursor.401 unauthorizedorinvalid_api_key: Check the token, rotation, and revocation state.403 insufficient_scope: Add only the scope required for this operation.403 api_key_not_allowed: Choose a supported external endpoint. Dashboard routes are not generally API-key routes.404 message_not_found,conversation_not_found, ormedia_not_found: Check the identifier and the key's number. The resource may be unavailable within that scope.404 template_not_found: Create or sync the exact template name and language for the sending account in the dashboard.409 instance_not_api_managed: This send or mutation requires Developer API handling. Scoped reads do not require changing the handler.409 contact_unavailable: The contact was deleted or is unavailable. Review the contact record before sending.409 message_not_allowed: Another admission policy blocks this message. Review the error and current conversation state.503 temporarily_unavailable: Delay and respectretryAfterSecondswhen returned. Do not bypass an unavailable safety check.409 instance_unavailable: Restore the sender connection for writes; retained reads remain subject to lifecycle and visibility checks.409 conversation_owned_by_human: Coordinate with the Inbox owner. Use Release to API when a person decides the server should resume.409 service_window_closed: Use an eligible approved template or wait for a new customer message.409 template_not_approvedorquality_blocked: Check template approval and current sender/template quality. Do not bypass admission with another key.409 contact_opted_out,contact_instance_restricted, orcontact_marketing_restricted: Respect the restriction. Review its exact scope in the dashboard.429: Reduce request volume and wait forRetry-Afterwhen supplied. Formedia_concurrency_exceeded, also close active downloads.503 backlog_full: Admission is temporarily at capacity. Delay further sends.- Other
5xxor network timeout: Reconcile the outcome before retrying a write.
An accepted message can fail later. Check its message status or message.failed callback as well as the initial HTTP response.
Validate request sizes and resource conflicts
JSON writes require Content-Type: application/json. Message and contact writes allow at most 256 KiB; segment creation allows at most 4 KiB. GET requests and label/member PUT or DELETE requests have no JSON body. Do not send unknown fields, query parameters or caller-selected workspace/number identifiers. Oversized requests can return 413.
| Status and code | Next action |
|---|---|
400 invalid_input or invalid_webhook_target | Correct the resource fields or use a public HTTPS callback on port 443 without credentials, query or fragment. |
404 contact_not_found, segment_not_found, contact_or_label_not_found, or contact_or_segment_not_found | Use an existing resource in this workspace. |
409 revision_conflict | Read webhook settings again and reconcile the latest revision before writing. |
409 idempotency_conflict or idempotency_result_unavailable | Reconcile the original send. Do not generate a replacement key automatically. |
422 template_action_unavailable | Refresh the template detail and correct the Flow launch descriptor and inputs. |
502 template_provider_unavailable or webhook_test_failed | Retry only after checking provider or receiver health and reconciling any write outcome. |
Segment creation and signing-secret rotation are not client-idempotent. After a lost response, read current state before deciding on another write. Signing secrets cannot be read back. Label and static membership PUT/DELETE are repeatable desired-state operations.
Respect rate limits
Read RateLimit-Policy, RateLimit-Limit, RateLimit-Remaining, and RateLimit-Reset when returned. Reset is a wait in seconds, not an absolute timestamp. A request-rate 429 includes Retry-After. Safety services can return 503 instead of allowing unbounded traffic.
Current Developer API request budgets are:
| Request class | Burst | Sustained |
|---|---|---|
| Writes | 20 per second per key and per number | 600 per minute per key and per number; 1,200 per minute across the workspace. |
| Reads | 60 per second per key and per number | 3,000 per minute per key and per number; 6,000 per minute across the workspace. |
Additional network-level limits and media-download concurrency checks apply. These request budgets do not promise WhatsApp delivery throughput or override Meta quality and capacity limits. Follow the returned limiting headers when several controls apply.
Retry without duplicate messages
Retry safe reads with bounded exponential backoff and jitter. Respect Retry-After instead of retrying immediately.
The API supports Idempotency-Key on POST /v1/messages. Persist one key per logical send and reuse it with exactly the same recipient and content after an uncertain result. Reusing it for another request returns 409. Without a key, a timeout does not prove rejection; reconcile before sending again. See safe send retries.
For help, record the request time, route, status, stable error code, and returned X-Waaru-Request-Id if present. Share sensitive contact or conversation details only through the appropriate support flow, and never include the API key or signing secret.
Next: Receive and reconcile webhooks.
Shared request admission
Rotating or adding keys does not reset the shared number or workspace budgets above.
Before authentication, public API routes also share an ingress budget of 100/second and 3,000/minute per trusted client IPv4 address or IPv6 /64. Invalid credentials and denied scopes consume this budget. Limits are distributed fixed windows; honor Retry-After and use jittered backoff after 429. Unavailable safety checks fail closed with 503. These are technical admission limits, not a guarantee against every network attack.
Public errors omit traces, provider payloads and internal diagnostics.
For GET /v1/messages/changes, 410 cursor_expired requires a fresh bootstrap plus retained-history reconciliation. 400 invalid_cursor can also signal a wrong scope or signing-key change. Follow the recovery sequence; neither error authorizes skipping a gap or resending a message. Campaign filters bind their cursors; 404 campaign_not_found can mean the campaign belongs to another number or workspace.
Webhook observation
Enable explicit all-origin webhook observations, understand excluded origins and media deadlines, and preserve native reply authority.
API endpoint reference
Find the supported Waaru external API routes, required scopes, success responses, and links to message, conversation, pagination, and media examples.