Skip to content

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.

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.

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
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

Server errors use a generic description: An internal error occurred. Reference: <request id>. Provide the X-Request-ID header to support.

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.

  • Retry 429 (after a delay) and 500 DB_ERROR (reads: retry as is; sends: retry with the same Idempotency-Key and body). Do not blind-retry 4xx — fix the request instead.
  • Alert on credit_blocked from balance, and use DLRs for the final outcome of messages already accepted by the API.
  • Re-authenticate on 401 TOKEN_EXPIRED / INVALID_TOKEN if using JWT.
  • Log error_code + description + the request ID from your HTTP client for support escalations.