Rate limits and quotas
Handle CompSniper per-minute rate limits and monthly quota exhaustion without retry storms.
Two independent limits protect every account.
Per-minute rate limit
Standard plans allow 60 requests per minute. When exceeded, the API returns:
{
"error": "Per-minute rate limit exceeded.",
"code": "rate_limited",
"retry_after": 12
}Wait for Retry-After, add a small random jitter, and retry with a strict maximum attempt count.
Monthly quota
When every request in the plan has been used, the API returns:
{
"error": "Free plan quota exhausted. Upgrade your CompSniper plan to continue now, or wait for your monthly reset.",
"code": "quota_exceeded",
"plan": "basic",
"quota": 100,
"used": 100,
"remaining": 0,
"reset_at": "2026-09-18T05:30:36.000Z",
"upgrade_url": "https://compsniper.com/dashboard/subscription"
}Do not retry quota_exceeded. Stop the job, show upgrade_url, or wait until reset_at.
Response headers
| Header | Meaning |
|---|---|
X-RateLimit-Limit | Requests allowed in the current minute |
X-RateLimit-Remaining | Requests left in the current minute |
X-RateLimit-Reset | Unix timestamp when the minute window resets |
X-Usage-Limit | Monthly plan allowance |
X-Usage-Remaining | Monthly requests remaining |
Retry-After | Seconds before a temporary retry |
Bounded retry example
async function searchWithRetries(url, apiKey) {
for (let attempt = 0; attempt < 5; attempt += 1) {
const response = await fetch(url, {
headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await response.json();
if (response.ok) return body;
if (response.status === 429 && body.code === "quota_exceeded") {
throw new Error(`Quota exhausted. Upgrade: ${body.upgrade_url}`);
}
const temporary =
(response.status === 429 && body.code === "rate_limited") ||
[500, 502, 503].includes(response.status);
if (!temporary || attempt === 4) throw new Error(body.error);
const delay = Number(response.headers.get("Retry-After") || body.retry_after || 2 ** attempt);
await new Promise((resolve) => setTimeout(resolve, (delay + Math.random() * 0.5) * 1000));
}
}