Skip to content
WhatIsUp.dev

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.

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).
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). Buckets are per-account and per-channel, not per key. Honour Retry-After.
worker_at_capacityNo worker has free slots right now — extremely rare; retry.
trial_expired402. Trial ended with no paid plan. Blocks sends, story posts, presence updates and channel creation; reads 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.

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.
media_fetch_failed400. We could not fetch your source_url (DNS, non-2xx, or timeout).
media_fetch_blocked400. source_url resolves to a private / loopback / link-local address.
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.