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

# Rate Limits

> Request limits per organization, the headers that report them and how to handle 429 responses

The API limits how many requests each organization can make per minute, so a runaway integration cannot degrade the platform for everyone else. Limits are counted per **organization** (the owner of the API key), not per key: rotating your API key does not reset your budget.

## Limits

| Scope                            | Limit                     | Applies to                                    |
| -------------------------------- | ------------------------- | --------------------------------------------- |
| Organization                     | **120 requests / minute** | Every authenticated endpoint                  |
| Organization — listing endpoints | **20 requests / minute**  | `GET /v1/contacts`, `GET /v1/calls`           |
| IP address                       | **600 requests / minute** | Every request, including unauthenticated ones |

Limits use a fixed 60-second window. Once a limit is exceeded, requests in that scope are rejected until the window resets — the `Retry-After` header tells you how long to wait. The listing budget is counted **in addition to** the organization budget: a call to `GET /v1/contacts` consumes one request from both.

<Note>
  Listing endpoints also cap `limit` at **1000** records per page. Send `limit` and `offset` together and iterate using `pagination.total_rows` from the response.
</Note>

## Response headers

Every response includes the state of your organization budget:

| Header                  | Meaning                             |
| ----------------------- | ----------------------------------- |
| `X-RateLimit-Limit`     | Requests allowed per window         |
| `X-RateLimit-Remaining` | Requests left in the current window |
| `X-RateLimit-Reset`     | Seconds until the window resets     |

On listing endpoints the same headers are also reported for the listing budget with the `-heavy` suffix (`X-RateLimit-Limit-heavy`, `X-RateLimit-Remaining-heavy`, `X-RateLimit-Reset-heavy`), and the IP budget uses the `-ip` suffix.

## When a limit is exceeded

The API responds with `429 Too Many Requests` and a `Retry-After` header (in seconds):

```http theme={null}
HTTP/1.1 429 Too Many Requests
Retry-After: 37
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 37
Content-Type: application/json

{
  "statusCode": 429,
  "message": "Rate limit exceeded: 120 requests per 60s. Retry in 37s."
}
```

Wait for `Retry-After` seconds before retrying. Retrying immediately does not help: the request is rejected again and the window does not reset any sooner.

## Best practices

* **Honor `Retry-After`.** Treat `429` as a signal to pause, not as an error to retry in a tight loop.
* **Paginate with `limit=1000`.** A full export of 100,000 contacts is 100 requests — about five minutes at the listing rate — and only needs to run once.
* **Poll on a schedule** (for example every few minutes) instead of continuously, and only ask for what changed when the endpoint supports it.
* **Batch on your side.** If several systems need the same data, fetch it once and share it rather than having each of them call the API.

If your integration needs higher limits, contact [support@contactship.ai](mailto:support@contactship.ai) with your organization name and expected volume.

## Handling 429 in code

<CodeGroup>
  ```javascript JavaScript theme={null}
  const API_KEY = 'your-api-key';

  async function apiGet(path, attempt = 0) {
    const response = await fetch(`https://api.contactship.ai${path}`, {
      headers: { 'x-api-key': API_KEY },
    });

    if (response.status === 429 && attempt < 5) {
      const retryAfter = Number(response.headers.get('retry-after') ?? '1');
      await new Promise((resolve) => setTimeout(resolve, retryAfter * 1000));
      return apiGet(path, attempt + 1);
    }

    if (!response.ok) {
      throw new Error(`HTTP ${response.status}`);
    }

    return response.json();
  }

  // Export every contact, one page at a time
  async function exportContacts() {
    const pageSize = 1000;
    const contacts = [];

    for (let offset = 0; ; offset += pageSize) {
      const page = await apiGet(`/v1/contacts?limit=${pageSize}&offset=${offset}`);
      contacts.push(...page.data);
      if (contacts.length >= page.pagination.total_rows) break;
    }

    return contacts;
  }
  ```

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

  API_KEY = "your-api-key"
  BASE_URL = "https://api.contactship.ai"


  def api_get(path, attempt=0):
      response = requests.get(f"{BASE_URL}{path}", headers={"x-api-key": API_KEY})

      if response.status_code == 429 and attempt < 5:
          retry_after = int(response.headers.get("Retry-After", "1"))
          time.sleep(retry_after)
          return api_get(path, attempt + 1)

      response.raise_for_status()
      return response.json()


  def export_contacts():
      page_size = 1000
      contacts = []
      offset = 0

      while True:
          page = api_get(f"/v1/contacts?limit={page_size}&offset={offset}")
          contacts.extend(page["data"])
          if len(contacts) >= page["pagination"]["total_rows"]:
              return contacts
          offset += page_size
  ```
</CodeGroup>
