Back to guides
Integration·July 26, 2026·Updated September 12, 2026·12 min read

Claude Code Router — route Claude Code to any model, or skip the router entirely

Most people reaching for claude-code-router only want Claude Code somewhere cheaper — and that is a base-URL swap, not an install. Here is when the router actually earns its place, how it is configured now that config.json no longer does anything, and an honest list of what a gateway breaks.

Last reviewed on .

Most people searching for Claude Code Router want one of two different things: route Claude Code across several model providers, or just run Claude Code somewhere cheaper than Anthropic's list price. Only the first needs the router. Claude Code reads ANTHROPIC_BASE_URL natively, so the second is three environment variables and no extra software at all.

This guide covers both paths, with the exact variable names, the credential trap that produces a silent 401, and an honest list of what stops working behind any gateway. If you have read another CCR write-up recently, skip to Option B first: the config.json those articles tell you to edit is no longer the configuration the router reads.

Which one do you actually need?

What you wantUse
Run Claude in Claude Code, cheaperBase URL swap — no install
A different model per task (plan / code / background)Either — ANTHROPIC_DEFAULT_* vars, or the router
Mix several providers behind one Claude Codeclaude-code-router
Drive Claude Code with non-Claude modelsclaude-code-router
Per-request logs: provider, model, latency, tokens, costclaude-code-router
Send subagents to a different model than the main loopclaude-code-router — the tier variables can't split that

The router is a local service: one more process to run, configure and keep current — and, as of 2026, a desktop app with its own UI rather than a file you edit. It earns that cost when you genuinely need multi-provider routing, per-request accounting, or subagent-level model selection. It does not earn it when a base URL would have done.

Option A — the base URL swap (no install)

Kunavo serves the native Anthropic Messages API at /v1/messages, which is the endpoint Claude Code calls. Point it there:

~/.zshrc
export ANTHROPIC_BASE_URL=https://api.kunavo.com
export ANTHROPIC_AUTH_TOKEN=sk-kn-...              # create at kunavo.com/app/keys
export ANTHROPIC_MODEL=claude-sonnet-4-6           # exact slug — see the table below
export ANTHROPIC_DEFAULT_HAIKU_MODEL=claude-haiku-4-5   # background tasks

ANTHROPIC_BASE_URL is the origin only — Claude Code appends /v1/messages itself, so don't include a path. Get the key from the dashboard after signing up and topping up $10; it's shown once.

Which credential variable — and why it matters

Claude Code sends the two credential variables in different HTTP headers, and a key in the header the server doesn't read fails with 401:

VariableHeader sentOn Kunavo
ANTHROPIC_AUTH_TOKENAuthorization: BearerRecommended — works everywhere
ANTHROPIC_API_KEYx-api-keyWorks for chat; breaks model discovery

Prefer ANTHROPIC_AUTH_TOKEN for two concrete reasons. First, ANTHROPIC_API_KEY needs a one-time approval in an interactive session, and a key you declined once is ignored afterwards with no prompt — a confusing failure where the variable is plainly set and plainly unused. Second, Claude Code's gateway model discovery sends only the bearer token when ANTHROPIC_AUTH_TOKEN is set, and falls back to x-api-key otherwise — and Kunavo's /v1/models endpoint reads the bearer header only. With ANTHROPIC_API_KEY, discovery fails silently.

Make it stick

Shell exports only apply to that terminal and anything launched from it — an editor opened from the dock won't see them, and neither will background agents. Put the values in a settings file to cover everything:

~/.claude/settings.json
{
  "env": {
    "ANTHROPIC_BASE_URL": "https://api.kunavo.com",
    "ANTHROPIC_AUTH_TOKEN": "sk-kn-...",
    "ANTHROPIC_MODEL": "claude-sonnet-4-6",
    "ANTHROPIC_DEFAULT_HAIKU_MODEL": "claude-haiku-4-5"
  }
}

Use ~/.claude/settings.json for all projects. Never put a key in a project's .claude/settings.json — that file gets committed.

Verify before you trust it

Test the endpoint directly first, so a failure points at the configuration rather than at Claude Code:

verify.sh
curl -X POST "$ANTHROPIC_BASE_URL/v1/messages" \
  -H "Authorization: Bearer $ANTHROPIC_AUTH_TOKEN" \
  -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -d '{"model":"claude-sonnet-4-6","max_tokens":1,"messages":[{"role":"user","content":"."}]}'

# A response starting with {"id":"msg_ means the URL and key both work.
# 401 -> the key is in the wrong header; see "Which credential variable" below.

Then start claude from the same shell and run /status. An Anthropic base URL line showing api.kunavo.com and an Auth token line naming your variable confirm both halves are live.

Set a custom model — pick the slug explicitly

Kunavo resolves model slugs by exact match and does not alias date-suffixed names, so claude-sonnet-4-5-20250929 returns 404 where claude-sonnet-4-6 succeeds. Always set ANTHROPIC_MODEL rather than relying on the built-in default:

RoleSlugInput / output per 1M
Everyday coding (default)claude-sonnet-4-6$1.20 / $6.00
Near-Opus coding qualityclaude-sonnet-5$2.00 / $10.00
Hardest refactors, plan modeclaude-opus-5$2.00 / $10.00
Background tasks, quick asksclaude-haiku-4-5$0.40 / $2.00

The alias variables give you per-task routing without any router at all: ANTHROPIC_DEFAULT_OPUS_MODEL backs the opus alias and plan mode, ANTHROPIC_DEFAULT_SONNET_MODEL backs sonnet, and ANTHROPIC_DEFAULT_HAIKU_MODEL backs haiku plus Claude Code's background work — the summaries and titles that quietly accumulate cost. Pointing that one at claude-haiku-4-5 is the single highest-value line in the config. (ANTHROPIC_SMALL_FAST_MODEL is the deprecated spelling of the same setting.)

Optional: show every model in the picker

Set CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1 (Claude Code v2.1.129+) and Claude Code queries GET /v1/models at startup, adding what it finds to the /model picker labelled From gateway. Kunavo serves that endpoint, so every enabled Claude model shows up and /model becomes a live menu instead of a list you maintain by hand. This is the path that requires ANTHROPIC_AUTH_TOKEN — see above.

Option B — claude-code-router, as it actually works now

Start here, because almost everything written about CCR is describing a version that no longer exists. The router used to be a JSON file and a ccr code command. It is now a local control plane with a desktop app, a management UI, request logs and a model gateway — and the config.json every tutorial shows you does not configure it any more.

CCR keeps its runtime configuration in ~/.claude-code-router/config.sqlite (%APPDATA%\claude-code-router\config.sqlite on Windows). A legacy config.json is read once, as a migration source, when no SQLite configuration exists yet. After that first run, editing it does not affect the running configuration — with no error and no warning. Your carefully pasted Providers block is simply not the config.

That includes this page before 2026-09-06: the JSON snippet that used to sit here was wrong, and it is the kind of wrong that costs an afternoon, because nothing tells you it was ignored. Configuration now happens in the UI (or, for backup, Settings → Export data — do not copy live SQLite files while CCR is running).

Install and launch

CCR ships two ways: a desktop app from GitHub Releases (tray, auto update, desktop integrations) and an npm CLI for headless or supervised deployments. They share the same configuration directory.

install and launch
# CCR ships as a desktop app (GitHub Releases) or an npm CLI. Both read the
# same ~/.claude-code-router directory. The CLI needs Node.js 22+.
npm install -g @musistudio/claude-code-router
ccr ui        # management UI on :3458, model gateway on :3456

# Configure the provider and an Agent Config profile in that UI, then launch
# Claude Code through the profile by name:
ccr "Claude Code - Kunavo"        # npm CLI
ccr-app "Claude Code - Kunavo"    # the desktop app's own launcher

# There is no 'ccr code' in the current command reference. The service commands
# are start / ui / stop / serve / web; everything else is a profile name.

Adding Kunavo is one provider entry — Providers → Add provider, preset Other / custom API endpoint, endpoint https://api.kunavo.com, your sk-kn- key — and one Agent Config → Add profile → Claude Code profile. The field-by-field version, including Check Connection and model discovery, is on the Claude Code Router integration page. The rest of this section is what that page doesn't cover: the credentials, the economics and the failure modes.

Three credentials, and which one a 401 is about

This is the most common way a working setup looks broken. CCR has three separate secrets and they authenticate three different hops:

CredentialAuthenticatesWhere it goes
Your Kunavo key (sk-kn-…)CCR → KunavoProviders → the provider's API key field
A CCR client keyAny client → the CCR gatewayCreated on the API Keys page; without one the gateway rejects model requests
The management token (ccr_web_token)You → the CCR UI and RPCIn the URL ccr ui prints — treat it as a password

The port pairing catches people too: management defaults to 127.0.0.1:3458 and the model gateway to 127.0.0.1:3456. A base URL pointed at 3458 reaches the UI, not the gateway. (Docker deliberately collapses both behind one Nginx endpoint, which is why Docker instructions look different.) A reachable UI is not a working gateway: check /health on the gateway address, and confirm Server shows Running.

The per-tier map is the point

Claude Code does not ask for a model, it asks for a tier — the main loop wants Sonnet or Opus, and background work (subagents, search, summaries, conversation titles) wants the small fast one. A Claude Code profile in Agent Config exposes those as separate fields: a default Model plus optional Fable, Opus, Sonnet and Haiku overrides, each taking a Provider/model value. Leave a tier empty and Claude Code picks it.

TierMap it toInput / output per 1MWhat actually runs there
OpusKunavo/claude-opus-5$2.00 / $10.00Plan mode, hard refactors
Sonnet (default)Kunavo/claude-sonnet-5$2.00 / $10.00The main agent loop — most of your tokens
Sonnet, cheaperKunavo/claude-sonnet-4-6$1.20 / $6.00Same loop at 60% off list
HaikuKunavo/claude-haiku-4-5$0.40 / $2.00Subagents, file triage, titles, summaries

Read that table before you copy someone's tier map, because the obvious split does not pay here: claude-opus-5 and claude-sonnet-5 cost the same on Kunavo ($2.00 / $10.00 each), so demoting the Opus tier to Sonnet 5 saves nothing at all. Two levers do pay. The Haiku tier is 5× cheaper than either, and it carries volume you never see — every subagent, every file-triage pass, every generated title. And claude-sonnet-4-6 on the main loop is 40% below Sonnet 5 if you can live on the previous generation.

Subagent routing — the thing the tier map can't do

Tier overrides pin all subagents to one model. CCR can go finer: when a Claude Code request matches the built-in route, it injects the list of available models into the Agent / Task tool description, and Claude Code prefixes each spawned agent's prompt with a tag naming the model it wants:

<CCR-SUBAGENT-MODEL>provider/model</CCR-SUBAGENT-MODEL>

CCR strips the tag and routes that one request accordingly, so a search subagent can run on Haiku while a review subagent runs on Opus, chosen per task rather than pinned. The switch is easy to miss: the mechanism is off until at least one model has a Description on the Models page. With no descriptions CCR injects nothing, and every subagent quietly falls back to the profile default. Write the descriptions as task fit — “code search, file triage, cheap parallel subagents” on Haiku, “architecture analysis, high-risk review” on Opus. When it works, request logs show builtin:claude-code-subagent as the route reason.

Protocol choice, and what it costs you in cache

CCR probes the endpoint and picks a wire protocol. Give it the bare origin https://api.kunavo.com and it talks Anthropic Messages; give it https://api.kunavo.com/v1 and it talks the OpenAI-compatible format. Both surfaces are live on the same key, and you can override the auto-detection in Advanced settings.

Prefer the Anthropic Messages form. It keeps cache_control on the wire, so prompt caching reaches the model and cached input bills at 10% of the input rate (how it works) — on an agent loop that resends a stable prefix every step, the largest single saving available. The honest caveat is that a router in the path still edits requests: CCR removes the billing-header system message Claude Code injects, and adds the model list to tool descriptions when subagent routing is on. Both sit ahead of your cache breakpoints, so each change to that content costs one cache miss while the new prefix warms. It is stable after that — but it is a real reason Option A caches marginally better than Option B, on top of being less to run.

Fallback: retry versus failover

The Routing page's Default on failure is worth setting before you need it. Retry re-sends to the same model on 408, 409, 429 and 5xx, honouring Retry-After and otherwise backing off exponentially from 1s to a 30s cap. Fallback targets walks an ordered list of backup models and triggers on any 4xx or 5xx, on the theory that model-not-found or a provider rejection may only affect the current target. Individual rules can override the global setting. When a fallback runs, the response carries x-ccr-fallback-attempts and x-ccr-fallback-model so you can tell after the fact.

Verify it is actually in the path

Launch Claude Code from the profile, send one message, then open Request logs in CCR. The row shows request model (what Claude Code asked for), resolved provider and resolved model (where it went) — that triple is the proof. Inside the CLI, /model lists the models CCR exposes. If Claude Code replies but no log row appears, you started Claude Code yourself rather than through CCR, and the profile's scope is Only opened from CCR.

What a coding session costs

Claude Code resends the system prompt, conversation and fresh file context on every step, so the per-token rate compounds quickly. At Kunavo rates on claude-sonnet-4-6:

UnitTokens (in / out)KunavoAnthropic list
One agentic step25,000 / 1,200$0.037$0.093
A 20-step task~500k / ~24k~$0.74~$1.86
A heavy day (5 tasks)~$3.72~$9.30

That is roughly 60% off on the mainline model, before prompt caching. Full rates are in the Claude API pricing guide, and the cost calculator takes your own token counts.

What still works — and what doesn't

Pointing Claude Code at any gateway changes a few things. The list is short and worth knowing before you commit:

FeatureBehind a gateway
Coding, tools, subagents, MCP, hooksUnaffected
Prompt cachingWorks — native Messages API route
Your claude.ai subscriptionNot used; billed per token to the key instead
Remote ControlUnavailable — needs a claude.ai identity
Voice dictationUnavailable — same reason
/context token countsEstimated locally (see below)

On that last row: token counting is the one endpoint Anthropic's own gateway specification marks optional, and Claude Code estimates context usage locally when it is absent. Kunavo does not serve /v1/messages/count_tokens today, so your /context figure is an estimate rather than an exact count. Nothing degrades beyond that number — auto-compaction and the session itself are unaffected.

Troubleshooting

The base-URL path

SymptomCause and fix
401 on every requestKey is in the header the server doesn't read. Switch between ANTHROPIC_AUTH_TOKEN and ANTHROPIC_API_KEY and retry the curl above.
Claude Code asks you to log in, but curl worksA reachable base URL is not a credential. Set ANTHROPIC_AUTH_TOKEN somewhere read before first-run setup: a shell export or ~/.claude/settings.json.
ANTHROPIC_API_KEY set but ignored, no promptThe one-time approval was declined earlier. Enable it under /configUse custom API key, or switch to ANTHROPIC_AUTH_TOKEN.
404 naming the modelExact-slug matching — drop any date suffix and use a slug from the table above.
400 naming thinking or adaptiveClaude Code requests adaptive reasoning on 4.6+ models. On Opus 4.6 and Sonnet 4.6, CLAUDE_CODE_DISABLE_ADAPTIVE_THINKING=1 works around it.
/fast says fast mode is disabledThe availability check calls api.anthropic.com directly and doesn't follow your base URL. Set CLAUDE_CODE_SKIP_FAST_MODE_ORG_CHECK=1.
Models missing from the pickerEnable CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1 with ANTHROPIC_AUTH_TOKEN set, or name them via the ANTHROPIC_DEFAULT_*_MODEL variables.

…and when the router is in the path

SymptomCause and fix
Edits to config.json change nothingThey can't. Runtime configuration is config.sqlite; the JSON file is a one-time migration source. Make the change in the UI.
ccr code not foundNot in the current command set. Launch a profile by name: ccr "My Profile", or ccr-app "My Profile" from the desktop app.
ccr not found after installnpm's global bin isn't on PATH, or Node is below 22. Check npm prefix -g and node --version.
The UI loads but model requests failManagement and gateway are different services on different ports. Confirm Server shows Running, and point clients at :3456, not :3458.
Gateway returns 401 though the provider checks outNo CCR client key. Create one on the API Keys page — it is a separate credential from your sk-kn- key.
Claude Code runs, but nothing appears in Request logsYou launched Claude Code directly while the profile scope is Only opened from CCR. Launch from CCR, or switch the scope to System default.
Every subagent uses the default modelSubagent routing is gated on the Models page Description field. With no descriptions, CCR injects no routing instruction and the tag is never written.
/model lists no CCR modelsNo provider and model are configured, or the profile is disabled. Run Check Connection on the provider first.

Error-by-error fixes for the API itself are in the invalid API key and rate limit troubleshooting pages.

FAQ

Do I need claude-code-router to use Claude Code with a different API?

No. Claude Code reads ANTHROPIC_BASE_URL natively, so pointing it at any endpoint that serves the Anthropic Messages API needs no extra software — three environment variables and you are done. CCR is worth running when you want routing across several providers, per-request logs of provider, model, latency, tokens and cost, or a different model per subagent rather than one model for all of them. If your goal is simply running Claude on a cheaper endpoint, the base-URL swap is the smaller, more reliable setup: no extra service, and prompt caching goes straight through.

Why does editing claude-code-router's config.json do nothing?

Because it is no longer the configuration CCR reads. Current builds keep runtime configuration in ~/.claude-code-router/config.sqlite (%APPDATA%\claude-code-router\config.sqlite on Windows) and read a legacy config.json exactly once, as a migration source, when no SQLite configuration exists yet. After that first run the JSON file is ignored — silently, with no error — so a hand-edited Providers array or Router block simply never takes effect. Make the change in the CCR desktop UI instead, and use Settings → Export data if you want a file-level backup. Most third-party CCR tutorials still describe the JSON file.

How do I start Claude Code through claude-code-router now?

By profile name, not with ccr code. Create a profile under Agent Config → Add profile → Claude Code, pick a model, save, then launch it: ccr "Claude Code - Work" with the npm CLI, or ccr-app "Claude Code - Work" with the desktop app, which also gives each profile card a terminal button for the CLI and a play button for the Claude app. The current CLI command set is start, ui, stop, serve and web, plus a profile name or id; there is no code subcommand. Append the agent's own flags after a double dash, for example: ccr "Claude Code - Work" cli -- --model sonnet.

Can Claude Code use a custom model?

Mechanically yes: ANTHROPIC_MODEL accepts any slug the endpoint behind ANTHROPIC_BASE_URL serves, and claude-code-router adds per-task routing across providers on top. The honest limit is that Anthropic's own gateway documentation states it does not support routing Claude Code to non-Claude models through any gateway, so tool-use and agentic behaviour on a non-Claude model are untested territory rather than a supported configuration. On Kunavo the supported path is a Claude slug on a cheaper endpoint; other catalog models are reachable through the OpenAI-compatible API rather than through Claude Code. The exact-slug rule and the model table are in set a custom model above, and the full catalog is on the models page.

What base URL and environment variables does Claude Code need?

Set ANTHROPIC_BASE_URL to https://api.kunavo.com (Claude Code appends /v1/messages itself), ANTHROPIC_AUTH_TOKEN to your sk-kn- key, and ANTHROPIC_MODEL to an exact model slug such as claude-sonnet-4-6. Add ANTHROPIC_DEFAULT_HAIKU_MODEL=claude-haiku-4-5 so background tasks bill at the cheapest rate.

Should I use ANTHROPIC_AUTH_TOKEN or ANTHROPIC_API_KEY?

Use ANTHROPIC_AUTH_TOKEN. It is sent as an Authorization: Bearer header and takes effect immediately, whereas ANTHROPIC_API_KEY is sent as x-api-key and needs a one-time interactive approval — a key you declined once is ignored silently afterwards. On Kunavo there is a second reason: the /v1/models endpoint used by Claude Code's gateway model discovery reads Bearer only, so discovery works with ANTHROPIC_AUTH_TOKEN and fails silently with ANTHROPIC_API_KEY.

Why does Claude Code say the model is not available?

Kunavo matches model slugs exactly and does not alias date-suffixed names, so a request for claude-sonnet-4-5-20250929 returns 404 while claude-sonnet-4-6 succeeds. Set ANTHROPIC_MODEL to an exact slug from the catalog rather than relying on Claude Code's built-in default.

What stops working when Claude Code runs through a gateway?

Three things, by design. Remote Control and voice dictation both need a claude.ai identity and are unavailable while a gateway credential is set. The /fast availability check calls api.anthropic.com directly rather than following your base URL, so it can report fast mode as unavailable while normal requests work; CLAUDE_CODE_SKIP_FAST_MODE_ORG_CHECK=1 restores it. Coding, tools, subagents, MCP, and prompt caching are unaffected.

Can I use my Claude Pro or Max subscription instead?

No. Chat subscriptions do not include API access, and setting a gateway credential deliberately parks your claude.ai login — the subscription's limits stop applying and usage bills per token to the key instead. See is Claude Code free for the full breakdown.

Does this work with the VS Code extension?

Yes, but the extension checks credentials before launching, so set them in VS Code's own claudeCode.environmentVariables setting rather than only in ~/.claude/settings.json.

What about Cursor, Kilo Code or Cline?

Those use an OpenAI-compatible provider field instead of environment variables — base URL https://api.kunavo.com/v1, same key. Setup and per-tool model routing are covered in the Cline, Roo Code and Kilo Code guides.