Billing
API
Full parameters and responses: balance, credits, usage, invoices, payment methods, plans.
GET /api/v1/billing/balance
GET /api/v1/billing/ledger
GET /api/v1/billing/ledger/reconcile
GET /api/v1/billing/usage
GET /api/v1/billing/usage/summary
POST /api/v1/billing/credits/purchase
GET/PUT /api/v1/billing/auto-reload
GET/POST /api/v1/billing/payment-methods
POST /api/v1/billing/payment-methods/setup-intent
POST /api/v1/billing/payment-methods/{id}/default
DELETE /api/v1/billing/payment-methods/{id}
GET /api/v1/billing/invoices
GET /api/v1/billing/invoices/{id}
GET /api/v1/billing/invoices/{id}/download
GET /api/v1/billing/plansTwo models
| Pay as you go | Enterprise | |
|---|---|---|
| Funding | Prepaid credit wallet | Contract with committed spend |
| Enforcement | Calls stop when credit runs out | Invoiced monthly |
| Pricing | Public catalogue | Negotiated per-customer prices |
The credit ledger
credit_ledger is insert-only. A balance is the sum of its rows, never a mutable column, so a balance can always be explained by the entries that produced it and ledger/reconcile can prove the two agree. Amounts are decimal(20, 8) with the ISO currency stored alongside.
Metering
Usage is metered per component, not per call: transcription seconds, model input and output tokens, synthesis characters, telephony minutes, storage, and the platform fee. Each becomes a usage_events row carrying the provider's own units and the price applied, so an invoice line can be traced to the second of the call that caused it.
A component is priced only from a registry row with a verified price. Where no verified price exists the row is still written — the quantity is not lost — but with cost = 0 and priced: false, and the breakdown reports partial coverage rather than presenting an under-count as a total. See Analytics & cost.
Provider cost vs price
Provider cost is what CallAgent paid; price is what you pay. With BYO keys the provider bills you directly and CallAgent charges only its platform fee — both figures are still recorded, because "why is my bill this number" needs both.
Auto reload
When the balance falls under a threshold, a configured amount is charged to the default payment method. Guarded against repeated failed charges so a declining card does not generate a hundred attempts.
Concurrency and admission
Before any call, admission checks concurrency headroom, credit balance, organization status and destination policy in one call, and returns a slot token released on finalize — without it, a crashed call leaks a concurrency slot forever.
Stripe has never been called with a real key
Purchases, setup intents and payment methods all need Stripe keys, and none has ever been supplied. STRIPE_SECRET is empty in the shipped example environment and the gateway throws a named error — "Payments are disabled until the owner supplies Stripe keys" — rather than degrading quietly.
Every Stripe path is covered by tests that drive the real Stripe SDK, so request signing and serialisation are exercised. Every HTTP response in those tests is a canned fixture. No live or test-mode Stripe endpoint has ever been contacted.
Webhook signature verification, idempotent event handling and ledger-authoritative crediting are all real code that has never met Stripe. See What is not built.