Inyo

Verification Sessions

A session represents one identity verification. Creating it returns a widgetUrl you deliver to your customer, and a sessionId you use to correlate the result.


Create a Session

Endpoint: POST /v1/sessions
Authentication: Bearer token with the sessions scope

curl --request POST \
  --url https://{FQDN}/v1/sessions \
  --header "Authorization: Bearer $ACCESS_TOKEN" \
  --header 'Content-Type: application/json' \
  --data '{
  "userRef": "user-123",
  "language": "es",
  "delivery": { "mode": "webhook" }
}'
{
  "sessionId": "9f1c8e42-7c3e-4f2b-9d7a-2b1e5c8f4a10",
  "status": "pending",
  "widgetUrl": "https://{FQDN}/verify/8Kd2mQ…"
}

Request Fields

FieldTypeRequiredDescription
userRefstring (1-255)YesYour identifier for the person being verified. Echoed back in every result
prefillobjectNoKnown data about the person β€” see Prefill
dataCheckbooleanNofalse (default) verifies the document as presented. true additionally compares extracted data against prefill β€” see Data Checks
deliveryobjectNoHow the result reaches you. Defaults to {"mode": "webhook"}
languagestringNoWidget language, xx or xx-XX (e.g. en, pt, es, pt-BR). Falls back to your configured default
maxCaptureAttemptsinteger (1-10)NoOverrides your tenant default for this session only. Applies per capture type β€” this many document attempts and this many selfie attempts

Response Fields

FieldDescription
sessionIdUse this to correlate results and to call GET /v1/sessions/{sessionId}
statusAlways pending on creation
widgetUrlThe customer-facing link. Contains a single-use code valid for 48 hours

Prefill

prefill carries what you already know about the person. It has two distinct effects.

FieldEffect
documentTypepassport, drivers_license, or identity_card. Locks the widget to this document β€” the type-selection screen is skipped, and a different document is rejected. Omit it to let the customer choose
documentNumberValidated for format at this call (see below), and available for cross-checking
issuingStateThe subdivision only β€” a US state code or name. Sharpens format validation for a licence. Not a country: a passport or identity card evidences no subdivision, so an alpha-3 sent here still resolves the jurisdiction but is never compared against the document. Name the country in issuingCountry
issuingCountryISO 3166-1 alpha-3, a country name, or a common alias. Names the issuing country directly β€” a licence carries none on its face, so without this a non-US one is checked against US tables
nationalityISO 3166-1 alpha-3. Used to resolve the jurisdiction when issuingState is absent. Never used for a licence β€” nationality is not issuance
firstName, lastNameAvailable for cross-checking
dateOfBirthYYYY-MM-DD. Available for cross-checking
{
  "userRef": "user-123",
  "prefill": {
    "documentType": "drivers_license",
    "issuingState": "PA",
    "documentNumber": "31612967",
    "firstName": "Ana",
    "lastName": "Silva",
    "dateOfBirth": "1988-03-04"
  }
}

Prefill fields other than documentType do not change the decision unless you set dataCheck: true. Without it, the values are carried for comparison and reported in the result, but a mismatch does not route the session anywhere.

prefill.documentNumber is validated at this call, however: a number that violates its jurisdiction's known format is rejected with 422 rather than accepted and failed later. Check a number before you get here with the validator endpoint.


Data Checks

Set dataCheck: true to verify that the document belongs to the person you expected β€” not just that the document is genuine.

{
  "userRef": "user-123",
  "dataCheck": true,
  "prefill": {
    "firstName": "Ana",
    "lastName": "Silva",
    "dateOfBirth": "1988-03-04"
  }
}

With dataCheck: true:

  • Extracted data is compared field by field against the prefill payload.
  • The result carries prefillComparison (per-field outcomes, including fuzzy name-match scores) and prefillMismatches (the field names that disagreed).
  • A mismatch adds a failing document_data_match check and is reported on the result.

Names are compared with fuzzy matching against a configurable similarity threshold, so ordinary spelling and transliteration variance does not create false mismatches. dataCheck: true without a prefill payload is a 422 β€” there would be nothing to compare against.


Delivery

ModeFieldsBehavior
webhook (default)β€”The result is POSTed to your configured webhookUrl
redirectredirectUrl (required)The customer is returned to your URL with the outcome and a signature
{ "delivery": { "mode": "redirect", "redirectUrl": "https://you.example/kyc-done" } }

Requesting webhook mode without a webhookUrl configured for your tenant is a 422. See Receiving Results for signature verification in both modes.


Error Responses

All of these return 422 with errorCode: "validation_error"; errors[] names the field and the cause:

CauseNotes
delivery.mode is webhook but no webhookUrl is configuredAsk Inyo to register your endpoint, or use redirect mode
delivery.redirectUrl missing in redirect modeRequired whenever mode is redirect
documentType is not enabled for your tenantThe message lists the document types you may request
The document's issuing jurisdiction is not accepted for that document typeThe message lists the jurisdictions you accept
prefill.documentNumber failed format validationThe message names the rule that rejected it
dataCheck: true with no prefill payloadProvide at least one comparable prefill field

Authentication and authorization failures return 401 or 403 β€” see Authentication.


Retrieve a Session

Endpoint: GET /v1/sessions/{sessionId}
Authentication: Bearer token with the sessions scope

This is the authoritative record of a widget session. Read it when you need a guarantee rather than a push, to recover a webhook you may have missed, or to read the outcome of a session that went to manual review.

A verification created with POST /v1/verifications is not served here β€” it has a read of its own at GET /v1/verifications/{verificationId}, reachable with the verifications scope. Passing one here returns 404.

curl --request GET \
  --url https://{FQDN}/v1/sessions/$SESSION_ID \
  --header "Authorization: Bearer $ACCESS_TOKEN"
{
  "sessionId": "9f1c8e42-7c3e-4f2b-9d7a-2b1e5c8f4a10",
  "userRef": "user-123",
  "status": "completed",
  "step": "done",
  "deliveryMode": "webhook",
  "result": { "…": "the normalized result, updated in place" },
  "createdAt": "2026-07-31T14:02:11.481Z",
  "updatedAt": "2026-07-31T14:04:57.902Z"
}
FieldDescription
statuspending, completed, or failed β€” the lifecycle position, never a verdict. The outcome, where you get one, is result.decision
stepHow far the customer got: document_front, document_back, selfie, or done
deliveryModewebhook or redirect β€” how this session's outcome reaches you, chosen at creation
resultThe full normalized result once available, null before then. Updated in place when an analyst decides

Sessions are strictly tenant-scoped. Another tenant's sessionId returns 404 β€” never a partial disclosure.


Next Steps