ReddGrowReddGrow Docs
AI Observability as a Service

Rate Limits

100 requests per minute per IP address. How to stay within limits and retry correctly.

Rate Limits

There is no per-key quota. All /v1/aeo/* endpoints share a per-IP limit:

WindowLimit
Per minute100 requests per IP address (default)

There is no hourly limit. Because scans run daily, read your data once per scan cycle and cache it instead of re-reading it per prompt.


When you hit a limit

Exceeding the limit returns HTTP 429 Too Many Requests:

{ "statusCode": 429, "message": "Too many requests" }

Wait until the current window resets before retrying. The per-minute window resets every 60 seconds.


Which endpoints are expensive vs. cheap

Not all endpoints have the same response cost. Plan your request patterns accordingly:

Cheaper endpoints (fast, low data volume — safe to call frequently):

  • GET /v1/aeo/me
  • GET /v1/aeo/engines
  • GET /v1/aeo/countries
  • GET /v1/aeo/score-methodology
  • GET /v1/aeo/brands
  • GET /v1/aeo/topics

Moderate endpoints (aggregated data, typically fast):

  • GET /v1/aeo/visibility/by-brand
  • GET /v1/aeo/visibility/by-engine
  • GET /v1/aeo/visibility/by-country
  • GET /v1/aeo/visibility/by-topic
  • GET /v1/aeo/visibility/timeline
  • GET /v1/aeo/prompts

Heavier endpoints (row-level data, larger payloads — use pagination):

  • GET /v1/aeo/prompts/:id/runs
  • GET /v1/aeo/sources/domains
  • GET /v1/aeo/sources/detail
  • GET /v1/aeo/citations
  • GET /v1/aeo/visibility/explain
  • GET /v1/aeo/runs/:id/explain

For heavy endpoints, always use limit and offset pagination rather than pulling everything in one call. Default limit is 20 for most paginated endpoints.

Tip: GET /v1/aeo/prompts/:id/runs accepts include_answer=false, which leaves out answer_text, answer_html and sources. Use it to list runs cheaply, then fetch one full answer with GET /v1/aeo/runs/:scan_result_id.


Retry advice

Use exponential backoff with jitter when retrying after a 429.

A simple pattern:

async function fetchWithRetry(url: string, headers: Record<string, string>, maxRetries = 4) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    const res = await fetch(url, { headers });
    if (res.status !== 429) return res;

    const baseDelay = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s, 8s
    const jitter = Math.random() * 500;
    await new Promise((r) => setTimeout(r, baseDelay + jitter));
  }
  throw new Error('Max retries exceeded');
}

For batch data sync jobs, spread requests over several minutes rather than issuing them all at once. Since data only changes with the daily scan, one sync per cycle stays far below the limit.