Skip to main content
A verification session represents one Kayle check you want to run. You create a session via the API, redirect the user to the session’s verification_url, and receive the Kayle result over a webhook when the user finishes, or a verification.session.expired event if they don’t.

The session object

The shape returned by the API:
{
  "id": "vs_mza7vec...",
  "status": "created",
  "contract_version": 1,
  "share_fields": {
    "date_of_birth":     { "required": true,  "reason": "Verify age",        "source": "rc" },
    "kayle_document_id": { "required": true,  "reason": "Sharing \"Kayle Document ID\"", "source": "default" }
  },
  "redirect_url": "https://yourapp.com/verification/done",
  "verification_url": "https://verify.kayle.id/vs_...?cancel_token=...",
  "cancel_token": "...",
  "expires_at": "2026-05-05T12:00:00Z",
  "completed_at": null,
  "created_at": "2026-05-05T11:00:00Z",
  "updated_at": "2026-05-05T11:00:00Z"
}
cancel_token is returned only at creation. The verify URL embeds it as a query parameter so the verify browser and native apps can pass it back when cancelling. If you want to surface a cancel link from somewhere else (a dashboard, an email), store it server-side at creation time.

Lifecycle

A session moves through these statuses:
StatusMeaning
createdThe session exists. The mobile client has not authenticated yet.
in_progressA mobile client is actively performing the Kayle check.
succeededAll Kayle checks confirmed and the user consented to share at least all required fields.
failedA check exhausted its retry budget, or a hard-kill code (anti-cloning attestation) terminated the session.
expired60 minutes elapsed since creation without a terminal outcome.
cancelledThe session was cancelled by you or by the user.
Sessions expire 60 minutes after created_at. A scheduled job sweeps expired sessions and emits verification.session.expired for any that didn’t reach a terminal status first.

Retries

A session has per-check retry budgets — not a single whole-session budget. When a user fails NFC, they don’t have to re-scan MRZ; when they fail liveness, they don’t have to re-tap the chip.
CheckBudget
MRZ scanUnlimited (purely client-side OCR; the server never sees a failed MRZ scan)
NFC chip read3 retries
Liveness capture3 retries
Retries happen entirely inside the mobile app — the user does not re-scan the handoff QR code, and your server is not notified per retry. Only the terminal outcome fires a webhook. The verification.session.failed payload’s data.nfc_tries_used and data.liveness_tries_used tell you which check exhausted. document_anti_cloning_attestation_failed is a hard-kill: the session terminates immediately, regardless of retry counters. The session response exposes the live counters on nfc_tries_used and liveness_tries_used so you can render an “attempts remaining” UI without subscribing to webhooks.

Creating a session

curl -X POST https://api.kayle.id/v1/sessions \
  -H "Authorization: Bearer kk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "redirect_url": "https://yourapp.com/verification/done",
    "share_fields": {
      "date_of_birth": { "required": true, "reason": "Check age eligibility" },
      "family_name":   { "required": true, "reason": "Match account name" }
    }
  }'
share_fields is optional. If you omit it, the session uses Kayle’s default share contract (a minimal set including kayle_document_id). If you include it, every entry needs required and a non-empty reason (200 characters max). See Share fields for the catalogue. redirect_url is also optional, but it must begin with https://. Both fields are reflected back to the user during the consent step, so write reason strings the user will see and trust.

Cancelling

Two cancel paths exist:
  • Authenticated cancelPOST /v1/sessions/:id/cancel with your API key. Idempotent. Use this when you decide server-side that you no longer want the Kayle check to proceed.
  • Public cancelPOST /v1/verify/session/:id/cancel with the one-shot cancel_token in the body. The verify app and native apps use this to abort from the user’s side. The token is consumed on first successful use; subsequent calls return 204 if the session is already terminal, otherwise CANCEL_TOKEN_USED.
A cancelled session emits verification.session.cancelled to subscribed webhooks. The payload carries a reason (cancelled, cancelled_after_failed_check, or one of the privacy_cancelled_after_terminal_* variants when the event replaces a scrubbed succeeded / failed payload) plus the per-check retry counters. See the reference for the full field list.
The verification_url in the create response already contains cancel_token as a query parameter, so you don’t need to surface it yourself unless you’re building a separate cancel UI.

Listing sessions

GET /v1/sessions supports filtering by status, created_from, and created_to, and follows the standard cursor pagination (limit, starting_after, next_cursor). Pass include_attempts=true to attach the attempt array to each session — handy for reporting, but increases payload size.
curl "https://api.kayle.id/v1/sessions?status=completed&limit=50" \
  -H "Authorization: Bearer kk_..."

What you don’t see

The session object never includes raw document data, MRZ fields, the on-chip portrait, selfies, or the face match score. Those are computed and discarded inside the verification pipeline. The consented claims surface only in the verification.session.succeeded webhook payload.