Headers
Per-request headers, how they beat the key policy, and what the response tells you.
Routing controls ride in headers, never in the body, so an OpenAI client stays untouched. Put them on the key instead and you send none of these — see keys and policy.
Request headers
| Header | Value | Effect |
|---|---|---|
X-Speko-Objective | latency, cost, quality, balanced | Which axis the ranking optimizes. |
X-Speko-Language | BCP 47 tag, e.g. es-CO | Which board ranks candidates, and whether TTS accent narrowing applies. |
X-Speko-Allow | Comma-separated | Restrict candidates to these. |
X-Speko-Deny | Comma-separated | Remove these from consideration. |
X-Speko-Max-Price | Number | Drop candidates above this price. |
Allow and deny entries take a provider (every model from that vendor) or a provider:model:
X-Speko-Allow: cartesia,elevenlabs:eleven_v3
X-Speko-Deny: openai
X-Speko-Objective: latencyWhat beats what
- A request header beats the key policy for objective, language and max price.
X-Speko-Allowdisables the key's chain entirely — the allowlist becomes the candidate set. This is how you override a pinned key for one call.- Deny and max-price beat even a pin. A pinned model that is denied, or too expensive, is not a candidate.
- A model with no provider credential is never a candidate — filtered before ranking, even if you allowlisted it. Allowing only unavailable models gives
503 no_candidate.
For TTS the accent also participates; full ordering is in languages and accents.
Response headers
| Header | Value |
|---|---|
x-route | Provider/model that actually served the request. |
x-route-reason | Why this candidate, including the ranking evidence class. |
x-speko-failover-count | How many candidates failed first. 0 means the first choice worked. |
x-speko-first-byte-ms | Time to first byte from the winning upstream, router-side. |
x-speko-first-byte-ms appears on success only — the requests you most want
to trace are the ones without it. Read x-speko-failover-count and the error's
details.attempts instead.
It measures the router's wait on the upstream, not your first sample. On a streaming route the two differ by the network leg to you, so time the first chunk client-side as well.
x-route names the provider and the model on every HTTP route. The realtime sockets send a
ready frame carrying provider only, so a model-level pin cannot be confirmed there.
Pin ids come from the API
GET /v1/models is public. The id values it returns are exactly what you write into a key chain, an X-Speko-Allow entry, or a body model. Copying an id from a vendor's own docs instead is how you get 400 unsupported_model.
GET /v1/routing/preview returns the candidate the router would pick without calling a provider or costing anything. Parameters for both are in the API reference.