Spend Brake
Prices and caps requests to the Brave search API through the Spend Brake proxy, including agent search use.
Sends Spend Brake spend alerts to Discord via webhooks, including actionable Kill/Raise/Mute links.
Proxies and prices Google Maps Platform requests (Places, Geocoding, Routes), enforcing spend caps before forwarding.
Proxies and prices HERE API requests, enforcing spend caps before forwarding.
Proxies and prices Mapbox API requests, enforcing spend caps before forwarding.
Sends Spend Brake spend alerts to Slack via webhooks, including actionable Kill/Raise/Mute links.
Proxies and prices Twilio Messaging and Verify requests, enforcing spend caps before forwarding.
Spend Brake
Hard dollar caps, enforced in real time, for every paid API your apps, test suites and AI agents
call. Spend Brake is a self-hosted egress proxy: swap one base URL and one key, and every request
is priced before it leaves, checked against your caps in one atomic step, and refused with a
clear 402 once a cap would be crossed. Built in: Google Maps Platform (Places, Geocoding,
Routes), Twilio Messaging and Verify, Mapbox, HERE, and the agent search APIs Tavily, Exa, Brave
and SerpApi; any other API can be added as a custom API, priced per request or at the cost it
reports.
You keep your provider account and your own key. The real key never leaves Spend Brake; clients only ever hold revocable virtual keys.
Documentation: docs.spendbrake.dev. Hosted service: spendbrake.dev.
5-minute quickstart
For a production install (HTTPS, published images, backups, upgrades, scaling), see Self-hosting (docs/self-hosting.md in this repo). To use the hosted service instead, see the Quickstart.
Requirements: Docker, Node 22+ (for the CLI), a Google Maps Platform key.
git clone https://github.com/KIvanow/spend-brake && cd spend-brake
cp infra/.env.example infra/.env
# fill in the secrets, e.g.:
sed -i "s|^BRAKE_MASTER_KEY=.*|BRAKE_MASTER_KEY=$(openssl rand -base64 32)|; \
s|^BRAKE_ACTION_SECRET=.*|BRAKE_ACTION_SECRET=$(openssl rand -base64 32)|; \
s|^BRAKE_SETUP_TOKEN=.*|BRAKE_SETUP_TOKEN=$(openssl rand -hex 24)|; \
s|^VALKEY_PASSWORD=.*|VALKEY_PASSWORD=$(openssl rand -hex 24)|; \
s|^POSTGRES_PASSWORD=.*|POSTGRES_PASSWORD=$(openssl rand -hex 24)|" infra/.env
export BRAKE_VERSION=0.3.0 # the published images to run
docker compose -f infra/docker-compose.yml -f infra/docker-compose.release.yml up -d --waitThat starts the proxy (:8080), admin API (:8081), notifier (:8082), dashboard (:3000),
worker, Valkey and Postgres, listening on 127.0.0.1 only. Then set up your org, store the Google key and get a virtual key:
npm install -g @spend-brake/cli # or run it without installing: npx @spend-brake/cli
# Secrets from the environment (or omit them to be prompted): flags would land in shell history.
export BRAKE_SETUP_TOKEN="$(grep BRAKE_SETUP_TOKEN infra/.env | cut -d= -f2)"
read -rs GOOGLE_MAPS_KEY && export GOOGLE_MAPS_KEY
brake init --api-url http://localhost:8081 --proxy-url http://localhost:8080 \
--org acme --day-cap 5 --month-cap 50To build the images and the CLI from source instead, leave out the release file and add
--build (docker compose -f infra/docker-compose.yml up -d --build --wait), then
pnpm install && pnpm --filter @spend-brake/cli build and use node apps/cli/dist/brake.cjs.
init prints the owner token once (keep it in a password manager) and saves an admin token for
the CLI. Valkey and Postgres are only reachable from the other containers.
brake init prints your virtual key once, checks whether the Google key is restricted, and
prints the exact Google Cloud steps (console and gcloud) to restrict the real key to the
proxy's egress IPs and the Maps APIs, so the proxy is the only path to spend. Then swap the base
URL and key in your app:
- https://maps.googleapis.com/maps/api/geocode/json?address=Sofia&key=AIza…
+ http://localhost:8080/google-maps/maps/api/geocode/json?address=Sofia&key=sb_live_…
- https://places.googleapis.com/v1/places:searchText X-Goog-Api-Key: AIza…
+ http://localhost:8080/google-places/v1/places:searchText X-Goog-Api-Key: sb_live_…Every response carries X-Brake-Cost-Micros and X-Brake-Remaining-Micros. Past a cap:
HTTP/1.1 402 Payment Required
{"error":"budget_exceeded","message":"Spend cap reached for org. Stop and ask the user to raise the cap.",
"scope":"org","window":"d:20261029","cap":5000000,"spent":4990000,"resetsAt":"2026-10-30T00:00:00.000Z"}Open the dashboard at http://localhost:3000 with the owner token, or the admin token brake init
saved in ~/.config/brake/config.json.
Related MCP server: AgentsHub Web Search
Capping an AI agent
Run any agent or script inside a session with its own budget:
export BRAKE_KEY=sb_live_… BRAKE_URL=http://localhost:8080 BRAKE_API_URL=http://localhost:8081
brake session --budget 5 --day-budget 20 --agent claude -- claudeClaude Code (or anything else) runs with a session key in BRAKE_KEY that can spend at most $5,
counts against the agent's $20 per day and the parent key's caps, and is revoked when the agent
exits. Inside, the agent calls brake maps geocode "…", brake maps search "…" or
brake call <provider> <method> <path>; each prints JSON with the cost. When a cap or the loop
breaker stops a call, brake exits 3 or 4 with one line telling the agent to stop and ask you.
See Capping an AI agent and the
CLI reference.
Run the agent as a different OS user than the one that ran brake init: the agent must not be
able to read the saved admin token, or it could lift its own caps (a different XDG_CONFIG_HOME
hides the file from brake, not from the agent). brake session warns when it can.
brake doctor checks a workload for these holes from where it runs (a readable admin token,
raw provider keys, an uncapped key, unpriced routes that are let through), and --prove shows
that an exhausted budget stops forwarding. It lists what it cannot verify rather than calling
anything protected. Agents that search through MCP can swap their Tavily or Exa server for
brake mcp (preview), which sends every search through the proxy.
Google's controls vs Spend Brake
Google Cloud budgets | Maps Platform quotas | Google spend caps | Spend Brake | |
Stops spend | No, alerts only | Yes | Yes | Yes, before the request leaves |
Unit | Dollars | Requests per API per minute/day | Dollars | Dollars (micro-dollar exact) |
Covers Maps Platform | Yes | Yes | No, per Google's docs at the time of writing; check yours | Yes |
Knows SKU tiers (field masks) | After billing | No: a $0.04 request counts like a free one | After billing | Yes, priced per field mask |
Delay | Hours (billing data) | Real time | Billing data | Real time |
Granularity | Project / billing account | Project, per API | Billing account | Org, key, SKU, environment, agent, session; day and month |
Loop / runaway detection | No | No | No | Yes, trips a circuit |
Kill switch | No | Set quota to 0 by hand | No | One call, link or button; immediate |
Alerts with actions | Email, Pub/Sub | No | No | Webhooks (Slack, Discord, Teams, raw) and email with Kill / Raise / Mute links |
Keep Google's own budgets and quotas on as a backstop. More in Google's controls vs Spend Brake.
Terms of service
Spend Brake is built to stay inside the Google Maps Platform Terms of Service:
Bring your own key, always. Each org uses its own Google key and billing account; Spend Brake never resells access or shares a key.
No caching to cut billing. Responses are streamed through, never stored to answer later requests. Only Place IDs may be stored indefinitely and geocode coordinates for at most 30 days; Spend Brake stores neither, only request metadata (SKU, units, cost, status, a hash).
Usage stays visible to Google. Every request reaches Google with your key.
Client-side Maps is out of scope. The Maps JavaScript API, tiles and client widgets are not proxied.
This is not legal advice: have someone read sections 3.2 and 3.3 of the Maps Platform Terms and the Service Specific Terms before you offer Spend Brake to others as a hosted service.
How it works
app / agent ──▶ proxy ──▶ Google
│ 1 authenticate the virtual key (cached 5 s)
│ 2 price: SKU from path + field mask, worst case in micro-dollars
│ 3 one Lua call: kill switch, loop breaker, reserve against every cap
│ 4 forward with the real key (decrypted locally, never logged)
│ 5 settle the actual cost + usage event (one Lua call, background)
▼
Valkey ──▶ worker ──▶ Postgres ledger, alerts ──▶ notifier ──▶ webhooks / emailOvershoot is bounded by one request's worst case. Valkey down means fail closed (503) unless a
key opts into a small fail-open allowance.
Part | What it does |
The request path | |
Admin API (keys, caps, kill, credentials, spend, events, webhooks, sessions); OpenAPI at | |
Ledger, sweeper, alert jobs, partitions, price drift check | |
Signed webhooks, email, action-link pages | |
| |
apps/web | Dashboard |
Atomic budgets on Valkey (Lua) | |
Prices, SKU rules, provider adapters, migration estimator | |
Postgres schema and migrations | |
packages/shared | Envelope encryption, signing, redaction, telemetry |
Also: a migration estimate (brake estimate), daily reconciliation with Google's billing
export (Business / Enterprise), alerts and signed webhooks,
Prometheus and OpenTelemetry,
CSV export and the
admin API.
Development
Requirements: Node 22+, pnpm 10, Docker.
pnpm install
pnpm dev # Valkey + Postgres in Docker, then every app in watch mode
pnpm test # integration tests start their own Valkey/Postgres containers
pnpm lint && pnpm typecheckValkey listens on localhost:16379 and Postgres on localhost:15432 (user/password/db brake).
Override with BRAKE_VALKEY_PORT / BRAKE_POSTGRES_PORT.
Editions and license
Everything outside ee/ is Apache-2.0 (LICENSE): the proxy, budget engine, caps, kill switch,
loop detection, CLI, catalog data, dashboard and signed webhooks. It never needs a license key.
ee/ (LICENSE-EE) adds, with a Business or Enterprise license: single sign-on (OIDC, SAML),
roles beyond owner/admin/viewer, two-person approval for raising caps, audit export (JSON Lines
to a file, S3 or syslog), more than one org per install, the signed catalog feed, and billing
reconciliation. Licenses are Ed25519-signed files checked offline (no phone-home): brake license install acme.license. After a license expires its features keep working for 14 days, then turn
read-only; the proxy and caps never stop. See ee/README.md.
The hosted service's plans and license sales run through Stripe (apps/billing, test mode);
see apps/billing/README.md.
Contributions use DCO sign-off (git commit -s); see CONTRIBUTING.md.
Available Tools
3 toolsbrake_budgetRemaining budgetARead-only
Show the spend caps that apply to this key and what is left of each. Read-only: it cannot change a cap.
Inputs: no named parameters.
exa_searchExa search (capped)A
Search the web with Exa. Each call is charged against a hard spend cap; the result shows its cost and the budget left.
Inputs: query (required), text, type, numResults, +3 more.
tavily_searchTavily search (capped)A
Search the web with Tavily. Each call is charged against a hard spend cap; the result shows its cost and the budget left.
Inputs: query (required), topic, time_range, max_results, +4 more.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 3 tools
brake_budget is clearly a read-only budget inspection tool, distinct from the two search tools. tavily_search and exa_search serve the same purpose (web search) and differ only by provider, which the descriptions make explicit, but an agent has no strong basis to choose between them besides name recognition.
All names are snake_case and the two search tools follow a clean <provider>_search pattern. brake_budget breaks the action-first convention but is still readable and clearly grouped under the server's 'brake' concept.
Three tools is thin for a spend-control server; the surface is essentially one read-only status tool plus two interchangeable search backends. It is not wrong for the purpose, but each tool barely stakes out distinct territory.
There is no way to set, raise, or reset caps, nor to view spend history or usage over time, so the budget lifecycle is read-only by design and leaves dead ends. Coverage of search is also limited to two providers with no fallback or aggregate search option.
Maintenance
Related MCP Connectors
Live web search and research synthesis for agents, with free samples and x402 USDC payments.
Web search and page fetch for AI agents. Results come from a shared cache; cached calls cost less.
Web search for AI agents — one tool across 6 engines, routed to the cheapest + cached.
x402 pay-per-call APIs for agents: web search, wallet/token checks, text structuring.
Related MCP Servers
- AlicenseAqualityCmaintenancePaid web research MCP tools for autonomous agents: search, page extraction, citations, and diff monitoring through a live x402 API. Unpaid calls return the Base USDC payment requirement so agents can pay and retry safely.41MIT
- AlicenseNot gradedqualityDmaintenanceWeb search for AI agents. Up to 10 results for $0.001 USDC via x402 on Base. No API key.MIT
- AlicenseNot gradedqualityCmaintenanceEnables LLM agents to search the web and fetch pages as clean, budget-controlled text through MCP tools.8MIT
- AlicenseNot gradedqualityAmaintenanceEnables free-first English and Chinese web search through zero-key engines plus optional paid providers, returning compact multi-source evidence with bounded token and request budgets. Each result exposes provenance, independent source counts, routing decisions, and visible provider failures so agents can verify claims rather than receive unexplained empty results.173 npmApache 2.0