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

# Rate limits

> The VMArea API enforces two stacked rate-limit buckets. Learn the thresholds, response headers, and how to handle 429s gracefully.

The VMArea Public API uses two stacked buckets, both backed by Redis so the limits are shared across every API instance.

## Current limits

| Bucket                   | Limit                                        | Keyed by     | Applies to                                                                      |
| ------------------------ | -------------------------------------------- | ------------ | ------------------------------------------------------------------------------- |
| Public API (per token)   | **600 requests / minute** (10 RPS sustained) | API token ID | Every `/api/public/v1/*` request after `x-api-key` auth succeeds                |
| Global fallback (per IP) | **100 requests / minute**                    | IP address   | All traffic — primarily catches abuse of unauthenticated routes (login, signup) |

The per-token bucket means heavy customers and clients behind shared NAT IPs aren't penalised for each other's traffic. Pair this with the `Idempotency-Key` header so safe retries don't burn extra requests — see [Idempotency](/en/idempotency).

## 429 response

When you exceed either limit, the API returns HTTP `429 Too Many Requests`:

```json theme={null}
{
  "success": false,
  "error": "Rate limit exceeded for this API token: 600 requests per minute. Retry after 42."
}
```

The `error` string identifies which bucket you tripped (per-token vs per-IP global fallback).

## Response headers

Every response includes the standard rate-limit headers; `Retry-After` is added on `429` responses.

| Header                  | Description                                      |
| ----------------------- | ------------------------------------------------ |
| `X-RateLimit-Limit`     | Maximum requests allowed in the current window.  |
| `X-RateLimit-Remaining` | Requests remaining in the current window.        |
| `X-RateLimit-Reset`     | Seconds until the window resets.                 |
| `Retry-After`           | Seconds to wait before retrying (only on `429`). |

## Handling 429 in your client

Read `Retry-After` for the minimum wait, then back off exponentially with jitter starting at 1 second:

```js theme={null}
async function requestWithRetry(url, options, maxAttempts = 5) {
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    const res = await fetch(url, options);
    if (res.status !== 429) return res;

    const retryAfter = parseInt(res.headers.get("Retry-After") ?? "1", 10);
    const base = Math.max(retryAfter, 1) * 1000;
    const jitter = Math.random() * 1000;
    const delay = base * Math.pow(2, attempt - 1) + jitter;
    await new Promise((r) => setTimeout(r, delay));
  }
  throw new Error("Rate limit: max retry attempts exceeded");
}
```

## Best practices

* **Use `Idempotency-Key` for writes.** Safe retries won't double-spend your quota or create duplicate resources.
* **Batch reads where possible.** Paginated list endpoints are cheaper than fan-out GETs.
* **Cache catalog data.** Plans, regions, and OS templates change infrequently — cache them locally rather than re-fetching.
* **Use webhooks for state changes.** Subscribing to `vm.started`, `vm.stopped`, etc. avoids polling `GET /vms/:id` in a tight loop.
* **Need higher limits?** Contact support — sustained 10 RPS covers virtually every integration, but we can raise per-token ceilings on request.
