Local API key routing for Claude-compatible clients.
Rotate keys, fail over gracefully, and see the whole system from one local dashboard.
Important
ClaudeRouter is a routing proxy. It does not supply API keys; use only keys, accounts, and upstream providers you are authorized to use.
A local, zero-dependency rotating proxy for Claude-compatible API keys. Point Claude Code (or any Anthropic-compatible client) at ClaudeRouter, and it spreads your traffic across multiple keys and providers, automatically skipping keys that hit rate limits or errors. A built-in web dashboard shows key health, usage, and lets you switch modes without editing files.
Claude Code ──► ClaudeRouter (127.0.0.1:3456) ──► Provider A key 1
│ rotates / fails over Provider A key 2
│ Provider B key 1
└── web dashboard local gateway ...
- Zero dependencies. Pure Node.js stdlib, one file. Nothing to
npm installfor the core. - Multi-provider. Each key can target a different upstream base URL.
- Automatic failover. A key that returns 429/5xx is put on cooldown; the next request tries another.
- Three routing modes. Rotate all keys, rotate a selected subset, or pin one fixed key.
- Local-first. Binds to
127.0.0.1by default. Your keys stay in a localconfig.jsonthat is git-ignored. - Web dashboard. Live key status, usage counters, cooldown control, config editor.
- Requirements
- Install
- Quick start
- Configuration
- Connecting Claude Code
- Routing modes
- Web dashboard
- HTTP API
- Environment variables
- Integrations
- Security notes
- Contributing
- License
- Node.js 18 or newer (
node --version) - That's it for the core. Integrations have their own requirements (see below).
Run it on demand with npx, no global install:
npx claude-routerOr install the command globally:
npm install -g claude-router
claude-routerOn first run in a directory, ClaudeRouter creates a config.json from the
template and exits so you can add your keys. config.json and state.json are
written to the current working directory, so pick a folder you'll launch it
from (e.g. ~/claude-router/).
git clone https://github.com/ThangTo/ClaudeRouter.git
cd ClaudeRouter
cp config.example.json config.json # then edit config.json
node claude-key-rotator.js-
Start it once to generate the config:
npx claude-router
-
Open the
config.jsonit created and add at least one key (see Configuration):{ "listenHost": "127.0.0.1", "listenPort": 3456, "providers": { "my-provider": { "upstreamBaseUrl": "https://api.example-provider.com/" } }, "keys": [ { "name": "key-1", "value": "sk-your-key-here", "provider": "my-provider" } ] } -
Start it again:
npx claude-router
claude-key-rotator listening on http://127.0.0.1:3456 dashboard: http://127.0.0.1:3456/__rotator/ mode: rotate keys: key-1 -
Open the dashboard at http://127.0.0.1:3456/__rotator/ and point your client at
http://127.0.0.1:3456/(see Connecting Claude Code).
All configuration lives in config.json. This file is git-ignored so your
keys never get committed. Use config.example.json as your starting point.
| Field | Type | Default | Description |
|---|---|---|---|
listenHost |
string | 127.0.0.1 |
Interface to bind. Keep it loopback unless you know what you're doing. |
listenPort |
number | 3456 |
Port ClaudeRouter listens on. |
requestTimeoutMs |
number | 600000 |
Upstream request timeout (10 min default, for long streams). |
upstreamBaseUrl |
string | — | Optional global fallback upstream for keys that specify neither provider nor upstreamBaseUrl. |
providers |
object | {} |
Named upstreams, so keys can reference them by name. |
keys |
array | [] |
Your API keys. At least one is required to start. |
pricing |
object | zeros | Optional per-model cost table used for usage/cost estimates in the dashboard. |
A provider is just a named upstream base URL. Define one per distinct backend so multiple keys can share it:
"providers": {
"provider-a": { "upstreamBaseUrl": "https://api.provider-a.com/" },
"provider-b": { "upstreamBaseUrl": "https://api.provider-b.com/v1" }
}There is no default provider out of the box. A key resolves its upstream in this order:
key.upstreamBaseUrl(per-key override), else- the provider named by
key.provider, else - the top-level
upstreamBaseUrl.
If none of those resolve, ClaudeRouter refuses to start and tells you which key is misconfigured.
Each entry in keys describes one credential:
| Field | Required | Description |
|---|---|---|
value |
yes | The API key/token sent upstream as the auth header. |
name |
recommended | Stable identifier shown in the dashboard and used by mode/cooldown endpoints. Defaults to key-N. |
provider |
one of these | Name of a provider defined in providers. |
upstreamBaseUrl |
one of these | Per-key upstream, overrides provider. |
disabled |
no | true to skip this key entirely. |
Every key needs an upstream from provider, upstreamBaseUrl, or the global
upstreamBaseUrl.
{
"listenHost": "127.0.0.1",
"listenPort": 3456,
"requestTimeoutMs": 600000,
"providers": {
"provider-a": { "upstreamBaseUrl": "https://api.provider-a.com/" },
"provider-b": { "upstreamBaseUrl": "https://api.provider-b.com/v1" }
},
"keys": [
{ "name": "a-key-1", "value": "REPLACE_ME", "provider": "provider-a" },
{ "name": "a-key-2", "value": "REPLACE_ME", "provider": "provider-a" },
{ "name": "b-key-1", "value": "REPLACE_ME", "provider": "provider-b" },
{ "name": "direct", "value": "REPLACE_ME", "upstreamBaseUrl": "https://direct.example.com/" }
],
"pricing": {
"default": { "inputPerMillion": 0, "outputPerMillion": 0, "cacheReadPerMillion": 0, "cacheCreationPerMillion": 0 },
"models": {}
}
}Point Claude Code at the local proxy with two environment variables. Any dummy string works for the API key — the real keys live in ClaudeRouter's config.
macOS / Linux (bash/zsh):
export ANTHROPIC_BASE_URL="http://127.0.0.1:3456/"
export ANTHROPIC_API_KEY="proxy-local-key"
export CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC="1"
claudeWindows (PowerShell):
$env:ANTHROPIC_BASE_URL = "http://127.0.0.1:3456/"
$env:ANTHROPIC_API_KEY = "proxy-local-key"
$env:CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC = "1"
claudeTo make it persistent, add these to Claude Code's settings.json
(~/.claude/settings.json):
{
"apiKeyHelper": "echo 'proxy-local-key'",
"env": {
"ANTHROPIC_API_KEY": "proxy-local-key",
"ANTHROPIC_BASE_URL": "http://127.0.0.1:3456/",
"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1"
}
}Windows users can run the included helper, which backs up settings.json first:
.\install-claude-settings.ps1Any Anthropic-compatible client works the same way — set its base URL to
http://127.0.0.1:3456/.
| Mode | Behavior |
|---|---|
rotate |
Cycle through all enabled keys, skipping any on cooldown. Default. |
rotate_selected |
Cycle through a chosen subset of keys only. |
fixed |
Always use one pinned key. No fallback if it fails. |
Switch modes from the dashboard, or via the API:
# rotate all
curl "http://127.0.0.1:3456/__rotator/mode?mode=rotate"
# rotate a subset (comma-separated key names)
curl "http://127.0.0.1:3456/__rotator/mode?mode=rotate_selected&keys=a-key-1,b-key-1"
# pin one key
curl "http://127.0.0.1:3456/__rotator/mode?mode=fixed&key=a-key-1"When a key returns a rate-limit or server error in a rotating mode, ClaudeRouter marks it on cooldown and immediately tries the next available key, so a single request still succeeds if any key is healthy.
Open http://127.0.0.1:3456/__rotator/:
- Dashboard — live per-key status, request counts, usage, cooldown state, and one-click mode switching.
- Config editor (
/__rotator/config) — edit keys and providers in the browser and save. - Test (
/__rotator/test) — send a probe request through a specific provider/key.
All control endpoints are under /__rotator/. The proxy forwards everything
else upstream.
| Endpoint | Method | Purpose |
|---|---|---|
/__rotator/status |
GET | Full state: mode, keys, cooldowns, usage. |
/__rotator/health |
GET | Liveness check. |
/__rotator/mode?mode=… |
GET | Switch routing mode (rotate, rotate_selected&keys=…, fixed&key=…). |
/__rotator/clear-cooldown |
GET/POST | Clear cooldown on a key so it re-enters rotation. |
/__rotator/reload |
GET/POST | Reload config.json without restarting. |
/__rotator/config-data |
GET | Current config as JSON (for the editor). |
/__rotator/config-save |
POST | Persist an edited config. |
/__rotator/test-provider |
POST | Probe a provider/key. |
Handy scripts (Windows PowerShell) are included: start.ps1, status.ps1,
reload.ps1.
| Variable | Effect |
|---|---|
CLAUDE_ROTATOR_CONFIG |
Path to config.json. Defaults to the current directory (npm) or the repo dir (from source). |
CLAUDE_ROTATOR_STATE |
Path to state.json (runtime state). Same default resolution. |
Example — keep config in a fixed location regardless of where you launch:
CLAUDE_ROTATOR_CONFIG=~/claude-router/config.json \
CLAUDE_ROTATOR_STATE=~/claude-router/state.json \
npx claude-routerOptional local backends that expose an Anthropic-compatible endpoint you can add as a ClaudeRouter provider. These are Windows/PowerShell-oriented and depend on third-party tools you supply yourself — the binaries are not bundled here.
integrations/kirocc/— run a local proxy backed by a Kiro IDE auth token, including a multi-account pool. Requires thekiroccbinary placed inintegrations/kirocc/bin/(not included).integrations/kiro-gateway/— wire Kiro credits through a local Docker gateway. Requires Docker and the third-partykiro-gatewayimage.
Both integrations are for use only with accounts and credentials you own. See each folder's README for setup. Their credential files, tokens, and account folders are git-ignored.
- ClaudeRouter binds to
127.0.0.1by default. Do not expose it on a public interface without adding your own authentication — it has none, by design, for local use. - Your real keys live only in
config.json, which is git-ignored. Never commit it. Double-check withgit statusbefore pushing. state.jsonrecords usage counters and can be large; it is git-ignored too.- If you ever suspect a key leaked, rotate it at the provider immediately.
Issues and PRs welcome. See CONTRIBUTING.md. Please never
include real API keys, tokens, or config.json/state.json in a PR.
MIT © ThangTo