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

# Rate limits

> The limits on both APIs, the headers you get back, and how to back off.

## Marketplace API

Per IP, per minute, with separate counters for the two kinds of endpoint:

|                                                              | Limit         |
| ------------------------------------------------------------ | ------------- |
| Lists — `/v1/collections`, `…/listings`, `…/sales`           | **120 / min** |
| Single items — `…/collections/{id}`, `…/floor`, `…/tokens/…` | **300 / min** |

The counters are genuinely separate, so hammering the list endpoints won't eat
your single-item allowance.

## Store API

Per key, per minute. Set on the key, so different integrations can have
different budgets.

## Headers

Both APIs return:

| Header                  |                             |
| ----------------------- | --------------------------- |
| `X-RateLimit-Limit`     | Your cap for this window    |
| `X-RateLimit-Remaining` | What's left                 |
| `X-RateLimit-Reset`     | When it resets              |
| `Retry-After`           | Seconds to wait, on a `429` |

## When you're limited

Both return `429`.

<CodeGroup>
  ```json Marketplace theme={"system"}
  {
    "error": {
      "code": "rate_limited",
      "message": "Too many requests. Please slow down."
    }
  }
  ```

  ```json Store theme={"system"}
  {
    "error": "Rate limit exceeded"
  }
  ```
</CodeGroup>

## Backing off

```js theme={"system"}
async function call(url, options = {}, attempt = 0) {
  const res = await fetch(url, options);

  if (res.status !== 429) return res;
  if (attempt >= 5) throw new Error('Rate limited, giving up');

  const retryAfter = Number(res.headers.get('Retry-After'));
  const waitMs = Number.isFinite(retryAfter) && retryAfter > 0
    ? retryAfter * 1000
    : Math.min(2 ** attempt * 1000, 30_000);

  await new Promise(r => setTimeout(r, waitMs));
  return call(url, options, attempt + 1);
}
```

Retrying a `429` immediately doesn't get you served sooner, and keeps the
counter pinned. Honour `Retry-After`.

## Staying under

<AccordionGroup>
  <Accordion title="Let the CDN do the work">
    Marketplace responses are CDN-cached with `stale-while-revalidate`. Repeated
    identical requests come from cache and don't touch the origin limit — so
    don't add cache-busting query parameters.
  </Accordion>

  <Accordion title="Use webhooks instead of polling">
    Polling an orders endpoint every few seconds is the most common way to burn
    through a key's budget. A webhook removes the poll entirely.
    → [Webhooks](/api/webhooks)
  </Accordion>

  <Accordion title="Use a sensible page size">
    `limit` maxes at 100 on both. Ten pages of 100 is ten requests; a hundred
    pages of ten is a hundred.
  </Accordion>

  <Accordion title="Raise the key's limit">
    Store API limits are per key and adjustable. If a legitimate integration is
    hitting the cap, raise it — don't add sleeps.
  </Accordion>
</AccordionGroup>
