Skip to content

Repository files navigation

tokenbrake

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

Why

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.

Install

npm install -g tokenbrake
# or run without installing:
npx tokenbrake --help

Requires Node.js ≥ 20.

Quickstart

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.com

Then point your client at it:

export ANTHROPIC_BASE_URL=http://127.0.0.1:4789
claude   # runs as normal — but stops cold at 5M tokens

When the budget is reached, every further request gets:

{
  "error": {
    "type": "circuit_open",
    "message": "tokenbrake budget exhausted: 5012345/5000000 tokens used. ...",
    "used": 5012345,
    "budget": 5000000
  }
}

OpenAI-compatible APIs (and Codex)

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.

Commands

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 window

Live web dashboard

Open 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

Options

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

How it counts

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:

  • Anthropicinput_tokens / output_tokens (plus cache_read_input_tokens and cache_creation_input_tokens unless --no-cache-tokens), from both JSON responses and the message_start / message_delta events of a stream.
  • OpenAI-compatibleprompt_tokens / completion_tokens (chat) or input_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).

Use as a library

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.

How the ceiling holds under concurrency

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-output to tune the reservation for requests that omit max_tokens.

Limitations

  • 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/reset control endpoint is unauthenticated — don't expose it to untrusted networks.

License

MIT © Caiden Jennings

About

A circuit breaker for your LLM bill: a zero-dependency local proxy that hard-stops requests when a token budget is reached.

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages