Skip to main content

Wallets, credits, and charges: how usage metering works

Orbit meters usage against a prepaid wallet: one balance per organization, one append-only ledger, and one pause gate on outbound sending. Use this page to model that correctly in your integration — which unit to do arithmetic in, why a wallet debit is sub-cent precise, and what a 402 pause response means for your retry logic. The surface map of every billing endpoint is the Billing overview; the request/response schemas are the Billing API reference. This page is the concept both of those assume.

The prepaid ledger — one balance, append-only history

Every organization holds a single wallet. The wallet has a balance and a ledger:
  • The balance is the spendable remainder, read live from GET /api/v1/billing/balance.
  • The ledger is the append-only history of every movement that produced that balance, read from GET /api/v1/billing/transactions with cursor pagination. Rows are never updated or deleted — a reversal is a new row of the opposite sign, so you can replay the ledger to reconcile the balance against your own records.
Money moves as integer math, never floats. The wallet stores whole cents ($1.50 = 150), while the charge ledger tracks sub-cent precision in micro-cents (1 cent = 1,000,000 micro-cents; 1 USD = 100,000,000 micro-cents). That headroom exists because real usage rates are sub-cent — a per-email or per-LLM-token rate doesn’t divide cleanly into whole cents. Charges accumulate in integer micro-cents so a million tiny debits sum exactly; a 2-decimal float would drift.

Credit paths vs. debits at usage time

A ledger row is a credit (positive amount_minor) or a debit (negative), tagged with a type and a human-meaningful reference:
  • Top-up credit — a card checkout or crypto payment settles:
  • Other credits — refunds and trial/admin grants land (type = refund, admin_grant, …) as positive rows the same way.
  • Usage debit — every send, call, or AI request debits the wallet as it happens, with the reference naming what was charged:
Debits are per-message, per-voice-minute, or per-AI-token depending on the channel — SMS bills per segment, so a long message debits more than one segment’s worth. Debits are also idempotent: a retried charge collapses to one ledger row instead of double-billing.

Pause semantics — when outbound sending stops

Outbound sending (messages, calls) is gated by the org’s pause state. Read it on GET /api/v1/billing/balance:
  • outbound_paused: true — sends are currently refused.
  • outbound_block_reason — the machine-readable cause when set (wallet_empty at a $0 balance, billing_alert:<id>:<name> when a billing alert’s pause action fired, dunning_failed_3x after repeated payment failures).
  • payment_failure_count — consecutive card payment failures (0–3). At 1 or 2, dunning is in progress and the dashboard warns the owner to update the card; at 3 the account is frozen until it’s fixed.
While paused, any outbound send API call is refused with HTTP 402:
Treat 402 as terminal for that send — don’t retry it in a hot loop. Poll the balance endpoint (or wait for the top-up to settle) until outbound_paused flips to false, then resume. The dashboard alert tells the owner the same thing.

Units — dollars, cents, and micro-cents

GET /api/v1/billing/balance returns the same balance in three representations, plus the ledger’s ISO 4217 currency:
Conversion: micro_cents = cents × 1,000,000 = usd × 100,000,000. If you project your own remaining runway, subtract your per-unit rates from balance_micro_cents in integer math — a float projection will disagree with the ledger on small amounts.

Degraded balance — the last-known fallback

The balance read has a cache-first fast path. During a short availability blip the endpoint can return the last-known value instead of an error, flagged on the response:
When degraded: true appears, treat the number as stale by at most the in-flight charges of the last few seconds — useful for display, not for gating your own sends. The dashboard shows a “Balance recently degraded” notice rather than letting the widget flash a phantom zero. The send-time pause gate is evaluated server-side at send time regardless of this flag, so a degraded read can never let a genuinely-exhausted wallet send.

Auto-top-up feeds the same ledger

Auto-top-up keeps the wallet funded without a human watching it: when the balance crosses your configured threshold, Orbit charges the saved payment method off-session and credits the wallet.
  • An evaluator runs on a 15-minute schedule and compares the balance to your threshold_minor.
  • A 5-minute cooldown separates consecutive auto-charges.
  • No more than one auto-charge per hour fires even after the cooldown clears, and a per-day cap (default 5) bounds runaway exposure.
A settled auto-charge is indistinguishable from a manual top-up in the ledger — it lands as a credit_purchase row against the same balance. Configure it via GET/PUT/DELETE /api/v1/billing/auto-topup or Dashboard → Billing → auto-replenish; the full field semantics are on the Billing overview.

Next steps