Skip to main content

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

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

Default Limits

WindowLimit
Per minute300 requests
Per day10,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.
HeaderDescription
RateLimit-PolicyEvery active window, e.g. 300;w=60, 10000;w=86400
RateLimit-LimitQuota for the most-restrictive currently-active window
RateLimit-RemainingRequests left in that window
RateLimit-ResetSeconds until that window resets (relative; clock-skew safe)
Retry-AfterSent only on 429 responses — seconds to wait before retrying
These follow the IETF draft-ietf-httpapi-ratelimit-headers draft.

Legacy aliases

For back-compat with older SDKs, we also emit:
HeaderDescription
X-RateLimit-LimitSame value as RateLimit-Limit
X-RateLimit-RemainingSame value as RateLimit-Remaining
X-RateLimit-ResetAbsolute 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:
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:
{
  "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:
{
  "data": [...],
  "pagination": {
    "page": 1,
    "page_size": 25,
    "total_count": 342,
    "total_pages": 14,
    "has_next": true,
    "has_previous": false
  }
}

Query Parameters

ParameterDefaultMaxDescription
page1Page number (1-indexed)
page_size25100Items per page

Iterating Through Pages

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