# Cap spend with a budget

A ceiling over a filtered slice of your ledger, alert-only or enforcing. What it stops, what it cannot, and how far past it you get.

Source: https://tokentriage.com/docs/build/dashboard-budgets

---
import Callout from '../../../components/Callout.astro';
import Details from '../../../components/Details.astro';
import NextSteps from '../../../components/NextSteps.astro';
import Screenshot from '../../../components/Screenshot.astro';

A budget is a spend ceiling over a slice of your traffic for a calendar period. Define one and
you get alerts as it fills. Turn on enforcement and requests in that slice stop going through
once it is exhausted.

This page covers the whole mechanism: the config, the two operating modes, the three
properties that decide how much to trust the number, and how a budget differs from the
similarly-named governance limit.

## Define one

```yaml
budgets:
  - id: live-model-cap
    filter: request.model = "gpt-4o-mini"
    period: day                  # day | week | month, UTC boundaries
    limit_usd: 20.00
    thresholds: [0.5, 0.8]       # ascending fractions in (0, 1]
    enforce: true                # default: false — alert-only
```

Five fields. The `filter` uses the same query language as the Requests explorer, so any slice
you can select on screen you can put a ceiling on: one model, one tenant, one feature, one
agent. An empty filter means all traffic. `thresholds` are the fractions at which an alert
fires as burn climbs.

Periods run on UTC boundaries and reset there. A `day` budget resets at 00:00 UTC rather than at
midnight where you are.

You can create a budget from the Budgets surface instead. It composes the same YAML, shows it
to you, and puts it through validate, diff and apply under a compare-and-set guard, so the
running config and your file stay in step.

## Alert-only or enforcing

`enforce` decides which of two things a budget is, and the surface states which one in words
rather than leaving you to infer it from a flag.

**Alert-only** (`enforce: false`, the default). Crossing a threshold fires an alert. Traffic is
untouched, and spend continues past the limit. The surface says so plainly: *this does not stop
spending*.

**Enforcing** (`enforce: true`). A request in the slice that arrives after the ceiling is
exhausted is turned away with a synthesized `429`, and that refusal lands in the ledger as a
first-class row rather than vanishing. Refused requests are excluded from burn, so turning
traffic away cannot inflate the number that caused it.

Start alert-only. Watch a full period. Then decide whether the ceiling is in the right place
before you let it stop anything.

## Three properties that decide how much to trust it

<Screenshot
  stem="budgets"
  dark={true}
  url="localhost:9090/ui/budgets"
  alt="The Budgets surface showing a budget's burn-down: the fraction of its ceiling consumed, its alert thresholds, whether it is enforcing or alert-only, and a disclosed approximate-overrun bound computed from lag and drop counters."
  caption="A budget's burn-down, with its enforcement mode and the disclosed bound on how far past the ceiling a breach can get (real dashboard, demo mode)."
/>

**Enforcement fails open.** The evaluator runs off the request path. If it goes stale or dies,
enforcement suspends: traffic flows, an alert fires, and the badge reads `suspended`. A broken
budget will not stop your agents. That is the right trade for a gateway, and it means a budget
is a cost control rather than a safety control.

**The overrun is bounded, and the bound is published.** Admission reads a snapshot while burn
is tallied off-path, so a breach can overshoot. The surface shows an approximate-overrun bound
computed from the actual lag and drop counters rather than an adjective. Read it as the answer
to "how much past the ceiling could I be", and treat the burn figure as a lower bound.

**Unpriced spend is excluded, not counted as zero.** Rows the price table cannot cost do not
contribute to burn, and their count is disclosed beside it. A slice full of an unpriced model
will not trip a budget, which is a reason to keep the Pricing surface's unpriced triage at
zero. [Override a price](/docs/build/dashboard-pricing-overrides) is how.

<Callout type="key">
A budget is a ceiling on what the ledger could price, evaluated off the request path. It fails
open, it can overshoot by a bound it publishes, and it ignores spend it could not cost. All
three are visible on the surface rather than being things you have to know.
</Callout>

## A budget is not a governance limit

Two mechanisms cap spend, they have similar names, and choosing the wrong one wastes an
afternoon.

| | `budgets:` | `governance.limits` |
|---|---|---|
| Scope | a filter over the ledger | a node in a tenancy tree |
| Selects by | any ledger field | the virtual key that made the request |
| Types | spend only | spend, token and request quotas, rate, concurrency |
| Hierarchy | none — budgets are flat | inherited down the tree, resolved at every level |
| Surface | Budgets | Governance |

Use a **budget** when the slice is a query: this model, this feature, this month. Use a
**governance limit** when the slice is a customer or a team you have issued a key to, and you
need more than dollars. [Governance limits](/docs/build/governance-limits) covers the second.

They coexist. A request can be inside both, and both can stop it.

## Alert on a budget

A budget's own `thresholds` fire alerts as it fills. You can also write a rule in the
`alerts:` block that watches a budget's state:

```yaml
alerts:
  rules:
    - id: cap-crossed
      type: budget_threshold
      budget_id: live-model-cap
      notify: [ops-webhook]
```

<Callout type="warning">
Do not set `min_requests` on a `budget_threshold` rule. That rule tracks a latched condition,
which has no windowed sample count, so the floor can never be satisfied and the rule silently
stops firing. Config validation does not reject it, because it only checks that the value is
non-negative. `min_requests` is meaningful on sampled rules such as `error_rate` and
`spend_threshold`.
</Callout>

[Alert on what the ledger sees](/docs/build/dashboard-alerts) covers the rest of the rule
types.

## What can go wrong

**The budget does not fire.** Check the filter against the Requests explorer: paste the same
filter text there and confirm it selects rows. A filter that matches nothing produces a budget
that never burns.

**Spend went past the ceiling.** Expected, within the published bound, and disclosed. If it
went far past, look at whether enforcement was suspended — a stale evaluator says so on the
badge.

**Burn looks lower than your provider bill.** Compare the unpriced count. Rows with no price
are excluded from burn by construction. [Invoice
reconciliation](/docs/build/cost-reconciliation) is the surface for that comparison.

**The Budgets entry is greyed out.** Budgets are defined but enforcement is not granted; the
entry carries its reason. [Why your sidebar is
shorter](/docs/build/dashboard-navigation) covers the three navigation states.

<NextSteps items={[
  {title: "Alert on what the ledger sees", href: "/docs/build/dashboard-alerts", description: "The nine rule types, silences, and the backtest that shows what would have fired."},
  {title: "Governance limits", href: "/docs/build/governance-limits", description: "The other spend control: per-tenant, hierarchical, and more than dollars."},
  {title: "Cap spend per virtual key", href: "/docs/recipes/cap-spend-per-virtual-key", description: "A worked recipe using the governance side of the choice above."},
  {title: "How to read a number", href: "/docs/build/dashboard-reading-a-number", description: "The disclosures around a burn figure, and what an approximate bound means."}
]} />
