claude-code-router with an OpenAI-Compatible Backend
Claude Code is hard-locked to Anthropic's API. It speaks the Anthropic message format and it will not be talked out of it by a config file. claude-code-router (CCR) sits in the middle and translates: Claude Code talks to a local port, and the router turns each Anthropic-shaped request into an OpenAI-shaped one for whatever backend you point it at.
That protocol gap is the whole reason the tool exists, and it also explains why its config is the least standardised of any tool on this site. Two layouts are in circulation, and the field names differ between them.
https://aicomp.ai/v1).
Create one free →
Which layout your config uses
Both of the following appear in current guides, and they are not interchangeable:
| Layout | Provider key | Endpoint field | Where it comes from |
|---|---|---|---|
| A | Providers (array) | api_base_url | the mainstream @musistudio/claude-code-router line |
| B | provider (object) | base_url | forks, and some older builds |
Layout A is the one that matches the npm package's own documentation. If your config file has a Providers array, you are on A. If it has a provider object with a default key inside it, you are on B, and copying a layout A file into it fails without an explanation.
Read your existing file before writing a new one. If the file does not exist yet, create ~/.claude-code-router/config.json and start from layout A.
Layout A, which is the one to use
{
"LOG": true,
"HOST": "127.0.0.1",
"PORT": 3456,
"APIKEY": "local-router-token",
"API_TIMEOUT_MS": "600000",
"Providers": [
{
"name": "gw",
"api_base_url": "https://gw.example.com/v1",
"api_key": "sk-your-gateway-key",
"models": ["claude-sonnet-5", "deepseek-v4-pro", "glm-5.3"],
"transformer": { "use": [] }
}
],
"Router": {
"default": "gw,claude-sonnet-5",
"background": "gw,glm-5.3",
"think": "gw,deepseek-v4-pro",
"longContext": "gw,claude-sonnet-5",
"longContextThreshold": 60000,
"webSearch": "gw,claude-sonnet-5"
}
}
The Router block is a routing table, not a preference list. Claude Code issues several distinct kinds of request and CCR maps each to a model:
| Route key | What Claude Code is doing |
|---|---|
default | ordinary turns |
background | cheap auxiliary calls, the ones worth a small model |
think | extended-reasoning turns |
longContext | turns past longContextThreshold tokens |
webSearch | turns that involve search |
The value format is provider,model, a comma, not a colon. That single character is a frequent typo because most other tools use a colon.
Putting a cheap model on background is the one routing change that reliably pays for itself, because those calls are frequent and their answers are rarely read.
The version segment
Here the guidance in the wild conflicts, and the reason is that a gateway can be reached two ways.
api_base_urlas a version root —https://gw.example.com/v1. This is the right value when the backend serves the OpenAI-compatible API and CCR appends the operation path.- The bare host — some guides show
https://api.example.comwith no version, which works only if the backend serves its API at the root.
What never works is pasting the full operation path (.../v1/chat/completions), because the router appends its own. That produces the doubled path failure, and it surfaces as a 404 alongside a message about the upstream provider rather than about the URL.
For an OpenAI-compatible gateway, use the version root.
Transformers decide whether tools work
Claude Code is an agent. It calls tools constantly, and a backend that returns tool calls in a different shape than Claude Code expects will appear to work for plain questions and fail the moment it tries to read a file.
The transformer.use array is where that is corrected. ["deepseek", "tooluse"] is the common pairing: one entry adapts the provider's response shape and the other normalises tool-call emission.
Leaving it empty is correct only for a backend already emitting Anthropic-shaped tool calls. For everything else, an empty array produces an agent that chats well and cannot act, which reads as a Claude Code bug rather than a config gap.
Launching it
The router is a server, and Claude Code needs two environment variables pointed at it. The ccr code subcommand starts the server if it is not running, exports both, and then launches Claude Code:
npm install -g @musistudio/claude-code-router
ccr code
The two variables it sets are worth knowing for the case where you launch the pieces by hand:
export ANTHROPIC_BASE_URL="http://127.0.0.1:3456"
export ANTHROPIC_API_KEY="placeholder"
The key here is not your gateway key. Claude Code performs a login check and rejects an unset value, so a dummy string satisfies it; the real credential is the api_key inside the provider block, which the router applies upstream. Setting this variable to a real Anthropic key changes nothing about where the traffic goes.
Switch models inside a session with the /model command using the same provider,model form as the routing table.
Why this setup costs less than it looks
Routing Claude Code to a gateway is mostly a cost decision, and the saving comes from two places.
Per-token rate. A gateway's rate for an equivalent model is lower than the first-party one. On an agent that re-sends the whole conversation on every tool call, that difference compounds over a long task rather than over a long message.
Routing. background calls are frequent and cheap to serve with a small model. think turns are rare and worth a strong one. Sending everything to one frontier model pays frontier rates for both.
A day on this page's workload is 300k input and 40k output tokens, or 6M input and 0.8M output across 20 working days. It is the highest input figure of any tool on this site, and it is high for the same reason the site exists: a coding agent reads far more than it writes.
| Model | Gateway rate in / out per 1M tokens | One day | 20 days |
|---|---|---|---|
| gpt-5.6-terra | $1.00 / $6.00 | $0.54 | $10.80 |
| claude-sonnet-5 | $1.00 / $5.00 | $0.50 | $10.00 |
| deepseek-v4-pro | $0.66 / $1.98 | $0.28 | $5.54 |
| qwen3.8-max | $1.00 / $3.00 | $0.42 | $8.40 |
| glm-5.3 | $0.70 / $2.2 | $0.30 | $5.96 |
One day = 300k input + 40k output on this page's workload. 20 days = 6M input and 0.8M output. Rates checked 2026-10-14 (gateway rates move with upstream promotions), so verify the current number in your dashboard before committing to a budget.
Verifying the route
- Open your existing config file first and see whether it uses
Providersorprovider. Match the layout you find. - Address each provider by the name in the config, because the routing table references providers by that name and a mismatch fails silently at routing time.
- Write routing values as
provider,modelwith a comma. - Set the version root in
api_base_url, not the operation path. - Fill
transformer.useunless your backend already returns Anthropic-shaped tool calls. - Launch with
ccr codeso the environment variables are set for you. - Check
/statusinside the session to confirm which model is actually serving.
FAQ
FAQ
Why is my config file ignored?
Two schema layouts are in circulation. One uses a Providers array with api_base_url, the other a provider object with base_url. The router reads the layout it expects and ignores keys it does not recognise, so a mismatched file looks like an empty config.
Why does Claude Code ask me to log in?
It requires ANTHROPIC_API_KEY to be set and does not validate it. ccr code sets a placeholder. The value has no effect on routing; the real key lives in the provider block.
Why do plain questions work but file reads fail?
Tool calling. Claude Code expects Anthropic-shaped tool calls and your backend emits a different shape. Add the appropriate entries to transformer.use, typically the provider transformer plus tooluse.
Does api_base_url need /v1?
For an OpenAI-compatible gateway, yes (the version root, with nothing after it). The router appends the operation path, so a value ending in /chat/completions produces a doubled path.
Is the router key the same as my gateway key?
No. APIKEY in the config is a local token between Claude Code and the router. api_key inside the provider block is the credential the router sends upstream.
Which model should background routing use?
A small, cheap one. Those calls are frequent and their output is rarely read, which makes them the best target for a rate reduction.
Related
- Claude Code with a custom API, setting the endpoint directly, without a translation layer
- Claude Code cost — what an agent loop actually bills
- Claude Code 429 rate limits — which limit you are hitting when it throttles
- OpenAI-compatible endpoints — the request shape the router converts to
- litellm model name mapping — the same routing idea as a proxy rather than a local server
- Tools that take one endpoint — the full matrix