API
Sessions
The life of a session: creating it, following it, deciding it and erasing it.
A session is one verification of one person. It holds a snapshot of its workflow, a hosted-flow link, the files and checks the person produced, and a status.
Creating a session
- workflow_id
- Optional. The app's default workflow is used when omitted.
- vendor_data
- Optional. Your reference, up to 256 characters, echoed in webhooks and filterable.
- callback_url
- Optional. Absolute http(s) URL the person returns to; overrides the workflow's
redirect_url. - metadata
- Optional. A JSON object up to 8 KB, returned with the decision.
- contact.email
- Optional. Pre-fills the email step.
- expires_in_hours
- Optional. 1 to 720; default 168 (7 days).
Sessions can also be created by a reviewer in the console as a verification link; those have created_via: "console". The link token in url is 32 random bytes and only its SHA-256 is stored, so a leaked database does not leak working links.
Statuses
not_started
in_progress
processing
approved
in_review
declined
not_started or in_progress past its expiry becomes expired; a link never used can end as abandoned.
A reviewer (console) or PATCH /v1/sessions/{id}/status moves in_review to approved or declined. Every transition sends a webhook.
| Status | Meaning | Final |
|---|---|---|
not_started | Link created, consent not yet given | No |
in_progress | Consent given; the person is completing steps | No |
processing | Submitted; background checks and the decision are running | No |
approved | Every check passed (or a reviewer approved) | Yes |
declined | A check failed (or a reviewer declined) | Yes |
in_review | A check needs a person; another event follows when decided | Yes, until reviewed |
expired | The link's lifetime passed before submit | Yes |
abandoned | Never used | Yes |
Processing usually takes seconds. Sessions interrupted by a restart are resumed when the API starts again, and one still processing after 5 minutes is resumed the next time its hosted flow is polled, so nobody has to resubmit.
Following a session
- Webhooks (recommended): one
session.status_updatedper transition, signed. See Webhooks. - Polling:
GET /v1/sessions/{id}is cheap. Poll no faster than every few seconds, and stop at a final status. - The return URL: when the hosted flow finishes it appends
session_idandstatustocallback_url. Treat these as hints for your UI; they come from the browser, so confirm with the API or the webhook before acting.
Reading the decision
{
"session_id": "ses_0k3v9x2m4a7qhd8f1rtb",
"status": "approved",
"decision_reason": null,
"workflow_id": "wf_0k3t1c8n5e2wpzr6g4ya",
"vendor_data": "user-42",
"metadata": {},
"identity": {
"full_name": "ANNA MARIA ERIKSSON",
"date_of_birth": "1974-08-12",
"nationality": "UTO",
"document_type": "passport",
"document_number_masked": "L8989****",
"expiry_date": "2032-04-15",
"sources": { "full_name": "mrz", "date_of_birth": "mrz" }
},
"checks": [
{ "kind": "document", "status": "passed", "score": 0.97 },
{ "kind": "liveness", "status": "passed", "score": 0.91 },
{ "kind": "face_match", "status": "passed", "score": 0.612 },
{ "kind": "aml", "status": "passed", "score": 0 }
],
"review": null,
"created_at": "2026-10-03T09:02:11Z",
"submitted_at": "2026-10-03T09:11:58Z",
"completed_at": "2026-10-03T09:12:44Z",
"expires_at": "2026-10-10T09:02:11Z"
}identity.sources records where each field came from. checks are listed in a fixed order: document, aadhaar, pan, email, liveness, face_match, aml, age, duplicate, ip. Each has data (what was measured) and warnings ({ code, message, severity }).
Deciding over the API
Usually in_review sessions are decided in the console's review queue. If your own tooling decides, use PATCH /v1/sessions/{id}/status with approved or declined and an optional note. Only in_review sessions can be decided; anything else returns 409 conflict.
Erasing a session
DELETE /v1/sessions/{id} removes the encrypted files, face embeddings, fingerprints and identity immediately, scrubs warnings, review notes and the Decision inside stored webhook deliveries (so a redelivery cannot resend it), and returns 204. A session that is still open is ended as expired with reason purged, so its link can never write data back; one that is processing returns 409 conflict until the engine has decided. The row remains as a tombstone (purged_at set) so your references, statistics and the audit trail stay consistent. The retention worker does the same automatically after each app's retention_days, for sessions the engine is done with.