> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.teekrr.com/llms.txt.
> For full documentation content, see https://docs.teekrr.com/llms-full.txt.

# Error codes

All error responses share the envelope `{ message, errors? }`. The `errors` array is only present on `400 Bad Request` validation failures.

## HTTP status reference

| Status | When                                           | Example `message`                                                                      |
| ------ | ---------------------------------------------- | -------------------------------------------------------------------------------------- |
| `400`  | Body validation failed                         | `Validation failed` (see `errors[]` for fields)                                        |
| `400`  | File upload missing or wrong MIME              | `No file uploaded` · `Only JPEG, PNG, WebP, MP4, and PDF files are supported`          |
| `401`  | No / malformed `Authorization` header          | `Missing or invalid Authorization header`                                              |
| `401`  | Bearer token doesn't match any active key      | `Invalid API key`                                                                      |
| `401`  | Key was revoked from the dashboard             | `API key has been revoked`                                                             |
| `401`  | Key has expired                                | `API key has expired`                                                                  |
| `402`  | Prepaid balance below required cost            | `Insufficient credit. Required: RMx.xx, available: RMy.yy`                             |
| `402`  | Postpaid spending cap exceeded                 | `Spending cap exceeded. Accrued: RM…, this broadcast: RM…, cap: RM…`                   |
| `403`  | Caller IP not in API key whitelist             | `Request IP is not in the API key whitelist`                                           |
| `403`  | Channel subscription inactive                  | `Client does not have an active SMS channel subscription`                              |
| `403`  | Same for WhatsApp                              | `Client does not have an active WhatsApp channel subscription`                         |
| `404`  | SMS keyword not found                          | `Keyword not found`                                                                    |
| `404`  | Template not found by name                     | `Template not found`                                                                   |
| `404`  | WhatsApp template not found                    | `WhatsApp template not found`                                                          |
| `404`  | Client has no WABA + platform-default disabled | `No active WhatsApp configuration found for this client`                               |
| `422`  | WhatsApp template not yet approved             | `Template is not approved (current status: pending)`                                   |
| `422`  | META rejected the test send                    | `Test send failed (<META code>): <reason>`                                             |
| `429`  | Per-channel per-client rate limit hit          | `Too many requests, please try again later`                                            |
| `500`  | Unexpected server error                        | `Unexpected error` (no further detail leaked)                                          |
| `500`  | SQS enqueue failed after DB write              | `Failed to enqueue broadcast — messages marked failed` (credit released automatically) |

## Validation error shape

```json
{
  "message": "Validation failed",
  "errors": [
    { "field": "campaignName",  "message": "Campaign name is required" },
    { "field": "recipients.0",  "message": "Must be a valid phone number (e.g. +60123456789)" },
    { "field": "scheduledAt",   "message": "scheduledAt is required for schedule broadcast" }
  ]
}
```

The `field` is a dotted JSON path. Array indices appear as numeric segments (`recipients.0`).

## Rate-limit headers

When you hit the per-client rate limit, the `429` response includes a `Retry-After` header (seconds until the limiter resets). Use it to back off rather than retrying immediately.

## Credit safety on enqueue failure

If the API has already debited credit and then the SQS enqueue fails, Teekrr **automatically releases** the reserved credit and marks the messages as `failed`. You'll see `500 — Failed to enqueue broadcast — messages marked failed` in this case. No manual ledger correction is needed on your side.