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

# Troubleshooting: sends rejected with INSUFFICIENT_BALANCE (402)

> Diagnose a 402 INSUFFICIENT_BALANCE rejection on messages, calls, and agent invocations: how the wallet pre-flight hold works, every cause from an empty wallet to an unreachable balance service, and the top-up and auto-top-up fixes that unblock sending.

# Troubleshooting: sends rejected with INSUFFICIENT\_BALANCE (402)

Orbit is prepaid pay-as-you-go: every outbound message, call minute, and
agent invocation debits your wallet, and the wallet is checked before the
send leaves the queue. When the check cannot clear, the API rejects the
request with a `402` and code `INSUFFICIENT_BALANCE` — the send never
reaches the provider. This page explains how that pre-flight works, what
each cause looks like, and how to get the wallet funded (and stay funded)
before you re-send.

Linear sends (a message POST, a call originate) fail fast in the request.
Queued sends (campaigns, scheduled messages) surface the same rejection
on the per-message row as a terminal `failed` status with code
`INSUFFICIENT_BALANCE`.

## How the wallet pre-flight works

Before a send is dispatched, Orbit estimates its worst-case cost and
places a hold on the wallet for that amount:

1. **Estimate.** The per-channel rate for the destination (messaging) or
   the configured per-minute rate (voice) is resolved, and the worst-case
   cost is computed. For WhatsApp Calling this is `max-minutes × per-minute
   rate`, and the sending surface caps that estimate explicitly.
2. **Hold.** The estimated amount is held against the available balance.
   If the available balance cannot cover the hold, the request is refused
   with `INSUFFICIENT_BALANCE` and nothing is dispatched.
3. **Settle.** When the send completes (or fails on the wire), the hold is
   released and the actual cost is debited; the unused estimate remainder
   returns to the available balance.

Because the check compares the estimate against the **available** balance
(current balance minus outstanding holds), a burst of concurrent sends can
push you into a 402 even when the headline balance looks sufficient.

### Where to see balance and holds

The dashboard's **Billing** section shows the current balance, recent
ledger entries, and outstanding holds. The Billing API returns the same
values — see the [Billing overview](/billing/overview) for the read
endpoints and ledger semantics. If a small set of writes is holding a
large share of the wallet, the ledger view is where you find them.

## Causes, mapped to fixes

Work the table top to bottom — the first row that matches your setup is
the fix.

| Cause                                   | How to recognize it                                                                                | Fix                                                                                                |
| --------------------------------------- | -------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| Wallet is empty or below the estimate   | Balance reads at or near zero in Billing                                                           | Top up (steps below), then re-send                                                                 |
| Auto-top-up is disabled                 | Wallet hit zero with no recharge event in the ledger                                               | Enable auto-top-up (below)                                                                         |
| Auto-top-up payment failed              | A recharge attempt exists but the card was declined or checkout was abandoned                      | Update the payment method in Billing, then re-enable or top up manually                            |
| 503 `BALANCE_SERVICE_UNAVAILABLE`       | The rejection carries code `BALANCE_SERVICE_UNAVAILABLE`, not `INSUFFICIENT_BALANCE`               | Platform-side read blip — retry with backoff; do not top up (balance may be fine)                  |
| Worst-case estimate exceeds balance     | Sends to expensive destinations or long `max-minutes` voice calls reject while small sends succeed | Lower the estimate surface (e.g. shorter max call minutes) or fund enough to cover the worst case  |
| Deprecated code in a legacy integration | Your client matches on `INSUFFICIENT_CREDITS`                                                      | Treat `INSUFFICIENT_BALANCE` (and the deprecated `INSUFFICIENT_CREDITS`) as the same 402 condition |

<Note>
  `INSUFFICIENT_CREDITS` is deprecated; new integrations should match on
  `INSUFFICIENT_BALANCE`. Both appear in the
  [error codes reference](/reference/error-codes), which also lists the
  fail-closed `BALANCE_SERVICE_UNAVAILABLE` (503) and the WhatsApp-calling
  pre-flight variant that can return either 402 or 503.
</Note>

## Top up and re-send

1. Open **Billing** in the dashboard and confirm the balance reads below
   the estimate the send requires.
2. Choose **Top up**, pick an amount that covers your expected volume
   (top-ups are one-off Stripe Checkout payments; credit never expires),
   and complete checkout.
3. Wait for the confirmation — the ledger entry posts as soon as the
   payment clears.
4. Re-send the rejected requests. For queued sends that failed with
   `INSUFFICIENT_BALANCE`, re-issue them from your application; the
   platform does not auto-replay terminal failures.

<Warning>
  Do not blind-retry a 402. Without new funds each retry re-runs the same
  pre-flight and returns the same rejection — and if the retry loop fans
  out, every attempt still burns a pre-flight estimate. Gate retries on a
  fresh balance read, or better, enable auto-top-up so the wallet refills
  without your loop noticing.
</Warning>

## Configure auto-top-up (tenant-controlled)

Auto-top-up refills the wallet automatically so sends stop failing at the
pre-flight. It is entirely tenant-configured — you set the trigger, the
recharge amount, the payment method, and the ceilings, and no support
ticket is required to change any of it.

1. Open Dashboard → **Billing → auto-replenish**.
2. Set the **threshold**: when the balance drops below this amount, a
   recharge fires.
3. Set the **recharge amount** charged to your saved payment method.
4. Optionally set spend ceilings (a daily or monthly cap on top-ups) so a
   runaway integration cannot drain the card — details in
   [Spend caps, budget alerts, and auto-cutoff](/billing/spend-caps).

When auto-top-up is on and the payment method is healthy, a low balance
triggers a recharge before the next send hits the pre-flight. If the
recharge itself fails (declined card), the rejection pattern returns —
update the payment method and the trigger fires again on the next low
reading.

## When to escalate

Open a support ticket when the rejection persists after a funded top-up,
or when every rejection is a `BALANCE_SERVICE_UNAVAILABLE` 503 that outlasts
a few minutes of retries. Include:

* Your **tenant ID** (dashboard → Settings → Organization; also returned
  by `GET /api/v1/me` as `organizationId`).
* The **exact error code** (`INSUFFICIENT_BALANCE` or
  `BALANCE_SERVICE_UNAVAILABLE`) and the **request ID** of one rejected
  call.
* For a payment failure: the **checkout/payment reference** shown on the
  Billing page's ledger entry.

That set lets support pull the wallet ledger, the pre-flight decision,
and the payment attempt without a back-and-forth.

## See also

* [Billing overview](/billing/overview) — wallet model, ledger endpoints,
  top-ups, invoices, and refunds
* [Spend caps, budget alerts, and auto-cutoff](/billing/spend-caps) —
  ceilings and enforcement actions for top-up and spend
* [Error codes reference](/reference/error-codes) — `INSUFFICIENT_BALANCE`,
  deprecated `INSUFFICIENT_CREDITS`, and `BALANCE_SERVICE_UNAVAILABLE`
* [FAQ: Billing](/reference/faq#billing) — the pay-as-you-go model and
  free-tier answer
