# Rate limits (/docs/rate-limits)

60 requests per minute and 10 concurrent requests per API key, what a 429 looks like, and how to size a job.



Each API key may send **60 requests per minute** and have **10 requests in flight** at once. Going over either limit returns a `429`, and a rejected request costs nothing. Beyond those two ceilings, what bounds your volume is your credit balance.

## The limits at a glance [#the-limits-at-a-glance]

| Limit        | Applies to  | Value                                | When exceeded                                                       |
| ------------ | ----------- | ------------------------------------ | ------------------------------------------------------------------- |
| Request rate | Per API key | **60 requests / sliding 60 seconds** | `429 RATE_LIMITED` with `Retry-After`                               |
| Concurrency  | Per API key | **10 requests in flight**            | `429 CONCURRENCY_LIMIT` with `Retry-After: 1`                       |
| Credits      | Per account | Your balance                         | `402 INSUFFICIENT_CREDITS`                                          |
| API keys     | Per account | **25 keys**                          | Key creation is refused. See [Authentication](/docs/authentication) |

The dashboard [Playground](https://www.insightsocial.app/portal/api/explorer) has its own allowance with the same numbers, so trying endpoints there does not eat into your keys' limits.

## Request rate: 60 per minute per key [#request-rate-60-per-minute-per-key]

Every data request on a key counts toward a sliding 60-second window. That includes requests that end in a `402`, `404` or `405`. A `max_pages` walk is one request and takes one slot. None of these count: `GET /v1/credits`, `GET /v1/endpoints`, the bare `GET /v1`, and any request rejected for a missing or bad key, since authentication runs first.

```json title="Response"
{
  "success": false,
  "error": {
    "type": "RATE_LIMITED",
    "message": "Too many requests on this key. Honour Retry-After, then back off with jitter."
  },
  "request_id": "req_1a2b3c4d5e6f",
  "credits_used": 0,
  "credits_remaining": null
}
```

`Retry-After` is the number of seconds until the oldest request in the window drops out, at least `1`.

## Concurrency: 10 in flight per key [#concurrency-10-in-flight-per-key]

Separately, a key may have at most 10 open requests. This is not a time window: a slot frees the moment one of your requests returns, so an 11th request sent while 10 are open is rejected.

```json title="Response"
{
  "success": false,
  "error": {
    "type": "CONCURRENCY_LIMIT",
    "message": "Too many calls in flight on this key. Retry shortly."
  },
  "request_id": "req_1a2b3c4d5e6f",
  "credits_used": 0,
  "credits_remaining": null
}
```

`Retry-After` is `1` on this one.

<Callout type="info" title="Only Retry-After">
  The API sends no headers describing your remaining headroom. The only timing header is `Retry-After`. It comes with every `429`, and also with `409 IDEMPOTENCY_IN_PROGRESS` and some `503` responses. Pace yourself from your own counts, as described below.
</Callout>

## Credits are the volume limit [#credits-are-the-volume-limit]

There is no daily or monthly request quota. What limits sustained work is your balance: the free plan includes 500 credits a month, Pro 10,000, and packs add more. Prices differ per endpoint and some are metered, so price every endpoint your job uses on [Endpoint pricing](/docs/endpoint-pricing) before you size it. See [Credits](/docs/credits) for how charging works.

## Handling a 429 [#handling-a-429]

<Steps>
  <Step>
    ### Read `error.type` [#read-errortype]

    `RATE_LIMITED` means you are sending too fast: slow the request rate. `CONCURRENCY_LIMIT` means too many requests are open at once: shrink the worker pool. They need different fixes.
  </Step>

  <Step>
    ### Wait for `Retry-After` [#wait-for-retry-after]

    Wait at least that many seconds before retrying.
  </Step>

  <Step>
    ### Back off with jitter [#back-off-with-jitter]

    If the retry also gets a `429`, double the wait and add random jitter so your workers do not all retry in the same instant.
  </Step>

  <Step>
    ### Cap the attempts [#cap-the-attempts]

    After a handful of attempts, give up and surface the error.
  </Step>
</Steps>

```ts title="Node.js"
async function callWithBackoff(url: string, init: RequestInit, maxRetries = 5): Promise<Response> {
  for (let attempt = 0; ; attempt++) {
    const res = await fetch(url, init);

    // Only 429s are handled here. A 402 or another 4xx will not
    // change on retry, so return it straight away.
    if (res.status !== 429 || attempt >= maxRetries) return res;

    const retryAfter = Number(res.headers.get("Retry-After")) || 1;
    const backoff = retryAfter * 2 ** attempt;
    const jittered = backoff * (0.5 + Math.random() * 0.5);
    await new Promise((r) => setTimeout(r, jittered * 1000));
  }
}
```

The [Errors](/docs/errors) page has complete retry loops in cURL, Python and Node.js that also cover `503` and `500`.

## Sizing a job [#sizing-a-job]

* Run at most **10 workers per key**.
* Keep the total under **60 requests a minute**, which is one request a second on average.
* Budget credits per page, not per job, and remember that metered calls hold the top of their price range while they run.
* Walk paginated endpoints until `pagination.has_more` is `false`. See [Pagination](/docs/pagination).
* Send an `Idempotency-Key` on each call so retries never pay twice.

## Next steps [#next-steps]

<Cards>
  <Card title="Errors" href="/docs/errors">
    Which failures to retry, with backoff loops.
  </Card>

  <Card title="Credits" href="/docs/credits">
    What each call costs and what is never charged.
  </Card>

  <Card title="Authentication" href="/docs/authentication">
    Key management.
  </Card>

  <Card title="Pagination" href="/docs/pagination">
    Cursor loops that stay inside the limits.
  </Card>
</Cards>

