本文档尚未翻译成简体中文,现显示英文原文。

Model fallbacks

Declaring a candidate chain with `models[]`

Instead of a single model, send models — an ordered list. We try them in order and stop at the first one that answers. You are billed for the one that answered, and only for that one.

json
{
  "models": ["lgai/exaone-4.0-32b", "google/gemma-4-26b-a4b"],
  "messages": [{ "role": "user", "content": "요약해 줘." }]
}

If models is present it wins; model is ignored. If neither is present, the request uses the service default, google/gemma-4-26b-a4b.

How the chain is built

Your list is not the chain — it is the input to it.

  1. Expand. Each model id is replaced by every provider that serves it, ordered by priority. Local GPU slots have the lowest priority number, so they come first. See Local-first routing.
  2. Concatenate. The expanded lists are joined in the order you gave. Duplicate provider/model pairs are collapsed, so listing the same model twice does nothing.
  3. Filter. Your provider{} preferences, context-window checks, and cooldowns remove candidates. See Provider routing.
  4. Run. Candidates are attempted in order until one responds.

So ["A", "B"] where A has a local slot and one external endpoint, and B has one external endpoint, produces a chain of three attempts, not two.

Unknown model ids

The rules differ between model and models, on purpose:

RequestBehaviour
"model": "nope/nope"400 model_not_found
"models": ["nope/nope", "google/gemma-4-26b-a4b"]Unknown entry is dropped silently; the request runs on google/gemma-4-26b-a4b
"models": ["nope/nope", "also/nope"]400 model_not_found

A list means "any of these", so an entry we cannot serve is simply not a candidate — that is the whole point of a fallback list. A single model is a specific instruction, so a typo is an error.

When retries happen, and when they stop

If a candidate refuses the request, times out on connect, or returns a non-2xx status, nothing has been sent to you yet and we move to the next candidate. If a candidate accepts and starts responding and then fails, we cannot silently restart on a different model — you have already received part of an answer from the first one. Instead:

  • Streaming: an error event is emitted on the stream and the stream ends. See Streaming.
  • Non-streaming: the failure surfaces as 502 provider_error.

This is why the model in X-MyIP-Model is authoritative: it is the model that produced the bytes you received.

When the whole chain fails

Statuserror_typeMeaning
404no_endpoints_foundNothing was left to try after filtering
408timeoutAt least one candidate hit the upstream timeout
502provider_errorCandidates failed. metadata.provider_code carries the last upstream status
503model_loadingEvery candidate was a local slot that could not wake up in time. metadata.retry_after_sec and a Retry-After header tell you when to come back

None of these are billed. A request that produced no tokens costs nothing.

Seeing what happened

Response headers name the winner:

X-MyIP-Model: google/gemma-4-26b-a4b
X-MyIP-Provider: MyIP Local GPU
X-MyIP-Generation-Id: gen-01JD8Q2K7M4X9N

The full attempt list — every candidate we tried, its status and how long it took — is in the generation record:

bash
curl "https://openrouter.myip.co.kr/api/v1/generation?id=gen-01JD8Q2K7M4X9N" \
  -H "Authorization: Bearer $MYIP_API_KEY"

provider_responses in that response is the attempt log. If you are debugging why a request took longer than expected, that is where to look first.

Examples

curl https://openrouter.myip.co.kr/api/v1/chat/completions \
  -H "Authorization: Bearer $MYIP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "models": ["lgai/exaone-4.0-32b", "google/gemma-4-26b-a4b"],
    "messages": [{ "role": "user", "content": "다음 문단을 세 줄로 요약해 줘." }]
  }' -D -

The -D - on the curl example prints the response headers, which is the quickest way to see which model won.

A practical pattern: cold-start insurance

Local models can be asleep. Putting a second local model behind the first turns a possible 503 model_loading into a slightly different answer:

json
{ "models": ["lgai/exaone-4.0-32b", "google/gemma-4-26b-a4b"] }

Because the GPU node is exclusive, whichever of the two is currently resident answers immediately. This is usually a better default than pinning a single local model and retrying on 503.

route

"route": "fallback" is accepted and does nothing — it describes the behaviour models[] already has. We take it rather than rejecting it so that code written for other gateways keeps working. There is no auto-router, no cost/quality frontier, and no model fusion; the chain is exactly what you declared. See Principles.

Combining with provider preferences

models[] chooses which models; provider{} chooses which endpoints for those models, and in what order. They compose:

json
{
  "models": ["google/gemma-4-26b-a4b", "lgai/exaone-4.0-32b"],
  "provider": { "only": ["local-gpu"], "allow_fallbacks": true }
}

Note that provider.allow_fallbacks: false truncates the whole chain to its first candidate — including candidates that came from later entries in models[]. If you want a fallback list, leave it alone.

最后更新于 2026年9月5日