Serpex

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

CodeMeaningAction
200Success
401Invalid or missing API keyFix the key; don't retry
402Insufficient creditsTop up at app.serpex.dev
429Rate limit exceededBack off and retry (see below)
500 / 503Transient server errorRetry 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: 1

Retry 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.

On this page