shunt loads configuration from, in increasing precedence:
-
Built-in defaults — every provider (
anthropic,openai,codex, …) is preconfigured. -
A TOML or YAML file. The format is chosen by extension —
.tomlis TOML,.yaml/.ymlis YAML (any other extension is parsed as TOML). With--config <path>that exact file is used (a missing file is an error). Otherwise shunt probes each directory forshunt.toml, thenshunt.yaml, thenshunt.yml, and takes the first file found:./$XDG_CONFIG_HOME/shunt/(default~/.config/shunt/)$HOMEBREW_PREFIX/etc/(default/opt/homebrewand/usr/localprefixes)
A local
shunt.yamltherefore still wins over a config file in a later directory, while an existingshunt.tomlalongside it takes priority within the same directory. Boot logs report which file was loaded, or that defaults are in use. -
Environment variables prefixed
SHUNT_, using__for nested keys — e.g.SHUNT_SERVER__BIND=0.0.0.0:3001.
Because the defaults already define every provider, your config only needs the parts you want to change. Start from shunt.toml.example for TOML or shunt.yaml.example for YAML.
Annotated example
[server]
bind = "127.0.0.1:3001" # address shunt listens on
default_provider = "anthropic" # provider for any model with no route (pass-through)
# Each provider is a [providers.<name>] table.
[providers.anthropic]
kind = "anthropic" # forward Claude Code's own credential unchanged
base_url = "https://api.anthropic.com"
[providers.openai]
kind = "responses" # translate Anthropic Messages -> OpenAI Responses
base_url = "https://api.openai.com/v1"
auth = "api_key"
api_key_env = "OPENAI_API_KEY" # env var the OpenAI key is read from
# effort = "high" # optional default reasoning effort for this provider
[providers.codex]
kind = "responses"
base_url = "https://chatgpt.com/backend-api"
auth = "chatgpt_oauth" # reuses ~/.codex/auth.json
# effort = "high"
# service_tier = "fast" # optional: opt in to Codex's "Fast" mode
# --- Routing: how a request's `model` id picks a provider ---
# A matching [models.upstream_model] entry wins first. [[routes]] is the legacy exact-match form checked next.
[[routes]]
model = "gpt-5.6-sol"
provider = "codex"
# upstream_model = "gpt-5.6-sol"
# effort = "high"
# service_tier = "priority" # optional: pin service_tier for this route
# Then prefix match.
[[route_prefixes]]
prefix = "gpt-"
provider = "openai"
# Optional: expose Claude-named aliases in the /model picker via discovery.
# The id MUST start with "claude" or "anthropic" or Claude Code ignores it.
# [[models]]
# id = "claude-opus-via-codex"
# display_name = "Opus (via Codex)"YAML equivalent
The same schema in YAML — save as shunt.yaml or shunt.yml. Tables become mappings and [[...]] arrays become lists:
server:
bind: "127.0.0.1:3001"
default_provider: anthropic
providers:
openai:
kind: responses
base_url: https://api.openai.com/v1
auth: api_key
api_key_env: OPENAI_API_KEY
routes:
- model: gpt-5.6-sol
provider: codex
route_prefixes:
- prefix: gpt-
provider: openaiRouting precedence
- Matching
[models.upstream_model]entry on the request’smodelid. - Exact
[[routes]]match on the request’smodelid. [[route_prefixes]]prefix match.server.default_provider— by defaultanthropic, so a model with no match falls through to Anthropic unchanged.
A route can override the forwarded model id (upstream_model), the reasoning effort (effort), and the response service_tier (Codex’s “Fast” mode) per model.
Partial overrides
Config maps are deep-merged, so a partial override of a built-in provider keeps the rest of its defaults:
# Only raise codex's default effort; everything else stays at the built-in values.
[providers.codex]
effort = "high"Validate
shunt check
# -> prints "config ok", or a specific error (bad bind address, unknown provider, …)See the Configuration Reference for every key, and Providers for adding new backends.