# Error reference
> Complete 23 Telecom API error code reference — authentication, validation, rate limiting and server errors with HTTP statuses and stable machine-readable error_code values.
> Source: https://docs.23telecom.co.uk/reference/errors/
Instructions for LLMs: This is one page of the 23 Telecom messaging API docs
(SMS today; more channels planned). Base URL: https://restlink23telecom.com/api/v1,
auth via the X-API-Key header. Match errors on the error_code field, never on
description text. Full docs: https://docs.23telecom.co.uk/llms-full.txt · Schemas: https://docs.23telecom.co.uk/openapi.yaml
All errors share one shape — `status` is always `false`, `error_code` is
stable and machine-readable:
```json
{
"status": false,
"error_code": "UNAUTHORIZED",
"description": "Human-readable message"
}
```
Match on `error_code`, never on `description` (wording may change).
## 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 not found or hash mismatch |
| 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 not configured on the account |
| 403 | `CONFIG_ERROR` | Credentials incomplete — contact support |
| 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` | Target workspace deleted — use a live workspace |
| 403 | `INTEGRATION_NOT_ENABLED` | Live strict integration sending is not enabled for this workspace |
## Validation
| HTTP | Code | When |
| --- | --- | --- |
| 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` | Same `Idempotency-Key` was used with a different request body |
| 409 | `IDEMPOTENCY_REQUEST_IN_PROGRESS` | Same `Idempotency-Key` is still processing; retry later |
| 413 | `REQUEST_TOO_LARGE` | Strict integration wire body exceeds 32 KiB |
| 415 | `UNSUPPORTED_MEDIA_TYPE` | Strict integration request is not `application/json` |
| 422 | `DESTINATION_BLOCKED` | Destination is blocked; no SMS or completed claim was created |
## Rate limiting
| HTTP | Code | When |
| --- | --- | --- |
| 429 | `RATE_LIMIT_EXCEEDED` | Per-account rate limit hit |
| 429 | `TOO_MANY_ATTEMPTS` | Login/2FA brute-force limit (includes `Retry-After`) |
| 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](https://docs.23telecom.co.uk/reference/rate-limits).
## Other
| HTTP | Code | When |
| --- | --- | --- |
| 404 | `NOT_FOUND` | Message or resource not found |
| 500 | `INTERNAL_ERROR` | Server-side error |
| 500 | `DB_ERROR` | Send request could not be queued — atomic rollback, [safe to retry](https://docs.23telecom.co.uk/sms/send#errors) |
| 500 | `IDEMPOTENCY_ERROR` | The server could not reserve an idempotency key; retry with the same key |
| 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 |
## Handling errors well
- **Retry** `429` (after a delay) and `500 DB_ERROR` (idempotent by design).
Do not blind-retry `4xx` — fix the request instead.
- **Alert** on `credit_blocked` from [balance](https://docs.23telecom.co.uk/account/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.
All errors share one shape — status is always false, 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).