# Rate limits

Each key may make **60** requests a minute unless agreed otherwise; over that, the API answers `429 rate_limited` with a `Retry-After` header in seconds.

## Headers

Every response carries:

| Header | Meaning |
| --- | --- |
| `X-RateLimit-Limit` | Requests allowed per minute for this key |
| `X-RateLimit-Remaining` | Requests left in the current minute |
| `X-RateLimit-Reset` | When the minute resets, in epoch seconds |
| `Retry-After` | On a `429` only: seconds to wait |

The window is a fixed minute per key. A `429` costs no credits.

## Other limits

| What | Limit |
| --- | --- |
| [`GET /v1/public/stats`](https://installgraph.com/docs/reference/get-public-stats.md) (no key) | 30 requests a minute per IP; cacheable for an hour |
| New accounts ([installgraph.com/login](https://installgraph.com/login)) | 3 per IP per day |
| Stores unlocked | 40,000 per account per 30 days ([Credits](https://installgraph.com/docs/credits.md#limits)) |

Need a higher rate? [Talk to us](https://installgraph.com/talk-to-us).

## Retrying

Retry `429` after `Retry-After` seconds, and `5xx` with exponential backoff. Do not retry other `4xx`: they fail the same way again.

```js
async function call(url, init = {}, attempt = 0) {
  const res = await fetch(url, {
    ...init,
    headers: { Authorization: `Bearer ${process.env.INSTALLGRAPH_API_KEY}`, ...init.headers },
  });
  if ((res.status === 429 || res.status >= 500) && attempt < 5) {
    const wait = Number(res.headers.get("Retry-After")) || 2 ** attempt;
    await new Promise((r) => setTimeout(r, wait * 1000));
    return call(url, init, attempt + 1);
  }
  const body = await res.json();
  if (!res.ok) throw Object.assign(new Error(body.error.message), body.error);
  return body;
}
```

Billed requests are safe to retry: an error charges nothing, and a store you already paid for is free again for 7 days.
