Every error response carries a JSON body with a stable code, a possibly-rewordable message, and optional details. Branch on code, not on message.
Body shape
json
{
"error": {
"code": "PINNED_SENDER_CONFLICT",
"message": "This API key is pinned to a specific sender; the hint requested a different one.",
"details": {
"pinnedNumberId": "num-a-uuid",
"hintedNumberId": "num-b-uuid"
}
}
}
Auth & generic
HTTP
Code
When
400
VALIDATION_ERROR
Body doesn't match the schema. details.issues lists path + message.
400
BAD_REQUEST
Validation outside zod (e.g. malformed UUID in the path).
401
UNAUTHORIZED
JWT missing or invalid.
401
INVALID_API_KEY
X-API-Key missing, malformed, or revoked.
401
MEDIA_ACCESS_TOKEN_INVALID
Media access token (?token=) bad / expired (10-min TTL).
404
NOT_FOUND
Resource missing or not owned by this workspace.
501
NOT_IMPLEMENTED
Feature flag off for this tier (e.g. not yet enabled).
Sender / rotation
HTTP
Code
When
400
AMBIGUOUS_HINT
Both sender.id and sender.label were set.
404
SENDER_NOT_FOUND
Hint refers to an ID/label not owned by the workspace.
503
SENDER_UNHEALTHY
The hinted sender is banned/disconnected right now.
409
PINNED_SENDER_CONFLICT
Pinned key + hint pointing elsewhere.
503
PINNED_SENDER_UNHEALTHY
Pin is unhealthy + onPinFailure=strict.
503
NO_HEALTHY_NUMBERS
Pool empty. Operator gets an alert in parallel.
Quota & rate-limit
HTTP
Code
When
429
QUOTA_EXCEEDED
Monthly plan exhausted. Upgrade tier or buy the extra-messages add-on.
429
—
Per-minute rate limit (Retry-After header set).
429
TIER_LIMIT_REACHED
Action needs a higher tier (e.g. adding a number above the cap).
Messages / upload
HTTP
Code
When
409
CONFLICT
storageKey already consumed (used by a prior send) or upload expired.
500
MESSAGE_SEND_FAILED
Gateway failed to deliver downstream. Check the message log (e.g. recipient not on WhatsApp).
503
UPSTREAM_ERROR
External provider (payment / mail) is currently failing.