Discovery (GET /v1/models) can populate Claude Code’s /model picker automatically. By default, shunt returns the admin-curated [[models]] entries first, followed by the models it discovers on its own. Exact-id duplicates are removed in favor of the curated entry. Set the top-level auto_include_builtin_models = false to expose only the curated list.
For that second half, shunt asks server.default_provider for the live list when it is Anthropic-kind, using that provider’s authentication mode. With auth = "passthrough", it forwards the caller’s credential, so each caller sees the models that credential is entitled to — except a slot holding shunt’s own [server.gateway] JWT (or a configured [server.auth] client token) rather than a real upstream credential, which is not forwarded. With api_key, it uses the configured key. With claude_oauth, it uses the first resolvable, non-disabled account from the same effective account set as inference, including store-scanned accounts in account_scope order, without pool selection, cooldown, or quota accounting. Those two gateway-owned modes therefore expose a shared credential-scoped catalog. If the default provider is not Anthropic-kind, there is no credential to ask with, or the call fails or times out (2 s cap), shunt falls back to a builtin snapshot of the Claude catalog. Nothing is cached.
Discovered models need no dedicated [[routes]] entry — they resolve through your normal routing rules, falling back to server.default_provider when no [[routes]] or [[route_prefixes]] entry matches.
A discovered id is also selected verbatim, so it bypasses Claude Code’s built-in alias table entirely. That table is what remaps the bare opus/sonnet aliases behind a gateway session — see Model Aliases & 1M Context.
Claude Code ignores any discovered id that doesn’t begin with claude/anthropic (protocol reference), so use a Claude-named alias when curating a non-Claude model such as gpt-*.
Unify discovery and routing
A curated model can declare its routing and upstream translation directly. The one-entry map key is a configured provider name and its value is the model id sent upstream:
[[models]]
id = "claude-gpt-5.6-sol-via-codex"
display_name = "GPT-5.6-Sol (via Codex)"
[models.upstream_model]
codex = "gpt-5.6-sol"Selecting the alias routes it through codex and sends gpt-5.6-sol upstream. This map is the recommended exact-id form instead of a separate [[routes]] entry and takes precedence over [[routes]], [[route_prefixes]], and server.default_provider. Exactly one configured provider is supported per entry; invalid maps and a same-id [[routes]] entry are startup errors.
Use a separate route
Map-less [[models]] entries remain backward compatible. You can still pair one with a [[routes]] entry to rewrite it to the real upstream slug:
[[models]]
id = "claude-gpt-5.6-sol-via-codex" # must begin with claude/anthropic
display_name = "GPT-5.6-Sol (via Codex)"
[[routes]]
model = "claude-gpt-5.6-sol-via-codex" # the alias Claude Code sends
provider = "codex"
upstream_model = "gpt-5.6-sol" # real slug forwarded to the ChatGPT backendThen enable discovery (Claude Code v2.1.129+) and restart shunt + Claude Code:
export CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1The alias appears in /model labeled From gateway; selecting it sends claude-gpt-5.6-sol-via-codex, which shunt routes to codex and rewrites to gpt-5.6-sol.
For gpt-* ids without an alias, use ANTHROPIC_CUSTOM_MODEL_OPTION instead — see Connect Claude Code.
Claude Desktop recognizes only tier-named ids
Claude Code accepts any discovered id beginning with claude/anthropic, but Claude Desktop is stricter: it surfaces only tier-named ids — claude-sonnet-*, claude-opus-*, claude-haiku-*, claude-fable-*. A claude-<slug>-via-<provider> alias like the one above therefore shows up in Claude Code but is silently dropped by Claude Desktop, since gpt is not a tier name.
The builtin catalog is all tier-named, so it stays visible in Desktop; only your curated claude-<slug>-via-<provider> aliases are lost. To expose a non-Anthropic backend to Claude Desktop, reuse a tier-named id and map it with a [[routes]] upstream_model:
[[routes]]
model = "claude-sonnet-5" # a tier-named id Claude Desktop recognizes
provider = "codex"
upstream_model = "gpt-5.6-sol" # real backend slugSelecting it in Desktop resolves to the intended upstream. The route overrides the builtin catalog entry’s default routing for that id, so pick a tier name whose backend mapping stays meaningful to your users.
Discovery needs a gateway credential
A claude.ai OAuth login alone won’t trigger discovery. Claude Code only issues the /v1/models request when ANTHROPIC_AUTH_TOKEN, an API key, or an apiKeyHelper is set; under a plain Max/Pro subscription login it sends nothing — no request reaches shunt, no cache is written — even with the flag on. See choosing the credential; claude setup-token is the recommended route.
When shunt has [server.auth] enabled, discovery also requires a valid client token. It accepts the configured client-token header (for example through ANTHROPIC_CUSTOM_HEADERS) and Claude Code’s discovery credential forms: x-api-key or Authorization: Bearer. Missing or invalid inbound credentials return 401 authentication_error. Without [server.auth], discovery remains open.
Debugging
Discovery fails silently (3-second timeout, any redirect counts as failure) and falls back to the cached/built-in list. Run claude --debug and look for [gatewayDiscovery] lines to confirm it ran.