Rate Limits & Quotas
Limits are enforced per API key. Three dimensions apply: a request rate (per minute), a monthly quota (total calls), and the maximum top_k you can request per call.
How limits apply#
Limits are tracked per API key, not per IP — so your throughput is your own, independent of where requests originate. Each call to POST /v1/retrieve counts against both your rate limit and your monthly quota.
Your key also has a maximum top_k you can request. If you send a top_k above your plan maximum — but at or below the absolute ceiling of 50 — the API clamps it down rather than rejecting the call. A top_k above 50 is rejected with 400 Bad Request.
Plan limits#
API access is paid; a key is provisioned by our team on one of the plans below. If your workload needs more, tell us your expected volume and we’ll raise the limits on your key.
When you hit a limit#
Exceeding your per-minute rate or your monthly quota returns 429 Too Many Requests in the standard error envelope. (A top_k above your plan maximum is clamped down instead, as long as it’s at or below the absolute ceiling of 50; a top_k above 50 is rejected with 400 Bad Request.)
{
"success": false,
"error": {
"statusCode": 429,
"message": "Rate limit exceeded",
"error": "Too Many Requests",
"path": "/v1/retrieve",
"timestamp": "2026-05-30T12:00:00.000Z"
}
}The message field distinguishes the two limits: a per-minute breach reads "Rate limit exceeded", while exceeding your monthly quota reads "Monthly quota exceeded". Both are 429.
Handling 429s#
Back off and retry with exponential backoff and jitter. A simple approach:
async function retrieveWithRetry(body, { maxRetries = 4 } = {}) {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
const res = await fetch("https://api.focusalpha.ai/v1/retrieve", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.FOCUSALPHA_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
if (res.status !== 429) return res;
// exponential backoff with full jitter
const delay = Math.random() * Math.min(1000 * 2 ** attempt, 15000);
await new Promise((r) => setTimeout(r, delay));
}
throw new Error("Rate limit: retries exhausted");
}filters so each call does more useful work. If you consistently need more headroom, talk to us about a higher plan.