Operate
Budget exhaustion
Screening spend is bounded on purpose, running out produces a hold rather than a shortcut, and the dangerous remedy is the automatic one.
Layer B — third-party attribution vendors — is billed per call. The budget exists so that a traffic spike, a retry storm, or a misconfigured loop cannot produce an unbounded bill. It is a cost control, and like every other control in this system, its degraded path is HOLD.
What you see
On the decision endpoint: HTTP 200, verdict: HOLD, reason code BUDGET_EXHAUSTED.
Not a 402, not a 429, not an error. A budget that has run out is a fact about your account, and the decision it produces is a real decision with a real decision_id and a real evidence reference.
Watching the spend
billable_units is returned on both POST /v1/screen/address and POST /v1/decide/deposit.
- Layer A is never billed. A layer-A-only screening response is
0. - An idempotent replay is not billed again. It carries
Idempotency-Replayed: true. - Requesting
depth: Bdoes not guarantee a vendor call is made — your routing rules, cost gate, and cache all sit in front of it. When no vendor call happened,layer_bisnull.
Alert on the burn rate, not on the exhaustion. By the time you see BUDGET_EXHAUSTED, holds are already accumulating.
Recovering
Two ways out, and they are not equivalent.
Wait for the period to roll. Correct, and free. Deposits held on BUDGET_EXHAUSTED carry RE_DECIDE_AFTER_RECOVERY in required_actions — re-decide with a new idempotency key once the budget is available.
Raise the budget. Also correct, when a human decides that the new level is the right one. Make it a decision with a name attached, not an automatic response to a threshold.
The automatic top-up is the dangerous one
An automatic budget increase triggered by BUDGET_EXHAUSTED removes the only backstop against a runaway loop. The condition that exhausted the budget is usually not “business grew 40% overnight”; it is a retry loop with a fresh idempotency key per attempt, a shadow-mode deployment left running at full rate, or a backfill someone started and forgot.
Before raising anything, check three things:
- Is
billable_unitsper deposit what you expected, or is one deposit producing several decisions? - Are your idempotency keys stable per deposit, or fresh per attempt?
- Is anything screening at
depth: Bthat only needs layer A?
Most budget incidents are one of those three, and none of them is solved by more budget.