Skip to main content
Glama

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 --wait

That 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 50

To 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 -- claude

Claude 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 / email

Overshoot 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

apps/proxy

The request path

apps/api

Admin API (keys, caps, kill, credentials, spend, events, webhooks, sessions); OpenAPI at /v1/openapi.json

apps/worker

Ledger, sweeper, alert jobs, partitions, price drift check

apps/notifier

Signed webhooks, email, action-link pages

apps/cli

brake: init, agent sessions, capped calls, admin

apps/web

Dashboard

packages/budget

Atomic budgets on Valkey (Lua)

packages/catalog

Prices, SKU rules, provider adapters, migration estimator

packages/db

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 typecheck

Valkey 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 tools
brake_budgetRemaining budgetA
Read-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.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A3.6/5.0

    Scored across 3 tools

    Disambiguation4/5

    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.

    Naming Consistency4/5

    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.

    Tool Count3/5

    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.

    Completeness3/5

    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

    ActivityMaintained
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      A
      maintenance
      Enables 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 npm
      Apache 2.0