Errors
The Qaf API's JSON error format, every status and code it returns, which errors to retry, and how request IDs help support find a request.
An error response has a 4xx or 5xx status and a JSON body with the same shape on every endpoint:
{
"code": "NOT_FOUND",
"status": 404,
"message": "No passage has ID d9335f69-e54b-59ae-a78c-50db734b8d51."
}
| Field | Description |
|---|---|
code |
A stable, machine-readable code. Branch on this, not on message. |
status |
The HTTP status, repeated. |
message |
A sentence for people. Its wording can change. |
data |
Details for some codes, described below. Left out when there are none. |
Status codes
| Status | code |
When | Retry |
|---|---|---|---|
400 |
BAD_REQUEST |
The body isn’t valid JSON, or the request can’t be run as sent, such as a filter with an unknown author ID. | No. Fix the request. |
401 |
UNAUTHORIZED |
The API key is missing, invalid or revoked. The WWW-Authenticate header is Bearer realm="api.qaf.ai", with error="invalid_token" added when the request sent a key. |
No. |
403 |
FORBIDDEN |
The key’s account can’t use the API. | No. |
404 |
NOT_FOUND |
No passage has the ID, or no endpoint matches the method and path. | No. |
405 |
METHOD_NOT_SUPPORTED |
The path exists but takes another method. The Allow header lists the methods it takes. |
No. |
413 |
PAYLOAD_TOO_LARGE |
The request body is larger than 64 KiB. | No. |
422 |
INPUT_VALIDATION_FAILED |
A field is missing, has the wrong type or breaks a limit, or the search body has a field it doesn’t define. | No. Fix the request. |
429 |
RATE_LIMITED |
The account went over a rate limit, or your address sent too many keys that don’t exist (invalid_keys). |
Only when data.reason is minute, after Retry-After seconds. |
500 |
INTERNAL_SERVER_ERROR |
Something went wrong on our side. | Yes, with backoff. |
503 |
SERVICE_UNAVAILABLE |
Qaf couldn’t finish the request within 30 seconds, or a service it relies on is down. | Yes, with backoff. |
Handle codes you don’t know by their status: new codes can appear in v1.
Validation errors
A 422 lists the problems in data. fieldErrors is keyed by the field’s name, and formErrors holds problems that belong to no single field:
{
"code": "INPUT_VALIDATION_FAILED",
"status": 422,
"message": "✖ Too big: expected number to be <=15\n → at limit",
"data": {
"formErrors": [],
"fieldErrors": {
"limit": ["Too big: expected number to be <=15"]
}
}
}
A field the search body doesn’t define appears in fieldErrors under the top-level field that holds it: topK under topK, and an unknown filter key under filters. message gives the full path, so you can tell which field to drop:
{
"code": "INPUT_VALIDATION_FAILED",
"status": 422,
"message": "✖ Unrecognized key: \"categoryIds\"\n → at filters.categoryIds",
"data": {
"formErrors": [],
"fieldErrors": {
"filters": ["Unrecognized key: \"categoryIds\""]
}
}
}
When the unknown key is the only one in filters, filters also lists Specify at least one filter, or omit filters for an unrestricted query.
Requests that can’t be run
A 400 from POST /v1/search carries data.reason:
reason |
Means |
|---|---|
unknown_author_id |
An ID in filters.authorIds isn’t in the catalog. Look the author up with GET /v1/catalog/lookup. |
unknown_book_id |
An ID in filters.bookIds isn’t in the catalog. |
no_matching_books |
No book can match the filters together, such as a genre and a death-year range none of its authors fall in. |
New reasons can appear in v1. When reason is missing or unknown, show message.
Rate limit errors
A 429 has a Retry-After header and data:
{
"code": "RATE_LIMITED",
"status": 429,
"message": "Too many requests. Try again later.",
"data": { "retryAfter": 23, "reason": "minute" }
}
retryAfter is the same number of seconds as the header. reason says which limit was reached. Only a minute refusal is worth waiting out: a day refusal can last most of a day, and invalid_keys means the key you sent doesn’t exist or was revoked. See Back off and retry.
Request IDs
Every API response, including errors, has an X-Request-Id header. The only exception is the answer to a browser’s CORS preflight (OPTIONS) request. Log the ID with failed requests, and include it when you email us at contact@qaf.ai, so we can find the request.