Stoke / Guides

Cap Claude Code API Spend with a Local Budget Gateway

Claude Code on a metered Anthropic API key can retry a failed tool call, re-send the same prompt in a loop, or grind through a task far longer than you expected. Anthropic's billing alerts fire hours late, so the first time you notice is on the invoice. This guide puts a small local gateway — Stoke — between Claude Code and the Anthropic API so requests over your cap are refused before Anthropic is contacted, and repeated identical prompts are stopped by a loop breaker.

How this works

Stoke is a single Rust binary (~5.5 MB) that listens on 127.0.0.1:8787 and speaks the Anthropic Messages API on POST /v1/messages. You point ANTHROPIC_BASE_URL at it. On every request Stoke runs its enforcement — auth, budget cap, rate limit, loop breaker — and only then forwards the request to Anthropic. Nothing that trips a check ever reaches Anthropic, so it never costs a token.

Be clear about the scope: this forwards to Anthropic. It does not run Claude Code on local models. Stoke is a policy-enforcing passthrough, not a translator — your prompts still go to Claude, you still pay Anthropic's per-token price, and the gateway's job is to refuse the calls you don't want to pay for.

Install Stoke

curl -sSf https://stokegate.com/install | sh -s -- --version nightly

This pulls a checksum-verified static binary for macOS (arm64/x64) or Linux (x64/arm64). There is no stable tag yet — a bare | sh resolves latest and compiles from source, so keep the --version nightly flag. It installs two binaries: stoke (the server) and stoke-cli.

Configure the gateway

Create stoke.toml. You need one [[providers]] block of type = "anthropic" and one [[keys]] block that sets the dollar cap:

[server]
host = "127.0.0.1"
port = 8787

[[providers]]
name = "anthropic"
type = "anthropic"
base_url = "https://api.anthropic.com"
api_key_env = "ANTHROPIC_API_KEY"
tier = "cloud"

[[keys]]
key = "cc-cap-key"
budget_usd = 20.0
rate_limit_rpm = 60

Two separate keys are in play, and keeping them straight is the whole trick:

When the metered spend for cc-cap-key reaches budget_usd, the next request is refused with HTTP 429 and a body like Budget exceeded: $20.0130/$20.0000 for key cc-cap-k (Stoke identifies the key by its first eight characters). rate_limit_rpm caps requests per rolling 60-second window.

One caveat that decides whether the dollar cap does anything: Stoke only meters models it can price. It ships a built-in price table that today knows claude-haiku-4 and claude-sonnet-4; a model ID it doesn't recognize is priced at $0, so its spend never moves the meter. Point Claude Code at a model Stoke prices (this guide uses claude-haiku-4), and check the table with stoke-cli pricing.

Start Stoke

Stoke is fail-closed: with no STOKE_API_KEYS set (and no STOKE_DEV=1), every request is rejected with 401. The gateway key from stoke.toml must also appear in STOKE_API_KEYS. From the directory containing stoke.toml:

export ANTHROPIC_API_KEY="sk-ant-...your real key..."
export STOKE_API_KEYS="cc-cap-key"
stoke-cli serve

The real key lives only in this process's environment. Anyone hitting the gateway needs the gateway key, not the Anthropic key.

Point Claude Code at Stoke

Claude Code authenticates to a base URL with a Bearer token when you set ANTHROPIC_AUTH_TOKEN (it sends Authorization: Bearer <token>). Stoke authenticates callers by exactly that header — so use ANTHROPIC_AUTH_TOKEN, not ANTHROPIC_API_KEY (which Claude Code would send as x-api-key, a header Stoke's auth ignores, giving you a 401). In the shell where you run Claude Code:

export ANTHROPIC_BASE_URL="http://127.0.0.1:8787"
export ANTHROPIC_AUTH_TOKEN="cc-cap-key"
unset ANTHROPIC_API_KEY
claude

unset ANTHROPIC_API_KEY keeps the client shell free of the real key — Stoke holds it. Claude Code now sends every request to http://127.0.0.1:8787/v1/messages with Authorization: Bearer cc-cap-key, and Stoke enforces before forwarding to Anthropic.

Verify it worked

Health check needs no auth:

curl -sS http://127.0.0.1:8787/health
# {"status":"ok","service":"stoke"}

Confirm fail-closed — a request with no Bearer token is rejected before it touches Anthropic:

curl -sS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8787/v1/messages \
  -H "content-type: application/json" \
  -d '{"model":"claude-haiku-4","max_tokens":16,"messages":[{"role":"user","content":"hi"}]}'
# 401

Now a real forwarded request, proving passthrough:

curl -sS http://127.0.0.1:8787/v1/messages \
  -H "Authorization: Bearer cc-cap-key" \
  -H "content-type: application/json" \
  -d '{
    "model": "claude-haiku-4",
    "max_tokens": 32,
    "messages": [{"role": "user", "content": "Say hi in three words."}]
  }'

You get back a normal Anthropic Messages response. Trip the loop breaker by sending the same prompt five times inside 60 seconds — the threshold is a global constant (5 similar requests in 60s → blocked for 120s):

for i in 1 2 3 4 5; do
  curl -sS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8787/v1/messages \
    -H "Authorization: Bearer cc-cap-key" \
    -H "content-type: application/json" \
    -d '{"model":"claude-haiku-4","max_tokens":16,"messages":[{"role":"user","content":"retry me"}]}'
done
# 200
# 200
# 200
# 200
# 429

The fifth call's body reads: Loop detected: key cc-cap-k sent 5 similar requests within 60s. Blocked for 120s. Check your agent's retry logic — it may be stuck. That is a stuck-retry storm — the most common way agents burn credits — stopped at the gateway.

Inspect the per-key ledger any time. spend_usd accrues only from the priced, non-streaming calls above, so expect a small figure; your exact numbers will differ:

curl -sS http://127.0.0.1:8787/v1/budget -H "Authorization: Bearer cc-cap-key"
# {"auth_enabled":true,"keys":[{"key":"cc-cap-k","spend_usd":0.0007,
#   "limit_usd":20.0,"recent_requests":6}], ... }

What this doesn't do

Read this section before you trust the dollar figure.

stoke-cli pricing

Next steps

Get the binary, drop a [[keys]] cap in front of your metered key, and let the loop breaker do the rest — source, config reference, and issues are at github.com/Ozperium/stoke.

Put a control point in front of your agents

Stoke is a single Rust binary: hard budget caps, a runaway-loop kill switch, and local-first routing — enforced before a provider is ever called. Open source, MIT.

Get Stoke on GitHub