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:
| Plan | Searches per month | Price | Notes |
|---|---|---|---|
| Free | 1,000 | €0 | No credit card required. |
| Pro | 6,000 | €29/month | Cancel anytime. |
| Top-up | +1,000 each | €7 one-time | Never 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:
| Situation | Status | Meaning |
|---|---|---|
| More than 60 requests in 60 seconds | 429 | Transient. Back off and retry. |
| Monthly quota exhausted, no top-up left | 403 | Permanent 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-Source—monthlywhile the plan quota lasts, thentopuponce top-up searches are being spent.Retry-After— Present on429only: 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.