Reference

Errors

The router's error codes, what each means, whether to retry, and how each surface reports them.

Error codes#

The WebSocket protocol, /v1/audio/speech and the LiveKit plugin share these codes. retryable says whether the same request can succeed if you try again.

bad_requestHTTP 400, not retryable
The request failed validation or a message arrived out of order. The message says which field or rule.
unauthorizedHTTP 401, not retryable
The API key is missing, wrong or deleted, or its org is disabled.
insufficient_creditsHTTP 402, not retryable
Your org is out of credit, so the router refuses new connections and requests until you buy more. Connections already open carry on.
provider_key_missingHTTP 400, not retryable
Your org has no key stored for the provider. Add one under Provider keys.
provider_rejectedHTTP 400, not retryable
The provider refused the request, e.g. a wrong stored provider key or an unknown voice. The provider’s message is passed through.
provider_rate_limitedHTTP 429, retryable
Your own quota at the provider is exhausted.
provider_unavailableHTTP 502, retryable
The provider returned a server error, or the connection to it failed or closed unexpectedly.
provider_timeoutHTTP 504, retryable
The provider took too long to send audio.
internalHTTP 500, retryable
The router failed.

The HTTP status is what /v1/audio/speech answers with. provider_status, when present, is the status the provider answered with.

How each surface reports them#

WebSocket (/v1/stream). An error message ends the segment; close the connection and open a new one. segment_id is null for an error before a valid start. Two refusals come before the connection opens, as the HTTP status of the handshake: 403 for an API key the router doesn’t accept, and 402 (insufficient_credits) when your org is out of credit.

{"type": "error", "segment_id": "c0ffee", "code": "provider_rejected", "message": "…", "retryable": false, "provider_status": 403}

/v1/audio/speech. A JSON body with the status above. The status is sent with the first audio, so an error after audio has started can only abort the body: treat a response that ends early as failed.

HTTP/1.1 400 Bad Request{"error": {"code": "provider_key_missing", "message": "…", "retryable": false, "provider_status": null}}

Provider drop-ins. In the provider’s own format and status, as the provider sent them, so your client’s error handling keeps working. The one error of the router’s own, insufficient_credits, comes as HTTP 402 in the provider’s error shape (on ElevenLabs’ WebSockets, as an in-band error and close 1008). See Sarvam, Raya and ElevenLabs.

In the LiveKit plugin#

Router errors become LiveKit's API errors, so LiveKit's retries and FallbackAdapter treat them as they treat any TTS's.

What happenedRaised
A router errorAPIStatusError, with the error’s retryable; status_code is provider_status or the status above
The router refused the API keyAPIStatusError, status 403, not retryable
Your org is out of creditAPIStatusError, status 402, not retryable
The connection failed or droppedAPIConnectionError
The router went quiet after all text was sentAPITimeoutError
A bad setting at constructionValueError

REST API errors#

The provider-keys API, and the dashboard's own API, answer with a code and a message.

HTTP/1.1 401 Unauthorized{"error": {"code": "unauthenticated", "message": "invalid API key"}}
codeStatusMeaning
unauthenticated401The API key is missing, wrong or deleted.
bad_request422The request failed validation; the message names the field, never its value.
not_found404Nothing to delete: no key is stored for that provider.
internal500The router failed.

The dashboard’s API adds these, from managing an org’s members. Each /api/orgs/… route also checks your role in the org: forbidden (403) when it isn’t enough, not_found (404) when you aren’t a member.

codeStatusMeaning
already_member409The invited email already belongs to a member of the org.
already_invited409The invited email already has a pending invite to the org.
last_admin409The change would leave the org without an admin: make another member an admin first.
last_member409The org's last member can't leave it; orgs are never deleted.