B2C Payments
Spending and the Ledger
How a running job pays for itself through holds, how billing picks which money to spend, and the append-only, double-entry ledger underneath.

Everything that costs money while it runs, such as an LLM call, a GPU job or a long pipeline, uses the same few calls, however the customer pays.
Holds
- Authorize reserves an estimate against a meter and returns a hold with an expiry, or a clear "no" when nothing can fund it. The context records who is spending (a person, an agent or a service), the job and free-form tags for reporting.
- Report records usage against the hold, rated with the rate card captured when the hold was made. Each record carries its own event id, so a retried call is never counted twice.
- Extend grows the hold when a job needs more than it estimated. A refusal is the mid-run cap: the job stops taking new paid steps, finishes or checkpoints what is in flight, and pauses. Running work is never killed.
- Settle captures the rated usage and releases the rest. A hold that expires without being settled is settled by the worker with the usage reported so far, so a crashed job leaks nothing.
Cheap, synchronous work uses a single charge call that does all four at once.
Billing doesn't invent estimates. The app provides one, the way Convo shows an upper-bound estimate before a run, and the client library turns an app's own token or seconds profile into an estimate with the current rate card, so no app hard-codes prices.
Which money gets spent first
On authorize and extend, billing draws from funding sources in an order set by a setting. By default that's the plan's included allowance for the period, then promotional credits, then purchased credits, then the postpaid credit line up to its cap, and finally an automatic top-up from a saved payment method if the customer turned it on. The hold remembers exactly which sources it reserved from, so the capture or release posts back to the same ones.
When nothing can fund the work, the app gets a plain refusal and turns it into a pause and an upgrade prompt.
Spending limits for agents
Our apps already treat AI agents and services as principals alongside people. A customer can give an agent its own spending authority: a cap per period, per hold and per meter, with an expiry. Authorize checks the agent's authority as well as the customer's funds, and an agent that hits its limit tells its human rather than retrying.
The ledger
Every money movement is a transaction in one append-only, double-entry ledger. Each transaction's entries sum to zero for every unit, enforced both by the ledger package and by a database constraint. Corrections are new reversing transactions, never edits or deletes, and balances are derived from entries. A cached balance is updated in the same database transaction as its entries, and a nightly sweep recomputes every balance and raises an alarm on any difference.
Credits and currency are kept apart. Buying a credit pack posts the payment as deferred revenue in the currency and issues a lot of credits in the credit unit. As a job consumes credits, the matching share of the payment moves from deferred to earned revenue at the lot's cost basis. Subscription revenue is posted per paid invoice and recognized on payment or spread over the service period, whichever the settings say. Refunds and lost disputes reverse unearned revenue first, and an open dispute freezes what the payment bought.
Apps can report their own cost for each piece of usage, such as the price of the tokens an LLM call used. Billing stores it with the usage and can post it to cost of goods sold, so margin per product, per meter and per customer is a query rather than a spreadsheet.
The ledger package is property-tested: random sequences of authorize, extend, report, settle, refund and expiry must keep every invariant. The invariants are also checked where money is read, not only where it is written.




