Error handling¶
Every error response is JSON with a single message field describing what
went wrong:
The HTTP status code is your primary signal. The body is for logging and the end-user-facing message you choose to surface.
Status codes¶
| Status | Category | What it means | What your client should do |
|---|---|---|---|
200 OK |
Success | Request completed. | Process the response body. |
201 Created |
Success | Resource created. | Same as 200; the new resource is in the body. |
202 Accepted |
Success | Request was accepted for asynchronous processing (e.g. a webhook ingest, a hosted verification session creation). | Treat as success; observe the resource via GET or wait for the webhook. |
400 Bad Request |
Client | The request is malformed or missing a required field. The body's message describes what's wrong. |
Fix the request; do not retry the same payload. |
401 Unauthorized |
Client | X-Api-Key / X-Api-Secret headers are missing or don't match an active key pair. |
Verify your credentials. If they were valid yesterday, check the product portal — they may have been deactivated. |
403 Forbidden |
Client | Credentials are valid but your product can't access this specific resource (e.g. an applicant that belongs to a different product). | Stop and review. Repeating the call won't help. |
404 Not Found |
Client | The applicant / level / document referenced in the URL does not exist for your product. | Check the externalUserId / id; consider whether you need to create the resource first. |
422 Unprocessable Entity |
Client | The request was syntactically valid but semantically rejected — for example, calling /verify on an applicant that has no eligible verification level configured. |
Fix the data and retry. |
429 Too Many Requests |
Client | You've exceeded a per-IP rate limit (in particular, auth endpoints are capped at 20/min). The response includes a Retry-After header with the seconds to wait. |
Back off until Retry-After elapses, then retry. Implement client-side rate limiting if you see this often. |
500 Internal Server Error |
Server | Unexpected server error. | Safe to retry idempotent requests after a brief back-off. Persistent? Reach out to support. |
502 Bad Gateway / 503 Service Unavailable / 504 Gateway Timeout |
Server | Transient infrastructure error, often during deploys. | Retry with exponential back-off (~1 s, 2 s, 4 s, 8 s, capped at ~30 s). |
The API does not currently emit
409 Conflict. Creating the same applicant twice viaPOST /v1/applicantsis idempotent onexternalUserId— you get200 OKwith the existing applicant, not a conflict.
Idempotency & retries¶
GET,DELETE, andPUTrequests are idempotent — safe to retry on any 5xx or network failure.POST /v1/applicantsis idempotent onexternalUserId— sending the sameexternalUserIdtwice returns the existing applicant, not a duplicate.- All other
POSTrequests are not automatically idempotent. If you're worried about duplicates from a retry storm, store a client-side hash of(endpoint + payload)and skip the second send.
Validation error example¶
Sending a creation request without the required externalUserId:
If multiple fields fail, the response describes the first problem encountered — the API stops at the first missing field rather than returning a list. Fix and retry.
What to log¶
For every non-2xx response, log:
- The HTTP method + path you called.
- The full response body.
- The exact UTC timestamp of the request.
- For 429s, the
Retry-Afterheader value.
Don't log request bodies that contain documents, selfies, or PII. Don't log
the X-Api-Secret header.
Talking to support¶
If a status code is genuinely unexpected (e.g. 500 on a request that worked
yesterday), open a ticket with:
- The full request method + URL (path only — strip query strings that contain personal info).
- The exact UTC timestamp.
- The applicant
externalUserIdif relevant. - A 1–2 sentence description of what you expected vs got.
Don't paste API keys or applicant PII into the ticket.