openapi: 3.0.0 info: version: 3.0.0 title: Didit Verification Billing Sessions API description: Identity verification API. Authenticate with x-api-key header. servers: - url: https://verification.didit.me tags: - name: Sessions paths: /v3/session/: post: summary: Create a verification session description: 'Create a User Verification (KYC) or Business Verification (KYB) session and receive a hosted verification `url` plus a `session_token` to redirect or embed for your end user. The `workflow_id` selects which verification steps run and whether the session is KYC or KYB. Call this from your backend (never the browser — the API key is a secret) whenever a user or business needs to be verified: at signup, before a sensitive action, or when re-verification is required. Prerequisites: a published workflow (created in the Console [Workflows](https://docs.didit.me/console/workflows) page) and an application API key with available credits. Sandbox applications bypass the credit check. **Idempotency:** when `vendor_data` is provided and an unfinished session (`Not Started`, `In Progress`, `Resubmitted`, or `Awaiting User`) with the same `vendor_data` already exists on the workflow''s **current latest published version** (and that session already has a hosted verification URL), the existing session is returned (still `201`) instead of creating a duplicate — with its `callback` and `metadata` updated to the newly provided values. An unfinished session created on an older version of the workflow (one that has since been republished) is **not** reused; a new session pinned to the latest published version is created instead. Sessions in `Approved`, `Declined`, `In Review`, `Expired`, `Abandoned`, or `Kyc Expired` are never reused. **Side effects:** the hosted verification URL is generated and stored; if `contact_details.send_notification_emails` is `true` and an email is provided, a verification invite email is sent. The session expires after the workflow''s configured session expiration time. For KYB workflows, `contact_details.phone` and `expected_details` are ignored.' operationId: post_v3_session_create tags: - Sessions responses: '201': description: Session created (or returned, if an unfinished session with the same `vendor_data` already exists on the workflow's latest published version). The response is the serialized session, including the hosted verification `url` to redirect the user to. content: application/json: schema: type: object required: - session_id - session_number - session_token - url - vendor_data - metadata - status - workflow_id - workflow_version - callback properties: session_id: type: string format: uuid description: Unique identifier of the verification session. Use this id when calling `GET /v3/session/{sessionId}/decision/`. example: 11111111-2222-3333-4444-555555555555 session_number: type: integer description: Sequential, human-friendly number assigned to the session inside your application. Useful for support and dashboards. example: 43762 session_token: type: string description: 12-character URL-safe token that authorizes the end user to access the hosted verification flow at `url`. Valid until the session expires. Treat it as a secret — anyone holding it can open the verification flow for this session. example: 3FaJ9wLqX2Mz url: type: string format: uri description: Hosted verification URL to redirect the end user to. The URL embeds the `session_token` and, if configured, uses your white-label domain. When the request includes `language`, the URL contains a language path segment — `https://verify.didit.me/{language}/session/{session_token}` (e.g. `/es/session/...`); without `language`, the segment is omitted. example: https://verify.didit.me/session/3FaJ9wLqX2Mz vendor_data: type: string nullable: true description: Identifier you passed in the request to link the session to a user or business in your own system. Echoed back verbatim. Null when not provided. example: user-123 metadata: description: Arbitrary JSON payload you stored with the session at creation time. Echoed back verbatim — whatever JSON value you sent (object, string, number, array) is returned as-is, though a JSON object is recommended. Not shown to the end user. Always present in responses; `null` when not provided at creation time. example: user_type: premium account_id: ABC123 status: type: string enum: - Not Started - In Progress - Approved - Declined - In Review - Expired - Abandoned - Kyc Expired - Resubmitted - Awaiting User description: Current status of the session. Newly created sessions return `Not Started`. If a non-finished session already existed for the same `vendor_data` on the workflow's latest published version, the returned status reflects that existing session. example: Not Started callback: type: string format: uri nullable: true description: Final redirect URL the user is sent to after completing the flow. Didit appends `?verificationSessionId={session_id}&status={status}` to this URL. Falls back to the workflow's configured callback URL when not provided in the request; `null` when neither is set. example: https://example.com/verification/callback workflow_id: type: string format: uuid description: Stable identifier of the workflow this session runs on. Always the workflow's stable group identifier — even if you referenced a specific workflow version UUID in the request (backward-compatible lookup), the response carries the stable `workflow_id`. Use it to correlate the session with the workflow you configured in the Console. example: 11111111-2222-3333-4444-555555555555 workflow_version: type: integer description: Version number of the published workflow version the session was pinned to at creation. Didit resolves `workflow_id` to the workflow's latest published version when the session is created, and the session keeps running that version even if the workflow is republished later. example: 3 examples: KYC session: summary: KYC session created value: session_id: 11111111-2222-3333-4444-555555555555 session_number: 43762 session_token: 3FaJ9wLqX2Mz url: https://verify.didit.me/en/session/3FaJ9wLqX2Mz vendor_data: user-123 metadata: user_type: premium account_id: ABC123 status: Not Started workflow_id: 11111111-2222-3333-4444-555555555555 workflow_version: 3 callback: https://example.com/verification/callback KYB session: summary: KYB session created value: session_id: 22222222-3333-4444-5555-666666666666 session_number: 43763 session_token: Yk7pQ2vN8aBc url: https://verify.didit.me/en/session/Yk7pQ2vN8aBc vendor_data: company-acme-001 metadata: tier: enterprise status: Not Started workflow_id: 33333333-4444-5555-6666-777777777777 workflow_version: 1 callback: https://example.com/kyb/callback '400': description: 'Bad request — the payload failed validation (missing or unknown `workflow_id`, invalid `language`, blocklisted `vendor_data`, malformed `portrait_image`, `address` without `country`/`poa_country`, invalid `sandbox_scenario`, etc.) or your organization does not have enough credits to start the session. The body is an object keyed by the offending field name (or `detail` for non-field errors). Values are an **array of strings** for input-validation failures (e.g. missing required field, invalid language, oversized portrait image) and a **plain string** for failures detected while creating the session (unknown `workflow_id`, blocklisted `vendor_data`, no reference face available for a Face Match workflow). Handle both shapes.' content: application/json: schema: type: object additionalProperties: true properties: detail: type: string description: Human-readable error message for non-field errors (e.g. insufficient credits). Field-level failures use the field name as the key instead, with a string or array-of-strings value. example: You don't have enough credits to perform this request. Please top up at https://business.didit.me examples: Missing workflow_id: summary: Required field omitted (array-of-strings shape) value: workflow_id: - This field is required. Invalid workflow_id: summary: Unknown workflow_id (plain-string shape) value: workflow_id: Invalid workflow_id. Insufficient credits: summary: Organization is out of credits value: detail: You don't have enough credits to perform this request. Please top up at https://business.didit.me Blocklisted vendor_data: summary: vendor_data is blocklisted value: vendor_data: This user or business has been blocked and cannot create new verification sessions. Portrait image too large: summary: portrait_image fails size validation value: portrait_image: - Image size exceeds 2MB. No stored face for vendor_data: summary: Face Match workflow without portrait_image and no stored face for the vendor_data user value: portrait_image: No stored face image was found for this user. Send a portrait_image, or complete an approved verification with face liveness or an ID document for this vendor_data first. Portrait image required without vendor_data: summary: Face Match workflow without portrait_image and without vendor_data value: portrait_image: A portrait image is required to perform face match for this workflow. Send a portrait_image, or provide the vendor_data of a user with a previously stored face image to reuse it. '403': description: 'Authentication or authorization failed. The endpoint returns `403` whenever the `x-api-key` header is missing, malformed, the API key has expired, or the API key is valid but the client does not have permission to create sessions in this application (for example the API key belongs to a different organization). Re-fetch the application API key via the [Auth API](/auth-api/get-credentials) and retry. **Note:** All four failure modes return the same response body — `{"detail": "You do not have permission to perform this action."}` — and there is no machine-readable discriminator (no error code field, no `WWW-Authenticate` challenge) that lets you tell them apart. If you need to differentiate (for example to surface a more specific error to the end user), check the `x-api-key` header is present and belongs to the intended application before calling this endpoint.' content: application/json: schema: type: object properties: detail: type: string example: You do not have permission to perform this action. examples: Missing or invalid API key: summary: No `x-api-key` header or invalid API key value: detail: You do not have permission to perform this action. No permission: summary: Client cannot create sessions value: detail: You do not have permission to perform this action. '429': description: 'Rate limit exceeded — more than 600 session-create requests in a 60-second window from the same API key / IP. Inspect `Retry-After` and back off before retrying. **Note:** The source code also defines a `FREE_SESSION_RATE_LIMIT` of 10 calls/minute alongside `PAID_SESSION_RATE_LIMIT=600` (`sessions/serializers/session.py`), but that free-tier ceiling is **not applied to this V3 endpoint** — the rate-limit middleware enforces the 600/min `SESSION_CREATE_RATE_LIMIT` regardless of plan. 600/min is the only limit you can hit here.' headers: Retry-After: description: Seconds the caller must wait before retrying the request. schema: type: integer minimum: 1 example: 42 X-RateLimit-Limit: description: Maximum number of requests allowed in the current 60-second window for the limit that was breached (600 for session-create). Absent on the sandbox-quota variant. schema: type: integer example: 600 X-RateLimit-Remaining: description: Number of requests still allowed in the current window for the breached limit. Absent on the sandbox-quota variant. schema: type: integer minimum: 0 example: 0 X-RateLimit-Reset: description: UTC epoch seconds when the current rate-limit window resets. Absent on the sandbox-quota variant. schema: type: integer minimum: 0 example: 1747497600 content: application/json: schema: type: object properties: detail: type: string description: Human-readable explanation of the rate-limit breach. Use this message to distinguish which of the three limits was hit. example: Session creation rate limit exceeded. You can make up to 600 requests per minute. examples: Session-create limit: summary: More than 600 session-create requests per minute value: detail: Session creation rate limit exceeded. You can make up to 600 requests per minute. Sandbox session quota: summary: Sandbox application exceeded 500 sessions in 24 hours value: detail: Sandbox session quota exceeded (500 sessions per 24h). Wait for the window to reset. Expected available in 3600 seconds. security: - ApiKeyAuth: [] x-codeSamples: - lang: curl label: cURL source: "curl -X POST 'https://verification.didit.me/v3/session/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"workflow_id\": \"11111111-2222-3333-4444-555555555555\",\n \"vendor_data\": \"user-123\",\n \"callback\": \"https://example.com/verification/callback\",\n \"callback_method\": \"both\",\n \"metadata\": {\"user_type\": \"premium\", \"account_id\": \"ABC123\"},\n \"language\": \"en\",\n \"contact_details\": {\n \"email\": \"john.doe@example.com\",\n \"send_notification_emails\": true,\n \"email_lang\": \"en\",\n \"phone\": \"+14155552671\"\n },\n \"expected_details\": {\n \"first_name\": \"John\",\n \"last_name\": \"Doe\",\n \"date_of_birth\": \"1990-05-15\",\n \"id_country\": \"USA\",\n \"expected_document_types\": [\"P\", \"ID\"]\n }\n }'" - lang: python label: Python source: "import requests\n\nurl = \"https://verification.didit.me/v3/session/\"\nheaders = {\n 'x-api-key': 'YOUR_API_KEY',\n \"Content-Type\": \"application/json\",\n}\npayload = {\n \"workflow_id\": \"11111111-2222-3333-4444-555555555555\",\n \"vendor_data\": \"user-123\",\n \"callback\": \"https://example.com/verification/callback\",\n \"callback_method\": \"both\",\n \"metadata\": {\"user_type\": \"premium\", \"account_id\": \"ABC123\"},\n \"language\": \"en\",\n \"contact_details\": {\n \"email\": \"john.doe@example.com\",\n \"send_notification_emails\": True,\n \"email_lang\": \"en\",\n \"phone\": \"+14155552671\",\n },\n \"expected_details\": {\n \"first_name\": \"John\",\n \"last_name\": \"Doe\",\n \"date_of_birth\": \"1990-05-15\",\n \"id_country\": \"USA\",\n \"expected_document_types\": [\"P\", \"ID\"],\n },\n}\n\nresponse = requests.post(url, json=payload, headers=headers, timeout=15)\nresponse.raise_for_status()\nsession = response.json()\nprint(session[\"session_id\"], session[\"url\"])" - lang: javascript label: JavaScript source: "const response = await fetch('https://verification.didit.me/v3/session/', {\n method: 'POST',\n headers: {\n 'x-api-key': 'YOUR_API_KEY',\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n workflow_id: '11111111-2222-3333-4444-555555555555',\n vendor_data: 'user-123',\n callback: 'https://example.com/verification/callback',\n callback_method: 'both',\n metadata: { user_type: 'premium', account_id: 'ABC123' },\n language: 'en',\n contact_details: {\n email: 'john.doe@example.com',\n send_notification_emails: true,\n email_lang: 'en',\n phone: '+14155552671',\n },\n expected_details: {\n first_name: 'John',\n last_name: 'Doe',\n date_of_birth: '1990-05-15',\n id_country: 'USA',\n expected_document_types: ['P', 'ID'],\n },\n }),\n});\n\nif (!response.ok) {\n throw new Error(`Session create failed: ${response.status}`);\n}\nconst session = await response.json();\nconsole.log(session.session_id, session.url);" requestBody: required: true content: application/json: schema: type: object required: - workflow_id properties: workflow_id: type: string format: uuid description: Stable identifier of the workflow that defines which verification steps the session will run. Workflows are created and managed in the [Workflows](/console/workflows) page of the Console. The `workflow_id` also implicitly selects whether the session is KYC or KYB. example: 11111111-2222-3333-4444-555555555555 x-readme-id: '0.0' vendor_data: type: string description: 'A unique identifier for the user being verified, such as a UUID, email, or internal user ID. This field is used for: (1) **User grouping** — sessions with the same vendor_data are linked to the same user profile in the Users tab. (2) **Cross-session duplicate detection** — when checking for duplicated faces, documents, phone numbers, emails, IP addresses, or device fingerprints, sessions with the same vendor_data are treated as the same user and excluded from matches. Without vendor_data, every session is treated as a unique user and all potential duplicates are surfaced. We strongly recommend always providing a vendor_data to reduce noise in duplicate detection.' example: user-123 x-readme-id: '0.1' callback: type: string description: URL to redirect the user to after verification completes. Didit automatically appends `verificationSessionId` and `status` (Approved, Declined, In Review) as query parameters. Custom URL schemes (e.g. `myapp://`) are supported for mobile callbacks. If omitted, the workflow's default `callback_url` is used. example: https://example.com/verification/callback x-readme-id: '0.2' callback_method: type: string description: Determines which device should handle the redirect to the provided callback URL. Use `initiator` to redirect only the device that started the flow, `completer` for the device that finishes it, or `both` to allow either device to trigger the callback. If you ever notice the callback not triggering reliably, we recommend setting this value to `both`. enum: - initiator - completer - both default: initiator example: both x-readme-id: '0.3' metadata: description: Arbitrary JSON stored with the session and echoed back in the response and webhooks. Any JSON value is accepted (object, string, number, array), but a JSON object of key/value pairs is recommended. Not shown to the end user. Use it to pass your own correlation ids, A/B variants, or business context. example: user_type: premium account_id: ABC123 x-readme-id: '0.4' language: type: string description: Language code (ISO 639-1) for the verification process interface. Controls the language displayed to the end user during verification. If not provided, the browser's language will be automatically detected and used. Check all the supported languages [here](/integration/supported-languages). enum: - en - ar - bg - bn - bs - ca - cnr - cs - da - de - el - es - et - fa - fi - fr - he - hi - hr - hu - hy - id - it - ja - ka - kk - ko - ky - lt - lv - mk - mn - ms - nl - 'no' - pl - pt-BR - pt - ro - ru - sk - sl - so - sq - sr - sv - th - tr - uk - uz - vi - zh-CN - zh-TW - zh example: en x-readme-id: '0.5' contact_details: type: object description: User contact information that can be used for notifications, prefilling verification forms, and phone verification. This includes email address, preferred language for communications, and phone number. example: email: john.doe@example.com send_notification_emails: true email_lang: en phone: '+14155552671' properties: email: type: string format: email description: Email address of the user (e.g., "john.doe@example.com") that will be used during the [Email Verification](/core-technology/email-verification/overview) step. If not provided, the user must provide it during the verification flow. example: john.doe@example.com x-readme-id: '1.0' send_notification_emails: type: boolean default: false description: If true and an email is provided, Didit sends the initial "Verify your identity" email asynchronously when the User Verification (KYC) or Business Verification (KYB) session is created. Didit also sends verification status notifications for sessions requiring manual review to the provided email address (e.g., from 'In Review' to 'Approved' or 'Declined'). This helps users return to your application once their verification is complete. If you have white-label activated for the session, the email sent will be white-labeled. example: true x-readme-id: '1.1' email_lang: type: string description: Language code (ISO 639-1) for email notifications. Controls the language of all email communications (e.g., "en", "es", "fr"). There is no stored default — when omitted, the verification invite email simply falls back to English (`en`) at send time. The enum below is a snapshot; the live source of truth for accepted values is the [supported languages](/integration/supported-languages) doc — always consult it before relying on a specific code. enum: - en - ar - bg - bn - bs - ca - cnr - cs - da - de - el - es - et - fa - fi - fr - he - hi - hr - hu - hy - id - it - ja - ka - kk - ko - ky - lt - lv - mk - mn - ms - nl - 'no' - pl - pt-BR - pt - ro - ru - sk - sl - so - sq - sr - sv - th - tr - uk - uz - vi - zh-CN - zh-TW - zh example: en x-readme-id: '1.2' phone: type: string description: Phone number in E.164 format (e.g., "+14155552671") that will be used during the [Phone Verification](/core-technology/phone-verification/overview) step. If not provided, the user must provide it during the verification flow. Ignored for Business Verification (KYB) workflows. **Important:** This phone number is only enforced if it is a valid E.164 phone number. If the provided number is invalid or cannot be parsed, it will be ignored and the user will be able to input any valid phone number during the Phone Verification step. example: '+14155552671' x-readme-id: '1.3' x-readme-id: '0.4' expected_details: type: object description: Expected user details used to cross-validate against the data extracted from the user's ID document, Proof of Address, and other verification steps. Mismatches are surfaced as warnings on the decision; some fields (e.g. `id_country`, `expected_document_types`) also alter the user-facing flow. For Business Verification (KYB) workflows only the business fields (`company_name`, `registry_country`, `registration_number`) are used — they pre-fill the company registry search for the user — and all person-level fields are ignored. example: first_name: John last_name: Doe date_of_birth: '1990-05-15' nationality: USA id_country: USA expected_document_types: - P - ID properties: first_name: type: string description: User's first name. For example, `John`. The matching uses fuzzy comparison, and you can tune the strictness by configuring the name match score threshold in the Console for both ID Document and Proof of Address workflows. example: John x-readme-id: '1.0' last_name: type: string description: User's last name. For example, `Doe`. The matching uses fuzzy comparison, and you can tune the strictness by configuring the name match score threshold in the Console for both ID Document and Proof of Address workflows. example: Doe x-readme-id: '1.1' date_of_birth: type: string format: date description: 'User''s date of birth with format: YYYY-MM-DD. For example, `1990-05-15`.' example: '1990-05-15' x-readme-id: '1.2' gender: type: string nullable: true enum: - M - F default: null description: User's gender. Must be either 'M', 'F', or null. example: M x-readme-id: '1.3' nationality: type: string description: ISO 3166-1 alpha-3 country code representing the applicant's country of origin. For example, `USA`. See the [full list of supported country codes](https://docs.didit.me/core-technology/id-verification/supported-documents-id-verification#supported-documents-by-country). example: USA x-readme-id: '1.4' country: type: string description: ISO 3166-1 alpha-3 country code used as a fallback for both ID Verification and Proof of Address country-mismatch checks. Required when `address` is provided and neither `id_country` nor `poa_country` is set. example: USA x-readme-id: 1.4.1 id_country: type: string description: ISO 3166-1 alpha-3 country code representing the expected country of the applicant's ID document, which may differ from nationality. For example, `GBR`. Takes priority over `country` for ID verification country mismatch checks. See the [full list of supported country codes](https://docs.didit.me/core-technology/id-verification/supported-documents-id-verification#supported-documents-by-country). example: GBR x-readme-id: '1.5' poa_country: type: string description: ISO 3166-1 alpha-3 country code representing the expected country of the Proof of Address document. For example, `USA`. Takes priority over `country` for POA country mismatch checks. See the [full list of supported country codes](https://docs.didit.me/core-technology/id-verification/supported-documents-id-verification#supported-documents-by-country). example: USA x-readme-id: 1.5.1 address: type: string description: The address in a human readable format, including as much information as possible. For example, `123 Main St, San Francisco, CA 94105, USA`. When provided, you must also pass `country` or `poa_country`, otherwise the request fails with `400`. example: 123 Main St, San Francisco, CA 94105, USA x-readme-id: '1.6' identification_number: type: string description: The user's document number, personal number, or tax number. For example, `123456789`. example: '123456789' x-readme-id: '1.7' ip_address: type: string description: Expected IP address for the session, in IPv4 or IPv6 format. If the actual IP address differs from this value, a warning will be logged. For example, `192.168.1.100` or `2001:db8::1`. example: 192.168.1.100 x-readme-id: '1.8' expected_document_types: type: array description: 'Restricts the document types the user can present during the ID verification (OCR) step. When set, the document selection screen only shows the requested types and the returned `documents_allowed` is filtered accordingly. Values are case-insensitive and deduplicated; unknown values are rejected with `400`. Allowed codes: `P` (passport), `ID` (national ID), `DL` (driver''s license), `RP` (residence permit), `HIC` (health insurance card), `TC` (tax card), `SSC` (social security card).' items: type: string enum: - P - ID - DL - RP - HIC - TC - SSC example: - P - ID x-readme-id: '1.9' company_name: type: string maxLength: 255 description: Expected legal name of the business being verified. For example, `Tesco PLC`. Pre-fills the company-name field of the registry search in Business Verification (KYB) workflows; when `registration_number` is also provided, the registry lookup combines both identifiers. Ignored for KYC workflows. example: Tesco PLC x-readme-id: '1.10' registry_country: type: string maxLength: 10 description: 'ISO 3166-1 alpha-2 code of the country whose company registry the business is registered in. For example, `GB`. Where company registries operate at state or province level, append the ISO 3166-2 subdivision code as `XX-YY`: `US-CA` targets the California registry. Same format as `country_code` on the [KYB Registry Search API](https://docs.didit.me/standalone-apis/kyb-registry-search). Pre-selects the registry country in Business Verification (KYB) workflows; ignored for KYC workflows.' example: US-CA x-readme-id: '1.11' registration_number: type: string maxLength: 100 description: Expected registration number of the business in the company registry. For example, `00445790`. Pre-fills the registration-number field of the registry search in Business Verification (KYB) workflows; when `company_name` is also provided, the registry lookup combines both identifiers. Ignored for KYC workflows. example: 00445790 x-readme-id: '1.12' x-readme-id: '0.5' portrait_image: type: string format: byte description: 'Base64-encoded portrait image of the end user''s face (max 2MB; JPEG, PNG, WebP, or TIFF). Used as the reference image to match against the liveness capture when the workflow is a `Biometric Authentication` workflow with Face Match enabled, or any graph workflow where Face Match runs before ID Verification (OCR). Optional when the `vendor_data` user already has a stored face image: Didit reuses it automatically, resolved in priority order: approved liveness face, ePassport chip photo, ID document portrait, manually enrolled profile face (only faces from approved sessions or faces you enrolled yourself are reused). When provided, `portrait_image` always takes precedence over the stored face. If omitted and no stored face exists (or `vendor_data` is not sent), session creation fails with `400`. Ignored for other workflow types.' example: iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII= x-readme-id: '0.6' sandbox_scenario: type: string description: Sandbox-only scenario slug. Only accepted when the API key belongs to a sandbox-mode application; rejected on live applications. When set, the create flow expands the scenario's bundled magic values onto the session's input fields (contact_details, expected_details) so the mocked providers reproduce the scenario's outcome through the real workflow pipeline. See the scenario catalog at GET /v1/sandbox/scenarios/ or [Sandbox & Test Data](/integration/sandbox-testing). You can also arm (or re-arm) a scenario after create via POST /v3/session/{sessionId}/sandbox/arm/, as long as the session is still `Not Started`. example: decline_document_expired example: workflow_id: 11111111-2222-3333-4444-555555555555 vendor_data: user-123 callback: https://example.com/verification/callback callback_method: both metadata: user_type: premium account_id: ABC123 language: en contact_details: email: john.doe@example.com send_notification_emails: true email_lang: en phone: '+14155552671' expected_details: first_name: John last_name: Doe date_of_birth: '1990-05-15' id_country: USA expected_document_types: - P - ID examples: Create KYC session: summary: Full happy-path payload for a User Verification (KYC) session description: 'Realistic create call that uses every commonly-set field: workflow selection, vendor linkage, callback routing, metadata, locale, contact prefill, and expected user details. Copy-paste this directly into the cURL sample above.' value: workflow_id: 11111111-2222-3333-4444-555555555555 vendor_data: user-123 callback: https://example.com/verification/callback callback_method: both metadata: user_type: premium account_id: ABC123 language: en contact_details: email: john.doe@example.com send_notification_emails: true email_lang: en phone: '+14155552671' expected_details: first_name: John last_name: Doe date_of_birth: '1990-05-15' nationality: USA id_country: USA expected_document_types: - P - ID Create KYB session: summary: Happy-path payload for a Business Verification (KYB) session description: Same shape as the KYC call — the `workflow_id` is what makes the session KYB. `vendor_data` typically references the company in your system and `contact_details.email` targets the legal representative who will complete the flow. `expected_details` accepts the business fields (`company_name`, `registry_country`, `registration_number`), which pre-fill the company registry search; person-level fields and `contact_details.phone` are ignored for KYB workflows. value: workflow_id: 33333333-4444-5555-6666-777777777777 vendor_data: company-acme-001 callback: https://example.com/kyb/callback callback_method: both metadata: tier: enterprise company_id: ACME-001 expected_details: company_name: Tesco PLC registry_country: GB registration_number: 00445790 language: en contact_details: email: legal-rep@acme.example.com send_notification_emails: true email_lang: en /v3/session/imports/: post: summary: Create a verification import job description: Create an asynchronous import job for historical User Verification sessions, Business Verification sessions, transaction monitoring records, or workflow custom status rules. Use this to migrate from providers such as Sumsub, MetaMap, Veriff, Onfido, Persona, Trulioo, Jumio, Incode, iDenfy, or any other provider after transforming the export into Didit's canonical CSV/NDJSON format. Imports are disabled by default and must be enabled per organization by Didit. operationId: post_v3_session_imports_create tags: - Sessions requestBody: required: true content: multipart/form-data: schema: type: object properties: import_type: type: string enum: - user_verification - business_verification - status_rules - transactions default: user_verification description: 'What to import: historical User Verification sessions, historical Business Verification sessions, workflow custom status rules, or historical transaction monitoring records.' workflow_id: type: string format: uuid description: Workflow UUID that imported sessions or status rules should be attached to. Required for user_verification, business_verification, and status_rules. Omit for transactions. source_format: type: string enum: - csv - ndjson description: Optional. Inferred from the uploaded file extension when omitted. file: type: string format: binary description: Canonical CSV or NDJSON file. Use NDJSON for large imports. Maximum upload size 25 MB (use `source_file_url` for bigger files). Mutually exclusive with `source_file_url`. source_file_url: type: string format: uri description: Alternative to file upload. Must be an HTTPS URL resolving to a public address (private/loopback networks are rejected). Mutually exclusive with `file`. application/json: schema: type: object required: - source_file_url properties: import_type: type: string enum: - user_verification - business_verification - status_rules - transactions default: user_verification description: 'What to import: historical User Verification sessions, historical Business Verification sessions, workflow custom status rules, or historical transaction monitoring records.' workflow_id: type: string format: uuid description: Workflow UUID that imported sessions or status rules should be attached to. Required for user_verification, business_verification, and status_rules. Omit for transactions. source_format: type: string enum: - csv - ndjson description: Optional. Inferred from the URL's file extension when omitted. source_file_url: type: string format: uri description: Alternative to file upload. Must be an HTTPS URL resolving to a public address (private/loopback networks are rejected). Mutually exclusive with `file`. examples: URL-based import: value: import_type: user_verification workflow_id: 9f9b1234-aaaa-bbbb-cccc-1234567890ab source_file_url: https://files.example.com/exports/sessions.ndjson responses: '201': description: Import job accepted. Poll the returned `uuid` until `status` is `COMPLETED` or `FAILED`. content: application/json: schema: $ref: '#/components/schemas/SessionImportJob' examples: Job accepted: value: uuid: 3f9c1a2b-4d5e-4f60-8a7b-9c0d1e2f3a4b status: PENDING import_type: user_verification source_format: ndjson source_filename: sessions.ndjson total_rows: 0 processed_rows: 0 checkpoint_row: 0 created_count: 0 updated_count: 0 skipped_count: 0 failed_count: 0 total_media_bytes: 0 last_error: null created_at: '2026-05-17T08:42:11Z' updated_at: '2026-05-17T08:42:11Z' started_at: null completed_at: null '400': description: Invalid workflow, file type, file size, source URL, or selector combination. content: application/json: examples: Invalid workflow: value: workflow_id: - Invalid workflow_id. Workflow required: value: workflow_id: - This field is required for this import type. Wrong file type: value: file: - Only CSV and NDJSON files are supported. File too large: value: file: - Import file exceeds the maximum allowed size. No source: value: file: - Upload a file or provide source_file_url. Both sources: value: file: - Use either file or source_file_url, not both. '403': description: Imports not enabled, sandbox application, or missing/invalid API key (this endpoint returns `403`, never `401`, for authentication failures). Verification imports are disabled by default and must be enabled per organization by Didit. content: application/json: examples: Imports not enabled: value: detail: Verification imports are not enabled for this organization. Sandbox: value: detail: Verification imports are not enabled for sandbox applications. Bad API key: value: detail: You do not have permission to perform this action. '429': description: The application already has an active import job (PENDING/PROCESSING/PAUSED). Wait for it to finish before starting another. content: application/json: examples: Active job: value: detail: This application already has an active import job. Wait for it to finish before starting another. security: - ApiKeyAuth: [] /v3/session/imports/template/: get: summary: Download verification import template description: Download the canonical CSV header for verification imports. The column set is determined by `import_type`. The first column of session templates is the idempotency key `external_id`. operationId: get_v3_session_imports_template tags: - Sessions parameters: - name: import_type in: query required: false schema: type: string enum: - user_verification - business_verification - status_rules - transactions default: user_verification responses: '200': description: CSV template file. content: text/csv: schema: type: string format: binary '403': description: Missing, invalid, or revoked API key, or the key cannot download import templates. This endpoint returns `403` (never `401`) for authentication failures, including requests with no credentials at all. content: application/json: examples: Forbidden: summary: Missing, invalid, or revoked API key value: detail: You do not have permission to perform this action. security: - ApiKeyAuth: [] /v3/session/imports/{importId}/: get: summary: Retrieve a verification import job description: Retrieve import progress counters and terminal status for one job. Poll until `status` is `COMPLETED` or `FAILED`; `failed_count` > 0 means some rows were rejected — list them via the errors endpoint. operationId: get_v3_session_imports_retrieve tags: - Sessions parameters: - name: importId in: path required: true schema: type: string format: uuid responses: '200': description: Import job status. content: application/json: schema: $ref: '#/components/schemas/SessionImportJob' '403': description: Missing, invalid, or revoked API key, or the key cannot read import jobs for this application. This endpoint returns `403` (never `401`) for authentication failures, including requests with no credentials at all. content: application/json: examples: Forbidden: summary: Missing, invalid, or revoked API key value: detail: You do not have permission to perform this action. '404': description: Import job not found for this application. content: application/json: examples: Not found: value: detail: Not found. security: - ApiKeyAuth: [] /v3/session/imports/{importId}/errors/: get: summary: List verification import row errors description: List row-level errors for a verification import job, ordered by `row_number`. Use this endpoint to produce a corrected retry file containing only failed rows. operationId: get_v3_session_imports_errors tags: - Sessions parameters: - name: importId in: path required: true schema: type: string format: uuid - name: limit in: query required: false schema: type: integer default: 50 minimum: 1 description: Page size. Defaults to 50 when omitted. - name: offset in: query required: false schema: type: integer default: 0 description: Zero-based offset of the first record to return. responses: '200': description: Paginated row errors. content: application/json: schema: type: object properties: count: type: integer description: Number of error rows — exact up to 100, capped at 100 beyond that. Rely on `next` being `null` to detect the last page. next: type: string nullable: true description: Absolute URL of the next page, or `null` on the last page. previous: type: string nullable: true description: Absolute URL of the previous page, or `null` on the first page. results: type: array items: $ref: '#/components/schemas/SessionImportError' examples: Row errors: value: count: 1 next: null previous: null results: - uuid: 5a6b7c8d-9e0f-4a1b-8c2d-3e4f5a6b7c8d row_number: 17 external_id: ext-000017 error: 'status: invalid value ''unknown_status''' raw_row: external_id: ext-000017 status: unknown_status first_name: Jane created_at: '2026-05-17T08:44:02Z' '403': description: Missing, invalid, or revoked API key, or the key cannot read import jobs for this application. This endpoint returns `403` (never `401`) for authentication failures, including requests with no credentials at all. content: application/json: examples: Forbidden: summary: Missing, invalid, or revoked API key value: detail: You do not have permission to perform this action. '404': description: Import job not found for this application. content: application/json: examples: Not found: value: detail: Not found. security: - ApiKeyAuth: [] /v3/session/{sessionId}/delete/: delete: summary: Soft-delete a verification session (KYC or KYB) description: 'Soft-delete a single verification session — User Verification (KYC) or Business Verification (KYB) — by its `session_id`. The id is resolved against KYC sessions first, then KYB sessions, so both kinds are deleted through this one URL. **What happens on deletion:** - The session is stamped with a deletion timestamp and immediately disappears from every read endpoint — `GET /v3/session/{sessionId}/decision/` returns `404` and `GET /v3/sessions/` stops listing it. - For KYC sessions, related face, liveness, and face-match records are soft-deleted together with the session. - A background job then moves every stored media file owned by the session to a quarantined storage prefix: document front/back photos (full, cropped, and privacy-blurred variants), document-capture videos, portrait crops, NFC chip portrait and signature images, face reference images, liveness videos, face-match source/target images, Proof of Address documents, and any extra uploaded files (KYC); uploaded company documents and extra files (KYB). Previously issued media URLs (`https:///...`) stop resolving once the move completes — the move is asynchronous, so a URL issued just before deletion may keep working for a short window after the `204`. **What is retained:** the underlying database records (decision, extracted data, audit events) are kept internally, marked with the deletion timestamp — they become unreachable through the API but are not erased at the moment of the call. Blocklist entries created from this session (face or document) are **not** removed — manage those with the blocklist endpoints. Credits already consumed are not refunded. **Irreversible:** there is no restore/undelete endpoint. **Side effects:** no webhook is emitted for deletions. **Idempotency:** not idempotent at the HTTP level — the first call returns `204`; repeating it returns `404` because the session no longer resolves. **Authentication:** send your application''s API key in the `x-api-key` header; the session must belong to that application. Console user access tokens (`Authorization: Bearer ...`) may also call this endpoint when they carry the `delete:sessions` permission. Authentication and permission failures both return `403` — this API never returns `401`. **Rate limit:** shared write budget of 300 requests/min per API key across all POST/PATCH/DELETE endpoints; exceeding it returns `429`. To bulk-delete KYC sessions by their numeric `session_number`, use `POST /v3/sessions/delete/` instead.' operationId: delete_v3_session_by_id tags: - Sessions parameters: - in: path name: sessionId required: true description: UUID (`session_id`) of the User Verification (KYC) or Business Verification (KYB) session to delete, as returned when the session was created. Must be a canonical hyphenated UUID — a non-UUID value does not match the route and returns `404`. schema: type: string format: uuid example: 11111111-2222-3333-4444-555555555555 responses: '204': description: Session soft-deleted. Empty body. The media quarantine continues asynchronously after the response. '403': description: 'Authentication or permission failure. Missing/invalid credentials also return `403` — this API never returns `401`. Exception: when the `sessionId` does not resolve to a live session, the pre-auth owner lookup returns `404` first, even with missing or invalid credentials.' content: application/json: schema: type: object properties: detail: type: string examples: Missing or invalid API key: summary: No x-api-key header, or the key is invalid value: detail: Authentication credentials were not provided or are invalid. Insufficient permission: summary: User token without delete:sessions, or credentials for an application that does not own the session value: detail: You do not have permission to perform this action. '404': description: No live KYC or KYB session with this `session_id` exists — the id is unknown or the session was already soft-deleted (repeating a successful delete lands here). content: application/json: schema: type: object properties: detail: type: string examples: Not Found: summary: Unknown or already-deleted session value: detail: Not found. '429': description: Shared write rate limit exceeded (300 POST/PATCH/DELETE requests per minute per API key). Inspect `Retry-After` and the `X-RateLimit-*` response headers before retrying. content: application/json: schema: type: object properties: detail: type: string examples: Rate limited: summary: Write budget exhausted value: detail: Write request rate limit exceeded. You can make up to 300 requests per minute. security: - ApiKeyAuth: [] x-codeSamples: - lang: curl label: curl source: "curl -X DELETE \\\n https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/delete/ \\\n -H 'x-api-key: YOUR_API_KEY'" - lang: python label: Python source: "import requests\n\nresponse = requests.delete(\n \"https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/delete/\",\n headers={\"x-api-key\": \"YOUR_API_KEY\"},\n)\nresponse.raise_for_status() # 204 on success; 404 if unknown or already deleted" - lang: javascript label: JavaScript source: "const response = await fetch(\n 'https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/delete/',\n {\n method: 'DELETE',\n headers: { 'x-api-key': 'YOUR_API_KEY' },\n },\n);\nif (response.status !== 204) throw new Error(`HTTP ${response.status}`);" /v3/session/{sessionId}/generate-pdf/: get: summary: Download a PDF report for a verification session description: 'Render and download a compliance-ready PDF report for a finished verification session. The same path serves both User Verification (KYC) and Business Verification (KYB) sessions — the server looks the `sessionId` up in both tables and picks the matching report template. **Eligible statuses.** The session must already carry a final or reviewable status: - KYC sessions: `Approved`, `Declined`, `In Review`, or `Kyc Expired`. - KYB sessions: `Approved`, `Declined`, or `In Review`. Any other status (`Not Started`, `In Progress`, `Expired`, `Abandoned`, …) returns `403` with an explanatory `detail` string — see the `403` response below. **What the report contains.** The PDF mirrors the console session view at the moment of the request: - **KYC** — rolled-up session status, the warnings list, and one section per executed feature: ID Verification (document images plus extracted fields), NFC, Liveness, Face Match, Email Verification, Phone Verification, Proof of Address, Questionnaire answers, Database Validation, AML Screening, and IP Analysis — followed by session tags, console review activity (comments and status changes), and the session event timeline. - **KYB** — registry checks (company data), AML screenings, business document verifications, key-people checks, questionnaire responses, phone/email verifications, IP analyses, and the session event timeline, followed by tags and review activity. **Branding.** If your application has white-label customization, the report is rendered with your logo and links to your privacy-policy URL; otherwise it carries Didit branding. Reports are rendered in English — there is no language parameter. **No caching.** Every call re-renders the PDF from the current session data, so a report generated after a manual review reflects the reviewer’s decision. Two calls for the same session can produce byte-different files — archive the downloaded file if you need an immutable copy. **Latency.** Rendering downloads every stored image (document sides, selfies, extra files) before composing the PDF, so media-heavy sessions can take several seconds. Use a generous client read timeout (60 s recommended) and stream the body to disk. **Trailing slash.** The canonical route ends with a trailing slash (`…/generate-pdf/`). Requests without it receive a `301` redirect to the slashed URL — most HTTP clients follow it automatically for `GET`, but plain `curl` needs `-L` (or call the slashed URL directly, as in the samples). If you need the underlying data as JSON instead of a rendered report, use `GET /v3/session/{sessionId}/decision/`.' operationId: get_v3_session_generate_pdf tags: - Sessions parameters: - name: sessionId in: path required: true description: 'UUID of the User Verification (KYC) or Business Verification (KYB) session to render — the `session_id` returned by `POST /v3/session/`. The same path works for both session kinds; use the `Content-Disposition` filename on the response to tell which report type you received. **Must be a well-formed, lowercase UUID.** The route only matches valid 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. The route''s UUID converter only matches the canonical lowercase form — uppercase-hex UUIDs are rejected with the HTML 404.' schema: type: string format: uuid example: 11111111-2222-3333-4444-555555555555 responses: '200': description: The rendered PDF document, returned directly as binary `application/pdf` — there is no JSON wrapper and no download-URL indirection. The body starts with the `%PDF` magic bytes (PDF 1.7). Save it to a `.pdf` file or stream it through to your caller. headers: Content-Disposition: description: '`attachment; filename=session_{sessionId}.pdf` for KYC sessions, `attachment; filename=business_{sessionId}.pdf` for KYB sessions — the prefix tells you which report template was rendered.' schema: type: string example: attachment; filename=session_11111111-2222-3333-4444-555555555555.pdf Content-Length: description: Total size of the PDF in bytes; the body is not chunked. schema: type: integer example: 25191 content: application/pdf: schema: type: string format: binary '403': description: 'Authentication, authorization, or session-status failure. **This endpoint never returns `401`** — Didit''s API-key authentication does not emit a `WWW-Authenticate` challenge, so missing or invalid credentials also surface as `403`. Distinguish the failure modes by the `detail` string: - `"Authentication credentials were not provided or are invalid."` — no `x-api-key` header (and no `Authorization: Bearer` token), or the supplied credential failed introspection (revoked, expired, or malformed). - `"You do not have permission to perform this action."` — the credential is valid but cannot read this session: a console-user Bearer token lacks the `read:sessions` permission or cannot access the owning application, or an API key belongs to a different organization than the session. - `"You can only generate a PDF for sessions in review, declined, approved or kyc expired"` — the KYC session exists but is not in an eligible status yet (for example `Not Started` or `In Progress`). Wait for the `status.updated` webhook before requesting the report. - `"You can only generate a PDF for sessions in review, declined or approved"` — same condition for KYB sessions. Note that for a completely unknown `sessionId` the endpoint resolves the owning application before validating credentials, so a missing session returns `404` even when the credential is missing or invalid.' content: application/json: schema: type: object properties: detail: type: string example: You can only generate a PDF for sessions in review, declined, approved or kyc expired examples: Missing or invalid credentials: summary: No x-api-key header, or the key failed introspection value: detail: Authentication credentials were not provided or are invalid. Insufficient permissions: summary: Valid credential lacking read:sessions or access to the owning application value: detail: You do not have permission to perform this action. KYC session not finished: summary: KYC session still Not Started / In Progress value: detail: You can only generate a PDF for sessions in review, declined, approved or kyc expired KYB session not finished: summary: KYB session still Not Started / In Progress value: detail: You can only generate a PDF for sessions in review, declined or approved '404': description: No active session with this `sessionId` exists — it was never created, or it has been deleted. Owner resolution happens before credential validation, so this `404` is returned even for unauthenticated requests. Depending on which lookup misses, the `detail` string is `"Not found."` (the common case) or `"Session not found."`. content: application/json: schema: type: object properties: detail: type: string example: Not found. examples: Unknown session: summary: sessionId does not match any KYC or KYB session value: detail: Not found. Recently deleted session: summary: Session was deleted between lookups value: detail: Session not found. '429': description: Rate limit exceeded. PDF generation has a dedicated limit of **50 requests per minute per credential** (API key, Bearer token, or source IP when unauthenticated), in addition to the global 600/min GET limit. Wait `Retry-After` seconds (also reflected in the `X-RateLimit-*` headers), then retry. Reports are not cached server-side, so download each report once and store the file instead of re-fetching it. headers: Retry-After: description: Seconds to wait before retrying the request. schema: type: integer minimum: 1 example: 30 X-RateLimit-Limit: description: Maximum number of PDF generations allowed in the current window (50). schema: type: integer example: 50 X-RateLimit-Remaining: description: Requests remaining in the current window. schema: type: integer example: 0 X-RateLimit-Reset: description: Unix timestamp (seconds) at which the current window resets. schema: type: integer example: 1750000000 content: application/json: schema: type: object properties: detail: type: string description: Human-readable explanation of the rate-limit breach. example: Session PDF generation rate limit exceeded. You can make up to 50 requests per minute. examples: Throttled: summary: More than 50 PDF generations in one minute from the same credential value: detail: Session PDF generation rate limit exceeded. You can make up to 50 requests per minute. security: - ApiKeyAuth: [] x-codeSamples: - lang: curl label: cURL source: "curl --fail \\\n https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/generate-pdf/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n --output report.pdf" - lang: python label: Python source: "import requests\n\nresponse = requests.get(\n \"https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/generate-pdf/\",\n headers={\"x-api-key\": \"YOUR_API_KEY\"},\n stream=True,\n timeout=60,\n)\nresponse.raise_for_status()\nwith open(\"report.pdf\", \"wb\") as fh:\n for chunk in response.iter_content(chunk_size=8192):\n fh.write(chunk)" - lang: javascript label: JavaScript source: "import { writeFile } from 'node:fs/promises';\n\nconst response = await fetch(\n 'https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/generate-pdf/',\n { headers: { 'x-api-key': 'YOUR_API_KEY' } },\n);\nif (!response.ok) throw new Error(`PDF generation failed: HTTP ${response.status}`);\nawait writeFile('report.pdf', Buffer.from(await response.arrayBuffer()));" /v3/session/{sessionId}/sandbox/arm/: post: summary: Arm a sandbox scenario on a session description: 'Persist a sandbox scenario on a `Not Started` session and expand its bundled magic values (email, phone, or `expected_details` fields) onto the session, so the mocked providers reproduce the scenario''s outcome once the flow runs. This is the endpoint the pre-flow scenario picker in Didit''s hosted verification UI calls when a tester chooses an outcome before starting capture. Build your own picker against it, or call it directly from a sandbox test harness. Authenticate with the session''s own `Session-Token` (returned in `session_token` by [Create Session](/sessions-api/create-session)), not your application `x-api-key`: this endpoint is meant to be called from the client, not your backend. A scenario is only accepted while `status` is `Not Started`. Calling it again while still `Not Started` re-arms the session: it swaps in the new scenario''s magic values and clears only the values the *previous* scenario wrote, so any value the user already typed by hand (email, first name, etc.) survives the re-arm. Arming a session that has moved past `Not Started`, or that belongs to a live (non-sandbox) application, is rejected. See [Sandbox & Test Data](/integration/sandbox-testing) for the full scenario catalog and the other two ways to drive an outcome: `sandbox_scenario` at session create, and typing magic values directly.' operationId: post_v3_session_sandbox_arm tags: - Sessions security: - SessionTokenAuth: [] parameters: - in: path name: sessionId required: true description: UUID of the sandbox verification session to arm. Must match the session encoded in the Session-Token used to authenticate. schema: type: string format: uuid example: 11111111-2222-3333-4444-555555555555 requestBody: required: true content: application/json: schema: type: object required: - scenario properties: scenario: type: string description: Slug of the scenario to arm. See the catalog at GET /v1/sandbox/scenarios/ or the scenario table in [Sandbox & Test Data](/integration/sandbox-testing). example: decline_document_expired examples: Arm: summary: Arm the AML PEP-hit scenario value: scenario: decline_aml_hit responses: '200': description: 'Scenario armed. Its magic values are now on the session. `status` reflects the session''s current status, which this call never changes: it is always `Not Started` on success.' content: application/json: schema: type: object properties: session_id: type: string format: uuid scenario: type: string description: The armed scenario slug (echoes the request). status: type: string example: Not Started examples: Armed: summary: Scenario armed on a Not Started session value: session_id: 11111111-2222-3333-4444-555555555555 scenario: decline_aml_hit status: Not Started '400': description: 'Validation error: an unknown scenario slug, or the session has already progressed past `Not Started`.' content: application/json: examples: Unknown Scenario: summary: scenario is not in the catalog value: scenario: - 'Unknown sandbox scenario: ''not-a-real-scenario''.' Already Started: summary: Session moved past Not Started value: - Scenario can only be armed before the verification starts. '404': description: The Session-Token does not resolve to this `session_id`, or the session belongs to a live (non-sandbox) application. content: application/json: examples: Not Found: summary: Live session, or session_id / token mismatch value: detail: Not found. x-codeSamples: - lang: curl label: curl source: "curl -X POST \\\n https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/sandbox/arm/ \\\n -H 'Session-Token: YOUR_SESSION_TOKEN' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"scenario\": \"decline_aml_hit\"\n }'" - lang: python label: Python source: "import requests\n\nresponse = requests.post(\n \"https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/sandbox/arm/\",\n headers={\n \"Session-Token\": \"YOUR_SESSION_TOKEN\",\n \"Content-Type\": \"application/json\",\n },\n json={\"scenario\": \"decline_aml_hit\"},\n)\nresponse.raise_for_status()\nprint(response.json()) # {'session_id': '...', 'scenario': 'decline_aml_hit', 'status': 'Not Started'}" - lang: javascript label: JavaScript source: "const response = await fetch(\n 'https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/sandbox/arm/',\n {\n method: 'POST',\n headers: {\n 'Session-Token': sessionToken,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ scenario: 'decline_aml_hit' }),\n },\n);\nif (!response.ok) throw new Error(`HTTP ${response.status}`);\nconst { session_id, scenario, status } = await response.json();" /v3/session/{sessionId}/update-status/: patch: summary: Approve, decline, or request resubmission for a verification session description: 'Manually override the final decision of a KYC or KYB verification session. Set `new_status` to `Approved` or `Declined` to record a manual decision, or to `Resubmitted` to clear specific verification steps and let the user redo them through the same verification URL. The endpoint accepts both user (KYC) and business (KYB) session IDs. Call it once a session has reached a reviewable state: the current status must be `Approved`, `Declined`, `In Review`, `Kyc Expired`, `Abandoned`, or `Resubmitted`. Sessions that are still `Not Started`, `In Progress`, or `Awaiting User`, or that ended as `Expired`, return `400`. The new status must differ from the current one, and your API key''s organization needs the `write:sessions` permission. The eligible-state check runs before body validation, so an ineligible session returns the `Wrong Current Status` error even when the payload is also invalid. For `Resubmitted`, pass `nodes_to_resubmit` to choose which workflow steps the user must redo, or omit it to auto-select existing feature attempts in a resubmittable state (`Declined`, `In Review`, `Not Finished`, `Expired`). Never-attempted features are not auto-selected — list them explicitly in `nodes_to_resubmit`, or the request returns `400` when no recorded attempt needs resubmission. Captured data for the selected steps is deleted, the steps are reordered to match the workflow graph, the session''s expiration window restarts, and the original verification URL becomes usable again. Backend-only steps (`AML`, `DATABASE_VALIDATION`, `IP_ANALYSIS`) at the head of the list run immediately without user interaction; if every resubmitted step is backend-only, the session re-finalizes within the same request. `KYB_REGISTRY` and `KYB_KEY_PEOPLE` cannot be resubmitted directly — resubmit the relevant child KYC sessions instead and the parent business session recomputes automatically. Every successful call fires the `status.updated` [webhook](/integration/webhooks) once the change commits and appends an activity entry — with actor attribution and your `comment` — to the session''s `reviews`, visible in [Get Decision](#get-/v3/session/-sessionId-/decision/) and in the console activity timeline. The user-profile aggregates linked to the session''s `vendor_data` update as well. Declining disables ongoing AML monitoring for the session; re-approving re-enables it (from `Resubmitted`, `Declined`, or `Kyc Expired`) when the workflow has monitoring turned on. Approving or declining a child KYC that belongs to a KYB key-people check recomputes the parent business session''s status. Set `send_email: true` (with `email_address`) to notify the user: a status notice for `Approved`/`Declined`, or a resubmission email containing the verification link and per-step reasons for `Resubmitted`. The operation is intentionally not repeatable with the same value: re-sending the current status returns `400` (`The new status is the same as the current status.`), so accidental double-submissions are harmless. Authentication and permission failures both return `403` — this API never responds `401`.' operationId: patch_v3_session_update_status tags: - Sessions security: - ApiKeyAuth: [] parameters: - in: path name: sessionId required: true description: UUID of the verification session to update. Accepts both user (KYC) and business (KYB) session IDs — the service resolves the ID across both session types. schema: type: string format: uuid example: 11111111-2222-3333-4444-555555555555 requestBody: required: true content: application/json: schema: type: object required: - new_status properties: new_status: type: string description: Target status. `Approved` and `Declined` record a final manual decision (each can also overturn the other). `Resubmitted` clears the selected steps and sends the session back to the user. Any other value returns `400`. enum: - Approved - Declined - Resubmitted example: Approved comment: type: string description: Free-text reason for the change, stored on the session's review trail and returned in the `reviews` array of [Get Decision](#get-/v3/session/-sessionId-/decision/). For example `Duplicated user`. example: All checks passed manual review nodes_to_resubmit: type: array description: Workflow steps the user must redo. Only acted on when `new_status` is `Resubmitted`. For `Approved`/`Declined` the entries are still schema-validated (an invalid `feature` value returns `400`) but schema-valid entries are semantically ignored. If omitted, the server auto-selects existing OCR, Liveness, Face Match, POA, Phone, Email, AML, Database Validation, and Questionnaire attempts whose status is `Declined`, `In Review`, `Not Finished`, or `Expired` (NFC, IP analysis, age estimation, face search, and KYB document attempts are never auto-selected; non-face-match face attempts are selected as `LIVENESS`) — features the user never attempted (no recorded attempt) are NOT selected, so a session with zero attempts returns `400` ("No features found that need resubmission") even though nothing was approved; pass `nodes_to_resubmit` explicitly in that case. Steps are executed in workflow-graph order regardless of the order you send them. `KYB_REGISTRY`, `KYB_KEY_PEOPLE`, and the `KYB` alias are rejected with `400` — those parent checks recompute from their child KYC sessions. items: type: object required: - node_id - feature properties: node_id: type: string description: Node identifier as it appears in the session's workflow definition (for example `feature_ocr`, `feature_liveness`). example: feature_ocr feature: type: string description: Feature type of the node. The aliases `ID_VERIFICATION`, `POA`, `PHONE`, and `EMAIL` are normalized server-side to `OCR`, `PROOF_OF_ADDRESS`, `PHONE_VERIFICATION`, and `EMAIL_VERIFICATION`. `KYB_REGISTRY`, `KYB_KEY_PEOPLE`, and `KYB` pass schema validation but are always rejected with the business-rule `400` shown in the examples. enum: - OCR - OCR_BACK - NFC - AML - FACE - LIVENESS - FACE_MATCH - IP_ANALYSIS - AGE_ESTIMATION - PROOF_OF_ADDRESS - PHONE_VERIFICATION - EMAIL_VERIFICATION - FACE_SEARCH - DATABASE_VALIDATION - QUESTIONNAIRE - DOCUMENT_AI - KYB_DOCUMENTS - ID_VERIFICATION - POA - PHONE - EMAIL - KYB_REGISTRY - KYB_KEY_PEOPLE - KYB example: OCR send_email: type: boolean description: Whether to email the user about the change. Requires `email_address`. For `Approved`/`Declined` the user receives a status notice; for `Resubmitted` the email includes the verification link and the per-step resubmission reasons. default: false example: false email_address: type: string format: email description: Recipient for the notification email. **Required when `send_email` is `true`** — omitting it returns `400`. example: user@example.com email_language: type: string description: Language for the notification email. Accepts any string at schema level; unsupported codes silently fall back to English (`en`). default: en example: en examples: Approve Session: summary: Approve a session after manual review value: new_status: Approved comment: All checks passed manual review Decline Session: summary: Decline a session value: new_status: Declined comment: Suspected fraud Resubmit Specific Steps: summary: Request resubmission of chosen steps, with email value: new_status: Resubmitted nodes_to_resubmit: - node_id: feature_ocr feature: OCR - node_id: feature_liveness feature: LIVENESS send_email: true email_address: user@example.com email_language: en Resubmit Auto-select: summary: Request resubmission (auto-select non-approved steps) value: new_status: Resubmitted responses: '200': description: Status updated. The response contains only the `session_id` — fetch the updated session via [Get Decision](#get-/v3/session/-sessionId-/decision/). The `status.updated` webhook fires once the change commits. content: application/json: schema: type: object properties: session_id: type: string format: uuid description: UUID of the updated session. examples: Updated: summary: Returned for Approved, Declined, and Resubmitted alike value: session_id: 3472fb7c-8f7c-4d1a-9cf4-cf3d74ce1a60 '400': description: 'Validation error. Field errors arrive as `{"": [""]}` (some business-rule rejections use a string value instead: `{"nodes_to_resubmit": ""}`), cross-state errors as `{"detail": ""}`, and the same-status error as a bare JSON array.' content: application/json: examples: Wrong Current Status: summary: Session is not in an updatable state value: detail: 'Only sessions with status Approved, Declined, In Review, Kyc Expired, Abandoned, Resubmitted can be updated. Current status: In Progress' Invalid Status Value: summary: new_status is a real session status but not one of Approved/Declined/Resubmitted value: new_status: - You can only update the status to 'Approved', 'Declined', or 'Resubmitted'. Missing new_status: summary: Required field omitted value: new_status: - This field is required. Same Status: summary: New status equals the current status value: - The new status is the same as the current status. Email Address Required: summary: send_email true without email_address value: email_address: - Email address is required when send_email is true. Non-resubmittable Feature: summary: Resubmit targets a KYB parent check value: nodes_to_resubmit: 'The following features cannot be resubmitted directly: KYB_REGISTRY. Resubmit the relevant child KYC sessions instead; the parent will recompute automatically.' Nothing To Resubmit: summary: Auto-select found no non-approved features value: nodes_to_resubmit: No features found that need resubmission. All features are already approved. Invalid Feature Choice: summary: Unknown feature in nodes_to_resubmit value: nodes_to_resubmit: - feature: - '"BOGUS" is not a valid choice. Valid choices are: OCR, OCR_BACK, NFC, AML, FACE, LIVENESS, FACE_MATCH, IP_ANALYSIS, AGE_ESTIMATION, PROOF_OF_ADDRESS, PHONE_VERIFICATION, EMAIL_VERIFICATION, FACE_SEARCH, DATABASE_VALIDATION, QUESTIONNAIRE, KYB_REGISTRY, KYB_DOCUMENTS, KYB_KEY_PEOPLE, ID_VERIFICATION, POA, PHONE, EMAIL, KYB' Invalid Status Value (not a status at all): summary: new_status is not a valid StatusChoices value value: new_status: - '"Bogus" is not a valid choice.' '403': description: Missing/invalid credentials or insufficient permissions. This API returns `403` for authentication failures — never `401`. content: application/json: examples: Invalid Credentials: summary: Missing or invalid x-api-key value: detail: Authentication credentials were not provided or are invalid. Missing Privilege: summary: API key lacks write:sessions value: detail: You do not have permission to perform this action. '404': description: No KYC or KYB session with this `session_id` exists in your application (soft-deleted sessions also return `404`). content: application/json: examples: Not Found: summary: Unknown session_id value: detail: Not found. x-codeSamples: - lang: curl label: curl source: "curl -X PATCH \\\n https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/update-status/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"new_status\": \"Approved\",\n \"comment\": \"All checks passed manual review\"\n }'" - lang: python label: Python source: "import requests\n\nresponse = requests.patch(\n \"https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/update-status/\",\n headers={\n \"x-api-key\": \"YOUR_API_KEY\",\n \"Content-Type\": \"application/json\",\n },\n json={\n \"new_status\": \"Resubmitted\",\n \"nodes_to_resubmit\": [\n {\"node_id\": \"feature_ocr\", \"feature\": \"OCR\"},\n {\"node_id\": \"feature_liveness\", \"feature\": \"LIVENESS\"},\n ],\n \"send_email\": True,\n \"email_address\": \"user@example.com\",\n \"email_language\": \"en\",\n },\n)\nresponse.raise_for_status()\nprint(response.json()) # {'session_id': '11111111-2222-3333-4444-555555555555'}" - lang: javascript label: JavaScript source: "const response = await fetch(\n 'https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/update-status/',\n {\n method: 'PATCH',\n headers: {\n 'x-api-key': process.env.DIDIT_API_KEY,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n new_status: 'Declined',\n comment: 'Suspected fraud',\n }),\n },\n);\nif (!response.ok) throw new Error(`HTTP ${response.status}`);\nconst { session_id } = await response.json();" /v3/sessions/: get: summary: List verification sessions (KYC and/or KYB) with filters and pagination description: 'Returns a paginated list of verification sessions for the application identified by your API key, newest first (descending `session_number`). **Canonical URL:** the route is `/v3/sessions/` (trailing slash). Requests to `/v3/sessions` without the slash receive a `301` redirect to the slashed URL with the query string preserved; most HTTP clients follow it transparently on GET. **When to use this endpoint vs webhooks.** Use this endpoint for dashboards, back-office search, reconciliation, and batch exports. Do not poll it to track the progress of individual verifications - configure [webhooks](/integration/webhooks) instead, which push `status.updated` events the moment a session changes. For one session''s full result, call `GET /v3/session/{sessionId}/decision/`. **KYC and KYB rows.** `session_kind` selects which tables are listed: - `user` (default) - User Verification (KYC) sessions only. Backward-compatible behavior. - `business` - Business Verification (KYB) sessions only. Rows have a different, smaller shape (see the response schema). - `all` - both kinds in one response: the KYC page and the KYB page for the same `limit`/`offset` are concatenated in `results` (KYC rows first), so a page can contain up to `2 x limit` rows. `count` is the sum of both kinds and `next`/`previous` reflect the KYC-side pagination. For strict pagination of mixed data, call `user` and `business` separately. Every row carries a `session_kind` discriminator (`"user"` or `"business"`). **Pagination.** Standard limit/offset: `limit` (default 50) and `offset` (default 0), with `next`/`previous` ready-made page URLs. For KYC rows `count` is exact. For KYB rows `count` is computed with a bounded scan and is capped at 100 - when there are more than 100 matches it stays at 100, so iterate using `next` (or by checking whether a full page came back) rather than computing total pages from `count`. **Filter semantics.** All filters combine with AND and apply to KYC rows. When `session_kind` is `business` or `all`, the KYB side honors the shared parameters `vendor_data`, `status`, `workflow_id`, `country` (registry-company country code), `date_from`, `date_to`, `search` (company name, registration number — type registration numbers without separators, e.g. `DPC001` not `DPC-001` — vendor_data, session number, or session UUID), `warning`, and `organization_name`; the remaining KYC-only filters are ignored for KYB rows. Comma-separated parameters (`status`, `country`, `document_type`, `region`, `workflow_id`, `document_name`, `warning`, `organization_name`, `session_type`) match any of the listed values (OR within the parameter). `vendor_data` is an exact match - use `search` for partial matching. Unknown values in list filters simply return no rows (no error); malformed typed values (`date_from`, `date_to`, `didit_internal_id`, `api_service`, `session_kind`) return `400` with a per-field error object. **Search.** `search` adapts to the input: a full UUID looks up `session_id`; digits (with optional `+ - . space`) match session-number prefix, document/personal numbers, phone-number prefix, or `vendor_data`; values containing `@` match email prefixes; any other text matches names (accent-insensitive) and `vendor_data`. No fixed per-endpoint rate limit is enforced, but keep list polling infrequent and rely on webhooks for real-time updates.' operationId: get_v3_sessions tags: - Sessions parameters: - name: session_kind in: query required: false description: 'Which kind of sessions to list: `user` (KYC only, default), `business` (KYB only), or `all` (both, concatenated - see the description for merged-pagination semantics). Matching is case-insensitive (`BUSINESS` works); any value other than a case variant of `user`/`business`/`all` returns `400`.' schema: type: string enum: - user - business - all default: user example: all - name: limit in: query required: false description: Maximum number of rows per page (per kind when `session_kind=all`). Defaults to 50. schema: type: integer default: 50 minimum: 1 example: 20 - name: offset in: query required: false description: Number of rows to skip before the first returned row. Defaults to 0. Prefer following the `next`/`previous` URLs from the response. schema: type: integer default: 0 minimum: 0 example: 20 - name: status in: query required: false description: Comma-separated list of session statuses to include, e.g. `Approved,In Review`. Values are case-sensitive; see [Verification Statuses](/integration/verification-statuses). Unknown values match nothing. schema: type: string example: Approved,In Review - name: vendor_data in: query required: false description: Exact-match filter on the `vendor_data` you sent at session creation (your own user/business identifier). For partial matches use `search` instead. schema: type: string example: your-user-id-123 - name: search in: query required: false description: Smart free-text search. A full UUID matches `session_id`; numeric input matches session-number prefix, document/personal numbers, phone prefix, or `vendor_data`; input containing `@` matches email prefixes; other text matches names (accent-insensitive) and `vendor_data`. Free text containing digits additionally matches document/personal-number search fields and session-number prefixes. schema: type: string example: jane doe - name: workflow_id in: query required: false description: Comma-separated workflow UUIDs (find them on the Workflows page in the Console). Malformed UUIDs in the list are ignored; if none are valid the result is empty. schema: type: string example: 9f9b1234-aaaa-bbbb-cccc-1234567890ab - name: date_from in: query required: false description: Only sessions created on or after this date (UTC), format `YYYY-MM-DD`. Malformed dates return `400`. schema: type: string format: date example: '2026-06-01' - name: date_to in: query required: false description: Only sessions created up to this date (UTC), format `YYYY-MM-DD`. For KYC rows the entire day is included; for KYB rows the comparison is `created_at <= date_to` (midnight), so use the next day's date to include a full day of KYB sessions. schema: type: string format: date example: '2026-06-30' - name: country in: query required: false description: 'Comma-separated country filter. For KYC rows: ISO 3166-1 alpha-3 codes of the ID document''s issuing state (e.g. `ESP,USA`), which may differ from nationality. For KYB rows: the registry company''s country code.' schema: type: string example: ESP,USA - name: document_type in: query required: false description: 'Comma-separated ID-document type codes verified in the session (KYC rows only): `P` (Passport), `ID` (Identity Card), `DL` (Driver''s License), `RP` (Residence Permit), `SSC` (Social Security Card), `HIC` (Health Insurance Card), `WP` (Work Permit), `TC` (Tax Card), `VISA`, `PSC` (Public Service Card), `BC` (Birth Certificate), `OTHER`. Note the response field `document_type` contains the display name, not the code.' schema: type: string example: P,ID - name: document_name in: query required: false description: Exact document names; multiple names can be passed comma-separated, but the value is only split on commas when it contains more than one ` - ` separator (a lone name with an embedded comma is matched whole). Names are as recognized by document verification (front or back side), e.g. `Spain - Id Card (2021)`. KYC rows only. schema: type: string example: Spain - Id Card (2021) - name: region in: query required: false description: Comma-separated document region/state values (for documents issued per region, such as US driver's licenses). KYC rows only. schema: type: string example: CA,TX - name: didit_internal_id in: query required: false description: Filter by Didit's internal user identifier (the `didit_internal_id` response field) to list all sessions of one verified user. Must be a valid UUID; malformed values return `400`. KYC rows only. schema: type: string format: uuid example: d9e78474-6c8d-4f2a-bb90-6f5c2f3a1147 - name: session_type in: query required: false description: 'Comma-separated session creation channels: `API`, `HOSTED`, `MIGRATED`. The special value `ALL` (or omitting the parameter) disables the filter. KYC rows only.' schema: type: string example: HOSTED - name: api_service in: query required: false description: Filter standalone API sessions by service. Invalid values return `400`. KYC rows only. schema: type: string enum: - ID_VERIFICATION - FACE_MATCH - AGE_ESTIMATION - FACE_SEARCH - POA - AML - KYB_REGISTRY - PASSIVE_LIVENESS - DATABASE_VALIDATION - PHONE_VERIFICATION - EMAIL_VERIFICATION example: AML - name: screened_full_name in: query required: false description: Accent-insensitive name search inside AML and database-validation screened data. When combined with `api_service=AML` or `api_service=DATABASE_VALIDATION`, the search is restricted to that feature's screened data. KYC rows only. schema: type: string example: Jane Doe - name: warning in: query required: false description: Comma-separated warning risk codes; returns sessions that raised any of them (e.g. `POSSIBLE_FRAUD`). Risk codes are listed per feature in the [warnings documentation](/integration/webhooks). Applies to KYC rows and, with business warning codes, to KYB rows. schema: type: string example: POSSIBLE_FRAUD - name: status_initialized_as_in_review in: query required: false description: When `true`, only sessions whose decision initially landed in `In Review` (useful to audit manual-review volume). KYC rows only. schema: type: boolean example: true - name: is_kyc_to_be_reviewed in: query required: false description: '`true` returns sessions whose document verification is flagged for manual review; `false` returns sessions with document verification explicitly not flagged. Omit to disable. KYC rows only.' schema: type: boolean example: true - name: euuid in: query required: false description: Filter by the end user's euuid (UUID assigned to the verifying user, used by Console deep links). Must be a valid UUID; malformed values return a 400 field error. KYC rows only. schema: type: string format: uuid example: 0f8fad5b-d9cb-469f-a165-70867728950e - name: organization_name in: query required: false description: Comma-separated organization names. Console-oriented; with an API key (already scoped to one application) it only matches your own organization's name. schema: type: string example: Acme Inc responses: '200': description: Paginated session list. `results` items are KYC rows, KYB rows, or both depending on `session_kind`; discriminate with each row's `session_kind` field. content: application/json: schema: type: object required: - count - next - previous - results properties: count: type: integer description: Number of sessions matching the filters. Exact for KYC rows; for KYB rows it is capped at 100 (use `next` to know whether more pages exist). For `session_kind=all` it is the sum of both kinds. example: 2 next: type: string format: uri nullable: true description: Absolute URL of the next page, or `null` on the last page. example: https://verification.didit.me/v3/sessions/?limit=20&offset=20 previous: type: string format: uri nullable: true description: Absolute URL of the previous page, or `null` on the first page. example: null results: type: array description: Session rows, newest first. With `session_kind=all`, KYC rows come first, then KYB rows. items: oneOf: - title: 'KYC session row (session_kind: user)' type: object description: Summary row for a User Verification (KYC) session. Identity fields (`full_name`, `country`, `document_type`, `portrait_image`, contact fields) are populated progressively as the user completes verification steps, and are `null` until the relevant step finishes. Fields restricted by the workflow's response-attributes configuration are also returned as `null`. required: - session_id - session_kind - session_number - session_url - portrait_image - document_type - full_name - country - status - vendor_data - didit_internal_id - created_at - features - phone_number - email_address - additional_details properties: session_id: type: string format: uuid description: Unique identifier of the verification session. Use it with `GET /v3/session/{sessionId}/decision/` to fetch the full decision payload. example: 5b3720ed-d429-42ef-b67f-37ea805f48ee session_kind: type: string enum: - user description: Row discriminator. Always `"user"` for KYC rows; Business Verification (KYB) rows have `"business"` and a different shape. example: user session_number: type: integer description: Human-friendly sequential number of the session within your application. Also matched by the `search` parameter. example: 720 session_url: type: string nullable: true description: Hosted verification URL the end user opens to complete the flow. `null` when the session has no hosted URL. example: https://verify.didit.me/session/CuXoBYCYVO5Y portrait_image: type: string nullable: true description: 'Expiring (presigned) URL of the best available portrait: the NFC chip photo when available, otherwise the ID document portrait, otherwise the liveness reference image. `null` until one of those exists. Download promptly; the link expires.' example: https:///portraits/5b3720ed.jpg?X-Amz-Expires=3600&X-Amz-Signature=... document_type: type: string nullable: true enum: - Passport - Identity Card - Driver's License - Residence Permit - Health Insurance Card - Tax Card description: 'Human-readable type of the verified ID document. `null` before document verification completes, and also `null` after completion when the verified code has no display mapping (`SSC`, `WP`, `VISA`, `PSC`, `BC`, `OTHER`). Note: the `document_type` query parameter filters by short codes (`P`, `ID`, `DL`, `RP`, ...), not by these display values.' example: Passport full_name: type: string nullable: true description: 'Best available full name for the session, resolved in order: ID document OCR, AML screened data, database-validation screened data, proof-of-address document, then the expected details provided at session creation. `null` when none is available yet.' example: Jane Marie Doe country: type: string nullable: true description: 'ISO 3166-1 alpha-3 country associated with the session, resolved in order: ID document issuing state, database-validation issuing state, proof-of-address issuing state, expected details, then the verified phone''s country. May differ from nationality.' example: ESP status: type: string enum: - Not Started - In Progress - Approved - Declined - In Review - Expired - Abandoned - Kyc Expired - Resubmitted - Awaiting User description: Overall session status. See the [Verification Statuses](/integration/verification-statuses) page for the lifecycle. example: Approved vendor_data: type: string nullable: true description: Your own identifier for the end user, echoed back exactly as sent at session creation. example: your-user-id-123 didit_internal_id: type: string format: uuid nullable: true description: Didit's internal identifier for the verified user profile behind this session. Stable across multiple sessions of the same user; filterable via the `didit_internal_id` query parameter. example: d9e78474-6c8d-4f2a-bb90-6f5c2f3a1147 created_at: type: string format: date-time description: UTC timestamp when the session was created. example: '2026-06-09T12:26:54.328364Z' features: type: array description: Per-feature status for every verification step configured in the session's workflow, in workflow order. items: type: object required: - feature - status properties: feature: type: string enum: - ID_VERIFICATION - NFC - LIVENESS - FACE_MATCH - POA - QUESTIONNAIRE - EMAIL_VERIFICATION - PHONE - AML - IP_ANALYSIS - AGE_ESTIMATION - DATABASE_VALIDATION description: Verification feature identifier. example: ID_VERIFICATION status: type: string enum: - Not Finished - Approved - Declined - In Review - Resub Requested description: Status of this feature. `Not Finished` until the step completes. example: Approved phone_number: type: object nullable: true description: 'Phone associated with the session: the verified phone when phone verification ran, otherwise the contact phone provided at session creation. `null` when neither exists.' required: - number - is_verified properties: number: type: string description: Phone number in E.164 format. example: '+14155552671' is_verified: type: boolean description: '`true` only when the phone-verification feature approved this number.' example: true email_address: type: object nullable: true description: 'Email associated with the session: the verified email when email verification ran, otherwise the contact email provided at session creation. `null` when neither exists.' required: - email - is_verified properties: email: type: string format: email example: jane@example.com is_verified: type: boolean description: '`true` only when the email-verification feature approved this address.' example: true additional_details: type: object nullable: true description: Compact per-feature extras. Only features that produced data appear as keys; `null` when no feature produced extras. Fields excluded by the workflow's response-attributes configuration are omitted. properties: aml: type: object description: Present when AML screening ran. properties: total_hits: type: integer description: Number of AML screening hits. example: 0 entity_type: type: string description: Screened entity type. example: person is_adverse_media_fetched: type: boolean example: false screened_data: type: object description: Subset of the data that was screened. Only non-empty keys are included. properties: nationality: type: string example: ESP document_number: type: string example: XX1234567 date_of_birth: type: string example: '1990-05-21' database_validation: type: object description: Present when database validation ran. properties: validation_type: type: string example: DOCUMENT_VALIDATION match_type: type: string example: FULL_MATCH identification_number: type: string description: Tax, personal, or document number that was validated (first available). example: 12345678Z poa: type: object description: Present when proof of address ran. properties: document_type: type: string example: UTILITY_BILL issuer: type: string example: Energy Co address: type: string example: Calle Mayor 1, 28013 Madrid name_on_document: type: string example: Jane Marie Doe liveness: type: object description: Present when a liveness check ran. properties: method: type: string example: PASSIVE score: type: number description: Liveness confidence score, rounded to 2 decimals. example: 0.98 face_match: type: object description: Present when face match ran. properties: score: type: number description: Face-match similarity score, rounded to 2 decimals. example: 0.95 source_image: type: string description: Expiring (presigned) URL of the source face image. example: https:///faces/source.jpg?X-Amz-Expires=3600&... target_image: type: string description: Expiring (presigned) URL of the target face image. example: https:///faces/target.jpg?X-Amz-Expires=3600&... phone_verification: type: object description: Present when phone verification ran. properties: country_code: type: string description: ISO 3166-1 alpha-2 country of the phone number. example: US is_temporary: type: boolean example: false is_ported: type: boolean example: false is_blocked: type: boolean example: false email_verification: type: object description: Present when email verification ran. properties: is_breached: type: boolean example: false is_disposable: type: boolean example: false is_undeliverable: type: boolean example: false is_blocklisted: type: boolean example: false - title: 'KYB session row (session_kind: business)' type: object description: Summary row for a Business Verification (KYB) session. Returned only when `session_kind` is `business` or `all`. required: - session_id - session_kind - session_number - status - vendor_data - created_at - workflow_id - workflow_type - workflow_label - company_name - registration_number - country properties: session_id: type: string format: uuid description: Unique identifier of the business verification session. example: 8a1f63c2-1b9e-4f3a-9c41-2f6a1f0b7d52 session_kind: type: string enum: - business description: Row discriminator. Always `"business"` for KYB rows. example: business session_number: type: integer description: Human-friendly sequential number of the business session within your application. Numbered independently from KYC sessions. example: 42 status: type: string enum: - Not Started - In Progress - Approved - Declined - In Review - Expired - Abandoned - Kyc Expired - Resubmitted - Awaiting User description: Overall session status. See the [Verification Statuses](/integration/verification-statuses) page. example: Approved vendor_data: type: string nullable: true description: Your own identifier for the business, echoed back exactly as sent at session creation. example: your-company-id-77 created_at: type: string format: date-time description: UTC timestamp when the session was created. example: '2026-06-08T09:14:02.118450Z' workflow_id: type: string format: uuid nullable: true description: Identifier of the workflow this session was created with. example: 9f9b1234-aaaa-bbbb-cccc-1234567890ab workflow_type: type: string nullable: true description: Workflow type. `kyb` for business workflows. example: kyb workflow_label: type: string nullable: true description: Display name of the workflow. example: Standard KYB company_name: type: string nullable: true description: Name of the company being verified, from the registry company linked to the session. `null` until a company is selected. example: Acme Holdings S.L. registration_number: type: string nullable: true description: Company registration number from the registry company linked to the session. example: B12345678 country: type: string nullable: true description: Country code of the registry company linked to the session. example: ES examples: KYC sessions (default): summary: session_kind=user - one completed and one not-started session value: count: 2 next: null previous: null results: - session_id: 5b3720ed-d429-42ef-b67f-37ea805f48ee session_kind: user session_number: 720 session_url: https://verify.didit.me/session/CuXoBYCYVO5Y portrait_image: https:///portraits/5b3720ed.jpg?X-Amz-Expires=3600&X-Amz-Signature=... document_type: Passport full_name: Jane Marie Doe country: ESP status: Approved vendor_data: your-user-id-123 didit_internal_id: d9e78474-6c8d-4f2a-bb90-6f5c2f3a1147 created_at: '2026-06-09T12:26:54.328364Z' features: - feature: ID_VERIFICATION status: Approved - feature: LIVENESS status: Approved - feature: FACE_MATCH status: Approved - feature: AML status: Approved - feature: IP_ANALYSIS status: Approved phone_number: number: '+14155552671' is_verified: true email_address: email: jane@example.com is_verified: true additional_details: aml: total_hits: 0 entity_type: person is_adverse_media_fetched: false liveness: method: PASSIVE score: 0.98 face_match: score: 0.95 - session_id: 038ad62f-5ebd-46c2-afe9-4b2956bf687f session_kind: user session_number: 719 session_url: https://verify.didit.me/session/7RGKiO6ioGH6 portrait_image: null document_type: null full_name: null country: null status: Not Started vendor_data: your-user-id-124 didit_internal_id: eb4e679d-ca4b-461f-9685-687bac29ebdb created_at: '2026-06-09T08:02:23.343053Z' features: - feature: ID_VERIFICATION status: Not Finished - feature: LIVENESS status: Not Finished - feature: FACE_MATCH status: Not Finished - feature: AML status: Not Finished - feature: IP_ANALYSIS status: Not Finished phone_number: null email_address: null additional_details: null Mixed KYC + KYB (session_kind=all): summary: session_kind=all - KYC row followed by a KYB row value: count: 2 next: null previous: null results: - session_id: 5b3720ed-d429-42ef-b67f-37ea805f48ee session_kind: user session_number: 720 session_url: https://verify.didit.me/session/CuXoBYCYVO5Y portrait_image: null document_type: Identity Card full_name: Jane Marie Doe country: ESP status: In Review vendor_data: your-user-id-123 didit_internal_id: d9e78474-6c8d-4f2a-bb90-6f5c2f3a1147 created_at: '2026-06-09T12:26:54.328364Z' features: - feature: ID_VERIFICATION status: In Review - feature: LIVENESS status: Approved phone_number: null email_address: null additional_details: liveness: method: PASSIVE score: 0.98 - session_id: 8a1f63c2-1b9e-4f3a-9c41-2f6a1f0b7d52 session_kind: business session_number: 42 status: Approved vendor_data: your-company-id-77 created_at: '2026-06-08T09:14:02.118450Z' workflow_id: 9f9b1234-aaaa-bbbb-cccc-1234567890ab workflow_type: kyb workflow_label: Standard KYB company_name: Acme Holdings S.L. registration_number: B12345678 country: ES '301': description: Redirect to the canonical trailing-slash URL. Returned when the request path is `/v3/sessions` (no trailing slash); the `Location` header points to `/v3/sessions/` with the query string preserved. GET clients typically follow it automatically. '400': description: Invalid query parameter. The body is an object keyed by the offending parameter. content: application/json: examples: Invalid session_kind: summary: session_kind not one of user/business/all value: session_kind: Must be "user", "business", or "all". Invalid date: summary: Malformed date_from/date_to value: date_from: - Enter a valid date. Invalid didit_internal_id: summary: didit_internal_id is not a UUID value: didit_internal_id: - Enter a valid UUID. Invalid api_service: summary: api_service not in the allowed choices value: api_service: - Select a valid choice. FOO is not one of the available choices. '403': description: Missing, malformed, or revoked API key. This API never returns `401`; all authentication failures are `403`. content: application/json: examples: Forbidden: summary: Missing or invalid x-api-key value: detail: You do not have permission to perform this action. security: - ApiKeyAuth: [] x-codeSamples: - lang: curl label: curl source: "curl -s 'https://verification.didit.me/v3/sessions/?status=Approved,In%20Review&date_from=2026-06-01&limit=20' \\\n -H 'x-api-key: YOUR_API_KEY'" - lang: python label: Python source: "import requests\n\nurl = \"https://verification.didit.me/v3/sessions/\"\nheaders = {\"x-api-key\": \"YOUR_API_KEY\"}\nparams = {\n \"session_kind\": \"user\", # \"user\" (default) | \"business\" | \"all\"\n \"status\": \"Approved,In Review\", # comma-separated, OR within the list\n \"date_from\": \"2026-06-01\",\n \"limit\": 20,\n}\n\nwhile url:\n response = requests.get(url, headers=headers, params=params)\n response.raise_for_status()\n page = response.json()\n for session in page[\"results\"]:\n print(session[\"session_kind\"], session[\"session_id\"], session[\"status\"])\n url, params = page[\"next\"], None # \"next\" already carries the query string" - lang: javascript label: JavaScript source: "const params = new URLSearchParams({\n session_kind: 'all',\n status: 'Approved,In Review',\n date_from: '2026-06-01',\n limit: '20',\n});\n\nconst response = await fetch(\n `https://verification.didit.me/v3/sessions/?${params}`,\n { headers: { 'x-api-key': process.env.DIDIT_API_KEY } },\n);\nif (!response.ok) throw new Error(`HTTP ${response.status}`);\n\nconst { count, next, results } = await response.json();\nfor (const session of results) {\n // session.session_kind is \"user\" (KYC) or \"business\" (KYB)\n console.log(session.session_kind, session.session_id, session.status);\n}" /v3/session/{sessionId}/share/: post: summary: Mint a share token for a finished verification session description: Mint a short-lived JWT that lets a specific Didit application import this finished session. Pair with `POST /v3/session/import-shared/`. operationId: post_v3_session_share tags: - Sessions responses: '200': description: Share token minted. content: application/json: schema: type: object properties: share_token: type: string description: HS256-signed JWT. Pass this verbatim as `share_token` to [`POST /v3/session/import-shared/`](/sessions-api/import-shared-session) on the target application. example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... for_application_id: type: string format: uuid description: Echoes the target application UUID that the token is bound to. example: a5f3bca2-46e2-411e-90ef-a580900a57ee session_kind: type: string enum: - user - business description: Whether the source session is a User Verification (KYC) or Business Verification (KYB) session. examples: User session: summary: Share a KYC session value: share_token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzZXNzaW9uX2lkIjoiMjBiYjNjM2ItNjE0Ni00MzlmLTg0YTQtYmQzMGQwMGFjNmEyIiwiZnJvbV9hcHBsaWNhdGlvbl9pZCI6ImRiZDIwZTM0LTQyZTktNGYyYy1iYTkxLWNmMDc2MjAxNmY2NCIsImZvcl9hcHBsaWNhdGlvbl9pZCI6ImE1ZjNiY2EyLTQ2ZTItNDExZS05MGVmLWE1ODA5MDBhNTdlZSIsImlhdCI6MTc1MzYzMDY2NiwiZXhwIjoxNzUzNjM0MjY2fQ.JJ9pNE_hqZsOtbR0XYZIWw4JzidjdEl279iUrsIkhGE for_application_id: a5f3bca2-46e2-411e-90ef-a580900a57ee session_kind: user Business session: summary: Share a KYB session value: share_token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... for_application_id: a5f3bca2-46e2-411e-90ef-a580900a57ee session_kind: business '400': description: Validation error. Field errors are keyed by field name; the not-finished error arrives under a `detail` key (as an array). content: application/json: examples: Wrong status: summary: Source session is not finished value: detail: - Only finished sessions ("Approved", "Declined", "In Review") can be shared. Target missing: summary: Target application does not exist value: for_application_id: - Target application does not exist. Self-share: summary: Target is the calling application value: for_application_id: - Cannot share a session with the same application. TTL bounds: summary: ttl_in_seconds outside bounds value: ttl_in_seconds: - Ensure this value is greater than or equal to 60. '401': description: Missing or invalid credentials. Unlike most v3 endpoints, this endpoint authenticates through the permission decorator and returns `401` when no valid token/key is presented. content: application/json: examples: Unauthorized: summary: Unauthorized value: detail: Authentication credentials were not provided or are invalid. '403': description: The credentials are valid but lack the `write:sessions` permission for this application. content: application/json: examples: No Permission: value: detail: You do not have permission to perform this action. summary: No Permission '404': description: No session with the given `session_id` exists in your application. content: application/json: examples: Not Found: summary: Session not found value: detail: Not found. parameters: - in: path name: sessionId required: true description: UUID of the source verification session to mint a token for. schema: type: string format: uuid example: 11111111-2222-3333-4444-555555555555 security: - ApiKeyAuth: [] requestBody: required: true content: application/json: schema: type: object required: - for_application_id properties: for_application_id: type: string format: uuid description: UUID of the Didit application that will redeem the token. Must exist, must not be soft-deleted, and must differ from the calling application. Find it in the Business Console under **Settings → Application**. example: a5f3bca2-46e2-411e-90ef-a580900a57ee ttl_in_seconds: type: integer description: Token lifetime, in seconds. Minimum `60`, maximum `86400` (24 h). Defaults to `3600` (1 h). minimum: 60 maximum: 86400 default: 3600 example: 3600 example: for_application_id: a5f3bca2-46e2-411e-90ef-a580900a57ee ttl_in_seconds: 7200 x-codeSamples: - lang: curl label: curl source: "curl -X POST \\\n https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/share/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"for_application_id\": \"a5f3bca2-46e2-411e-90ef-a580900a57ee\",\n \"ttl_in_seconds\": 7200\n }'" - lang: python label: Python source: "import requests\n\nresponse = requests.post(\n \"https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/share/\",\n headers={\n 'x-api-key': 'YOUR_API_KEY',\n \"Content-Type\": \"application/json\",\n },\n json={\n \"for_application_id\": \"a5f3bca2-46e2-411e-90ef-a580900a57ee\",\n \"ttl_in_seconds\": 7200,\n },\n)\nresponse.raise_for_status()\nshare_token = response.json()[\"share_token\"]" - lang: javascript label: JavaScript source: "const response = await fetch(\n 'https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/share/',\n {\n method: 'POST',\n headers: {\n 'x-api-key': 'YOUR_API_KEY',\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n for_application_id: 'a5f3bca2-46e2-411e-90ef-a580900a57ee',\n ttl_in_seconds: 7200,\n }),\n },\n);\nif (!response.ok) throw new Error(`HTTP ${response.status}`);\nconst { share_token } = await response.json();" /v3/session/import-shared/: post: summary: Redeem a share token to clone a verification session into your application description: Redeem a share token to clone a KYC or KYB session into the calling application with a fresh `session_id`. Not idempotent — a token redeems once per receiver. operationId: post_v3_session_import_shared tags: - Sessions responses: '201': description: 'Session cloned. For user (KYC) sessions the body is the V2 decision payload (`session_id`, `session_number`, `status`, `workflow_id`, per-feature blocks such as `id_verification`, `liveness`, `aml`, plus `created_at`/`expires_at` — no `session_kind` field). For business (KYB) sessions it is the V3 KYB decision payload, which includes `session_kind: "business"` and blocks like `registry_checks` and `aml_screenings`. With `trust_review: false` the cloned session''s `status` is forced to `In Review`.' content: application/json: examples: User session imported: summary: Cloned KYC session (truncated — full V2 decision payload in reality) value: session_id: 11111111-2222-3333-4444-555555555555 session_number: 43762 session_url: null status: In Review workflow_id: 9f9b1234-aaaa-bbbb-cccc-1234567890ab vendor_data: user-1 created_at: '2026-05-17T08:42:11Z' expires_at: '2026-05-24T08:42:11Z' Business session imported: summary: Cloned KYB session (truncated — full V3 KYB decision payload in reality) value: session_id: 22222222-3333-4444-5555-666666666666 session_kind: business session_number: 982 session_url: null status: Approved workflow_id: 9f9b1234-aaaa-bbbb-cccc-1234567890ab vendor_data: company-1 '400': description: Validation error (token invalid, expired, wrong target, or references a session that no longer exists). content: application/json: examples: Invalid Share Token: summary: Invalid JWT value: share_token: - Invalid share token. Expired Share Token: summary: Token past `exp` value: share_token: - Share token has expired. Wrong audience: summary: Token not bound to this application value: share_token: - This token is not valid for this application. Original missing: summary: Source session was deleted value: share_token: - Original session does not exist. '403': description: Missing/invalid API key (this endpoint returns `403`, never `401`, for authentication failures), or this source session has already been imported into the calling application (one redeem per receiver). content: application/json: examples: Forbidden: summary: Forbidden value: detail: You do not have permission to perform this action. Already imported: summary: Source already imported once value: detail: This session has already been shared with your application. '404': description: '`workflow_id` does not belong to the calling application.' content: application/json: examples: Workflow not found: summary: Workflow unknown value: detail: Workflow does not exist for this application. requestBody: required: true content: application/json: schema: type: object required: - share_token - trust_review - workflow_id properties: share_token: type: string description: JWT share token issued by [`POST /v3/session/{sessionId}/share/`](/sessions-api/share-session). example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... trust_review: type: boolean description: If `true`, the cloned session keeps the source's final `status`. If `false`, it is forced into `In Review`. example: false workflow_id: type: string format: uuid description: UUID of a workflow in the calling application. Cross-application IDs are rejected with `404`. example: 9f9b1234-aaaa-bbbb-cccc-1234567890ab vendor_data: type: string nullable: true description: Optional override for the cloned session's `vendor_data`. example: user-1 example: share_token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... trust_review: false workflow_id: 9f9b1234-aaaa-bbbb-cccc-1234567890ab vendor_data: user-1 security: - ApiKeyAuth: [] x-codeSamples: - lang: curl label: curl source: "curl -X POST \\\n https://verification.didit.me/v3/session/import-shared/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"share_token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\",\n \"trust_review\": false,\n \"workflow_id\": \"9f9b1234-aaaa-bbbb-cccc-1234567890ab\",\n \"vendor_data\": \"user-1\"\n }'" - lang: python label: Python source: "import requests\n\nresponse = requests.post(\n \"https://verification.didit.me/v3/session/import-shared/\",\n headers={\n 'x-api-key': 'YOUR_API_KEY',\n \"Content-Type\": \"application/json\",\n },\n json={\n \"share_token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\",\n \"trust_review\": False,\n \"workflow_id\": \"9f9b1234-aaaa-bbbb-cccc-1234567890ab\",\n \"vendor_data\": \"user-1\",\n },\n)\nresponse.raise_for_status()\nimported = response.json()" - lang: javascript label: JavaScript source: "const response = await fetch(\n 'https://verification.didit.me/v3/session/import-shared/',\n {\n method: 'POST',\n headers: {\n 'x-api-key': 'YOUR_API_KEY',\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n share_token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',\n trust_review: false,\n workflow_id: '9f9b1234-aaaa-bbbb-cccc-1234567890ab',\n vendor_data: 'user-1',\n }),\n },\n);\nif (!response.ok) throw new Error(`HTTP ${response.status}`);\nconst imported = await response.json();" /v3/sessions/delete/: post: summary: Bulk soft-delete KYC sessions by session_number description: 'Bulk soft-delete User Verification (KYC) sessions by their numeric `session_number`, or wipe every KYC session in your application with `delete_all: true`. `session_number` is the human-friendly incrementing counter shown in the Console session list and returned as `session_number` by the create-session and decision endpoints — it is **not** the `session_id` UUID. To delete a session by UUID — or to delete a Business Verification (KYB) session, which this endpoint never touches — use `DELETE /v3/session/{sessionId}/delete/` instead. Each deleted session gets exactly the same treatment as the single-session delete endpoint: stamped with a deletion timestamp, removed from all read endpoints, related face/liveness/face-match records soft-deleted, and all stored media moved to a quarantined storage prefix by a background job so previously issued media URLs stop resolving. Database records are retained internally but become unreachable through the API; blocklist entries are **not** removed; no webhook is emitted; there is no undo. **Selection semantics:** - Entries in `session_numbers` that do not match a live KYC session in your application — unknown numbers, already-deleted sessions, or sessions owned by another application — are **silently skipped**. There is no per-item failure report: the endpoint returns `204` whether it deleted all, some, or none of the listed sessions. - Validation failures (a non-numeric entry, an empty list, or neither `session_numbers` nor `delete_all` provided) reject the **whole request** with `400` and delete nothing. - All deletions run inside a single database transaction. - There is no documented cap on the list length; the shared write rate limit (below) is the practical bound. **Idempotency:** fully idempotent — repeating the same request returns `204` again (already-deleted numbers are skipped). **`delete_all: true` — destructive:** soft-deletes **every** non-deleted KYC session in the application; `session_numbers` is ignored when set. There is no confirmation step and no undo. **Authentication:** API key in the `x-api-key` header. Missing or invalid credentials return `403` (`{"detail": "You do not have permission to perform this action."}`) — this API never returns `401`. **Rate limit:** shared write budget of 300 requests/min per API key across all POST/PATCH/DELETE endpoints; exceeding it returns `429`. This endpoint accepts API keys only — Business Console user Bearer tokens are rejected with `403` (unlike the single-session delete, which accepts both).' operationId: batch_delete_sessions tags: - Sessions security: - ApiKeyAuth: [] requestBody: required: true content: application/json: schema: type: object properties: session_numbers: type: array description: Numeric `session_number` values of the KYC sessions to soft-delete, as digit-only strings (JSON numbers are also accepted and coerced). Required unless `delete_all` is `true`; an empty list returns `400`. Any non-digit entry rejects the whole request with `400` (unless `delete_all` is `true`, in which case validation is skipped and the entries are ignored). Numbers that do not match a live session in your application are silently skipped. items: type: string pattern: ^[0-9]+$ example: '1001' minItems: 1 example: - '1001' - '1002' - '1003' delete_all: type: boolean description: When `true`, every non-deleted KYC session in the calling application is soft-deleted and `session_numbers` is ignored. Defaults to `false`. Irreversible — there is no confirmation step. default: false example: false examples: Specific sessions: summary: Delete specific sessions by number value: session_numbers: - '1001' - '1002' - '1003' All sessions: summary: Delete every KYC session in the application value: delete_all: true responses: '204': description: Request accepted and all matching sessions soft-deleted. Empty body. Returned even when some or all `session_numbers` matched nothing (unknown or already deleted) — there is no per-item report. '400': description: Validation error — nothing was deleted. Error values are arrays of message strings keyed by field name. content: application/json: schema: type: object properties: session_numbers: type: array items: type: string examples: Missing selector: summary: Neither session_numbers nor delete_all provided value: session_numbers: - This field is required unless delete_all is true. Empty list: summary: session_numbers is an empty array value: session_numbers: - This list may not be empty. Non-numeric entry: summary: session_numbers contains a non-digit string value: session_numbers: - All session_numbers must be numeric. '403': description: Missing or invalid API key. This API returns `403` for authentication failures — never `401`. content: application/json: schema: type: object properties: detail: type: string examples: Missing or invalid API key: summary: No x-api-key header, or the key is invalid value: detail: You do not have permission to perform this action. '429': description: Shared write rate limit exceeded (300 POST/PATCH/DELETE requests per minute per API key). Inspect `Retry-After` and the `X-RateLimit-*` response headers before retrying. content: application/json: schema: type: object properties: detail: type: string examples: Rate limited: summary: Write budget exhausted value: detail: Write request rate limit exceeded. You can make up to 300 requests per minute. x-codeSamples: - lang: curl label: curl source: "curl -X POST \\\n https://verification.didit.me/v3/sessions/delete/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"session_numbers\": [\"1001\", \"1002\", \"1003\"]\n }'" - lang: python label: Python source: "import requests\n\nresponse = requests.post(\n \"https://verification.didit.me/v3/sessions/delete/\",\n headers={\n \"x-api-key\": \"YOUR_API_KEY\",\n \"Content-Type\": \"application/json\",\n },\n json={\"session_numbers\": [\"1001\", \"1002\", \"1003\"]},\n)\nresponse.raise_for_status() # 204 on success, even if some numbers matched nothing\n\n# Destructive: delete every KYC session in the application\n# requests.post(\n# \"https://verification.didit.me/v3/sessions/delete/\",\n# headers={\"x-api-key\": \"YOUR_API_KEY\"},\n# json={\"delete_all\": True},\n# )" - lang: javascript label: JavaScript source: "const response = await fetch(\n 'https://verification.didit.me/v3/sessions/delete/',\n {\n method: 'POST',\n headers: {\n 'x-api-key': 'YOUR_API_KEY',\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ session_numbers: ['1001', '1002', '1003'] }),\n },\n);\nif (response.status !== 204) throw new Error(`HTTP ${response.status}`);" /v3/sessions/{session_id}/reviews/: get: summary: List session reviews and activity log description: 'Return the audit trail / activity feed for a User Verification (KYC) session, newest first, in a paginated envelope (`count` / `next` / `previous` / `results`, 50 entries per page by default). Feed entries are written by every actor that touches the session: manual decisions made through `PATCH /v3/session/{session_id}/update-status/` (which records `previous_status` → `new_status`), Console reviewer activity (comments with `@email` mentions, KYC/POA data edits, file uploads/removals, tag changes, detail-page views), AML ongoing-monitoring matches, and notes appended via `POST` on this same path. Reading this feed is the API-side equivalent of the **Activity** panel in the Didit Console session view. Only User Verification (KYC) sessions are addressable here; Business Verification (KYB) session IDs return `404`.' operationId: list_session_reviews tags: - Sessions parameters: - name: session_id in: path required: true description: UUID of the User Verification (KYC) session. Must belong to the same application as the API key — cross-application UUIDs, deleted sessions, and Business Verification (KYB) session IDs all return `404`. schema: type: string format: uuid example: 8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d - name: limit in: query required: false description: Page size. Defaults to `50`. schema: type: integer default: 50 example: 50 - name: offset in: query required: false description: Number of entries to skip from the start of the (ordered) feed. Defaults to `0`. The `next` / `previous` URLs in the response carry the right values for walking the feed. schema: type: integer default: 0 example: 0 - name: ordering in: query required: false description: Sort order. The feed defaults to newest first (`-created_at`); pass `created_at` to read it oldest first. Unknown values are silently ignored (default order is used); other serializer field names are also accepted. schema: type: string default: -created_at example: created_at responses: '200': description: Paginated activity feed, newest first by default. `count` is the total number of entries; `next` / `previous` are ready-made page URLs (`null` at the ends). A `count` of `0` with empty `results` means no activity has been recorded yet. content: application/json: schema: type: object properties: count: type: integer description: Total number of feed entries for this session (across all pages). example: 3 next: type: string nullable: true description: URL of the next page, or `null` on the last page. example: null previous: type: string nullable: true description: URL of the previous page, or `null` on the first page. example: null results: type: array items: $ref: '#/components/schemas/ReviewItem' description: Feed entries for this page. examples: DecisionTrail: summary: Manual approval plus API-added notes value: count: 3 next: null previous: null results: - uuid: de0561bc-5583-4409-9171-1a31a4c8a5f4 activity_type: STATUS_UPDATED actor_display: API Client actor_email: null actor_type: API_KEY new_status: Approved previous_status: In Review comment: Document re-checked manually; address matches. mentioned_emails: [] previous_value: status: In Review new_value: status: Approved changed_fields: - status metadata: null created_at: '2026-06-12T00:19:31.918735Z' - uuid: 35370f58-5098-44ee-8a06-8c9af80b2455 activity_type: STATUS_UPDATED actor_display: Unknown actor_email: null actor_type: CONSOLE_USER new_status: null previous_status: null comment: Note without status. mentioned_emails: [] previous_value: null new_value: null changed_fields: [] metadata: null created_at: '2026-06-12T00:18:44.504157Z' - uuid: 00338f82-f1c5-4f57-8614-c49bb258af1a activity_type: STATUS_UPDATED actor_display: Unknown actor_email: null actor_type: CONSOLE_USER new_status: In Review previous_status: null comment: Flagging for manual document re-check. mentioned_emails: [] previous_value: null new_value: null changed_fields: [] metadata: null created_at: '2026-06-12T00:18:44.466537Z' PaginatedFirstPage: summary: First page with limit=2 — next holds the follow-up URL value: count: 4 next: https://verification.didit.me/v3/sessions/8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d/reviews/?limit=2&offset=2 previous: null results: - uuid: de0561bc-5583-4409-9171-1a31a4c8a5f4 activity_type: STATUS_UPDATED actor_display: API Client actor_email: null actor_type: API_KEY new_status: Approved previous_status: In Review comment: Document re-checked manually; address matches. mentioned_emails: [] previous_value: status: In Review new_value: status: Approved changed_fields: - status metadata: null created_at: '2026-06-12T00:19:31.918735Z' - uuid: bd4e6227-bdd2-42b2-a726-1d12f785a457 activity_type: STATUS_UPDATED actor_display: Unknown actor_email: null actor_type: CONSOLE_USER new_status: null previous_status: null comment: null mentioned_emails: [] previous_value: null new_value: null changed_fields: [] metadata: null created_at: '2026-06-12T00:18:44.540238Z' Empty: summary: Session with no recorded activity yet value: count: 0 next: null previous: null results: [] '403': description: Missing, malformed, expired, or revoked `x-api-key`. Authentication is checked before the session lookup, so an invalid key returns `403` even when the session UUID does not exist. content: application/json: schema: type: object properties: detail: type: string example: You do not have permission to perform this action. examples: InvalidKey: summary: Missing or invalid x-api-key header value: detail: You do not have permission to perform this action. '404': description: 'No User Verification (KYC) session with this UUID is visible to your application: the UUID does not exist, the session was deleted, or it belongs to a different application. Business Verification (KYB) session IDs also return `404` here — the reviews feed is only addressable for KYC sessions.' content: application/json: schema: type: object properties: detail: type: string example: Session not found. examples: NotFound: summary: Unknown, deleted, or cross-application session UUID value: detail: Session not found. '429': description: Rate limit exceeded. Inspect `Retry-After` and `X-RateLimit-*` response headers. See [Rate limiting](/integration/rate-limiting). security: - ApiKeyAuth: [] x-codeSamples: - lang: curl label: curl source: "curl -s 'https://verification.didit.me/v3/sessions/8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d/reviews/?limit=50' \\\n -H 'x-api-key: YOUR_API_KEY'" - lang: Python label: Python (requests) source: "import os, requests\n\nsession_id = \"8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d\"\nurl = f\"https://verification.didit.me/v3/sessions/{session_id}/reviews/\"\n\nwhile url: # walk every page of the feed\n resp = requests.get(url, headers={\"x-api-key\": os.environ[\"DIDIT_API_KEY\"]}, timeout=10)\n resp.raise_for_status()\n page = resp.json()\n for entry in page[\"results\"]:\n print(entry[\"created_at\"], entry[\"activity_type\"], entry[\"actor_display\"], entry.get(\"new_status\"))\n url = page[\"next\"]" - lang: JavaScript label: Node.js (fetch) source: "const sessionId = \"8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d\";\nconst res = await fetch(\n `https://verification.didit.me/v3/sessions/${sessionId}/reviews/`,\n { headers: { 'x-api-key': 'YOUR_API_KEY' } },\n);\nif (!res.ok) throw new Error(`Didit ${res.status}`);\nconst { count, results } = await res.json();\nconsole.log(`${count} feed entries`);\nfor (const entry of results) {\n console.log(entry.created_at, entry.activity_type, entry.actor_display, entry.new_status ?? \"\");\n}" post: summary: Add a review note to a session's audit trail description: 'Append a note to a User Verification (KYC) session''s review / activity feed. **This endpoint only records an entry — it never changes the session.** The session''s `status` is left untouched, no status-transition validation runs, and no webhook fires. To actually approve, decline, or re-open a session (which also writes a fully-attributed `STATUS_UPDATED` entry to this feed), use `PATCH /v3/session/{session_id}/update-status/` instead. Both body fields are optional and an empty JSON object is accepted. `new_status` is stored purely as a label on the entry and accepts any of the ten session status values (case-sensitive). `@email` mentions inside `comment` are stored as plain text only — they trigger no notifications on this endpoint, and the entry''s `mentioned_emails` stays empty. Note on attribution: entries created here carry no actor information. In the feed they show `actor_display: "Unknown"`, `actor_type: "CONSOLE_USER"`, and `activity_type: "STATUS_UPDATED"` (storage defaults), and the `201` body''s `user` field is `"Unknown"`.' operationId: create_session_review tags: - Sessions parameters: - name: Content-Type in: header required: false description: Set to `application/json` when sending a JSON body. schema: type: string example: application/json - name: session_id in: path required: true description: UUID of the User Verification (KYC) session. Must belong to the same application as the API key — cross-application UUIDs, deleted sessions, and Business Verification (KYB) session IDs all return `404`. schema: type: string format: uuid example: 8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d requestBody: required: false content: application/json: schema: type: object properties: new_status: type: string nullable: true enum: - Not Started - In Progress - Approved - Declined - In Review - Expired - Abandoned - Kyc Expired - Resubmitted - Awaiting User description: Optional status label to record on the entry. Accepts any session status value, case-sensitive (`"approved"` is rejected with `400`). Recording a status here does **not** change the session's actual status — use the update-status endpoint for that. example: In Review comment: type: string nullable: true description: Optional free-text note (no enforced length limit). Stored verbatim; `@email` mentions are not processed on this endpoint. example: Flagging for manual document re-check. examples: NoteWithStatusLabel: summary: Note carrying a status label (session status unchanged) value: new_status: In Review comment: Flagging for manual document re-check. CommentOnly: summary: Plain note without a status label value: comment: Customer emailed an updated utility bill; awaiting upload. responses: '201': description: 'Entry recorded on the session''s audit trail. The session itself is unchanged — its `status` keeps its previous value and no webhook is sent. The new entry also appears at the top of `GET …/reviews/` (with `actor_display: "Unknown"`).' content: application/json: schema: type: object properties: user: type: string description: Display name of the entry's author. Always `"Unknown"` for entries created through this endpoint, because it records no actor attribution. example: Unknown new_status: type: string nullable: true enum: - Not Started - In Progress - Approved - Declined - In Review - Expired - Abandoned - Kyc Expired - Resubmitted - Awaiting User description: The status label recorded on the entry, or `null` if none was sent. The session's actual status is not affected. example: In Review comment: type: string nullable: true description: The note recorded on the entry, or `null` if none was sent. example: Flagging for manual document re-check. created_at: type: string format: date-time description: When the entry was written, ISO 8601 UTC. example: '2026-06-12T00:18:44.466537Z' examples: NoteWithStatusLabel: summary: Note with a status label value: user: Unknown new_status: In Review comment: Flagging for manual document re-check. created_at: '2026-06-12T00:18:44.466537Z' CommentOnly: summary: Plain note value: user: Unknown new_status: null comment: Note without status. created_at: '2026-06-12T00:18:44.504157Z' '400': description: Validation error, returned as a per-field map of messages. The usual cause is a `new_status` value that is not one of the ten allowed statuses — the check is case-sensitive. content: application/json: schema: type: object properties: new_status: type: array items: type: string example: - '"approved" is not a valid choice.' examples: InvalidStatusChoice: summary: Lowercase / unknown status value value: new_status: - '"approved" is not a valid choice.' '403': description: Missing, malformed, expired, or revoked `x-api-key`. Authentication is checked before the session lookup, so an invalid key returns `403` even when the session UUID does not exist. content: application/json: schema: type: object properties: detail: type: string example: You do not have permission to perform this action. examples: InvalidKey: summary: Missing or invalid x-api-key header value: detail: You do not have permission to perform this action. '404': description: 'No User Verification (KYC) session with this UUID is visible to your application: the UUID does not exist, the session was deleted, or it belongs to a different application. Business Verification (KYB) session IDs also return `404` here — the reviews feed is only addressable for KYC sessions.' content: application/json: schema: type: object properties: detail: type: string example: Session not found. examples: NotFound: summary: Unknown, deleted, or cross-application session UUID value: detail: Session not found. '429': description: Rate limit exceeded. Inspect `Retry-After` and `X-RateLimit-*` response headers. See [Rate limiting](/integration/rate-limiting). security: - ApiKeyAuth: [] x-codeSamples: - lang: curl label: curl source: "# Records an audit-trail note only -- the session's status does NOT change.\ncurl -s -X POST https://verification.didit.me/v3/sessions/8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d/reviews/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"new_status\": \"In Review\",\n \"comment\": \"Flagging for manual document re-check.\"\n }'" - lang: Python label: Python (requests) source: "import os, requests\n\n# Records an audit-trail note only -- the session's status does NOT change.\n# To approve/decline a session use PATCH /v3/session/{session_id}/update-status/.\nsession_id = \"8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d\"\nresp = requests.post(\n f\"https://verification.didit.me/v3/sessions/{session_id}/reviews/\",\n headers={\n \"x-api-key\": os.environ[\"DIDIT_API_KEY\"],\n \"Content-Type\": \"application/json\",\n },\n json={\n \"new_status\": \"In Review\",\n \"comment\": \"Flagging for manual document re-check.\",\n },\n timeout=10,\n)\nresp.raise_for_status()\nprint(resp.json()) # {'user': 'Unknown', 'new_status': 'In Review', ...}" - lang: JavaScript label: Node.js (fetch) source: "// Records an audit-trail note only -- the session's status does NOT change.\n// To approve/decline a session use PATCH /v3/session/{session_id}/update-status/.\nconst sessionId = \"8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d\";\nconst res = await fetch(\n `https://verification.didit.me/v3/sessions/${sessionId}/reviews/`,\n {\n method: \"POST\",\n headers: {\n 'x-api-key': 'YOUR_API_KEY',\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n new_status: \"In Review\",\n comment: \"Flagging for manual document re-check.\",\n }),\n },\n);\nif (!res.ok) throw new Error(`Didit ${res.status}: ${await res.text()}`);\nconsole.log(await res.json()); // { user: 'Unknown', new_status: 'In Review', ... }" /v3/session/{sessionId}/update-data/: patch: summary: Update KYC data extracted from the ID document on a verification session description: 'Correct the identity fields that were extracted (OCR / NFC) from the user''s ID document — misread names, dates, document numbers, and so on — after the verification has finished. Typical callers are a compliance analyst working from your own back office, or an automated reconciliation job. Every field in the body is optional: only the keys you send are written, everything else is left untouched (`PATCH` semantics). The session must belong to your application and be in `Approved`, `Declined`, `In Review`, or `Kyc Expired` status. Sessions in any other state (e.g. still `In Progress`) return `404` with `"No Session matches the given query."` — distinct from the `"Not found."` returned for an unknown or deleted `sessionId`. The session must also carry an ID-verification (KYC) record: sessions whose workflow never ran an ID-verification step return `404` (`"The session does not have a KYC."`). For graph workflows with multiple ID-verification steps, target a specific step with `node_id` (query parameter or body field); when omitted, the most recent KYC record is patched. Two derived fields are recomputed on every call: `full_name` (from `first_name` + `last_name`) and `issuing_state_name` (from `issuing_state`). If the free-text `address` changes, the server may also re-geocode it and refresh `parsed_address` (skipped for sandbox sessions). `extra_fields` and `parsed_address` are merged key-by-key rather than replaced — see the per-field descriptions for the `null`-to-delete semantics. Submitting at least one valid field fires the `data.updated` [webhook](/integration/webhooks) (trigger `manual_review`) — even when the submitted values equal the stored ones. When values actually change, a `KYC_DATA_UPDATED` activity entry recording the changed fields with previous and new values is appended to the session''s `reviews`, visible in [Get Decision](#get-/v3/session/-sessionId-/decision/) and the console activity timeline. The session''s status and decision are **not** re-evaluated — use [Update Status](#patch-/v3/session/-sessionId-/update-status/) to change the outcome. An empty JSON body (`{}`) is accepted and simply returns the current KYC data without firing the webhook. Accepts an API key or a console user token; either way the credential needs the `write:sessions` permission. Authentication and permission failures both return `403` — this API never responds `401`. Note that the session lookup runs before credential checks, so an unknown `sessionId` returns `404` even with missing credentials. This endpoint has a dedicated rate limit of 10 requests per minute per API key (invalid requests count too); the shared write budget of 300 requests per minute also applies.' operationId: patch_v3_session_update_data tags: - Sessions security: - ApiKeyAuth: [] parameters: - name: sessionId in: path required: true description: UUID of the User Verification (KYC) session whose extracted document data you are correcting. Business (KYB) session IDs are not valid here — for company data use [Update KYB Company Data](#patch-/v3/session/-sessionId-/kyb/-companyUuid-/update-data/). schema: type: string format: uuid example: 11111111-2222-3333-4444-555555555555 - name: node_id in: query required: false description: Workflow graph node whose KYC record to patch, for graph workflows with multiple ID-verification steps (also accepted as a `node_id` body field). When omitted, the most recent KYC record on the session is patched. If no KYC exists for the given node, the call returns `404` (`"The session does not have a KYC for node ''."`). schema: type: string example: feature_ocr requestBody: required: true content: application/json: schema: type: object description: All fields optional — send only what you want to change. properties: document_type: type: string description: 'Document type code. Accepts the short codes listed in the enum, plus six long-form aliases normalized server-side before validation: `Passport` → `P`, `Identity Card` → `ID`, `Driver''s License` → `DL`, `Residence Permit` → `RP`, `Health Insurance Card` → `HIC`, `Tax Card` → `TC`. Any other value (including `PASSPORT` in caps) returns `400`.' enum: - P - DL - ID - RP - SSC - HIC - WP - TC - VISA - PSC - BC - OTHER nullable: true example: P document_subtype: type: string maxLength: 128 nullable: true description: Document subtype from the document registry, including the region prefix when the registry distinguishes regional variants. document_number: type: string maxLength: 255 nullable: true description: Document number as printed on the ID. example: PAB123456 personal_number: type: string maxLength: 255 nullable: true description: Personal / national identification number where the document carries one (e.g. Spanish DNI on a passport). example: 99999999R date_of_birth: type: string format: date nullable: true description: Date of birth in `YYYY-MM-DD`. Any other format returns `400`. example: '1980-01-01' date_of_issue: type: string format: date nullable: true description: Issue date of the document in `YYYY-MM-DD`. expiration_date: type: string format: date nullable: true description: Expiration date of the document in `YYYY-MM-DD`. issuing_state: type: string nullable: true description: Issuing country as an ISO 3166-1 alpha-3 code. Case-insensitive (`esp` is stored as `ESP`); the human-readable `issuing_state_name` shown in [Get Decision](#get-/v3/session/-sessionId-/decision/) is recomputed from this value. Invalid codes return `400`. example: ESP first_name: type: string maxLength: 255 nullable: true description: Given name(s). The stored `full_name` is recomputed as `first_name + ' ' + last_name` on every call to this endpoint. example: Carmen last_name: type: string maxLength: 255 nullable: true description: Family name(s). Also feeds the recomputed `full_name`. example: Espanola Espanola gender: type: string enum: - M - F - U nullable: true description: 'Sex as printed on the document: `M` (male), `F` (female), or `U` (unknown). Any other value — including `X` — returns `400`.' address: type: string maxLength: 255 nullable: true description: Free-text address as printed on the document. When this value changes (non-sandbox sessions only) and the structured address is still missing or incomplete, the server re-geocodes it synchronously before responding — the 200 body already reflects the refreshed `parsed_address`. The re-geocode also requires `issuing_state` to be present on the KYC record. place_of_birth: type: string maxLength: 255 nullable: true description: Place of birth as printed on the document. nationality: type: string maxLength: 32 nullable: true description: Nationality as printed on the document. Free text up to 32 characters — typically an ISO 3166-1 alpha-3 code (e.g. `ESP`), but not validated as one. marital_status: type: string enum: - SINGLE - MARRIED - DIVORCED - WIDOWED - UNKNOWN description: Marital status where the document carries it. Any other value returns `400`. extra_fields: type: object additionalProperties: true nullable: true description: 'Per-key overrides for document-specific extra fields (e.g. `blood_group`, `dl_categories`, `mrz_string`). Keys are merged into the existing override map: send a key with a value to set it, or with `null` / `""` to remove that key from the merged view. Send the whole field as `null` to clear all previously applied overrides. Must be a JSON object, otherwise `400`. The response always returns the merged view (OCR-extracted values with your overrides applied).' parsed_address: type: object nullable: true description: 'Structured address. Only these nested keys are accepted: `address_type`, `city`, `region`, `street_1`, `street_2`, `postal_code`, `country` — any other key returns `400`. Per-key semantics: a value sets the key, `null` removes it. `formatted_address` is recomputed server-side by joining `street_1`, `street_2`, `city`, `region`, `postal_code`, and `country` and is returned in the response — you cannot set it directly.' properties: address_type: type: string nullable: true description: Free-form address classification (e.g. `home`). city: type: string nullable: true example: Madrid region: type: string nullable: true description: State / province / autonomous community. street_1: type: string nullable: true example: Avda de Madrid 34 street_2: type: string nullable: true postal_code: type: string nullable: true example: '28013' country: type: string nullable: true description: ISO 3166-1 alpha-2 country code. Lowercase input is normalized to uppercase; alpha-3 codes are rejected with `400`. example: ES examples: Correct name and DOB: summary: Fix OCR-misread name, date of birth, and structured address value: first_name: Carmen last_name: Espanola Espanola date_of_birth: '1980-01-01' issuing_state: ESP marital_status: MARRIED extra_fields: blood_group: O+ parsed_address: street_1: Avda de Madrid 34 city: Madrid postal_code: '28013' country: ES Remove an extra field: summary: Delete one extra-field key, keep the rest value: extra_fields: blood_group: null Long-form document type: summary: Long-form alias is normalized to the short code (here P) value: document_type: Passport responses: '200': description: KYC data updated. Returns the **full current KYC record** (every updatable field with its stored value, plus the merged `extra_fields` and the current `parsed_address`) — not just the fields you submitted. The `data.updated` webhook fires when at least one valid field was in the payload. content: application/json: schema: type: object properties: document_type: type: string description: 'Document type code. Accepts the short codes listed in the enum, plus six long-form aliases normalized server-side before validation: `Passport` → `P`, `Identity Card` → `ID`, `Driver''s License` → `DL`, `Residence Permit` → `RP`, `Health Insurance Card` → `HIC`, `Tax Card` → `TC`. Any other value (including `PASSPORT` in caps) returns `400`.' enum: - P - DL - ID - RP - SSC - HIC - WP - TC - VISA - PSC - BC - OTHER nullable: true example: P document_subtype: type: string maxLength: 128 nullable: true description: Document subtype from the document registry, including the region prefix when the registry distinguishes regional variants. document_number: type: string maxLength: 255 nullable: true description: Document number as printed on the ID. example: PAB123456 personal_number: type: string maxLength: 255 nullable: true description: Personal / national identification number where the document carries one (e.g. Spanish DNI on a passport). example: 99999999R date_of_birth: type: string format: date nullable: true description: Date of birth in `YYYY-MM-DD`. Any other format returns `400`. example: '1980-01-01' date_of_issue: type: string format: date nullable: true description: Issue date of the document in `YYYY-MM-DD`. expiration_date: type: string format: date nullable: true description: Expiration date of the document in `YYYY-MM-DD`. issuing_state: type: string nullable: true description: Issuing country as an ISO 3166-1 alpha-3 code. Case-insensitive (`esp` is stored as `ESP`); the human-readable `issuing_state_name` shown in [Get Decision](#get-/v3/session/-sessionId-/decision/) is recomputed from this value. Invalid codes return `400`. example: ESP first_name: type: string maxLength: 255 nullable: true description: Given name(s). The stored `full_name` is recomputed as `first_name + ' ' + last_name` on every call to this endpoint. example: Carmen last_name: type: string maxLength: 255 nullable: true description: Family name(s). Also feeds the recomputed `full_name`. example: Espanola Espanola gender: type: string enum: - M - F - U nullable: true description: 'Sex as printed on the document: `M` (male), `F` (female), or `U` (unknown). Any other value — including `X` — returns `400`.' address: type: string maxLength: 255 nullable: true description: Free-text address as printed on the document. When this value changes (non-sandbox sessions only) and the structured address is still missing or incomplete, the server re-geocodes it in the background of the same request and may overwrite `parsed_address` with the geocoder result. place_of_birth: type: string maxLength: 255 nullable: true description: Place of birth as printed on the document. nationality: type: string maxLength: 32 nullable: true description: Nationality as printed on the document. Free text up to 32 characters — typically an ISO 3166-1 alpha-3 code (e.g. `ESP`), but not validated as one. marital_status: type: string enum: - SINGLE - MARRIED - DIVORCED - WIDOWED - UNKNOWN description: Marital status where the document carries it. Any other value returns `400`. extra_fields: type: object additionalProperties: true nullable: true description: 'Merged view of the document''s extra fields: the OCR-extracted values with all operator overrides applied. Keys removed via a `null` override are absent from this map.' parsed_address: type: object nullable: true description: Current structured address (with internal bookkeeping keys stripped), or `null` when none exists. Includes the recomputed `formatted_address` alongside the seven editable keys. properties: address_type: type: string nullable: true description: Free-form address classification (e.g. `home`). city: type: string nullable: true example: Madrid region: type: string nullable: true description: State / province / autonomous community. street_1: type: string nullable: true example: Avda de Madrid 34 street_2: type: string nullable: true postal_code: type: string nullable: true example: '28013' country: type: string nullable: true description: ISO 3166-1 alpha-2 country code. Lowercase input is normalized to uppercase; alpha-3 codes are rejected with `400`. example: ES formatted_address: type: string nullable: true description: Joined, human-readable form of the structured address. Recomputed server-side after every `parsed_address` edit. example: Avda de Madrid 34, Madrid, 28013, ES examples: Updated KYC: summary: Full KYC record after the correction value: document_type: P document_subtype: null document_number: PAB123456 personal_number: 99999999R date_of_birth: '1980-01-01' date_of_issue: '2021-02-09' expiration_date: '2031-02-08' issuing_state: ESP first_name: Carmen last_name: Espanola Espanola gender: F address: AVDA DE MADRID 34, MADRID, MADRID 28013 place_of_birth: MADRID nationality: ESP marital_status: MARRIED extra_fields: mrz_string: P": [""]}`; errors on nested `parsed_address` / `extra_fields` keys use an object value: `{"parsed_address": {"": ""}}`.' content: application/json: examples: Invalid gender: summary: gender must be M, F, or U value: gender: - '"X" is not a valid choice.' Invalid document type: summary: Not a short code or long-form alias value: document_type: - '"PASSPORT" is not a valid choice.' Invalid issuing state: summary: issuing_state is not ISO 3166-1 alpha-3 value: issuing_state: - Invalid ISO 3166-1 alpha-3 country code for issuing_state. Invalid marital status: summary: marital_status not in the enum value: marital_status: - '"ENGAGED" is not a valid choice.' Bad date format: summary: Dates must be YYYY-MM-DD value: date_of_birth: - 'Date has wrong format. Use one of these formats instead: YYYY-MM-DD.' Unsupported parsed_address key: summary: Only the seven documented keys are allowed value: parsed_address: zipcode: 'Unsupported key. Allowed keys: address_type, city, region, street_1, street_2, postal_code, country' Invalid parsed_address country: summary: Nested country must be alpha-2, not alpha-3 value: parsed_address: country: Invalid ISO 3166-1 alpha-2 country code. extra_fields not an object: summary: extra_fields must be a JSON object value: extra_fields: - This field must be a JSON object. '403': description: Missing/invalid credentials or insufficient permissions. This API returns `403` for authentication failures — never `401`. content: application/json: examples: Invalid Credentials: summary: Missing or invalid x-api-key value: detail: Authentication credentials were not provided or are invalid. Missing Privilege: summary: Credential lacks write:sessions value: detail: You do not have permission to perform this action. '404': description: Unknown session, session in a non-editable state, or no KYC record to patch. Each case has a distinct `detail` message (see examples). The unknown-session lookup runs before credential checks. content: application/json: examples: Unknown session: summary: No session with this ID in your application (or it was deleted) value: detail: Not found. Wrong session state: summary: Session exists but is not Approved / Declined / In Review / Kyc Expired value: detail: No Session matches the given query. No KYC record: summary: The workflow never ran an ID-verification step value: detail: The session does not have a KYC. No KYC for node: summary: node_id given but no KYC exists for it value: detail: The session does not have a KYC for node 'feature_missing'. '429': description: Dedicated rate limit exceeded — 10 KYC data updates per minute per API key (rejected requests count toward the budget). The shared 300/min write budget can also trigger with its own message. Inspect `Retry-After` and the `X-RateLimit-*` response headers before retrying. content: application/json: schema: type: object properties: detail: type: string examples: Rate limited: summary: KYC update budget exhausted value: detail: Session KYC data update rate limit exceeded. You can make up to 10 requests per minute. x-codeSamples: - lang: curl label: curl source: "curl -X PATCH \\\n https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/update-data/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"first_name\": \"Carmen\",\n \"last_name\": \"Espanola Espanola\",\n \"date_of_birth\": \"1980-01-01\",\n \"issuing_state\": \"ESP\",\n \"parsed_address\": {\n \"street_1\": \"Avda de Madrid 34\",\n \"city\": \"Madrid\",\n \"postal_code\": \"28013\",\n \"country\": \"ES\"\n }\n }'" - lang: python label: Python source: "import requests\n\nresponse = requests.patch(\n \"https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/update-data/\",\n headers={\n \"x-api-key\": \"YOUR_API_KEY\",\n \"Content-Type\": \"application/json\",\n },\n json={\n \"first_name\": \"Carmen\",\n \"last_name\": \"Espanola Espanola\",\n \"date_of_birth\": \"1980-01-01\",\n \"issuing_state\": \"ESP\",\n \"extra_fields\": {\"blood_group\": \"O+\"},\n \"parsed_address\": {\"city\": \"Madrid\", \"postal_code\": \"28013\", \"country\": \"ES\"},\n },\n)\nresponse.raise_for_status()\nkyc = response.json() # full current KYC record\nprint(kyc[\"first_name\"], kyc[\"parsed_address\"][\"formatted_address\"])" - lang: javascript label: JavaScript source: "const response = await fetch(\n 'https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/update-data/',\n {\n method: 'PATCH',\n headers: {\n 'x-api-key': process.env.DIDIT_API_KEY,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n first_name: 'Carmen',\n last_name: 'Espanola Espanola',\n date_of_birth: '1980-01-01',\n issuing_state: 'ESP',\n parsed_address: { city: 'Madrid', postal_code: '28013', country: 'ES' },\n }),\n },\n);\nif (!response.ok) throw new Error(`HTTP ${response.status}`);\nconst kyc = await response.json(); // full current KYC record" /v3/session/{sessionId}/features/{nodeId}/update-status/: patch: summary: Update an individual feature (step) status description: 'Set the status of a single verification feature (step) within a session **without changing the overall session decision**. Target the feature by its workflow graph `nodeId` (use `default` for non-graph workflows where the step has no node). Works for both User Verification (KYC) and Business Verification (KYB) sessions and every step type — ID/OCR, Liveness, Face Match, NFC, AML, Proof of Address, Phone, Email, Database Validation, Questionnaire, and the KYB registry / documents / key-people checks. `new_status` must be one of `Approved`, `Declined`, or `In Review`, and must differ from the feature''s current status. A successful call fires a `data.updated` webhook and appends an activity entry (with actor attribution) to the session''s review trail. This endpoint is **not** available for standalone API sessions (`session_type = API`). Authenticate with your API key (`x-api-key`) or a user authorization header — either must carry the `write:sessions` privilege.' operationId: patch_v3_session_update_feature_status tags: - Sessions security: - ApiKeyAuth: [] parameters: - in: path name: sessionId required: true description: UUID of the verification session. Accepts both user (KYC) and business (KYB) session IDs — the service resolves the ID across both session types. schema: type: string format: uuid example: 11111111-2222-3333-4444-555555555555 - in: path name: nodeId required: true description: Workflow graph node ID of the feature to update (for example `feature_ocr_1`). Use the literal value `default` for non-graph (single-step) workflows where the feature has no node ID. schema: type: string example: feature_ocr_1 requestBody: required: true content: application/json: schema: type: object required: - new_status properties: new_status: type: string description: Target status for this feature. Must differ from the current value. enum: - Approved - Declined - In Review example: Approved comment: type: string description: Optional free-text note recorded on the session's review trail for this feature change. example: Document manually verified by compliance. responses: '200': description: Feature status updated. Returns the targeted feature, its node ID, and the new status. content: application/json: schema: type: object properties: session_id: type: string format: uuid node_id: type: string example: feature_ocr_1 feature_type: type: string description: Model name of the updated feature (e.g. `KYC`, `Face`, `FaceMatch`, `AML`, `POA`). example: KYC new_status: type: string example: Approved example: session_id: 11111111-2222-3333-4444-555555555555 node_id: feature_ocr_1 feature_type: KYC new_status: Approved '400': description: Validation error. Returned when `new_status` is invalid or unchanged, or when the session is a standalone API session. content: application/json: examples: Same Status: summary: new_status equals the current status value: new_status: The new status is the same as the current status. API Session: summary: Feature status cannot be changed on standalone API sessions value: detail: Cannot change feature status for API sessions. '403': description: Missing/invalid credentials or insufficient permissions. This API returns `403` for authentication failures — never `401`. content: application/json: examples: Invalid Credentials: summary: Missing or invalid x-api-key value: detail: Authentication credentials were not provided or are invalid. Missing Privilege: summary: API key lacks write:sessions value: detail: You do not have permission to perform this action. '404': description: The session was not found, or it has no feature with the given `nodeId`. content: application/json: examples: Session Not Found: summary: Unknown session_id value: detail: Session not found. Feature Not Found: summary: No feature for this node_id value: detail: No feature found with node_id 'feature_ocr_1' in this session. x-codeSamples: - lang: curl label: curl source: "curl -X PATCH \\\n https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/features/feature_ocr_1/update-status/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"new_status\": \"Approved\",\n \"comment\": \"Document manually verified by compliance.\"\n }'" - lang: python label: Python source: "import requests\n\nresponse = requests.patch(\n \"https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/features/feature_ocr_1/update-status/\",\n headers={\"x-api-key\": \"YOUR_API_KEY\", \"Content-Type\": \"application/json\"},\n json={\"new_status\": \"Approved\", \"comment\": \"Document manually verified by compliance.\"},\n)\nprint(response.json())" /v3/session/{sessionId}/update-aml-hit-status/: patch: summary: Update an AML hit review status description: 'Set the review status of a **single AML hit** (a potential sanction / PEP / watchlist / adverse-media match) returned by AML screening, without changing the overall session decision. Identify the hit by its `hit_id` (the `id` of an entry in the AML check''s `hits` array — see the session decision). `review_status` is one of `Unreviewed`, `Confirmed Match`, `False Positive`, or `Inconclusive`. Reviewer decisions are preserved across ongoing-monitoring updates, so a hit you mark `False Positive` stays that way when the provider re-screens. A successful call fires a `data.updated` webhook and appends an activity entry (with actor attribution) to the session''s review trail. For graph workflows with multiple AML checks, pass `node_id` to target a specific check. Authenticate with your API key (`x-api-key`) or a user authorization header — either must carry the `write:sessions` privilege.' operationId: patch_v3_session_update_aml_hit_status tags: - Sessions security: - ApiKeyAuth: [] parameters: - in: path name: sessionId required: true description: UUID of the verification session. Accepts both user (KYC) and business (KYB) session IDs — the service resolves the ID across both session types. schema: type: string format: uuid example: 11111111-2222-3333-4444-555555555555 - in: query name: node_id required: false description: Workflow graph node ID. Required only when the session has multiple AML checks (one per node); omit it for single-AML sessions. schema: type: string example: feature_aml_1 requestBody: required: true content: application/json: schema: type: object required: - hit_id - review_status properties: hit_id: type: string description: The `id` of the hit in the AML check's `hits` array. example: abc123 review_status: type: string description: The new review status for the hit. enum: - Unreviewed - Confirmed Match - False Positive - Inconclusive example: False Positive node_id: type: string description: Optional graph node ID. Use it (here or as a query parameter) when the session has multiple AML checks. example: feature_aml_1 responses: '200': description: Hit review status updated. content: application/json: example: message: Hit status updated successfully hit_id: abc123 review_status: False Positive '400': description: Validation error — `hit_id` empty or `review_status` not one of the allowed values. content: application/json: example: review_status: - '"Maybe" is not a valid choice.' '403': description: Missing/invalid credentials or insufficient permissions. This API returns `403` for authentication failures — never `401`. content: application/json: examples: Invalid Credentials: summary: Missing or invalid x-api-key value: detail: Authentication credentials were not provided or are invalid. Missing Privilege: summary: API key lacks write:sessions value: detail: You do not have permission to perform this action. '404': description: The session was not found, the session has no AML check (optionally for the given `node_id`), or no hit matches `hit_id`. content: application/json: examples: No AML Check: summary: Session has no AML check value: detail: The session does not have an AML check. Hit Not Found: summary: Unknown hit_id value: detail: Hit with ID 'abc123' not found in the AML check. x-codeSamples: - lang: curl label: curl source: "curl -X PATCH \\\n https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/update-aml-hit-status/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"hit_id\": \"abc123\",\n \"review_status\": \"False Positive\"\n }'" - lang: python label: Python source: "import requests\n\nresponse = requests.patch(\n \"https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/update-aml-hit-status/\",\n headers={\"x-api-key\": \"YOUR_API_KEY\", \"Content-Type\": \"application/json\"},\n json={\"hit_id\": \"abc123\", \"review_status\": \"False Positive\"},\n)\nprint(response.json())" /v3/session/{sessionId}/bulk-update-aml-hit-status/: patch: summary: Bulk update AML hit review statuses description: 'Update the review status of **multiple AML hits** in one call. Provide `hit_updates`, a list of `{ hit_id, review_status }` pairs. `review_status` is one of `Unreviewed`, `Confirmed Match`, `False Positive`, or `Inconclusive`. The update is all-or-nothing: if any `hit_id` is not found in the AML check, the request returns `404` and no hit is changed. A successful call fires a single `data.updated` webhook and records one activity entry covering all changes. For graph workflows with multiple AML checks, pass `node_id` to target a specific check. Authenticate with your API key (`x-api-key`) or a user authorization header — either must carry the `write:sessions` privilege.' operationId: patch_v3_session_bulk_update_aml_hit_status tags: - Sessions security: - ApiKeyAuth: [] parameters: - in: path name: sessionId required: true description: UUID of the verification session. Accepts both user (KYC) and business (KYB) session IDs — the service resolves the ID across both session types. schema: type: string format: uuid example: 11111111-2222-3333-4444-555555555555 - in: query name: node_id required: false description: Workflow graph node ID. Required only when the session has multiple AML checks (one per node); omit it for single-AML sessions. schema: type: string example: feature_aml_1 requestBody: required: true content: application/json: schema: type: object required: - hit_updates properties: hit_updates: type: array description: Non-empty list of hit updates. items: type: object required: - hit_id - review_status properties: hit_id: type: string example: abc123 review_status: type: string enum: - Unreviewed - Confirmed Match - False Positive - Inconclusive example: Confirmed Match node_id: type: string description: Optional graph node ID. Use it (here or as a query parameter) when the session has multiple AML checks. example: feature_aml_1 responses: '200': description: All hits updated. content: application/json: example: message: Updated 2 hit(s) successfully updated_hits: - abc123 - def456 '400': description: Validation error — `hit_updates` empty, or an entry is missing `hit_id` / `review_status` or carries an invalid value. content: application/json: example: hit_updates: - hit_updates cannot be empty '403': description: Missing/invalid credentials or insufficient permissions. This API returns `403` for authentication failures — never `401`. content: application/json: examples: Invalid Credentials: summary: Missing or invalid x-api-key value: detail: Authentication credentials were not provided or are invalid. Missing Privilege: summary: API key lacks write:sessions value: detail: You do not have permission to perform this action. '404': description: The session was not found, the session has no AML check, or one or more `hit_id`s do not exist (no hit is changed in that case). content: application/json: example: detail: 'Hits not found: zzz999' x-codeSamples: - lang: curl label: curl source: "curl -X PATCH \\\n https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/bulk-update-aml-hit-status/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"hit_updates\": [\n { \"hit_id\": \"abc123\", \"review_status\": \"Confirmed Match\" },\n { \"hit_id\": \"def456\", \"review_status\": \"False Positive\" }\n ]\n }'" - lang: python label: Python source: "import requests\n\nresponse = requests.patch(\n \"https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/bulk-update-aml-hit-status/\",\n headers={\"x-api-key\": \"YOUR_API_KEY\", \"Content-Type\": \"application/json\"},\n json={\n \"hit_updates\": [\n {\"hit_id\": \"abc123\", \"review_status\": \"Confirmed Match\"},\n {\"hit_id\": \"def456\", \"review_status\": \"False Positive\"},\n ]\n },\n)\nprint(response.json())" /v3/session/{sessionId}/update-poa-data/: patch: summary: Update Proof of Address data extracted from the POA document on a verification session description: 'Correct the fields extracted from the user''s Proof of Address document — utility bill, bank statement, or other address evidence — after the verification has finished: issuer, dates, the printed name, the address itself, and banking details. Every field in the body is optional: only the keys you send are written, everything else is left untouched (`PATCH` semantics). The session must belong to your application and be in `Approved`, `Declined`, `In Review`, or `Kyc Expired` status. Sessions in any other state (e.g. still `In Progress`) return `404` with `"No Session matches the given query."` — distinct from the `"Not found."` returned for an unknown or deleted `sessionId`. The session must also carry a POA record: sessions whose workflow never ran a Proof of Address step return `404` (`"The session does not have a POA."`). For graph workflows with multiple POA steps, target a specific step with `node_id` (query parameter or body field); when omitted, the most recent POA record is patched. `extra_fields` is merged key-by-key: nine known keys (`bank_iban`, `additional_names`, …) write through to dedicated POA columns, any other key is kept as a custom extra, and `null` / `""` deletes a key. `poa_parsed_address` accepts only the seven documented keys and recomputes `formatted_address` / `poa_formatted_address` after every edit. If the free-text `poa_address` changes, the server may also re-geocode it and refresh the structured address (skipped for sandbox sessions). Unlike the KYC variant of this endpoint, the `data.updated` [webhook](/integration/webhooks) (trigger `manual_review`) fires only when at least one stored value **actually changed** — re-sending identical values is a silent no-op. When values change, a `POA_DATA_UPDATED` activity entry recording the changed fields with previous and new values is appended to the session''s `reviews`, visible in [Get Decision](#get-/v3/session/-sessionId-/decision/) and the console activity timeline. The session''s status and decision are **not** re-evaluated — use [Update Status](#patch-/v3/session/-sessionId-/update-status/) to change the outcome. Accepts an API key or a console user token; either way the credential needs the `write:sessions` permission. Authentication and permission failures both return `403` — this API never responds `401`. Note that the session lookup runs before credential checks, so an unknown `sessionId` returns `404` even with missing credentials. This endpoint has a dedicated rate limit of 10 requests per minute per API key (invalid requests count too); the shared write budget of 300 requests per minute also applies. An empty JSON body `{}` is a no-op: it returns `200` echoing the current POA record without firing the webhook (`has_changes` stays false).' operationId: patch_v3_session_update_poa_data tags: - Sessions security: - ApiKeyAuth: [] parameters: - name: sessionId in: path required: true description: UUID of the User Verification (KYC) session whose attached POA record you are correcting. schema: type: string format: uuid example: 11111111-2222-3333-4444-555555555555 - name: node_id in: query required: false description: Workflow graph node whose POA record to patch, for graph workflows with multiple Proof of Address steps (also accepted as a `node_id` body field). When omitted, the most recent POA record on the session is patched. If no POA exists for the given node, the call returns `404` (`"The session does not have a POA for node ''."`). schema: type: string example: feature_poa requestBody: required: true content: application/json: schema: type: object description: All fields optional — send only what you want to change. properties: issuing_state: type: string nullable: true description: Country that issued the POA document, ISO 3166-1 alpha-3. Case-insensitive (normalized to uppercase); invalid codes return `400`. example: ESP document_type: type: string enum: - UTILITY_BILL - BANK_STATEMENT - GOVERNMENT_ISSUED_DOCUMENT - OTHER_POA_DOCUMENT - UNKNOWN description: POA document category. Values are uppercase — lowercase variants like `utility_bill` return `400`. example: UTILITY_BILL document_language: type: string maxLength: 50 nullable: true description: Language of the document, typically an ISO 639-1 code (e.g. `es`). Free text — not validated as a language code. example: es issuer: type: string maxLength: 255 nullable: true description: Organization that issued the document (utility company, bank, government agency). example: Energy Provider S.A. issue_date: type: string format: date nullable: true description: Issue date of the POA document in `YYYY-MM-DD`. Any other format returns `400`. example: '2026-04-12' poa_address: type: string nullable: true description: Free-text address as printed on the document. When this value changes (non-sandbox sessions only) and the structured address is still missing or incomplete, the server re-geocodes it in the background of the same request and may overwrite `poa_parsed_address` / `poa_formatted_address` with the geocoder result. The re-geocode also requires `issuing_state` to be present on the POA record. example: Avda de Madrid 34, 28013 Madrid name_on_document: type: string maxLength: 255 nullable: true description: Recipient name as printed on the document. example: Carmen Espanola extra_fields: type: object additionalProperties: true nullable: true description: 'Document-specific extras, merged key-by-key. Nine keys map to dedicated POA columns: `bank_account_number`, `bank_iban`, `bank_sort_code`, `bank_routing_number`, `bank_swift_bic`, `bank_branch_name`, `bank_branch_address`, `document_phone_number`, and `additional_names` (must be a list of strings or `null`, otherwise `400`). Any other key is stored as a custom extra field. Per-key semantics: a value sets the key; `null` or `""` deletes it. Must be a JSON object, otherwise `400`.' poa_parsed_address: type: object nullable: true description: 'Structured address. Only these nested keys are accepted: `address_type`, `city`, `region`, `street_1`, `street_2`, `postal_code`, `country` — any other key returns `400`. Per-key semantics: a value sets the key, `null` removes it. `formatted_address` (and the top-level `poa_formatted_address`) is recomputed server-side by joining `street_1`, `street_2`, `city`, `region`, `postal_code`, and `country` — you cannot set it directly.' properties: address_type: type: string nullable: true description: Free-form address classification (e.g. `home`). city: type: string nullable: true example: Madrid region: type: string nullable: true description: State / province / autonomous community. street_1: type: string nullable: true example: Avda de Madrid 34 street_2: type: string nullable: true postal_code: type: string nullable: true example: '28013' country: type: string nullable: true description: ISO 3166-1 alpha-2 country code. Lowercase input is normalized to uppercase; alpha-3 codes are rejected with `400`. example: ES examples: Correct issuer and address: summary: Fix document classification, issuer, and the structured address value: issuing_state: ESP document_type: BANK_STATEMENT issuer: Banco Example S.A. issue_date: '2026-05-02' name_on_document: Carmen Espanola poa_address: Avda de Madrid 34, 28013 Madrid extra_fields: bank_iban: ES9121000418450200051332 additional_names: - Carmen E. Espanola poa_parsed_address: street_1: Avda de Madrid 34 city: Madrid postal_code: '28013' country: ES Remove a banking detail: summary: Delete one extra-field key, keep the rest value: extra_fields: bank_iban: null responses: '200': description: POA data updated. Returns the **full current POA record** (every updatable field with its stored value, plus the complete `extra_fields` map, `poa_parsed_address`, and `poa_formatted_address`) — not just the fields you submitted. The `data.updated` webhook fires only when at least one value actually changed. content: application/json: schema: type: object properties: issuing_state: type: string nullable: true description: Country that issued the POA document, ISO 3166-1 alpha-3. Case-insensitive (normalized to uppercase); invalid codes return `400`. example: ESP document_type: type: string enum: - UTILITY_BILL - BANK_STATEMENT - GOVERNMENT_ISSUED_DOCUMENT - OTHER_POA_DOCUMENT - UNKNOWN description: POA document category. Values are uppercase — lowercase variants like `utility_bill` return `400`. example: UTILITY_BILL document_language: type: string maxLength: 50 nullable: true description: Language of the document, typically an ISO 639-1 code (e.g. `es`). Free text — not validated as a language code. example: es issuer: type: string maxLength: 255 nullable: true description: Organization that issued the document (utility company, bank, government agency). example: Energy Provider S.A. issue_date: type: string format: date nullable: true description: Issue date of the POA document in `YYYY-MM-DD`. Any other format returns `400`. example: '2026-04-12' poa_address: type: string nullable: true description: Free-text address as printed on the document. When this value changes (non-sandbox sessions only) and the structured address is still missing or incomplete, the server re-geocodes it in the background of the same request and may overwrite `poa_parsed_address` / `poa_formatted_address` with the geocoder result. example: Avda de Madrid 34, 28013 Madrid name_on_document: type: string maxLength: 255 nullable: true description: Recipient name as printed on the document. example: Carmen Espanola extra_fields: type: object additionalProperties: true nullable: true description: 'Always present in the response: the nine known keys (with `null` for unset ones, `additional_names` defaulting to `[]`) plus any custom keys previously stored.' poa_parsed_address: type: object nullable: true description: Current structured address, or `null` when none exists. Includes the recomputed `formatted_address` alongside the seven editable keys. Returned as stored — after a server-side geocode it can contain bookkeeping keys (e.g. `raw_results`) alongside the editable keys. properties: address_type: type: string nullable: true description: Free-form address classification (e.g. `home`). city: type: string nullable: true example: Madrid region: type: string nullable: true description: State / province / autonomous community. street_1: type: string nullable: true example: Avda de Madrid 34 street_2: type: string nullable: true postal_code: type: string nullable: true example: '28013' country: type: string nullable: true description: ISO 3166-1 alpha-2 country code. Lowercase input is normalized to uppercase; alpha-3 codes are rejected with `400`. example: ES formatted_address: type: string nullable: true description: Joined, human-readable form of the structured address. Recomputed server-side after every `poa_parsed_address` edit. example: Avda de Madrid 34, Madrid, 28013, ES poa_formatted_address: type: string nullable: true description: Top-level copy of the recomputed `formatted_address`, kept in sync when the structured address changes. example: Avda de Madrid 34, Madrid, 28013, ES examples: Updated POA: summary: Full POA record after the correction value: issuing_state: ESP document_type: BANK_STATEMENT document_language: es issuer: Banco Example S.A. issue_date: '2026-05-02' poa_address: Avda de Madrid 34, 28013 Madrid name_on_document: Carmen Espanola extra_fields: bank_account_number: null bank_iban: ES9121000418450200051332 bank_sort_code: null bank_routing_number: null bank_swift_bic: null bank_branch_name: null bank_branch_address: null document_phone_number: null additional_names: - Carmen E. Espanola poa_parsed_address: city: Madrid street_1: Avda de Madrid 34 postal_code: '28013' country: ES formatted_address: Avda de Madrid 34, Madrid, 28013, ES poa_formatted_address: Avda de Madrid 34, Madrid, 28013, ES '400': description: 'Validation error. Field errors arrive as `{"": [""]}`; errors on nested `poa_parsed_address` / `extra_fields` keys use an object value: `{"extra_fields": {"": ""}}`.' content: application/json: examples: Invalid document type: summary: Enum is uppercase — lowercase variants are rejected value: document_type: - '"utility_bill" is not a valid choice.' Invalid issuing state: summary: issuing_state is not ISO 3166-1 alpha-3 value: issuing_state: - Invalid ISO 3166-1 alpha-3 country code for issuing_state. additional_names not a list: summary: additional_names accepts a list of strings or null value: extra_fields: additional_names: Must be a list or null. Unsupported poa_parsed_address key: summary: Only the seven documented keys are allowed value: poa_parsed_address: zipcode: 'Unsupported key. Allowed keys: address_type, city, region, street_1, street_2, postal_code, country' Bad date format: summary: Dates must be YYYY-MM-DD value: issue_date: - 'Date has wrong format. Use one of these formats instead: YYYY-MM-DD.' '403': description: Missing/invalid credentials or insufficient permissions. This API returns `403` for authentication failures — never `401`. content: application/json: examples: Invalid Credentials: summary: Missing or invalid x-api-key value: detail: Authentication credentials were not provided or are invalid. Missing Privilege: summary: Credential lacks write:sessions value: detail: You do not have permission to perform this action. '404': description: Unknown session, session in a non-editable state, or no POA record to patch. Each case has a distinct `detail` message (see examples). The unknown-session lookup runs before credential checks. content: application/json: examples: Unknown session: summary: No session with this ID in your application (or it was deleted) value: detail: Not found. Wrong session state: summary: Session exists but is not Approved / Declined / In Review / Kyc Expired value: detail: No Session matches the given query. No POA record: summary: The workflow never ran a Proof of Address step value: detail: The session does not have a POA. No POA for node: summary: node_id given but no POA exists for it value: detail: The session does not have a POA for node 'feature_missing'. '429': description: Dedicated rate limit exceeded — 10 POA data updates per minute per API key (rejected requests count toward the budget). The shared 300/min write budget can also trigger with its own message. Inspect `Retry-After` and the `X-RateLimit-*` response headers before retrying. content: application/json: schema: type: object properties: detail: type: string examples: Rate limited: summary: POA update budget exhausted value: detail: Session POA data update rate limit exceeded. You can make up to 10 requests per minute. x-codeSamples: - lang: curl label: curl source: "curl -X PATCH \\\n https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/update-poa-data/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"issuing_state\": \"ESP\",\n \"document_type\": \"BANK_STATEMENT\",\n \"issuer\": \"Banco Example S.A.\",\n \"issue_date\": \"2026-05-02\",\n \"name_on_document\": \"Carmen Espanola\",\n \"poa_address\": \"Avda de Madrid 34, 28013 Madrid\",\n \"extra_fields\": {\n \"bank_iban\": \"ES9121000418450200051332\"\n },\n \"poa_parsed_address\": {\n \"city\": \"Madrid\",\n \"postal_code\": \"28013\",\n \"country\": \"ES\"\n }\n }'" - lang: python label: Python source: "import requests\n\nresponse = requests.patch(\n \"https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/update-poa-data/\",\n headers={\n \"x-api-key\": \"YOUR_API_KEY\",\n \"Content-Type\": \"application/json\",\n },\n json={\n \"issuing_state\": \"ESP\",\n \"document_type\": \"BANK_STATEMENT\",\n \"issuer\": \"Banco Example S.A.\",\n \"issue_date\": \"2026-05-02\",\n \"name_on_document\": \"Carmen Espanola\",\n \"poa_address\": \"Avda de Madrid 34, 28013 Madrid\",\n \"extra_fields\": {\"bank_iban\": \"ES9121000418450200051332\"},\n \"poa_parsed_address\": {\"city\": \"Madrid\", \"postal_code\": \"28013\", \"country\": \"ES\"},\n },\n)\nresponse.raise_for_status()\npoa = response.json() # full current POA record\nprint(poa[\"issuer\"], poa[\"poa_formatted_address\"])" - lang: javascript label: JavaScript source: "const response = await fetch(\n 'https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/update-poa-data/',\n {\n method: 'PATCH',\n headers: {\n 'x-api-key': process.env.DIDIT_API_KEY,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n issuing_state: 'ESP',\n document_type: 'BANK_STATEMENT',\n issuer: 'Banco Example S.A.',\n issue_date: '2026-05-02',\n name_on_document: 'Carmen Espanola',\n poa_address: 'Avda de Madrid 34, 28013 Madrid',\n poa_parsed_address: { city: 'Madrid', postal_code: '28013', country: 'ES' },\n }),\n },\n);\nif (!response.ok) throw new Error(`HTTP ${response.status}`);\nconst poa = await response.json(); // full current POA record" /v3/session/{sessionId}/kyb/{companyUuid}/update-data/: patch: summary: Update registry-extracted company data on a Business Verification (KYB) session description: 'Correct the registry-extracted company data on a Business Verification (KYB) session — company name, registration number, addresses, status, contact details — identified by `companyUuid`. Typical callers are a compliance analyst fixing a registry misread, or your back office syncing corrected company data. Every field in the body is optional (`PATCH` semantics); an empty JSON body (`{}`) is a no-op that simply echoes the current company record. Edits are written in two places at once: fields with a canonical company column (name, registration number, country, incorporation date, tax number, type, statuses, capital fields (`registered_capital`, `registered_capital_amount`, `registered_capital_currency`), alternative names, nature of business, website, email, phone, LEI, location, and `legal_address` → `registered_address`) update that column, and **every** submitted key is also merged into the `registry_data` snapshot (dates as ISO strings) so the "extracted from registry" view reflects the correction. Fields without a column — `region`, `registration_date`, `dissolution_date`, `control_scheme` — land in `registry_data` only. `user_provided_data` (what the end user confirmed in the hosted flow) is never touched, and `last_console_edit_at` is stamped on every non-empty PATCH. Unlike the KYC/POA update endpoints, there is **no session-status gate**: the company can be edited at any point in the business session''s lifecycle. The company must belong to the session in the path (`404` with `"KYB company does not belong to the given session."` otherwise) and to your application. Every non-empty PATCH fires the `data.updated` [webhook](/integration/webhooks) on the business session (trigger `console_registry_edit`) — even when the submitted values equal the stored ones. The session''s status and decision are **not** re-evaluated, and no review/audit entry is recorded. Accepts an API key or a console user token; either way the credential needs the `write:businesses` permission. Authentication and permission failures both return `403` — this API never responds `401`. Note that the session lookup runs before credential checks, so an unknown `sessionId` returns `404` even with missing credentials. The shared write budget of 300 requests per minute per API key applies. For User Verification sessions, the KYC counterpart is `PATCH /v3/session/{sessionId}/update-data/`; to change the session decision itself use `PATCH /v3/session/{sessionId}/update-status/`.' operationId: patch_v3_session_kyb_company_update_data tags: - Sessions security: - ApiKeyAuth: [] parameters: - name: sessionId in: path required: true description: UUID of the parent Business Verification (KYB) session. schema: type: string format: uuid example: 33333333-4444-5555-6666-777777777777 - name: companyUuid in: path required: true description: UUID of the company record to update (`registry_checks[].company.uuid` on the business session decision). Must belong to the session referenced by `sessionId`. schema: type: string format: uuid example: 88888888-9999-aaaa-bbbb-cccccccccccc requestBody: required: true content: application/json: schema: type: object description: All fields optional — send only what you want to change. Provided fields update the canonical company columns (where one exists) and are merged into the `registry_data` snapshot. properties: company_name: type: string maxLength: 255 description: Legal name of the company. Written to the canonical column and the registry snapshot. example: Docs Probe Corp registration_number: type: string maxLength: 100 description: Official registration number with the company registry. example: DPC-001 country_code: type: string maxLength: 2 description: Country of incorporation, ISO 3166-1 alpha-2. Case-insensitive (`us` is stored as `US`). Alpha-3 codes fail the 2-character limit with `400`. example: US region: type: string maxLength: 10 nullable: true description: Subdivision / region tag where the registry has one (e.g. US state). Stored in the registry snapshot only — it has no canonical column. incorporation_date: type: string format: date description: Incorporation date in `YYYY-MM-DD`. Optional like every other field — the `PATCH` is partial. Any other date format returns `400`. example: '2015-04-12' registration_date: type: string format: date nullable: true description: Registration date where the registry distinguishes it from incorporation. Stored in the registry snapshot only. dissolution_date: type: string format: date nullable: true description: Dissolution date for companies that are no longer active. Stored in the registry snapshot only. tax_number: type: string maxLength: 100 description: Tax identification number. legal_address: type: string description: Legal / registered address. Aliased server-side to the canonical `registered_address` column — the response returns it under `registered_address`, while the registry snapshot keeps the `legal_address` key you sent. example: 1 Example Plaza, Wilmington, DE 19801 company_type: type: string maxLength: 100 description: Legal entity type (e.g. `Limited Liability Company`, `PLC`, `GmbH`). example: Limited Liability Company registry_status: type: string enum: - active - dissolved - deregistered - see full details - authorised - appointed representative - unauthorised - inactive - no longer authorised - closed - struck off nullable: true description: Registry-reported company status. Lowercase, some values contain spaces (e.g. `struck off`, `no longer authorised`). Any other value returns `400`. example: active verification_status: type: string enum: - verified - failed - unknown nullable: true description: Outcome of the registry verification. Any other value returns `400`. example: verified alternative_names: type: string description: Trade names, former legal names, DBA names (free text). nature_of_business: type: string description: Short description of the company's activity. example: Software development registered_capital: type: string maxLength: 100 description: Human-readable registered capital (e.g. `USD 50000`). Use `registered_capital_amount` + `registered_capital_currency` for structured values, or this field for free-form text. example: USD 50000 registered_capital_amount: type: number format: double nullable: true description: Numeric registered-capital amount (up to 20 digits, 2 decimal places). Sets the `registered_capital_amount` column and is mirrored into `registry_data` as a decimal string; the response echoes it as a decimal string (e.g. `"1500000.50"`). registered_capital_currency: type: string maxLength: 3 nullable: true description: ISO 4217 currency code for `registered_capital_amount`. Case-insensitive (`usd` is stored as `USD`); values that are not exactly 3 letters return `400` with the ISO 4217 message. Sets the canonical `registered_capital_currency` column and is mirrored into `registry_data`. example: USD website: type: string maxLength: 500 description: Company website. Bare domains are accepted and normalized with an `https://` prefix (`docsprobe.example.com` is stored as `https://docsprobe.example.com`); values that still fail URL validation return `400`. example: https://docsprobe.example.com email: type: string format: email description: Contact email on file with the registry. example: legal@docsprobe.example.com phone: type: string maxLength: 50 description: Contact phone. Must match `+`, digits, spaces, dashes, dots, or parentheses, with 7-15 digits total — otherwise `400`. example: +1 302 555 0143 legal_entity_identifier: type: string maxLength: 50 description: LEI, where the company has one. location_of_registration: type: string maxLength: 255 description: City / subdivision of the registry of record. vat_number: type: string maxLength: 64 description: EU VAT number. Merged into the `registry_data` snapshot only — editing it here does not re-run VIES validation, so `vat_validation_status` and the other `vat_*` result fields are unchanged. control_scheme: type: string description: Control / governance scheme when the registry exposes it. Stored in the registry snapshot only. examples: Correct registry data: summary: Fix the company's registry-extracted fields value: company_name: Docs Probe Corp registration_number: DPC-001 country_code: US incorporation_date: '2015-04-12' company_type: Limited Liability Company legal_address: 1 Example Plaza, Wilmington, DE 19801 registry_status: active verification_status: verified website: docsprobe.example.com email: legal@docsprobe.example.com phone: +1 302 555 0143 registered_capital: USD 50000 nature_of_business: Software development Single field: summary: Only the keys you send are written value: registry_status: dissolved No-op: summary: Empty body echoes the current record without firing the webhook value: {} responses: '200': description: Company data updated (or echoed unchanged for an empty body). Returns the full KYB company record — the same shape as `registry_checks[].company` on the business session decision. Note how `legal_address` comes back as `registered_address`. `registered_capital_amount`/`registered_capital_currency` populate their canonical columns and are mirrored into `registry_data`. content: application/json: schema: type: object description: Full KYB company record — the same shape as the `company` object inside `registry_checks[]` on the business session decision. properties: uuid: type: string format: uuid description: Company record identifier (the `companyUuid` path parameter). node_id: type: string nullable: true description: Workflow graph node this company record belongs to, when the workflow is graph-based. status: type: string description: Feature lifecycle status of the registry check (e.g. `Not Finished`, `Approved`, `Declined`, `In Review`). registry_status: type: string nullable: true description: Registry-reported company status (e.g. `active`, `dissolved`, `struck off`). data_resolved: type: boolean description: '`true` when the company data is complete — always `true` for manual-entry companies; for registry-sourced companies, `true` once the registry fetch resolved.' company_name: type: string nullable: true registration_number: type: string nullable: true country_code: type: string nullable: true description: ISO 3166-1 alpha-2 country of incorporation. region: type: string nullable: true company_type: type: string nullable: true incorporation_date: type: string format: date nullable: true registered_address: type: string nullable: true description: Canonical registered address — updated when you send `legal_address`. tax_number: type: string nullable: true risk_level: type: string nullable: true verification_status: type: string nullable: true description: '`verified`, `failed`, or `unknown`.' is_from_registry: type: boolean description: '`true` when the company was pre-filled from a registry search rather than entered manually.' fetch_status: type: string nullable: true description: Registry fetch lifecycle for registry-sourced companies (`pending` / `resolved`); `null` for manual entries. alternative_names: type: string nullable: true nature_of_business: type: string nullable: true registered_capital: type: string nullable: true registered_capital_amount: type: string nullable: true description: Canonical column. Returned as a decimal string (e.g. `"2750000.00"`). registered_capital_currency: type: string nullable: true description: Canonical column. Set by the registry flow or by console/API edits via this endpoint. website: type: string nullable: true email: type: string nullable: true phone: type: string nullable: true legal_entity_identifier: type: string nullable: true location_of_registration: type: string nullable: true vat_number: type: string nullable: true description: EU VAT number, when collected during the hosted registry step or edited afterwards. vat_validation_status: type: string enum: - valid - invalid - could_not_validate - not_applicable description: Result of the EU VIES check run at registry submit. `not_applicable` when no VAT number was provided or the company is outside the EU VAT area (EU-27 plus Northern Ireland). vat_validated_name: type: string nullable: true description: Trader name registered with VIES, when the member state discloses it. Only set when the VAT number is `valid`. vat_validated_address: type: string nullable: true description: Trader address registered with VIES, when the member state discloses it. Only set when the VAT number is `valid`. vat_checked_at: type: string format: date-time nullable: true description: Timestamp of the VIES check. `null` when no check ran. financial_summary: type: object nullable: true additionalProperties: true officers: type: array items: type: object additionalProperties: true description: Directors / officers attached to the company, including per-person KYC progress. beneficial_owners: type: array items: type: object additionalProperties: true description: Beneficial owners attached to the company, including per-person KYC progress. addresses: type: array nullable: true items: type: object additionalProperties: true description: Registry address detail, when the registry returned any. industries: type: array nullable: true items: type: object additionalProperties: true description: Registry industry classification, when available. accounts: type: array nullable: true items: type: object additionalProperties: true description: Registry filing-accounts detail, when available. registry_data: type: object nullable: true additionalProperties: true description: Raw registry payload snapshot. Every key you PATCH is merged into this map (dates serialized as ISO strings), so the "extracted from registry" view reflects operator corrections. user_provided_data: type: object nullable: true additionalProperties: true description: What the end user typed during the hosted registry-confirmation step. Never modified by this endpoint. confirmed_by_user_at: type: string format: date-time nullable: true description: When the end user confirmed the company data in the hosted flow. last_console_edit_at: type: string format: date-time nullable: true description: Stamped on every successful non-empty PATCH, so UIs can surface "edited by operator" context. is_editable: type: boolean description: '`true` while the end user can still edit the registry-sourced data in the hosted flow (registry-sourced and not yet confirmed).' examples: Updated company: summary: Full company record after the correction value: uuid: 6b9479f5-6769-46c8-baf1-df63bbbc5f8c node_id: null status: Not Finished registry_status: active data_resolved: true company_name: Docs Probe Corp registration_number: DPC-001 country_code: US region: null company_type: Limited Liability Company incorporation_date: '2015-04-12' registered_address: 1 Example Plaza, Wilmington, DE 19801 tax_number: null risk_level: null verification_status: verified is_from_registry: false fetch_status: null alternative_names: null nature_of_business: Software development registered_capital: USD 50000 registered_capital_amount: null registered_capital_currency: USD website: https://docsprobe.example.com email: legal@docsprobe.example.com phone: +1 302 555 0143 legal_entity_identifier: null location_of_registration: null financial_summary: null officers: [] beneficial_owners: [] addresses: null industries: null accounts: null registry_data: company_name: Docs Probe Corp registration_number: DPC-001 country_code: US incorporation_date: '2015-04-12' legal_address: 1 Example Plaza, Wilmington, DE 19801 company_type: Limited Liability Company registry_status: active verification_status: verified nature_of_business: Software development registered_capital: USD 50000 registered_capital_currency: USD website: https://docsprobe.example.com email: legal@docsprobe.example.com phone: +1 302 555 0143 user_provided_data: null confirmed_by_user_at: null last_console_edit_at: '2026-06-12T00:02:34.276061Z' is_editable: false '400': description: 'Validation error. Field errors arrive as `{"": [""]}`. Max-length checks run before format checks, so `country_code: "USA"` reports the 2-character limit rather than the ISO message.' content: application/json: examples: Alpha-3 country code: summary: country_code must be alpha-2 — length check fires first value: country_code: - Ensure this field has no more than 2 characters. Non-alphabetic country code: summary: 2 characters but not letters value: country_code: - country_code must be a 2-letter ISO 3166-1 alpha-2 code. Invalid currency: summary: 3 characters but not an ISO 4217 code value: registered_capital_currency: - Currency must be a 3-letter ISO 4217 code (e.g. USD, EUR, GBP). Invalid phone: summary: Phone fails the format / digit-count check value: phone: - Invalid phone number format. Invalid registry status: summary: registry_status not in the enum value: registry_status: - '"liquidated" is not a valid choice.' Invalid email: summary: Malformed email address value: email: - Enter a valid email address. Invalid website: summary: Value fails URL validation even after https:// normalization value: website: - Enter a valid URL. Bad date format: summary: Dates must be YYYY-MM-DD value: incorporation_date: - 'Date has wrong format. Use one of these formats instead: YYYY-MM-DD.' '403': description: Missing/invalid credentials or insufficient permissions. This API returns `403` for authentication failures — never `401`. content: application/json: examples: Invalid Credentials: summary: Missing or invalid x-api-key value: detail: Authentication credentials were not provided or are invalid. Missing Privilege: summary: Credential lacks write:businesses value: detail: You do not have permission to perform this action. '404': description: Unknown session, unknown company, or a company that belongs to a different session. Each case has a distinct `detail` message (see examples). The unknown-session lookup runs before credential checks. content: application/json: examples: Unknown session: summary: No session with this ID in your application (or it was deleted) value: detail: Not found. Unknown company: summary: No company with this UUID in your application value: detail: No KYBCompany matches the given query. Company / session mismatch: summary: companyUuid exists but belongs to another session value: detail: KYB company does not belong to the given session. '429': description: Shared write rate limit exceeded (300 POST/PATCH/DELETE requests per minute per API key). Inspect `Retry-After` and the `X-RateLimit-*` response headers before retrying. content: application/json: schema: type: object properties: detail: type: string examples: Rate limited: summary: Write budget exhausted value: detail: Write request rate limit exceeded. You can make up to 300 requests per minute. x-codeSamples: - lang: curl label: curl source: "curl -X PATCH \\\n https://verification.didit.me/v3/session/33333333-4444-5555-6666-777777777777/kyb/88888888-9999-aaaa-bbbb-cccccccccccc/update-data/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"company_name\": \"Docs Probe Corp\",\n \"registration_number\": \"DPC-001\",\n \"country_code\": \"US\",\n \"incorporation_date\": \"2015-04-12\",\n \"company_type\": \"Limited Liability Company\",\n \"legal_address\": \"1 Example Plaza, Wilmington, DE 19801\",\n \"registry_status\": \"active\"\n }'" - lang: python label: Python source: "import requests\n\nresponse = requests.patch(\n \"https://verification.didit.me/v3/session/33333333-4444-5555-6666-777777777777/kyb/\"\n \"88888888-9999-aaaa-bbbb-cccccccccccc/update-data/\",\n headers={\n \"x-api-key\": \"YOUR_API_KEY\",\n \"Content-Type\": \"application/json\",\n },\n json={\n \"company_name\": \"Docs Probe Corp\",\n \"registration_number\": \"DPC-001\",\n \"country_code\": \"US\",\n \"incorporation_date\": \"2015-04-12\",\n \"legal_address\": \"1 Example Plaza, Wilmington, DE 19801\",\n \"registry_status\": \"active\",\n },\n)\nresponse.raise_for_status()\ncompany = response.json() # full company record\nprint(company[\"company_name\"], company[\"registered_address\"])" - lang: javascript label: JavaScript source: "const response = await fetch(\n 'https://verification.didit.me/v3/session/33333333-4444-5555-6666-777777777777/kyb/' +\n '88888888-9999-aaaa-bbbb-cccccccccccc/update-data/',\n {\n method: 'PATCH',\n headers: {\n 'x-api-key': process.env.DIDIT_API_KEY,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n company_name: 'Docs Probe Corp',\n registration_number: 'DPC-001',\n country_code: 'US',\n incorporation_date: '2015-04-12',\n legal_address: '1 Example Plaza, Wilmington, DE 19801',\n registry_status: 'active',\n }),\n },\n);\nif (!response.ok) throw new Error(`HTTP ${response.status}`);\nconst company = await response.json(); // full company record" components: schemas: ReviewItem: type: object description: One entry in a session's review / activity feed. Status changes carry `previous_status`, `new_status`, `previous_value`, `new_value`, and `changed_fields`; comments carry `comment` and (for Console comments) `mentioned_emails`; the remaining fields are `null` or empty when they do not apply to the activity type. properties: uuid: type: string format: uuid description: Unique identifier of this feed entry. example: de0561bc-5583-4409-9171-1a31a4c8a5f4 activity_type: type: string enum: - COMMENT - STATUS_UPDATED - KYC_DATA_UPDATED - POA_DATA_UPDATED - FILE_ADDED - FILE_REMOVED - AML_ONGOING_MATCH - AML_STATUS_UPDATED - AML_HIT_STATUS_UPDATED - KYB_DATA_UPDATED - KYB_DOCUMENT_UPLOADED - TAG_ADDED - TAG_REMOVED description: What kind of activity this entry records. `STATUS_UPDATED` rows come from manual decisions (`PATCH /v3/session/{session_id}/update-status/`) and from entries added via `POST …/reviews/` (which default to this type even when no status is recorded). `COMMENT` rows come from Console reviewers; Console detail-page views are also stored as `COMMENT` rows flagged with `metadata.system_event = "DETAIL_VIEWED"`. The remaining types track data edits, file changes, tags, and AML monitoring activity. STATUS_UPDATED rows are also written by system actors (e.g. AML ongoing monitoring with actor_type SYSTEM, and automatic feature-status updates), not only by the update-status endpoint and manual review posts. example: STATUS_UPDATED actor_display: type: string description: 'Human-readable author label, resolved in order: actor''s full name → Console account identifier → local part of `actor_email` → `"API Client"` (for `API_KEY` actors) → `"System"` (for `SYSTEM` actors) → `"Unknown"`. Entries created via `POST …/reviews/` always display `"Unknown"` because that endpoint records no actor attribution.' example: API Client actor_email: type: string nullable: true description: Email of the Console user who performed the action. `null` for API-key and system actors. example: null actor_type: type: string enum: - API_KEY - CONSOLE_USER - SYSTEM description: 'Who performed the action: an API key, a Console user, or Didit itself (`SYSTEM`, e.g. AML ongoing monitoring). Entries created via `POST …/reviews/` carry the default `CONSOLE_USER` regardless of the caller.' example: API_KEY new_status: type: string nullable: true enum: - Not Started - In Progress - Approved - Declined - In Review - Expired - Abandoned - Kyc Expired - Resubmitted - Awaiting User description: Session status recorded by this entry (for status updates). `null` for comments and data-change entries. example: Approved previous_status: type: string nullable: true enum: - Not Started - In Progress - Approved - Declined - In Review - Expired - Abandoned - Kyc Expired - Resubmitted - Awaiting User description: Status the session had before the change. Populated on `STATUS_UPDATED` entries written by the update-status endpoint; `null` on entries added via `POST …/reviews/`. Also populated on system-written STATUS_UPDATED rows. example: In Review comment: type: string nullable: true description: Free-text note attached to the entry, or `null`. example: Document re-checked manually; address matches. mentioned_emails: type: array items: type: string format: email description: Console users `@email`-mentioned in the comment. Always empty for entries created via this API — mention processing is a Console-only feature. example: [] previous_value: type: object nullable: true description: 'Snapshot of the changed data before the action, e.g. `{"status": "In Review"}` on a status change. `null` when nothing was changed.' example: status: In Review new_value: type: object nullable: true description: 'Snapshot of the changed data after the action, e.g. `{"status": "Approved"}`. `null` when nothing was changed.' example: status: Approved changed_fields: type: array items: type: string description: Names of the fields modified by this activity (e.g. `["status"]`). Empty for comments and for entries added via `POST …/reviews/`. example: - status metadata: type: object nullable: true description: 'Extra context for system-generated entries. Console detail-page views carry `{"system_event": "DETAIL_VIEWED", "detail_type": "verification"}` so they can be filtered out of notification logic. `null` for ordinary entries.' example: null created_at: type: string format: date-time description: When the entry was written, ISO 8601 UTC. example: '2026-06-12T00:19:31.918735Z' SessionImportError: type: object required: - uuid - row_number - error - raw_row properties: uuid: type: string format: uuid row_number: type: integer format: int64 external_id: type: string nullable: true error: type: string raw_row: type: object additionalProperties: true created_at: type: string format: date-time SessionImportJob: type: object required: - uuid - status - import_type - source_format - source_filename - total_rows - processed_rows - created_count - updated_count - skipped_count - failed_count properties: uuid: type: string format: uuid description: Import job id. Use this value to poll progress and fetch row errors. status: type: string enum: - PENDING - PROCESSING - COMPLETED - FAILED - PAUSED - CANCELLED import_type: type: string enum: - user_verification - business_verification - status_rules - transactions source_format: type: string enum: - csv - ndjson source_filename: type: string total_rows: type: integer format: int64 processed_rows: type: integer format: int64 checkpoint_row: type: integer format: int64 created_count: type: integer format: int64 updated_count: type: integer format: int64 skipped_count: type: integer format: int64 failed_count: type: integer format: int64 total_media_bytes: type: integer format: int64 last_error: type: string nullable: true created_at: type: string format: date-time updated_at: type: string format: date-time started_at: type: string format: date-time nullable: true completed_at: type: string format: date-time nullable: true securitySchemes: ApiKeyAuth: type: apiKey in: header name: x-api-key TransactionTokenAuth: type: apiKey in: header name: X-Transaction-Token description: Short-lived scoped token minted by your backend via POST /v3/transactions/sdk-token/. Used by the Didit SDKs on the device-facing /v1/transactions/ endpoints. SessionTokenAuth: type: apiKey in: header name: Session-Token description: Short-lived token returned in the `session_token` field of POST /v3/session/. Used by the hosted verification flow (and custom sandbox UIs) to call session-scoped endpoints on behalf of the verifying user, without your server-side API key.