Skip to content
Developers
Esc
↑↓navigate↵open⌘Jpreview
On this page

Rate limits

The Qaf API allows 60 requests a minute and 1,000 searches a day per account. The rate-limit headers, the 429 response, and how to back off.

Limits apply per Qaf account, across all of its API keys:

Limit Applies to
60 requests a minute Every endpoint.
1,000 searches a day POST /v1/search, counted over 24 hours.

The MCP server has its own limit, also counted per account: see MCP server.

Headers

Once the API has accepted your key, its response reports the account’s minute window:

Header Value
X-RateLimit-Limit Requests the account can make per minute.
X-RateLimit-Remaining Requests left in the current minute.
X-RateLimit-Reset Seconds until the minute’s allowance resets.

These headers always describe the minute window. A 429 for the minute or day limit has them too; on a day refusal, Retry-After is the time until searches work again instead. They’re absent when the API refuses a request before it accepts the key: any 401 or 403, an invalid_keys 429, a body over 64 KiB (413), a body that isn’t valid JSON (400), or a path the API doesn’t have (404, 405).

When you reach a limit

The API answers 429 with a Retry-After header, in seconds, and the reason in the body:

{
  "code": "RATE_LIMITED",
  "status": 429,
  "message": "Too many requests. Try again later.",
  "data": { "retryAfter": 23, "reason": "minute" }
}
reason Means
minute The account made 60 requests this minute.
day The account ran 1,000 searches in the last 24 hours. The other endpoints still work. Retry-After is the time until searches work again, which can be many hours.
invalid_keys The key you sent doesn’t exist or was revoked, and your network address has sent too many such keys. A key that works is never refused for this reason.

Back off and retry

Retry a 429 only when reason is minute: wait Retry-After seconds, then send the request again. Don’t wait out the other reasons:

  • day: stop searching, and tell your users when searches work again. Retry-After can be close to a day.
  • invalid_keys: the key you send doesn’t exist or was revoked. Fix it: retrying with the same key fails again.

For 500 and 503 responses, which have no Retry-After, wait longer after each failed attempt.

const sleep = (seconds: number) =>
	new Promise((resolve) => setTimeout(resolve, seconds * 1000));

async function callQaf(url: string, init: RequestInit, attempts = 4) {
	for (let attempt = 1; ; attempt++) {
		const response = await fetch(url, init);

		if (response.status === 429) {
			const { data } = await response.clone().json();

			if (data?.reason === "day")
				throw new Error(`Daily search limit. Searches resume in ${data.retryAfter} s.`);

			if (data?.reason === "invalid_keys")
				throw new Error("Invalid API key. Check QAF_API_KEY.");

			if (data?.reason !== "minute" || attempt === attempts) return response;

			await sleep(Number(response.headers.get("Retry-After")));
		} else if (response.status >= 500 && attempt < attempts) {
			await sleep(2 ** attempt);
		} else {
			return response;
		}
	}
}
import time

import requests


def call_qaf(method, url, attempts=4, **kwargs):
    for attempt in range(1, attempts + 1):
        response = requests.request(method, url, timeout=30, **kwargs)

        if response.status_code == 429:
            data = response.json().get("data") or {}

            if data.get("reason") == "day":
                raise RuntimeError(f"Daily search limit. Searches resume in {data['retryAfter']} s.")

            if data.get("reason") == "invalid_keys":
                raise RuntimeError("Invalid API key. Check QAF_API_KEY.")

            if data.get("reason") != "minute" or attempt == attempts:
                return response

            time.sleep(int(response.headers["Retry-After"]))
        elif response.status_code >= 500 and attempt < attempts:
            time.sleep(2**attempt)
        else:
            return response

Spread requests over the minute rather than sending them in bursts, and cache results you read more than once: passages and verses rarely change.

Need more

If your project needs higher limits, email us at contact@qaf.ai with what you’re building and the volume you expect.