> ## 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.

# The outbound billing gate chain: what every send walks, in order

> One ordered chain every outbound send walks before funds move — the pre-flight balance check, the pause/block flags gate, the per-channel fund path, and the atomic check-and-decrement — with the one voice hold and the integration rules for 402 refusals.

# The outbound billing gate chain

When a paid send comes back with HTTP 402, the cause lives in one of four
stages — and today those stages are described on three separate pages. The
[wallet, rating, and ledger](/concepts/billing-and-wallet) page covers the
balance pre-flight and the authorize-before-use model, the
[pause gate and free-channel exemption](/concepts/outbound-pause-and-free-channel-exemption)
page covers the flags gate, and
[wallets, credits, and charges](/concepts/wallets-credits-and-charges)
covers the atomic check-and-decrement. To reconcile a 402 you assemble the
order from all three.

This page narrates the whole chain once. The stages run in the same order
for every channel, and the order is the point:

1. **Pre-flight balance check** — at `$0` the reject is `INSUFFICIENT_BALANCE` (402).
2. **Pause/block flags gate** — `SENDING_PAUSED` (soft) or `SENDING_BLOCKED` (hard), 402, with the free-channel exemption.
3. **Per-channel fund path** — the voice hold of up to 2 minutes at dial; per-segment SMS debit; per-token AI debit.
4. **Atomic check-and-decrement** — the wallet never overdraws, and idempotent ledger rows collapse retries.

A send that clears the first gate proceeds only to the next — the refusal
code names how far the chain got.

## Stage 1 — pre-flight balance check

Every paid channel reads the wallet before it sends. An exhausted wallet
rejects with `INSUFFICIENT_BALANCE` (402). An account-level hold still wins
by construction — a blocked tenant is refused even with money in the
wallet, because the next gate checks those flags too.

## Stage 2 — pause/block flags gate

Right after the money check, the send hits two flags on your organization:
`outbound_paused` (soft) and `outbound_blocked` (hard). Balance-driven
causes — `wallet_empty` from the balance monitor, or
`billing_alert:<id>:<name>` from a threshold you set — flip the soft flag;
account-lifecycle causes (suspension, dunning, dispute, admin hold, any
unknown `null` cause) flip the hard flag. Both reject with 402.

The balance-driven class has one deliberate exception: the \$0-on-platform
free channels (WhatsApp, Instagram, Messenger, Telegram, LINE, Apple
Business Messages, push, web widget) are bypassed, because the send costs
the platform nothing and you pay the upstream provider directly. The
account-lifecycle class exempts no channel, and the gate never guesses an
unknown cause into "probably just the balance." The reason taxonomy is
spelled out on
[the pause gate and free-channel exemption](/concepts/outbound-pause-and-free-channel-exemption).

## Stage 3 — per-channel fund path

A send that clears both gates pays in its own unit:

* **Voice — one authorized hold.** At dial, Orbit debits two minutes' worth
  up front (minimum 2¢). An empty wallet means no hold, and no hold means
  the call never dials. At call end, Orbit debits only the delta above the
  hold (minimum 1¢ per call); an unanswered call refunds the hold in full.
  The FX rate locked at hold time is reused at settlement, so the two
  computations never disagree on currency. This is the **only**
  hold/reservation in the billing model — every other channel pre-flights
  without escrowing funds.
* **SMS and messaging — per segment.** The debit posts when the message
  is sent, one segment's worth per billable segment, so a long message
  debits more than one.
* **AI agents — per token.** Each model call debits its token usage as it
  runs.

## Stage 4 — atomic check-and-decrement

The wallet check and the decrement happen inside one server-side
operation, so concurrent debits can't race each other into an overdraft.

Every movement carries an idempotency key naming the real-world event it
pays for (message id, call id, checkout session id). The key caches the
result for 24 hours, drives the ledger row's deterministic identifier,
and collapses retries to one row — concurrent attempts with the same key
either collapse or get a 409 `DEDUCT_IN_FLIGHT` to back off with.
Insufficient-balance outcomes are never cached, so top-up plus a same-key
retry works cleanly.

## Worked examples — one SMS, one call, one AI conversation

Three sends walk the chain, each labelled at the gate that consumed it:

```text theme={null}
                ┌──────────────────────────────────────────────────┐
                │                 your send request                │
                └──────────────────────────────────────────────────┘
                                   │
        ┌──────────────────────────▼──────────────────────────┐
        │ 1. pre-flight balance check — 402 INSUFFICIENT_     │
        │    BALANCE on an exhausted wallet                    │
        └──────────────────────────┬──────────────────────────┘
                                   │
        ┌──────────────────────────▼──────────────────────────┐
        │ 2. pause/block flags gate — 402 SENDING_PAUSED or   │
        │    SENDING_BLOCKED (free channels bypass a soft     │
        │    pause only)                                      │
        └──────────────────────────┬──────────────────────────┘
                                   │
        ┌──────────────────────────▼──────────────────────────┐
        │ 3. per-channel fund path                            │
        │    SMS: per-segment debit · AI: per-token debit ·   │
        │    voice: up-to-2-min hold, settle delta at end     │
        └──────────────────────────┬──────────────────────────┘
                                   │
        ┌──────────────────────────▼──────────────────────────┐
        │ 4. atomic check-and-decrement — idempotent ledger   │
        │    row, no overdraft race                           │
        └─────────────────────────────────────────────────────┘
```

* **SMS send** — clears the balance pre-flight, clears the flags gate,
  debits one ledger row per billable segment at send time (`type: debit`,
  `reference: charge:sms_mccmnc:msg_…`). A retry replays the same key and
  moves no money twice.
* **Voice call** — clears the same two gates, then escrows up to two
  minutes before the provider leg is dialed. At hangup the settle computes
  the true charge and debits only the delta; an unanswered call releases
  the whole hold. The FX rate locked at hold time is the rate the settle
  reuses.
* **AI-agent conversation** — clears the same two gates, then each model
  call debits its tokens as it runs, per token. A refusal at the wallet
  mid-conversation surfaces at stage 1 of the next model call, not as a
  post-hoc debt.

## Boundary — what is NOT in this chain

The chain decides **whether** funds move, and in what order the refusals
fire. It deliberately excludes:

* **Rate resolution** — how a destination's per-unit price is attributed
  (pricing tables, tier lookup). Lives on
  [pricing and rate resolution](/concepts/pricing-rate-resolution).
* **Usage-metering rollups and committed-use meters** — rollup engines and
  enterprise monthly-commit drawdown. Lives on
  [committed-use drawdown](/concepts/committed-use-drawdown-model).
* **What-if previews** — stateless rate simulations over your usage lanes;
  nothing persists, nothing gates. Lives on
  [what-if pricing preview](/concepts/whatif-pricing-preview-model).

## Integration rules

* **Treat 402 as terminal.** `INSUFFICIENT_BALANCE`, `SENDING_PAUSED`, and
  `SENDING_BLOCKED` are all 402 — do not hot-retry; resolve the cause.
* **Poll `GET /api/v1/billing/balance`.** Read `outbound_paused` /
  `outbound_blocked` and `outbound_block_reason` until the flag flips
  false, then resume.
* **A degraded balance read never gates yours.** The endpoint can return a
  last-known value flagged `degraded: true`; send-time checks run
  server-side regardless, so an exhausted wallet can never send on a stale
  display value.
* **Voice FX is locked at hold, reused at settle.** The same rate
  computation runs twice with no currency drift.

## Related pages

* [Wallets, credits, and charges](/concepts/wallets-credits-and-charges) —
  the ledger mechanics behind stage 4 (units, idempotency, auto-top-up).
* [The outbound pause gate and free-channel exemption](/concepts/outbound-pause-and-free-channel-exemption) —
  the stage-2 flags taxonomy.
* [How billing meters your usage](/concepts/billing-and-wallet) — the
  rating pipeline and the authorize-before-use summary.
