Errors
The scanr.ai API uses standard HTTP status codes. A 2xx is success; anything else carries a message saying what went wrong.
Error bodies
Read the status code first; it tells you what kind of failure you have. Every error body is JSON, with the message in error.message:
message is written for a person and says what failed: the field that didn't validate, the plan a feature needs, the window that ran out. Don't parse it; branch on the status code. A 5xx message is for a support request, not for your code to act on.
A scan the scanner refuses (422 from POST /api/v2/scan) adds the scanner's code and params, and causes when several refusals applied at once:
New scan refusal codes appear without notice, so treat an unknown code as "show message", never as a failure to parse.
One scan 422 is not about the payload alone: request.query_timeout means the scan ran past the scanner's time limit. It depends on load as well as on what the scan asks for, so narrow the scan (fewer tickers, filters or lookback), or retry it later with backoff.
Concept research refusals add a stable code and retryable, because one status can mean different things there (a 503 is either "not available yet" or "try again later"). Branch on code for those endpoints; see Research a Concept.
The AI credit budget's 429 adds machine-readable fields:
Reading the message takes one line. The fallback covers a response that never reached the API, such as a proxy's error page:
Status codes
Retrying
Retry only what can succeed on a second attempt:
- Retry
429after the seconds in itsRetry-Afterheader, and500,502,503and504with exponential backoff (for example 1 s, 2 s, 4 s, then give up). - Don't retry
400,401,403,404,413or422. The same request will fail the same way. The exception is a scan422withcoderequest.query_timeout: narrowing the scan is the reliable fix, but the same scan can also succeed later, so it may be retried with backoff. - A retry before the reset time is refused again. It doesn't extend the window, but it wastes a call, so wait for the reset instead of looping.
MCP errors
The MCP server reports errors differently. Authentication failures are an HTTP error before any tool runs, with the reason in error_description: 401 for a missing, malformed, revoked or expired token, 403 for a missing read scope or an inactive API plan, 503 when the plan couldn't be checked. Everything else (a rate limit, the AI budget, personal data access being off, an invalid scan payload, a scan the scanner refused) comes back as a normal tool result with isError: true and the message as text, so the assistant can read it and adjust. A refused scan's text ends with Refusal code: <code>, the same code the REST 422 carries.


