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.
| code | HTTP | type |
|---|---|---|
invalid_request | 400 | invalid_request |
malformed_json | 400 | invalid_request |
missing_file | 400 | invalid_request |
source_file_required | 400 | invalid_request |
invalid_option | 400 | invalid_request |
invalid_api_key | 401 | authentication |
browser_origin_forbidden | 401 | authentication |
not_found | 404 | not_found |
method_not_allowed | 405 | invalid_request |
not_reviewable | 409 | conflict |
file_not_retained | 409 | conflict |
link_already_active | 409 | conflict |
link_already_consumed | 409 | conflict |
sandbox_only | 409 | conflict |
capture_not_ready | 409 | conflict |
analysis_in_progress | 409 | conflict |
link_expired | 410 | conflict |
file_too_large | 413 | file_error |
request_too_large | 413 | invalid_request |
unsupported_type | 415 | file_error |
file_encrypted | 415 | file_error |
url_fetch_failed | 422 | processing_error |
unreadable_document | 422 | processing_error |
message_would_disclose_flag | 422 | invalid_request |
idempotency_key_reuse | 422 | invalid_request |
expected_total_below_submitted | 422 | invalid_request |
attribution_currency_conflict | 422 | invalid_request |
rate_limited | 429 | rate_limited |
server_error | 500 | server_error |
invalid_request HTTP 400 · type invalid_requestThe 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_requestThe 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_requestA 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_requestA 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_requestThe 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 authenticationNo 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 authenticationThe 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_foundNo 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_requestThe 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 conflictThe 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 conflictThe document's bytes are not held, so it cannot be reprocessed without the file.
What to do. Resubmit the document.
link_already_active HTTP 409 · type conflictA re-capture link for this document is still pending. One live link per document.
What to do. Revoke it (POST /v1/verifit/links/{id}/revoke) or wait for it to expire, then mint again.
link_already_consumed HTTP 409 · type conflictThe borrower already used this link; it cannot be revoked.
What to do. Read the capture from capture_id.
sandbox_only HTTP 409 · type conflictsimulate-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 conflictThe capture exists but its evidence bundle is still being produced.
What to do. Retry shortly.
analysis_in_progress HTTP 409 · type conflictPOST /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.
link_expired HTTP 410 · type conflictThe link's expires_at has passed.
What to do. Mint a new link.
file_too_large HTTP 413 · type file_errorThe 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_requestThe 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_errorThe 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_errorThe 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_errorThe 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_errorThe 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_requestThe 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_requestThis 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_requestexpected_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_requestA second currency was attributed on one loan.
What to do. Keep one currency per loan.
rate_limited HTTP 429 · type rate_limitedMore 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_errorSomething failed on our side; nothing was recorded.
What to do. Retry with backoff and the same Idempotency-Key.