A circuit breaker for your LLM bill.
tokenbrake is a tiny, zero-dependency local proxy that sits between any LLM client and the API it talks to. It forwards every request untouched, reads the token usage out of each response, and hard-stops further requests the moment you hit a token budget — returning a clear 429 instead of forwarding the call.
Point a coding agent, a cron job, or an autonomous loop at it and it puts a hard ceiling on how far past your budget a request loop can go.
client ──▶ tokenbrake ──▶ api.anthropic.com / api.openai.com
│
└─ counts tokens, trips at your budget
Autonomous agents fail in an expensive direction. A stuck loop, a runaway retry, a misconfigured max_tokens, a fallback to a pricier model — any of these can burn through a budget while you're asleep. Dashboards tell you after the money is gone. tokenbrake is a hard ceiling, enforced in the request path: once the budget is spent, the next request is refused.
It is deliberately small and boring. No SDK to adopt, no code changes — just an environment variable.
npm install -g tokenbrake
# or run without installing:
npx tokenbrake --helpRequires Node.js ≥ 20.
Cap Claude Code at 5 million tokens:
tokenbrake --budget 5m
# tokenbrake v0.1.0 listening on http://127.0.0.1:4789 → https://api.anthropic.comThen point your client at it:
export ANTHROPIC_BASE_URL=http://127.0.0.1:4789
claude # runs as normal — but stops cold at 5M tokensWhen the budget is reached, every further request gets:
{
"error": {
"type": "circuit_open",
"message": "tokenbrake budget exhausted: 5012345/5000000 tokens used. ...",
"used": 5012345,
"budget": 5000000
}
}tokenbrake -u https://api.openai.com -b 1m --window 1h
export OPENAI_BASE_URL=http://127.0.0.1:4789/v1--window 1h makes it a rolling budget: 1M tokens per hour, auto-resetting each window. Without --window, the budget is absolute until you reset it.
tokenbrake [options] # start the proxy
tokenbrake status # print current usage from a running proxy
tokenbrake status --json # same, as JSON (scriptable)
tokenbrake watch # live auto-refreshing budget dashboard
tokenbrake reset # clear the current windowOpen http://127.0.0.1:4789/__tokenbrake in a browser for a live view of the
budget. It's a single self-contained HTML page (no build, no CDN, no
dependencies — same ethos as the proxy) that subscribes to a Server-Sent-Events
stream and updates the instant a request settles, with a polling fallback if the
stream drops. Shows the usage bar, used/budget/available/reserved, lifetime,
circuit state, and a live sparkline.
For the terminal, tokenbrake watch gives you the same live view:
tokenbrake → https://api.anthropic.com
[████████████░░░░░░░░░░░░░░░░] 42%
used 420,000 / 1,000,000 reserved 12,000 available 568,000
lifetime 1,250,000 circuit: closed
updated 12:30:01 · ctrl-c to exit
You can also hit the control endpoints directly:
curl http://127.0.0.1:4789/__tokenbrake/status
curl -X POST http://127.0.0.1:4789/__tokenbrake/reset| Flag | Default | Description |
|---|---|---|
-u, --upstream <url> |
https://api.anthropic.com |
Upstream API base URL |
-b, --budget <tokens> |
1000000 |
Token budget. Accepts 2m, 500k, 1.5b, 2_000_000 |
-p, --port <n> |
4789 |
Listen port |
-h, --host <addr> |
127.0.0.1 |
Listen host |
--window <duration> |
none | Rolling window, e.g. 30s, 15m, 1h, 1d. Omit for an absolute budget |
--state <path> |
~/.tokenbrake/ledger.json |
Where usage is persisted |
--no-cache-tokens |
off | Don't count prompt-cache tokens toward the budget |
tokenbrake counts tokens, not dollars — token counts come straight from the provider's own response and are always correct, whereas pricing tables go stale and vary per account. It reads:
- Anthropic —
input_tokens/output_tokens(pluscache_read_input_tokensandcache_creation_input_tokensunless--no-cache-tokens), from both JSON responses and themessage_start/message_deltaevents of a stream. - OpenAI-compatible —
prompt_tokens/completion_tokens(chat) orinput_tokens/output_tokens(Responses API), from JSON or the trailing usage chunk of a stream.
The streamed response is tee'd: your client receives the bytes verbatim and in real time, while a second copy is read only to tally usage. The stream is never buffered or modified.
Note: for OpenAI streaming, usage is only emitted if the client sets
stream_options.include_usage: true. Claude Code and the Anthropic SDK report usage by default.
A USD layer, with user-supplied per-model rates, is on the roadmap (see CHANGELOG).
import { createProxyServer, Ledger, type Config } from "tokenbrake";
const config: Config = { /* ... */ };
const server = createProxyServer(config);
server.listen(4789);The token-counting primitives (SseUsageExtractor, usageFromJson, Ledger) are exported and independently usable.
tokenbrake uses optimistic reservation. At the gate, before forwarding, it
estimates the request's cost (its max_tokens output cap plus an input estimate
of ~chars/4) and reserves that against the budget. After the response, the
reservation is reconciled to the real usage. So if your client fires many
requests in parallel, they can't all clear the budget check before any of them
records usage — concurrent requests must collectively fit, or the extras get a
429 immediately.
Two deliberate edges:
- A lone request larger than the remaining budget is still admitted (we
can't shrink it, and refusing it outright is worse than one bounded overshoot).
So worst-case overshoot is one request's size — not
concurrency × size. - The estimate is rough (tokenizers vary), so reconciliation can leave the final
total slightly over budget by the last request's estimate error. Set
--default-max-outputto tune the reservation for requests that omitmax_tokens.
- Counts tokens, not dollars (by design — see above).
- OpenAI streaming usage requires the client to send
stream_options.include_usage: true(Chat Completions) — the Responses API reports it automatically. Without it, a streamed OpenAI Chat call reports no usage and won't count toward the budget. - The OpenAI Responses-API usage path is exercised against a mock upstream in the test suite, not yet against the live API — please report any mismatch.
- Designed for local/loopback use in front of one or more agents. It is not an authenticating gateway, and the
/__tokenbrake/resetcontrol endpoint is unauthenticated — don't expose it to untrusted networks.
MIT © Caiden Jennings