Errors
RFC 9457 problem responses and every error code the API returns.
Every failed API call returns application/problem+json as defined by RFC 9457. The type links to the section of this page for the status, title and code name the error, and detail says what to do about it. Quote request_id when you contact jonathan@getwabi.dev; you can also send your own in X-Request-Id.
403 Forbidden
{ "type": "https://getwabi.dev/docs/errors#403", "title": "scope_missing", "status": 403, "detail": "simulate_inbound_message needs the sandbox:write scope; this API key has messages:read.", "request_id": "5b0e3c7a-…", "code": "scope_missing", "required_scope": "sandbox:write", "scopes": [ "messages:read" ]}A 401 also carries WWW-Authenticate: Bearer resource_metadata=…, which MCP clients follow to sign in with OAuth. Over MCP, the same errors come back as tool errors with the code and message.
400 Bad request
| Code | Meaning |
|---|---|
| invalid_json | The body (or ?input for GET) is not valid JSON. |
| invalid_input | The input failed the op's schema. `issues` lists each problem with its path. |
| invalid_slug | The name produced no usable slug. Use letters, digits and dashes. |
| confirm_mismatch | A destructive op needs its target's name typed back in `confirm`, and it did not match. |
| principal_required | Name exactly one team or person to share with or remove. |
| same_person | The op cannot target yourself (for example transferring ownership to yourself). |
| no_person | No member of this organization has that email address or id. |
| invalid_grant | CLI sign-in: the device code is unknown or was already used. Run login again. |
| expired_token | CLI sign-in: the device code expired before it was approved. Run login again. |
| access_denied | CLI sign-in: the person declined the request in the browser. |
401 Unauthorized
| Code | Meaning |
|---|---|
| unauthorized | No credential. Send `Authorization: Bearer wabi_…`. |
| invalid_key | The API key does not exist or was revoked. |
| expired_key | The API key passed its expiry date. Create a new one. |
| invalid_token | The OAuth access token is invalid or expired. Refresh it and retry. |
402 Payment required
| Code | Meaning |
|---|---|
| payment_failed | Stripe declined the charge for a plan change. Ask a human to update the card (get_billing_link) or pay through create_checkout_link. |
403 Forbidden
| Code | Meaning |
|---|---|
| forbidden | The op needs a higher role than this credential has. `required_role` names it. |
| scope_missing | The API key's scopes do not cover this op. `required_scope` names the scope it needs. |
| scope_escalation | A scoped key tried to create a key with wider scopes than its own. |
| dashboard_only | A person does this in the dashboard (inviting members, changing roles); it is not available to keys. |
| person_required | Only a signed-in person can do this, not an API key. |
| no_organization | The signed-in user is not a member of the requested organization (or of any). |
404 Not found
| Code | Meaning |
|---|---|
| unknown_op | No op has that name. `GET /api/v1` lists them. |
| not_found | The account, contact, template, agent or other record is not in this organization, or you cannot see it. |
405 Method not allowed
| Code | Meaning |
|---|---|
| method_not_allowed | A write op was sent with GET. Use POST. |
409 Conflict
| Code | Meaning |
|---|---|
| organization_exists | An organization with that slug already exists. |
| team_exists | A team with that name already exists in this organization. |
| already_member | That person is already a member of the organization. |
| last_owner | An organization keeps at least one owner: the last owner cannot leave or be demoted. |
| sandbox_not_provisioned | The organization's sandbox does not exist yet. Call get_sandbox first; it creates it. |
| already_subscribed | The organization is already on that plan and interval. get_billing shows the subscription. |
| subscription_active | The organization already pays for a plan; change it with subscribe or the billing portal. |
| no_subscription | There is no paid subscription to cancel or resume. |
| checkout_incomplete | The Stripe Checkout session was not paid yet. |
| already_claimed | That Checkout session is already attached to an organization. |
413 Content too large
| Code | Meaning |
|---|---|
| context_too_large | The context attached to an issue report is over 128 KB. |
428 Precondition required
| Code | Meaning |
|---|---|
| authorization_pending | CLI sign-in: the person has not approved the code yet. Poll again after `interval` seconds. |
429 Too many requests
| Code | Meaning |
|---|---|
| rate_limited | Too many requests. Wait for `Retry-After` seconds and retry. |
500 Internal error
| Code | Meaning |
|---|---|
| internal_error | Something failed on our side. Retry, and quote `request_id` if it persists. |
503 Service unavailable
| Code | Meaning |
|---|---|
| billing_unavailable | Billing is not configured on this server yet. Email support to subscribe. |
Retrying
- 4xx errors will fail the same way again until you change the request.
- 429 and 5xx errors are safe to retry with backoff. A write may have taken effect before a 5xx: read the record back before sending it again.
- A 402 carries the paid plans and a
checkout_url, so an agent can hand a person the fix in one step.
Meta error codes
When Meta refuses or fails a message, the message's timeline and its message.failed event carry Meta's own code. The sandbox returns the same codes. The common ones:
131047: Free-form message outside the 24-hour customer service window. Send an approved template instead.131026: Undeliverable: the number is not on WhatsApp, blocked the business, or Meta held the message back.130429, 131056: Rate limited by Meta (throughput or per-recipient pair rate). Retried with backoff.132000–132999: Template problem: not approved in that language, paused, disabled, or the parameters do not match.190: The WhatsApp Business Account's access token expired or was revoked. Reconnect the account.