How to switch models in Claude Code with one command
Claude Code speaks Anthropic's Messages protocol and so do the major gateways. A small shell wrapper points it at any model, with live rate cards to prove it.
This morning the cheapest listed route for GLM 5.2, a reasoning model with a million-token context window, charged $0.0945 per million cached input tokens. DeepSeek V4 Flash charged $0.0028 for the same thing. Claude Opus 5 charged $0.50.
What sits between you and any of those numbers is which host your coding agent points at, and that's an environment variable. Claude Code speaks Anthropic's Messages protocol, and both Vercel AI Gateway and OpenRouter implement it. A short shell wrapper, with nothing to run as a proxy, turns the model you're using in a given coding session into a command-line argument:
claude-gateway zai/glm-5.2

Why a shell wrapper is enough
Claude Code is an HTTP client with a fixed protocol. Point ANTHROPIC_BASE_URL at a different host and every request goes there instead, tool calls and token counting included. Vercel AI Gateway publishes Anthropic-compatible endpoints for exactly this purpose. OpenRouter calls its equivalent the Anthropic Skin, says it behaves exactly like the Anthropic API, and passes through Thinking blocks and native tool use. Neither needs a local proxy server.
There's nothing to install, nothing to keep running, and no request translation that can drift out of date when the protocol changes. The Claude Agent SDK inherits it for free, because the SDK spawns Claude Code as a subprocess and reads the same variables.
The three exports
Vercel's documented setup is three lines:
export ANTHROPIC_BASE_URL="https://ai-gateway.vercel.sh"
export ANTHROPIC_AUTH_TOKEN="your-ai-gateway-api-key"
export ANTHROPIC_API_KEY=""
The empty third line is the one that costs people money. Claude Code checks ANTHROPIC_API_KEY first, and a non-empty value there is used instead of ANTHROPIC_AUTH_TOKEN. It's sent as the X-Api-Key header and it overrides your Claude subscription even when you're logged in. A leftover Anthropic key in your shell profile means your gateway is configured, your wrapper looks correct, and your requests are going straight to the vendor you were trying to route around.
OpenRouter's documented setup inverts the assignment:
export OPENROUTER_API_KEY="<your-openrouter-api-key>"
export ANTHROPIC_BASE_URL="https://openrouter.ai/api"
export ANTHROPIC_API_KEY="$OPENROUTER_API_KEY"
export ANTHROPIC_AUTH_TOKEN="" # Important: Must be explicitly empty
export CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1
OpenRouter puts the key in ANTHROPIC_API_KEY and blanks ANTHROPIC_AUTH_TOKEN, and warns that leaving ANTHROPIC_AUTH_TOKEN unset rather than empty can send Claude Code back to authenticating against Anthropic's servers. Copying one provider's snippet while running the other's gateway is the most common way this setup fails.
Two commands can help. /logout clears a cached Anthropic OAuth session, which a stale login leaves behind; it doesn't clear a real token exported from your shell profile. /status prints which auth variable is in play and which base URL is live:
> /status
Auth token: ANTHROPIC_API_KEY
Anthropic base URL: https://openrouter.ai/api
The wrapper
Save this as ~/.local/bin/claude-gateway and chmod +x it. It takes a full catalog model ID, validates it against the live catalog, sets every model slot, and hands off to claude. It defaults to Vercel AI Gateway, and GATEWAY=openrouter claude-gateway z-ai/glm-5.2 runs the same weights through OpenRouter instead.
#!/usr/bin/env bash
# claude-gateway <provider/model> [claude args...]
# GATEWAY=vercel|openrouter (default: vercel)
set -euo pipefail
MODEL="${1:?usage: claude-gateway <provider/model> [claude args...]}"
shift
GW="${GATEWAY:-vercel}"
case "$GW" in
vercel)
BASE_URL="https://ai-gateway.vercel.sh"
export ANTHROPIC_AUTH_TOKEN="${AI_GATEWAY_API_KEY:?AI_GATEWAY_API_KEY is not set}"
export ANTHROPIC_API_KEY=""
;;
openrouter)
BASE_URL="https://openrouter.ai/api"
export ANTHROPIC_API_KEY="${OPENROUTER_API_KEY:?OPENROUTER_API_KEY is not set}"
export ANTHROPIC_AUTH_TOKEN=""
;;
*)
echo "claude-gateway: unknown GATEWAY '$GW' (use vercel or openrouter)" >&2
exit 1
;;
esac
if [[ "$MODEL" != */* ]]; then
echo "claude-gateway: use a full creator/model ID from the catalog" >&2
exit 1
fi
CATALOG="$(curl -fsS --max-time 8 "$BASE_URL/v1/models")"
if ! printf '%s' "$CATALOG" | grep -Fq -- "\"id\":\"$MODEL\""; then
echo "claude-gateway: '$MODEL' is not in the $GW catalog" >&2
exit 1
fi
export ANTHROPIC_BASE_URL="$BASE_URL"
export ANTHROPIC_MODEL="$MODEL"
export ANTHROPIC_DEFAULT_HAIKU_MODEL="$MODEL"
export ANTHROPIC_DEFAULT_SONNET_MODEL="$MODEL"
export ANTHROPIC_DEFAULT_OPUS_MODEL="$MODEL"
export CLAUDE_CODE_SUBAGENT_MODEL="$MODEL"
export CLAUDE_CODE_MAX_OUTPUT_TOKENS="${CLAUDE_CODE_MAX_OUTPUT_TOKENS:-32000}"
exec claude "$@"
Here's three key things this script is doing:
Validating against the live catalog before launching. Model IDs carry the creator prefix: deepseek/deepseek-v4-flash-0731, not the bare name. They also disagree across gateways for the same weights. OpenRouter lists GLM 5.2 as z-ai/glm-5.2 and Vercel lists it as zai/glm-5.2. An unauthenticated GET /v1/models check catches the typo, or a retired model, before Claude Code enters a retry loop.
Set every model slot, not just the primary one. ANTHROPIC_MODEL covers your main thread. The haiku alias also serves background functionality, and CLAUDE_CODE_SUBAGENT_MODEL covers the subagents Claude Code spawns. Miss those and part of your session quietly keeps talking to the default vendor.
Pin the output cap. Claude Code defaults to 32,000 output tokens for model IDs it doesn't recognize, which includes most gateway-specific names. Setting it explicitly means the number is yours rather than a fallback.
Leave prompt caching alone when you're pointed at a gateway. Local engines like llama-server and ds4 manage their own KV cache, which is why local wrappers turn Claude's caching off. With ds4's disk cache, warm starts dropped from 66 seconds to 10. On a gateway, caching is the reason the bill is small.
Understanding the environment variables
| Variable | What it does | What to set it to |
|---|---|---|
ANTHROPIC_BASE_URL |
Routes every request to a proxy or gateway instead of the Anthropic API | https://ai-gateway.vercel.sh, https://openrouter.ai/api, a direct provider, or http://127.0.0.1:8080 |
ANTHROPIC_AUTH_TOKEN |
Supplies the Authorization header value, prefixed with Bearer |
Vercel: your gateway key. OpenRouter: empty string |
ANTHROPIC_API_KEY |
Sent as X-Api-Key; checked first and overrides both ANTHROPIC_AUTH_TOKEN and your subscription |
Vercel: empty string. OpenRouter: your key |
ANTHROPIC_MODEL |
The primary model; --model and /model override it |
A full creator/model ID from the gateway catalog |
ANTHROPIC_DEFAULT_SONNET_MODEL |
What the sonnet alias resolves to |
Same ID, or a cheaper one for general coding |
ANTHROPIC_DEFAULT_OPUS_MODEL |
What the opus alias resolves to |
Same ID, or your best model |
ANTHROPIC_DEFAULT_HAIKU_MODEL |
What haiku resolves to, and the model used for background functionality |
Same ID, or the cheapest usable model |
ANTHROPIC_DEFAULT_FABLE_MODEL |
What the fable alias resolves to; on OpenRouter, the only way to make Fable selectable, since it isn't offered in /model by default |
Same ID |
CLAUDE_CODE_SUBAGENT_MODEL |
Model for subagent tasks Claude Code spawns | Same ID |
CLAUDE_CODE_MAX_OUTPUT_TOKENS |
Max output tokens; defaults to 32000 for unrecognized model IDs | An explicit number your model actually supports |
CLAUDE_CODE_MAX_CONTEXT_TOKENS |
Overrides the context window Claude Code assumes for the active model | The real window, when the gateway model's name doesn't match a Claude one |
CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY |
Populates the /model picker from the gateway's /v1/models; off by default |
1, if you want to browse from inside the CLI |
CLAUDE_CODE_ALWAYS_ENABLE_EFFORT |
Sends the effort parameter even for model IDs Claude Code doesn't recognize as effort-capable | 1 on gateways serving models under custom identifiers |
CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS |
Strips Anthropic-specific beta headers | 1 when your route lands on Bedrock or Vertex |
CLAUDE_CODE_API_KEY_HELPER_TTL_MS |
Refresh interval for the credential produced by apiKeyHelper |
A millisecond interval, when you fetch the key from a keychain instead of a file |
ENABLE_TOOL_SEARCH |
MCP tool search turns off automatically on a non-first-party host | true if your gateway forwards tool_reference blocks |
One important precedence rule to understand that can save you an afternoon of debugging: a variable set in a settings.json env block beats the same variable exported from your shell.
If the wrapper looks right and the routing is wrong, check the settings file before you check your profile.
Base URLs by backend
Vercel AI Gateway. https://ai-gateway.vercel.sh, key in ANTHROPIC_AUTH_TOKEN, ANTHROPIC_API_KEY blanked. You pay provider list price with zero token markup, and you get routing controls: provider order and only, provider timeouts, model fallbacks, and sorting by cost, time to first token, or output throughput, with every model and provider attempt recorded in the response metadata.
OpenRouter. https://openrouter.ai/api, key in ANTHROPIC_API_KEY, ANTHROPIC_AUTH_TOKEN blanked. Upstream rates pass through, plus a 5.5% fee when you buy credits and a $0.80 minimum on card payments. Two behaviors change how you use it: rolling ~creator/model-latest aliases are excellent for scouting and bad for reproducibility, since the same slug can resolve elsewhere next week; and a session_id pins a conversation to the provider holding your warm cache.
A direct first-party provider. Whatever base URL its docs give you, with the key in whichever variable that provider documents. You give up failover and get one relationship to reason about, which is the right trade for the one model whose behavior you've actually evaluated.
A local server. http://127.0.0.1:8080, and no credential at all. ds4 serves the Anthropic-style API directly at 30 to 40 tokens per second on an M5 Max with nothing leaving the machine, and llama-server hosts Llama-3.3-70B and Qwen2.5-Coder-32B on ports 8080 and 8081. This is the one backend where you turn Claude's prompt caching off, because the local engine manages its own KV cache.
Keep the key out of your shell profile
Those three exports have one real problem: they leave a long-lived credential sitting in ~/.zshrc. OpenRouter's own docs say it plainly — a plaintext key in a shell profile is easy to commit to a dotfiles repo or paste into a gist, and a leaked one has to be revoked and rotated. On a Mac you fix that in two commands, and Vercel documents the pattern for this exact key:
# store it once
security add-generic-password -a "$USER" -s AI_GATEWAY_API_KEY \
-w "your-ai-gateway-api-key"
# read it at launch instead of hardcoding it
export AI_GATEWAY_API_KEY="$(
security find-generic-password -a "$USER" -s AI_GATEWAY_API_KEY -w
)"
Rotation is the same command with -U. The wrapper needs no changes, because it already reads AI_GATEWAY_API_KEY or OPENROUTER_API_KEY from the environment, and OpenRouter documents the same one-liner for its own key. Claude Code is already a Keychain client: on macOS it keeps its own cached session there as a generic password named Claude Code-credentials, and native installs never write ~/.claude/.credentials.json.
There's a better version. Claude Code's apiKeyHelper setting takes a command that produces the auth value, sends the result as both the X-Api-Key and Authorization: Bearer headers, and refetches it on the interval you set with CLAUDE_CODE_API_KEY_HELPER_TTL_MS:
{
"apiKeyHelper": "/usr/bin/security find-generic-password -a $USER -s AI_GATEWAY_API_KEY -w",
"env": {
"ANTHROPIC_BASE_URL": "https://ai-gateway.vercel.sh"
}
}
Two things fall out of that. The credential is never written to a file or parked in a shell variable — it is fetched, used, and fetched again on a timer. And because the helper's output populates both headers, the Vercel-versus-OpenRouter variable inversion stops mattering at all.
The other option is BYOK: register your own provider keys and let the gateway authenticate upstream with those instead of spending its credits. What the two gateways offer differs enough to matter.
Vercel. Credentials go in the dashboard, scoped to the whole team and shared across projects, with no markup added; a request whose BYOK credential fails is retried on Vercel's own system credentials. That needs the paid tier and purchased credits, because the fallback is billed against your credit balance.
You can also pass credentials per request through providerOptions.gateway.byok. Know the tradeoff before you lean on it: BYOK spend is metered separately and doesn't count toward team, project, or API-key budgets, so a budget cannot cap it. And with Zero Data Retention on, the gateway skips BYOK keys unless you mark a key ZDR-compliant.
OpenRouter. Keys are stored encrypted in workspace settings and cost 5% of what the same model and provider would have cost on OpenRouter, deducted from credits, with a plan-dependent free allowance measured in list-price inference: $25,000 a month on pay-as-you-go, $200,000 on Enterprise.
Keys sort into Prioritized and Fallback sections tried in order, and an "Always use for this provider" toggle forbids falling back to shared capacity. The part that matters for a team: every key takes a model filter, an API-key filter, and a member filter restricting which workspace members may use it.
Budgets have the same blind spot as Vercel's, with an escape hatch — BYOK spend is excluded by default until you turn on "Include BYOK spend". And your own key buys you nothing past a data policy: ZDR and data_collection restrictions are applied before BYOK endpoints are created.
For one laptop, the Keychain is the answer and BYOK is not. BYOK relocates the credential rather than protecting it, and on both gateways it steps outside the budget that was supposed to contain a runaway loop. BYOK earns its place when you already hold provider capacity, prepaid quota, or a data agreement your requests need to run under.
The cache-read line is the whole bill
A coding agent resends its entire context on every turn: system prompt, tool definitions, conversation so far. Providers bill repeat reads of that prefix at a discounted cache-hit rate, so the cache-read column, not the headline input price, decides what you pay.
My own 12-day Claude Code ledger on one laptop makes the shape concrete: 4,127 assistant turns, 1.065 billion input tokens served as cache reads against 33 million cache writes and 183 thousand fresh input tokens, with 5 million tokens generated. Cache reads were 96.6% of all input, and input outnumbered output 218 to 1. That's one engineer's personal sample rather than a benchmark, but the composition transfers: any long-context agent loop looks like this.
Push those exact token counts through three rate cards published on August 14, 2026:
| Route | Cache read /M | Input /M | Output /M | Same 12 days ~ | Per month |
|---|---|---|---|---|---|
| DeepSeek V4 Flash, DeepSeek first-party | $0.0028 | $0.14 | $0.28 | $4.41 | $11 |
| GLM 5.2, cheapest OpenRouter route | $0.0945 | $0.63 | $1.98 | $111 | $277 |
| Claude Opus 5, first-party | $0.50 | $5.00 | $25.00 | $865 | $2,162 |
The dollar totals are illustrative, not a benchmark: the rate cards are the durable part and the token mix is mine. The Opus 5 row includes cache writes at its published $6.25 per million; neither open-weight listing published a separate cache-write rate, so those two rows count reads, fresh input, and output only.
Re-derive the table before you trust it, because these cards move faster than a blog post. GLM 5.2's cheapest OpenRouter cache-read rate was $0.12 per million four days ago and $0.0945 when I re-checked this morning, a 21% cut inside one week. Measured throughput has landed 30 to 45% below published figures on top of that.
Two things fall out of the arithmetic. Cheap doesn't mean equally cheap: GLM 5.2's cheapest cache-read rate is roughly 34 times DeepSeek V4 Flash's, which becomes a 25-fold difference in the bill for identical work. And the same model is priced far apart across routes. GLM 5.2 cache reads ran from $0.0945 on OpenRouter's cheapest provider to $0.275 on Vercel's Alibaba route, with the model's own creator, Z.AI, listed at $0.26. Fifteen Vercel endpoints and more than twenty OpenRouter endpoints serve those same weights. Moving between them is a one-string change in the wrapper.
Running four sessions instead of one
Once a capable model costs a few hundred dollars a month per continuous session, the constraint stops being money. Four times the $277 row above is about $1,108 a month for four parallel GLM 5.2 sessions, roughly half of one Opus 5 session at $2,162. On DeepSeek V4 Flash's first-party route that same budget buys close to two hundred of them.
claude-gateway zai/glm-5.2 in four terminals is four sessions on one key and one balance, and nothing stops you from pointing one of them at a different model to compare behavior on the same task. GLM 5.2 reaches a million-token context on some routes, while OpenRouter's listings for the same model run as low as 96,890 tokens depending on the provider. Throughput varies just as widely: Vercel measured p50 output for that model from 25.5 tokens per second on Morph up to 329.5 on Wafer. Vercel also lists zai/glm-5.2-fast, the high-speed variant with the same 1M context and 1.5 to 2 times the output throughput, at $0.21 per million cache reads against the standard model's cheapest $0.14 — 50% more on the line that dominates the bill, which pays for itself only when you're watching the stream.
The limits are worth stating plainly. Parallel sessions multiply cache writes, which the arithmetic above barely counts and your invoice will. Each session needs its own git worktree or branch unless you enjoy resolving conflicts you created yourself. And human review becomes the bottleneck well before the bill does: four agents producing diffs faster than one person can read them is not four times the throughput.
What breaks
A fallback can succeed technically and fail semantically. Provider fallback within one model preserves broad behavior. Falling to a different model changes tool behavior, reasoning, and output quality, and the request still returns 200.
The same open weights behave differently across hosts. Quantization, context length, tool support, tokenizer patches, and inference parameters all vary between deployments. GLM 5.2's OpenRouter endpoints advertise context lengths from 96,890 tokens up to 1,048,576, at fp4, fp8, or undisclosed quantization. Pin the route you evaluated, and treat a new backend as an experiment until it survives representative work: OpenRouter itself only guarantees Claude Code against the Anthropic first-party provider.
A routing change destroys cache economics. After a cached request, OpenRouter keeps the conversation on the same provider endpoint, and a session_id establishes that affinity before the first cache hit. Failover reopens routing, which is availability bought with a cold cache. On Vercel, pin the preferred provider for long conversations and treat failover as a cache miss you agreed to. Caching support isn't uniform either: every Vercel endpoint for zai/glm-5.2 reports implicit caching support while every OpenRouter endpoint for z-ai/glm-5.2 reports none. When cache reads carry 96.6% of your input tokens, that one flag decides the bill.
A low prepaid balance is an availability problem. Both gateways run on prepaid credits, so configure auto top-up and a budget: one prevents an accidental outage, the other contains a runaway loop. Rate caps bite the same way. A long streaming read of mine hit a shared 100k-tokens-per-minute cap mid-turn, came back 429, and Claude Code spent the remaining pool on retries until I pointed the wrapper somewhere else.
Aliases are for scouting, not production. OpenRouter's rolling aliases like ~anthropic/claude-sonnet-latest make browsing fast and reproduction hard, because the same slug can resolve to a different model next week. Pin exact IDs anywhere the output matters.
Frequently asked questions
Can I use Claude Code without an Anthropic subscription?
Yes. Point ANTHROPIC_BASE_URL at a gateway and supply that gateway's key. Whichever credential variable you set overrides the subscription: ANTHROPIC_API_KEY explicitly takes precedence over a Pro, Max, Team, or Enterprise login.
Do tool calls work through a gateway?
Yes on both gateways covered here. Vercel exposes Anthropic-compatible endpoints for Claude Code and the Agent SDK, and OpenRouter's Anthropic Skin passes through Thinking blocks and native tool use. MCP tool search is the exception: it turns off by default on a non-first-party host, and you re-enable it with ENABLE_TOOL_SEARCH=true if your gateway forwards tool_reference blocks.
Where should I keep the gateway key on a Mac?
In the login Keychain, read at launch rather than pasted into a shell profile. Better, point Claude Code's apiKeyHelper at a security find-generic-password command so the value is fetched on a refresh interval and never written to a file at all.
Why is my gateway being ignored?
Almost always a credential that takes precedence over the one you meant to use. Run /status and read which auth variable Claude Code reports. If it names the wrong one, blank the other explicitly. An unset variable isn't the same as an empty one. If a stale Anthropic login is in play, /logout clears the cached session, though not a token exported from your shell. Then check whether a settings.json env block is overriding your shell.
How do I switch models mid-session?
/model and the --model flag both override ANTHROPIC_MODEL. To pick from the gateway's own catalog inside the CLI, set CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1, which populates the picker from /v1/models. On OpenRouter that picker is a curated shortlist rather than the whole catalog, and pinning a model with an environment variable bypasses it. Relaunching through the wrapper with a different model argument is the cleaner move when you want a fresh context anyway.
Does this work with other coding agents?
The Claude Agent SDK does, unchanged: it spawns Claude Code as a subprocess and reads the same environment variables. Other agents need whatever base-URL and credential mechanism they define. The pattern generalizes; the variable names don't.
One laptop is easy, a team is the hard part
Everything above protects one credential on one machine. A team sharing gateway access hits problems the Keychain can't reach: a key per developer instead of one shared string, spend attributed to a person instead of a pool, rotation that doesn't require a group chat, and revocation that actually completes when someone leaves.
The gateways hand you pieces of it — per-key budgets and per-key usage on both, per-member filters on OpenRouter's BYOK keys — but notice where the pieces stop: BYOK spend escapes the budget entirely on Vercel, and on OpenRouter until someone opts in.
Vercel's answer on its own platform shows where this ends up: a deployment authenticates through OIDC, so there's no long-lived gateway key to rotate at all, and local development pulls a short-lived token instead. If you want the general version of that problem, our guides on machine identity for AI agents and CLI authentication cover the credential choices, and managing secrets for AI agents covers where they should live.