Skip to content

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

  1. not_started

  2. in_progress

  3. 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.

Every status a session row can hold, and what moves it.
StatusMeaningFinal
not_startedLink created, consent not yet givenNo
in_progressConsent given; the person is completing stepsNo
processingSubmitted; background checks and the decision are runningNo
approvedEvery check passed (or a reviewer approved)Yes
declinedA check failed (or a reviewer declined)Yes
in_reviewA check needs a person; another event follows when decidedYes, until reviewed
expiredThe link's lifetime passed before submitYes
abandonedNever usedYes

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_updated per 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_id and status to callback_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

GET /v1/sessions/{id}/decision · json
{
  "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.