# 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` | Client IP not in the allowed list |
| 401 | `SIGNATURE_REQUIRED` | Key requires HMAC but signature headers missing |
| 401 | `INVALID_SIGNATURE` | HMAC signature failed verification |
| 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 |

## Validation

| HTTP | Code | When |
| --- | --- | --- |
| 400 | `INVALID_BODY` | Malformed JSON body |
| 400 | `INVALID_PARAMS` | Invalid query or path parameters |
| 400 | `INVALID_TO` | Missing/empty recipients |
| 400 | `INVALID_MESSAGE` | Missing/empty message |
| 400 | `INVALID_SENDER` | Missing/empty sender_id |
| 400 | `TOO_MANY_RECIPIENTS` | Over 100 recipients |
| 400 | `INVALID_WORKSPACE_HEADER` | `X-Workspace-ID` is not a positive integer |
| 402 | `INSUFFICIENT_BALANCE` | Balance too low — body includes cost breakdown |

## 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`) |

Details and recommended client behavior: [rate limits](/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](/sms/send#errors) |

## 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 `402 INSUFFICIENT_BALANCE` and `credit_blocked` from
  [balance](/account/balance) before campaigns stall.
- **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.