Over HTTP, an error body is JSON with an
error and a message field. The SDKs raise an error whose message starts with the prefix shown under each entry, followed by the API’s message.
400 Invalid request parameters
Retry: fix, then retry. What you see If a parameter is missing or invalid, search and fetch return400 with an error such as Invalid parameter. The body also has a message, and can have a details field that lists the validation errors.
Cause
A request parameter does not match what the endpoint accepts: a missing query or url, a value outside its range, a bad date filter, or a type the schema rejects. Both the keyed paths, such as /v1/search and /v1/fetch, and the /public paths return it.
Fix
- Read the
messageand anydetailsin the response body. They name the field that failed. - Check the request against the Search or Fetch parameter list and send the call again with a corrected body.
- If the body says
Missing app identifierinstead, see 400 Missing app identifier.
400 Missing app identifier
Retry: fix, then retry. What you see/v1/search/public or /v1/fetch/public arrived without the X-Keenable-Title header. Keyed requests to /v1/search or /v1/fetch never need this header. A 400 on those paths means an invalid parameter.
Fix
Add the header with your application’s name. This request includes it:
400. If you have an API key, call /v1/search instead, which doesn’t need the header.
401 Malformed key
Retry: fix, then retry. What you see If the key has the wrong format, the API returns:/v1/search or /v1/fetch carries no key at all, the API returns:
KEENABLE_API_KEY isn’t set, they call the keyless /public paths, so you get the second body only over raw HTTP. If the key is malformed, the SDKs raise KeenableAuthError (Python, TypeScript) or *keenable.AuthError (Go) with the message prefix Keenable authentication failed (401).
Cause
Malformed API key means the value does not look like a Keenable key at all. Common causes are a truncated copy, a stray quote or space, or a placeholder such as keen_<your_key> left in place. Missing authentication means the request reached a keyed path without an X-API-Key or Authorization: Bearer header.
Fix
- Check the value of
KEENABLE_API_KEY. It starts withkeen_and has no quotes or whitespace around it. - Over raw HTTP, send the key in the
X-API-Keyheader. To call without a key, use/v1/search/publicor/v1/fetch/publicwith anX-Keenable-Titleheader instead. - To keep working while you sort it out, unset
KEENABLE_API_KEY. The SDKs fall back to the keyless tier.
401 Invalid key
Retry: fix, then retry. What you see The API returns:KeenableAuthError (Python, TypeScript) or *keenable.AuthError (Go) with the message prefix Keenable authentication failed (401).
Cause
The key has the right format, but Keenable has no key with that value. A typo that keeps the keen_ prefix causes this.
Fix
- Compare
KEENABLE_API_KEYwith the key in the console ↗. - If you cannot find it there, create a new key and replace the old value. See API keys.
- To keep working while you sort it out, unset
KEENABLE_API_KEY. The SDKs fall back to the keyless tier.
402 Out of credits
Retry: do not retry. What you see The SDKs raiseKeenableAPIError (Python, TypeScript) or *keenable.APIError (Go) with status 402 and the message prefix Keenable: insufficient credits (402).
Cause
Your organization has spent its free monthly allowance of 100,000 requests and has no purchased credits left. Every retry returns the same 402 until someone adds credits or the allowance resets. See credits.
Fix
Stop and ask a human to buy credits or turn on auto top-up in the console ↗.
SDK 0.1.x has no dedicated error type for 402, so catch the generic API error and check its status. This code stops on 402 and passes every other error through:
402: KeenableCreditsError in Python and TypeScript, *keenable.CreditsError in Go. Once you upgrade, catch that type instead of checking the status.
403 Key rejected or revoked
Retry: do not retry; check the key, then ask a human. What you see A revoked key returns:KeenableAuthError (Python, TypeScript) or *keenable.AuthError (Go) with the message prefix Keenable authentication failed (403).
Cause
403 means the server won’t accept this key. Two cases return the same status:
- On
/v1/search,/v1/fetchand/mcp, the key usually exists but was revoked in your organization. - On
/mcp-public, a key the server doesn’t accept also returns403, not401, so a mistyped or unknown key looks like a revoked one. See MCP server.
403 for reasons unrelated to your key. If the body says "error": "Upstream forbidden", the target site blocked the fetch, for example with a bot check. If it says Fetching from private/internal hosts is not allowed, the URL points at a private address. In both cases, check the error and message before you touch the key, and try a different URL instead.
Fix
- Compare the key value with the one in the console ↗. Fix typos, stray quotes and leftover placeholders before treating the key as revoked.
- If the value matches and still gets
403, ask a human with console access to issue a new key, then updateKEENABLE_API_KEY. - On
/mcp-public, you can also drop the key and stay on the public tier, or switch the URL to/mcponce the key is confirmed.
404 Page not found
Retry: fix the URL, then retry. What you see If the target page doesn’t exist, fetch returns404:
404. If search gave you the link, try the next result instead.
422 Content could not be extracted
Retry: fix the URL or skip; do not retry the same URL. What you see If the page exists but Keenable can’t extract readable content from it, fetch returns422 with "error": "Unprocessable entity" and a message such as The page is behind a login or paywall. Search returns 422 with Search failed and a status code in the message when the search fails in a way a retry won’t fix.
Cause
The URL is reachable, but extraction failed: an unsupported domain, a login wall or paywall, a page that is mostly binary or empty of text, an error status from the target site, or a failure inside the extractor.
Fix
Don’t retry the same URL. It fails the same way every time. Try a different URL from search, or fetch a cleaner source for the same topic. If many URLs from one site fail, the domain is probably unsupported, so skip that site.
429 Rate limit exceeded
Retry: wait, then retry. What you see The SDKs raiseKeenableRateLimitError (Python, TypeScript) or *keenable.RateLimitError (Go) with the message prefix Keenable rate limit exceeded (429).
Responses carry these headers, and a 429 adds Retry-After:
Cause
You sent requests faster than your limit allows:
- Keyless: 1,000 requests per hour and 10 per second per IP, on a pool shared with everyone else on your egress address.
- With an API key: 10 requests per second per organization, with no hourly cap.
429 can also come from further down the line. If the message says the target server rate-limited the fetch, or that search or fetch is temporarily rate-limited, your own limit isn’t the cause. Those responses carry Retry-After: 1.
Fix
Wait for the time in Retry-After, then retry. If you hit the keyless hourly cap, add an API key: it removes the hourly cap and gives you your own limit. For a higher per-second limit, talk to us.
5xx Server error
Retry: retry up to 3 times. What you see The SDKs raiseKeenableAPIError (Python, TypeScript) or *keenable.APIError (Go) with the message prefix Keenable API error (5xx), where 5xx is the actual status, such as 500 or 503.
Cause
Something failed on Keenable’s side or on the way to the target page. The request itself is fine. The error field tells the cases apart:
502Bad gateway: fetch couldn’t reach the target page, for example after a DNS, TLS or connection failure.503Authentication temporarily unavailable: Keenable couldn’t verify your key. The response carriesRetry-After: 5.504Gateway timeout: the target page took too long to respond.529Overloaded: search is temporarily overloaded. The response carriesRetry-After: 1.
Retry-After header, wait at least that long. If the error persists after 3 attempts, stop and report it.
Report a problem
Responses from search and fetch carry anX-Request-Id header. Include its value when you get help, so we can find your request in our logs. The Go SDK adds it to the error message as request_id. If Keenable rejects a request before it reaches search or fetch, such as one that returns 400 or 401, the response has no request ID. For those, include the time of the request and the full response body.