> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.telekesher.dev/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.telekesher.dev/_mcp/server.

# Error handling

API errors use a consistent JSON format with one of the HTTP status codes documented below. An unknown path or unsupported method returns plain text instead, as described in the next section.

## Error Response Format

An enveloped error response follows this structure:

```json
{
  "success": false,
  "error": "Human-readable error message",
  "code": "invalid_request"
}
```

**Two responses are not JSON.** If the path you requested does not exist, or exists but does not accept the method you used, you get a plain-text `404 Not Found` or `405 Method Not Allowed` instead of the envelope below — so do not assume every error body parses as JSON. A `404` about a resource, such as `Session not found` or `Room not found`, does use the envelope.

* `error` — a human-readable, English-only message whose phrasing may change. Use it for display and diagnostics.
* `code` — a stable, machine-readable class from a small closed set (see [Error Codes](#error-codes)). Branch on this. New codes are only added with a documented API change.

Successful responses omit the `error` and `code` body fields and use the response shape documented for the endpoint. Depending on the endpoint, a 2xx response includes `"success": true` or uses a bare result shape. Determine success from the HTTP status code, not the `success` field.

## Error Codes

The `code` field is the stable contract for programmatic error handling. The closed set contains twelve classes, mapped to the HTTP status code(s) below.

| Code                   | HTTP Status   | Meaning                                                                                                                                                                                                                                            |
| ---------------------- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `invalid_request`      | 400, 415, 422 | The request was malformed or semantically invalid — including a missing or malformed `Authorization` credential, bad JSON, a missing or out-of-range parameter, or a wrong/absent `Content-Type`.                                                  |
| `payload_too_large`    | 413           | The request body exceeded the endpoint's size limit.                                                                                                                                                                                               |
| `result_set_too_large` | 413           | A list endpoint's full matched set exceeds the system ceiling. Distinct from `payload_too_large` (oversized request body): here the *response* would be too large. Narrow a sessions query with filters; `limit` is applied only after this check. |
| `unauthorized`         | 401           | Credentials are missing or invalid.                                                                                                                                                                                                                |
| `forbidden`            | 403           | Authenticated, but not permitted to act on this resource (e.g. the caller ID is not on the allow-list, or a per-app cap was reached). Acting on another app's resource is `404`, not `403`.                                                        |
| `not_found`            | 404           | The addressed resource does not exist.                                                                                                                                                                                                             |
| `conflict`             | 409           | The request conflicts with the current state of the resource.                                                                                                                                                                                      |
| `recording_not_active` | 400           | A room-recording stop addressed a room with no active Recording: a failed precondition, not a concurrency conflict. No recording command or lifecycle event is produced.                                                                           |
| `room_full`            | 409           | A join was rejected because the room is at its member cap. A count cap, not a rate limit — there is no `retry_after`; free a slot by removing a member. Distinct from `forbidden` (the per-app room-count cap on room create).                     |
| `rate_limited`         | 429           | A rate-limit bucket denied the request. The `retry_after` field gives the seconds to wait.                                                                                                                                                         |
| `unavailable`          | 502, 503, 529 | The gateway is overloaded, a dependency is unavailable, or required support-managed configuration is absent. Retry only conditions documented as transient; configuration failures require support.                                                |
| `internal`             | 500           | An unexpected server-side failure.                                                                                                                                                                                                                 |

**The set is closed.** These twelve codes are the complete list. Branch on `code` for programmatic handling and fall back to the HTTP status class for anything you do not special-case; do not pattern-match the `error` string.

## W3C Trace Context

The API supports the W3C Trace Context `traceparent` and `tracestate` request headers. The gateway validates their W3C shape and ignores malformed values.

A valid incoming context is linked to a new server-owned trace with its own trace identifier.

Every response carries the server-owned `traceparent`. Log that response header with your request context and quote it when reporting a problem. The same trace is propagated through gateway commands and webhook delivery.

**Transport.** The Voice API is machine-to-machine (M2M), server-to-server HTTP/JSON: there is no CORS support and the gateway is not callable from a browser. Send `Content-Type: application/json` on requests with a JSON body. Request-body limits are documented under [413 Payload Too Large](#413-payload-too-large).

## HTTP Status Codes

### 400 Bad Request

The request is malformed or contains invalid parameters. This includes a missing
or malformed `Authorization` credential; a syntactically valid credential with
an invalid API key returns [401 Unauthorized](#401-unauthorized) instead.

| Error Message                         | Cause                                                                                       | Fix                                                                                                     |
| ------------------------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| `Missing Authorization header`        | The `Authorization: Bearer` header was not provided.                                        | Include `Authorization: Bearer <app_uuid>:<api_key>` in every API request.                              |
| `Invalid app identifier format`       | The `app_uuid` portion of the Bearer token is not a provisioned `app_uuid`.                 | Use the identifier provided by support                                                                  |
| `Invalid request body`                | The JSON body could not be parsed.                                                          | Ensure your request body is valid JSON with `Content-Type: application/json`.                           |
| `Invalid command`                     | The command path in the URL is not recognized.                                              | Use a command path documented for the endpoint, such as `answer`, `playback/play`, or `recording/stop`. |
| `urls is required`                    | A playback or play\_and\_get\_digits command is missing the `urls` or `prompt_files` field. | Include an array of HTTP/HTTPS URLs in the request body.                                                |
| `operation_uuid must be a valid UUID` | The `operation_uuid` field is not a bare lowercase RFC-4122 v4 UUID.                        | Send `operation_uuid` as a bare lowercase v4 UUID (no prefix), or omit it and let the gateway mint one. |

### 401 Unauthorized

Authentication credentials are missing or invalid.

| Error Message     | Cause                                                             | Fix                                                                        |
| ----------------- | ----------------------------------------------------------------- | -------------------------------------------------------------------------- |
| `Missing API key` | The Bearer token does not include the API key after the colon.    | Include `Authorization: Bearer <app_uuid>:<api_key>` in every API request. |
| `Invalid API key` | The provided API key does not match the current key for this app. | Use the current key supplied by support.                                   |

### 403 Forbidden

You are authenticated, but the action is not permitted. A `403` indicates a policy or resource limit on a resource your application owns. A resource owned by another application returns `404` (see [404 Not Found](#404-not-found)).

| Error Message                                          | Cause                                                                                                                                                                   | Fix                                                                               |
| ------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
| `cid_not_allowed`                                      | The `from` on `POST /v1/sessions:dial` is not on this app's caller-ID allow-list.                                                                                       | Use a caller ID provisioned for the app, or ask support to update the allow-list. |
| `Maximum number of rooms reached for this application` | The per-app room cap was hit on `POST /api/v1/rooms`. This is a resource limit, not a rate limit. See [Resource Limits](/rate-limits#resource-limits-not-a-rate-limit). | Delete unused rooms to free capacity, or contact support.                         |
| `Forbidden`                                            | The recording URL is invalid or expired.                                                                                                                                | Use the URL from the latest recording lifecycle event before its `expires_at`.    |

### 404 Not Found

The requested resource does not exist. Sessions and rooms owned by another application also return `404`.

| Error Message       | Cause                                                                                       | Fix                                                           |
| ------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------- |
| `Session not found` | No active session exists with the given UUID, including when the session has already ended. | Verify the session UUID and that the session is still active. |
| `Room not found`    | No room exists with the given room\_id, including when the room has been destroyed.         | Verify the room\_id and that the room still exists.           |

### 409 Conflict

The request conflicts with the current state of the resource.

| Error Message                                                                                                                                            | Cause                                                                                                                                                                                                                           | Fix                                                                                                                                                                                       |
| -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Session is still joining a conference room`                                                                                                             | The session is joining another room and does not yet have a member ID. (A session already fully joined to a different room is *not* an error: `POST /rooms/{room_id}/members` auto-leaves the prior room and joins the target.) | Wait for the [`room.member.joined`](/webhooks/rooms-events#roommemberjoined) event for the in-progress join, then retry.                                                                  |
| `Session is already muted / unmuted`                                                                                                                     | The mute state is already what you requested.                                                                                                                                                                                   | Check the current mute state before toggling. This is a state-conflict guard.                                                                                                             |
| `Request already in progress`                                                                                                                            | A request with the same `Idempotency-Key` is still being processed.                                                                                                                                                             | Do not send another concurrent request. After the original request completes, retry the same request with the same key to retrieve its result. See the [Idempotency guide](/idempotency). |
| `Another room media transition is in progress` / `Room recording stop is already in progress` / `Room recording state changed; retry` (`code: conflict`) | A room-recording stop overlapped another room-media transition or a concurrent recording lifecycle update.                                                                                                                      | Wait for the in-progress transition to finish, then retry against the room's current state.                                                                                               |
| `Room is full` (`code: room_full`)                                                                                                                       | The room is at its member cap (system default 250 unless the application has its own cap). The member was not seated. This is a count cap, not a rate limit.                                                                    | Remove a member to free a slot, or ask support to raise the member cap. There is no `retry_after`. See [Resource Limits](/rate-limits#resource-limits-not-a-rate-limit).                  |

### 422 Unprocessable Entity

The request was well-formed but could not be processed. It is produced when an `Idempotency-Key` is reused with a different request body.

| Error Message                                     | Cause                                                | Fix                                                                                |
| ------------------------------------------------- | ---------------------------------------------------- | ---------------------------------------------------------------------------------- |
| `Idempotency-Key reused with a different request` | The same key was sent with a different request body. | Use a fresh key per distinct operation. See the [Idempotency guide](/idempotency). |

### 413 Payload Too Large

Two distinct conditions return `413`, each with its own `code`. `payload_too_large` means the *request* body is too big; `result_set_too_large` means the *response* a list endpoint would return is too big. Branch on the `code` field to tell them apart.

For `payload_too_large`: the request body exceeds the per-route size limit. The default limit is 1 MiB; the per-session command route (`POST /v1/sessions/{uuid}/{command}`) has a 64 KiB limit.

For `result_set_too_large`: list endpoints (sessions, rooms, members) return all matching items by default. A full matched set above 50,000 items returns this error. A smaller `limit` does not avoid the check because it applies to the full matched set first. Narrow a sessions query with its filters. Room and member list endpoints have no narrowing filter.

| Error Message                                  | Cause                                                                                                                                                                                                                 | Fix                                                                                                                                                                                                                           |
| ---------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Request body too large` (`payload_too_large`) | The request body exceeds the route's maximum size (1 MiB default; 64 KiB on session command routes).                                                                                                                  | Reduce the payload size. Command payloads should contain only small fields (DTMF strings, file URLs, flags). Do not embed audio or other large data inline.                                                                   |
| `result_set_too_large`                         | A list endpoint's full matched set exceeds the system ceiling of 50,000 items. Sessions return `Too many results to return; narrow your query using filters.`; rooms and members return `Too many results to return.` | For sessions, narrow the query with filters such as state or creation time. A smaller `limit` does not help. Rooms and members expose no narrowing filter or request-side workaround. This is unrelated to request-body size. |

### 415 Unsupported Media Type

The request was sent without a `Content-Type` header, or the header value is not `application/json`. Routes that decode a JSON body require the header on every request that carries a body. Action endpoints that take no body (e.g. `DELETE /rooms/{room_id}/members/{uuid}`, `/rooms/{room_id}/playback/stop`) do not require it.

| Error Message                           | Cause                                                                                                                                                                        | Fix                                                                             |
| --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
| `Content-Type must be application/json` | The request omits the `Content-Type` header or sets it to a value other than `application/json` (charset parameters such as `application/json; charset=utf-8` are accepted). | Set `Content-Type: application/json` on every request that carries a JSON body. |

### 429 Too Many Requests

You have exceeded the rate limit. See [Rate Limits](/rate-limits) for details.

| Error Message                     | Cause                                                          | Fix                                                                               |
| --------------------------------- | -------------------------------------------------------------- | --------------------------------------------------------------------------------- |
| `Command rate limit exceeded`     | A per-room/per-session per-command token bucket was exhausted. | Slow down and retry after `retry_after` seconds. See [Rate Limits](/rate-limits). |
| `Application rate limit exceeded` | The per-app command token bucket was exhausted.                | Slow down and retry after `retry_after` seconds. See [Rate Limits](/rate-limits). |

### 500 Internal Server Error

An unexpected error occurred on the server.

| Error Message                       | Cause                                                                                            | Fix                                                             |
| ----------------------------------- | ------------------------------------------------------------------------------------------------ | --------------------------------------------------------------- |
| `Internal error validating API key` | The server could not look up the app configuration because a required dependency is unavailable. | Retry with backoff. If repeated attempts fail, contact support. |
| `Internal error`                    | An unexpected condition occurred.                                                                | Retry with backoff. If repeated attempts fail, contact support. |

### 502 Bad Gateway

The gateway could not prepare an upstream media source for room playback. The response uses `code: unavailable`, so treat it as transient.

| Error Message                 | Cause                                                                                                              | Fix                                                                                                        |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------- |
| `Playback source unavailable` | The gateway could not determine the size of a finite playback source or could not prepare the source for playback. | Verify that the media URL is reachable and retry with backoff. If repeated attempts fail, contact support. |

### 503 Service Unavailable

`503` covers the capacity, dependency, and configuration conditions below.
Follow the recovery action documented for the condition you receive;
`Recording service not available` requires support-managed configuration and must not
be retried.

| Error Message                     | Cause                                                                                                                        | Fix                                                                                  |
| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
| `Recording service not available` | Recording is not enabled on this gateway.                                                                                    | Contact support to enable the recording service.                                     |
| `No source node available`        | No capacity was available to host the room, so no room was created.                                                          | This is a transient capacity condition. Retry with backoff.                          |
| `Source node unavailable`         | The infrastructure hosting the room could not be reached, so the room playback or room recording command was not dispatched. | Retry with backoff. If repeated attempts fail, contact support.                      |
| `Service temporarily unavailable` | The idempotency store is unavailable, so a request carrying an `Idempotency-Key` was **not** processed (fail-closed).        | Retry the same request with the same key. See the [Idempotency guide](/idempotency). |

### 529 Site Overloaded

The gateway has no outbound capacity. Retry this response as described below. `529` is a non-standard status code for overload.

| Error Message | Cause                                                  | Fix                                                                                                                                                                                  |
| ------------- | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `overloaded`  | No outbound capacity is available to place a new call. | Wait for the duration in the `Retry-After` response header (also returned in the `retry_after` body field as seconds), then retry. Use exponential backoff after repeated responses. |

## Best Practices

* **Always check the HTTP status code first.** A 2xx status means the request was accepted. Any other status indicates an error.
* **Branch on the `code` field, not the message.** The `code` is a stable closed enum; the `error` string is human-readable and may change.
* **Capture the response `traceparent`.** Log the server-owned response value alongside your request context and quote it when reporting a problem to support.
* **Propagate W3C trace context.** Send a valid `traceparent` with the request and preserve `tracestate` when present.
* **Parse the `error` field for display only.** Error messages are designed to be human-readable and actionable, but are not part of the programmatic contract.
* **Handle 429 with exponential backoff.** When rate-limited, wait before retrying. Do not retry continuously without a delay.
* **Retry only documented transient 5xx conditions.** Use backoff for overload and temporary dependency failures. Do not loop on support-managed configuration errors such as `Recording service not available`; contact support.