Rate limits and quotas

How ODEN rate limits requests per key, what a 429 means, how quotas map to Free, Pro and top-ups, and how to back off and retry cleanly.

Documentation3 min readUpdated 2026-07-30

Requests are limited per key in two independent ways: a monthly search quota tied to your plan, and a short-term burst limit that protects the service from runaway agent loops.

Quotas and plan allocations#

Your monthly search quota is determined by your subscription tier and displayed in real time in your dashboard:

PlanSearches per monthPriceNotes
Free1,000€0No credit card required.
Pro6,000€29/monthCancel anytime.
Top-up+1,000 each€7 one-timeNever expires, stacks on any active plan.

A search is defined as one successful HTTP 200 call to /search. Failed requests (e.g. 400 Bad Request or 401 Unauthorized) do not consume quota. Cached calls count as searches, but return in tens of milliseconds from edge memory. Purchased top-up searches are consumed automatically after your base monthly allocation is exhausted.

Burst limit#

Every key is limited to 60 requests per 60 seconds, on every plan. Exceeding it returns 429 with a Retry-After header. This is a short window: wait and retry, and the request succeeds.

The two limits return different status codes#

This distinction matters when you write retry logic:

SituationStatusMeaning
More than 60 requests in 60 seconds429Transient. Back off and retry.
Monthly quota exhausted, no top-up left403Permanent until the monthly rollover, or until you add a top-up. Do not retry.

Retrying a 403 will not succeed before the next calendar month. Upgrade the plan or buy a top-up pack instead.

Quota response headers#

Every successful response carries your current quota state, so you can track consumption without a separate call:

HTTP/1.1 200 OK
Content-Type: application/json
X-Quota-Limit: 6000
X-Quota-Remaining: 4635
X-Quota-Period: 2026-09
X-Quota-Source: monthly
  • X-Quota-Limit — Searches included in the current plan for this month.
  • X-Quota-Remaining — Searches left before the monthly quota is exhausted.
  • X-Quota-Period — The calendar month the counter belongs to (YYYY-MM).
  • X-Quota-Sourcemonthly while the plan quota lasts, then topup once top-up searches are being spent.
  • Retry-After — Present on 429 only: seconds to wait before the next request.

All of these are exposed via CORS, so browser clients can read them too.

Exponential backoff with full jitter#

Do not hammer the endpoint with naive constant sleep loops. In production, use exponential backoff with full jitter to desynchronize concurrent client retries:

async function fetchWithBackoff(url: string, options: RequestInit, maxRetries = 4): Promise<Response> {
  let attempt = 0;
  while (attempt <= maxRetries) {
    const res = await fetch(url, options);
    if (res.status !== 429 && res.status < 500) {
      return res;
    }

    // Read Retry-After header if present, or compute exponential backoff with full jitter
    const retryAfter = parseInt(res.headers.get("Retry-After") || "0", 10);
    const baseDelay = retryAfter > 0 ? retryAfter * 1000 : Math.pow(2, attempt) * 1000;
    const jitter = Math.random() * baseDelay;
    const sleepTime = Math.min(baseDelay + jitter, 16000);

    if (attempt === maxRetries) {
      throw new Error(`ODEN request failed with status ${res.status} after ${maxRetries} retries`);
    }

    await new Promise((resolve) => setTimeout(resolve, sleepTime));
    attempt++;
  }
  throw new Error("Retries exhausted");
}

High-concurrency worker pools#

If you run batch scraping or high-volume agent reasoning loops, do not launch hundreds of unthrottled concurrent promises. Regulate throughput with a semaphore or worker queue:

import asyncio
from typing import List

# Bound concurrency to 5 simultaneous requests
SEM = asyncio.Semaphore(5)

async def throttled_search(query: str) -> dict:
    async with SEM:
        return await async_oden_search(query)

async def batch_research(queries: List[str]):
    return await asyncio.gather(*(throttled_search(q) for q in queries))

Staying under quota#

  • Cache repeated queries: Autonomous agents frequently issue identical search phrases across multi-turn reasoning steps. Implement a local memory or Redis cache with a 4-to-12 hour TTL.
  • Use depth: "basic": For broad entity lookups or autocomplete checks where full synthesis is not needed.
  • Set realistic timeouts: Prevent hanging connections by enforcing client-side socket timeouts of 20–30 seconds.

Next steps#