Error handling

Understand and handle API errors consistently.
View as Markdown

Expected API errors use a consistent JSON format and one of the documented HTTP status codes. This includes requests to an unknown path and requests that use an unsupported method on a known path. Unexpected 500 responses add a support_id, as described under 500 Internal Server Error.

Error response format

An expected error response follows this structure:

1{
2 "code": "invalid_request",
3 "message": "Human-readable error message"
4}
  • code — a stable, machine-readable class from a small closed set (see Error Codes). Branch on this. New codes are only added with a documented API change.
  • message — a human-readable, English-only message whose phrasing may change. Use it for display and diagnostics.

Use the HTTP status code to determine whether a request succeeded. Any 2xx status indicates success. Successful response bodies vary by endpoint and do not use a common success field.

Request validation errors

A request-validation response contains exactly one error. The message embeds the failing path:

1{
2 "code": "invalid_request",
3 "message": "The \"to\" phone number must match E.164 format"
4}

Fix that error and retry the request. If another field is invalid, the next response identifies it. Validation responses do not include submitted values or validator-specific fields such as detail, errors, location, or value.

Error codes

For expected errors, the code field is the stable contract for programmatic error handling. The closed set contains thirteen classes, mapped to these HTTP status codes:

CodeHTTP StatusMeaning
invalid_request400, 405, 415, 422The request was malformed or semantically invalid, including bad JSON, a missing or out-of-range parameter, an unsupported method on a known path, or a wrong or malformed Content-Type.
invalid_authorization_header400The Authorization header is missing or the Bearer credential is malformed.
payload_too_large413The request body exceeded the endpoint’s size limit.
result_set_too_large413A 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.
unauthorized401The Bearer credential has a valid application identifier, but its API key is missing or invalid.
forbidden403Authenticated, 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_found404The requested path or addressed resource does not exist.
conflict409The request conflicts with the current state of the resource.
recording_not_active409A 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_full409A join was rejected because the room is at its member cap. A count cap, not a rate limit — no Retry-After header is sent; free a slot by removing a member. Distinct from forbidden (the per-app room-count cap on room create).
rate_limited429A rate-limit bucket denied the request. The Retry-After response header gives the seconds to wait.
playback_source_error502A playback or DTMF prompt source could not be admitted. The detail object identifies the failure category and whether retrying may succeed.
unavailable503, 529A required service is temporarily unavailable or the platform is overloaded. Use the HTTP status and operation documentation to determine how to retry.

The set is closed. These thirteen expected-error 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 message string. Unexpected 500 responses use internal_error in a separate format.

W3C trace context

The API supports the W3C Trace Context traceparent and tracestate request headers. It 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 this response header with your request context, and include it when you report a problem. The same trace propagates through command processing and webhook delivery.

Transport. The API is machine-to-machine (M2M), server-to-server HTTP/JSON. Send Content-Type: application/json on requests with a JSON body. Request-body limits are documented under 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 instead.

Error MessageCauseFix
Missing Authorization header. Expected format: Bearer <app_uuid>:<api_key> (invalid_authorization_header)The Authorization: Bearer header was not provided.Include Authorization: Bearer <app_uuid>:<api_key> in every API request.
Invalid Authorization header. Expected format: Bearer <app_uuid>:<api_key> (invalid_authorization_header)The Bearer credential is malformed or its app_uuid has an invalid shape.Use the application identifier and API key provided by support.
The request body is not valid JSON (invalid_request)The JSON body could not be parsed.Ensure your request body is valid JSON with Content-Type: application/json.
Operation-Id must be a lowercase RFC-4122 v4 UUIDThe Operation-Id request header is not a lowercase RFC-4122 v4 UUID.Send a lowercase v4 UUID in Operation-Id, or omit the header and let the platform generate one.

401 Unauthorized

The Bearer credential has a valid application identifier, but its API key is missing or invalid. Missing or malformed Bearer credentials return 400 invalid_authorization_header instead.

Error MessageCauseFix
Missing API keyThe Bearer token does not include the API key after the colon.Include Authorization: Bearer <app_uuid>:<api_key> in every API request.
Invalid API keyThe 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).

Error MessageCauseFix
Caller ID is not permitted for this applicationThe 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 applicationThe per-app room cap was hit on POST /api/v1/rooms. This is a resource limit, not a rate limit. See Resource Limits.Delete unused rooms to free capacity, or contact support.
ForbiddenThe recording URL is invalid or expired.Use the URL from the latest recording lifecycle event before its expires_at.

404 Not Found

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

Error MessageCauseFix
No route matches this pathThe request path does not match a public API route.Use a path documented in the API reference.
Session not foundNo 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 foundNo room exists with the given room_id, including when the room has been destroyed.Verify the room_id and that the room still exists.

405 Method Not Allowed

The request path exists, but it does not accept the HTTP method you used.

Error MessageCauseFix
Method not accepted for this pathThe request used an unsupported method on a known public API path.Use a method documented for the path in the API reference.

409 Conflict

The request conflicts with the current state of the resource.

Error MessageCauseFix
Session is still joining a conference roomThe 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 event for the in-progress join, then retry.
Session is already muted / unmutedThe mute state is already what you requested.Check the current mute state before toggling. This is a state-conflict guard.
Request already in progressA 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.
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. The response does not include a Retry-After header. See Resource Limits.

422 Unprocessable Entity

The request was well-formed but could not be processed. A 422 uses code: invalid_request for either of these conditions:

Error MessageCauseFix
The "to" phone number must match E.164 formatA Dial request failed schema validation. The response contains exactly one validation error and embeds the failing path in message.Fix the named field and retry. If another field is invalid, the next response identifies it.
Idempotency-Key reused with a different requestThe same key was sent with a different method, route, query, content type, or raw request body.Use a fresh key per distinct operation. See the Idempotency guide.

Both conditions have the same code, so do not use code alone to select the recovery action. Use the request context: correct a Dial validation failure, or use a new idempotency key when you changed a request. Do not pattern-match the human-readable message, because its phrasing may change.

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 MessageCauseFix
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_largeA 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 explicitly supplies a malformed Content-Type header or a media type other than application/json.

Error MessageCauseFix
Unsupported Content-Type; use application/jsonThe request explicitly supplies a malformed Content-Type header or a media type other than application/json. Charset parameters such as application/json; charset=utf-8 are accepted.Use Content-Type: application/json with a JSON request body.

429 Too Many Requests

You have exceeded the rate limit. The error body contains only code and message. Read the integer delay from the Retry-After response header, wait at least that many seconds, and then retry. See Rate Limits for details.

1HTTP/1.1 429 Too Many Requests
2Retry-After: 1
3
4{
5 "code": "rate_limited",
6 "message": "Command rate limit exceeded. See Retry-After header."
7}
Error MessageCauseFix
Command rate limit exceeded. See Retry-After header.A per-room/per-session per-command token bucket was exhausted.Slow down and retry after the delay in the Retry-After response header. See Rate Limits.
Application rate limit exceeded. See Retry-After header.The per-app command token bucket was exhausted.Slow down and retry after the delay in the Retry-After response header. See Rate Limits.

500 Internal Server Error

An unexpected platform failure returns a top-level code, message, and opaque support_id:

1{
2 "code": "internal_error",
3 "message": "An internal error occurred",
4 "support_id": "err_0123456789abcdef0123456789abcdef"
5}

Do not parse the support ID. Quote it when you contact support. The response does not expose the technical cause or a vendor event identifier.

502 Bad Gateway

The platform could not admit a playback or DTMF prompt source. The response uses code: playback_source_error and includes a detail object:

  • reason classifies the failure as unsupported, not_found, access_denied, timeout, unreachable, rejected, invalid_response, or preparation_failed.
  • source_index identifies the failing zero-based item when the request contains multiple URLs.
  • retryable indicates whether retrying the same source later may succeed.
  • upstream_status contains the source origin’s HTTP status when one was received.

Use detail.reason and detail.retryable to decide whether to correct the source or retry. The response never contains source URLs, credentials, raw upstream response bodies, filesystem paths, or media-engine details.

503 Service Unavailable

503 covers the temporary capacity and dependency failures below. Follow the recovery action documented for the condition you receive.

Error MessageCauseFix
Recording unavailableThe recording service could not admit the command.Retry with backoff. If repeated attempts fail, contact support.
No source node availableNo capacity was available to host the room, so no room was created.This is a transient capacity condition. Retry with backoff.
Source node unavailableThe 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 unavailableThe 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.

529 Site Overloaded

The platform has no outbound capacity. The error body contains only code and message. Read the integer delay from the Retry-After response header, wait at least that many seconds, and then retry. 529 is a non-standard status code for overload.

1HTTP/1.1 529 Site Overloaded
2Retry-After: 5
3
4{
5 "code": "unavailable",
6 "message": "overloaded. See Retry-After header."
7}
Error MessageCauseFix
overloaded. See Retry-After header.No outbound capacity is available to place a new call.Wait for the duration in the Retry-After response header, then retry. Use exponential backoff after repeated responses.

Handle errors

  • 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 message 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.
  • Use the message field for display and diagnostics only. Error messages are designed to be human-readable and actionable, but are not part of the programmatic contract.
  • Honor Retry-After. For 429 and 529 responses, wait at least the number of seconds in the response header before retrying. Do not look for retry timing in the JSON body.
  • Back off after repeated retryable errors. If another 429 or 529 follows a retry, honor its Retry-After header and increase your backoff. Use backoff for other documented temporary dependency failures and Recording unavailable. Contact support if repeated attempts fail.