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 want | Use |
|---|---|
| Run Claude in Claude Code, cheaper | Base 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 Code | claude-code-router |
| Drive Claude Code with non-Claude models | claude-code-router |
| Per-request logs: provider, model, latency, tokens, cost | claude-code-router |
| Send subagents to a different model than the main loop | claude-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:
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 tasksANTHROPIC_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:
| Variable | Header sent | On Kunavo |
|---|---|---|
ANTHROPIC_AUTH_TOKEN | Authorization: Bearer | Recommended — works everywhere |
ANTHROPIC_API_KEY | x-api-key | Works 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:
{
"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:
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:
| Role | Slug | Input / output per 1M |
|---|---|---|
| Everyday coding (default) | claude-sonnet-4-6 | $1.20 / $6.00 |
| Near-Opus coding quality | claude-sonnet-5 | $2.00 / $10.00 |
| Hardest refactors, plan mode | claude-opus-5 | $2.00 / $10.00 |
| Background tasks, quick asks | claude-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.sqliteon Windows). A legacyconfig.jsonis 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 pastedProvidersblock 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.
# 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:
| Credential | Authenticates | Where it goes |
|---|---|---|
Your Kunavo key (sk-kn-…) | CCR → Kunavo | Providers → the provider's API key field |
| A CCR client key | Any client → the CCR gateway | Created on the API Keys page; without one the gateway rejects model requests |
The management token (ccr_web_token) | You → the CCR UI and RPC | In 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.
| Tier | Map it to | Input / output per 1M | What actually runs there |
|---|---|---|---|
| Opus | Kunavo/claude-opus-5 | $2.00 / $10.00 | Plan mode, hard refactors |
| Sonnet (default) | Kunavo/claude-sonnet-5 | $2.00 / $10.00 | The main agent loop — most of your tokens |
| Sonnet, cheaper | Kunavo/claude-sonnet-4-6 | $1.20 / $6.00 | Same loop at 60% off list |
| Haiku | Kunavo/claude-haiku-4-5 | $0.40 / $2.00 | Subagents, 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:
| Unit | Tokens (in / out) | Kunavo | Anthropic list |
|---|---|---|---|
| One agentic step | 25,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:
| Feature | Behind a gateway |
|---|---|
| Coding, tools, subagents, MCP, hooks | Unaffected |
| Prompt caching | Works — native Messages API route |
| Your claude.ai subscription | Not used; billed per token to the key instead |
| Remote Control | Unavailable — needs a claude.ai identity |
| Voice dictation | Unavailable — same reason |
/context token counts | Estimated 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
| Symptom | Cause and fix |
|---|---|
401 on every request | Key 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 works | A 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 prompt | The one-time approval was declined earlier. Enable it under /config → Use custom API key, or switch to ANTHROPIC_AUTH_TOKEN. |
404 naming the model | Exact-slug matching — drop any date suffix and use a slug from the table above. |
400 naming thinking or adaptive | Claude 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 disabled | The 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 picker | Enable 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
| Symptom | Cause and fix |
|---|---|
Edits to config.json change nothing | They 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 found | Not 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 install | npm'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 fail | Management 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 out | No 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 logs | You 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 model | Subagent 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 models | No 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.