How a cap decides
This page explains the hard cap: the capMicroUsd and spentMicroUsd figures on the Budgets page, and the 402 or 429 a capped workspace receives at the gateway.
What it is
A budget is a monthly spend ceiling on a workspace, evaluated in path, before the provider is contacted. An armed budget is designed to refuse at the ceiling, before the provider is contacted. A disarmed budget measures and alerts only. The 50, 80 and 95 percent alert thresholds are a separate mechanism and never refuse anything.
How it is computed
Unit. Integer micro-USD, 1000000 = $1.00. No float touches a counter. capUsd parses with integer math: dollars times 1,000,000 plus a right-zero-padded six-digit fraction. More than six decimals is rejected, not rounded.
Period. The UTC calendar month, keyed YYYY-MM. The reset is intrinsic: on the 1st at 00:00 UTC a new key addresses a counter never written, which reads zero.
Decision, in order:
- Build the scope chain: workspace, the key, and the project if the request carries one.
- Keep matching budgets; take the armed subset.
- Verifiability gate. An armed cap needs a spend-checkpoint round trip inside 60 seconds. Never checkpointed, or older, refuses
402 budget_unverifiable. - Tightest wins.
headroom = cap - spentper armed budget, integers. Lowest headroom decides; ties break on the lower budget id. - If
spent >= cap, refuse. Block:402, codebudget_exceeded. Pause:429, same code,Retry-After = ceil((next month start - now) / 1000), floored at 1. - Otherwise allow, then debit this request's estimate onto every scope in the chain.
The estimate. Admission has no token count, so it uses the published rule tokens = max(1, floor(request_bytes / 4)) priced at 50,000 micro-USD per million tokens — the cheapest input rate in the frozen price list, i.e. 0.05 micro-USD per token. cost = floor(tokens x 50000 / 1000000).
That rate is the price-list FLOOR on purpose. The admission estimate is a deliberate lower bound: it is spent before anything is measured, so an over-estimate would let a cap refuse traffic on spend that never happened. A cap may trip a hair late on real spend not yet reconciled; it must never trip early on invented spend. Because the floor rate divides and floors, a request on an expensive model is under-estimated at admission and corrected the moment the receipt is priced.
After the response the rule is recomputed over measured bytes and the difference against the priced result is applied: debit if positive, credit if negative, clamped at zero. An unpriced model produces no reconcile and the estimate stands.
Across nodes. Every 5 seconds each node publishes its counters and the control plane publishes each capped workspace's authoritative priced month-to-date from rollup_daily. Imports take the maximum and never roll backwards, so a lagging view under-states and never invents spend.
Worked example. Cap $2,000.00, armed, block, workspace scope, so cap = 2000000000. The counter reads 1999400000. A request arrives with an 8,192-byte body.
| Step | Arithmetic | Result |
|---|---|---|
| Headroom | 2000000000 - 1999400000 | 600000 (= $0.60) |
| Exceeded? | 1999400000 >= 2000000000 | no, allow |
| Estimate tokens | max(1, floor(8192 / 4)) | 2048 |
| Estimate cost | floor(2048 x 50000 / 1000000) | 102 |
| After debit | 1999400000 + 102 | 1999400102 |
| Priced actual | frozen price list | 45000 |
| Reconcile delta | 45000 - 102 | +44898, debit |
| After reconcile | 1999400102 + 44898 | 1999445000 |
The counter lands on 1999400000 + 45000, the measured figure, whichever direction the reconcile runs. Note row two: the request that crosses the cap is allowed, and the refusal falls on the next one.
Note the sign. Because the admission estimate is priced at the floor rate it is almost always below the measured cost, so the reconcile usually debits. That is the safe direction: the counter catches up to reality rather than running ahead of it. Between admission and reconcile a workspace's counter under-states by at most one reconcile interval of in-flight requests, which is why a cap trips slightly after the cap rather than slightly before.
What it does not include
- Spend that did not route through the gateway: provider consoles, other SDKs, other tools.
- Provider invoices. The meter measures; the provider invoice is the bill. Discounts, negotiated rates, minimums, credits and taxes are not modeled.
- Recovea subscription fees. A cap governs model spend only.
- Refusals. A refused request has no cost field and never counts toward the cap.
- Rate-limit 429s from the token bucket. Those are throughput, not money.
Limits and current status
- Only workspace-scoped caps are enforced. The gateway snapshot filters on
scope_kind = 'workspace'. Key- and project-scoped budgets can be created, stored, alerted on and displayed, but nothing refuses on them. A project cap could not match even if pushed: admission passes no project, because receipts carry no project dimension yet. - A cap is a stop, not a ceiling. The counter is read before this request's estimate lands, so expect to finish roughly one request over cap.
- Alerts are not an ordering guarantee. The sweep runs about once a minute. A fast burst can cross 80 percent and reach the cap between checks. Alerts warn on the way up; nothing promises one arrives before a refusal.
- Some refusals carry no exceeded cap. A stale or missing checkpoint returns
402 budget_unverifiable, and a node holding no cap snapshot fails closed for any workspace the entitlement snapshot marks armed. A retry clears both once a fresh checkpoint lands. - Known layer drift, from our own audit. The entitlement armed-cap hint takes the minimum over armed budgets of every scope, so a workspace whose only armed budget is key-scoped can be fail-closed for a cap the gateway would never enforce. Two spend numbers also exist: in path the gateway reads local estimate counters max-merged with the published priced total, while
spentMicroUsdcomes straight from the rollup. They agree at rest and differ by up to one publish interval plus any unreconciled estimate under load. Workspace month-to-date sumsrollup_daily.spend_micro_usdwhile key month-to-date sumsreceipts.cost_micro_usd; project month-to-date returns nothing rather than a zero. - Counterfactual savings are measured, never applied. Levers default to off, the state machine forbids off to live directly, and none is activated in any deployed environment.
- Cost per successful output is served at list price. Invoice reconciliation at period close is planned, not wired.
- The Recovea Score engine is not shipped. No workspace has a score today.
- The pause 429 carries
type: "rate_limit_error", still an open decision. Key oncode, which is stable.
Where to check us
- Export the month from
GET /platform/v1/ledger/export. The header names the reciperecovea-chain-v1, the 64-zero genesis, the chain head, the row count, and the frozen price list versions the rows pin. The key-authedGET /v1/ledger/exportserves the same rows in snake_case; the field map for both is on the ledger page. - Sum
costMicroUsdover the period's rows, divide by 1,000,000. That is the aggregatespentMicroUsdderives from. On the key-authed route that integer iscostin JSONL and JSON, and thecost_micro_usdcolumn in CSV. - Re-derive a row's cost from its token counts and the price list named by its
priceListVersion, orprice_list_versionon the key-authed route. Rows carrybasis: "measured". - Re-derive the chain offline with SHA-256 and nothing else.
recovea-chain-v1gives both the per-row hash input and the linking; it is the only recipe production writes. - Check headroom by hand:
capMicroUsd - spentMicroUsd, integer subtraction, no rounding step.
If our arithmetic disagrees with yours, that is a bug and we want the request id.