API
Errors and limits
One error envelope, eight codes, and every limit the API enforces.
Every error, from every endpoint, has the same shape. code is stable and machine-readable; message is a sentence for people and may change.
{ "error": { "code": "not_found", "message": "Session not found" } }| HTTP | code | When | Retry? |
|---|---|---|---|
| 400 | bad_request | Malformed JSON or multipart, or a field fails validation | No: fix the request |
| 401 | unauthorized | Missing, unknown or revoked API key; expired console session | No: check the key |
| 403 | forbidden | Your console role may not do this | No |
| 404 | not_found | No such resource in your app (or organisation) | No |
| 409 | conflict | Wrong state: deciding a session not in review, uploading after submit | After reloading the state |
| 422 | unprocessable | Understood but refused: no face in an image, an MRZ that cannot be parsed, a document type the workflow does not accept | With different input |
| 429 | rate_limited | Too many requests in the window | Yes, after the window |
| 500 | internal | Something failed on the server; details are logged, not returned | Yes, with backoff |
Handling errors
const res = await fetch(url, init);
if (!res.ok) {
const { error } = await res.json();
switch (error.code) {
case "rate_limited":
case "internal":
return retryWithBackoff();
case "conflict":
return reloadAndDecide();
default:
throw new Error(`${res.status} ${error.code}: ${error.message}`);
}
}Limits
| What | Limit |
|---|---|
| Hosted flow requests | 60 per minute per session token |
| Console sign-in | 10 attempts per minute per IP and email; 20 failed attempts per hour per account |
| Sign-up | 20 per hour per IP, 200 per hour across the service |
| Email verification codes | 10 per hour per address; 50 (sandbox) or 500 (live) per hour per organisation |
| Request body | 40 MB (multipart with several frames) |
| Each upload | 10 MB; JPEG, PNG, WebP or ZIP by content, not extension |
| Image dimensions | 8,192 pixels a side and 24 megapixels, read from the header before decoding |
| Liveness frames | 16 per attempt |
vendor_data · metadata | 256 characters · a JSON object up to 8 KB |
expires_in_hours | 1 to 720 |
page_size | 1 to 100, default 25 |
| Review note | 2,000 characters |
AML full_name | 300 characters |
Rate limits are kept in process memory. The public /v1 API has no per-key rate limit today; if you plan sustained high volumes, tell us first.
Pagination
List endpoints return { data, page, page_size, total }. Request the next page while page * page_size < total.