Skip to content
WaaruDocs

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_pagination or invalid_cursor: Use only the supported list parameters and the returned opaque cursor.
  • 401 unauthorized or invalid_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, or media_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 respect retryAfterSeconds when 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_approved or quality_blocked: Check template approval and current sender/template quality. Do not bypass admission with another key.
  • 409 contact_opted_out, contact_instance_restricted, or contact_marketing_restricted: Respect the restriction. Review its exact scope in the dashboard.
  • 429: Reduce request volume and wait for Retry-After when supplied. For media_concurrency_exceeded, also close active downloads.
  • 503 backlog_full: Admission is temporarily at capacity. Delay further sends.
  • Other 5xx or 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 codeNext action
400 invalid_input or invalid_webhook_targetCorrect 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_foundUse an existing resource in this workspace.
409 revision_conflictRead webhook settings again and reconcile the latest revision before writing.
409 idempotency_conflict or idempotency_result_unavailableReconcile the original send. Do not generate a replacement key automatically.
422 template_action_unavailableRefresh the template detail and correct the Flow launch descriptor and inputs.
502 template_provider_unavailable or webhook_test_failedRetry 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 classBurstSustained
Writes20 per second per key and per number600 per minute per key and per number; 1,200 per minute across the workspace.
Reads60 per second per key and per number3,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.

On this page