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

# Rate Limits

> The published per-endpoint and account-wide limits, and how to back off when you hit one.

## Limit behavior

Every request is subject to an **account-wide default of 120 requests per 60-second window**. A handful of endpoints carry a **tighter, endpoint-specific** limit on top of that default, because they're either expensive, carrier-facing, or an abuse surface:

| Endpoint                                      | Limit    |
| --------------------------------------------- | -------- |
| `POST /api/v1.1/sims/{iccid}/renew-number`    | 20 / 60s |
| `POST /api/v1.1/subscriptions/{iccid}/cancel` | 30 / 60s |
| `POST /api/v1.1/plans/purchase`               | 60 / 60s |
| `POST /api/v3/plans/purchase`                 | 60 / 60s |
| `POST /api/v1.1/esim`                         | 60 / 60s |
| `POST /api/v1.1/sms`                          | 30 / 60s |
| `POST /api/v1.1/top-up`                       | 20 / 60s |
| `POST /api/v1.1/port-in/eligibility`          | 20 / 60s |
| `POST /api/v1.1/team-members`                 | 20 / 60s |
| `POST /api/v1.1/webhooks/{id}/test`           | 10 / 60s |

Every other endpoint is subject only to the 120/60s account-wide default. When you exceed a limit, the request fails with:

```json theme={null}
{
  "success": false,
  "error": { "code": "RATE_LIMITED", "message": "ThrottlerException: Too Many Requests" }
}
```

```http theme={null}
HTTP/1.1 429 Too Many Requests
```

<Note>
  These are the limits **coded today** — they can change, and a limit tied to your specific account plan may differ. Read the rate-limit headers at runtime rather than hardcoding these numbers into alerting thresholds.
</Note>

## Relevant headers

**A `429` response itself only carries one header: `Retry-After`** (seconds until you can retry). The `X-RateLimit-*` headers below are only sent on requests that succeeded (i.e. weren't throttled) — read them to see how close you are to the limit *before* you hit it, not as part of handling the 429 itself:

| Header                  | Meaning                                   | Present on                           |
| ----------------------- | ----------------------------------------- | ------------------------------------ |
| `Retry-After`           | Seconds until you can retry.              | The `429` response only.             |
| `X-RateLimit-Limit`     | Requests allowed in the current window.   | Successful (non-429) responses only. |
| `X-RateLimit-Remaining` | Requests remaining in the current window. | Successful (non-429) responses only. |
| `X-RateLimit-Reset`     | Seconds until the window resets.          | Successful (non-429) responses only. |

## Handling throttling

* Treat `429 RATE_LIMITED` as a **signal to slow down**, not a bug to route around by hammering the endpoint faster.
* Apply backoff **per endpoint** — a limit on `renew-number` doesn't mean your other calls are also throttled, so don't globally pause your whole integration in response to one endpoint's `429`.
* Never retry a `429` immediately in a tight loop; it will not succeed sooner and behaves poorly for every other caller sharing your account's limit.

## Recommended backoff strategy

`Retry-After` (on the `429` itself) tells you when to retry — prefer waiting at least that long. As a general-purpose fallback (or when you want to smooth out repeated throttling rather than waiting for one window), use **exponential backoff with jitter**:

```js theme={null}
async function withRetry(fn, { maxAttempts = 5, baseDelayMs = 1000 } = {}) {
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      return await fn();
    } catch (err) {
      const retryable = err.status === 429 || err.status >= 500;
      if (!retryable || attempt === maxAttempts) throw err;
      const delay = baseDelayMs * 2 ** (attempt - 1) * (0.5 + Math.random());
      await new Promise((r) => setTimeout(r, delay));
    }
  }
}
```

Bound `maxAttempts` — an unbounded retry loop against a sustained `429` just extends an outage into your own system.

## Next steps

* **[Errors](/errors)** for the full status-code and retry matrix.
* **[Security & Best Practices](/security)** for how rate-limit handling fits into a production-ready client.
