Provider spend caps stop at monthly totals for one vendor: no per-run ceiling, no cross-provider budget, and tool spend isn't counted. A task budget is the number that actually matters, what this job is allowed to cost, across every model and tool it touches, enforced before the money is spent.
A task groups many calls under one hard ceiling. Three rules:
1, The first preflight that names a task_ref creates the task
and fixes its task_ceiling.
2, Every later preflight atomically reserves against the same budget; the call that would
cross the ceiling is blocked before it runs.
3, Records report reality: a failed run releases its reservation, and spend that lands past
the ceiling is still recorded and flagged task_exceeded, never
silently dropped.
# First call creates the task: this job dies at 50 units curl -X POST https://agentbill.dev/preflight \ -H "Authorization: Bearer agb_your_key" \ -H "Content-Type: application/json" \ -d '{"agent_id":"researcher","estimated_units":2, "task_ref":"job-42","task_ceiling":50}' # ... run the LLM / tool call, then record what actually happened curl -X POST https://agentbill.dev/events \ -H "Authorization: Bearer agb_your_key" \ -H "Content-Type: application/json" \ -d '{"customer_id":"default","event_type":"llm_call", "idempotency_key":"job-42-step-1","units":2,"task_ref":"job-42"}' # The call that would cross the ceiling is refused: # {"approved":false,"reason":"task_ceiling_exceeded", # "task_used_units":48,"task_remaining_units":2}
pip install agentbill-sdk # >= 0.4.0from agentbill import AgentBillClient, TaskCeilingExceededError client = AgentBillClient(api_key="agb_your_key") # preflight before, record after, or wrap it all with the gate decorator: @client.gate("researcher", estimated_units=2, task_ref="job-42", task_ceiling=50) def run_step(query: str) -> str: return call_llm(query) # the run that would cross 50 units raises TaskCeilingExceededError # a run that throws releases its reservation automatically
npm install agentbill # >= 0.2.0import { preflight, record, getTask } from 'agentbill'
await preflight({ agentId: 'researcher', estimatedUnits: 2,
taskRef: 'job-42', taskCeiling: 50 })
// ... run the call ...
await record({ agentId: 'researcher', units: 2, taskRef: 'job-42' })
const t = await getTask('job-42') // live burn-down
console.log(t.usedUnits, '/', t.ceilingUnits)task_ref, job identifier (1-128 chars). Same ref = same budget.
task_ceiling, required on the first preflight of a new task_ref;
fixed at creation, ignored afterwards.
Approved responses include task_remaining_units. A blocked run returns
reason: "task_ceiling_exceeded"; a new task_ref without a ceiling
returns 422 task_ceiling_required.
task_ref, attributes the spend to the task. success: false releases the reservation without billing. Responses include task_used_units, task_remaining_units and task_exceeded.
Per-agent cost attribution: every job's ceiling, spend, live reservations and overage flag. Filter with ?agent_id=.