ultrarouter docs
Concepts

Routing modes

Pinned, multi-offer fallback, and auto-routing with deadlines and provider prefs.

A logical model id (e.g. openai/gpt-5.2) can be served by several providers (OpenRouter-style offers). ultrarouter resolves your model chain into an ordered list of concrete provider offers and executes them with deadlines.

The router field

Set router on the request (or per call type):

  • off (default) / pinned — use the resolved model + its offers in order; no cross-model substitution.
  • auto / candidates — auto-routing. The response ur.routing carries engine: "not_enabled" where auto-selection is not yet active for your workspace, so the contract is stable for SDKs while the eval-driven engine rolls out per workspace via canary. Auto-selection only ever adopts a model a passing eval verdict has cleared, then rolls it out behind an auto-rollback canary — a downgrade in autonomy (a per-request router can only lower it, never raise it).

Fallbacks and offers

  • model is the primary; models: [...] are ordered fallbacks. The chain is the outer loop; a model's provider offers are the inner loop.

  • Offers are ordered by provider.sort: price (default, per input-token cost) or throughput. The suffixes :nitro (throughput) and :floor (price) set the sort per model.

  • Provider preferences (provider.order, ignore, quantizations, data_collection: "deny", allow_fallbacks: false) filter and order candidates. Every rejected candidate is recorded with a machine-readable reason.

  • A capability guard skips any offer that lacks a capability your request needs (tools, json_schema, vision, embeddings). In a multi-offer chain a capable sibling still serves the request; only the incapable offer is skipped (with a reason).

  • Capability refusal, never a silent downgrade. If no offer for your requested model can honour a capability you explicitly asked for — most commonly a response_format: json_schema pinned to a model that does not support structured output — the gateway refuses the request before dispatch with the GW-70 capability_unsupported error (HTTP 400, not retryable), naming the missing capability. It will never quietly downgrade your json_schema to json_object or drop it, and never substitute a different model — your output contract is honoured or the request is refused with the real reason. If a provider itself rejects an unsupported response_format at dispatch (a raw upstream 400), that failure is mapped onto the same capability_unsupported code, so logs and clients see the real cause instead of a generic upstream error.

  • Unpriced offers are never routed. An offer with no usable price — a model a live catalog sync discovered that nobody has priced yet, or a custom provider registered without pricing — is removed before planning, because the gateway cannot compute what a call to it costs and would otherwise record it as $0. A sibling offer that is priced still serves the request, and the removed offer is listed under unpriced_refused in the routing trail. If the model you asked for has no priced offer, the request is refused before dispatch with the GW-70 model_unpriced error (HTTP 403, not retryable). It is never silently served by a fallback model. A price of 0 that you declared on a custom provider counts as a price.

  • Retired and renamed provider models. The catalog is checked against each provider's live model list. When a provider stops listing a model, that offer stops being routed: a request that names it gets not_found if no other offer serves it, and a sibling offer still serves the model in a multi-offer chain. When a provider renames a model and the catalog records the new id, the offer moves to the new id at the same price, so the model stays routable under its logical id.

Deadlines

ur.timeout_ms is the total budget (default 30 s non-stream / 60 s stream, clamped [1s, 300s]) and spans all attempts. Each attempt is capped at min(remaining_total, attempt_cap). A retryable provider error advances to the next offer, then the next model; a non-retryable error surfaces immediately.

Streaming may fall back only before the first content byte; once bytes flow, a failure is a terminal event: error frame — never a silent model switch.

The routing trail

Every request records the full trail — mode, sort, each attempt (model, provider, outcome, latency), the skipped candidates with reasons, whether a fallback was used, and the per-field config sources. Fetch it after the fact with GET /v1/generation.