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
| 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.
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 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.
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.
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
| Parameter | Default | Max | Description |
|---|
page | 1 | — | Page number (1-indexed) |
page_size | 25 | 100 | Items 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