API error codes
Every error response uses the v1 envelope: object, code, message, and request_id. Each code includes a stable docs_url pointing here.
Error catalog
| Code | HTTP | Retryable | Meaning | Docs |
|---|---|---|---|---|
| validation_failed | 400 | no | Request body or query parameters failed validation. Inspect `details` for per-field errors. | Docs |
| missing_api_key | 401 | no | No API key was supplied. Send `Authorization: Bearer <key>` or `X-Integration-Key`. | Docs |
| insufficient_scope | 403 | no | The key authenticated successfully but does not hold the scope required for this endpoint. | Docs |
| invalid_api_key | 403 | no | The supplied key is unknown, revoked, or expired. | Docs |
| scope_invalid | 403 | no | The key's scope_level does not permit this resource. Personal keys cannot access organization-only resources (DNC, org-level usage, raw org number lists), and organization keys must be issued by a user who still holds the owner role. | Docs |
| not_found | 404 | no | Resource not found, or visible only to a different organization. Cross-tenant access never returns 200 or 403. | Docs |
| idempotency_in_progress | 409 | yes | A request with the same `Idempotency-Key` is already in flight. Retry shortly. | Docs |
| idempotency_key_reused | 409 | no | The same `Idempotency-Key` was reused with a different request body. Use a fresh key for distinct requests. | Docs |
| approval_required | 422 | no | Organization is not yet approved for messaging. Complete onboarding/registration before sending. | Docs |
| dnc_blocked | 422 | no | Recipient is blocked by DNC settings; the send was not enqueued. | Docs |
| insufficient_credits | 422 | no | Credit balance is too low to cover this send. Top up before retrying. | Docs |
| opted_out | 422 | no | Recipient has opted out (replied STOP). The send cannot complete. | Docs |
| rate_limited | 429 | yes | Per-IP or per-key rate limit exceeded. Honor the `Retry-After` header before retrying. | Docs |
| internal_error | 500 | no | Unexpected server error. Do not tight-loop retry; use exponential backoff if you retry at all, and contact support with the `request_id` if it persists. | Docs |
