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-readableThe 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.0At 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.0burnlens budgets # per-team status for the current month
burnlens customers # per-customer spend and budget statusVirtual 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-teamThe 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 # defaultThis 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 # defaultThe 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.
Something here wrong or missing? Open an issue or email support@burnlens.app.