Docs/Use casesv0.0.1-preview
Usage metering and billing
Per-event billing — API calls, kilowatt-hours, compute minutes — where the invoice is a balance read and every event is counted exactly once.
Usage-based billing has a counting problem: millions of small events, delivered at-least-once, that must each be billed exactly once — and a total that must be right, because it becomes an invoice someone disputes. That’s a ledger workload wearing a metering costume: each event is a transfer, the meter’s balance is the invoice, and idempotency does the deduplication you’d otherwise build into a pipeline.
The model
One meter account per customer (the debit side — what they owe accrues here) and a provider revenue account (the credit side):
M = c.create_ledger(LedgerInstance(ledger_id=0, currency=840)).ledger_id
PROVIDER, CUST_METER, BILLING = 13001, 14001, 1 # integer ids
for a in (PROVIDER, CUST_METER):
c.create_account(AccountCreate(id=a, ledger_id=M, currency=840,
limit_low=-10**15, limit_high=10**15))
Events in batches, exactly once
Meter events arrive in bulk, so submit them in bulk — a batch frame carries up to 1,024 transfers, each with its own identity:
batch = [TransferCreate(source_id=BILLING, ledger_id=M, currency=840,
debit_account_id=CUST_METER, credit_account_id=PROVIDER,
amount=3, # price of one event, in minor units
txn_id=f"api-call-{event_id}")
for event_id in event_page]
results = c.transfer_batch(batch, pipeline_depth=8)
A full 1,024-event frame posts 1,024 items, each with its own status, and the provider balance lands at exactly 1024 × 3. Because each event’s txn_id is its identity, redelivering the whole batch is safe — landed events answer OK_DUPLICATE, missed ones apply. Your ingestion pipeline gets exactly-once billing semantics from at-least-once delivery with no dedup store of its own.
The invoice is a query
b = c.get_balance(AccountKey(ledger_id=M, currency=840, id=CUST_METER))
owed = b.debits_posted - b.credits_posted # what this customer owes, now
No aggregation job, no eventual consistency window at invoice time — the balance is the sum of every counted event, and linearizable reads mean the number you invoice is the number that’s true. Payment against the invoice is a credit back to the meter account; the full event history behind any line item is an account-history query, and finance can work from the Iceberg export without touching the write path.
Prepaid and capped plans
Flip the flow for prepaid: fund the meter account up front and floor it at zero — metering then debits against the prepayment, and the engine’s ERR_LIMIT decline is your quota enforcement, atomic with the event (spend controls is this same pattern). A monthly spending cap on postpaid is the meter account’s floor: usage debits the meter, and debits are bounded by limit_low, so create it with limit_low = -cap (not limit_high, which only bounds credits) and the first event past the cap declines with ERR_LIMIT.
Sizing honestly
Per-event granularity is only viable if the engine keeps up. The published campaign figures carry their hardware and workload shape; note that metering is the friendly shape — events spread across many customer meters, not one hot account — and the batch path amortises consensus across 1,024 events per frame. As always: measure your own rate on your own hardware. If you bill in points, kWh, or compute-seconds rather than currency, register the unit — see Rewards and closed-loop economies.
Related
Spend controls · Idempotency · Analytics export · Limits and sizing