← CheckReality API docs

Error codes

Every error response is one envelope: {"error": {"type", "code", "message", "doc_url"}}. type is the coarse class, code the stable key to branch on; context keys such as matched_terms sit inside error. Each doc_url points at its section on this page.

codeHTTPtype
invalid_request400invalid_request
malformed_json400invalid_request
missing_file400invalid_request
source_file_required400invalid_request
invalid_option400invalid_request
invalid_api_key401authentication
browser_origin_forbidden401authentication
not_found404not_found
method_not_allowed405invalid_request
not_reviewable409conflict
file_not_retained409conflict
link_already_active409conflict
link_already_consumed409conflict
sandbox_only409conflict
capture_not_ready409conflict
analysis_in_progress409conflict
link_expired410conflict
file_too_large413file_error
request_too_large413invalid_request
unsupported_type415file_error
file_encrypted415file_error
url_fetch_failed422processing_error
unreadable_document422processing_error
message_would_disclose_flag422invalid_request
idempotency_key_reuse422invalid_request
expected_total_below_submitted422invalid_request
attribution_currency_conflict422invalid_request
rate_limited429rate_limited
server_error500server_error

invalid_request HTTP 400 · type invalid_request

The request is malformed or fails validation: a missing required field, a wrong type, a value outside its bounds, a zero or malformed expires_in, an Idempotency-Key over 64 characters, a sig missing from a download URL, or (in production) a re-capture minted without borrower.phone or borrower.email. Malformed multipart bodies (missing boundary, truncated body) are 400 invalid_request too.

What to do. Read message — it names the field. Fix the request; do not retry unchanged.

malformed_json HTTP 400 · type invalid_request

The body is not valid JSON.

What to do. Send Content-Type: application/json and a body that parses. A form-encoded body is not read.

missing_file HTTP 400 · type invalid_request

A multipart submission had no file part, or the file was empty.

What to do. Attach the document bytes in the file part.

source_file_required HTTP 400 · type invalid_request

A reprocess request (POST /documents/{id}/reprocess) carried no file. Re-scoring needs the original document supplied again.

What to do. Attach the original document in the multipart file part. The bytes must be identical to the submitted document (its sha256 is checked); a mismatch is invalid_request. If you no longer hold the file, resubmit instead.

invalid_option HTTP 400 · type invalid_request

The options object could not be parsed or contains an unknown value (category, model, attribution, webhook_url). webhook_url must be an absolute, public http(s) URL — a malformed URL or one pointed at a private/internal host (localhost, an RFC1918 address, the cloud metadata address, .internal) is refused. An empty webhook_url is not a URL and is not refused: it means "use the account default", exactly as omitting the field does.

What to do. Check options against SubmitOptions in the OpenAPI.

invalid_api_key HTTP 401 · type authentication

No key, a malformed key, an unissued key, or a key for the other environment (sk_test_ against production, sk_live_ against the sandbox).

What to do. Send X-API-Key: sk_… or Authorization: Bearer sk_…. Keys are environment-specific.

browser_origin_forbidden HTTP 401 · type authentication

The request carried a browser Origin; the API is server-to-server only.

What to do. Call the API from your backend, never from a page.

not_found HTTP 404 · type not_found

No object with that id in this environment (sandbox and production ids are disjoint), an unknown template_id, a download sig that does not name this report's file set, a format: json report's download (it has no file), or the QR of a link that is no longer pending.

What to do. Check the id and the environment. For QR codes, mint a new link.

method_not_allowed HTTP 405 · type invalid_request

The path exists, but not for this HTTP method — e.g. PUT /v1/documents, which only answers to GET and POST. The response carries an Allow header naming the methods that do work. An unknown path (rather than an unsupported method on a known one) is still 404 not_found.

What to do. Read the Allow response header and use one of the listed methods.

not_reviewable HTTP 409 · type conflict

The document has not completed analysis, so it cannot be reviewed or used for a re-capture yet.

What to do. Wait for status: completed (poll or ?wait=true).

file_not_retained HTTP 409 · type conflict

The document's bytes are not held, so it cannot be reprocessed without the file.

What to do. Resubmit the document.

sandbox_only HTTP 409 · type conflict

simulate-capture was called against a deployment with a live re-capture backend.

What to do. Use a real capture in production; simulate only in the sandbox.

capture_not_ready HTTP 409 · type conflict

The capture exists but its evidence bundle is still being produced.

What to do. Retry shortly.

analysis_in_progress HTTP 409 · type conflict

POST /documents/{id}/reprocess was called while the document is still queued or processing. A second job for the same document would double-bill it and publish a second document.completed webhook.

What to do. Wait for status: completed or failed (poll or ?wait=true), then reprocess.

file_too_large HTTP 413 · type file_error

The file exceeds 200 MiB or 200 pages — uploaded, or fetched from a file_url. A PDF's page count is read at submit when the file can be parsed, so an over-limit PDF is refused synchronously — this 413, on the submitting request, before any analysis runs. When the count cannot be read there (encrypted, malformed), the same limit is enforced after scoring instead: the document is accepted, then ends status: failed with this same code rather than answering 413 directly.

What to do. Send a smaller file.

request_too_large HTTP 413 · type invalid_request

The JSON request body itself — options, a JSON file_url submission, a review, a report request — exceeds the 1 MB limit. This is distinct from file_too_large, which is about the document's bytes, not the request that carries or names them.

What to do. Keep the JSON body under 1 MB; send file bytes as multipart, not inline JSON.

unsupported_type HTTP 415 · type file_error

The file's type is detected from its content, not its name. This is raised when the content is a recognised container in another format (GIF, WEBP, TIFF, HEIC, BMP) whatever the name claims, or when the bytes match no known format and the name's extension is not .pdf, .png, .jpg or .jpeg either. For a file_url, the name comes from Content-Disposition, else the last URL path segment — it is used only for this fallback, never to override real PDF/PNG/JPEG bytes.

What to do. Send PDF, PNG or JPEG content — the file name does not matter.

file_encrypted HTTP 415 · type file_error

The PDF cannot be opened without its password, so it was never examined and no verdict is produced. Reported asynchronously, on the document: whether a file opens without a password is only knowable by trying to open it, which happens during analysis, so the submission is accepted and the document ends status: failed with this code in its error object. Certificate-encrypted (public-key) PDFs report the same code. A PDF carrying only an owner password — the "printing and copying restricted" kind, with an empty user password — opens without one and is analysed normally, as is a file whose text merely contains /Encrypt.

What to do. Remove the password and resubmit.

url_fetch_failed HTTP 422 · type processing_error

The file_url could not be fetched: not an absolute http(s) URL, a private or internal host (localhost, an RFC1918 address, the cloud metadata address, .internal) — the same rule webhook_url follows — DNS or connect failure, TLS error, no response within 60 s, a non-2xx status, or an empty body. Redirects are followed, at most three of them, and every hop is checked against that same host rule before it is fetched: a redirect onto a private host, or a fourth redirect, is refused here too. The reason is in message.

What to do. Make the URL reachable without cookies and within 60 s, then resubmit. Pre-signed URLs expire — mint a fresh one.

unreadable_document HTTP 422 · type processing_error

The bytes could not be read as a document, so no verdict is produced rather than a misleading one. Raised synchronously at submit when the content matches no known PDF/PNG/JPEG signature; raised asynchronously — the document ends status: failed — when the content is one of those formats but the engine could not open it: a truncated or corrupt file, or one whose internal structure is damaged. A file the engine could not open because it is encrypted is file_encrypted instead, which is the same failure with a remedy attached.

What to do. Check the file opens in a viewer; rescan if it is an image.

message_would_disclose_flag HTTP 422 · type invalid_request

The borrower message would reveal that the document is under review (matched_terms lists the words). No link was minted.

What to do. Rewrite the message, or set acknowledge_disclosure: true to override (recorded in message_audit).

idempotency_key_reuse HTTP 422 · type invalid_request

This Idempotency-Key was already used with a different payload. Keys are bound to the payload they first carried; array order and explicit null versus omitted both count.

What to do. Use a new key for a new payload, or resend the original payload exactly.

expected_total_below_submitted HTTP 422 · type invalid_request

expected_total is lower than the number of documents already submitted for the loan.

What to do. Declare at least as many as have been submitted.

attribution_currency_conflict HTTP 422 · type invalid_request

A second currency was attributed on one loan.

What to do. Keep one currency per loan.

rate_limited HTTP 429 · type rate_limited

More requests than the per-key token bucket allows: 600 requests per minute sustained with a burst of 1,200 by default — a key that has been idle can send 1,200 requests at once, then 10 per second. X-RateLimit-Limit reports the burst capacity, not the per-minute refill.

What to do. Honour Retry-After (the seconds until the next request will be accepted) and keep your sustained rate under the refill; X-RateLimit-Remaining is what is left in the bucket right now.

server_error HTTP 500 · type server_error

Something failed on our side; nothing was recorded.

What to do. Retry with backoff and the same Idempotency-Key.