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

# Rate Limits & Pagination

> Understand API rate limits, response headers, and pagination conventions.

## Rate Limits

All API endpoints are rate-limited per API key. Default limits apply unless overridden at the key level.

### Default Limits

| Window     | Limit           |
| ---------- | --------------- |
| Per minute | 300 requests    |
| Per day    | 10,000 requests |

**Per-key overrides**: API keys can have custom `rate_limit_per_minute` and `rate_limit_per_day` values set at creation time. A value of `0` means "use the global default."

**Publishable keys** (`cpk_` prefix) default to 100 requests/minute and can be configured independently.

### Rate-Limit Headers

Every response includes enough state for callers to **self-throttle before they hit a 429** — an "informed governor" pattern.

| Header                | Description                                                      |
| --------------------- | ---------------------------------------------------------------- |
| `RateLimit-Policy`    | Every active window, e.g. `300;w=60, 10000;w=86400`              |
| `RateLimit-Limit`     | Quota for the **most-restrictive currently-active** window       |
| `RateLimit-Remaining` | Requests left in that window                                     |
| `RateLimit-Reset`     | **Seconds until** that window resets (relative; clock-skew safe) |
| `Retry-After`         | Sent only on 429 responses — seconds to wait before retrying     |

These follow the IETF [`draft-ietf-httpapi-ratelimit-headers`](https://datatracker.ietf.org/doc/draft-ietf-httpapi-ratelimit-headers/) draft.

#### Legacy aliases

For back-compat with older SDKs, we also emit:

| Header                  | Description                                                                                       |
| ----------------------- | ------------------------------------------------------------------------------------------------- |
| `X-RateLimit-Limit`     | Same value as `RateLimit-Limit`                                                                   |
| `X-RateLimit-Remaining` | Same value as `RateLimit-Remaining`                                                               |
| `X-RateLimit-Reset`     | **Absolute Unix timestamp** when the window resets (note: different shape from `RateLimit-Reset`) |

New integrations should prefer the unprefixed `RateLimit-*` headers.

#### Why most-restrictive wins

If you have 250 requests left in your minute quota but only 8 left in your day quota, the headers report the **day** state — that's the one that will trip first. `RateLimit-Policy` declares all windows so you can plan ahead for the long-tail (daily) one even when minute is the active constraint.

### The "informed governor" pattern

Read the headers and pause when you're getting close to the limit, instead of waiting for a 429:

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

GOVERNOR_THRESHOLD = 0.05  # pause when < 5% remaining

def call_with_governor(url, headers):
    response = requests.get(url, headers=headers)

    limit = int(response.headers.get("RateLimit-Limit", 0))
    remaining = int(response.headers.get("RateLimit-Remaining", limit))
    reset = int(response.headers.get("RateLimit-Reset", 0))

    if limit and remaining / limit < GOVERNOR_THRESHOLD:
        # Sleep until the window resets — never hit a 429.
        time.sleep(reset + 1)

    return response
```

### Handling 429 Responses

If you do hit a 429 — usually because your governor wasn't running, or the quota was already exhausted by another caller sharing the key — the response has both a `Retry-After` header and a `retry_after_seconds` field in the error envelope:

```json theme={null}
{
  "error": {
    "code": "RATE_LIMITED",
    "message": "Rate limit exceeded.",
    "status": 429,
    "details": {
      "retry_after_seconds": 12
    }
  }
}
```

For production, implement exponential backoff with jitter on top of `Retry-After`.

## Pagination

All list endpoints return paginated results in a standard envelope:

```json theme={null}
{
  "data": [...],
  "pagination": {
    "page": 1,
    "page_size": 25,
    "total_count": 342,
    "total_pages": 14,
    "has_next": true,
    "has_previous": false
  }
}
```

### Query Parameters

| Parameter   | Default | Max | Description             |
| ----------- | ------- | --- | ----------------------- |
| `page`      | 1       | —   | Page number (1-indexed) |
| `page_size` | 25      | 100 | Items per page          |

### Iterating Through Pages

```python theme={null}
import requests

page = 1
all_items = []

while True:
    response = requests.get(
        "https://www.closient.com/products/api/v1/products",
        params={"page": page, "page_size": 100},
        headers={"X-API-Key": "YOUR_API_KEY"},
    )
    data = response.json()
    all_items.extend(data["data"])

    if not data["pagination"]["has_next"]:
        break
    page += 1
```
