Errors
Every error code, and exactly which upstream failures fail over.
Every error uses the same envelope:
{
"error": {
"code": "no_candidate",
"message": "No candidate can serve es-PR for tts.",
"type": "routing_error",
"details": {}
}
}Codes
| Status | Code | Cause |
|---|---|---|
| 400 | invalid_routing_header | A routing header is malformed — unknown objective, bad language tag, unparseable allow/deny entry. |
| 400 | invalid_request_body | Not valid JSON, or a required field is missing. |
| 400 | invalid_parameter | A field is present but its value is not acceptable. |
| 400 | unsupported_model | The model you named is not routable. details.available_models lists what is. |
| 400 | unsupported_language | The tag is not enabled for this key. details.allowed_languages lists what is. |
| 400 | unsupported_response_format | Streaming TTS takes pcm or pcm16. details.supported lists them. |
| 401 | invalid_api_key | Key missing, revoked or wrong. |
| 413 | request_too_large | Body or audio exceeds the size limit. |
| 502 | all_upstreams_failed | Every candidate was tried and failed. details.attempts[] records each one. |
| 503 | no_candidate | Nothing was eligible to try. |
| 503 | no_streaming_tts_provider | Eligible candidates existed, but none can stream this request. details names the language and stage. |
| 503 | no_streaming_provider | The same, on streaming transcription. |
There is no 402 and no 504. Upstream timeouts surface as
502 all_upstreams_failed once the candidate list is exhausted.
502 versus 503 is the distinction worth internalising. 502 means candidates existed and every one failed — read details.attempts[], which gives status and message per candidate in order. All 401s is a credential problem; all timeouts is usually the upstream region. 503 means nothing was eligible in the first place: deny or max-price emptied the set, an allowlist named only models with no credential behind them, or an accent's vendors are all unavailable.
The three streaming variants are the same 503 with the transport named, so a pin the router narrowed away is not misread as a provider outage. On a WebSocket route they arrive before the upgrade, so the client sees a handshake failure and close code 1002 rather than this envelope — reproduce the request over HTTP to read the body.
What fails over
The router advances to the next candidate on an upstream 3xx, 401, 403, 404, 408, 429, or any 5xx.
Every other 4xx is returned to you verbatim. A malformed body does not become a different provider's problem.
Failover needs a replayable request
Failover only happens before a byte reaches you. Once the router starts streaming a response, the attempt is committed.
The same applies inbound: a body the router cannot replay — streamed, or too large to buffer — gets exactly one attempt. If failover matters more than streaming for a call, send a buffered body and read a non-streamed response.
Handling them
| Class | Do |
|---|---|
| 400, 413 | Fix the request. Retrying is pointless. |
| 401 | Rotate the key. Do not retry in a loop. |
| 502 | Read details.attempts[], then retry with backoff. |
| 503 | Loosen the constraint that emptied the set, or fail deliberately. On an accent request, decide in advance whether you would rather error or drop to a bare language tag. |