curl -X GET 'https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/decision/' \
-H 'x-api-key: YOUR_API_KEY' \
-H 'Accept: application/json'import requests
session_id = "11111111-2222-3333-4444-555555555555"
url = f"https://verification.didit.me/v3/session/{session_id}/decision/"
headers = {
'x-api-key': 'YOUR_API_KEY',
"Accept": "application/json",
}
response = requests.get(url, headers=headers, timeout=15)
response.raise_for_status()
decision = response.json()
print("session_kind:", decision["session_kind"])
print("top-level status:", decision["status"])
# nfc_verifications is ALWAYS an array — never a singular `nfc` field.
for nfc in decision.get("nfc_verifications") or []:
print("nfc node_id:", nfc["node_id"], "status:", nfc["status"])
for id_check in decision.get("id_verifications") or []:
print("id node_id:", id_check["node_id"], "status:", id_check["status"])const sessionId = '11111111-2222-3333-4444-555555555555';
const response = await fetch(
`https://verification.didit.me/v3/session/${sessionId}/decision/`,
{
method: 'GET',
headers: {
'x-api-key': 'YOUR_API_KEY',
Accept: 'application/json',
},
}
);
if (!response.ok) {
throw new Error(`Decision fetch failed: ${response.status}`);
}
const decision = await response.json();
console.log('session_kind:', decision.session_kind);
console.log('top-level status:', decision.status);
// nfc_verifications is ALWAYS an array — never a singular `nfc` field.
for (const nfc of decision.nfc_verifications ?? []) {
console.log('nfc node_id:', nfc.node_id, 'status:', nfc.status);
}
for (const idCheck of decision.id_verifications ?? []) {
console.log('id node_id:', idCheck.node_id, 'status:', idCheck.status);
}<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://verification.didit.me/v3/session/{sessionId}/decision/",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"x-api-key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://verification.didit.me/v3/session/{sessionId}/decision/"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("x-api-key", "<api-key>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://verification.didit.me/v3/session/{sessionId}/decision/")
.header("x-api-key", "<api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("https://verification.didit.me/v3/session/{sessionId}/decision/")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["x-api-key"] = '<api-key>'
response = http.request(request)
puts response.read_bodyRetrieve Session
Retrieve the complete decision report for a verification session — the rolled-up session status plus one report per verification feature the workflow executed. This is the canonical read endpoint for both User Verification (KYC) and Business Verification (KYB) sessions: the same path serves both, and the top-level session_kind discriminator tells you which shape you received ("user" or "business").
Every feature report is a plural array. V3 workflows are graphs that can run the same feature more than once, so each block (id_verifications, nfc_verifications, liveness_checks, face_matches, poa_verifications, document_ai_documents, phone_verifications, email_verifications, aml_screenings, ip_analyses, database_validations, questionnaire_responses) is a JSON array whose items each carry a node_id identifying the workflow graph node that produced them. Read NFC as response.nfc_verifications[0] — never as a singular nfc field. A block is null until the workflow has run that feature at least once; features the workflow does not include never appear with data.
When to call. The recommended pattern is event-driven: wait for the status.updated webhook that fires when the session reaches a decision (Approved, Declined, In Review), then call this endpoint once to fetch the full report. Polling also works — the endpoint can be called at any point in the session lifecycle and returns the data produced so far — but webhook-then-fetch is cheaper and faster. Media URLs in the response (document images, videos, PDFs) are short-lived presigned links: fetch them promptly or re-request the decision to get fresh ones, and do not persist them as long-term references.
Statuses. The top-level status is the session lifecycle status (Not Started, In Progress, Awaiting User, In Review, Approved, Declined, Resubmitted, Expired, Kyc Expired, Abandoned). Each feature item additionally carries its own feature-level status (Not Finished, Approved, Declined, In Review, …). A session can be Approved overall while an individual feature is In Review, depending on the workflow’s decision rules.
Authentication and visibility. Authenticate with the application API key in the x-api-key header (a console user Bearer token with the read:sessions permission also works). Only sessions owned by the authenticated application are visible. If your workflow restricts response attributes, non-allowed fields inside feature items are returned as null while status, warnings, and node_id are always preserved.
Rate limits. GET requests are limited to 600 per minute per API key. Exceeding the limit returns 429 with Retry-After and X-RateLimit-* headers.
curl -X GET 'https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/decision/' \
-H 'x-api-key: YOUR_API_KEY' \
-H 'Accept: application/json'import requests
session_id = "11111111-2222-3333-4444-555555555555"
url = f"https://verification.didit.me/v3/session/{session_id}/decision/"
headers = {
'x-api-key': 'YOUR_API_KEY',
"Accept": "application/json",
}
response = requests.get(url, headers=headers, timeout=15)
response.raise_for_status()
decision = response.json()
print("session_kind:", decision["session_kind"])
print("top-level status:", decision["status"])
# nfc_verifications is ALWAYS an array — never a singular `nfc` field.
for nfc in decision.get("nfc_verifications") or []:
print("nfc node_id:", nfc["node_id"], "status:", nfc["status"])
for id_check in decision.get("id_verifications") or []:
print("id node_id:", id_check["node_id"], "status:", id_check["status"])const sessionId = '11111111-2222-3333-4444-555555555555';
const response = await fetch(
`https://verification.didit.me/v3/session/${sessionId}/decision/`,
{
method: 'GET',
headers: {
'x-api-key': 'YOUR_API_KEY',
Accept: 'application/json',
},
}
);
if (!response.ok) {
throw new Error(`Decision fetch failed: ${response.status}`);
}
const decision = await response.json();
console.log('session_kind:', decision.session_kind);
console.log('top-level status:', decision.status);
// nfc_verifications is ALWAYS an array — never a singular `nfc` field.
for (const nfc of decision.nfc_verifications ?? []) {
console.log('nfc node_id:', nfc.node_id, 'status:', nfc.status);
}
for (const idCheck of decision.id_verifications ?? []) {
console.log('id node_id:', idCheck.node_id, 'status:', idCheck.status);
}<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://verification.didit.me/v3/session/{sessionId}/decision/",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"x-api-key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://verification.didit.me/v3/session/{sessionId}/decision/"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("x-api-key", "<api-key>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://verification.didit.me/v3/session/{sessionId}/decision/")
.header("x-api-key", "<api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("https://verification.didit.me/v3/session/{sessionId}/decision/")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["x-api-key"] = '<api-key>'
response = http.request(request)
puts response.read_bodyKYC and KYB support
This endpoint works for both User Verification (KYC) and Business Verification (KYB) sessions. The response always includes a top-levelsession_kind field:
"user"— User Verification (KYC) session. Response carriesid_verifications,liveness_checks,face_matches,nfc_verifications,poa_verifications,database_validations."business"— Business Verification (KYB) session. Response carriesregistry_checks,document_verifications,key_people_checks.
aml_screenings, phone_verifications, email_verifications, questionnaire_responses, ip_analyses, document_ai_documents, reviews, contact_details — plus the common top-level fields (session_id, session_number, session_url, status, workflow_id, vendor_data, metadata, callback, features, expected_details, created_at, expires_at).
Didit resolves the session_id against both session types (User Verification first, Business Verification second) and returns whichever matches. UUIDs are unique per application — no ambiguity.
See sessions overview for the full matrix of kind-specific features.
Examples
- User Verification (KYC)
- Business Verification (KYB)
curl https://verification.didit.me/v3/session/4c5c7f3a-1f82-4f3b-8d8e-1a8d2d2f9b7a/decision/ \
-H "x-api-key: YOUR_API_KEY"
{
"session_id": "4c5c7f3a-1f82-4f3b-8d8e-1a8d2d2f9b7a",
"session_kind": "user",
"session_number": 1024,
"session_url": "https://verify.didit.me/...",
"status": "APPROVED",
"workflow_id": "wf_kyc_standard",
"vendor_data": "user-42",
"features": "ID_VERIFICATION,LIVENESS,FACE_MATCH,AML",
"id_verifications": [
{ "node_id": "feature_ocr_1", "status": "Approved", "document_type": "Passport", "full_name": "John Doe", "barcodes": [], "...": "..." }
],
"liveness_checks": [
{ "node_id": "feature_liveness_1", "status": "Approved", "score": 0.98 }
],
"face_matches": [
{ "node_id": "feature_face_match_1", "status": "Approved", "score": 0.94 }
],
"aml_screenings": [
{ "node_id": "feature_aml_1", "status": "Approved", "total_hits": 0, "score": 0 }
],
"phone_verifications": [],
"email_verifications": [],
"questionnaire_responses": [],
"ip_analyses": [],
"reviews": [],
"contact_details": { "email": "alex.sample@example.com", "phone": null },
"expected_details": null,
"metadata": {},
"callback": null,
"created_at": "2026-04-16T10:00:00Z",
"expires_at": "2026-04-23T10:00:00Z"
}
curl https://verification.didit.me/v3/session/bs-01HJX1.../decision/ \
-H "x-api-key: YOUR_API_KEY"
{
"session_id": "bs-01HJX1...",
"session_kind": "business",
"session_number": 89,
"session_url": "https://verify.didit.me/...",
"status": "APPROVED",
"workflow_id": "wf_kyb_standard",
"vendor_data": "biz-acme-001",
"features": ["KYB_REGISTRY", "KYB_COMPANY_AML", "KYB_DOCUMENTS", "KYB_KEY_PEOPLE"],
"registry_checks": [
{
"status": "Approved",
"node_id": "feature_kyb_registry",
"data_resolved": true,
"company": {
"uuid": "...",
"node_id": "feature_kyb_registry",
"status": "Approved",
"registry_status": "active",
"company_name": "Acme Corporation Limited",
"registration_number": "12345678",
"country_code": "GBR",
"company_type": "Private Limited",
"incorporation_date": "2010-05-14",
"registered_address": "1 Main Street, London EC1A 1AA, United Kingdom",
"tax_number": "SAMPLE-TAX-12345",
"risk_level": "LOW",
"verification_status": "verified",
"is_from_registry": true,
"fetch_status": "resolved",
"alternative_names": ["Acme Ltd"],
"nature_of_business": "Software publishing",
"registered_capital": "100000 GBP",
"website": "https://acme.example",
"email": "alex.sample@example.com",
"phone": "+15550101000",
"legal_entity_identifier": "529900XXXXXXXXXXXXX",
"financial_summary": {},
"officers": [
{ "uuid": "...", "name": "Jane Doe", "role": "director", "nationality": "USA", "is_active": true, "kyc_status": "Approved", "kyc_session_url": "https://verify.didit.me/..." }
],
"beneficial_owners": [
{ "uuid": "...", "name": "John Smith", "entity_type": "person", "roles": ["ubo"], "ownership_min_shares": 40, "ownership_max_shares": 40, "kyc_status": "Pending", "effective_ownership_percent": 40.0 }
],
"addresses": [],
"industries": [],
"accounts": [],
"registry_data": { },
"user_provided_data": { },
"confirmed_by_user_at": "2026-04-16T10:10:00Z",
"is_editable": false
},
"ownership_structure": { },
"warnings": []
}
],
"aml_screenings": [
{
"node_id": "feature_kyb_company_aml",
"status": "Approved",
"total_hits": 0,
"score": 0,
"entity_type": "COMPANY",
"hits": []
}
],
"document_verifications": [
{
"status": "Approved",
"node_id": "feature_kyb_documents",
"items": [
{ "uuid": "...", "document_type": "certificate_of_incorporation", "status": "Approved", "file_url": "https://...", "ocr_data": { } }
],
"groups": { "legal_presence": { "approved": 1, "pending": 0, "missing": 0 } },
"required_groups": ["legal_presence", "ownership_structure"],
"warnings": []
}
],
"key_people_checks": [
{
"status": "Approved",
"node_id": "feature_kyb_key_people",
"officers": [
{ "uuid": "...", "name": "Jane Doe", "role": "director", "kyc_status": "Approved", "kyc_session_url": "..." }
],
"beneficial_owners": [
{ "uuid": "...", "name": "John Smith", "roles": ["ubo"], "ownership_min_shares": 40, "ownership_max_shares": 40, "kyc_status": "Pending" }
],
"registry": {
"officers": [
{ "uuid": "...", "name": "Jane Doe", "role": "director", "kyc_status": "Approved" }
],
"beneficial_owners": [
{ "uuid": "...", "name": "John Smith", "roles": ["ubo"], "ownership_min_shares": 40, "kyc_status": "Pending" }
]
},
"submitted": {
"parties": [
{ "uuid": "...", "entity_type": "person", "name": "Alice Chen", "role": "ubo", "ownership_percent": 35.0, "requires_verification": true, "kyc_session_status": "Not Started", "kyc_session_url": "https://verify.didit.me/..." }
]
},
"ubo_kyc_summary": {
"total": 2,
"approved": 1,
"flagged": 0,
"pending": 1
},
"warnings": []
}
],
"phone_verifications": null,
"email_verifications": null,
"questionnaire_responses": null,
"ip_analyses": null,
"reviews": [],
"contact_details": { "email": "alex.sample@example.com", "email_lang": "en", "send_notification_emails": true, "phone": null },
"expected_details": null,
"metadata": {},
"callback": null,
"created_at": "2026-04-16T10:00:00Z",
"expires_at": "2026-04-23T10:00:00Z"
}
Handling the response on your side
const res = await fetch(`${BASE}/v3/session/${sessionId}/decision/`, {
headers: { "x-api-key": API_KEY },
});
const decision = await res.json();
switch (decision.session_kind) {
case "user":
handleUserDecision(decision);
break;
case "business":
handleBusinessDecision(decision);
break;
}
Workflow routing events
?include=events adds the session’s event timeline. Alongside the step and status
changes, a workflow that routes through branch nodes leaves one event per routing
decision, so an integration (or the Console timeline) can explain why a session ended
on a given outcome without replaying the workflow graph by hand:
event_type | When it is written | details |
|---|---|---|
WORKFLOW_BRANCH_EVALUATED | A node with branch conditions was evaluated while the session advanced (branch nodes, and feature or webhook nodes with branches) | node_id, node_label, node_type, matched_branch_id (the branch that matched, or null when none did), fallback (true when no condition matched and the node’s default edge was taken), next_node_id, next_node_label |
WORKFLOW_STATUS_NODE_REACHED | A status node was executed | node_id, node_label, session_status (the configured value, Determine included), resolved_session_status (what it resolved to), feature_statuses ([{target, status}]) |
sent_at in milliseconds like every other event and have no
component, feature_step or step: they come from the workflow engine, not from
the user’s device. An “else” branch (a branch with no conditions) counts as a matched
branch; fallback is only true when nothing matched. Nodes with a single outgoing
edge make no decision and leave no event.
The same shape also reports workflow_terminal_node_id: the node_id of the last
recorded workflow status node that set a session status, or null if no such node ID
has been recorded. This is historical workflow provenance. Manual approvals,
declines and resubmissions do not clear it; a later workflow status node replaces it.
For example, if a workflow sends a session to an In Review node and a reviewer
later approves it, the current status is Approved while
workflow_terminal_node_id still identifies the In Review node. It can also remain
populated while a resubmission is in progress. Read the current status and the
event and activity_reviews timelines to distinguish workflow outcomes from later
manual decisions.
{
"event_type": "WORKFLOW_BRANCH_EVALUATED",
"sent_at": 1789336414007,
"details": {
"node_id": "branch-d57da8aa",
"node_label": "Credential required?",
"node_type": "branch",
"matched_branch_id": null,
"fallback": true,
"next_node_id": "status-6ba0bd0d",
"next_node_label": "In Review"
}
}
ID Verification methods
Eachid_verifications[] item says which of the three ID Verification methods produced it, so you can tell a document read apart from a register lookup or a wallet sign-in without guessing from the fields that happen to be populated:
| Field | Value |
|---|---|
verification_method | document, id_lookup or wallet |
assurance | documentary, data_match or cryptographic |
wallet_provider | Catalog wallet id when a wallet produced it, otherwise null |
id_lookup | Registry evidence — source, attempts, per-field comparison, registry portrait — or null |
wallet_verification | Credential evidence — provider, issuing authority, level of assurance, signature validity, shared attributes — or null |
fallback_from | Why a non-document method ended the ID step, when it declined the session |
document and documentary and leaves the rest null, so existing integrations need no change. Field-by-field types are in Data models.
Errors
| Status | Reason |
|---|---|
404 | No session with that session_id exists for this application (both session types are checked). |
403 | Missing read:sessions privilege. |
401 | Invalid API key. |
Related
- Sessions overview — kind discrimination and feature matrix.
- KYB response schema — field-by-field business decision reference.
- Verification statuses — status machine shared by both kinds.
- Webhooks — subscribe to
status.updatedfor both User and Business Verification sessions (filter onsession_kind). - ID Verification methods — document capture, non-doc lookup and digital ID wallets, and what each one puts on the session.
Authorizations
Path Parameters
UUID of the verification session whose decision you want to retrieve. This is the session_id returned by POST /v3/session/. The same path works for both User Verification (KYC) and Business Verification (KYB) sessions — the server resolves the id against both session types.
Must be a valid UUID. The route only matches well-formed UUIDs, so a malformed value is rejected by the URL router before any application code runs and the response is a 404 HTML page rather than the JSON {"detail": ...} envelope shown below. Validate the UUID format client-side so you can distinguish an input error from a genuine missing-session 404.
"11111111-2222-3333-4444-555555555555"
Query Parameters
Comma-separated list of extra payload sections. The only supported value is events. When include=events is passed, the response additionally contains the session event timeline (events, including the WORKFLOW_BRANCH_EVALUATED / WORKFLOW_STATUS_NODE_REACHED routing events described on the Retrieve Session page), the full activity log (activity_reviews), blocklist flags (blocklisted), a per-session cost_breakdown, workflow metadata (workflow_type, workflow_version, workflow_version_id, and workflow_terminal_node_id - the last recorded workflow status node that set a session status, or null if none has been recorded; historical provenance that is not cleared by manual approvals, declines or resubmissions, and is replaced by a later workflow status node), and session metadata (api_service — string or null, set only for standalone-API sessions; session_type — API, HOSTED, or MIGRATED; has_device_nfc_support — boolean). Two fields change relative to the plain shape: the top-level session_kind discriminator is omitted (the events shape only exists for user sessions), and the features array switches from plain strings to {feature, node_id} objects — where the email feature is reported as EMAIL instead of EMAIL_VERIFICATION. This expanded shape is primarily used by the Didit Console; most API integrations should omit the parameter. KYB limitation: include=events only applies to user (KYC) sessions. Business sessions (session_kind = "business") always return the standard business decision shape — events, activity_reviews, blocklisted, cost_breakdown and the extra workflow_*/session metadata are never returned for business sessions, and their features array stays a plain string array.
events "events"
Response
Full decision report. session_kind discriminates the two shapes: user sessions carry the KYC feature arrays shown below; business sessions instead carry registry_checks, document_verifications and key_people_checks alongside the shared arrays. Feature arrays are null until the workflow has run that feature; media URLs are short-lived presigned links.
The unique identifier of the session for which to retrieve verification results.
Discriminator indicating whether this is a User Verification (KYC) or Business Verification (KYB) session. When user, the KYC feature arrays (id_verifications, nfc_verifications, liveness_checks, face_matches, poa_verifications, document_ai_documents, database_validations, ...) apply and the business arrays are absent. When business, the payload instead carries registry_checks, document_verifications, key_people_checks plus the shared arrays (aml_screenings, phone_verifications, email_verifications, ip_analyses, questionnaire_responses). Omitted when include=events is passed — the events shape only exists for user sessions, so the discriminator is dropped from that variant.
user, business The number of the session.
The URL of the session.
Overall lifecycle status of the session — the rolled-up workflow result. Terminal values are Approved, Declined, In Review, Expired, Kyc Expired, and Abandoned; Not Started, In Progress, Awaiting User, and Resubmitted indicate the session is still moving through the workflow.
Not Started, In Progress, Awaiting User, In Review, Approved, Declined, Resubmitted, Expired, Kyc Expired, Abandoned Stable identifier of the workflow group that produced this session. Workflows are versioned — this value identifies the workflow itself, not the specific version.
List of verification features configured for this session, as display names: ID_VERIFICATION, NFC, LIVENESS, FACE_MATCH, POA, PHONE, EMAIL_VERIFICATION, AML, IP_ANALYSIS, DATABASE_VALIDATION, QUESTIONNAIRE, AGE_ESTIMATION, FACE_SEARCH (Business Verification sessions use the KYB feature set: KYB_REGISTRY, KYB_DOCUMENTS, KYB_KEY_PEOPLE, AML, PHONE, EMAIL_VERIFICATION, IP_ANALYSIS, QUESTIONNAIRE). With include=events each entry becomes an object {feature, node_id} instead of a plain string, and the email feature is renamed: it appears as EMAIL in events mode instead of the EMAIL_VERIFICATION used here.
The feature of the session.
The vendor data of the session.
The metadata of the session.
Expected details supplied at session creation — used for cross-validation against extracted data (KYC) or to pre-fill the company registry search (KYB). Echoes back exactly the keys that were provided; null when none were supplied. KYC sessions carry the person fields; Business (KYB) sessions carry company_name, registry_country and registration_number.
Show child attributes
Show child attributes
Contact details supplied when the session was created (used for notification emails, prefilled phone/email verification, and communication language). null when neither an email nor a phone number was provided.
Show child attributes
Show child attributes
The callback URL of the session.
Array of ID verification (OCR) reports produced by ID-document steps in the workflow graph. Always a JSON array — read id_verifications[0] and iterate if your workflow can run more than one ID check (for example a primary check plus a step-up). null until at least one ID step has produced data. Each item is the full report, including node_id, feature-level status, extracted document fields, image URLs, quality scores, MRZ, address parsing, cross-session matches, and warnings[].
Show child attributes
Show child attributes
Why NFC chip verification did not run, populated whenever the workflow includes an NFC step. null while NFC is pending or when the chip was actually read. Values: USER_SKIPPED - the user chose to skip on an NFC-capable device (allowed by allow_nfc_skip); DOCUMENT_WITHOUT_CHIP - the document has no readable NFC chip (not an ICAO 9303 electronic document); CHIP_CERTIFICATE_UNAVAILABLE - the issuing country's signing certificate (CSCA) covering the document's issue period is unavailable, so the chip signature cannot be verified; DEVICE_WITHOUT_NFC - the device reported no NFC reader; INTEGRATION_WITHOUT_NFC_ACCESS - the verification ran without a native SDK (e.g. in a web browser), which has no access to the phone's NFC reader; MRZ_KEY_UNAVAILABLE - the chip access key could not be derived from the document's MRZ.
USER_SKIPPED, DOCUMENT_WITHOUT_CHIP, CHIP_CERTIFICATE_UNAVAILABLE, DEVICE_WITHOUT_NFC, INTEGRATION_WITHOUT_NFC_ACCESS, MRZ_KEY_UNAVAILABLE Array of NFC / ePassport reports produced by NFC steps in the workflow graph. This is always a JSON array, never a singular nfc object — read nfc_verifications[0] and iterate if your workflow can run NFC more than once. null until at least one NFC step has produced data. Each item is the full report, including node_id, feature-level status, chip_data, authenticity (SOD + DG integrity), certificate_summary, and warnings[]. When the NFC step was bypassed before it could ever be offered (chipless document, missing certificate, no NFC capability reported, unreadable MRZ key), this stays null and nfc_skip_reason explains why; a skip at the step itself (user tapped skip, or the reader turned out unavailable at scan time) still produces a report with is_nfc_skipped: true.
Show child attributes
Show child attributes
Array of liveness reports produced by liveness steps in the workflow graph. Always a JSON array — never a singular liveness object. null until at least one liveness step has produced data. Each item is the full report, including node_id, feature-level status, method, score, reference_image, video_url, optional age_estimation, cross-session matches[], and warnings[].
Show child attributes
Show child attributes
Array of face-match reports produced by face-match steps in the workflow graph. Always a JSON array — never a singular face_match object. null until at least one face-match step has produced data. Each item is the full report, including node_id, feature-level status, similarity score, source_image, target_image, and warnings[].
Show child attributes
Show child attributes
Array of phone verification reports — always a JSON array, never a singular phone object. null until at least one phone step has produced data. Each item is the full report, including node_id, feature-level status, the phone number and country metadata, lifecycle[], matches[], and warnings[].
Show child attributes
Show child attributes
Array of email verification reports — always a JSON array, never a singular email object. null until at least one email step has produced data. Each item is the full report, including node_id, feature-level status, the email address, breach data, lifecycle[], matches[], and warnings[].
Show child attributes
Show child attributes
Array of Proof of Address (POA) reports — always a JSON array, never a singular poa object. null until at least one POA step has produced data. Each item is the full report, including node_id, feature-level status, the document file URL, issuer, dates, parsed address, and warnings[].
Show child attributes
Show child attributes
Document AI reports grouped by workflow node — always a JSON array, never a singular object. null until at least one Document AI step has produced data. Present only when session_kind = user. Each item is a node group with an aggregate status, the node_id, an items[] array of the per-document results (extracted data keyed by the configured field key), and warnings.
Show child attributes
Show child attributes
Array of questionnaire response reports — always a JSON array, never a singular questionnaire object. null until at least one questionnaire step has produced data. Each item is the full report, including node_id, the questionnaire metadata, sections with form elements, and the user's answers.
Show child attributes
Show child attributes
Array of AML screening reports — always a JSON array, never a singular aml object. null until at least one AML step has produced data. Each item is the full report, including node_id, feature-level status, total_hits, entity_type, hits[], score, screened_data, is_ongoing_monitoring_enabled, and warnings[].
Show child attributes
Show child attributes
Array of Device & IP Analysis reports — always a JSON array, never a singular ip_analysis object. null until at least one IP-analysis step has produced data. Each item is the full report, including node_id, feature-level status, IP and geolocation data, device fingerprint, and warnings[]. Entries with duplicate (node_id, ip_address, device_fingerprint) are deduplicated server-side.
Show child attributes
Show child attributes
Array of Database Validation reports — always a JSON array, never a singular database_validation object. null until at least one DB validation step has produced data. Each item is the full report, including node_id, feature-level status, screened_data, the per-service validations[] (each with its registry source_data), and warnings[]. The screened_data and source_data fields vary by country:
ARG (Argentina): screened_data: {document_number, selfie, gender, first_name, last_name}. source_data: {identification_number, first_name, last_name, full_name, date_of_birth, date_of_death, tax_id, tax_id_type, face_match_score, sit_1_since, banks, last_position, highest_position, rejected_checks}. Uses RENAPER biometric face-match validation. Gender is auto-inferred from face analysis or given name when not present in the document (e.g., driver licenses)
BOL (Bolivia): screened_data: {document_number, date_of_birth, first_name, last_name}. source_data: {identification_number, first_name, last_name, full_name, date_of_birth, gender}
BRA (Brazil): screened_data: {tax_number, first_name, last_name, date_of_birth}. source_data always includes {identification_number, first_name, last_name, date_of_birth, lgpd_minor, minor_under_18, minor_under_16} for successful CPF matches. Successful adult lookups set those flags to false. The Receita Federal Consulta CPF service may return HTTP 422 (minor under 18) or 451 (under 16) with LGPD-withheld data; in those cases source_data sets the minor flags to true as appropriate, includes the upstream HTTP status, and field-level validation is no_match. See the Brazil section of the Database Validation Outcome Codes reference for the full code list.
CHL (Chile): screened_data: {personal_number, first_name, last_name, date_of_birth}. source_data: {identification_number, first_name, last_name, date_of_birth, gender}
COL (Colombia): screened_data: {document_number, document_type, first_name, last_name}. source_data: {identification_number, first_name, last_name, full_name, date_of_birth, document_type}
CRI (Costa Rica): screened_data: {personal_number, first_name, last_name}. source_data: {identification_number, first_name, last_name, full_name}
DOM (Dominican Republic): screened_data: {personal_number}. source_data: {identification_number} (returns valid/invalid only)
ECU (Ecuador): screened_data: {personal_number, first_name, last_name}. source_data: {identification_number, first_name, last_name, street, formatted_address, gender, nationality, education, marital_status, spouse, mother_name, father_name, profession}
ESP (Spain): screened_data: {personal_number, document_type, expiration_date, first_name, last_name}. source_data: {identification_number, first_name, last_name, full_name, document_type, expiration_date}
GTM (Guatemala): screened_data: {document_number, first_name, last_name}. source_data: {identification_number, first_name, last_name, full_name}
HND (Honduras): screened_data: {document_number, first_name, last_name}. source_data: {identification_number, first_name, last_name, full_name}
MEX (Mexico): screened_data: {personal_number (CURP), first_name, last_name}. source_data: {identification_number, first_name, last_name, full_name, date_of_birth, gender, nationality, curp_status, state_of_birth, doc_probatorio, registration_year, registration_state, registration_municipality, num_acta, crip, issuing_state_code, foreign_registry_number, folio_carta, folio_certificado}. Uses the RENAPO civil registry. A separate INE validity-check service is also available for MEX as an additional one-by-one validation.
PAN (Panama): screened_data: {personal_number, selfie}. source_data: {identification_number}. Uses SIB biometric validation
PER (Peru): screened_data: {personal_number (DNI), first_name, last_name}. source_data: {identification_number, first_name, last_name, paternal_name, maternal_name, verification_number, verification_letter}
PRY (Paraguay): screened_data: {document_number, first_name, last_name}. source_data: {identification_number, first_name, last_name, full_name}
SLV (El Salvador): screened_data: {document_number, date_of_birth, first_name, last_name}. source_data: {identification_number, first_name, last_name, full_name}
URY (Uruguay): screened_data: {personal_number, date_of_birth, first_name, last_name}. source_data: {identification_number, first_name, last_name, full_name}
VEN (Venezuela): screened_data: {document_number, first_name, last_name}. source_data: {identification_number, first_name, last_name, full_name, gender, date_of_birth, document_type}
Show child attributes
Show child attributes
Show child attributes
Show child attributes
Timestamp at which the session was created (ISO 8601, UTC).
Timestamp at which this session is scheduled to expire. Set on User Verification (KYC) sessions when an expiration was configured at creation time (or once Approved status triggers KYC expiry computation). null for sessions without a configured expiration, and for kinds that do not use expiry (e.g. Business Verification (KYB) sessions that never received an expires_at).
Company registry checks. Present only when session_kind = business. Each item wraps the full company-registry payload.
Show child attributes
Show child attributes
Aggregate key-people checks. Present only when session_kind = business. Each item uses the two-bucket shape (registry, submitted, ubo_kyc_summary).
Show child attributes
Show child attributes
Document verification checks grouped by workflow node. Present only when session_kind = business.
Show child attributes
Show child attributes
Whether the session was created by a live application (live) or a sandbox application (sandbox). Sandbox sessions are free, fully simulated, and never bill credits.
live, sandbox