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

# Troubleshoot CARRIER_RATE_LIMITED on bulk number purchase

> Recover a partially-completed bulk number purchase when the batch returns failed rows with code CARRIER_RATE_LIMITED — the carrier-throttle breaker skipped the remaining items without a carrier call, so retry only the failed rows after a short wait, never the whole batch.

# Troubleshoot CARRIER\_RATE\_LIMITED on bulk number purchase

A bulk purchase (`POST /api/v1/numbers/buy-bulk`, up to 50 rows in one call)
processes row by row. When one row hits a carrier-side rate limit (HTTP 429),
Orbit stops pulling new rows from the batch — the breaker trips — and every
remaining row is stamped with `error.code = "CARRIER_RATE_LIMITED"` in the
`failed[]` array **without another carrier call**. A 50-row batch can come
back `200 OK` with, for example, 12 rows in `succeeded[]` and 38 rows in
`failed[]`, all marked `CARRIER_RATE_LIMITED`.

That shape means: nothing is stuck, nothing was charged for the skipped rows,
and the fix is to retry **only** the failed rows a few seconds later.

<Note>
  Only the per-row error `RATE_LIMITED` (the row whose carrier call actually
  returned 429) and the breaker-skip marker `CARRIER_RATE_LIMITED` trigger the
  stop. Deterministic per-row failures (validation, compliance gates, inventory
  races) do not trip the breaker — see
  [Troubleshoot number purchase failures](/troubleshooting/numbers-provisioning-failed)
  for those.
</Note>

## Cause table

| Cause | How it reads | Recover by |
| - | - | - |
| Parallel bulk writes from your own tenant | Two `buy-bulk` calls (or a bulk call plus retries of an earlier one) run at once and jointly exceed the carrier's per-key ceiling | Run one bulk call at a time per tenant; stagger follow-up batches |
| Carrier-side bulk throttle under shared load | The upstream carrier rate-limits purchase traffic globally, so even a single 50-row batch can trip the breaker mid-flight | Wait a few seconds and retry the failed slice; if the same pattern persists after a handful of retries, escalate (below) |

## Fix table

| Step | Do this |
| - | - |
| 1. Read the gate right | Check `succeeded.length` — the response is `200 OK` even when rows fail, so never branch on the status code |
| 2. Retry the failed slice only | Collect the `phone_number` (plus `country_code` and `id` if you carry them) from each `failed[]` row and re-submit exactly those in a new `buy-bulk` call |
| 3. Back off briefly | Wait a few seconds before the retry so the carrier window clears; keep retries serial, never parallel |
| 4. Never re-run the whole batch | `succeeded[]` rows already own their numbers — re-running them wastes a rate-limit window and risks inventory-gone failures |

## Example: failure shape and recovery

The bulk response splits successful rows from skipped ones; `debited_cents`
covers only what succeeded:

```json theme={null}
{
  "data": {
    "order_id": "numOrder_01J9Z8ABCDEF",
    "succeeded": [
      { "id": "num_abc123", "phone_number": "+14155550100", "status": "active" }
    ],
    "failed": [
      {
        "phone_number": "+14155550101",
        "error": {
          "code": "CARRIER_RATE_LIMITED",
          "message": "The carrier rate-limited the batch. Some numbers were skipped — retry the failed rows in a few seconds."
        }
      },
      {
        "phone_number": "+14155550102",
        "error": {
          "code": "CARRIER_RATE_LIMITED",
          "message": "The carrier rate-limited the batch. Some numbers were skipped — retry the failed rows in a few seconds."
        }
      }
    ],
    "total_cost_cents": 450,
    "debited_cents": 150
  }
}
```

Recovery call — take each `failed[].phone_number`, wait a few seconds, and
submit exactly that slice (never the original batch):

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/numbers/buy-bulk \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "items": [
      { "phone_number": "+14155550101", "country_code": "US" },
      { "phone_number": "+14155550102", "country_code": "US" }
    ]
  }'
```

The batch returns a pollable `order_id`; failed rows are never charged, so
re-submitting them is billing-safe.

## When to retry vs escalate

Retry the failed slice after a few seconds. Escalate when the **same pattern
recurs after a handful of spaced retries** — that points to a sustained
carrier-side throttle, which only the operator can clear. Open a ticket with:

* The **exact `meta.request_id`** of the failing bulk call — a wildcard or
  partial id is rejected when support queries logs.
* The batch's **`order_id`** (pollable via the bulk-order history endpoints).
* The carrier name from `error.details.carrier`, if the per-row error carries
  one, and the country code of the failed numbers.

## See also

* [Phone numbers overview](/numbers/overview) — search filters, purchase
  fields, number types, and routing catalog
* [Buy and provision numbers](/guides/buy-numbers) — the full search → buy
  flow, including the bulk `failed[]` contract
* [Bulk order history](/guides/numbers-bulk-orders-history) — the
  delivered/partial/failed ledger every `order_id` lands in
* [Troubleshoot number purchase failures](/troubleshooting/numbers-provisioning-failed) —
  the 409/502 sibling codes on single and bulk checkout
* [Error codes reference](/reference/error-codes) — the `CARRIER_RATE_LIMITED`
  catalogue entry (429)
