---
description: >-
  Create a verification session with POST /v1/sessions — userRef, prefill, dataCheck, delivery mode, language, and capture attempts — plus GET /v1/sessions/{sessionId} and the standalone document-number format validator.
---

# 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

```bash
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" }
}'
```

```json
{
  "sessionId": "9f1c8e42-7c3e-4f2b-9d7a-2b1e5c8f4a10",
  "status": "pending",
  "widgetUrl": "https://{FQDN}/verify/8Kd2mQ…"
}
```

#### Request Fields

| Field | Type | Required | Description |
| ----- | ---- | -------- | ----------- |
| `userRef` | string (1-255) | **Yes** | Your identifier for the person being verified. Echoed back in every result |
| `prefill` | object | No | Known data about the person — see [Prefill](#prefill) |
| `dataCheck` | boolean | No | `false` (default) verifies the document as presented. `true` additionally compares extracted data against `prefill` — see [Data Checks](#data-checks) |
| `delivery` | object | No | How the result reaches you. Defaults to `{"mode": "webhook"}` |
| `language` | string | No | Widget language, `xx` or `xx-XX` (e.g. `en`, `pt`, `es`, `pt-BR`). Falls back to your configured default |
| `maxCaptureAttempts` | integer (1-10) | No | Overrides your tenant default for this session only |

#### Response Fields

| Field | Description |
| ----- | ----------- |
| `sessionId` | Use this to correlate results and to call `GET /v1/sessions/{sessionId}` |
| `status` | Always `pending` on creation |
| `widgetUrl` | The 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.

| Field | Effect |
| ----- | ------ |
| `documentType` | `passport`, `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 |
| `documentNumber` | Validated for format at this call (see below), and available for cross-checking |
| `issuingState` | US state code or name for driver's licenses; ISO 3166-1 alpha-3 country for passports and identity cards. Sharpens format validation |
| `nationality` | ISO 3166-1 alpha-3. Used to resolve the jurisdiction when `issuingState` is absent |
| `firstName`, `lastName` | Available for cross-checking |
| `dateOfBirth` | `YYYY-MM-DD`. Available for cross-checking |

```json
{
  "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](document-number-validator.md).

***

### Data Checks

Set `dataCheck: true` to verify that the document belongs to the person you expected — not just that the document is genuine.

```json
{
  "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 soft check, routing the session to `in_review` rather than declining it.

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

| Mode | Fields | Behavior |
| ---- | ------ | -------- |
| `webhook` (default) | — | The result is `POST`ed to your configured `webhookUrl` |
| `redirect` | `redirectUrl` (**required**) | The customer is returned to your URL with the outcome and a signature |

```json
{ "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](results.md) for signature verification in both modes.

***

### Error Responses

All of these return `422` with a `detail` explaining the specific cause:

| Cause | Notes |
| ----- | ----- |
| `delivery.mode` is `webhook` but no `webhookUrl` is configured | Ask Inyo to register your endpoint, or use redirect mode |
| `delivery.redirectUrl` missing in redirect mode | Required whenever `mode` is `redirect` |
| `documentType` is not enabled for your tenant | The message lists the document types you may request |
| The document's issuing jurisdiction is not accepted for that document type | The message lists the jurisdictions you accept |
| `prefill.documentNumber` failed format validation | The message names the rule that rejected it |
| `dataCheck: true` with no prefill payload | Provide at least one comparable prefill field |

Authentication and authorization failures return `401` or `403` — see [Authentication](authentication.md#error-responses).

***

### Retrieve a Session

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

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

```bash
curl --request GET \
  --url https://{FQDN}/v1/sessions/$SESSION_ID \
  --header "Authorization: Bearer $ACCESS_TOKEN"
```

```json
{
  "sessionId": "9f1c8e42-7c3e-4f2b-9d7a-2b1e5c8f4a10",
  "userRef": "user-123",
  "status": "in_review",
  "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"
}
```

| Field | Description |
| ----- | ----------- |
| `status` | `pending`, `approved`, `declined`, `in_review`, or `expired` |
| `step` | How far the customer got: `document_front`, `document_back`, `selfie`, or `done` |
| `deliveryMode` | `webhook`, `redirect`, or `sync` (a [server-to-server](server-to-server.md) verification) |
| `result` | The 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

* [Document Number Validator](document-number-validator.md) — check a number before you get here
* [Widget Delivery](widget-delivery.md) — getting the customer through the flow
* [Receiving Results](results.md) — webhook and redirect handling
