Skip to content
WhatIsUp.dev
Esta página está disponible solo en inglés por ahora.

Errors

Every 4xx and 5xx response body uses the same envelope:

{
  "error": {
    "code": "channel_unavailable",
    "message": "Channel is not connected. Re-pair to continue.",
    "details": { "channel_id": "..." }
  },
  "request_id": "req-3f7a1c02"
}

Match on code — it's the stable contract. message is human-readable and may change between releases. details is optional and code-specific.

The full code set ships in the Zod contract ERROR_CODES. The table below is canonical.

Auth / identity

CodeWhen you'll see it
auth_missingNo Authorization header sent.
auth_invalidBearer token doesn't match any active key.
auth_malformedHeader present but not Bearer …, or token format wrong.
auth_expiredKey or token is past its expiry. (Revocation is auth_revoked.)
auth_no_emailDashboard session token has no verified email.
auth_revokedKey existed and has been revoked.
auth_backend_unavailable503, not 401. Your token was valid; our user lookup failed. Retry, and keep the session — do not sign the user out.
email_not_verified403. Dashboard sign-in only: first sign-in with an email your sign-in provider has not verified. Open the verification link, then sign in again. API keys never see this.
account_suspended403. The account has been suspended by our team. Every API key and dashboard call is refused until it is restored — retrying won't help. Your account page and support tickets keep working; contact support.

Channel lifecycle

CodeWhen you'll see it
channel_not_foundChannel UUID doesn't exist (or belongs to another customer).
channel_access_deniedChannel-scoped API key being used against a different channel.
insufficient_scope403. The key's scopes don't cover this route. details carries required and granted. An empty scopes array is unrestricted and never triggers this.
channel_unavailableChannel is in a terminal state (logged_out, stopped_by_user, disabled_by_admin, error, failed), or has never been paired (pending / qr with details.paired: false — scan the QR code first).
idempotency_key_in_use409. A send with this Idempotency-Key is still running. Retry after Retry-After to get its result.
idempotency_key_reused422. The Idempotency-Key was already used for a different request. Nothing was sent — use a new key per message.
channel_scoped_keyKey is scoped to a single channel but a multi-channel route was called.
invalid_lifecycle_transitionTried to connect an already-connected channel, etc.
pair_code_unavailableChannel state doesn't allow generating a pair code right now.
pair_code_failedUpstream rejected the pair code request.
no_qrQR not yet ready (channel hasn't booted), or no QR for this state.
not_disabledTried to re-enable a channel that wasn't admin-disabled.
concurrent_modificationTwo writes collided on the same row; retry your read-modify-write.

Plan / quota

CodeWhen you'll see it
plan_channel_limitPlan's channel cap reached. Upgrade or delete an existing channel.
rate_limitedA token bucket is exhausted. X-RateLimit-Scope says which (customer, channel or ip; ip also guards public media downloads). Buckets are per-account and per-channel, not per key, and shared across all our servers. Honour Retry-After.
worker_at_capacityNo worker has free slots right now — extremely rare; retry.
trial_expired402. Trial ended with no paid plan. Blocks every action that reaches WhatsApp — sends, story posts, presence, groups, communities, newsletters, labels, block/unblock, call reject, profile edits, connect and pair-code — plus channel creation; reads, disconnect and logout keep working.
payment_overdue402. Account is past due. Same blocking as above.
sse_connection_limit429. More than 10 concurrent /v1/events streams on the account.

Channel proxy

CodeWhen you'll see it
proxy_invalid400. Not a usable proxy URL — message says which part (scheme, host, port, credentials).
proxy_blocked400. The proxy host is a private, loopback or internal address.
proxy_unreachable422. The test through the proxy failed (details.check.error says why), or the saved proxy can't be read. Nothing was saved.
proxy_not_configured404. POST /v1/channels/:id/proxy/test with no url, on a channel without a saved proxy.
proxy_unavailable503. Proxies can't be saved right now.
proxy_test_limit429. More than 10 proxy tests in a minute on your account. Honour Retry-After — nothing else is throttled.

Webhook + media

CodeWhen you'll see it
webhook_endpoint_not_foundEndpoint UUID doesn't exist.
media_gone410. Token is valid but the bytes aged past retention. Permanent — nothing to retry.
media_token_expired401. The signed media token has expired.
media_token_tampered401. Media token signature does not verify.
media_token_malformed401. Media token is not parseable.
media_too_large413. Media exceeds the 25 MB ceiling (pre-upload), or 64 MB for a send's media_url.
media_fetch_failed400 on pre-upload: we could not fetch your source_url (DNS, non-2xx, or timeout). 422 on a send, story or picture: your media_url answered 4xx, redirected more than 3 times, or was empty. A timeout or 5xx on a send is queued and retried instead.
media_fetch_blocked400. source_url / media_url resolves to a private / loopback / link-local / internal address — checked on every redirect hop.
message_not_found404. No message with that id on that channel.
chat_not_found404. No such chat on that channel.
contact_not_found404. No such contact on that channel.

Generic

CodeWhen you'll see it
invalid_requestBody or query failed Zod validation. details contains the issue list.
not_foundGeneric 404.
bad_requestGeneric 400 where no narrower code fits.
no_opThe mutation was a no-op (already in the target state).
otherCatch-all for upstream-provider errors.
internal_errorUnhandled server-side exception. We're alerted; request_id is the trace key.
undeliverable_recipient400. to is a @lid, has more than 15 digits, is non-numeric, has an unknown suffix, or WhatsApp says the number is not registered. details.to carries the value.
not_implemented501. The endpoint exists but is a declared stub (GET /v1/channels/:id/stories, GET /v1/channels/:id/calls).
payload_too_large413. Request body exceeded the server limit.

This list is not closed. Match on code with a default branch — new codes ship without a major version bump, and a handful of routes emit codes outside the core set documented here.

Best practices

  • Always log request_id. Customer support uses it to find the exact request in our traces.
  • Match on code, never on message. The messages are tuned for human readability and shift over time.
  • Treat unknown codes as transient. New codes get added; assume retryable until the docs are updated.
  • For 429, honor Retry-After instead of guessing backoff.