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

# Cal ID API Rate Limits

> Read the rate-limit headers Cal ID returns on every API response, recognise the 429 error, and back off cleanly so your integration keeps running.

Cal ID limits how many API calls you can make over a given window. You do not have to guess where you stand: every response from `https://api.cal.id` carries headers describing your current allowance, so a well-behaved integration can throttle itself before it is throttled.

## Rate-limit headers

These headers are returned on all responses, including error responses.

| Header                  | Description                                                                     |
| ----------------------- | ------------------------------------------------------------------------------- |
| `X-RateLimit-Limit`     | The maximum number of requests you are permitted to make in the current window. |
| `X-RateLimit-Remaining` | The number of requests left in the current window.                              |
| `X-RateLimit-Reset`     | The time the current window resets, in UTC epoch seconds.                       |

<Tip>
  Log `X-RateLimit-Remaining` in your client. Watching it trend toward zero is the cheapest early warning that a job is calling the API too aggressively.
</Tip>

## When you exceed the limit

Once you run out of allowance, Cal ID responds with HTTP **429 Too Many Requests** and a JSON body:

```json theme={null}
{
  "error": "Rate limit exceeded",
  "message": "Too many requests. Please try again later.",
  "retryAfter": 30
}
```

`retryAfter` is the number of seconds to wait before your next call is allowed.

<Warning>
  Do not retry a `429` immediately or in a tight loop. Hammering the endpoint keeps you throttled and delays your own recovery.
</Warning>

## Handle limits gracefully

<Steps>
  <Step title="Check the status code">
    Treat `429` as its own case, separate from other errors.
  </Step>

  <Step title="Wait for retryAfter">
    Sleep for the number of seconds in `retryAfter` — or until `X-RateLimit-Reset` — before retrying.
  </Step>

  <Step title="Back off exponentially">
    If you are still throttled after the wait, double the delay on each subsequent attempt and cap the number of retries.
  </Step>

  <Step title="Reduce the call volume">
    Batch work, cache responses that rarely change (such as event types), and raise the `limit` parameter on list endpoints instead of paging one record at a time.
  </Step>
</Steps>

The first three steps together make a small wrapper you can put every call through. Both versions log `X-RateLimit-Remaining`, sleep for `retryAfter`, double the delay on each attempt, and give up after five tries.

<CodeGroup>
  ```javascript JavaScript theme={null}
  async function calIdRequest(path, options = {}, attempt = 1) {
    const res = await fetch(`https://api.cal.id${path}`, {
      ...options,
      headers: {
        Authorization: `Bearer ${process.env.CALID_KEY}`,
        ...options.headers,
      },
    });

    console.log("Remaining:", res.headers.get("X-RateLimit-Remaining"));

    if (res.status === 429) {
      if (attempt >= 5) throw new Error("Still rate limited after 5 attempts");
      const throttled = await res.json();
      // Wait retryAfter seconds, doubling the delay on each attempt.
      const waitSeconds = (throttled.retryAfter ?? 30) * 2 ** (attempt - 1);
      await new Promise((resolve) => setTimeout(resolve, waitSeconds * 1000));
      return calIdRequest(path, options, attempt + 1);
    }

    const body = await res.json();
    // A 500 is often NOT transient — read the message before retrying it.
    if (!res.ok) throw new Error(body.message ?? `HTTP ${res.status}`);
    return body.data;
  }

  calIdRequest("/users/me").then((user) => console.log(user.username));
  ```

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

  import requests

  CALID_KEY = os.environ["CALID_KEY"]


  def cal_id_request(path, max_attempts=5):
      for attempt in range(max_attempts):
          response = requests.get(
              f"https://api.cal.id{path}",
              headers={"Authorization": f"Bearer {CALID_KEY}"},
          )
          print("Remaining:", response.headers.get("X-RateLimit-Remaining"))

          if response.status_code == 429:
              # Wait retryAfter seconds, doubling the delay on each attempt.
              retry_after = response.json().get("retryAfter", 30)
              time.sleep(retry_after * 2**attempt)
              continue

          # A 500 is often NOT transient — read the message before retrying it.
          response.raise_for_status()
          return response.json()["data"]

      raise RuntimeError("Still rate limited after 5 attempts")


  print(cal_id_request("/users/me")["username"])
  ```
</CodeGroup>

<Note>
  A `429` is genuinely transient, so retrying is correct. A `500` often is not: Cal ID returns `500` for scheduling-rule failures such as `no_available_users_found_error`, which will fail identically on every retry. (Malformed requests return `400`, not `500`.) Read the response body before you retry. See [API integration](/docs/developers/api-integration) for details.
</Note>

## Related API documentation

<CardGroup cols={2}>
  <Card title="API integration" icon="plug" href="/docs/developers/api-integration">
    Base URL, authentication, response envelopes, and common pitfalls.
  </Card>

  <Card title="Get your API key" icon="key" href="/docs/developers/api-key">
    Use separate keys per integration so you can isolate a noisy one.
  </Card>

  <Card title="Webhooks" icon="webhook" href="/docs/developers/webhooks">
    Receive booking events as they happen instead of polling the API.
  </Card>

  <Card title="API Reference" icon="code" href="/docs/api-reference/introduction">
    Every endpoint, with schemas and a live "Try it" playground.
  </Card>
</CardGroup>


## Related topics

- [Cal ID API Integration](/docs/developers/api-integration.md)
- [How to Get Your Cal ID API Key](/docs/developers/api-key.md)
- [Cal ID Webhooks](/docs/developers/webhooks.md)
- [Cal ID Scheduling API Reference](/docs/api-reference/introduction.md)
