Limits are enforced per API key using a token-bucket algorithm. When a bucket empties the API returns 429 Too Many Requests.
Default limits
| Environment | Requests / minute | Burst | Write ops / minute |
|---|---|---|---|
Sandbox (qxk_test_…) | 300 | 60 | 120 |
Live (qxk_live_…) | 1,200 | 200 | 600 |
| Enterprise | Custom | Custom | Custom |
Contact support to lift live limits for production launches.
Headers
Every response — including 2xx — carries the current bucket state:
| Header | Description |
|---|---|
X-RateLimit-Limit | Bucket capacity (per minute). |
X-RateLimit-Remaining | Tokens left in the current window. |
X-RateLimit-Reset | Unix seconds until the bucket refills. |
Retry-After | Seconds to wait. Present on 429 only. |
429 response
HTTP/1.1 429 Too Many Requests
Retry-After: 3
X-RateLimit-Limit: 1200
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1737049200
Content-Type: application/json
{
"error": {
"code": "rate_limited",
"type": "rate_limit_error",
"message": "Rate limit exceeded. Retry in 3 seconds.",
"retryable": true,
"request_id": "req_01HR2X8YQ4T5B0G6ZP"
}
}
Handling 429s
- Read
Retry-Afterand wait exactly that long — do not retry sooner. - Use the same
Idempotency-Keyso retries don't double-charge. - Add jitter to smooth out fleet-wide spikes.
- Watch
X-RateLimit-Remainingproactively and shed load client-side.
async function callWithBackoff(req) {
for (let i = 0; i < 5; i++) {
const res = await fetch(req);
if (res.status !== 429) return res;
const wait = Number(res.headers.get("Retry-After") ?? 1) * 1000;
await new Promise(r => setTimeout(r, wait + Math.random() * 250));
}
throw new Error("rate_limit_exceeded");
}
Aggressive retries without honoring Retry-After may result in the key being throttled to a lower tier.