Skip to content

Anthropic

The default passthrough provider — forward the caller's own credential, inject an API key, or pool Claude subscription accounts with OAuth.

Updated View as Markdown

The built-in anthropic provider is shunt’s default destination: any model with no matching route is forwarded to api.anthropic.com unchanged (configurable via server.default_provider). It is a kind = "anthropic" provider — the upstream already speaks the Anthropic Messages API, so shunt passes the request through without translation and only decides which credential goes upstream.

One request field is the exception. A thinking block whose signature shunt minted for another provider — the Responses, Gemini, and Cursor paths each mint one, because a thinking block needs the field to be well-formed — is dropped before the request is forwarded, along with an assistant turn left holding nothing else. Anthropic decrypts that field and rejects a value it did not issue, so forwarding it would fail the request. A signature Anthropic issued is opaque to shunt and is never touched. The strip does not read conversation history, so it is not confined to sessions that used a mapped provider: a thinking block carrying no signature at all is dropped too, since shunt’s own Responses path emits one when the upstream returned no signature, and Anthropic rejects a thinking block whose signature it cannot verify either way.

A third-party Anthropic-kind provider — any kind = "anthropic" upstream that is not on api.anthropic.com and not a claude_oauth pool, such as MiniMax, Kimi, or Zhipu — may put a signature on its thinking blocks that Anthropic never issued. On the way to the client, streamed or not, shunt wraps that signature in an envelope of its own carrying a digest of the provider’s base_url (trailing / trimmed), never the URL itself, so the URL and any credentials in it never reach the client. The digest is unsalted and stable, though, so it confirms a guess: anyone who can guess the whole base_url — a well-known endpoint, or a weak credential embedded in one — can check the guess against it. When a later turn goes back to a provider with the same base_url, shunt unwraps it and forwards the original signature byte-for-byte, so that provider’s extended-thinking continuation keeps working; sent anywhere else, Anthropic included, the envelope counts as shunt-minted and the block is dropped as above. Switching between a third-party Anthropic-kind provider and Anthropic therefore drops that provider’s thinking blocks rather than failing the request, and returning to the same provider restores them. Responses from api.anthropic.com and from claude_oauth pools are never wrapped.

Auth mode Credential Use case
passthrough (default) the caller’s own x-api-key / Authorization transparent proxy — each client brings its own key or OAuth token
api_key a server-side key from an env var re-key every request with one operator-owned API key
claude_oauth one or more shunt-managed Claude subscription accounts spend Claude subscriptions, with pooling and quota-aware rotation

Quick start

Let a coding agent wire it up for you — shunt add prints an embedded setup blueprint (offline and read-only; the agent edits the config, the command never does):

shunt add upstream anthropic --print | claude

Or follow the manual steps below.

Configure the upstream

anthropic is built in — with no config at all, shunt already forwards unrouted models to it as passthrough. To declare it explicitly (for ordered failover or to change auth), use the preset, which supplies kind = "anthropic", base_url = "https://api.anthropic.com", and auth = "passthrough":

[[upstreams]]
name = "anthropic"
provider = "anthropic"

Explicit fields override preset defaults. The legacy [providers.anthropic] table form remains supported — but do not mix [[upstreams]] and [providers.*] in one file.

Passthrough (default)

Nothing else to configure: shunt forwards the client’s own x-api-key or Authorization header. No server-side secret belongs in the config. Which credential the client should send is covered in Connect Claude Code.

Server-side API key

Inject an operator-owned key instead of trusting callers to bring one:

[[upstreams]]
name = "anthropic"
provider = "anthropic"
auth = { mode = "api_key", env = "ANTHROPIC_API_KEY", header = "x_api_key" }

If you inject a server-side credential, gate the gateway with [server.auth] before exposing it beyond loopback.

Claude subscription accounts (claude_oauth)

Create a refreshable shunt-managed account once, then scope the upstream to it:

shunt login claude --name primary --mode oauth
[[upstreams]]
name = "anthropic"
provider = "anthropic"
auth = { mode = "claude_oauth", account = "primary" }

shunt login claude has three modes: --mode oauth runs shunt’s own refreshable OAuth flow (recommended), --mode import copies the current Claude Code login, and --mode setup-token creates a one-year inference-only token. Omit account (and accounts) to scan the whole shunt-managed account store — with several accounts in the store this becomes a load-balanced pool with session stickiness and quota-aware rotation. The full pooling behavior — explicit account entries, per-account thresholds, [server.pool] tuning, and failover — is documented in Anthropic Multi-Account.

Models & routing

As the default provider, anthropic needs no routes — everything you did not map elsewhere lands here. To pin a model to it explicitly inside an ordered failover chain:

[[models]]
id = "claude-sonnet-5"
display_name = "Claude Sonnet 5"

[models.upstream_model]
anthropic = "claude-sonnet-5"

The map key is the [[upstreams]].name, not the preset id. With auto_include_builtin_models = true (the default), GET /v1/models first tries the live model list from server.default_provider when it is Anthropic-kind; otherwise, it falls back to shunt’s builtin Claude catalog snapshot — see Model Discovery.

Auto-mode classifier

When Claude Code runs in auto mode, every action that leaves the workspace or touches the network goes through a permission classifier — a separate Messages request whose system prompt asks the model for a safety verdict on the pending action. It omits the Claude Code identity block api.anthropic.com looks for on a subscription-OAuth credential, as do side queries such as mods’ $.model.complete with CLAUDE_CODE_ATTRIBUTION_HEADER=0. shunt restores the identity on the auto-mode classifier and on any Claude Code request (identified by x-claude-code-session-id, a claude-cli/ user agent, or x-app: cli) that carries no accepted marker. The repair applies only when the outbound bearer is a subscription OAuth token, never to count_tokens (which upstream does not gate), and an empty x-claude-code-session-id does not count. Other clients, and requests on an API key, are left alone by this repair. The 429 troubleshooting row describes the failure that repairs.

The classifier does not use the session’s model by default. Measured on Claude Code 2.1.226, it takes a server-supplied model if the client was given one, otherwise the Sonnet tier alias, and only then the session’s own model — which is why ANTHROPIC_DEFAULT_SONNET_MODEL moves the classifier too, as seen on the wire. That was read out of the distributed client, so it describes the version measured rather than a contract.

Set classifier_model to pin the classifier request to one model from the gateway side, without touching what the session’s own turns run on:

[providers.anthropic]
kind = "anthropic"
base_url = "https://api.anthropic.com"
classifier_model = "claude-sonnet-5"

The key is accepted on kind = "anthropic" providers only, and it is a model remap inside that provider, never a route to a different one. The classifier request depends on stop_sequences: while shunt’s Responses translation still dropped that field, pointing the classifier at a Codex upstream through the Sonnet alias made it retry its first stage, and every permission check took 5–7 s longer. shunt now emulates stop_sequences on the Responses path, so that particular failure no longer applies — the key stays an in-provider remap regardless. An unset key (the default) leaves the classifier on whatever model the client asked for.

On a gateway-login deployment you can push the alias to the client instead, through managed settings:

[server.gateway.policies.cli.env]
ANTHROPIC_DEFAULT_SONNET_MODEL = "claude-sonnet-5"

That moves the classifier the same way, but it changes what sonnet resolves to for the whole session, not just the classifier — and the id must also appear in availableModels when that list is set.

Verify

shunt check    # -> config ok
shunt run
curl -sS http://127.0.0.1:3001/v1/models

Then send one minimal request (with passthrough, include your own credential; with claude_oauth, omit it):

curl -sS http://127.0.0.1:3001/v1/messages \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H 'anthropic-version: 2023-06-01' \
  -H 'content-type: application/json' \
  -d '{"model":"claude-sonnet-5","max_tokens":16,"messages":[{"role":"user","content":"Reply with OK."}]}'
Navigation

Type to search…

↑↓ navigate↵ selectEsc close