Rate Limits
Per-plan request-per-second limits, stealth throttling, and retry guidance.
Serpex enforces per-organization rate limits based on your highest completed purchase. Limits are exact — there is no burst allowance.
Limits by tier
Prop
Type
The tier is derived from your cumulative top-up history, not the current balance.
Stealth mode cap
Stealth extract requests ("stealth": true on /api/crawl) are additionally rate-capped to a fixed global cap (default: 10 concurrent stealth requests). Requests that exceed this cap are queued, not rejected — they will resolve once a slot is available.
Stealth requests may take a few seconds longer when the queue is busy. Factor this into your timeout settings. No extra credits are charged for queue wait time.
HTTP response codes
| Code | Meaning | Action |
|---|---|---|
200 | Success | — |
401 | Invalid or missing API key | Fix the key; don't retry |
402 | Insufficient credits | Top up at app.serpex.dev |
429 | Rate limit exceeded | Back off and retry (see below) |
500 / 503 | Transient server error | Retry with exponential backoff |
The 429 response includes Retry-After and X-RateLimit-Limit headers:
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 50
X-RateLimit-Remaining: 0
Retry-After: 1Retry strategy
Implement exponential backoff for 429 and 5xx responses:
async function searchWithRetry(client: SerpexClient, query: string, maxRetries = 4) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await client.search({ q: query });
} catch (err: any) {
const status = err?.status ?? err?.response?.status;
if (status === 429 || status === 503) {
const waitMs = Math.min(1000 * 2 ** attempt, 16000);
await new Promise((r) => setTimeout(r, waitMs));
continue;
}
throw err; // non-retryable (401, 402, 400, …)
}
}
throw new Error("Max retries exceeded");
}import time
def search_with_retry(client, query: str, max_retries: int = 4):
for attempt in range(max_retries):
try:
return client.search({"q": query})
except Exception as e:
status = getattr(e, "status_code", None)
if status in (429, 503):
wait = min(1 * (2 ** attempt), 16)
time.sleep(wait)
continue
raise # non-retryable
raise RuntimeError("Max retries exceeded")Do not retry 401 (bad key) or 402 (no credits). Those errors won't resolve on their own.
High-volume usage
If your workload requires sustained throughput above the Scale tier, contact us to discuss an Enterprise plan with a custom limit.