BURNLENSDocsDashboard

Budgets and hard caps

Config keys read out of burnlens/config.py and burnlens/proxy/router.py on 2026-08-16.

Why this is different from an alert

An alert tells you that money is gone. Because the proxy sees a request before forwarding it, BurnLens can decline one that would breach a cap: the caller gets 429, the upstream request is never made, and the call never appears on a bill. That is the whole reason the proxy is a proxy and not a log shipper.

Where config lives

BurnLens looks for a YAML file in this order: the path in BURNLENS_CONFIG_PATH, then ./burnlens.yaml or ./burnlens.yml, then ~/.burnlens/config.yaml. Every example below goes in that file.

It is YAML, not TOML. A .toml file in the same place is not picked up, and the failure is quiet — your limits simply never apply.

Per-key daily caps

Register a key by label so caps can target it, then give the label a daily dollar limit. Raw keys are never displayed back to you.

burnlens key register --label prod-openai --provider openai
burnlens key list
burnlens keys              # today's spend per label against its cap
burnlens keys --json       # same, machine-readable

The caps themselves nest under alerts:, which is not guessable from the CLI:

alerts:
  api_key_budgets:
    reset_timezone: Asia/Kolkata   # IANA name; an invalid value falls back to UTC
    prod-openai:
      daily_usd: 50.0
    default:                       # registered labels with no override of their own
      daily_usd: 5.0

At 100% of the cap the proxy returns 429 before forwarding. The 50% and 80% thresholds fire alerts instead of blocking.

Team and customer budgets

These are keyed on the tag values described in tagging — untagged traffic cannot be budgeted, so tag first.

alerts:
  budget:                    # your own overall spend
    daily_usd: 100.0
    monthly_usd: 2000.0
  budgets:                   # per-team, from X-BurnLens-Tag-Team
    global: 5000.0
    teams:
      platform: 2000.0
      growth: 500.0
  customer_budgets:          # per-customer, from X-BurnLens-Tag-Customer
    default: 50.0
    customers:
      acme-corp: 500.0
burnlens budgets       # per-team status for the current month
burnlens customers     # per-customer spend and budget status

Virtual keys

A virtual key lets you hand a team a token instead of the real provider key. The real key stays in an environment variable on the proxy host; the virtual key carries a monthly budget and an optional model allowlist, and can be revoked without rotating anything upstream.

burnlens vkey issue --label growth-team --team growth \
  --provider openai --upstream-env OPENAI_API_KEY \
  --budget 500 --allow gpt-5.6-terra,gpt-5.6-sol

burnlens vkey list
burnlens vkey revoke --label growth-team

The raw token is printed once, at issue time, and never stored — so it can never be shown again. Store it when you see it.

Unpriced models are blocked by default

A model with no pricing entry costs BurnLens $0. Its spend therefore never advances a budget, and a cap covering it would enforce nothing at all. Rather than let a cap silently become decorative, such a request is rejected with 403.

block_unpriced_models: true   # default

This only applies where a budget actually covers the request. Uncapped traffic on a brand-new model is unaffected. Set it to false if you would rather have availability than enforcement — for instance when a provider has shipped a model before BurnLens has shipped its price — and its spend then counts as $0. Check burnlens pricing to see what is priced.

Budget-aware model downgrade

Instead of hard-blocking, BurnLens can route a request to a cheaper model as a budget runs low. This is on by default, and it only ever activates once a budget exists — with no budget configured, nothing is downgraded.

routing:
  budget_downgrade: true        # default
  downgrade_threshold_pct: 20   # under 20% of the budget remaining
  downgrade_threshold_usd: 5.0  # ...or under $5 remaining
  log_downgrades: true          # default

The percentage check runs first, so when both would trigger the reason is recorded as budget_pct. Every downgrade is logged and burnlens routing shows the activity. Set budget_downgrade: false to always receive the model you asked for and take the 429 instead.

Exact enforcement semantics

Behaviour under concurrency, streaming responses, retries and unpriced models — with the implementing function cited for every claim — is documented in BUDGET_ENFORCEMENT.md. Read it before relying on a cap as a financial control rather than a guardrail.

All documentation

  • Overview & install — What BurnLens is, how to install it, and which of the two entry points you want.
  • Scanning coding agents — Import Claude Code, Cursor, Codex and Gemini CLI cost history from local logs.
  • Proxy & tagging — Route production API traffic through the local proxy and attribute it with tags.
  • Budgets & hard caps — Daily key caps, team and customer budgets, virtual keys, downgrade routing.
  • CLI reference — Every burnlens command, and where the config file and database live.

Something here wrong or missing? Open an issue or email support@burnlens.app.