Speko Docs
RouterReference

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

StatusCodeCause
400invalid_routing_headerA routing header is malformed — unknown objective, bad language tag, unparseable allow/deny entry.
400invalid_request_bodyNot valid JSON, or a required field is missing.
400invalid_parameterA field is present but its value is not acceptable.
400unsupported_modelThe model you named is not routable. details.available_models lists what is.
400unsupported_languageThe tag is not enabled for this key. details.allowed_languages lists what is.
400unsupported_response_formatStreaming TTS takes pcm or pcm16. details.supported lists them.
401invalid_api_keyKey missing, revoked or wrong.
413request_too_largeBody or audio exceeds the size limit.
502all_upstreams_failedEvery candidate was tried and failed. details.attempts[] records each one.
503no_candidateNothing was eligible to try.
503no_streaming_tts_providerEligible candidates existed, but none can stream this request. details names the language and stage.
503no_streaming_providerThe 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

ClassDo
400, 413Fix the request. Retrying is pointless.
401Rotate the key. Do not retry in a loop.
502Read details.attempts[], then retry with backoff.
503Loosen 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.

On this page