Error reference
Copy page
Application errors generally use this shape — status is false and error_code is
stable and machine-readable:
{ "status": false, "error_code": "UNAUTHORIZED", "description": "Human-readable message"}Match on error_code, never on description (wording may change).
Every application 401 is this JSON envelope with status: false and an
error_code.
Authentication & authorization
Section titled “Authentication & authorization”| HTTP | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED |
Missing or unrecognized credentials |
| 401 | INVALID_TOKEN |
JWT invalid, malformed or revoked |
| 401 | TOKEN_EXPIRED |
JWT past expiry |
| 401 | INVALID_API_KEY |
API key unknown or malformed (description invalid_api_key; Invalid permissions when the key’s stored permissions cannot be read) |
| 401 | API_KEY_INACTIVE |
API key disabled |
| 401 | API_KEY_EXPIRED |
API key expired |
| 401 | INVALID_CREDENTIALS |
Wrong username/password |
| 401 | USER_NOT_ACTIVE |
Account suspended or inactive |
| 401 | IP_NOT_ALLOWED |
JWT was issued with an IP restriction and the client IP is not allowed (API keys have no IP allowlist) |
| 401 | SIGNATURE_REQUIRED |
Key requires HMAC but signature headers missing |
| 401 | INVALID_SIGNATURE |
HMAC signature failed verification |
| 401 | API_KEY_REQUIRED |
Strict integration endpoint requires X-API-Key; JWT is not accepted |
| 403 | FORBIDDEN |
Authenticated, but route or permission denied |
| 403 | NO_SMS_ACCESS |
SMS is not set up for the workspace in use, or its SMS settings could not be loaded; no message is created |
| 403 | CONFIG_ERROR |
The workspace’s SMS settings are incomplete; no message is created — contact support. sk_test_ keys never get this error |
| 403 | CROSS_TENANT_WORKSPACE |
X-Workspace-ID names a workspace you don’t own (or deleted) |
| 403 | NO_MAIN_WORKSPACE |
No live main workspace — contact support |
| 403 | WORKSPACE_NOT_AVAILABLE |
Workspace deleted while the request was processed, or the integration key’s workspace is not available; no message is created |
| 403 | INTEGRATION_NOT_ENABLED |
Customer.io sending is not enabled yet for this account. Please ask 23 Telecom support to enable it. |
| 403 | INTEGRATION_DISABLED_BY_SUPPORT |
Customer.io sending is disabled for this workspace by 23 Telecom support. |
| 403 | INTEGRATION_KEY_NOT_ELIGIBLE |
This API key cannot send SMS through Customer.io. Use a live API key (sk_prod_) with the SMS send permission. |
| 403 | SANDBOX_ROUTE_FORBIDDEN |
Sandbox (sk_test_) key used on an endpoint without a sandbox branch — only send, send-individual, integration send, status and messages accept test keys |
| 403 | CORS_ROUTE_NOT_ALLOWED |
Browser preflight from the docs playground for a route without a sandbox branch |
| 403 | LIVE_KEY_FROM_PLAYGROUND |
Live (sk_prod_) key sent from the docs playground origin — use a sk_test_ key there |
| 403 | PERMISSION_DENIED |
A team-member portal session was refused by a route’s own permission check. Most member denials get SECTION_ACCESS_DENIED first; API keys missing a permission get FORBIDDEN with required_permission |
| 403 | ADMIN_ACCESS_REQUIRED |
An admin-only route was called without an admin session |
| 500 | PERMISSION_CHECK_FAILED |
Permissions could not be read. Nothing changed; retry |
| 409 | EMAIL_ALREADY_EXISTS |
A team invite or email change uses an address that is already in use |
Additional authentication and workspace errors:
| HTTP | Code | When |
|---|---|---|
| 400 | AMBIGUOUS_AUTHENTICATION |
API key and session cookie supplied together |
| 401 | API_KEY_OWNER_INACTIVE |
Key owner is inactive |
| 401 | CROSS_TENANT_WORKSPACE |
API key bound to a deleted workspace |
| 403 | NO_ACCESSIBLE_WORKSPACE |
No workspace is accessible to the member |
| 403 | CSRF_FAILED |
Cookie-authenticated write lacks a valid CSRF token |
| 403 | API_KEY_CREATOR_INACTIVE |
Key creator is inactive |
| 403 | SECTION_ACCESS_DENIED |
Key creator lacks section access |
| 403 | WORKSPACE_ACCESS_RESTRICTED |
Selected-workspace access cannot filter this view; ask the account owner |
An API key with no live workspace binding returns 401 CROSS_TENANT_WORKSPACE with description invalid_api_key.
Validation
Section titled “Validation”| HTTP | Code | When |
|---|---|---|
| 400 | INVALID_ID |
Invalid message ID |
| 400 | INVALID_BODY |
Malformed JSON body |
| 400 | INVALID_PARAMS |
Invalid query or path parameters |
| 400 | INVALID_TO |
Missing, malformed or overlong recipient phone number |
| 400 | INVALID_MESSAGE |
Missing/empty message, oversized body or over-10-segment SMS text |
| 400 | INVALID_SENDER |
Missing or invalid sender_id (3–11 ASCII alphanumeric characters, not starting with a number) |
| 400 | INVALID_SENDER_ID |
Strict integration endpoint received an invalid sender_id |
| 400 | TOO_MANY_RECIPIENTS |
Over 100 recipients |
| 400 | INVALID_MESSAGES |
/sms/send-individual body has no messages[] entries |
| 400 | TOO_MANY_MESSAGES |
/sms/send-individual has over 1000 entries |
| 400 | IDEMPOTENCY_KEY_REQUIRED |
/sms/send-individual missing required Idempotency-Key header |
| 400 | IDEMPOTENCY_KEY_REQUIRED |
Strict integration request is missing both Idempotency-Key and X-CIO-Idempotency-Key; exactly one is required |
| 400 | MULTIPLE_IDEMPOTENCY_KEYS |
Strict integration request sent both sources or repeated either header |
| 400 | INVALID_IDEMPOTENCY_HEADER |
Strict endpoint received a legacy or unsupported idempotency header |
| 400 | INVALID_IDEMPOTENCY_KEY |
Standard Idempotency-Key is over 128 characters/uses unsupported characters, or Customer.io’s key is not 1–1024 visible ASCII bytes |
| 400 | INVALID_WORKSPACE_HEADER |
X-Workspace-ID is not a positive integer |
| 409 | IDEMPOTENCY_KEY_REUSED |
Matched by a fingerprint of the normalized send (trimmed recipient numbers, message text and sender ID, in order), not by the raw JSON |
| 409 | IDEMPOTENCY_REQUEST_IN_PROGRESS |
Same Idempotency-Key is still processing, or an earlier attempt could not release it (released at the latest 15 minutes after the first attempt); the integration endpoint sets Retry-After: 1 |
| 413 | REQUEST_TOO_LARGE |
Strict integration wire body exceeds 32 KiB |
| 415 | UNSUPPORTED_MEDIA_TYPE |
Strict integration request is not application/json, or any JSON endpoint while strict media-type checking is enabled |
| 422 | URL_TRACKING_INVALID |
URL tracking configuration is invalid |
| 422 | DESTINATION_BLOCKED |
Destination is blocked; no SMS or completed claim was created |
Rate limiting
Section titled “Rate limiting”| HTTP | Code | When |
|---|---|---|
| 429 | RATE_LIMIT_EXCEEDED |
Per-IP, per-account, or integration budget limit; includes Retry-After, X-RateLimit-Limit, and X-RateLimit-Burst. Suspension uses Retry-After: 60 and limit/burst 0; contact support instead of retrying tightly. |
| 429 | SANDBOX_QUOTA_EXCEEDED |
Sandbox daily recipient quota reached; Retry-After counts down to 00:00 UTC |
| 429 | TOO_MANY_ATTEMPTS |
Login/2FA or API-key authentication failure limit (includes Retry-After) |
| 429 | ACCOUNT_TEMPORARILY_LOCKED |
Account temporarily locked; retry after Retry-After |
| 503 | AUTH_PROTECTION_UNAVAILABLE |
Brute-force protection store is temporarily unavailable for login, 2FA or API-key authentication; the request was refused, not bypassed. Retry after Retry-After: 1 |
| 503 | RATE_LIMIT_UNAVAILABLE |
Per-account limiter is temporarily unavailable; retry with the same idempotency key for /sms/send-individual |
Details and recommended client behavior: rate limits.
| HTTP | Code | When |
|---|---|---|
| 403 | FORBIDDEN |
API key lacks an endpoint permission. Read required_permission, then enable it in Portal → API Keys → Edit permissions. |
| 403 | CROSS_SITE_LOGIN |
Browser login from another site, including the docs playground, is refused; sign in through the portal. |
| 403 | CORS_ORIGIN_NOT_ALLOWED |
Browser preflight (OPTIONS) from an origin that is not allowed; requests without an Origin header never get it |
| 404 | NOT_FOUND |
Unknown or expired sandbox test_ message ID (retained 24 hours); use the sk_test_ key that sent it |
| 404 | NOT_FOUND |
A test_ ID was queried with a live key; use its sk_test_ key |
| 404 | NOT_FOUND |
Live ID is not in this workspace; live message IDs start with api_ |
| 405 | METHOD_NOT_ALLOWED |
The Allow header names supported methods (for example, GET /sms/send returns Allow: POST) |
| 500 | INTERNAL_ERROR |
Generic server error, for example a failed message status lookup. Retry; for sends, use the same Idempotency-Key and body |
| 500 | DB_ERROR |
A database operation failed. On read endpoints (statistics and message lists) nothing was changed; retry. On sends the batch is usually rolled back, but an error at the final commit can leave it saved and later sent: retry with the same Idempotency-Key and body, or, without a key, check GET /sms/messages first (details) |
| 500 | IDEMPOTENCY_ERROR |
The server could not reserve an idempotency key; retry with the same key |
| 500 | INTERNAL_SERVER_ERROR |
The request failed unexpectedly (the server recovered from a crash); the body has only the generic description. Retry; for sends, use the same Idempotency-Key and body |
| 503 | WORKSPACE_ACCESS_UNAVAILABLE |
Workspace access could not be validated; retry after Retry-After: 1 |
| 503 | API_KEY_VALIDATION_UNAVAILABLE |
API key validation unavailable; retry after Retry-After: 1 |
| 503 | REPLAY_PROTECTION_UNAVAILABLE |
Replay protection unavailable for a write; retry after Retry-After: 1 |
| 503 | SECTION_ACCESS_UNAVAILABLE |
Section access could not be checked |
| 503 | PASSWORD_HASH_BUSY |
Password verification is busy; retry after Retry-After: 1 |
| 503 | URL_TRACKING_UNAVAILABLE |
URL tracking temporarily unavailable |
| 503 | IDEMPOTENCY_ERROR |
Integration send only: a refused attempt’s key could not be released; no SMS was accepted. For up to 15 minutes a retry with this key may return 409 IDEMPOTENCY_REQUEST_IN_PROGRESS |
| 503 | SANDBOX_BUSY |
Sandbox capacity is full; retry after Retry-After (5 s) |
| 503 | SANDBOX_UNAVAILABLE |
Sandbox simulator is not enabled for sk_test_ sends |
| 503 | QUEUE_UNAVAILABLE |
SMS queue temporarily unavailable; retry shortly |
| 503 | INTEGRATION_GATE_UNAVAILABLE |
Strict live access could not be verified; retry with the same key |
5xx bodies
Section titled “5xx bodies”Server errors use a generic description: An internal error occurred. Reference: <request id>. Provide the X-Request-ID header to support.
HTTP 200 with status: false
Section titled “HTTP 200 with status: false”An account being configured can return HTTP 200 with {"status":false,"error":"Account is in setup process","message":"Your account is being configured. Please contact support."}. See balance for account details.
Handling errors well
Section titled “Handling errors well”- Retry
429(after a delay) and500 DB_ERROR(reads: retry as is; sends: retry with the sameIdempotency-Keyand body). Do not blind-retry4xx— fix the request instead. - Alert on
credit_blockedfrom balance, and use DLRs for the final outcome of messages already accepted by the API. - Re-authenticate on
401 TOKEN_EXPIRED/INVALID_TOKENif using JWT. - Log
error_code+description+ the request ID from your HTTP client for support escalations.
