Skip to content
docs/routingModels

Routing

A request names a model. What happens when the first attempt at serving it fails.

A request names a model and may include OpenRouter-compatible provider preferences. Behind each model is a chain of eligible upstream targets; request preferences may narrow or order that chain, and the request walks it until something answers.

Failover

When the chosen target errors or times out, the next in the chain is tried inside the same request, and you see one response. Three rules bound that. A failure that will repeat ends the chain immediately. A 400 is your body and a 401 is our credential, and four more attempts produce four more identical rejections and four more seconds of latency. A stream commits at its first byte, after which a second attempt would append a different answer to one you already hold, so nothing fails over past that point. And every attempt is recorded whether it wins or loses, because a fallback that quietly rescues every request hides a dead target indefinitely.

The circuit breaker

A target that keeps failing is skipped rather than tried and waited out. The count is consecutive failures, not a failure rate: 2% failures on a busy target is background noise, while five in a row is a target that is not answering, and any success resets the streak. While its circuit is open the target is not in the chain at all, which is what keeps a dead target costing microseconds instead of a full timeout on every call. It is retried on a probe once the open window passes, and one success closes it again.

Response headers about your request

HEADERVALUE
X-Routehook-Request-IdThe id to quote in support, and to GET /v1/generation.
X-Routehook-AttemptsHow many attempts the request took.
X-Routehook-Fallbacktrue when the attempt that answered was not the first one.
X-Routehook-CostUSD charged for this call, as a decimal string.
X-Routehook-Upstream-Latency-MsHow long the upstream leg took, excluding our own overhead.

Every one of those is a measurement of your own call. None of them identifies the host that served it, and that is deliberate rather than an oversight. A header is the easiest thing in an API to add by accident, so it is worth saying plainly that two which used to be here, naming the provider and the upstream model id, were removed and are not coming back.

What X-Routehook-Fallback: true means

It means the first attempt failed and a later one answered. The response is good (that is the point of a chain), but it is also the only signal you get that something behind this model is having a bad afternoon, because nothing else about the response looks different. Log the header next to your own request log line. A run of trues on one model is worth raising with us before it turns into errors you can see.

Whether a fallback changes what you pay

The published rate belongs to the model, so being served by a later attempt does not change the rate. What can change is the quantity the rate is applied to: a fallback target may run a different build of the same model, which tokenises your prompt slightly differently and returns an answer of a different length. Both figures are on the response (usage.cost is exact, and GET /v1/generation reports the same call's whole economics), so a bill is always reconcilable against what you were actually served.

models is the lever you do have

You cannot choose the route, but you can choose the fallback *model*. models names other slugs to try, in order, and one is used when the model before it cannot be served at all. Nothing enabled, nothing priced, nothing left in the chain. That is a change of model rather than of route, which is why it is your decision and never ours, and it does move the bill: the model that answers is billed at its own published price. The response body always carries the model that actually answered, so cost attribution keyed on that field stays correct.

Naming a fallback model

JSONA cheaper sibling, tried only if the first cannot be served
{
  "model": "openai/gpt-4o",
  "models": ["openai/gpt-4o-mini"],
  "messages": [{ "role": "user", "content": "Hello" }]
}

An ordinary request

cURLThere is nothing to configure. Name the model and send it
curl https://api.routehook.ai/v1/chat/completions \
  -H "Authorization: Bearer $ROUTEHOOK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "openai/gpt-4o-mini",
    "messages": [{ "role": "user", "content": "Hello" }]
  }'

When nothing can serve it

409 model_unavailable means the chain came out empty: the model is in the catalogue and no route for it is available right now. The message does not enumerate what was dropped or why. Those reasons name individual upstream targets, and there is nothing in them you could act on anyway. Retry, or send a different slug from GET /v1/models. If a model answers 409 for more than a few minutes, that is worth reporting.