Configuration

router.yaml is the whole config, commented and ready to run. The keys worth knowing:

KeyMeaning
listenBind address. Loopback by default.
apiKeyShared secret clients must present. "" accepts any.
ui.enabledEnable the embedded console at /ui/. Requires a nonempty apiKey.
ui.secureCookiesUse HTTPS cookies and origin checks behind a TLS-terminating proxy. Defaults to false.
defaults.*Retry budget, timeouts, sticky affinity, breaker thresholds.
store.pathSQLite file for usage history. Empty keeps no history.
store.queueSizeRecords waiting to be written before they are dropped.
store.maxRowsRecords kept, oldest deleted first. 0 keeps everything.
upstreams.<name>.baseUrlProvider root; the protocol’s path is appended.
upstreams.<name>.apiKeyEnvEnv var holding the key. Preferred over apiKey.
upstreams.<name>.kindhttp (default), cli for Claude Code, or codex for ChatGPT subscription access.
upstreams.<name>.modeClaude Code: one-shot (default) or persistent. Codex is always persistent.
upstreams.<name>.toolModeClaude Code: claude or non-streaming client. Codex uses streaming-capable native client tools.
upstreams.<name>.commandClaude one-shot uses {prompt}; persistent may use {model}. Codex defaults to codex app-server --listen stdio:// without placeholders.
upstreams.<name>.cwdWorking directory for a CLI process; Claude Code discovers project configuration here.
upstreams.<name>.timeoutBounds one CLI turn. Defaults to maxStreamDuration.
upstreams.<name>.maxSessionsMaximum live persistent CLI processes. Defaults to maxConcurrency.
upstreams.<name>.sessionIdleTimeoutCloses inactive persistent CLI processes. Zero keeps them alive until shutdown.
upstreams.<name>.authStylebearer (the default) or anthropic.
upstreams.<name>.headersExtra headers per upstream, overriding the client’s.
upstreams.<name>.bodyDropTop-level JSON fields removed before dispatch.
upstreams.<name>.bodyPatchTop-level JSON fields forced.
upstreams.<name>.maxConcurrencyIn-flight turn cap. At the limit: skipped, never queued.
models.<alias>.apiWire protocol clients speak for this alias.
models.<alias>.contextWindow, maxOutputTokens, costAdvertised to clients for budgeting; not enforced upstream.
models.<alias>.targets[]Where the alias may be served: upstream, model id, weight.

Choosing a model version, provider and thinking mode

These are set per target — per provider route — because the same logical model needs different fields on each:

Decided atKey
Which model version or varianttargets[].model
Which provider serves ittargets[].bodyPatch — OpenRouter’s provider object
How hard it thinkstargets[].bodyPatch — reasoning on OpenRouter, thinking/output_config on Claude
A field a provider must not receivetargets[].bodyDrop

Which OpenRouter provider serves a request is the provider object: order/only/ignore select providers by slug, allow_fallbacks decides whether OpenRouter may fall back to another, and sort, require_parameters, data_collection, zdr and max_price constrain the choice further.

Translating between wires

A target may name a different wire protocol than its alias. The router then translates the request on the way out, and the response and its stream on the way back.

What translates: messages (system hoisting, role coalescing, tool calls and results, images), tools and tool choice, token limits, stop sequences and reasoning controls are mapped. Native structured outputs, citations, computer use, server-side tools and PDF input are not. Fields with no equivalent are dropped rather than guessed at, because the Messages API rejects unknown parameters instead of ignoring them.

Streaming is translated incrementally with no whole-stream buffering, and the usage observer still reads the upstream’s own events — so accounting works identically whether or not the wire changed.

The capability table

capabilities.json is an embedded snapshot of the Claude models’ limits, pricing and capability flags, transcribed from the providers’ published figures and cross-checked against LiteLLM’s model map. Loading a config uses it to warn about metadata that disagrees with the table, and to refuse thinking modes a model does not support.