Rate Limits & Quotas
SearchJet applies three kinds of limit:
- Monthly plan quotas cap how many searches, indexed pages, reindexes and AI answers you get.
- Request rate limits cap how fast you can call the API.
- Abuse detection watches for unusually heavy bulk indexing.
This page explains each one, and how to handle the 429 responses they produce.
Plans and quotas
| Free | Pro | Growth | Enterprise | |
|---|---|---|---|---|
| Price | $0 | $19/month | $79/month | $299 |
| Searches per month | 10,000 | 50,000 | 250,000 | Unlimited |
| Indexed pages | 1,000 | 25,000 | 100,000 | Unlimited |
| Sites | 1 | 1 | 3 | 10 |
| Full reindexes per day | 1 | 4 | Unlimited | Unlimited |
| AI answers per month | 50 | 500 | 5,000 | 100,000 |
| Semantic search | – | Yes | Yes | Yes |
| Requests per minute per private key | 10 | 60 | 300 | 1,000 |
| Burst per second per private key | 2 | 10 | 50 | 200 |
| Max documents per index request | 1,000 | 5,000 | 5,000 | 5,000 |
See Pricing for everything included in each plan.
Request rate limits
There are two layers.
1. Per client IP, on every route. Most endpoints allow 30 requests per minute from a single IP address, including /search and all /index/* routes. client-info and ai/generate allow 20, and index/tasks and ai/status allow 60. The counter is shared across routes: a server that both indexes and searches uses one budget for both. Successful responses include X-RateLimit-Limit and X-RateLimit-Remaining headers. When you go over, the response is 429 with a Retry-After header. Its body is {"message": "Too Many Attempts."} when you send Accept: application/json, and an HTML page otherwise.
2. Per private key, by plan. Endpoints that take the private key (indexing, analytics, clicks, AI generation) also enforce the per-minute and per-second limits in the table above. Exceeding them returns one of these:
{ "error": "Rate limit exceeded", "message": "Your plan allows 10 requests per minute. Please upgrade for higher limits.", "plan": "free", "limit": 10 }
{ "error": "Burst limit exceeded", "message": "Too many requests in a short time. Please slow down.", "plan": "free", "burst_limit": 2 }
These 429 responses have no Retry-After header, so back off exponentially.
Search from the browser where you can
Because the search limit is per IP, a backend that proxies all its visitors' searches shares 30 searches per minute in total. Calling /search directly from the browser with the public key gives each visitor their own budget. If you must search from a server, cache results.
Monthly search quota
Every search response includes remaining_quota: the number of searches left for your site this month. On unlimited plans it's null. Every authenticated search request counts, including cached results and many error responses. When the quota is used up, search returns a hard block until the next month or until you upgrade:
{ "error": "Search quota exceeded.", "code": "QUOTA_EXCEEDED" }
const res = await fetch('https://app.searchjetengine.com/api/v1/search?q=test', {
headers: { Authorization: 'Bearer YOUR_PUBLIC_KEY', Accept: 'application/json' },
});
const data = await res.json();
if (data.remaining_quota !== null && data.remaining_quota >= 0 && data.remaining_quota < 1000) {
console.warn(`SearchJet quota running low: ${data.remaining_quota} searches left`);
}
To see your plan and usage limits from a server, call GET /api/v1/client-info with your private key. It returns plan and usage_limits (search_queries_per_month, max_indexed_pages, reindexing_per_day, used_indexed_pages, reindexes_today and more). For a live count of remaining searches, rely on remaining_quota from search responses.
Indexing limits
- Pages: an index request that would take you past your page limit returns
429 {"success": false, "error": "quota_exceeded", ...}. Near the limit, re-sent ids are counted as new documents. - Full reindex:
full_reindex: truebeyond your daily allowance returns429 reindex_limit. - Abuse detection: on Free and Pro, indexing more than 1,000 documents per minute (2,000 on Growth and Enterprise) is flagged with an
X-Abuse-Warningheader. On Free and Pro, repeated violations within an hour temporarily suspend the key with429 {"error": "Abuse detected", ...}. A single 5,000-document request on Pro already counts as a violation. Keep batches to 1,000 documents or fewer, and wait at least a minute between large batches.
AI quotas
ai_answer=true on search: once you're over the monthly AI-answer quota, the search still returns 200, but without answer, and meta.warnings contains "AI answer quota exceeded.". POST /ai/generate uses the same monthly allowance, and returns 429 limit_exceeded when it runs out.
Handling 429 correctly
Not every 429 is worth retrying:
| 429 response | Retry? |
|---|---|
Too Many Attempts., Rate limit exceeded, Burst limit exceeded |
Yes, with exponential backoff |
QUOTA_EXCEEDED, quota_exceeded, reindex_limit, limit_exceeded |
No. Wait for the quota to reset, or upgrade. |
Abuse detected |
No. Retrying extends the suspension. Slow down, or contact support. |
import os
import random
import time
import httpx
NO_RETRY = {"QUOTA_EXCEEDED", "quota_exceeded", "reindex_limit", "limit_exceeded", "Abuse detected"}
def search_with_retry(query: str, max_retries: int = 4) -> dict:
for attempt in range(max_retries + 1):
response = httpx.get(
"https://app.searchjetengine.com/api/v1/search",
params={"q": query},
headers={
"Authorization": f"Bearer {os.environ['SEARCHJET_PUBLIC_KEY']}",
"Accept": "application/json",
},
timeout=10.0,
)
if response.status_code != 429 and response.status_code < 500:
response.raise_for_status()
return response.json()
body = response.json() if "json" in response.headers.get("content-type", "") else {}
if body.get("code") in NO_RETRY or body.get("error") in NO_RETRY or attempt == max_retries:
response.raise_for_status()
retry_after = response.headers.get("Retry-After", "")
time.sleep(int(retry_after) if retry_after.isdigit() else 2 ** attempt + random.random())
JavaScript and Laravel versions are in Error Handling.
Upgrading
If you regularly hit these limits, compare plans on the Pricing page and upgrade from your dashboard at app.searchjetengine.com.