From zero to a metered, capped, breaker-protected AI budget in a few minutes.
TokenBrake runs on your own machine. One zip, zero dependencies, needs Node 24+. There is deliberately only one download and only one way to start it.
Download: tokenbrake-beta.zip unzip it into your home folder so it lands at ~/TokenBrake cd ~/TokenBrake && node proxy.mjs
You should see:
TokenBrake proxy live → http://localhost:8787 (point your AI's base URL here: /openai or /anthropic) runaway breaker: GUARD · state: http://localhost:8787/breaker
Confirm it's alive at any time with curl http://localhost:8787/health.
It binds to 127.0.0.1 on purpose. The proxy is reachable only from your own machine. There's no shared secret to set, no port to firewall, and no network surface to get wrong — anyone who can reach it can already read your files.
Two commands, no API keys, no network, no money — both run against the same detection code that guards your traffic:
node demo-runaway.mjs # simulates one agent stuck 02:00–08:00, nobody awake node test-runaway.mjs # 47 detection & breaker tests node test.mjs # 100 metering & pricing tests node test-license.mjs # 29 licence-verification tests
The demo prints the comparison the whole product exists for: $280.01 with a budget cap alone (23,334 calls, acted after 5.8 hours) versus $3.60 with the breaker (300 calls, tripped in 10 seconds).
TokenBrake is a drop-in proxy. Change one line — your provider base URL. Your provider API key still passes straight through to the real provider and is never stored.
from openai import OpenAI
client = OpenAI(
base_url="http://localhost:8787/openai/v1",
api_key="sk-...your real key...",
default_headers={
"x-tokenbrake-agent": "support-bot", # label this stream
},
)
from anthropic import Anthropic
client = Anthropic(
base_url="http://localhost:8787/anthropic",
api_key="sk-ant-...your real key...",
default_headers={
"x-tokenbrake-agent": "content-gen",
},
)
What's an "agent"? The x-tokenbrake-agent header labels one stream of usage — a bot, a script, a project. Budgets and the breaker are both per agent, so a stuck summariser can be stopped without touching your support bot. Leave the header off and everything from that provider is lumped under the provider's own name.
Give each agent a monthly budget and a mode:
node set-budget.mjs support-bot 200 hard node set-budget.mjs research-bot 50 soft
An agent with no budget set is metered but never capped. Budgets are monthly and reset with the period.
A budget cap is a lagging indicator and always will be — it cannot act until the money is already spent. If your ceiling is $400, a stuck loop will happily spend $400 and then stop. That limitation is exactly why the next section exists. Set budgets anyway; they're the backstop.
The breaker watches the shape of your traffic rather than the total, so it can act in seconds instead of at the cap. It's on by default.
| Reason | What triggers it |
|---|---|
loop | ≥80% of a rolling window (up to 60 seconds, minimum 12 calls) is the same request and the rate is ≥20 calls/min. Both, or it isn't a loop — so 12 identical calls in 10 seconds trips it, and a busy hour of varied ones never does. |
burn | Spend passes $5.00/minute, whatever the pattern. |
error_storm | ≥50% of calls failing at ≥15 calls/min — a retry storm. |
surge | ≥8× this agent's own learned baseline rate, and at least 30 calls/min. |
Repetition, not volume, is the primary signal. A stuck agent sends the same request over and over; a legitimate batch job sends a lot of different requests. That asymmetry is the whole design — a fast batch job with 200 varied prompts passes straight through. A false positive that blocks real work costs more trust than a missed loop costs money.
Fingerprinting has two forms: a hash of the whole normalised request, and a hash of the model plus the last message only. The second catches the nastier shape — an agent appending to a growing conversation while asking the same question forever, so every request differs and nothing progresses. Only hashes are kept. Your prompt text is never retained.
Closed → open → half-open. After the cooldown it lets exactly one call through to see whether the agent recovered. Recovered, it closes itself. Still stuck, it re-opens and backs off — 60 seconds, then 2 minutes, then 4, up to a 15-minute ceiling. Thirty minutes clean and it forgets the trip count entirely.
That self-healing is what makes it safe to leave switched on: a false trip costs you one cooldown, not a 3am page.
Fail-open is absolute. Every detection path is wrapped. If the breaker itself throws, your call goes through. It cannot take your production down.
node reset.mjs --status # what's tripped, why, and for how long node reset.mjs summariser # clear one agent node reset.mjs # clear everything
Or read the raw state at http://localhost:8787/breaker, which returns the mode, every agent's state, and the last 20 incidents as JSON.
You should rarely need reset.mjs — a tripped breaker half-opens on its own. It's the manual override for when you know better than it does, which you sometimes will.
TB_BREAKER=watch node proxy.mjs # detect and log, never block TB_BREAKER=off node proxy.mjs # disable entirely
Watch mode is the honest way to start if you're nervous. Run it for a week against real traffic, read /breaker, and see whether it would have tripped on anything you didn't want stopped. Then turn on guard mode.
Optional, and macOS-only — the proxy and breaker run anywhere Node does. The widget shows cloud API dollars and local-model electricity in one glance.
brew install swiftbar # then open SwiftBar and choose ~/TokenBrake/swiftbar as its plugin folder
A 🔥 $… appears in your menu bar. Local models running through Ollama and friends are detected automatically — nothing to configure. Set your electricity rate with TB_CENTS_PER_KWH if 15¢/kWh isn't right for you.
TokenBrake meters eight providers including streaming and prompt caching. Most are OpenAI-compatible — point the OpenAI SDK at the matching path. Gemini uses Google's own wire format, so point the Google GenAI SDK's base URL at …/gemini.
| Provider | Base URL to use |
|---|---|
| OpenAI | http://localhost:8787/openai/v1 |
| Anthropic | http://localhost:8787/anthropic |
| xAI / Grok | …/xai/v1 or …/grok/v1 |
| Groq | …/groq/v1 |
| DeepSeek | …/deepseek/v1 |
| Mistral | …/mistral/v1 |
| Google Gemini | …/gemini (Google GenAI SDK) |
| OpenRouter | …/openrouter/v1 (hundreds of models) |
from openai import OpenAI
client = OpenAI(
base_url="http://localhost:8787/groq/v1", # or /xai, /deepseek, /mistral, /openrouter
api_key="...your real provider key...",
default_headers={"x-tokenbrake-agent": "grok-bot"},
)
The proxy forwards only to this allowlist, and strips any scheme or host a caller tries to smuggle into the path. Anything else is refused rather than fetched.
Pricing is estimated from provider-published rates and rounds up when uncertain, so a cap can't be quietly slipped. It's a safety brake, not a billing system of record — keep your provider-side limits on too.
A metering tool with stale prices is worse than no metering tool, because it's confidently wrong. So the price table carries the date a human last checked it against every provider's published pricing page, and test.mjs fails once that date is more than 90 days old. You can see both for yourself:
node -e "import('./lib/pricing.js').then(p=>console.log(p.VERIFIED_ON, p.priceBookAgeDays()+' days old'))"
Two known limits, stated rather than buried. Long-context tiers aren't modelled: OpenAI, Gemini and xAI all charge roughly double past a context threshold, and the table holds one rate per model — so very large-context calls under-estimate. Time-of-day pricing isn't modelled either: DeepSeek bills differently at peak and off-peak. Everywhere else the rule holds — when we're unsure, we round up, and an unrecognised model bills at the highest rate we know of rather than a comfortable guess.
There is no config file. Everything is an environment variable, and every one of them is optional.
| Variable | Default | What it does |
|---|---|---|
TB_PORT | 8787 | Port the proxy listens on. |
TB_BREAKER | guard | guard blocks while open · watch records only · off disables. |
TB_BURN_PER_MIN | 5.00 | Dollars per minute that counts as a burn, regardless of pattern. |
TB_CENTS_PER_KWH | 15 | Your electricity rate, for the local-model estimate. |
TokenBrake is source-available, under PolyForm Small Business 1.0.0. It's free to use — including at work — for individuals and for any company under 100 people and under $1M revenue. That covers almost everyone, and nothing is held back in the free version: no trial clock, no locked features, no key to enter.
Companies above that threshold buy one commercial licence, once — $249, one company, unlimited machines and agents, no renewal. Pricing has the details.
Buy on Gumroad, and you'll get a licence key on the receipt and by email. Paste it at tokenbrake.com/get and you'll get your TokenBrake key straight back. Set it as TB_LICENSE and restart:
TB_LICENSE="TB-…" node proxy.mjs TokenBrake proxy live → http://localhost:8787 runaway breaker: GUARD commercial licence: VALID · business · ref 9f2a1c4e7b8d · perpetual
It also appears at /health, as JSON, so you can point a monitoring check or an auditor at it.
Be clear about what that key is — it's proof, not a lock.
Nothing in TokenBrake is gated behind a licence. Every feature works in the free version, and the key is never consulted while your traffic is flowing. Setting it changes one line of startup output and one field in /health, and nothing else. An unlicensed install and a licensed one behave identically.
That is on purpose. This software sits in front of your production API calls. The day it starts refusing work over a licensing question is the day it stops being safe to install — so it never will. We would rather a company that owes us $249 quietly not pay than have one that did pay get woken at 3am by our licence check.
What the key gives you is the thing a company over the threshold actually needs: something verifiable to show that your use is licensed.
Paste the same Gumroad key again. Your TokenBrake key is derived from your purchase rather than looked up in a database, so the same purchase always returns the same key. There is no support ticket to raise and no account to recover.
Verification is offline and permanent. The key is signed with Ed25519; TokenBrake carries only the public half, which can check a signature and never create one. So verification needs no network, ever — and your licence keeps working whether or not we're still here. Gumroad is the till, not the lock.