error string. Match on the string, not on the HTTP status: two 429 codes and several 403 codes mean different things. Some errors add fields (status, message, balance_halalas, price_halalas, provider_code, fields).
{ "error": "insufficient_credits", "message": "Insufficient credits", "balance_halalas": 0, "price_halalas": 9 }
X-Request-Id header. Quote it when you contact support.
Shared by every endpoint
Checked in this order, before the endpoint’s own logic.| HTTP | error | Meaning |
|---|---|---|
| 429 | too_many_requests | Per-key rate limit exceeded (120 requests per minute, fixed 60-second window). Checked before authentication. |
| 401 | unauthorized | Missing, unknown, revoked or mode-mismatched key, or an unknown or suspended partner. |
| 401 | key_expired | The key’s expires_at has passed. Checked right after the key is recognised. |
| 403 | ip_not_allowed | The caller’s IP is outside the key’s ip_allowlist. |
| 403 | insufficient_scope | A check-scoped key called anything but check or GET /v1/verify/{id}. |
| 400 | service_required | A partner key with no application (or service) in the request. |
| 404 | service_not_found | A partner key, but application does not match any of that partner’s provisioned applications. |
| 403 | account_not_active | The application is not approved yet, the account is banned, or a partner disabled the application. |
| 400 | invalid_json | The request body is not valid JSON. |
| 400 | invalid_request | A required field is missing: to on start, id or code on check. |
| 500 | internal_error | An unhandled server error. Quote the X-Request-Id. |
Verify
| HTTP | error | Endpoints | Meaning |
|---|---|---|---|
| 422 | unsupported_channel | start | Unknown channel, or a known channel that is not live over this API. |
| 422 | sandbox_unsupported_channel | start | A sandbox key on a channel that cannot carry the [TEST] stamp (WhatsApp). |
| 422 | invalid_destination | start | Not an accepted Saudi mobile format. See Destinations. |
| 422 | sandbox_unverified_destination | start | Sandbox key, and the destination is not on the account’s proven list. |
| 429 | sandbox_quota_exceeded | start | The application’s lifetime sandbox cap of 10 sends is reached. |
| 429 | rate_limited | start, resend | The per-destination hourly cap is reached. Same status as too_many_requests, different meaning. |
| 429 | ip_rate_limited | start, resend | The client_ip you passed sent more starts this hour than max_per_ip_per_hour allows. |
| 429 | spend_cap_reached | start, resend | Today’s charges plus this send would pass daily_spend_cap_halalas. |
| 403 | application_paused | start, resend | Auto-paused by the protection guard after a spike in new destinations. The owner resumes it from the dashboard. |
| 402 | insufficient_credits | start, resend | The prepaid balance is below the channel price. Nothing was sent. |
| 502 | send_failed | start, resend | The delivery provider failed. The charge is refunded automatically; retry with the same Idempotency-Key. |
| 404 | not_found | check, status, resend, cancel | Unknown id, or an id that belongs to another account or application. |
| 409 | not_resendable | resend | Already verified, failed, expired or canceled. The body carries the current status. |
| 409 | not_cancelable | cancel | Already in a terminal state. The body carries the current status. |
| 429 | resend_limit_reached | resend | This verification already used its 3 resends. |
| HTTP | error | Meaning |
|---|---|---|
| 403 | live_key_required | A sandbox key. WhatsApp has no sandbox; use the application’s live key. |
| 403 | whatsapp_not_enabled | WhatsApp is turned off for this application. Contact support if that is a mistake. |
| 409 | no_whatsapp_number | No WhatsApp number is connected to this application yet. Connect one from the application’s WhatsApp tab. |
| 409 | number_disconnected | The number is attached but Meta does not report it as connected right now. The dashboard shows the number’s health. |
| 422 | invalid_destination | Not an accepted Saudi mobile format. |
| 422 | unknown_template | No template with that name (and language, when given) on the number’s WhatsApp Business Account. GET /v1/whatsapp/templates lists what exists. |
| 422 | template_not_approved | The template exists but Meta has not approved it. message carries Meta’s status. |
| 422 | template_category_not_allowed | An authentication template. One-time codes go through Verify. |
| 422 | unsupported_template | The template needs a media header, a non-text button parameter, or mixes named and positional placeholders. message says which. |
| 422 | invalid_params | A placeholder value is missing, empty, unexpected, too long or contains a control character, or the template exists in several languages and lang was not given. message names the placeholder. |
| 402 | insufficient_credits | The balance is below the WhatsApp message fee. The body adds balance_halalas and price_halalas. |
| 502 | send_failed | Meta refused the send or is unreachable. The body adds provider_code and message; the fee is refunded. |
| 503 | templates_unavailable | Meta did not answer the read of the template list. Nothing was sent or charged; retry shortly. |