> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Wallets, credits, and charges: how usage metering works

> The metering ledger model behind Orbit's prepaid wallet — append-only credits and debits in integer micro-cents, the pause flags that gate outbound sends, the three-unit balance representation, the degraded-balance fallback, and how auto-top-up feeds the same ledger.

# 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](/billing/overview); the request/response schemas are the
[Billing API reference](/api-reference/billing). 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:
  ```json theme={null}
  {
    "type": "credit_purchase",
    "amount_minor": 10000,
    "reference": "checkout:cs_live_a1b2c3",
    "created_at": "2026-08-01T14:22:10.000Z"
  }
  ```
* **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:
  ```json theme={null}
  {
    "type": "debit",
    "amount_minor": -2,
    "reference": "charge:sms_mccmnc:msg_4f8c1d",
    "created_at": "2026-08-01T14:35:41.000Z"
  }
  ```

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:

```json theme={null}
{
  "error": {
    "code": "SENDING_PAUSED",
    "message": "Outbound sending is paused for this organization. Top up the wallet or reset the billing alert to resume.",
    "details": {
      "reason": "wallet_empty"
    }
  }
}
```

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`:

```json theme={null}
{
  "data": {
    "balance_usd": 150.00,
    "balance_cents": 15000,
    "balance_micro_cents": 15000000000,
    "currency": "USD"
  }
}
```

| Field                 | Type    | Use for                                                               |
| --------------------- | ------- | --------------------------------------------------------------------- |
| `balance_usd`         | float   | Display only. Never do arithmetic on it.                              |
| `balance_cents`       | integer | Whole-cent comparisons ("is the balance above my \$50 alert floor?"). |
| `balance_micro_cents` | integer | Exact math against sub-cent rates (per-email, per-token pricing).     |

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:

```json theme={null}
{ "data": { "balance_cents": 15000, "degraded": true, "source": "db-fallback" } }
```

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](/billing/overview#3-auto-top-up).

## Next steps

* [Billing overview](/billing/overview) — endpoint surface map (top-ups,
  alerts, invoices, statements, refunds).
* [Billing API reference](/api-reference/billing) — request/response schemas
  for every endpoint named here.
* [How billing meters your usage](/concepts/billing-and-wallet) — the rating
  pipeline behind each debit (included quantity, graduated tiers, FX on
  converted top-ups).
