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

# Rate limits

> The per-key request budget, the headers that report it, and how to back off

## The budget

Requests to the Developer API are counted per API key in a fixed one minute window, with one budget:

| Applies to | Default |
| - | - |
| Every request (all are `GET`) | 120 requests per minute |

One budget, because this version only reads. When write endpoints arrive they will get their own, much lower, ceiling: a read loop is cheap and integrations legitimately do a lot of it, while a write loop creates rows.

The budget belongs to the **key**, not to your user or your organization. Two keys in one organization each get their own, which is what lets you isolate a batch job from your live integration by giving it its own key.

<Note>
  The unversioned payment-acceptance endpoints (`/one-time-payment`, `/payment-intent`, `/payment-method`) are not metered by this mechanism. The limit above applies to `/api/developer/v1` only.
</Note>

## Reading your remaining budget

Every Developer API response carries the current state, including successful ones, so you can slow down before you are cut off rather than after.

| Header | Meaning |
| - | - |
| `X-RateLimit-Limit` | Requests allowed in the window for this budget |
| `X-RateLimit-Remaining` | Requests left in the current window |
| `X-RateLimit-Reset` | Seconds until the window rolls over and the full budget returns |

## When you exceed it

The API answers `429` with a `Retry-After` header, in seconds:

```json theme={null}
{
  "message": "Rate limit exceeded. Retry in 43 seconds.",
  "code": "RATE_LIMIT_EXCEEDED"
}
```

<Warning>
  Requests that receive a `429` still count against the window. A client that keeps hammering after being throttled keeps its own window alive rather than getting a fresh budget the moment the old one lapses. Honour `Retry-After`.
</Warning>

## Backing off correctly

Wait for `Retry-After`, then add a little randomness so that several workers throttled at the same moment do not all return at the same instant.

<CodeGroup>
  ```javascript Node.js theme={null}
  async function compagoRequest(url, options, attempt = 0) {
    const res = await fetch(url, options);

    if (res.status !== 429 || attempt >= 5) return res;

    const retryAfter = Number(res.headers.get('retry-after') ?? 60);
    // Jitter, so a fleet of workers throttled together does not return together.
    const waitMs = retryAfter * 1000 + Math.random() * 1000;

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

  ```python Python theme={null}
  import random, time, requests

  def compago_request(method, url, api_key, attempts=5, **kwargs):
      headers = {"x-api-key": api_key, **kwargs.pop("headers", {})}

      for attempt in range(attempts):
          res = requests.request(method, url, headers=headers, timeout=30, **kwargs)
          if res.status_code != 429:
              return res

          retry_after = int(res.headers.get("Retry-After", 60))
          # Jitter, so a fleet of workers throttled together does not return together.
          time.sleep(retry_after + random.random())

      return res
  ```
</CodeGroup>

## Staying well under the limit

<AccordionGroup>
  <Accordion title="Filter server side, do not page and discard">
    Fetching every payment to keep the ten you wanted spends your whole budget on rows you throw away. `GET /developer/v1/payment` accepts `status`, date bounds and the id of the payment link, subscription or saved card the payment came from. See [Pagination](/core-concepts/pagination).
  </Accordion>

  <Accordion title="Raise limit instead of making more requests">
    One request for 100 rows costs one unit of budget. Ten requests for 10 rows cost ten. `limit=100` is the maximum.
  </Accordion>

  <Accordion title="Poll on a schedule, not in a loop">
    Nothing in a payments system changes fast enough to need second-by-second polling. Ask for what changed since your last run with `createdAtFrom`, on a cadence measured in minutes.
  </Accordion>

  <Accordion title="Use a separate key for batch work">
    Because the budget is per key, a nightly export running on its own key cannot throttle the key another integration depends on. Create keys in the dashboard under **Configuraciones**, then **Desarrollador**.
  </Accordion>
</AccordionGroup>

## Requesting a higher limit

If a legitimate workload does not fit the defaults, contact us with your key name, the endpoints involved and the request rate you need. The limits are configured per environment and can be raised.
