claude-rotate
Health Gecti
- License — License: MIT
- Description — Repository has a description
- Active repo — Last push 0 days ago
- Community trust — 18 GitHub stars
Code Uyari
- network request — Outbound network request in rotator.py
Permissions Gecti
- Permissions — No dangerous permissions requested
Bu listing icin henuz AI raporu yok.
Multi-account rotating proxy for Claude Code — consume-first quota rotation, burst-aware 429 handling, hold-until-reset, per-device analytics. Herd-safe by test.
claude-rotate
A tiny self-hosted proxy that lets Claude Code ride multiple Claude
Max/Pro subscriptions and switches accounts automatically when one hits its
rate-limit window — with a built-in analytics panel showing quota gauges,
per-device consumption, and cost-equivalent dollars.
One process, one config file, no database. Built for individual developers who
own more than one subscription and are tired of "You've hit your session
limit".
Herd-safe, by test — not by vibes: when your whole fleet slams into a rate
limit at the same instant, exactly one rotation decision happens — 100
in-flight requests, 1 switch, and every request recovers with a single retry
onto the new account. Reproducible numbers in
Performance.
Terms-of-Service note. This tool automates switching between accounts
you personally own and pay for. Each account's limits are still fully
enforced by Anthropic. Rotating accounts to work past limits is a gray area
under Anthropic's consumer terms — use at your own risk, and do not use
consumer subscriptions to back a shared/commercial service.
Contents:
How it works ·
Performance ·
Quickstart: server ·
Generating tokens ·
Quickstart: devices ·
Networking ·
Analytics panel ·
Configuration ·
Run as a service ·
Troubleshooting ·
Limitations ·
Operations ·
Comparison ·
Security ·
License
How it works

Per request the proxy: authenticates the device (device key → device name),
swaps in the active account's long-lived setup-token, forwards transparently,
reads the exact anthropic-ratelimit-unified-* quota headers off the
response, applies the rotation policy (consume-first; quota 429 → rotate,
burst 429 → pace, all spent → hold), and logs usage per device/model.
Diagram regenerates with python3 screenshots/architecture.py
(needs pip install diagrams + graphviz).
Key insight: Claude Code respects ANTHROPIC_BASE_URL, and claude setup-token mints a ~1-year OAuth token per account. The proxy holds those
tokens; devices only ever hold an internal device key. Every response from
Anthropic carries exact quota telemetry headers, so switching at 80% is
measured, not guessed.
Performance (herd-safe)
The point of a server proxy is that a whole fleet leans on it at once — so
high-concurrency behavior is tested, not assumed. All numbers from the
reproducible stress suite (python3 tests/test_stress.py, mocked upstream,
Apple-silicon dev machine — run it on yours):
| Scenario | Result |
|---|---|
| 300 concurrent requests | every one served, audit complete, ~3,500 req/s proxy overhead ceiling |
| Quota limit hit with 100 requests in flight (50 ms simulated upstream RTT) | exactly 1 switch — the in-flight herd hits the dying account once each (they were already on the wire), then every request retries once onto the survivor; all 100 recover |
| 50 burst-429s across 100 concurrent requests | 0 rotations — paced on the same account, prompt caches kept warm |
| All accounts spent, 20 requests arrive | all park via hold-until-reset and release together at the window reset |
| 50 parallel SSE streams | byte-identical relay, usage audited on every stream |
| 50,000-row audit log | analytics rollup in 0.42 s |
Why this matters with a fleet: the failure mode of naive rotators under load
is the switch stampede — N in-flight requests each trigger their own switch,
the account pool burns down in seconds, and every device's prompt cache is
thrown away N times. Here a single lock serializes the decision: one switch,
everyone else rides it. To be precise about what the lock can and cannot do:
requests already on the wire when the quota hits still reach the dying account
once each — nothing can un-send them — but there is exactly one switch and
exactly one retry per request, and both counts are asserted in the test.
Honest scope: single process by design (state lives in one place — don't run
uvicorn workers), and the upstream is mocked (the race test adds 50 ms of
simulated RTT), so figures measure the proxy's own overhead and race behavior,
not Anthropic latency.
Quickstart (server)
Requires Python 3.10+ and three packages: fastapi, httpx, uvicorn
(pinned in requirements.txt). Any always-on box works — a Mac mini, a
home-lab Linux machine, a small VPS.
git clone <this repo> && cd claude-rotate
pip install -r requirements.txt
./setup.sh # creates config.json, tokens/, logs/
./setup.sh add-account acct-1 # paste token from: claude setup-token (account 1's browser)
./setup.sh add-account acct-2 # …account 2
./setup.sh add-device my-laptop # prints the env vars for that device
python3 rotator.py
Generating account tokens (claude setup-token)
Each subscription account contributes one long-lived OAuth token. Facts below
are from the official authentication docs
unless flagged otherwise.
Minting a token:
claude setup-token
- Opens the same browser OAuth flow as
/login; if no browser can reach the
local callback (headless server, SSH session), it falls back to a paste-a-code
flow — so you can mint tokens on the proxy server itself. - Prints a token starting
sk-ant-oat01-…to the terminal and does not
save it anywhere — copy it straight into./setup.sh add-account <name>,
which stores it undertokens/(chmod 600, gitignored). - Valid for about one year. Requires a Claude subscription (Pro, Max,
Team, or Enterprise).
Minting for multiple accounts from one machine. The account that completes
the browser OAuth is the account the token belongs to — the CLI's current
login doesn't decide it. (Officially undocumented; this is the behavior in
practice.) Two workable recipes:
- Browser profiles: keep one browser profile (or incognito window) logged
into claude.ai per account. Runclaude setup-token, and complete the OAuth
in the profile of the account you're minting for — copy the URL into that
profile if the wrong one opens. - Config-dir isolation:
CLAUDE_CONFIG_DIR=~/.claude-acct2 claude setup-tokenkeeps a fully separate CLI login per directory, useful if you
also want to run Claude Code as different accounts.
After adding an account, send one request through the proxy and check/rotate/status — the unified utilization headers it reports are per-account,
so a token minted against the wrong account shows up immediately as the wrong
gauge moving.
Lifetime & revocation:
- There is currently no CLI command to list or revoke setup tokens
(open feature request);
revoke manually from claude.ai settings./logoutonly revokes the CLI's
active session credential, not previously minted setup tokens. - Treat each token file as a year-long bearer credential for that Anthropic
account (see Security notes). When a token dies early, the
proxy's upstream calls start returning 401 for that account — the roadmap has
an alert for this. - Scope note: subscription OAuth tokens are for Claude Code traffic — which is
all this proxy forwards — and per the 2026 docs they are rejected by the raw
Messages API.
Quickstart (each device)
Two env vars in your shell profile — that's the whole client install:
export ANTHROPIC_BASE_URL=http://<server>:8484
export CLAUDE_CODE_OAUTH_TOKEN=<device key printed by add-device>
Run claude as usual. When account 1's 5-hour window fills, the next request
rides account 2. Use a VPN/overlay like Tailscale between devices and server —
the proxy speaks plain HTTP and device keys are bearer secrets. See
Connecting distributed devices for setups.
Connecting distributed devices
The proxy is plain HTTP and device keys are bearer secrets, so the transport
between devices and server must be private. Pick one:
Same LAN / subnet (nothing to install)
If every device lives on the same trusted network as the server — home lab,
office LAN — you don't need Tailscale or any overlay at all:
# on the server: find its LAN address
ipconfig getifaddr en0 # macOS (en0 = Ethernet/Wi-Fi)
hostname -I # Linux
# on each device:
export ANTHROPIC_BASE_URL=http://192.168.1.42:8484
- macOS servers get a free stable name via Bonjour/mDNS:
http://<hostname>.local:8484(e.g.http://mac-mini.local:8484) works from
Macs, iPhones, and most Linux devices (withavahi-daemon) — no IP to
remember. Otherwise give the server a DHCP reservation in your router so
its IP never changes under the devices pointing at it. - Set
bindinconfig.jsonto the LAN IP (or keep0.0.0.0if the box has
only one network). Verify from a device:curl http://<server>:8484/rotate/status?key=<device key>. - Do not port-forward 8484 on your router. LAN-only means the firewall
boundary is your router's NAT; forwarding the port turns this into the
internet-exposed scenario below.
The trust caveat: traffic is plain HTTP, so anyone on the same subnet can read
device keys off the wire. Fine for a home network you control; on a shared
office network or anywhere with guests, prefer one of the encrypted options
below. And a hybrid is normal — LAN for the desktop next to the server,
Tailscale for the laptop that leaves the house.
Tailscale (recommended)
Zero-config WireGuard mesh; free tier covers personal use easily.
# on the server AND every device:
# macOS: brew install tailscale && brew services start tailscale
# Linux: curl -fsSL https://tailscale.com/install.sh | sh
tailscale up # login once per machine, same tailnet
tailscale status # note the server's name / 100.x.y.z address
With MagicDNS on (default on new tailnets), devices reach the server by name:
export ANTHROPIC_BASE_URL=http://<server-hostname>:8484 # e.g. http://my-server:8484
export CLAUDE_CODE_OAUTH_TOKEN=<device key>
Tighten the listener so the proxy is only reachable over the tailnet — setbind in config.json to the server's Tailscale IP instead of 0.0.0.0:
{ "bind": "100.x.y.z", "port": 8484 }
Optional TLS: tailscale serve --bg http://127.0.0.1:8484 publishes the proxy
as https://<server>.<tailnet>.ts.net (valid cert, tailnet-only). Then use
that URL as ANTHROPIC_BASE_URL and set bind to 127.0.0.1.
CI runners and containers work too: ephemeral auth keys
(tailscale up --auth-key=tskey-...) join a runner to the tailnet for the
duration of a job; there's a ready-made GitHub Action (tailscale/github-action).
Plain WireGuard
Same effect, no third party. Sketch: generate a keypair per machine
(wg genkey | tee private.key | wg pubkey > public.key), give the server awg0 with an internal subnet (e.g. 10.84.0.1/24), add each device as a[Peer] with AllowedIPs = 10.84.0.X/32, and point devices athttp://10.84.0.1:8484. Set bind to 10.84.0.1. More manual than
Tailscale (key distribution, NAT traversal is on you), but fully self-hosted.
SSH tunnel (zero install)
Any device that can SSH to the server needs nothing else:
ssh -N -L 8484:127.0.0.1:8484 user@server &
export ANTHROPIC_BASE_URL=http://127.0.0.1:8484
With bind: 127.0.0.1 on the server, this is the tightest setup — the proxy
never listens on a network interface at all. Use autossh (orServerAliveInterval 30 in ~/.ssh/config) to keep the tunnel up; fine for a
laptop or a single CI box, tedious beyond a few devices.
Cloudflare Tunnel (device without VPN access)
For a device that can't join your tailnet (locked-down corp machine, hosted
CI you can't install agents on), cloudflared can expose the proxy through
Cloudflare without opening ports:
# server:
cloudflared tunnel login
cloudflared tunnel create claude-rotate
cloudflared tunnel route dns claude-rotate rotate.example.com
cloudflared tunnel run --url http://127.0.0.1:8484 claude-rotate
# device:
export ANTHROPIC_BASE_URL=https://rotate.example.com
This makes the proxy internet-reachable — the only thing between the
world and your Anthropic tokens is the device-key check. If you go this
route, put Cloudflare Access
(a Zero Trust service-token policy) in front so unauthenticated requests never
reach the proxy, and treat device keys as revocable: delete a leaked one fromconfig.json and restart. Prefer any of the VPN options above when possible.
Other overlays
ZeroTier and NetBird work identically to Tailscale for this purpose (private
overlay IP + bind to it); use whichever your fleet already runs. Whatever
the transport, the checklist is the same: proxy bound to a private interface,
HTTP never exposed publicly, one device key per machine so any single machine
can be revoked alone.
Analytics panel
Open http://<server>:8484/rotate/panel?key=<any device key>:

Live 5-hour and weekly gauges per account, straight from Anthropic's own
rate-limit headers:

- Devices — every registered device: online status, last seen, last model,
which account its traffic rode, requests + tokens in the last hour. - Accounts — live 5-hour and weekly utilization gauges (from Anthropic's
own headers), active account, reset countdowns. - Alerts — device over N tokens/hour, expensive-model usage (Opus/Fable),
account near the switch threshold. Thresholds inconfig.json. - Consumption (24h) — by device / model / account, with a cost-equivalent
column: what the usage would cost at API list prices (incl. cache read 0.1× /
write 1.25×) — i.e. what the subscriptions are saving you.
JSON endpoints: /rotate/status (accounts + switch events) and/rotate/stats (rollups + alerts), same ?key= or Authorization: Bearer
auth. Audit trail: logs/audit.jsonl, one JSON record per request.
OpenAI-compatible endpoint
POST /v1/chat/completions (same device-key auth) accepts OpenAI-format chat
requests — streaming included — and rides the same rotated accounts, so any
OpenAI-format app or LLM router can use the capacity, not just Claude Code:
curl http://<server>:8484/v1/chat/completions \
-H "Authorization: Bearer <device key>" -H "content-type: application/json" \
-d '{"model":"claude-sonnet-5","max_tokens":256,
"messages":[{"role":"user","content":"hello"}]}'
Text conversations only (tool use → 400). When every account's window is spent
it returns 503 + Retry-After: <seconds to earliest reset> — point a
router's circuit breaker at that. Responses carry x-rotate-account and the
upstream anthropic-ratelimit-unified-* headers for per-request telemetry.
Configuration (config.json)
| Field | Meaning |
|---|---|
accounts[] |
{name, token_file} — one claude setup-token per subscription, stored under tokens/ (chmod 600) |
devices{} |
name → device key; key is what the device puts in CLAUDE_CODE_OAUTH_TOKEN |
accounts[].priority |
lower = preferred; accounts form tiers, backup tiers only used when every preferred account is spent (default 100) |
accounts[].disabled |
true benches an account: never rotated onto, still shown in the panel |
switch_threshold |
5h-window utilization that triggers rotation (default 0.8) |
switch_threshold_7d |
weekly-window utilization that makes an account unusable (default 0.98) |
strategy |
consume-first (default) or least-used — see below |
switch_cooldown_s |
min seconds between voluntary switches (default 300); hard limits ignore it |
switch_margin |
hysteresis: a threshold switch needs a candidate this much better (default 0.05) |
consume_first_margin_s |
proactive switch only if the candidate's weekly reset is this much sooner (default 3600) |
hold_max_s |
when every account is spent, hold the request open up to this long waiting for a window reset instead of returning 429 (default 0 = off) |
prices_per_mtok |
substring-matched [input, output] $ per MTok for the cost columns |
alerts |
device_tokens_per_hour, expensive_model_patterns, util_warn |
Rotation policy
An account is usable while its 5h window is under switch_threshold and its
weekly window under switch_threshold_7d; a window past its reset counts as
0%. State survives restarts in state.json.
consume-first(default): burn the usable account whose weekly window
resets soonest — weekly quota is use-it-or-lose-it, so the perishable
account is spent first and no paid quota expires unused. The proxy also
switches proactively (below the threshold) when another usable account's
weekly reset is at leastconsume_first_margin_ssooner.least-used: classic — lowest 5h utilization wins.- A cooldown plus a hysteresis margin stop accounts ping-ponging at the
threshold; a hard limit (quota actually rejected) always switches
immediately. - Burst vs quota 429s: a per-minute rate-limit 429 (utilization not
exhausted) does not rotate — rotating would move the burst to the next
account and throw away its warm prompt cache. The account is paced forretry-afterseconds and the request retried. - Hold-until-reset (
hold_max_s> 0): when every account is spent, the
proxy keeps the request open and retries after the soonest 5h reset instead
of failing — an unattended CI/agent run finishes on its own instead of dying
at 2am. Make sure your client's request timeout tolerates the wait (Claude
Code's default is generous; other clients may need tuning).
Preferring one account over another
Three levels of control, from lazy to pro — all in the same config.json,
deliberately no separate rules file (see design note below):
Level 0 — do nothing. The defaults (consume-first + thresholds + cooldown)
already make a sane global decision. Most single-owner setups need nothing else.
Level 1 — priority tiers. Give accounts a priority (lower = preferred):
"accounts": [
{ "name": "max-20x", "token_file": "tokens/max-20x.token", "priority": 1 },
{ "name": "max-5x", "token_file": "tokens/max-5x.token", "priority": 1 },
{ "name": "old-pro", "token_file": "tokens/old-pro.token", "priority": 2 }
]
Rotation happens within the lowest-numbered tier that still has a usable
account; old-pro above is touched only when both Max accounts are spent.
When a preferred account's window resets, traffic is pulled back automatically
(priority_recovery in the events feed) after the cooldown — the backup is a
spillway, not a new home. Accounts without a priority share one default tier,
which is why Level 0 works unchanged.
Level 2 — bench an account. "disabled": true takes an account out of
rotation entirely (a work account you don't want touched, one you're resting)
while keeping it visible in the panel. If the active account is disabled in
config, the proxy abandons it on the next response, cooldown or not. Re-enable
by deleting the flag; both changes need a restart (hot reload is on the roadmap).
Combine with strategy for the remaining temperament choice: consume-first
(spend perishable weekly quota first) or least-used (spread evenly).
Design note — why no rules YAML: a rules engine (per-model routes,
time-of-day windows, per-device pinning) would add a parser, a second config
file, and an ordering semantics to a one-file tool, and every use case we've
actually hit decomposes into the four knobs above (strategy, priority,
disabled, thresholds). If a real need appears that doesn't decompose — say
per-device account pinning — add it as another plain field on the existing
config objects, not as a DSL.
Run as a service
- macOS:
deploy/com.example.claude-rotate.plist(read its comments —
launchd needs the full python3 path and a local-disk install). - Linux:
deploy/claude-rotate.service(systemd).
Troubleshooting
Client gets 401 with claude-rotate: unknown device key. The proxy is
rejecting the device (the error body names claude-rotate, so it's not
Anthropic). The device's CLAUDE_CODE_OAUTH_TOKEN doesn't match any entry inconfig.json devices{} — re-check the key, or re-run./setup.sh add-device and restart.
Client gets a 401 whose error body comes from Anthropic. The account
token the proxy swapped in was rejected upstream — on a previously working
account this almost always means the setup token died (revoked, or hit its
~1-year expiry). Re-mint with claude setup-token, replacetokens/<name>.token, restart.
A request seems to hang. If every account is spent and hold_max_s is
set, that's hold-until-reset doing its job — the request is parked until the
soonest 5h window reset. Check recent_events in /rotate/status for ahold entry. Set hold_max_s: 0 if you'd rather fail fast.
"Why did it switch accounts?" Every switch is an event in/rotate/status → recent_events (and the panel's Recent switches), with a
reason: quota_exhausted, utilization>=<threshold>, consume_first,priority_recovery, or account_disabled. If you saw a 429 upstream but no
switch, it was a per-minute burst — the proxy paced the same account on
purpose (see Rotation policy).
Config edits don't take effect. Config is read once at startup. Restart:launchctl kickstart -k gui/$(id -u)/<label> (macOS) orsystemctl restart claude-rotate (Linux). Hot reload is on the roadmap.
Does the VS Code extension work? Yes — it honours the sameANTHROPIC_BASE_URL / CLAUDE_CODE_OAUTH_TOKEN environment variables. Launch
VS Code from a shell that has them exported (or set them user-wide) so the
extension inherits them.
Panel is empty or unauthorized. The panel needs?key=<any device key> in the URL; consumption tables build fromlogs/audit.jsonl and only show the last 24h, so a fresh install shows
zeros until traffic flows.
Client throws Zlib/Brotli decoding errors. Shouldn't happen — the proxy
forces accept-encoding: identity upstream and strips content-encoding
from responses. If it appears after modifying rotator.py, you broke
load-bearing fact #2 in the continuation notes.
Limitations
Stated plainly, in the same spirit as the comparison:
- One active account, globally. Every device rides the same account at any
moment, so a token-hungry CI box drains the window your laptop is using.
Per-device visibility exists (panel, alerts); per-device quotas don't. - No per-model weekly caps. Anthropic tracks some model families
separately; the proxy only tracks the account-wide 5h/7d windows, so an
account out of (say) Opus quota gets benched entirely even if Sonnet still
has room. teamclaude does this better today. - Switches drop warm prompt caches. After a rotation, every device's first
request re-writes its prompt cache on the new account — that's the cost that
burst-pacing (and the hysteresis margin) exist to avoid paying needlessly. - Restart to reconfigure. No hot reload yet.
- Plain HTTP by design. Transport privacy is delegated to the network
layer — see Connecting distributed devices. - Built on undocumented headers. Quota telemetry comes from
anthropic-ratelimit-unified-*; if Anthropic changes them, rotation
degrades to reacting to 429s until the code is updated. - No automatic token renewal. Setup tokens last ~1 year and die silently;
the symptom is upstream 401s (see Troubleshooting).
Operations
Audit log growth.
logs/audit.jsonlgrows without bound (one JSON line
per request) and the stats endpoint re-reads it fully on every refresh, so
after months it slows the panel. The panel only uses the last 24h — rotate
the file whenever you like:# e.g. monthly via cron/launchd; the proxy recreates the file on the next request mv logs/audit.jsonl "logs/audit-$(date +%Y%m).jsonl"No restart needed (the file is opened per write). In-process rotation is on
the roadmap.What to back up.
tokens/andconfig.json— that's the whole
identity of the install.state.jsonis disposable (quota state is
re-learned from the first responses; you only lose the events history), andlogs/is disposable analytics.Upgrades.
git pull, runpython3 tests/run_all.py, restart the
service, then eyeball/rotate/panel— active account, gauges, and a
request flowing end-to-end.Health check.
curl -fs http://<server>:8484/rotate/status?key=<device key>
exits non-zero when the proxy is down — wire it into uptime monitoring if
the fleet depends on it. The launchd/systemd units indeploy/already
restart the process if it dies.
Comparison with similar projects
The multi-account rotation niche is well populated. Stars as of Sep 2026.
| Project | ⭐ | Type | Auto-rotate | Exact quota telemetry | Multi-device | Dashboard | ToS risk |
|---|---|---|---|---|---|---|---|
| claude-rotate (this) | — | server proxy | ✓ consume-first, burst pacing, hold — herd-safe under load | ✓ unified headers | ✓ device keys, per-device analytics | ✓ web panel + $-equivalent | ⚠️ gray |
| teamclaude | 283 | local proxy | ✓ quota-based, per-model caps, burst pacing | ✓ unified headers | ✗ (one user's machine) | ✓ TUI | ⚠️ gray |
| claude-swap | 2.2k | credential switcher | ✓ threshold + consume-first | ✓ polls usage | ✗ per-machine | ✓ CLI/TUI + menu bar | ⚠️ gray |
| claude-relay-service | 12.6k | relay platform | ✓ account pool | partial | ✓ per-key clients | ✓ full admin UI | ❌ built for account sharing |
| CC-Router | 28 | local proxy | round-robin only | ✗ | ✗ | ✗ | ⚠️ gray |
| claude-account-switcher | 53 | menu-bar switcher | ✓ at limits | ✓ live usage | ✗ macOS local | menu bar | ⚠️ gray / ✓ manual |
| ccrotate, claude-rotator, cc-relay-proxy | ≤7 | small CLIs/proxies | basic | ✗ | ✗ | ✗ | ⚠️ gray |
Honest reviews:
- teamclaude — the most sophisticated rotation engine in the field:
per-model weekly caps, burst-vs-quota 429 discrimination, post-failover
pacing, OAuth refresh, a MITM catch for hardcoded endpoints, zero npm
dependencies. claude-rotate adopted its burst-pacing and hold-until-reset
ideas. If you're one person on one machine, it's arguably the better tool;
it has no multi-device story. - claude-swap — the most popular and most polished UX (uv/pipx install,
hysteresis, quarantine of dead tokens, parallel sessions per terminal).
It swaps local credentials rather than proxying, so there's no
fleet/CI story and each machine manages its own accounts. Itsconsume-firststrategy is where we took the name and idea from. - claude-relay-service — by far the biggest (Redis, admin UI, multi-provider:
Claude/OpenAI/Gemini). It is explicitly built for 拼车 / carpooling —
pooling subscription accounts across multiple people to split costs. That is
not a gray area: sharing consumer subscription access with third parties
violates Anthropic's consumer terms, and account bans around such services
are regularly reported. Impressive engineering; don't use it with consumer
Max/Pro accounts. - CC-Router — clean and simple, but blind round-robin: no telemetry, so it
can rotate onto a spent account and drops warm prompt caches for no reason. - claude-account-switcher (and the Alfred/menu-bar family) — solves login
juggling, not routing. Manual switching between your own accounts is the
most ToS-defensible workflow of all; the auto-switch-at-limit mode shares
the same gray area as everything else here. - claude-rotate — what we actually add over the field: the multi-device
server model (devices hold revocable device keys, year-long account tokens
never leave the server), per-device consumption/cost attribution, and a
published concurrency story — the stress suite
proves one-switch-per-herd and no-rotation burst storms under 100+ in-flight
requests, numbers you can re-run rather than take on faith. (teamclaude
engineered for herd failover too — its post-switch pacing — but doesn't
publish load measurements.) What others do better: teamclaude's per-model
caps and OAuth refresh, claude-swap's install/UX polish. One file, no
database, by design.
On ToS, plainly: every tool above that automatically rotates consumer
subscriptions to continue past a rate limit — including claude-rotate — sits
in the same gray area described in the note at the top of this README: you're
automating around limits Anthropic set per account, using accounts you
personally own and pay for. Anthropic's consumer terms prohibit sharing
account credentials and reselling access; they are less explicit about one
person owning several subscriptions. The bright line: rotating your own
accounts = gray, at your own risk; pooling/sharing/reselling access
(claude-relay-service's core use case) = violation. If your workload is
commercial or shared, use the API with metered billing instead.
Security notes
tokens/*.tokenare year-long bearer credentials for your Anthropic
accounts. They stay on the server, mode 600, and are gitignored along withconfig.json,state.json, andlogs/.- Device keys authenticate devices to the proxy only; revoke one by deleting
its entry inconfig.jsonand restarting. - Don't expose port 8484 to the public internet; bind to a tailnet/LAN
interface or keepbind: 0.0.0.0behind a firewall.
For the next agent (continuation notes)
Everything lives in rotator.py (~450 lines, FastAPI) — read it top to
bottom before changing anything. phase0_proxy.py is a standalone logging
passthrough kept for debugging header behavior; not part of the service.
Load-bearing implementation facts (each was verified empirically — keep them):
- Transparent passthrough. The proxy forwards all paths/methods to
api.anthropic.com, replacing only theAuthorizationheader. Claude Code
sends its ownanthropic-beta: oauth-2025-04-20etc. — do not strip or
reorder client headers. - Encoding trick. Forwarded requests force
accept-encoding: identity
and the response'scontent-encodingheader is dropped — otherwise httpx
auto-decompresses while the original header survives and the client throws
Zlib/Brotli errors. - Quota telemetry comes from
anthropic-ratelimit-unified-5h-utilization
/-7d-utilization/-5h-reset/-statusresponse headers (present on
Max subscription traffic; undocumented — re-verify after Anthropic API
changes). - SSE usage capture parses
data:lines containing"usage"while
streaming chunks through untouched; audit is written in the stream'sfinally.
Roadmap, in intended order:
- Tests.
python3 tests/run_all.py— 101 offline checks, no network
(needs a validconfig.jsonto import the module):test_rotator.pyswitch logic (consume-first ordering, burst-vs-quota
429, cooldown, hysteresis, hold/exhausted verdicts, window-reset
recovery);test_analytics.pySSE usage capture, pricing/cost math,aggregate_auditrollups/alerts/roster;test_proxy.pyend-to-end HTTP
through the real ASGI app against a mocked upstream (auth, header
rewriting incl. the encoding trick, SSE relay, transparent quota-429
rotate+retry, burst pacing, 429 passthrough vs hold-until-reset, admin
endpoints);test_stress.pyhigh-traffic behavior (300-request
fan-out with throughput floor, exactly-one-switch race check with 100
in-flight requests during a quota hit, burst storm without rotation,
20 concurrent held requests, 50 parallel SSE streams, events-list
bound under churn, aggregate_audit speed on a 50k-row log). - OpenAI-compatible endpoint.
POST /v1/chat/completions(see its
section above): OpenAI ↔ Anthropic translation incl. SSE chunks, inoai_compat.py(pure, tested offline intests/test_oai_compat.py);
injectsanthropic-version+anthropic-beta: oauth-2025-04-20,
forwards NO client headers, refuses tool use with a 400. The rotation
loop is shared with the transparent proxy (send_upstream). - Exhaustion signal. The OpenAI endpoint returns 503 +
Retry-After: <earliest reset>when all accounts are spent (after anyhold_max_shold is exhausted) — point a router's breaker at it. The
transparent proxy deliberately still passes Anthropic's 429 through:
Claude Code handles those natively, and that path's contract is
transparency. - Webhook alerts (Slack/Teams/generic POST) firing on the same rules
as the panel's alerts section. - In-process audit rotation — roll
logs/audit.jsonlpast a size/age
threshold so the Operations section's cron recipe becomes unnecessary,
and stop re-reading the whole file per stats call (seek from the tail). - Dockerfile (+ compose example) — also enables running as a sidecar
next to a router in k8s; tokens mounted as secrets. - Hot config reload (
SIGHUPor mtime check) soadd-account/add-devicedon't need a restart. - Token renewal automation — setup-tokens last ~1 year; at minimum
alert on auth failures (401 from upstream on a known-good account
usually means the token died).
Style: keep it one file until it genuinely hurts; stdlib + fastapi/httpx only;
every new claim about Anthropic behavior gets verified against the live API
before being relied on (the phase0_proxy.py harness exists for exactly
that).
License
MIT © 2026 John Doxaras. The Terms-of-Service note at the top of
this README is part of the deal: this software is for rotating accounts you
own and pay for, at your own risk.
Yorumlar (0)
Yorum birakmak icin giris yap.
Yorum birakSonuc bulunamadi