# Get Async Job Source: https://docs.deepidv.com/api-reference/async-jobs/get-async-job GET /v1/async-jobs/{jobId} Poll the status and result of a long-running async job ``` GET /v1/async-jobs/{jobId} ``` Returns the current state of an async job. Endpoints that kick off long-running work — such as [adverse media screening](/api-reference/silent-screening/adverse-media) — return a `jobId` immediately; poll this endpoint until the job reaches a terminal state (`ready` or `failed`). The `result` shape depends on which endpoint created the job. ## Job lifecycle A job moves through these states: | `status` | Terminal | Payload | Meaning | | ------------ | -------- | -------- | -------------------------------------------- | | `pending` | No | — | Queued, not yet picked up | | `processing` | No | — | Actively running | | `ready` | Yes | `result` | Completed successfully — result is available | | `failed` | Yes | `error` | Failed — `error` describes what went wrong | Poll on a backoff interval (e.g. every 2–5 seconds) until `status` is `ready` or `failed`. Jobs are retained temporarily and then expire via a TTL, after which the job ID returns `404` — fetch the result promptly once it's ready. ## Request ### Path parameters | Parameter | Type | Required | Description | | --------- | ------ | -------- | -------------------------------------------- | | `jobId` | string | Yes | The job ID returned by the creating endpoint | ### Headers | Header | Required | Description | | ----------- | -------- | ------------ | | `x-api-key` | Yes | Your API key | ### Example request ```bash cURL theme={null} curl https://api.deepidv.com/v1/async-jobs/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \ -H "x-api-key: YOUR_API_KEY" ``` ```javascript Node.js theme={null} const response = await fetch( "https://api.deepidv.com/v1/async-jobs/a1b2c3d4-e5f6-7890-abcd-ef1234567890", { headers: { "x-api-key": "YOUR_API_KEY" }, }, ); const job = await response.json(); ``` ```python Python theme={null} import requests response = requests.get( "https://api.deepidv.com/v1/async-jobs/a1b2c3d4-e5f6-7890-abcd-ef1234567890", headers={"x-api-key": "YOUR_API_KEY"}, ) job = response.json() ``` ## Response ### 200 — Job state The response is a discriminated union on `status`. All variants include `jobId`, `createdAt`, `updatedAt`, and `status`. | Field | Type | Present when | Description | | ----------- | ------ | -------------------- | ----------------------------------------------------- | | `jobId` | string | always | The job identifier | | `status` | string | always | `pending`, `processing`, `ready`, or `failed` | | `createdAt` | number | always | Epoch timestamp the job was created | | `updatedAt` | string | always | ISO 8601 timestamp the job was last updated | | `result` | object | `status` is `ready` | The job result (shape depends on the originating job) | | `error` | string | `status` is `failed` | Description of the failure | ### Error responses | Status | Description | | ------------------ | ---------------------------------------------------- | | `400 Bad Request` | Malformed job ID | | `401 Unauthorized` | API key is invalid | | `403 Forbidden` | API key is missing or the job belongs to another org | | `404 Not Found` | No job with that ID (unknown or expired) | | `500 Server Error` | Unexpected server error | ```json ready theme={null} { "jobId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "status": "ready", "createdAt": 1717593600000, "updatedAt": "2026-06-05T12:00:00.000Z", "result": { "totalHits": 2, "riskLevel": "MEDIUM", "riskScore": 45, "summary": "Two adverse-media findings corroborated by court records.", "findings": [], "exposuresByCategory": {} } } ``` ```json processing theme={null} { "jobId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "status": "processing", "createdAt": 1717593600000, "updatedAt": "2026-06-05T12:00:01.000Z" } ``` ```json failed theme={null} { "jobId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "status": "failed", "createdAt": 1717593600000, "updatedAt": "2026-06-05T12:00:30.000Z", "error": "Upstream provider timed out" } ``` # Create Credit Check Source: https://docs.deepidv.com/api-reference/credit-check/create-credit-check POST /v1/credit-check/hard Create a soft or hard credit check session for an applicant ``` POST /v1/credit-check/hard POST /v1/credit-check/soft ``` Creates a credit check session and sends email and SMS invitations to the applicant. Use the `/hard` endpoint for a full credit report with AI-powered insights, or `/soft` for a score-only check. ## Request ### Headers | Header | Required | Description | | -------------- | -------- | ------------------ | | `x-api-key` | Yes | Your API key | | `Content-Type` | Yes | `application/json` | ### Body parameters Both camelCase and snake\_case parameter names are accepted. If both are provided for the same field, the camelCase value takes priority. | Parameter | Alias | Type | Required | Description | | ------------------- | ----------------- | ------- | -------- | --------------------------------------------------------------------------------------- | | `first_name` | `firstName` | string | Yes | Applicant's first name | | `last_name` | `lastName` | string | Yes | Applicant's last name | | `email` | — | string | Yes | Applicant's email address | | `phone` | — | string | Yes | Applicant's phone number in E.164 format (e.g. `+15192223333`) | | `external_id` | `externalId` | string | No | Your internal reference ID for this session | | `send_email_invite` | `sendEmailInvite` | boolean | No | Send an email invitation to the applicant. Defaults to `true` | | `send_phone_invite` | `sendPhoneInvite` | boolean | No | Send an SMS invitation to the applicant. Defaults to `true` | | `redirect_url` | `redirectUrl` | string | No | HTTPS URL to redirect the end-user to after the session ends. Must be a valid HTTPS URL | | `uat` | — | boolean | No | Put the session into test mode. See [Testing](#testing) below | | `uat_type` | `uatType` | string | No | The credit scenario to simulate in test mode. See [Testing](#testing) below | ### Example request ```bash cURL theme={null} curl -X POST https://api.deepidv.com/v1/credit-check/hard \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -d '{ "first_name": "John", "last_name": "Doe", "email": "john.doe@example.com", "phone": "+15192223333", "external_id": "user-12345" }' ``` ```javascript Node.js theme={null} const response = await fetch("https://api.deepidv.com/v1/credit-check/hard", { method: "POST", headers: { "Content-Type": "application/json", "x-api-key": "YOUR_API_KEY", }, body: JSON.stringify({ first_name: "John", last_name: "Doe", email: "john.doe@example.com", phone: "+15192223333", external_id: "user-12345", }), }); const data = await response.json(); ``` ```python Python theme={null} import requests response = requests.post( "https://api.deepidv.com/v1/credit-check/hard", headers={ "Content-Type": "application/json", "x-api-key": "YOUR_API_KEY", }, json={ "first_name": "John", "last_name": "Doe", "email": "john.doe@example.com", "phone": "+15192223333", "external_id": "user-12345", }, ) ``` ## Response ### 200 — Success | Field | Type | Description | | ------------- | ------ | ------------------------------------------------------------------------ | | `id` | string | Unique identifier for the created session | | `session_url` | string | URL where the applicant completes the credit check | | `type` | string | Session type — `credit-check-hard` or `credit-check-soft` | | `externalId` | string | Your external ID (only returned if provided in the request) | | `links` | array | Convenience links — admin console view and session details API reference | ### Error responses | Status | Description | | ----------------------- | ------------------------------------------------------------- | | `400 Bad Request` | Invalid request body — check required fields and phone format | | `401 Unauthorized` | Missing or invalid API key | | `402 Payment Required` | Insufficient token balance | | `429 Too Many Requests` | Rate limit exceeded | ```json 200 theme={null} { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "session_url": "https://verify.deepidv.com/credit-check-hard/a1b2c3d4-e5f6-7890-abcd-ef1234567890?oid=your-org-id", "type": "credit-check-hard", "externalId": "user-12345", "links": [ { "rel": "admin_console", "href": "https://app.deepidv.com/dashboard/session/a1b2c3d4-e5f6-7890-abcd-ef1234567890", "description": "Click this link to view this session in your DeepIDV admin console" }, { "rel": "session_details", "href": "https://api.deepidv.com/v1/sessions/a1b2c3d4-e5f6-7890-abcd-ef1234567890", "description": "Use this ref to fetch the session details" } ] } ``` *** ## Testing When building your integration, you can create a session in **test mode** so it runs against Equifax's UAT environment using a pre-defined test persona instead of the real applicant's data. To enable test mode, pass `uat: true` in the request body: ```json theme={null} { "first_name": "John", "last_name": "Doe", "email": "john.doe@example.com", "phone": "+15192223333", "uat": true, "uat_type": "STANDARD_HIT" } ``` In test mode, the applicant can enter **any value** for their SIN and address when completing the session — the real data is not used to query Equifax. The result is driven entirely by the `uat_type` scenario you specify. ### `uat_type` values Use `uat_type` to control which credit scenario the session simulates. If omitted, or if an unrecognized value is passed, a scenario is chosen at random. #### Clean / standard profiles | Value | Description | | ------------------ | ------------------------------------------------- | | `SHELL_FILE` | Minimal credit file — one address, no trades | | `MULTIPLE_ADDRESS` | Consumer with multiple addresses on file | | `STANDARD_HIT` | Standard hit — typical credit profile with trades | #### Bankruptcy | Value | Description | | ---------------------------- | ------------------------------------------ | | `BANKRUPTCY_DISCHARGED` | Past bankruptcy, now discharged | | `BANKRUPTCY_UNDISCHARGED` | Active/unresolved bankruptcy | | `BANKRUPTCY_VOLUNTARY` | Voluntary deposit under the Bankruptcy Act | | `BANKRUPTCY_PROPOSAL` | Consumer proposal — debt restructuring | | `BANKRUPTCY_RECEIVING_ORDER` | Receiving order bankruptcy | #### Collections | Value | Description | | --------------------- | ----------------------------------------- | | `COLLECTION_UNPAID` | Unpaid collection account on file | | `COLLECTION_UNPAID_2` | Unpaid collection — alternate consumer | | `COLLECTION_PAID` | Paid/settled collection account on file | | `COLLECTION_MULTIPLE` | Multiple collections with narrative codes | #### Legal items | Value | Description | | --------------------- | ------------------------------------ | | `JUDGEMENT` | Active judgement on file | | `JUDGEMENT_SATISFIED` | Satisfied/resolved judgement on file | | `FORECLOSURE` | Foreclosure on file | | `GARNISHMENT` | Garnishment on file | #### Loans & trades | Value | Description | | -------------------- | -------------------------------------------------------- | | `SECURE_LOAN` | Secure loan trade on file | | `MORTGAGE` | Mortgage and revolving credit | | `STUDENT_LOAN` | Student loan on file | | `AUTO_LOAN` | Auto loan on file | | `TRADE_R5` | Trade with R5 (write-off) and R3 ratings | | `TRADE_VARIOUS` | Various credit cards with semi-monthly payment frequency | | `TRADE_REPOSSESSION` | Trade with voluntary repossession | #### Alerts & inquiries | Value | Description | | ----------------- | ------------------------------------------ | | `FRAUD_ALERT` | Lost/stolen wallet alert on file | | `FOREIGN_INQUIRY` | Foreign bureau inquiry on file | | `MANY_INQUIRIES` | Consumer with many recent credit inquiries | #### Other | Value | Description | | -------------- | --------------------------------------------------- | | `EMPLOYMENT` | Consumer with current and former employment records | | `BANKING` | Personal chequing and savings accounts on file | | `TELCO` | Telco trade with unpaid collection | | `DEATH_NOTICE` | Consumer with a death notice on file | ### Example — test request ```bash cURL theme={null} curl -X POST https://api.deepidv.com/v1/credit-check/hard \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -d '{ "first_name": "John", "last_name": "Doe", "email": "john.doe@example.com", "phone": "+15192223333", "uat": true, "uat_type": "BANKRUPTCY_DISCHARGED" }' ``` ```javascript Node.js theme={null} const response = await fetch("https://api.deepidv.com/v1/credit-check/hard", { method: "POST", headers: { "Content-Type": "application/json", "x-api-key": "YOUR_API_KEY", }, body: JSON.stringify({ first_name: "John", last_name: "Doe", email: "john.doe@example.com", phone: "+15192223333", uat: true, uat_type: "BANKRUPTCY_DISCHARGED", }), }); const data = await response.json(); ``` ```python Python theme={null} import requests response = requests.post( "https://api.deepidv.com/v1/credit-check/hard", headers={ "Content-Type": "application/json", "x-api-key": "YOUR_API_KEY", }, json={ "first_name": "John", "last_name": "Doe", "email": "john.doe@example.com", "phone": "+15192223333", "uat": True, "uat_type": "BANKRUPTCY_DISCHARGED", }, ) ``` # List Bank Statements Source: https://docs.deepidv.com/api-reference/financial/list-bank-statements GET /v1/financial List all bank statement requests for your organization ``` GET /v1/financial ``` Returns a paginated list of bank statement requests for your organization, sorted by creation date (newest first). Credit terms records are excluded from this list. ## Request ### Headers | Header | Required | Description | | ----------- | -------- | ------------ | | `x-api-key` | Yes | Your API key | ### Query parameters | Parameter | Type | Required | Description | | ----------- | ------ | -------- | ----------------------------------------- | | `nextToken` | string | No | Pagination token from a previous response | ### Example request ```bash cURL theme={null} curl -X GET "https://api.deepidv.com/v1/financial" \ -H "x-api-key: YOUR_API_KEY" ``` ```javascript Node.js theme={null} const response = await fetch( "https://api.deepidv.com/v1/financial", { headers: { "x-api-key": "YOUR_API_KEY" }, } ); const data = await response.json(); ``` ```python Python theme={null} import requests response = requests.get( "https://api.deepidv.com/v1/financial", headers={"x-api-key": "YOUR_API_KEY"}, ) ``` ## Response ### 200 — Success | Field | Type | Description | | ---------------- | -------------- | -------------------------------------------------------------------- | | `bankStatements` | array | Array of bank statement summary objects | | `nextToken` | string \| null | Pagination token to fetch the next page. `null` when no more results | ### Bank statement summary object | Field | Type | Description | | ---------------- | ------ | -------------------------------------------------------------- | | `id` | string | Unique bank statement identifier | | `organizationId` | string | Organization that owns this request | | `userId` | string | User ID of the applicant | | `senderUserId` | string | User ID of the person who created the request | | `status` | string | `PENDING`, `IN_PROGRESS`, `FLINKS_IN_PROGRESS`, or `COMPLETED` | | `type` | string | `request` or `upload` | | `Config` | object | Request configuration (e.g. `{ "period": "6" }`) | | `createdAt` | string | ISO 8601 creation timestamp | | `updatedAt` | string | ISO 8601 last-updated timestamp | ### Pagination To fetch the next page, pass the `nextToken` from the response as a query parameter: ```bash theme={null} curl -X GET "https://api.deepidv.com/v1/financial?nextToken=eyJpZCI6ImFiYzEyMyJ9" \ -H "x-api-key: YOUR_API_KEY" ``` Continue paginating until `nextToken` is `null`. ### Error responses | Status | Description | | ----------------------- | ---------------------------------------------- | | `400 Bad Request` | Invalid query parameters | | `401 Unauthorized` | Missing or invalid API key | | `404 Not Found` | No bank statements found for your organization | | `429 Too Many Requests` | Rate limit exceeded | ```json 200 theme={null} { "bankStatements": [ { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "organizationId": "da760e2f-2f7b-4f5d-b394-766ce9c4fad8", "userId": "c3d4e5f6-a7b8-9012-cdef-123456789012", "senderUserId": "d4e5f6a7-b8c9-0123-defa-234567890123", "status": "COMPLETED", "type": "request", "Config": { "period": "6" }, "createdAt": "2026-02-15T14:30:00.000Z", "updatedAt": "2026-02-15T15:00:00.000Z" }, { "id": "b2c3d4e5-f6a7-8901-bcde-f12345678901", "organizationId": "da760e2f-2f7b-4f5d-b394-766ce9c4fad8", "userId": "e5f6a7b8-c9d0-1234-efab-345678901234", "senderUserId": "d4e5f6a7-b8c9-0123-defa-234567890123", "status": "PENDING", "type": "request", "Config": { "period": "3" }, "createdAt": "2026-02-14T10:00:00.000Z", "updatedAt": "2026-02-14T10:00:00.000Z" } ], "nextToken": null } ``` # Retrieve Bank Statement Source: https://docs.deepidv.com/api-reference/financial/retrieve-bank-statement GET /v1/financial/{id} Retrieve a bank statement request by ID ``` GET /v1/financial/{id} ``` Retrieves a bank statement request by its ID. If the request is completed, the response includes the full bank statement data with account details and transaction history. ## Request ### Headers | Header | Required | Description | | ----------- | -------- | ------------ | | `x-api-key` | Yes | Your API key | ### Path parameters | Parameter | Type | Required | Description | | --------- | ------ | -------- | ----------------------------- | | `id` | string | Yes | The bank statement request ID | ### Example request ```bash cURL theme={null} curl -X GET "https://api.deepidv.com/v1/financial/a1b2c3d4-e5f6-7890-abcd-ef1234567890" \ -H "x-api-key: YOUR_API_KEY" ``` ```javascript Node.js theme={null} const response = await fetch( "https://api.deepidv.com/v1/financial/a1b2c3d4-e5f6-7890-abcd-ef1234567890", { headers: { "x-api-key": "YOUR_API_KEY" }, } ); const data = await response.json(); ``` ```python Python theme={null} import requests response = requests.get( "https://api.deepidv.com/v1/financial/a1b2c3d4-e5f6-7890-abcd-ef1234567890", headers={"x-api-key": "YOUR_API_KEY"}, ) ``` ## Response ### 200 — Success ### Bank statement record | Field | Type | Description | | ---------------- | ------ | ----------------------------------------------------------------------------------- | | `id` | string | Unique bank statement identifier | | `organizationId` | string | Organization that owns this request | | `userId` | string | User ID of the applicant | | `senderUserId` | string | User ID of the person who created the request | | `externalId` | string | Your external reference ID (if provided) | | `status` | string | `PENDING`, `IN_PROGRESS`, `FLINKS_IN_PROGRESS`, or `COMPLETED` | | `type` | string | `request` or `upload` | | `Config` | object | Request configuration (e.g. `{ "period": "6" }`) | | `createdAt` | string | ISO 8601 creation timestamp | | `updatedAt` | string | ISO 8601 last-updated timestamp | | `statement` | object | Bank statement data (only present when status is `COMPLETED` and type is `request`) | ### `statement` object Included only when the applicant has completed the bank connection. Sensitive fields (`TransitNumber`, `InstitutionNumber`) are stripped from account data. | Field | Type | Description | | ----------------- | ------ | ------------------------------------------------- | | `HttpStatusCode` | number | Status code from the bank data provider | | `InstitutionName` | string | Name of the financial institution | | `InstitutionId` | number | Identifier for the financial institution | | `Institution` | string | Institution code | | `Accounts` | array | Array of account objects with transaction history | ### Account object | Field | Type | Description | | ---------------- | -------------- | -------------------------------------------------------------- | | `Id` | string | Account identifier | | `Title` | string | Account title/name | | `AccountNumber` | string | Full account number | | `LastFourDigits` | string \| null | Last four digits of account number | | `Category` | string | Account category (e.g. `Operations`) | | `Type` | string | Account type (e.g. `Chequing`, `CreditCard`) | | `AccountType` | string | Detailed account type | | `Currency` | string | Account currency (e.g. `CAD`) | | `Balance` | object | Account balances: `Available`, `Current`, `Limit` | | `Holder` | object | Account holder info: `Name`, `Address`, `Email`, `PhoneNumber` | | `Transactions` | array | Array of transaction objects | ### Transaction object | Field | Type | Description | | ------------- | -------------- | --------------------------------- | | `Id` | string | Transaction identifier | | `Date` | string | Transaction date | | `Description` | string | Transaction description | | `Debit` | number \| null | Debit amount (if applicable) | | `Credit` | number \| null | Credit amount (if applicable) | | `Balance` | number | Running balance after transaction | | `Code` | string \| null | Transaction code | ### Error responses | Status | Description | | ----------------------- | -------------------------------------------------- | | `400 Bad Request` | Invalid bank statement ID format | | `401 Unauthorized` | Missing or invalid API key | | `403 Forbidden` | Bank statement belongs to a different organization | | `404 Not Found` | Bank statement ID does not exist | | `429 Too Many Requests` | Rate limit exceeded | ```json 200 theme={null} { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "organizationId": "da760e2f-2f7b-4f5d-b394-766ce9c4fad8", "userId": "c3d4e5f6-a7b8-9012-cdef-123456789012", "senderUserId": "d4e5f6a7-b8c9-0123-defa-234567890123", "externalId": "loan-app-12345", "status": "COMPLETED", "type": "request", "Config": { "period": "6" }, "createdAt": "2026-02-15T14:30:00.000Z", "updatedAt": "2026-02-15T15:00:00.000Z", "statement": { "HttpStatusCode": 200, "InstitutionName": "Royal Bank of Canada", "InstitutionId": 3, "Institution": "RBC", "Accounts": [ { "Id": "acct_123456", "Title": "Chequing Account", "AccountNumber": "1234567890", "LastFourDigits": "7890", "Category": "Operations", "Type": "Chequing", "AccountType": "PersonalChequing", "Currency": "CAD", "Balance": { "Available": 5230.45, "Current": 5230.45, "Limit": null }, "Holder": { "Name": "JOHN DOE", "Address": { "CivicAddress": "123 Main St", "City": "Toronto", "Province": "ON", "PostalCode": "M5V 2T6", "POBox": null, "Country": "CA" }, "Email": "john.doe@example.com", "PhoneNumber": "+15192223333" }, "Transactions": [ { "Id": "txn_001", "Date": "2026-02-14", "Description": "PAYROLL DEPOSIT", "Debit": null, "Credit": 3200.00, "Balance": 5230.45, "Code": null }, { "Id": "txn_002", "Date": "2026-02-13", "Description": "GROCERY STORE", "Debit": 87.32, "Credit": null, "Balance": 2030.45, "Code": null } ], "Statements": [], "OverdraftLimit": 500 } ] } } ``` # List Bank Statements by External ID Source: https://docs.deepidv.com/api-reference/financial/retrieve-bank-statement-by-external-id GET /v1/financial/externalId/{externalId} List bank statement requests by your external reference ID ``` GET /v1/financial/externalId/{externalId} ``` Returns a paginated list of bank statement requests matching the given external ID, sorted by creation date (newest first). ## Request ### Headers | Header | Required | Description | | ----------- | -------- | ------------ | | `x-api-key` | Yes | Your API key | ### Path parameters | Parameter | Type | Required | Description | | ------------ | ------ | -------- | -------------------------- | | `externalId` | string | Yes | Your external reference ID | ### Query parameters | Parameter | Type | Required | Description | | ----------- | ------ | -------- | ----------------------------------------- | | `nextToken` | string | No | Pagination token from a previous response | ### Example request ```bash cURL theme={null} curl -X GET "https://api.deepidv.com/v1/financial/externalId/loan-app-12345" \ -H "x-api-key: YOUR_API_KEY" ``` ```javascript Node.js theme={null} const response = await fetch( "https://api.deepidv.com/v1/financial/externalId/loan-app-12345", { headers: { "x-api-key": "YOUR_API_KEY" }, } ); const data = await response.json(); ``` ```python Python theme={null} import requests response = requests.get( "https://api.deepidv.com/v1/financial/externalId/loan-app-12345", headers={"x-api-key": "YOUR_API_KEY"}, ) ``` ## Response ### 200 — Success | Field | Type | Description | | ---------------- | -------------- | -------------------------------------------------------------------- | | `bankStatements` | array | Array of bank statement summary objects | | `nextToken` | string \| null | Pagination token to fetch the next page. `null` when no more results | ### Bank statement summary object | Field | Type | Description | | ---------------- | ------ | -------------------------------------------------------------- | | `id` | string | Unique bank statement identifier | | `organizationId` | string | Organization that owns this request | | `userId` | string | User ID of the applicant | | `senderUserId` | string | User ID of the person who created the request | | `status` | string | `PENDING`, `IN_PROGRESS`, `FLINKS_IN_PROGRESS`, or `COMPLETED` | | `type` | string | `request` or `upload` | | `Config` | object | Request configuration (e.g. `{ "period": "6" }`) | | `createdAt` | string | ISO 8601 creation timestamp | | `updatedAt` | string | ISO 8601 last-updated timestamp | ### Error responses | Status | Description | | ----------------------- | --------------------------------------------------- | | `400 Bad Request` | Invalid external ID or query parameters | | `401 Unauthorized` | Missing or invalid API key | | `403 Forbidden` | Bank statements belong to a different organization | | `404 Not Found` | No bank statements found with the given external ID | | `429 Too Many Requests` | Rate limit exceeded | ```json 200 theme={null} { "bankStatements": [ { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "organizationId": "da760e2f-2f7b-4f5d-b394-766ce9c4fad8", "userId": "c3d4e5f6-a7b8-9012-cdef-123456789012", "senderUserId": "d4e5f6a7-b8c9-0123-defa-234567890123", "status": "COMPLETED", "type": "request", "Config": { "period": "6" }, "createdAt": "2026-02-15T14:30:00.000Z", "updatedAt": "2026-02-15T15:00:00.000Z" } ], "nextToken": null } ``` # OpenAPI Specification Source: https://docs.deepidv.com/api-reference/openapi-spec Download the full deepidv API contract as an OpenAPI 3.1 specification The complete deepidv API contract is published as an OpenAPI 3.1 specification, generated directly from the Zod schemas on the server. Use it to drive your own integration tooling — Postman, Insomnia, Stoplight, contract tests in CI, or contract-diffing between releases. Latest version of the deepidv server-to-server API contract (OpenAPI 3.1, YAML). ## Use cases * **Import into API tooling** — every major API client supports OpenAPI 3.1 import. * **Contract-test your integration** in CI by validating recorded requests and responses against the spec. * **Diff the contract** between releases to spot breaking changes before they hit your integration. ## Versioning The spec follows the same release cadence as the deepidv API. Breaking changes are announced ahead of time and called out in the changelog. When in doubt, pin to a specific spec version by downloading and committing the file alongside your integration tests. # Sandbox Models Source: https://docs.deepidv.com/api-reference/sandbox/models Test data models and response examples for sandbox API keys > Reference for all sandbox test data returned by the deepidv API when using a sandbox API key. See [Sandbox Mode](/sandbox) for setup instructions. ## How Sandbox Responses Work When you make a `GET` request with a sandbox API key, the API returns pre-built test data instead of querying real records. The response shape is **identical** to production — the only difference is that the data is synthetic. Non-GET requests (`POST`, `PATCH`, `PUT`, `DELETE`) return a `403` error: ```json theme={null} { "error": "This is a sandbox API key. Use a live API key to create or modify resources." } ``` *** ## Sessions ### Retrieve Session — `GET /v1/sessions/:id` Use well-known test IDs to control the scenario. Any unrecognized ID returns a random scenario. #### `test_verified` — Full "everything" response Returns a `VERIFIED` session with every possible field populated. Use this to validate your integration handles the complete response shape. **Key fields included:** * `type: "session"` (workflow-based) * `workflow_id`, `workflow_steps` (all 11 steps) * `bank_statement_request_id` * `location`, `submitted_at`, `meta_data` * Full `uploads` (primary, secondary, and tertiary IDs, selfies, custom prompt) * Complete `analysis_data` with all sub-objects * `user` (applicant profile) and `sender_user` (session creator profile) **Analysis data includes:** | Field | Description | | ---------------------------- | ------------------------------------------------------------------------------------------------ | | `id_analysis_data` | Primary ID — detect face data, all extracted text fields (21 fields), expiry/state/age checks | | `secondary_id_analysis_data` | Secondary ID — same structure as primary | | `tertiary_id_analysis_data` | Tertiary ID — same structure as primary | | `compare_faces_data` | Face match confidence with full face result (landmarks, quality, bounding box, pose, similarity) | | `id_matches_selfie` | `true` | | `faceliveness_score` | `99.49` | | `pep_sanctions_data` | One PEP match, no sanctions | | `adverse_media_data` | 4 hits across financial\_crime and uncategorized categories with sample articles | | `title_search_data` | Full property title with sale info, owner info, location info, and address details | | `document_risk_data` | One analyzed document with risk signals, AI analysis, and metadata | | `custom_form_data` | One question/answer entry | | `selected_document_types` | Primary (drivers\_license), secondary (passport), tertiary (pr\_card) | ```json test_verified (truncated) theme={null} { "session_record": { "id": "test_verified", "organization_id": "your-org-id", "user_id": "sandbox-user-000000", "sender_user_id": "your-user-id", "external_id": "sandbox-external-001", "status": "VERIFIED", "type": "session", "session_progress": "COMPLETED", "location": { "country": "United States" }, "workflow_id": "sandbox-workflow-001", "bank_statement_request_id": "sandbox-bank-stmt-001", "workflow_steps": [ "ID_VERIFICATION", "FACE_LIVENESS", "AGE_ESTIMATION", "PEP_SANCTIONS", "ADVERSE_MEDIA", "BANK_STATEMENT_UPLOAD", "DOCUMENT_UPLOAD", "TITLE_SEARCH", "CUSTOM_PROMPT", "CUSTOM_FORM", "AI_BANK_STATEMENT_ANALYSIS" ], "meta_data": { "applicantSubmissionIp": "192.168.1.1, 10.0.0.1", "applicantSubmissionDevice": "Mac", "applicantViewTime": "2025-01-15T10:35:00.000Z", "applicantSubmissionBrowser": "Chrome", "applicantSubmissionLocation": "Illinois, Chicago", "applicantSubmissionLocationDetails": { "accuracyRadius": 500, "continent": "North America", "country": "United States", "countryIsoCode": "US", "latitude": 41.8483, "longitude": -87.6517, "subdivision": "Illinois", "timeZone": "America/Chicago" } }, "uploads": { "id_front": true, "id_back": true, "secondary_id_front": true, "secondary_id_back": true, "tertiary_id_front": true, "tertiary_id_back": true, "selfie_front": true, "selfie_right": true, "selfie_left": true, "hold_up_3_fingers": true }, "analysis_data": { "id_analysis_data": { "detect_face_data": [{ "confidence": 99.99, "age_range": { "high": 23, "low": 19 }, "..." : "..." }], "id_extracted_text": [ { "type": "FIRST_NAME", "value": "JANE", "confidence": 97.6 }, { "type": "LAST_NAME", "value": "DOE", "confidence": 97.15 }, { "type": "DATE_OF_BIRTH", "value": "1990/05/15", "confidence": 95.75 }, { "type": "DOCUMENT_NUMBER", "value": "D1234567890", "confidence": 90.11 }, "... 17 more fields" ], "expiry_date_pass": true, "valid_state_pass": true, "age_restriction_pass": true }, "compare_faces_data": { "face_match_confidence": 99.92, "face_match_result": { "similarity": 99.92, "face": { "..." : "..." } } }, "id_matches_selfie": true, "faceliveness_score": 99.49, "pep_sanctions_data": { "peps": [{ "name": "Jane Doe-Smith", "country": "Canada", "score": 0.85 }], "sanctions": null, "both": null }, "adverse_media_data": { "total_hits": 4, "news_exposures": { "financial_crime": { "hits": 2, "articles": ["..."] }, "..." : "..." } }, "secondary_id_analysis_data": { "..." : "same structure as id_analysis_data" }, "tertiary_id_analysis_data": { "..." : "same structure as id_analysis_data" }, "selected_document_types": { "primary": "drivers_license", "secondary": "passport", "tertiary": "pr_card" }, "title_search_data": { "last_market_sale_information": { "..." : "..." }, "owner_information": { "..." : "..." }, "..." : "..." }, "document_risk_data": { "documents_analyzed": 1, "overall_risk_score": 25, "..." : "..." }, "custom_form_data": [{ "question": "What is your Company Business Number", "answer": "1234567890001", "type": "short-text" }] } }, "user": { "id": "sandbox-user-000000", "email": "jane.doe@sandbox.example.com", "first_name": "Jane", "last_name": "Doe", "phone": "+14165551234", "created_at": "2025-01-10T08:00:00.000Z", "updated_at": "2025-01-15T10:42:00.000Z" }, "sender_user": { "id": "your-user-id", "email": "admin@sandbox.example.com", "first_name": "Admin", "last_name": "User", "phone": "+14165559999", "created_at": "2024-12-01T09:00:00.000Z", "updated_at": "2025-01-15T10:30:00.000Z" }, "resource_links": {} } ``` #### `test_rejected` Returns a `REJECTED` session with basic analysis data — failing face match and ID checks. When the rejection came from AI auto-decline, `meta_data.declinedData` records the decisive reason: | Field | Value | | ----------------------- | ---------- | | `status` | `REJECTED` | | `face_match_confidence` | `42.3` | | `id_matches_selfie` | `false` | | `expiry_date_pass` | `false` | | `valid_state_pass` | `false` | ```json theme={null} "meta_data": { "declinedData": { "declined": true, "score": 2, "declinedAt": "2025-01-15T10:43:00.000Z", "reason": { "code": "LOW_FACE_MATCH", "detail": "Face match confidence is low", "slot": "PRIMARY", "severity": "CRITICAL" } } } ``` See [`declinedData`](/api-reference/sessions/retrieve-session#declined-data-object) for the full field reference. #### `test_submitted` Returns a `SUBMITTED` session with basic analysis data — passing scores, awaiting manual review. #### `test_pending` Returns a `PENDING` session with no analysis data and `session_progress: "PENDING"`. #### `test_voided` Returns a `VOIDED` session with no analysis data. #### `test_failed` Returns a `FAILED` session — a session that was auto-failed after reaching the workflow's configured number of failed attempts. `session_progress` is `COMPLETED` and `meta_data.failureData` is populated with the recorded attempts: ```json theme={null} "meta_data": { "failureData": { "failedAttempts": 3, "attempts": [ { "reason": "NO_FACE_DETECTED", "slot": "PRIMARY", "failedAt": "2025-01-15T10:36:00.000Z" }, { "reason": "OCR_NAME_MISSING", "slot": "PRIMARY", "failedAt": "2025-01-15T10:38:14.000Z" }, { "reason": "DOC_TYPE_NOT_ALLOWED", "slot": "SECONDARY", "failedAt": "2025-01-15T10:40:02.000Z" } ] } } ``` See [`failureData`](/api-reference/sessions/retrieve-session#failure-data-object) for the full field reference. ### List Sessions — `GET /v1/sessions` Returns 5 sessions (one per status) with lightweight analysis data. The list uses basic analysis — the full "everything" payload is only returned when fetching `test_verified` by ID. ```json theme={null} { "sessions": [ { "status": "VERIFIED", "..." : "..." }, { "status": "REJECTED", "..." : "..." }, { "status": "PENDING", "..." : "..." }, { "status": "SUBMITTED", "..." : "..." }, { "status": "VOIDED", "..." : "..." } ], "next_token": null } ``` *** ## Financial ### Retrieve Bank Statement — `GET /v1/financial/:id` #### `test_completed` Returns a completed bank statement with full statement data including accounts, transactions, and holder info. ```json test_completed theme={null} { "id": "test_completed", "organizationId": "your-org-id", "status": "COMPLETED", "type": "request", "Config": { "period": "3" }, "statement": { "HttpStatusCode": 200, "Accounts": [ { "Title": "Personal Chequing", "AccountNumber": "****1234", "Type": "Chequing", "Currency": "CAD", "Balance": { "Available": 5188.32, "Current": 5188.32, "Limit": null }, "Holder": { "Name": "Jane Doe", "Address": { "CivicAddress": "123 Sandbox Street", "City": "Toronto", "Province": "ON", "PostalCode": "M5V 2T6", "Country": "CA" }, "Email": "jane.doe@sandbox.example.com", "PhoneNumber": "+14165551234" }, "Transactions": [ { "Description": "Direct Deposit - Employer", "Credit": 3250.00, "Balance": 5420.75 }, { "Description": "Grocery Store Purchase", "Debit": 87.43, "Balance": 5333.32 }, { "Description": "Utility Bill Payment", "Debit": 145.00, "Balance": 5188.32 } ] } ], "InstitutionName": "Sandbox Bank", "Institution": "SandboxBank" } } ``` Sensitive fields (`TransitNumber`, `InstitutionNumber`) are omitted from sandbox responses, matching production behavior. #### `test_pending` Returns a pending bank statement with no `statement` field. ### List Bank Statements — `GET /v1/financial` Returns 2 bank statements (completed and pending) without the `statement` body — matching production list behavior. ```json theme={null} { "bankStatements": [ { "id": "...", "status": "COMPLETED", "type": "request" }, { "id": "...", "status": "PENDING", "type": "request" } ], "nextToken": null } ``` *** ## Credit Checks ### Retrieve Credit Check — `GET /v1/credit-checks/:id` Credit checks come in two types: **soft** and **hard**. Soft checks return only the credit score. Hard checks include the full AI-powered credit insights analysis. The Credit Checks API is coming soon. Sandbox mode lets you preview the response shape before the live API is available. #### `test_soft_pass` — Soft credit check Returns a soft credit check with score only — no detailed insights. ```json test_soft_pass theme={null} { "id": "test_soft_pass", "created_at": "2026-04-06T12:00:00.000Z", "updated_at": "2026-04-07T12:00:00.000Z", "organization_id": "your-org-id", "user_id": "sandbox-user-000000", "sender_user_id": "your-user-id", "type": "SOFT", "credit_check_data": { "credit_insights": { "score": { "value": 741, "status": "FAIR" }, "timestamp": "2026-04-07T12:00:00.000Z" }, "type": "SOFT" } } ``` #### `test_hard_pass` — Hard credit check (low risk) Returns a hard credit check with a clean credit profile and full AI insights. ```json test_hard_pass theme={null} { "id": "test_hard_pass", "created_at": "2026-04-06T12:00:00.000Z", "updated_at": "2026-04-07T12:00:00.000Z", "organization_id": "your-org-id", "user_id": "sandbox-user-000000", "sender_user_id": "your-user-id", "type": "HARD", "credit_check_data": { "credit_insights": { "score": { "value": 741, "status": "FAIR" }, "summary": "The applicant presents a low lending risk with a fair credit score of 741. The profile is characterized by a perfect payment history, low credit utilization, and a clean public record.", "insights": [ { "severity": "INFO", "category": "SCORE", "title": "Fair Score with Recent Credit Activity", "description": "The credit score of 741 is in the fair range..." }, { "severity": "INFO", "category": "PAYMENT_HISTORY", "title": "Excellent Payment History", "description": "All trade lines are consistently reported as 'Paid as agreed'..." }, { "severity": "INFO", "category": "UTILIZATION", "title": "Low Revolving Credit Utilization", "description": "Revolving credit utilization is low at 25%..." }, { "severity": "INFO", "category": "COLLECTIONS", "title": "No Collections or Public Records", "description": "The credit file is clear of any collection accounts, bankruptcies, or other adverse legal items..." } ], "overall_risk": "LOW", "timestamp": "2026-04-07T12:00:00.000Z" }, "type": "HARD" } } ``` #### `test_hard_review` — Hard credit check (medium risk) Returns a hard credit check with late payments, high utilization, and a paid collection — flagged for manual review. | Key Insight | Severity | | -------------------------------- | --------- | | Below-average credit score (612) | `WARNING` | | Late payment history | `WARNING` | | High credit utilization (78%) | `WARNING` | | Paid collection on record | `INFO` | | Recent credit inquiries | `INFO` | #### `test_hard_high_risk` — Hard credit check (high risk) Returns a hard credit check with active consumer proposal, unpaid collections, and a fraud alert. | Key Insight | Severity | | ----------------------------------- | ---------- | | High-risk credit score (480) | `CRITICAL` | | Active consumer proposal | `CRITICAL` | | Multiple unpaid collections | `CRITICAL` | | Severely delinquent payment history | `WARNING` | | Fraud alert on file | `WARNING` | ### List Credit Checks — `GET /v1/credit-checks` Returns all 4 credit check scenarios: ```json theme={null} { "credit_checks": [ { "id": "test_soft_pass", "type": "SOFT", "..." : "..." }, { "id": "test_hard_pass", "type": "HARD", "..." : "..." }, { "id": "test_hard_review", "type": "HARD", "..." : "..." }, { "id": "test_hard_high_risk", "type": "HARD", "..." : "..." } ], "next_token": null } ``` ### Credit Insights Fields | Field | Type | Present In | Description | | ------------------------ | -------- | ----------- | -------------------------------------------------------------------------------------------------- | | `score.value` | `number` | Soft + Hard | Numeric credit score | | `score.status` | `string` | Soft + Hard | Score bucket: `GOOD`, `FAIR`, `POOR`, `HIGH_RISK` | | `summary` | `string` | Hard only | Plain-English summary of the credit profile | | `insights` | `array` | Hard only | Categorized risk insights from AI analysis | | `insights[].category` | `string` | Hard only | One of: `SCORE`, `PAYMENT_HISTORY`, `UTILIZATION`, `COLLECTIONS`, `INQUIRIES`, `FRAUD`, `IDENTITY` | | `insights[].severity` | `string` | Hard only | `INFO`, `WARNING`, or `CRITICAL` | | `insights[].title` | `string` | Hard only | Short insight title | | `insights[].description` | `string` | Hard only | Detailed explanation | | `overall_risk` | `string` | Hard only | Overall risk level: `LOW`, `MEDIUM`, `HIGH`, `CRITICAL` | | `timestamp` | `string` | Soft + Hard | ISO 8601 timestamp of when insights were generated | *** ## Workflows ### Retrieve Workflow — `GET /v1/workflows/:id` #### `test_workflow` — Full workflow Returns a workflow with all 11 available steps, each with realistic config objects: | Step | Config | | ---------------------------- | ---------------------------------------------------------------------------------------------------------- | | `id-verification` | Age restriction (18–55), expiry date check, secondary + tertiary ID required, valid ID types, valid states | | `face-liveness` | Confidence threshold 70, FaceMovementChallenge method | | `age-estimation` | Minimum age 18 | | `pep-sanctions` | Default config | | `adverse-media` | Default config | | `bank-statement-upload` | Checking account, 12-month period | | `document-upload` | "Articles of Incorporation" required | | `title-search` | USA, all location info fields enabled | | `custom-prompt` | "Hold up 3 fingers" | | `custom-form` | "What is your Company Business Number" (short-text) | | `ai-bank-statement-analysis` | Default config | ```json test_workflow (truncated) theme={null} { "workflow": { "id": "test_workflow", "name": "Sandbox Full Workflow", "status": "active", "steps": [ { "id": "id-verification", "config": { "id-scan-settings": { "require-secondary-id": true, "require-tertiary-id": true }, "valid-id-types-settings": { "valid-id-types": { "driver-license-ca": true, "passport-ca": true, "..." : "..." } }, "age-restriction-settings": { "minimum-age": { "lower": 18, "upper": 55 } }, "..." : "..." } }, { "id": "face-liveness", "config": { "..." : "..." } }, { "id": "age-estimation", "config": { "..." : "..." } }, { "id": "pep-sanctions", "config": {} }, { "id": "adverse-media", "config": {} }, { "id": "bank-statement-upload", "config": { "..." : "..." } }, { "id": "document-upload", "config": { "..." : "..." } }, { "id": "title-search", "config": { "..." : "..." } }, { "id": "custom-prompt", "config": { "..." : "..." } }, { "id": "custom-form", "config": { "..." : "..." } }, { "id": "ai-bank-statement-analysis", "config": {} } ] } } ``` #### Any other ID — Basic workflow Returns a workflow with 2 steps: `id-verification` and `face-liveness`. ### List Workflows — `GET /v1/workflows` Returns 2 workflows (summary only): ```json theme={null} { "workflows": [ { "id": "...", "name": "Sandbox Full Workflow", "created_at": "..." }, { "id": "...", "name": "Sandbox Basic Workflow", "created_at": "..." } ] } ``` # Scan Document Source: https://docs.deepidv.com/api-reference/server-to-server/document-scan POST /v1/document/scan Extract fields from a government-issued ID and run authenticity checks ``` POST /v1/document/scan ``` Submit a single image of a government-issued ID. The response contains the extracted text fields (name, date of birth, document number, address, expiry, etc.), a normalized `documentType`, and an average extraction confidence score. ## Request ### Headers | Header | Required | Description | | -------------- | -------- | ------------------------------------------- | | `x-api-key` | Yes | Your API key | | `Content-Type` | Yes | `multipart/form-data` or `application/json` | ### Body parameters The endpoint accepts the image either as raw multipart bytes or as a JSON string (base64, base64url, or an S3 key from an earlier presigned upload). See the [overview](/api-reference/server-to-server/overview#image-inputs) for the shared image rules — accepted formats, the 15 MB limit, and when to prefer one over the other. | Parameter | Type | Required | Description | | -------------- | ---------------- | -------- | ----------------------------------------------------------------------------------------- | | `image` | binary \| string | Yes | The image. For multipart, the file part. For JSON, a base64, base64url, or S3-key string. | | `documentType` | string | No | One of `passport`, `drivers_license`, `national_id`, `auto`. Defaults to `auto`. | ### Example request ```bash cURL (multipart) theme={null} curl -X POST https://api.deepidv.com/v1/document/scan \ -H "x-api-key: YOUR_API_KEY" \ -F "image=@/path/to/id.jpg" \ -F "documentType=drivers_license" ``` ```bash cURL (JSON) theme={null} curl -X POST https://api.deepidv.com/v1/document/scan \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "image": "uploads/abc123-driver-license.jpg", "documentType": "drivers_license" }' ``` ```javascript Node.js theme={null} import fs from "node:fs"; const form = new FormData(); form.append("image", new Blob([fs.readFileSync("/path/to/id.jpg")]), "id.jpg"); form.append("documentType", "drivers_license"); const response = await fetch("https://api.deepidv.com/v1/document/scan", { method: "POST", headers: { "x-api-key": "YOUR_API_KEY" }, body: form, }); const data = await response.json(); ``` ```python Python theme={null} import requests with open("/path/to/id.jpg", "rb") as f: response = requests.post( "https://api.deepidv.com/v1/document/scan", headers={"x-api-key": "YOUR_API_KEY"}, files={"image": f}, data={"documentType": "drivers_license"}, ) ``` ## Response ### 200 — Success | Field | Type | Description | | ---------------- | ------------ | ----------------------------------------------------------------- | | `documentType` | string | Normalized document type extracted from the image | | `fullName` | string | Full name as printed on the document | | `firstName` | string | First / given name | | `lastName` | string | Last / family name | | `dateOfBirth` | string | Date of birth as printed on the document | | `gender` | string | Gender as printed on the document | | `nationality` | string | Nationality as printed on the document | | `documentNumber` | string | Document number | | `expirationDate` | string | Document expiration date | | `issuingCountry` | string | Issuing country | | `address` | string | Address as printed on the document (when present) | | `mrzData` | string | Machine-readable zone, raw (passports / some IDs) | | `rawFields` | object | All fields returned by AWS Textract, keyed by Textract field name | | `confidence` | number (0–1) | Average extraction confidence across all detected fields | ### Error responses | Status | Description | | ------ | ------------------------------------------------------------------ | | `400` | Invalid body, unsupported image format, or image larger than 15 MB | | `401` | Missing or invalid `x-api-key` | | `402` | Insufficient token balance | | `403` | The supplied `image` S3 key is not readable by this organization | | `429` | Rate limit exceeded | | `500` | Unexpected server error — safe to retry with backoff | ```json 200 theme={null} { "documentType": "drivers_license", "fullName": "JANE Q PUBLIC", "firstName": "JANE", "lastName": "PUBLIC", "dateOfBirth": "1990-04-12", "gender": "F", "nationality": "USA", "documentNumber": "D1234567", "expirationDate": "2030-04-12", "issuingCountry": "USA", "address": "123 MAIN ST, SPRINGFIELD, IL 62701", "rawFields": { "FIRST_NAME": "JANE", "LAST_NAME": "PUBLIC", "DATE_OF_BIRTH": "04/12/1990" }, "confidence": 0.97 } ``` ## When to use `/v1/identity/verify` instead If you also have a selfie and want a single round-trip that runs document scan + face detect + face compare in parallel, call [`/v1/identity/verify`](/api-reference/server-to-server/identity-verify) instead — it returns one aggregated payload with an `overallConfidence` score. # Compare Faces Source: https://docs.deepidv.com/api-reference/server-to-server/face-compare POST /v1/face/compare Compare two faces and return a similarity score ``` POST /v1/face/compare ``` Submit two images and receive a similarity score plus a boolean `isMatch` decision. Typical use: comparing a selfie to the photo on a scanned ID. ## Request ### Headers | Header | Required | Description | | -------------- | -------- | ------------------------------------------- | | `x-api-key` | Yes | Your API key | | `Content-Type` | Yes | `multipart/form-data` or `application/json` | ### Body parameters | Parameter | Type | Required | Description | | --------- | ---------------- | -------- | ----------------------------------------------------------------------------------------------- | | `source` | binary \| string | Yes | The first image. For multipart, the file part. For JSON, a base64, base64url, or S3-key string. | | `target` | binary \| string | Yes | The second image. Same input rules as `source`. | You can mix input modes within a single call — for example, send `source` as raw multipart bytes and `target` as a JSON S3 key in a multipart body. See the [overview](/api-reference/server-to-server/overview#image-inputs) for the shared image rules. ### Example request ```bash cURL (multipart) theme={null} curl -X POST https://api.deepidv.com/v1/face/compare \ -H "x-api-key: YOUR_API_KEY" \ -F "source=@id-photo.jpg" \ -F "target=@selfie.jpg" ``` ```bash cURL (JSON) theme={null} curl -X POST https://api.deepidv.com/v1/face/compare \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "source": "uploads/id-photo-abc.jpg", "target": "uploads/selfie-xyz.jpg" }' ``` ```javascript Node.js theme={null} import fs from "node:fs"; const form = new FormData(); form.append("source", new Blob([fs.readFileSync("id-photo.jpg")]), "source.jpg"); form.append("target", new Blob([fs.readFileSync("selfie.jpg")]), "target.jpg"); const response = await fetch("https://api.deepidv.com/v1/face/compare", { method: "POST", headers: { "x-api-key": "YOUR_API_KEY" }, body: form, }); const data = await response.json(); ``` ```python Python theme={null} import requests with open("id-photo.jpg", "rb") as src, open("selfie.jpg", "rb") as tgt: response = requests.post( "https://api.deepidv.com/v1/face/compare", headers={"x-api-key": "YOUR_API_KEY"}, files={"source": src, "target": tgt}, ) ``` ## Response ### 200 — Success | Field | Type | Description | | -------------------- | ------- | --------------------------------------------------------------------- | | `isMatch` | boolean | True when `confidence >= threshold` | | `confidence` | number | Raw similarity score from AWS Rekognition, **0–100** (not normalized) | | `threshold` | number | Server-side similarity threshold used for `isMatch`, **0–100** | | `sourceFaceDetected` | boolean | True when a face was detected in `source` | | `targetFaceDetected` | boolean | True when a face was detected in `target` | ### Error responses | Status | Description | | ------ | ------------------------------------------------------------------------ | | `400` | Invalid body, unsupported image format, or image larger than 15 MB | | `401` | Missing or invalid `x-api-key` | | `402` | Insufficient token balance | | `403` | A supplied `source`/`target` S3 key is not readable by this organization | | `429` | Rate limit exceeded | | `500` | Unexpected server error — safe to retry with backoff | ```json 200 — match theme={null} { "isMatch": true, "confidence": 96.4, "threshold": 80, "sourceFaceDetected": true, "targetFaceDetected": true } ``` ```json 200 — both faces present, similarity below threshold theme={null} { "isMatch": false, "confidence": 62.1, "threshold": 80, "sourceFaceDetected": true, "targetFaceDetected": true } ``` ```json 200 — no face in source theme={null} { "isMatch": false, "confidence": 0, "threshold": 80, "sourceFaceDetected": false, "targetFaceDetected": true } ``` ```json 200 — no face in target theme={null} { "isMatch": false, "confidence": 0, "threshold": 80, "sourceFaceDetected": true, "targetFaceDetected": false } ``` ## Telling failure modes apart The response shape lets you distinguish three distinct `isMatch: false` outcomes without a second API call: | Outcome | `confidence` | `sourceFaceDetected` | `targetFaceDetected` | | ------------------------------------ | ------------ | -------------------- | -------------------- | | Both faces found, similarity too low | `> 0` | `true` | `true` | | No face in source | `0` | `false` | `true`/`false` | | No face in target | `0` | `true` | `false` | | No face in either image | `0` | `false` | `false` | Surface a "retake your selfie" prompt when `sourceFaceDetected` is `false`, a "retake the ID photo" prompt when `targetFaceDetected` is `false`, and a "doesn't match" outcome when both booleans are `true` but `isMatch` is `false`. # Detect Face Source: https://docs.deepidv.com/api-reference/server-to-server/face-detect POST /v1/face/detect Detect a face in an image and return bounding box and landmarks ``` POST /v1/face/detect ``` Submit a single image. If a face is present, the response includes its bounding box, key facial landmarks, and a normalized confidence score. Use this endpoint when you need to confirm a usable face image before sending it to [`/v1/face/compare`](/api-reference/server-to-server/face-compare) or [`/v1/face/estimate-age`](/api-reference/server-to-server/face-estimate-age) — for example, to give the user immediate feedback that their selfie is too dark or off-frame. ## Request ### Headers | Header | Required | Description | | -------------- | -------- | ------------------------------------------- | | `x-api-key` | Yes | Your API key | | `Content-Type` | Yes | `multipart/form-data` or `application/json` | ### Body parameters | Parameter | Type | Required | Description | | --------- | ---------------- | -------- | ----------------------------------------------------------------------------------------- | | `image` | binary \| string | Yes | The image. For multipart, the file part. For JSON, a base64, base64url, or S3-key string. | See the [overview](/api-reference/server-to-server/overview#image-inputs) for the shared image rules. ### Example request ```bash cURL (multipart) theme={null} curl -X POST https://api.deepidv.com/v1/face/detect \ -H "x-api-key: YOUR_API_KEY" \ -F "image=@selfie.jpg" ``` ```bash cURL (JSON) theme={null} curl -X POST https://api.deepidv.com/v1/face/detect \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "image": "uploads/selfie-abc123.jpg" }' ``` ```javascript Node.js theme={null} import fs from "node:fs"; const form = new FormData(); form.append( "image", new Blob([fs.readFileSync("selfie.jpg")]), "selfie.jpg", ); const response = await fetch("https://api.deepidv.com/v1/face/detect", { method: "POST", headers: { "x-api-key": "YOUR_API_KEY" }, body: form, }); const data = await response.json(); ``` ```python Python theme={null} import requests with open("selfie.jpg", "rb") as f: response = requests.post( "https://api.deepidv.com/v1/face/detect", headers={"x-api-key": "YOUR_API_KEY"}, files={"image": f}, ) ``` ## Response ### 200 — Success | Field | Type | Description | | -------------- | ------------ | -------------------------------------------------------------------- | | `faceDetected` | boolean | True when a face was detected | | `confidence` | number (0–1) | Detection confidence for the top face. `0` when no face was detected | | `boundingBox` | object | Normalized bounding-box coordinates (omitted when no face detected) | | `landmarks` | `object[]` | Facial landmark points (omitted when no face detected) | `boundingBox` shape: `{ top, left, width, height }`, each normalized to `0–1` against the source image dimensions. `landmarks[]` shape: `{ type, x, y }`, where `type` is the landmark name (e.g. `eyeLeft`, `nose`, `mouthRight`) and `x`/`y` are normalized to the image dimensions. ### Error responses | Status | Description | | ------ | ------------------------------------------------------------------ | | `400` | Invalid body, unsupported image format, or image larger than 15 MB | | `401` | Missing or invalid `x-api-key` | | `402` | Insufficient token balance | | `403` | The supplied `image` S3 key is not readable by this organization | | `429` | Rate limit exceeded | | `500` | Unexpected server error — safe to retry with backoff | ```json 200 — face found theme={null} { "faceDetected": true, "confidence": 0.99, "boundingBox": { "top": 0.18, "left": 0.31, "width": 0.41, "height": 0.52 }, "landmarks": [ { "type": "eyeLeft", "x": 0.44, "y": 0.38 }, { "type": "eyeRight", "x": 0.58, "y": 0.38 }, { "type": "nose", "x": 0.51, "y": 0.47 }, { "type": "mouthLeft", "x": 0.46, "y": 0.59 }, { "type": "mouthRight", "x": 0.57, "y": 0.59 } ] } ``` ```json 200 — no face theme={null} { "faceDetected": false, "confidence": 0 } ``` When no face is detected the endpoint still returns `200 OK` with `faceDetected: false`. Treat that as a UX-level failure (prompt the user to retake) rather than an integration error. Only the highest-confidence face is returned. # Estimate Age Source: https://docs.deepidv.com/api-reference/server-to-server/face-estimate-age POST /v1/face/estimate-age Estimate the age range and gender from a single face image ``` POST /v1/face/estimate-age ``` Submit a single image containing a face. The response returns an age range, a single integer estimate within the range, and a gender estimate with confidence. Use this when you need to gate an experience by approximate age — for example, age-restricted product access — without requiring a full ID scan. ## Request ### Headers | Header | Required | Description | | -------------- | -------- | ------------------------------------------- | | `x-api-key` | Yes | Your API key | | `Content-Type` | Yes | `multipart/form-data` or `application/json` | ### Body parameters | Parameter | Type | Required | Description | | --------- | ---------------- | -------- | ----------------------------------------------------------------------------------------- | | `image` | binary \| string | Yes | The image. For multipart, the file part. For JSON, a base64, base64url, or S3-key string. | See the [overview](/api-reference/server-to-server/overview#image-inputs) for the shared image rules. ### Example request ```bash cURL (multipart) theme={null} curl -X POST https://api.deepidv.com/v1/face/estimate-age \ -H "x-api-key: YOUR_API_KEY" \ -F "image=@selfie.jpg" ``` ```bash cURL (JSON) theme={null} curl -X POST https://api.deepidv.com/v1/face/estimate-age \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "image": "uploads/selfie-abc123.jpg" }' ``` ```javascript Node.js theme={null} import fs from "node:fs"; const form = new FormData(); form.append("image", new Blob([fs.readFileSync("selfie.jpg")]), "selfie.jpg"); const response = await fetch("https://api.deepidv.com/v1/face/estimate-age", { method: "POST", headers: { "x-api-key": "YOUR_API_KEY" }, body: form, }); const data = await response.json(); ``` ```python Python theme={null} import requests with open("selfie.jpg", "rb") as f: response = requests.post( "https://api.deepidv.com/v1/face/estimate-age", headers={"x-api-key": "YOUR_API_KEY"}, files={"image": f}, ) ``` ## Response ### 200 — Success | Field | Type | Description | | ------------------ | ------------ | --------------------------------------------------------------- | | `faceDetected` | boolean | True when a face was detected | | `estimatedAge` | integer | Single age estimate (midpoint of the range). Omitted if no face | | `ageRange` | object | `{ low, high }` integer age band. Omitted if no face | | `gender` | string | `male` or `female`. Omitted if no face | | `genderConfidence` | number (0–1) | Confidence of the gender classification. Omitted if no face | ### Error responses | Status | Description | | ------ | ------------------------------------------------------------------ | | `400` | Invalid body, unsupported image format, or image larger than 15 MB | | `401` | Missing or invalid `x-api-key` | | `402` | Insufficient token balance | | `403` | The supplied `image` S3 key is not readable by this organization | | `429` | Rate limit exceeded | | `500` | Unexpected server error — safe to retry with backoff | ```json 200 — face found theme={null} { "faceDetected": true, "estimatedAge": 34, "ageRange": { "low": 28, "high": 40 }, "gender": "female", "genderConfidence": 0.97 } ``` ```json 200 — no face theme={null} { "faceDetected": false } ``` ## Interpreting the range The returned range is broad by design — Rekognition estimates an age band, not a single value. A common pattern is: * If `ageRange.low >= your_minimum_age`, accept immediately. * If `ageRange.high < your_minimum_age`, reject immediately. * Otherwise the user falls in the ambiguous band — fall back to a full ID-based verification via [`/v1/document/scan`](/api-reference/server-to-server/document-scan) or [`/v1/identity/verify`](/api-reference/server-to-server/identity-verify). Age estimation is probabilistic and should not be used as a sole signal for regulated age checks (alcohol, tobacco, gambling). Combine it with an ID scan when compliance matters. # Verify Identity Source: https://docs.deepidv.com/api-reference/server-to-server/identity-verify POST /v1/identity/verify Run document scan, face detect, and face compare in a single call ``` POST /v1/identity/verify ``` Submit one document image and one selfie image. The endpoint runs [`/v1/document/scan`](/api-reference/server-to-server/document-scan), [`/v1/face/detect`](/api-reference/server-to-server/face-detect) on the selfie, and [`/v1/face/compare`](/api-reference/server-to-server/face-compare) between the document photo and the selfie — **in parallel** — and returns a single aggregated response with an `overallConfidence` score. Use this when you have both images already and want one round-trip instead of orchestrating three calls yourself. If you only have one of the two images, call the individual endpoints directly. ## Request ### Headers | Header | Required | Description | | -------------- | -------- | ------------------------------------------- | | `x-api-key` | Yes | Your API key | | `Content-Type` | Yes | `multipart/form-data` or `application/json` | ### Body parameters | Parameter | Type | Required | Description | | --------------- | ---------------- | -------- | ---------------------------------------------------------------------------------- | | `documentImage` | binary \| string | Yes | Image of the document. Multipart file part or JSON base64/base64url/S3-key string. | | `faceImage` | binary \| string | Yes | Selfie image. Same input rules as `documentImage`. | | `documentType` | string | No | One of `passport`, `drivers_license`, `national_id`, `auto`. Defaults to `auto`. | You can mix input modes — for example, send `documentImage` as a JSON S3 key (from an earlier presigned upload) and `faceImage` as fresh multipart bytes. See the [overview](/api-reference/server-to-server/overview#image-inputs) for the shared image rules. ### Example request ```bash cURL (multipart) theme={null} curl -X POST https://api.deepidv.com/v1/identity/verify \ -H "x-api-key: YOUR_API_KEY" \ -F "documentImage=@/path/to/id.jpg" \ -F "faceImage=@/path/to/selfie.jpg" \ -F "documentType=drivers_license" ``` ```bash cURL (JSON) theme={null} curl -X POST https://api.deepidv.com/v1/identity/verify \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "documentImage": "uploads/id-abc123.jpg", "faceImage": "uploads/selfie-xyz.jpg", "documentType": "drivers_license" }' ``` ```javascript Node.js theme={null} import fs from "node:fs"; const form = new FormData(); form.append( "documentImage", new Blob([fs.readFileSync("/path/to/id.jpg")]), "id.jpg", ); form.append( "faceImage", new Blob([fs.readFileSync("/path/to/selfie.jpg")]), "selfie.jpg", ); form.append("documentType", "drivers_license"); const response = await fetch("https://api.deepidv.com/v1/identity/verify", { method: "POST", headers: { "x-api-key": "YOUR_API_KEY" }, body: form, }); const data = await response.json(); ``` ```python Python theme={null} import requests with open("/path/to/id.jpg", "rb") as doc, open("/path/to/selfie.jpg", "rb") as face: response = requests.post( "https://api.deepidv.com/v1/identity/verify", headers={"x-api-key": "YOUR_API_KEY"}, files={"documentImage": doc, "faceImage": face}, data={"documentType": "drivers_license"}, ) ``` ## Response ### 200 — Success All confidence and threshold values on this response are reported on a **0–100** scale. | Field | Type | Description | | ------------------- | -------------- | ---------------------------------------------------------------------------------------- | | `verified` | boolean | Overall pass/fail derived from the three sub-results | | `document` | object | Subset of [`/v1/document/scan`](/api-reference/server-to-server/document-scan) response | | `faceDetection` | object | Result of [`/v1/face/detect`](/api-reference/server-to-server/face-detect) on the selfie | | `faceMatch` | object | Comparison between document photo and selfie | | `overallConfidence` | number (0–100) | Weighted aggregate confidence across all three sub-results | #### `document` | Field | Type | Description | | ---------------- | -------------- | -------------------------------------- | | `documentType` | string | Normalized document type | | `fullName` | string | Full name as printed on the document | | `firstName` | string | First name | | `lastName` | string | Last name | | `dateOfBirth` | string | Date of birth | | `gender` | string | Gender | | `nationality` | string | Nationality | | `documentNumber` | string | Document number | | `expirationDate` | string | Document expiration date | | `issuingCountry` | string | Issuing country | | `address` | string | Address (when present) | | `confidence` | number (0–100) | Average document-extraction confidence | #### `faceDetection` | Field | Type | Description | | -------------- | -------------- | ------------------------------------------- | | `faceDetected` | boolean | True when a face was detected in the selfie | | `confidence` | number (0–100) | Detection confidence for the top face | #### `faceMatch` | Field | Type | Description | | ------------ | -------------- | ----------------------------------- | | `isMatch` | boolean | True when `confidence >= threshold` | | `confidence` | number (0–100) | Best face-match similarity | | `threshold` | number (0–100) | Match threshold | ### Error responses | Status | Description | | ------ | ------------------------------------------------------------------ | | `400` | Invalid body, unsupported image format, or image larger than 15 MB | | `401` | Missing or invalid `x-api-key` | | `402` | Insufficient token balance | | `403` | A supplied S3 key is not readable by this organization | | `429` | Rate limit exceeded | | `500` | Unexpected server error — safe to retry with backoff | ```json 200 theme={null} { "verified": true, "document": { "documentType": "drivers_license", "fullName": "JANE Q PUBLIC", "firstName": "JANE", "lastName": "PUBLIC", "dateOfBirth": "1990-04-12", "gender": "F", "nationality": "USA", "documentNumber": "D1234567", "expirationDate": "2030-04-12", "issuingCountry": "USA", "address": "123 MAIN ST, SPRINGFIELD, IL 62701", "confidence": 97 }, "faceDetection": { "faceDetected": true, "confidence": 99 }, "faceMatch": { "isMatch": true, "confidence": 96.4, "threshold": 80 }, "overallConfidence": 95 } ``` ## How `overallConfidence` is calculated `overallConfidence` is a fixed weighted blend of the three sub-results: | Component | Weight | | ------------------------------------------------ | ------ | | Document extraction confidence | 0.4 | | Face-detection confidence (selfie) | 0.2 | | Face-match confidence (document photo vs selfie) | 0.4 | The weights are fixed today. If you need to apply your own policy on top of the individual signals, use the per-block fields and ignore `overallConfidence`. ## Partial failures The three sub-calls run in parallel and are independent. If one fails (for example, no face detected in the selfie), the others still run and the corresponding block reports the failure in-band rather than 4xx-ing the whole request. Always inspect each block before acting on `overallConfidence`. # Overview Source: https://docs.deepidv.com/api-reference/server-to-server/overview Server-to-server API integrations for direct backend verification Server-to-server APIs enable direct backend integrations without using deepidv's hosted verification flows. If you want to build your own UI and handle the applicant experience yourself, these APIs let you submit data directly from your server and receive results synchronously — ideal for custom frontends, batch processing, automated pipelines, and real-time decisioning. ## Endpoints Five endpoints are available under the `/v1` prefix: | Endpoint | Purpose | | ---------------------------- | ---------------------------------------------------------------------- | | `POST /v1/document/scan` | Extract fields from a government-issued ID and run authenticity checks | | `POST /v1/face/detect` | Detect a face in an image and return bounding box + landmarks | | `POST /v1/face/compare` | Compare two faces and return a similarity score | | `POST /v1/face/estimate-age` | Estimate the age range from a single face image | | `POST /v1/identity/verify` | Run document scan + face detect + face compare in a single call | All five endpoints share the same auth, image-input model, and error format described below. ## Authentication Every request must include your organization API key in the `x-api-key` header. See [Authentication](/authentication) for full details and rate limit information. ```bash theme={null} x-api-key: YOUR_API_KEY ``` ## Image inputs Every server-to-server endpoint accepts images in one of two ways. You can mix them within a single call (for example, `/v1/face/compare` can take one image as a `fileKey` and the other as raw multipart bytes). ### Option 1 — `multipart/form-data` Send the raw image bytes as a form field. Use this when the image lives on your server and you don't need to keep it around after the call. ```bash cURL theme={null} curl -X POST https://api.deepidv.com/v1/face/detect \ -H "x-api-key: YOUR_API_KEY" \ -F "image=@selfie.jpg" ``` Supported content types: `image/jpeg`, `image/png`, `image/gif`, `image/bmp`, `image/tiff`, `image/webp`. Max size: 15 MB per image. The content type is detected from the file's magic bytes, not the `Content-Type` header — mislabelled files are still accepted as long as the bytes match a supported format. ### Option 2 — `application/json` with a presigned `fileKey`, base64, or base64url string If your image is already in deepidv-managed storage (for example, uploaded during an earlier session or via the upload endpoint), reference it by its `fileKey`. You can also send the image inline as a base64 or base64url-encoded string. In all three cases the value is a **bare string**: ```bash cURL — S3 key theme={null} curl -X POST https://api.deepidv.com/v1/face/detect \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "image": "uploads/abc123-selfie.jpg" }' ``` ```bash cURL — base64 theme={null} curl -X POST https://api.deepidv.com/v1/face/detect \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "image": "iVBORw0KGgoAAAANSUhEUgAA..." }' ``` The server detects which form you sent by inspecting the string (valid base64 → base64; valid base64url → base64url; otherwise → S3 key). Re-using an existing `fileKey` is the recommended pattern for high-throughput pipelines since it avoids re-uploading bytes on each call. ## Response shape Successful responses are JSON, with a service-specific payload per endpoint (see each endpoint's reference page). ## Error shape All errors return a consistent JSON shape: ```json theme={null} { "error": "Human-readable description of what went wrong", "hints": ["Optional suggestions to help you recover"] } ``` Common status codes: | Status | Meaning | | ------- | -------------------------------------------------------------------------------- | | **400** | Malformed request — bad image, missing field, invalid schema | | **401** | Missing or invalid `x-api-key` | | **402** | Insufficient token balance | | **403** | API key lacks access to the endpoint, or the referenced `fileKey` cannot be read | | **429** | Rate limit exceeded — back off and retry | | **500** | Unexpected error on our side — safe to retry with backoff | ## Compound verification `POST /v1/identity/verify` is a compound endpoint that runs `/v1/document/scan`, `/v1/face/detect`, and `/v1/face/compare` in parallel and returns a single aggregated response with an `overallConfidence` score. Use it when you want one round-trip for a full ID + selfie verification instead of orchestrating the three calls yourself. ## What's next Endpoint-by-endpoint request and response schemas are coming to this section shortly. In the meantime, the contract is also published as an OpenAPI specification in the open-api repository — reach out to your account contact if you need early access. # Create Session Source: https://docs.deepidv.com/api-reference/sessions/create-session POST /v1/sessions Create a new identity verification session ``` POST /v1/sessions ``` Creates a new identity verification session and optionally sends email and SMS invitations to the applicant. ## Request ### Headers | Header | Required | Description | | -------------- | -------- | ------------------ | | `x-api-key` | Yes | Your API key | | `Content-Type` | Yes | `application/json` | ### Body parameters Both camelCase and snake\_case parameter names are accepted. If both are provided for the same field, the camelCase value takes priority. | Parameter | Alias | Type | Required | Description | | ------------------- | ----------------- | ------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `first_name` | `firstName` | string | Yes | Applicant's first name | | `last_name` | `lastName` | string | Yes | Applicant's last name | | `email` | — | string | Yes | Applicant's email address | | `phone` | — | string | Yes | Applicant's phone number in E.164 format (e.g. `+15192223333`) | | `external_id` | `externalId` | string | No | Your internal reference ID for this session | | `send_email_invite` | `sendEmailInvite` | boolean | No | Send an email invitation to the applicant. Defaults to `true` | | `send_phone_invite` | `sendPhoneInvite` | boolean | No | Send an SMS invitation to the applicant. Defaults to `true` | | `workflow_id` | `workflowId` | string | No | ID of the workflow to use. If omitted, runs as a standalone verification | | `redirect_url` | `redirectUrl` | string | No | HTTPS URL to redirect the end-user to after the verification session ends. Must be a valid HTTPS URL | | `expires_in_hours` | `expiresInHours` | integer | No | Auto-expire the session after this many hours (1-8760). Overrides the workflow's expiry setting if both are provided. If omitted, the workflow's expiry setting is used (if configured) | ### Example request ```bash cURL theme={null} curl -X POST https://api.deepidv.com/v1/sessions \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -d '{ "first_name": "John", "last_name": "Doe", "email": "john.doe@example.com", "phone": "+15192223333", "external_id": "user-12345", "workflow_id": "wf_abc123", "redirect_url": "https://yourapp.com/verify-callback", "expires_in_hours": 48 }' ``` ```javascript Node.js theme={null} const response = await fetch("https://api.deepidv.com/v1/sessions", { method: "POST", headers: { "Content-Type": "application/json", "x-api-key": "YOUR_API_KEY", }, body: JSON.stringify({ first_name: "John", last_name: "Doe", email: "john.doe@example.com", phone: "+15192223333", external_id: "user-12345", workflow_id: "wf_abc123", redirect_url: "https://yourapp.com/verify-callback", expires_in_hours: 48, }), }); const data = await response.json(); ``` ```python Python theme={null} import requests response = requests.post( "https://api.deepidv.com/v1/sessions", headers={ "Content-Type": "application/json", "x-api-key": "YOUR_API_KEY", }, json={ "first_name": "John", "last_name": "Doe", "email": "john.doe@example.com", "phone": "+15192223333", "external_id": "user-12345", "workflow_id": "wf_abc123", "redirect_url": "https://yourapp.com/verify-callback", "expires_in_hours": 48, }, ) ``` ## Response ### 200 — Success | Field | Type | Description | | ------------- | ------ | ------------------------------------------------------------------------------------------- | | `id` | string | Unique identifier for the created session | | `session_url` | string | Base URL where the applicant completes verification | | `externalId` | string | Your external ID (only returned if provided in the request) | | `expires_at` | string | ISO 8601 timestamp when the session will auto-expire (only present if expiry is configured) | | `links` | array | Associated verification links | ### Error responses | Status | Description | | ----------------------- | ------------------------------------------------------------- | | `400 Bad Request` | Invalid request body — check required fields and phone format | | `401 Unauthorized` | Missing or invalid API key | | `402 Payment Required` | Insufficient token balance | | `404 Not Found` | Workflow ID not found | | `429 Too Many Requests` | Rate limit exceeded | ```json 200 theme={null} { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "session_url": "https://verify.deepidv.com/session/a1b2c3d4-e5f6-7890-abcd-ef1234567890?oid=your-org-id&redirect_url=https%3A%2F%2Fyourapp.com%2Fverify-callback", "externalId": "user-12345", "links": [ { "rel": "admin_console", "href": "https://app.deepidv.com/dashboard/session/a1b2c3d4-e5f6-7890-abcd-ef1234567890", "description": "Click this link to view this session in your DeepIDV admin console" }, { "rel": "session_details", "href": "https://api.deepidv.com/v1/sessions/a1b2c3d4-e5f6-7890-abcd-ef1234567890", "description": "Use this ref to fetch the session details" } ] } ``` *** ## Redirect URL When a `redirect_url` is provided in the request, the returned `session_url` will include it as an encoded query parameter: ``` https://verify.deepidv.com/session/?oid=&redirect_url=https%3A%2F%2Fyourapp.com%2Fverify-callback ``` Upon session completion, failure, or user exit, the verification app will redirect the end-user back to your `redirect_url` with the following query parameters appended: | Parameter | Type | Description | | ------------ | ------ | --------------------------------------------------------------------- | | `session_id` | string | The session ID | | `status` | string | The outcome of the session | | `reason` | string | Additional context for the outcome (omitted when status is `success`) | ### Status values | Value | Description | | ----------- | ------------------------------------------------------------------------ | | `success` | Verification session was completed and submitted successfully | | `failed` | Session encountered an error during the verification process | | `abandoned` | User manually exited the session without completing | | `expired` | Session was automatically expired before the user completed verification | ### Reason values | Value | Description | | --------------------- | ------------------------------------------------------------------------------------- | | `document_unreadable` | The uploaded ID document could not be processed (blurry, glare, wrong doc type, etc.) | | `face_mismatch` | Liveness/selfie did not match the document photo | | `session_expired` | The session timed out before the user completed verification | | `internal_error` | An unexpected server-side error occurred | | `user_cancelled` | User explicitly chose to leave/cancel the verification | | `unknown` | Catch-all for any unclassified failure | The `reason` parameter is omitted when `status=success`. When `status=abandoned`, the reason will typically be `user_cancelled`. New reason values may be added in the future — your integration should handle unknown reasons gracefully. ### Example redirect URLs ```bash theme={null} # Success https://yourapp.com/verify-callback?session_id=sess_abc&status=success # Failure https://yourapp.com/verify-callback?session_id=sess_abc&status=failed&reason=document_unreadable # Abandoned https://yourapp.com/verify-callback?session_id=sess_abc&status=abandoned&reason=user_cancelled # Expired https://yourapp.com/verify-callback?session_id=sess_abc&status=expired&reason=session_expired ``` # List Sessions Source: https://docs.deepidv.com/api-reference/sessions/list-sessions GET /v1/sessions List verification sessions with flexible filtering ``` GET /v1/sessions ``` Returns a paginated list of verification sessions. By default, returns sessions created by the authenticated user. Use query parameters to list by organization or filter by external ID. ## Request ### Headers | Header | Required | Description | | ----------- | -------- | ------------ | | `x-api-key` | Yes | Your API key | ### Query parameters | Parameter | Type | Required | Default | Description | | ----------------- | ------- | -------- | ------- | ----------------------------------------------------------------------------------------- | | `limit` | number | No | `50` | Number of sessions to return (1–500) | | `next_token` | string | No | — | Pagination token from a previous response | | `start_date` | string | No | — | Filter sessions created on or after this date (ISO 8601) | | `end_date` | string | No | — | Filter sessions created on or before this date (ISO 8601) | | `by_organization` | boolean | No | `false` | When `true`, returns all sessions for your organization instead of only those you created | | `external_id` | string | No | — | Filter sessions by your external reference ID | | `workflow_id` | string | No | — | Filter sessions by workflow ID | Query modes are evaluated in priority order: `external_id` > `workflow_id` > `by_organization` > default sender-based query. ### Example requests #### List your sessions (default) ```bash cURL theme={null} curl -X GET "https://api.deepidv.com/v1/sessions?limit=25&start_date=2025-01-01T00:00:00Z" \ -H "x-api-key: YOUR_API_KEY" ``` ```javascript Node.js theme={null} const params = new URLSearchParams({ limit: "25", start_date: "2025-01-01T00:00:00Z", }); const response = await fetch( `https://api.deepidv.com/v1/sessions?${params}`, { headers: { "x-api-key": "YOUR_API_KEY" }, } ); const data = await response.json(); ``` ```python Python theme={null} import requests response = requests.get( "https://api.deepidv.com/v1/sessions", headers={"x-api-key": "YOUR_API_KEY"}, params={ "limit": 25, "start_date": "2025-01-01T00:00:00Z", }, ) ``` #### List all organization sessions ```bash cURL theme={null} curl -X GET "https://api.deepidv.com/v1/sessions?by_organization=true&limit=100" \ -H "x-api-key: YOUR_API_KEY" ``` ```javascript Node.js theme={null} const params = new URLSearchParams({ by_organization: "true", limit: "100", }); const response = await fetch( `https://api.deepidv.com/v1/sessions?${params}`, { headers: { "x-api-key": "YOUR_API_KEY" }, } ); const data = await response.json(); ``` ```python Python theme={null} import requests response = requests.get( "https://api.deepidv.com/v1/sessions", headers={"x-api-key": "YOUR_API_KEY"}, params={ "by_organization": "true", "limit": 100, }, ) ``` #### Filter by workflow ID ```bash cURL theme={null} curl -X GET "https://api.deepidv.com/v1/sessions?workflow_id=wf_abc123" \ -H "x-api-key: YOUR_API_KEY" ``` ```javascript Node.js theme={null} const params = new URLSearchParams({ workflow_id: "wf_abc123", }); const response = await fetch( `https://api.deepidv.com/v1/sessions?${params}`, { headers: { "x-api-key": "YOUR_API_KEY" }, } ); const data = await response.json(); ``` ```python Python theme={null} import requests response = requests.get( "https://api.deepidv.com/v1/sessions", headers={"x-api-key": "YOUR_API_KEY"}, params={"workflow_id": "wf_abc123"}, ) ``` #### Filter by external ID ```bash cURL theme={null} curl -X GET "https://api.deepidv.com/v1/sessions?external_id=user-12345" \ -H "x-api-key: YOUR_API_KEY" ``` ```javascript Node.js theme={null} const params = new URLSearchParams({ external_id: "user-12345", }); const response = await fetch( `https://api.deepidv.com/v1/sessions?${params}`, { headers: { "x-api-key": "YOUR_API_KEY" }, } ); const data = await response.json(); ``` ```python Python theme={null} import requests response = requests.get( "https://api.deepidv.com/v1/sessions", headers={"x-api-key": "YOUR_API_KEY"}, params={"external_id": "user-12345"}, ) ``` ## Response ### 200 — Success | Field | Type | Description | | ------------ | -------------- | -------------------------------------------------------------------------------------------------------------------- | | `sessions` | array | Array of session objects (see [Retrieve Session](/api-reference/sessions/retrieve-session) for full field reference) | | `next_token` | string \| null | Pagination token to fetch the next page. `null` when no more results | The `user`, `sender_user`, and `resource_links` fields are only returned when retrieving a single session by ID. List responses include the `session_record` fields only. ### Pagination To fetch the next page of results, pass the `next_token` from the response as a query parameter: ```bash theme={null} curl -X GET "https://api.deepidv.com/v1/sessions?limit=25&next_token=eyJpZCI6ImFiYzEyMyJ9" \ -H "x-api-key: YOUR_API_KEY" ``` Continue paginating until `next_token` is `null`. ### Error responses | Status | Description | | ----------------------- | --------------------------------------------------------------------------------------------- | | `400 Bad Request` | Invalid query parameters (e.g., limit out of range, invalid date format, invalid next\_token) | | `401 Unauthorized` | Missing or invalid API key | | `429 Too Many Requests` | Rate limit exceeded | ```json 200 theme={null} { "sessions": [ { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "organization_id": "org_abc123", "user_id": "usr_def456", "sender_user_id": "usr_ghi789", "status": "VERIFIED", "type": "session", "session_progress": "COMPLETED", "created_at": "2025-01-15T10:30:00.000Z", "updated_at": "2025-01-15T10:45:00.000Z" } ], "next_token": "eyJpZCI6ImFiYzEyMyJ9" } ``` # Retrieve Session Source: https://docs.deepidv.com/api-reference/sessions/retrieve-session GET /v1/sessions/{id} Retrieve a session by its ID ``` GET /v1/sessions/{id} ``` Retrieves the full details of a single verification session by its session ID, including analysis results and presigned URLs for uploaded documents. ## Request ### Headers | Header | Required | Description | | ----------- | -------- | ------------ | | `x-api-key` | Yes | Your API key | ### Path parameters | Parameter | Type | Required | Description | | --------- | ------ | -------- | -------------------------------------------------------------- | | `id` | string | Yes | The session ID (returned as `id` when the session was created) | ### Example request ```bash cURL theme={null} curl -X GET https://api.deepidv.com/v1/sessions/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \ -H "x-api-key: YOUR_API_KEY" ``` ```javascript Node.js theme={null} const response = await fetch( "https://api.deepidv.com/v1/sessions/a1b2c3d4-e5f6-7890-abcd-ef1234567890", { headers: { "x-api-key": "YOUR_API_KEY" }, } ); const data = await response.json(); ``` ```python Python theme={null} import requests response = requests.get( "https://api.deepidv.com/v1/sessions/a1b2c3d4-e5f6-7890-abcd-ef1234567890", headers={"x-api-key": "YOUR_API_KEY"}, ) ``` ## Response ### 200 — Success ### Top-level response | Field | Type | Description | | ---------------- | ------ | --------------------------------------------------------------------------------- | | `session_record` | object | The full session object | | `resource_links` | object | Presigned S3 URLs for uploaded documents and images (valid for a limited time) | | `user` | object | The applicant's user profile (omitted if the user record is unavailable) | | `sender_user` | object | The user who created/sent the session (omitted if the user record is unavailable) | ### Session object | Field | Type | Description | | --------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `id` | string | Unique session identifier | | `organization_id` | string | Organization that owns this session | | `user_id` | string | User ID of the applicant | | `sender_user_id` | string | User ID of the person who created the session | | `external_id` | string | Your external reference ID (if provided) | | `permalink_id` | string | Permalink ID (if applicable) | | `location` | object | Applicant's location (if captured), e.g. `{ "country": "Canada" }` | | `status` | string | `PENDING`, `SUBMITTED`, `VERIFIED`, `REJECTED`, `VOIDED`, `EXPIRED`, or `FAILED` | | `type` | string | `session` (workflow-based), `verification` (standalone), `credit-application` (ClearView), `silent-screening` (silent screening), or `deep-doc` (document transfer) | | `session_progress` | string | `PENDING`, `STARTED`, or `COMPLETED` | | `created_at` | string | ISO 8601 timestamp of session creation | | `updated_at` | string | ISO 8601 timestamp of last update | | `submitted_at` | string | ISO 8601 timestamp when the applicant submitted | | `workflow_id` | string | Workflow ID used for this session | | `workflow_steps` | string\[] | List of workflow step IDs (e.g. `ID_VERIFICATION`, `FACE_LIVENESS`) | | `expires_at` | string | ISO 8601 timestamp when the session will auto-expire (if configured) | | `bank_statement_request_id` | string | Associated bank statement request ID | | `deep_sign_id` | string | Associated e-sign ID | | `face_liveness_session_id` | string | Face liveness session ID | | `uploads` | object | Boolean flags for each uploaded document type | | `analysis_data` | object | Verification analysis results (see below) | | `meta_data` | object | Applicant submission metadata | ### `meta_data` object | Field | Type | Description | | ------------------------------------ | ------ | -------------------------------------------------------------------------------------------------------------------- | | `applicantSubmissionIp` | string | IP address at time of submission | | `applicantSubmissionDevice` | string | Device used for submission | | `applicantViewTime` | string | ISO 8601 timestamp when the applicant viewed the verification page | | `applicantSubmissionBrowser` | string | Browser used for submission | | `applicantSubmissionLocation` | string | Human-readable submission location (typically `"City, Country"` when available) | | `applicantSubmissionLocationDetails` | object | Structured geo lookup of the submission IP (see below) | | `failureData` | object | Auto-failure attempt history (only present once at least one failed attempt has been recorded — see below) | | `declinedData` | object | Automated decline decision (only present when the session was auto-declined by AI identity verification — see below) | ### `applicantSubmissionLocationDetails` object Derived from a geo lookup of the submission IP. All fields are optional — any field may be absent if the lookup could not resolve it. | Field | Type | Description | | ---------------- | ------ | --------------------------------------------------------- | | `accuracyRadius` | number | Radius (km) of confidence around the resolved coordinates | | `city` | string | City name | | `continent` | string | Continent name | | `country` | string | Country name | | `countryIsoCode` | string | ISO 3166-1 alpha-2 country code | | `latitude` | number | Latitude of the resolved location | | `longitude` | number | Longitude of the resolved location | | `postalCode` | string | Postal/ZIP code | | `subdivision` | string | State, province, or other primary subdivision | | `timeZone` | string | IANA timezone identifier (e.g. `"America/Chicago"`) | ### `failureData` object Tracks per-attempt failures when the workflow has auto-session-failure enabled. Absent until the first failed attempt is recorded. When `failedAttempts` reaches the workflow's configured `maxAttempts`, the session's top-level `status` flips to `FAILED` and `session_progress` to `COMPLETED`. | Field | Type | Description | | ---------------- | --------- | ------------------------------------------------------------------------ | | `failedAttempts` | number | Count of failed attempts recorded for this session | | `attempts` | object\[] | Ordered list of attempts (oldest first) — one entry per recorded failure | Each entry in `attempts` has the following shape: | Field | Type | Description | | ---------- | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `reason` | string | The check that failed. One of `NO_FACE_DETECTED`, `OCR_NAME_MISSING`, `DOC_TYPE_NOT_ALLOWED`, `ID_TYPE_UNRECOGNIZED`, `ID_TYPE_LOW_CONFIDENCE`, `FACE_MISMATCH`, or `LIVENESS_ID_MISMATCH`. The two `ID_TYPE_*` values are emitted only when the workflow's ID confidence threshold is enabled (driver's licence / passport): `ID_TYPE_UNRECOGNIZED` when the document type could not be read, and `ID_TYPE_LOW_CONFIDENCE` when it was read below the configured confidence. `FACE_MISMATCH` is the selfie↔ID face-match failure; `LIVENESS_ID_MISMATCH` is the Face Liveness↔ID identity-binding failure. New values may be added over time — consumers should treat this as an open string. | | `slot` | string \| null | Which ID slot the failure applies to: `PRIMARY`, `SECONDARY`, `TERTIARY`, or `null` when the failure is not slot-specific. | | `failedAt` | string | ISO 8601 timestamp of when the attempt was recorded | ```json theme={null} "failureData": { "failedAttempts": 3, "attempts": [ { "reason": "NO_FACE_DETECTED", "slot": "PRIMARY", "failedAt": "2026-05-26T15:30:00.000Z" }, { "reason": "OCR_NAME_MISSING", "slot": "SECONDARY", "failedAt": "2026-05-26T15:32:14.000Z" }, { "reason": "ID_TYPE_LOW_CONFIDENCE", "slot": "PRIMARY", "failedAt": "2026-05-26T15:34:02.000Z" } ] } ``` ### `declinedData` object Present only when the session was automatically declined by deepidv's AI identity verification. It records the single decisive reason for the decline. Absent for sessions that passed, are awaiting manual review, or were auto-failed via attempt limits (see `failureData` — that is a separate, attempt-based mechanism). The full scoring breakdown that drove the decision remains available under `analysis_data`. | Field | Type | Description | | ------------ | -------------- | ------------------------------------------------------------------------------------------------------------------------------------- | | `declined` | boolean | Always `true` when this object is present | | `score` | number \| null | Normalized verification score (0–10) behind the decision. `0` for a deterministic ID-type decline; `null` when a score does not apply | | `declinedAt` | string | ISO 8601 timestamp of the decline decision | | `reason` | object | The single decisive decline reason (see below) | The `reason` object: | Field | Type | Description | | ---------- | -------------- | ---------------------------------------------------------------------------------------------------------------------------------- | | `code` | string | Machine-readable reason code (see table below). New values may be added over time — consumers should treat this as an open string. | | `detail` | string | Short, human-readable description suitable for display | | `slot` | string \| null | Which ID slot the reason applies to: `PRIMARY`, `SECONDARY`, `TERTIARY`, or `null` when the reason is not slot-specific | | `severity` | string | `LOW`, `MEDIUM`, `HIGH`, or `CRITICAL` | Reason codes: | `code` | Severity | Meaning | | ------------------------ | ---------- | ----------------------------------------------------------------------------------------------- | | `ID_TYPE_UNRECOGNIZED` | `CRITICAL` | The document type could not be read from the ID | | `ID_TYPE_LOW_CONFIDENCE` | `CRITICAL` | The document type was read below the configured confidence threshold | | `DOC_TYPE_NOT_ALLOWED` | `CRITICAL` | The detected document type is not permitted for the workflow / does not match the selected type | | `LOW_FACE_MATCH` | `CRITICAL` | Face-match confidence between the selfie and the ID photo was too low | | `FACE_SELFIE_MISMATCH` | `CRITICAL` | The face on the ID did not match the selfie | | `LOW_LIVENESS` | `CRITICAL` | The liveness check did not pass | | `DOC_TYPE_MISMATCH` | `CRITICAL` | The submitted document type differed from the type the applicant selected | | `LOW_SCORE` | `HIGH` | The overall verification score fell below the auto-approval threshold | ```json theme={null} "declinedData": { "declined": true, "score": 2, "declinedAt": "2025-01-15T10:43:00.000Z", "reason": { "code": "LOW_FACE_MATCH", "detail": "Face match confidence is low", "slot": "PRIMARY", "severity": "CRITICAL" } } ``` ### `analysis_data` object | Field | Type | Description | | ---------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | | `created_at` | string | When analysis was performed | | `id_analysis_data` | object | Primary ID document analysis | | `id_matches_selfie` | boolean | Rollup identity result — logical AND of `id_matches_face_capture` and `id_matches_liveness` (whichever ran). `false` if either present check failed | | `faceliveness_score` | number | Liveness confidence score (0–100) | | `id_matches_face_capture` | boolean | Whether the face/selfie capture matched the ID portrait. `null` if not evaluated | | `id_matches_liveness` | boolean | Whether the Face Liveness capture matched the ID portrait (Face Liveness workflows only). `null` if not evaluated | | `compare_faces_data` | object | Face comparison results | | `pep_sanctions_data` | object | PEP & sanctions screening results | | `adverse_media_data` | object | Adverse media screening results | | `secondary_id_analysis_data` | object | Secondary ID document analysis | | `tertiary_id_analysis_data` | object | Tertiary ID document analysis | | `selected_document_types` | object | Document types selected by applicant | | `document_risk_data` | object | Document fraud/risk analysis | | `title_search_data` | object | Property title search results | | `custom_form_data` | array | Custom form question/answer entries | ### `id_analysis_data` object | Field | Type | Description | | ---------------------- | ------- | -------------------------------------------------------------- | | `detect_face_data` | array | Face detection results from the ID document | | `id_extracted_text` | array | Text fields extracted from the ID (name, DOB, ID number, etc.) | | `expiry_date_pass` | boolean | Whether the ID has not expired | | `valid_state_pass` | boolean | Whether the ID state/province is valid | | `age_restriction_pass` | boolean | Whether the applicant meets age requirements | ### `pep_sanctions_data` object | Field | Type | Description | | ----------- | ------------- | ------------------------------------------------- | | `peps` | array \| null | Matches against Politically Exposed Persons lists | | `sanctions` | array \| null | Matches against sanctions lists | | `both` | array \| null | Matches appearing on both PEP and sanctions lists | Each match item contains: | Field | Type | Description | | ------------ | --------- | --------------------------------- | | `name` | string | Matched name | | `country` | string | Country associated with the match | | `score` | number | Match confidence score | | `id` | string | Record ID in the source dataset | | `datasets` | string\[] | Source datasets for this match | | `birth_date` | string | Date of birth (if available) | ### `compare_faces_data` object | Field | Type | Description | | ----------------------- | ------ | -------------------------------------------------------------------------- | | `face_match_confidence` | number | Overall face match confidence (0–100) | | `face_match_result` | object | Detailed comparison result including `similarity` score and `face` details | ### `document_risk_data` object | Field | Type | Description | | ------------------------ | ------ | ------------------------------------------------------------------ | | `documents_analyzed` | number | Number of documents analyzed | | `documents_with_signals` | number | Number of documents with risk signals | | `overall_risk_score` | number | Aggregate risk score | | `analysis_timestamp` | string | When the risk analysis was performed | | `document_analysis` | array | Per-document analysis with risk signals, AI analysis, and metadata | ### `user` / `sender_user` object Both `user` (the applicant) and `sender_user` (the session creator) share the same shape: | Field | Type | Description | | ------------ | ------ | ----------------------------------- | | `id` | string | Unique user identifier | | `email` | string | User's email address | | `first_name` | string | User's first name | | `last_name` | string | User's last name | | `phone` | string | User's phone number | | `created_at` | string | ISO 8601 timestamp of user creation | | `updated_at` | string | ISO 8601 timestamp of last update | ### Error responses | Status | Description | | ----------------------- | ------------------------------------------- | | `400 Bad Request` | Invalid session ID format | | `401 Unauthorized` | Missing or invalid API key | | `403 Forbidden` | Session belongs to a different organization | | `404 Not Found` | Session ID does not exist | | `429 Too Many Requests` | Rate limit exceeded | ```json 200 theme={null} { "session_record": { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "organization_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901", "user_id": "c3d4e5f6-a7b8-9012-cdef-123456789012", "sender_user_id": "d4e5f6a7-b8c9-0123-defa-234567890123", "status": "SUBMITTED", "type": "session", "session_progress": "COMPLETED", "location": { "country": "Canada" }, "workflow_id": "e5f6a7b8-c9d0-1234-efab-345678901234", "bank_statement_request_id": "f6a7b8c9-d0e1-2345-fabc-456789012345", "workflow_steps": ["ID_VERIFICATION", "FACE_LIVENESS", "PEP_SANCTIONS"], "created_at": "2025-01-15T10:30:00.000Z", "updated_at": "2025-01-15T10:45:00.000Z", "submitted_at": "2025-01-15T10:42:00.000Z", "meta_data": { "applicantSubmissionIp": "192.168.1.1, 10.0.0.1", "applicantSubmissionDevice": "Mac", "applicantViewTime": "2025-01-15T10:35:00.000Z", "applicantSubmissionBrowser": "Safari", "applicantSubmissionLocation": "Chicago, United States", "applicantSubmissionLocationDetails": { "accuracyRadius": 500, "continent": "North America", "country": "United States", "countryIsoCode": "US", "latitude": 41.8483, "longitude": -87.6517, "subdivision": "Illinois", "timeZone": "America/Chicago" }, "failureData": { "failedAttempts": 1, "attempts": [ { "reason": "NO_FACE_DETECTED", "slot": "PRIMARY", "failedAt": "2025-01-15T10:37:00.000Z" } ] }, "declinedData": { "declined": true, "score": 2, "declinedAt": "2025-01-15T10:43:00.000Z", "reason": { "code": "LOW_FACE_MATCH", "detail": "Face match confidence is low", "slot": "PRIMARY", "severity": "CRITICAL" } } }, "uploads": { "id_front": true, "id_back": true, "secondary_id_front": true, "secondary_id_back": true, "tertiary_id_front": true, "tertiary_id_back": true, "selfie_front": true, "selfie_right": true, "selfie_left": true, "faceliveness": true, "hold_up_3_fingers": true, "pdf_summary": true }, "analysis_data": { "created_at": "2025-01-15T10:43:00.000Z", "id_matches_selfie": true, "faceliveness_score": 99.49, "id_matches_face_capture": true, "id_matches_liveness": true, "id_analysis_data": { "detect_face_data": [ { "confidence": 99.99, "age_range": { "high": 35, "low": 28 }, "quality": { "brightness": 97.56, "sharpness": 38.90 }, "bounding_box": { "height": 0.32, "left": 0.11, "top": 0.33, "width": 0.19 }, "pose": { "pitch": 11.74, "yaw": 3.85, "roll": -2.84 }, "landmarks": [ { "x": 0.164, "y": 0.458, "type": "eyeLeft" }, { "x": 0.248, "y": 0.449, "type": "eyeRight" }, { "x": 0.215, "y": 0.507, "type": "nose" } ], "gender": { "value": "Male", "confidence": 91.22 } } ], "id_extracted_text": [ { "type": "FIRST_NAME", "value": "JOHN", "confidence": 97.64 }, { "type": "LAST_NAME", "value": "DOE", "confidence": 97.15 }, { "type": "MIDDLE_NAME", "value": "", "confidence": 99.15 }, { "type": "DATE_OF_BIRTH", "value": "1990/05/15", "confidence": 95.46 }, { "type": "EXPIRATION_DATE", "value": "2028/05/19", "confidence": 95.87 }, { "type": "DATE_OF_ISSUE", "value": "2023/06/30", "confidence": 95.40 }, { "type": "ID_TYPE", "value": "DRIVER LICENSE FRONT", "confidence": 90.02 }, { "type": "DOCUMENT_NUMBER", "value": "D123456789", "confidence": 0.66 }, { "type": "ADDRESS", "value": "123 MAIN ST", "confidence": 94.86 }, { "type": "CITY_IN_ADDRESS", "value": "SPRINGFIELD IL", "confidence": 66.81 }, { "type": "STATE_IN_ADDRESS", "value": "IL", "confidence": 47.99 }, { "type": "CLASS", "value": "G", "confidence": 97.54 }, { "type": "RESTRICTIONS", "value": "X", "confidence": 97.29 } ], "expiry_date_pass": true, "valid_state_pass": true, "age_restriction_pass": true }, "secondary_id_analysis_data": { "detect_face_data": [ { "confidence": 99.99, "age_range": { "high": 35, "low": 28 }, "quality": { "brightness": 97.30, "sharpness": 38.90 }, "bounding_box": { "height": 0.30, "left": 0.12, "top": 0.35, "width": 0.18 }, "pose": { "pitch": 11.98, "yaw": 3.80, "roll": -2.13 }, "landmarks": [ { "x": 0.174, "y": 0.466, "type": "eyeLeft" }, { "x": 0.253, "y": 0.460, "type": "eyeRight" }, { "x": 0.219, "y": 0.513, "type": "nose" } ], "gender": { "value": "Male", "confidence": 87.16 } } ], "expiry_date_pass": true, "valid_state_pass": false, "id_extracted_text": [ { "type": "FIRST_NAME", "value": "JOHN", "confidence": 97.63 }, { "type": "LAST_NAME", "value": "DOE", "confidence": 97.17 }, { "type": "DATE_OF_BIRTH", "value": "1990/05/15", "confidence": 94.95 }, { "type": "EXPIRATION_DATE", "value": "2028/05/19", "confidence": 95.64 }, { "type": "ID_TYPE", "value": "DRIVER LICENSE FRONT", "confidence": 94.87 }, { "type": "DOCUMENT_NUMBER", "value": "D123456789", "confidence": 90.14 } ], "age_restriction_pass": true }, "tertiary_id_analysis_data": { "detect_face_data": [ { "confidence": 99.99, "age_range": { "high": 35, "low": 28 }, "quality": { "brightness": 97.39, "sharpness": 38.90 }, "bounding_box": { "height": 0.30, "left": 0.14, "top": 0.36, "width": 0.18 }, "pose": { "pitch": 11.39, "yaw": 3.27, "roll": -3.04 }, "landmarks": [ { "x": 0.191, "y": 0.479, "type": "eyeLeft" }, { "x": 0.270, "y": 0.472, "type": "eyeRight" }, { "x": 0.239, "y": 0.526, "type": "nose" } ], "gender": { "value": "Male", "confidence": 92.90 } } ], "expiry_date_pass": true, "valid_state_pass": false, "id_extracted_text": [ { "type": "FIRST_NAME", "value": "JOHN", "confidence": 97.60 }, { "type": "LAST_NAME", "value": "DOE", "confidence": 97.15 }, { "type": "DATE_OF_BIRTH", "value": "1990/05/15", "confidence": 95.75 }, { "type": "EXPIRATION_DATE", "value": "2028/05/19", "confidence": 95.78 }, { "type": "ID_TYPE", "value": "DRIVER LICENSE FRONT", "confidence": 92.32 }, { "type": "DOCUMENT_NUMBER", "value": "D123456789", "confidence": 90.11 } ], "age_restriction_pass": true }, "pep_sanctions_data": { "peps": [ { "name": "John Smith", "country": "United Kingdom", "score": 1, "id": "Q180589", "datasets": ["ann_pep_positions", "wd_peps"], "birth_date": "1964-06-19" }, { "name": "Jane Smith", "country": "Unknown", "score": 0.776, "id": "Q19360548", "datasets": ["wd_peps", "ann_pep_positions"], "birth_date": "1923-06-23" } ], "sanctions": null, "both": null }, "compare_faces_data": { "face_match_confidence": 99.92, "face_match_result": { "face": { "landmarks": [ { "x": 0.164, "y": 0.458, "type": "eyeLeft" }, { "x": 0.248, "y": 0.449, "type": "eyeRight" }, { "x": 0.215, "y": 0.507, "type": "nose" } ], "confidence": 99.99, "quality": { "brightness": 97.56, "sharpness": 38.90 }, "bounding_box": { "height": 0.32, "left": 0.11, "top": 0.33, "width": 0.19 }, "pose": { "pitch": 11.74, "yaw": 3.85, "roll": -2.84 } }, "similarity": 99.92 } }, "adverse_media_data": { "total_hits": 10, "news_exposures": { "violent_crime": { "category": "violent_crime", "hits": 2, "articles": [ { "source": "serper", "headline": "Example headline about violent crime", "source_link": "https://example.com/article-1", "timestamp": "3 weeks ago" } ] }, "terrorism": { "category": "terrorism", "hits": 0, "articles": [] }, "regulatory": { "category": "regulatory", "hits": 0, "articles": [] }, "financial_crime": { "category": "financial_crime", "hits": 0, "articles": [] }, "political": { "category": "political", "hits": 0, "articles": [] }, "uncategorized": { "category": "uncategorized", "hits": 6, "articles": [ { "source": "serper", "headline": "Example uncategorized article headline", "source_link": "https://example.com/article-2", "timestamp": "Sep 14, 2025" } ] }, "organized_crime": { "category": "organized_crime", "hits": 0, "articles": [] }, "criminal_legal": { "category": "criminal_legal", "hits": 2, "articles": [ { "source": "serper", "headline": "Example criminal legal article headline", "source_link": "https://example.com/article-3", "timestamp": "Sep 9, 2025" } ] } }, "timestamp": "2025-01-15T10:43:16.338Z" }, "selected_document_types": { "secondary": "passport", "tertiary": "pr_card", "primary": "drivers_license" }, "document_risk_data": { "document_analysis": [ { "expected_document_name": "Articles of Incorporation", "metadata": { "creator": "Microsoft\u00ae Word for Microsoft 365", "mod_date": "2022-04-14T03:02:41.000Z", "page_count": 1, "encrypted": false, "author": "John Doe", "producer": "Microsoft\u00ae Word for Microsoft 365", "creation_date": "2022-04-14T03:02:41.000Z", "title": null }, "signals": [ { "severity": "high", "weight": 45, "code": "DOCUMENT_TYPE_MISMATCH", "evidence": { "mismatch_details": "The uploaded document does not match the expected document type 'Articles of Incorporation'.", "detected_type": "Other", "ai_confidence": 1, "expected_type": "Articles of Incorporation" }, "message": "Uploaded document does not match the expected document type" } ], "document_key": "b2c3d4e5-f6a7-8901-bcde-f12345678901/a1b2c3d4-e5f6-7890-abcd-ef1234567890/1772386555165-pdf", "field_label": "document_1", "total_weight": 0, "max_possible_weight": 190, "ai_analysis": { "overall_assessment": "likely_fabricated", "claimed_dates": { "document_date": null, "statement_period": null, "transaction_dates": [] }, "content_analysis": { "expected_document_type": "Articles of Incorporation", "content_quality": "complete", "document_type": "Other", "issuing_entity": null }, "document_type_mismatch_details": "The uploaded document does not match the expected document type.", "confidence": 1, "date_mismatch_details": null, "date_mismatch_detected": false, "document_type_mismatch": true, "reasoning": "The document provided does not match the expected 'Articles of Incorporation'. This represents a critical document type mismatch.", "suspicious_patterns": [ { "severity": "high", "type": "Document Content Irrelevance", "description": "The document content does not match 'Articles of Incorporation' as expected." } ], "risk_score": 95, "risk_indicators": [ "Document type mismatch", "Irrelevant content submitted" ] }, "risk_score": 95, "status_message": null, "status": "ok" } ], "analysis_timestamp": "2025-01-15T10:43:21.588Z", "documents_with_signals": 1, "overall_risk_score": 95, "documents_analyzed": 1 }, "title_search_data": { "last_market_sale_information": { "sale_type": "", "first_mortgage_amount": null, "sale_date": "2010-10-19T00:00:00", "sale_price": 38000, "deed_type": "WARRANTY DEED", "buyer_name": "SMITH,JOHN & JANE", "seller_name": "JOHNSON,ROBERT", "recording_date": "2010-12-15T00:00:00", "book_page": "47589.183", "current_through_date": "2026-02-17T00:00:00" }, "owner_transfer_information": { "current_through_date": "2026-02-17T00:00:00", "sale_date": "2020-02-20T00:00:00", "recording_sale_date": "2021-05-07T00:00:00", "buyer_name": "SMITH JANE L", "sale_price": null, "deed_type": "QUIT CLAIM DEED", "seller_name": "SMITH JOHN S" }, "owner_information": { "owner_names": "SMITH JANE L", "owner1_full_name": "SMITH JANE L", "owner2_full_name": "", "occupancy": "Absentee Owner", "owner_occupied_indicator": "N", "mailing_address": { "street_address": "1170 OAK CT", "city": "SPRINGFIELD", "state": "IL", "zip9": "62701-2804", "mail_carrier_route": "R006" }, "owner_vesting_info": { "vesting_owner": "SINGLE WOMAN", "vesting_etal": "", "vesting_ownership_right": "" } }, "subject_property": { "property_id": 29002737, "parsed_street_address": { "street_name": "PINEWALK", "direction_suffix": "N", "standardized_house_number": 3330, "street_suffix": "DR", "apartment_or_unit": "1613" }, "situs_address": { "street_address": "3330 PINEWALK DR N #1613", "state": "FL", "city": "MARGATE", "county": "BROWARD", "zip9": "33063-9338", "apn": "48-41-23-BC-2950" } }, "location_information": { "census_tract": "020204", "neighborhood_name": "CORAL KEY", "subdivision": "CORAL KEY CONDO", "municipality_township": "MARGATE", "latitude": 26.26596, "longitude": -80.228435, "school_district": "BROWARD", "legal_description": "CORAL KEY CONDO UNIT 1613 BLDG 16", "county_fips": 12011 }, "title_search_address_details": { "address_components": [ { "long_name": "3330", "short_name": "3330", "types": ["street_number"] }, { "long_name": "Pinewalk Drive North", "short_name": "Pinewalk Dr N", "types": ["route"] }, { "long_name": "Margate", "short_name": "Margate", "types": ["locality", "political"] }, { "long_name": "Broward County", "short_name": "Broward County", "types": ["administrative_area_level_2", "political"] }, { "long_name": "Florida", "short_name": "FL", "types": ["administrative_area_level_1", "political"] }, { "long_name": "United States", "short_name": "US", "types": ["country", "political"] }, { "long_name": "33063", "short_name": "33063", "types": ["postal_code"] } ], "formatted_address": "3330 Pinewalk Drive North, Margate, FL, USA", "unit_number": "1613" } }, "custom_form_data": [ { "question": "What is your Company Business Number", "answer": "1234567890", "type": "short-text" } ] } }, "user": { "id": "c3d4e5f6-a7b8-9012-cdef-123456789012", "email": "john.doe@example.com", "first_name": "John", "last_name": "Doe", "phone": "+1234567890", "created_at": "2025-01-10T08:00:00.000Z", "updated_at": "2025-01-15T10:42:00.000Z" }, "sender_user": { "id": "d4e5f6a7-b8c9-0123-defa-234567890123", "email": "admin@example.com", "first_name": "Admin", "last_name": "User", "phone": "+1987654321", "created_at": "2024-12-01T09:00:00.000Z", "updated_at": "2025-01-15T10:30:00.000Z" }, "resource_links": { "id_back": "https://s3.amazonaws.com/...", "secondary_id_front": "https://s3.amazonaws.com/...", "secondary_id_back": "https://s3.amazonaws.com/...", "pdf_summary": "https://s3.amazonaws.com/...", "selfie_front": "https://s3.amazonaws.com/...", "faceliveness": "https://s3.amazonaws.com/...", "tertiary_id_front": "https://s3.amazonaws.com/...", "tertiary_id_back": "https://s3.amazonaws.com/...", "selfie_right": "https://s3.amazonaws.com/...", "id_front": "https://s3.amazonaws.com/...", "Hold up 3 fingers": "https://s3.amazonaws.com/...", "selfie_left": "https://s3.amazonaws.com/...", "document_1": "https://s3.amazonaws.com/..." } } ``` # Update Session Status Source: https://docs.deepidv.com/api-reference/sessions/update-session-status PATCH /v1/sessions/{id}/update-status Manually update a session's status to VERIFIED or REJECTED ``` PATCH /v1/sessions/{id}/update-status ``` Manually updates the status of a verification session to either `VERIFIED` or `REJECTED`. ## Request ### Headers | Header | Required | Description | | ----------- | -------- | ------------ | | `x-api-key` | Yes | Your API key | ### Path parameters | Parameter | Type | Required | Description | | --------- | ------ | -------- | -------------------------------------------------------------- | | `id` | string | Yes | The session ID (returned as `id` when the session was created) | ### Body parameters | Parameter | Type | Required | Description | | ------------ | ------ | -------- | ------------------------------------------------------- | | `new_status` | string | Yes | The new status to set. Must be `VERIFIED` or `REJECTED` | ### Example request ```bash cURL theme={null} curl -X PATCH https://api.deepidv.com/v1/sessions/a1b2c3d4-e5f6-7890-abcd-ef1234567890/update-status \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"new_status": "VERIFIED"}' ``` ```javascript Node.js theme={null} const response = await fetch( "https://api.deepidv.com/v1/sessions/a1b2c3d4-e5f6-7890-abcd-ef1234567890/update-status", { method: "PATCH", headers: { "x-api-key": "YOUR_API_KEY", "Content-Type": "application/json", }, body: JSON.stringify({ new_status: "VERIFIED" }), } ); const data = await response.json(); ``` ```python Python theme={null} import requests response = requests.patch( "https://api.deepidv.com/v1/sessions/a1b2c3d4-e5f6-7890-abcd-ef1234567890/update-status", headers={"x-api-key": "YOUR_API_KEY"}, json={"new_status": "VERIFIED"}, ) ``` ## Response ### 200 — Success ### Response fields | Field | Type | Description | | ---------------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `session_record` | object | The updated session object (same shape as the `session_record` in the [Retrieve Session](/api-reference/sessions/retrieve-session) response). Does not include `resource_links`, `user`, or `sender_user` | ### Error responses | Status | Description | | ----------------------- | -------------------------------------------------------------------------- | | `400 Bad Request` | Invalid session ID or `new_status` value. Must be `VERIFIED` or `REJECTED` | | `401 Unauthorized` | Missing or invalid API key | | `403 Forbidden` | Session belongs to a different organization | | `404 Not Found` | Session ID does not exist | | `429 Too Many Requests` | Rate limit exceeded | ```json 200 theme={null} { "session_record": { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "organization_id": "org_abc123", "user_id": "usr_def456", "sender_user_id": "usr_ghi789", "external_id": "user-12345", "status": "VERIFIED", "type": "verification", "session_progress": "COMPLETED", "created_at": "2025-01-15T10:30:00.000Z", "updated_at": "2025-01-15T11:00:00.000Z" } } ``` # Adverse Media Source: https://docs.deepidv.com/api-reference/silent-screening/adverse-media POST /v1/screening/adverse-media Screen individuals for negative news and legal mentions ``` POST /v1/screening/adverse-media ``` Starts an **asynchronous** adverse-media check across news, court records, and watchlist databases. Because the scan fans out across multiple vendors, the request returns immediately with `202 Accepted` and a `jobId` — poll [`GET /v1/async-jobs/{jobId}`](/api-reference/async-jobs/get-async-job) to retrieve the result once it's ready. This endpoint returns a job reference, **not** the screening result. The result is delivered through the async-jobs polling endpoint. ## Request ### Headers | Header | Required | Description | | ----------------- | -------- | ------------------------------------------------------------------------------------------------------------------ | | `x-api-key` | Yes | Your API key | | `Content-Type` | Yes | `application/json` | | `Idempotency-Key` | No | Client-supplied key. Reusing a key returns the existing job (`200`) instead of queuing a new one — safe on retries | ### Body parameters | Parameter | Type | Required | Description | | ------------- | ------ | -------- | --------------------------------------------------------------- | | `email` | string | Yes | Individual's email address | | `firstName` | string | Yes | Individual's first name (1–255 chars) | | `lastName` | string | Yes | Individual's last name (1–255 chars) | | `dateOfBirth` | string | Yes | Date of birth in `YYYY-MM-DD` format | | `country` | string | No | ISO 3166-1 alpha-2 country code (e.g. `US`) to focus the search | ### Example request ```bash cURL theme={null} curl -X POST https://api.deepidv.com/v1/screening/adverse-media \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -H "Idempotency-Key: 7c9e6679-7425-40de-944b-e07fc1f90ae7" \ -d '{ "email": "applicant@example.com", "firstName": "John", "lastName": "Doe", "dateOfBirth": "1980-01-15", "country": "US" }' ``` ```javascript Node.js theme={null} const response = await fetch("https://api.deepidv.com/v1/screening/adverse-media", { method: "POST", headers: { "Content-Type": "application/json", "x-api-key": "YOUR_API_KEY", "Idempotency-Key": "7c9e6679-7425-40de-944b-e07fc1f90ae7", }, body: JSON.stringify({ email: "applicant@example.com", firstName: "John", lastName: "Doe", dateOfBirth: "1980-01-15", country: "US", }), }); const { jobId } = await response.json(); ``` ```python Python theme={null} import requests response = requests.post( "https://api.deepidv.com/v1/screening/adverse-media", headers={ "Content-Type": "application/json", "x-api-key": "YOUR_API_KEY", "Idempotency-Key": "7c9e6679-7425-40de-944b-e07fc1f90ae7", }, json={ "email": "applicant@example.com", "firstName": "John", "lastName": "Doe", "dateOfBirth": "1980-01-15", "country": "US", }, ) job_id = response.json()["jobId"] ``` ## Idempotency Supply an optional `Idempotency-Key` header to make retries safe. The first request with a given key queues the job and returns `202`. Any subsequent request with the **same key** returns `200` with the **existing** job reference instead of starting a duplicate scan. Use a unique key (e.g. a UUID) per logical screening request. ## Response ### 202 — Accepted (new job queued) ### 200 — Existing job returned (reused `Idempotency-Key`) Both responses share the same body shape: | Field | Type | Description | | --------- | ------ | ------------------------------------------------------------------ | | `jobId` | string | Identifier to poll for the result via `GET /v1/async-jobs/{jobId}` | | `status` | string | Job status — one of `pending`, `processing`, `ready`, `failed` | | `message` | string | Human-readable acknowledgement | ### Error responses | Status | Description | | ------------------ | ----------------------------------------- | | `400 Bad Request` | Request body failed schema validation | | `401 Unauthorized` | API key is invalid | | `403 Forbidden` | API key is missing or resource not in org | | `404 Not Found` | Referenced resource was not found | | `500 Server Error` | Unexpected server error | ```json 202 theme={null} { "jobId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "status": "pending", "message": "Adverse media check queued. Poll GET /v1/async-jobs/{jobId} for the result." } ``` ## Result shape Once the job reaches `ready`, the async-jobs endpoint returns the screening result in its `result` field: | Field | Type | Description | | --------------------- | ------- | ----------------------------------------------------- | | `totalHits` | integer | Total number of findings | | `riskLevel` | string | Overall risk — `LOW`, `MEDIUM`, `HIGH`, or `CRITICAL` | | `riskScore` | integer | Numeric risk score `0–100` | | `summary` | string | Human-readable summary of the findings | | `findings` | array | Individual findings (see below) | | `exposuresByCategory` | object | Hit counts and article lists bucketed by category | Each entry in `findings` has the following shape: | Field | Type | Description | | ------------- | -------------- | ------------------------------------------------------------------------ | | `findingId` | string | Unique identifier for the finding | | `severity` | string | `MEDIUM`, `HIGH`, or `CRITICAL` | | `category` | string | Adverse-media category (e.g. `financial_crime`, `terrorism`) | | `title` | string | Short title of the finding | | `detail` | string | Longer description | | `sourceUrl` | string \| null | Link to the source article/record | | `sourceName` | string \| null | Normalized publisher/source name | | `articleDate` | string \| null | Publication date of the source, if known | | `confidence` | number | Match confidence `0–1` | | `confirmedBy` | array | Source types that corroborate the finding (e.g. `news`, `court-records`) | # PEP & Sanctions Source: https://docs.deepidv.com/api-reference/silent-screening/pep-sanctions POST /v1/screening/pep-sanctions Screen individuals against global PEP and sanctions lists ``` POST /v1/screening/pep-sanctions ``` Screens an individual against politically-exposed-person (PEP) and sanctions watchlists (local lists plus OpenSanctions) using only their name and date of birth. Matches are normalized, grouped into `peps`, `sanctions`, and `both`, and deduped by `(name, dataset)` with a confidence score. ## Request ### Headers | Header | Required | Description | | -------------- | -------- | ------------------ | | `x-api-key` | Yes | Your API key | | `Content-Type` | Yes | `application/json` | ### Body parameters | Parameter | Type | Required | Description | | ------------- | ------ | -------- | ------------------------------------- | | `email` | string | Yes | Individual's email address | | `firstName` | string | Yes | Individual's first name (1–255 chars) | | `lastName` | string | Yes | Individual's last name (1–255 chars) | | `dateOfBirth` | string | Yes | Date of birth in `YYYY-MM-DD` format | ### Example request ```bash cURL theme={null} curl -X POST https://api.deepidv.com/v1/screening/pep-sanctions \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -d '{ "email": "applicant@example.com", "firstName": "John", "lastName": "Doe", "dateOfBirth": "1980-01-15" }' ``` ```javascript Node.js theme={null} const response = await fetch("https://api.deepidv.com/v1/screening/pep-sanctions", { method: "POST", headers: { "Content-Type": "application/json", "x-api-key": "YOUR_API_KEY", }, body: JSON.stringify({ email: "applicant@example.com", firstName: "John", lastName: "Doe", dateOfBirth: "1980-01-15", }), }); const data = await response.json(); ``` ```python Python theme={null} import requests response = requests.post( "https://api.deepidv.com/v1/screening/pep-sanctions", headers={ "Content-Type": "application/json", "x-api-key": "YOUR_API_KEY", }, json={ "email": "applicant@example.com", "firstName": "John", "lastName": "Doe", "dateOfBirth": "1980-01-15", }, ) ``` ## Response ### 200 — Success | Field | Type | Description | | ----------------- | ------- | ------------------------------------------------- | | `totalMatches` | integer | Total number of matched records across all groups | | `peps` | array | Matches found only on PEP lists | | `sanctions` | array | Matches found only on sanctions lists | | `both` | array | Matches found on both PEP and sanctions lists | | `searchedSources` | array | Names of the datasets/sources that were queried | Each match in `peps`, `sanctions`, and `both` has the following shape: | Field | Type | Description | | ------------- | -------------- | ------------------------------------------------ | | `name` | string | Matched record name | | `country` | string \| null | Country associated with the record, if known | | `dateOfBirth` | string \| null | Date of birth on the record, if known | | `confidence` | number | Match confidence `0–1` (higher = stronger match) | | `datasets` | array | Source datasets that contributed this match | ### Error responses | Status | Description | | ------------------ | ----------------------------------------- | | `400 Bad Request` | Request body failed schema validation | | `401 Unauthorized` | API key is invalid | | `403 Forbidden` | API key is missing or resource not in org | | `404 Not Found` | Referenced resource was not found | | `500 Server Error` | Unexpected server error | ```json 200 theme={null} { "totalMatches": 1, "peps": [ { "name": "John Doe", "country": "US", "dateOfBirth": "1980-01-15", "confidence": 0.92, "datasets": ["us_ofac_sdn"] } ], "sanctions": [], "both": [], "searchedSources": ["local", "opensanctions"] } ``` # Title Check Source: https://docs.deepidv.com/api-reference/silent-screening/title-check POST /v1/screening/title-check Look up property title records, ownership history, and lien data ``` POST /v1/screening/title-check ``` Runs a property/title search for a US address via DataTree and returns the result synchronously. The endpoint geocodes the supplied address, resolves the matching property, and responds with a discriminated union describing the outcome. ## Request ### Headers | Header | Required | Description | | -------------- | -------- | ------------------ | | `x-api-key` | Yes | Your API key | | `Content-Type` | Yes | `application/json` | ### Body parameters | Parameter | Type | Required | Description | | ----------- | ------ | -------- | -------------------------------------------- | | `email` | string | Yes | Applicant email address | | `firstName` | string | Yes | Applicant first name (1–255 chars) | | `lastName` | string | Yes | Applicant last name (1–255 chars) | | `address` | string | Yes | US property address to look up (1–500 chars) | ### Example request ```bash cURL theme={null} curl -X POST https://api.deepidv.com/v1/screening/title-check \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -d '{ "email": "applicant@example.com", "firstName": "John", "lastName": "Doe", "address": "1600 Amphitheatre Parkway, Mountain View, CA 94043" }' ``` ```javascript Node.js theme={null} const response = await fetch("https://api.deepidv.com/v1/screening/title-check", { method: "POST", headers: { "Content-Type": "application/json", "x-api-key": "YOUR_API_KEY", }, body: JSON.stringify({ email: "applicant@example.com", firstName: "John", lastName: "Doe", address: "1600 Amphitheatre Parkway, Mountain View, CA 94043", }), }); const data = await response.json(); ``` ```python Python theme={null} import requests response = requests.post( "https://api.deepidv.com/v1/screening/title-check", headers={ "Content-Type": "application/json", "x-api-key": "YOUR_API_KEY", }, json={ "email": "applicant@example.com", "firstName": "John", "lastName": "Doe", "address": "1600 Amphitheatre Parkway, Mountain View, CA 94043", }, ) data = response.json() ``` ## Response ### 200 — Success The body is a **discriminated union on `status`**: | `status` | Description | | --------------------- | -------------------------------------------------------------------- | | `found` | A single property matched — includes the property detail fields | | `multiple_properties` | The address matched more than one property — disambiguation required | | `unsupported_region` | The address is outside the supported (US) coverage area | | `not_found` | No property/title record matched the address | When `status` is `found`, the response carries the property detail. Each object is `null` when the underlying data is unavailable: | Field | Type | Description | | --------------------------- | -------------- | ----------------------------------------------------------------- | | `subjectProperty` | object \| null | APN, full address, city, state, zoning, lot/building size, etc. | | `ownerInformation` | object \| null | Owner names, mailing address, vesting/ownership rights | | `locationInformation` | object \| null | County, census tract/block, school district, flood zone | | `ownerTransferInformation` | object \| null | Most recent ownership transfer (document, date, deed type, price) | | `lastMarketSaleInformation` | object \| null | Last market sale (date, price, buyer, seller, deed type) | When `status` is `multiple_properties`, the response includes `message`, an `availableUnits` array, and a `properties` array of `{ owner, apartmentOrUnit }` to disambiguate. When `status` is `unsupported_region` or `not_found`, the response includes a human-readable `message`. ### Error responses | Status | Description | | ------------------ | ----------------------------------------- | | `400 Bad Request` | Request body failed schema validation | | `401 Unauthorized` | API key is invalid | | `403 Forbidden` | API key is missing or resource not in org | | `404 Not Found` | Referenced resource was not found | | `500 Server Error` | Unexpected server error | ```json 200 (found) theme={null} { "status": "found", "subjectProperty": { "APNFormatted": "123-456-789", "PropertyFullStreetAddress": "1600 Amphitheatre Pkwy", "PropertyCity": "Mountain View", "PropertyState": "CA", "PropertyZipCode": "94043", "PropertyCounty": "Santa Clara", "YearBuilt": 1998 }, "ownerInformation": { "Owner1LastName": "Doe", "Owner1FirstNameMiddleInitial": "John", "MailingCity": "Mountain View", "MailingState": "CA" }, "locationInformation": null, "ownerTransferInformation": null, "lastMarketSaleInformation": null } ``` ```json 200 (not_found) theme={null} { "status": "not_found", "message": "Could not resolve the provided address." } ``` # Create Workflow Source: https://docs.deepidv.com/api-reference/workflows/create-workflow POST /v1/workflows Create a new verification workflow ``` POST /v1/workflows ``` Creates a new workflow for your organization. Workflows define the ordered sequence of verification steps that applicants must complete. Currently supported steps: `ID_VERIFICATION`, `FACE_LIVENESS`, `AGE_ESTIMATION`, `PEP_SANCTIONS`, `ADVERSE_MEDIA`. ## Request ### Headers | Header | Required | Description | | ----------- | -------- | ------------ | | `x-api-key` | Yes | Your API key | ### Body parameters | Parameter | Type | Required | Description | | ---------------- | ------ | -------- | ------------------------------------------------------------------------------------- | | `name` | string | Yes | Workflow name (1–255 characters) | | `steps` | array | Yes | Ordered list of verification steps (1–10, no duplicates) | | `steps[].id` | string | Yes | Step identifier (see [Available Steps](#available-steps)) | | `steps[].config` | object | No | Step-specific configuration overrides (see [Step Configuration](#step-configuration)) | ### Available Steps | Step ID | Description | | ----------------- | ------------------------------------------------- | | `ID_VERIFICATION` | Government ID scanning and validation | | `FACE_LIVENESS` | Active anti-spoofing liveness detection | | `AGE_ESTIMATION` | Biometric age estimation | | `PEP_SANCTIONS` | Politically exposed persons & sanctions screening | | `ADVERSE_MEDIA` | Negative press and media mention detection | ### Step Configuration Each step accepts optional configuration. If omitted, sensible defaults are applied. #### ID\_VERIFICATION config | Field | Type | Default | Description | | --------------------------- | ------- | -------- | --------------------------------------------------------------------------------------------------------------------- | | `minimum_age` | integer | `18` | Minimum accepted age | | `maximum_age` | integer | `51` | Maximum accepted age | | `expiry_date_years` | integer | `2` | Maximum years until ID expiry is rejected | | `require_secondary_id` | boolean | `false` | Require a second form of ID | | `require_tertiary_id` | boolean | `false` | Require a third form of ID | | `face_front_photo_only` | boolean | `false` | Only capture front-facing photo | | `require_front_only` | boolean | `false` | Only scan front of ID | | `enable_fraud_analysis` | boolean | `false` | Enable synchronous fraud analysis on submitted IDs. Automatically enabled when `escalation_type` is not `none` | | `escalation_type` | string | `"none"` | Escalation method when fraud risk is high. One of `none`, `nfc-passport`. Implicitly enables `enable_fraud_analysis` | | `escalation_risk_threshold` | integer | `60` | Fraud risk score (0–100) at or above which escalation is triggered. Only applies when `escalation_type` is not `none` | #### FACE\_LIVENESS config | Field | Type | Default | Description | | ---------------------- | ------- | ------- | ------------------------------------- | | `confidence_threshold` | integer | `70` | Liveness confidence threshold (1–100) | #### AGE\_ESTIMATION config | Field | Type | Default | Description | | ------------- | ------- | ------- | ------------------------------- | | `minimum_age` | integer | `18` | Minimum age requirement (1–100) | #### PEP\_SANCTIONS / ADVERSE\_MEDIA No configuration options. These steps use default settings. ### Fraud Analysis & Escalation The `ID_VERIFICATION` step supports optional fraud analysis and conditional escalation. When fraud analysis is enabled, submitted IDs are evaluated synchronously for tampering and fraud signals before the session completes. **Fraud analysis only** — enable analysis without triggering an extra verification step: ```json theme={null} { "id": "ID_VERIFICATION", "config": { "enable_fraud_analysis": true } } ``` **Fraud analysis with NFC passport escalation** — when the fraud risk score meets or exceeds the threshold, the applicant is prompted for a Passport NFC Scan: ```json theme={null} { "id": "ID_VERIFICATION", "config": { "escalation_type": "nfc-passport", "escalation_risk_threshold": 75 } } ``` Setting `escalation_type` to a value other than `none` implicitly enables fraud analysis. You do not need to set `enable_fraud_analysis` separately. | Scenario | `enable_fraud_analysis` | `escalation_type` | Behavior | | --------------------- | ----------------------- | ----------------- | ------------------------------------------------------------------------------------ | | Default (no config) | `false` | `"none"` | Fraud analysis runs asynchronously, no escalation | | Analysis only | `true` | `"none"` | Fraud analysis runs synchronously, results returned, no escalation | | Analysis + escalation | `true` (implicit) | `"nfc-passport"` | Fraud analysis runs synchronously, escalation triggered when risk score >= threshold | ### Example request ```bash cURL theme={null} curl -X POST "https://api.deepidv.com/v1/workflows" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Full KYC Workflow", "steps": [ { "id": "ID_VERIFICATION", "config": { "minimum_age": 21, "expiry_date_years": 5, "escalation_type": "nfc-passport", "escalation_risk_threshold": 75 } }, { "id": "FACE_LIVENESS", "config": { "confidence_threshold": 85 } }, { "id": "AGE_ESTIMATION" }, { "id": "PEP_SANCTIONS" }, { "id": "ADVERSE_MEDIA" } ] }' ``` ```javascript Node.js theme={null} const response = await fetch( "https://api.deepidv.com/v1/workflows", { method: "POST", headers: { "x-api-key": "YOUR_API_KEY", "Content-Type": "application/json", }, body: JSON.stringify({ name: "Full KYC Workflow", steps: [ { id: "ID_VERIFICATION", config: { minimum_age: 21, expiry_date_years: 5, escalation_type: "nfc-passport", escalation_risk_threshold: 75, }, }, { id: "FACE_LIVENESS", config: { confidence_threshold: 85 }, }, { id: "AGE_ESTIMATION" }, { id: "PEP_SANCTIONS" }, { id: "ADVERSE_MEDIA" }, ], }), } ); const data = await response.json(); ``` ```python Python theme={null} import requests response = requests.post( "https://api.deepidv.com/v1/workflows", headers={ "x-api-key": "YOUR_API_KEY", "Content-Type": "application/json", }, json={ "name": "Full KYC Workflow", "steps": [ { "id": "ID_VERIFICATION", "config": { "minimum_age": 21, "expiry_date_years": 5, "escalation_type": "nfc-passport", "escalation_risk_threshold": 75, }, }, { "id": "FACE_LIVENESS", "config": {"confidence_threshold": 85}, }, {"id": "AGE_ESTIMATION"}, {"id": "PEP_SANCTIONS"}, {"id": "ADVERSE_MEDIA"}, ], }, ) ``` ## Response ### 201 — Created Returns the full workflow record. The response uses the same format as [Retrieve Workflow](/api-reference/workflows/retrieve-workflow). | Field | Type | Description | | -------------------------- | ------ | ------------------------------------------------------ | | `workflow` | object | The created workflow record | | `workflow.id` | string | Unique workflow identifier | | `workflow.name` | string | Workflow name | | `workflow.status` | string | Always `active` for newly created workflows | | `workflow.organization_id` | string | Your organization ID | | `workflow.created_at` | string | ISO 8601 creation timestamp | | `workflow.updated_at` | string | ISO 8601 last-updated timestamp | | `workflow.steps` | array | Ordered list of steps with full resolved configuration | ### Error responses | Status | Description | | ----------------------- | ------------------------------------------------------------------------------------- | | `400 Bad Request` | Invalid request body (missing name, invalid step ID, invalid config, duplicate steps) | | `401 Unauthorized` | Missing or invalid API key | | `403 Forbidden` | Sandbox API keys cannot create workflows | | `429 Too Many Requests` | Rate limit exceeded | ```json 201 theme={null} { "workflow": { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "name": "Full KYC Workflow", "status": "active", "organization_id": "da760e2f-2f7b-4f5d-b394-766ce9c4fad8", "created_at": "2026-03-31T14:00:00.000Z", "updated_at": "2026-03-31T14:00:00.000Z", "steps": [ { "id": "id-verification", "config": { "age_restriction_settings": { "age_restriction_mode": "slider", "country_category_minimum_age": null, "minimum_age": { "lower": 21, "upper": 51 } }, "expiry_date_settings": { "expiry_date": 5 }, "face_scan_settings": { "face_front_photo_only": false }, "id_scan_face_settings": { "require_front_only": false }, "id_scan_settings": { "require_secondary_id": false, "require_tertiary_id": false }, "valid_id_types_settings": { "valid_id_types": { "driver_license_ca": true, "driver_license_us": true, "passport_ca": true, "passport_us": true } }, "valid_states_settings": { "valid_states": { "AL": true, "AK": true, "AZ": true, "...": "all US + CA" } }, "fraud_analysis_settings": { "enable_fraud_analysis": true }, "fraud_analysis_escalation": { "escalation_type": "nfc-passport", "escalation_risk_threshold": 75 } } }, { "id": "face-liveness", "config": { "face_liveness_confidence_settings": { "confidence_threshold": 85 }, "face_liveness_settings": { "preferred_liveness_method": "FaceMovementChallenge" } } }, { "id": "age-estimation", "config": { "age_restriction_settings": { "age_restriction_mode": "slider", "country_category_minimum_age": null, "minimum_age": { "lower": 18, "upper": 100 } } } }, { "id": "pep-sanctions", "config": {} }, { "id": "adverse-media", "config": {} } ] } } ``` # List Workflows Source: https://docs.deepidv.com/api-reference/workflows/list-workflows GET /v1/workflows List all workflows for your organization ``` GET /v1/workflows ``` Returns all workflows belonging to your organization, sorted by creation date (newest first). ## Request ### Headers | Header | Required | Description | | ----------- | -------- | ------------ | | `x-api-key` | Yes | Your API key | ### Example request ```bash cURL theme={null} curl -X GET "https://api.deepidv.com/v1/workflows" \ -H "x-api-key: YOUR_API_KEY" ``` ```javascript Node.js theme={null} const response = await fetch( "https://api.deepidv.com/v1/workflows", { headers: { "x-api-key": "YOUR_API_KEY" }, } ); const data = await response.json(); ``` ```python Python theme={null} import requests response = requests.get( "https://api.deepidv.com/v1/workflows", headers={"x-api-key": "YOUR_API_KEY"}, ) ``` ## Response ### 200 — Success | Field | Type | Description | | ------------------------ | ------ | -------------------------------------------------------------------------------------------- | | `workflows` | array | Array of workflow summary objects | | `workflows[].id` | string | Unique workflow identifier | | `workflows[].name` | string | Workflow name | | `workflows[].status` | string | Workflow status (`active` or `inactive`) | | `workflows[].steps` | array | Array of step identifiers included in the workflow (e.g. `ID_VERIFICATION`, `FACE_LIVENESS`) | | `workflows[].created_at` | string | ISO 8601 creation timestamp | ### Error responses | Status | Description | | ----------------------- | -------------------------- | | `401 Unauthorized` | Missing or invalid API key | | `429 Too Many Requests` | Rate limit exceeded | ```json 200 theme={null} { "workflows": [ { "id": "6d6da499-9225-40fb-9ffd-a06634b915bd", "name": "Full Verification", "status": "active", "steps": [ "ID_VERIFICATION", "FACE_LIVENESS", "PEP_SANCTIONS", "DOCUMENT_UPLOAD" ], "created_at": "2026-03-01T17:30:24.573Z" }, { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "name": "Basic ID Check", "status": "active", "steps": [ "ID_VERIFICATION", "FACE_LIVENESS" ], "created_at": "2026-02-15T09:30:00.000Z" } ] } ``` # Retrieve Workflow Source: https://docs.deepidv.com/api-reference/workflows/retrieve-workflow GET /v1/workflows/{id} Retrieve a workflow by ID ``` GET /v1/workflows/{id} ``` Returns the full workflow record for the given ID. The workflow must belong to your organization. ## Request ### Headers | Header | Required | Description | | ----------- | -------- | ------------ | | `x-api-key` | Yes | Your API key | ### Path parameters | Parameter | Type | Required | Description | | --------- | ------ | -------- | --------------- | | `id` | string | Yes | The workflow ID | ### Example request ```bash cURL theme={null} curl -X GET "https://api.deepidv.com/v1/workflows/6d6da499-9225-40fb-9ffd-a06634b915bd" \ -H "x-api-key: YOUR_API_KEY" ``` ```javascript Node.js theme={null} const response = await fetch( "https://api.deepidv.com/v1/workflows/6d6da499-9225-40fb-9ffd-a06634b915bd", { headers: { "x-api-key": "YOUR_API_KEY" }, } ); const data = await response.json(); ``` ```python Python theme={null} import requests response = requests.get( "https://api.deepidv.com/v1/workflows/6d6da499-9225-40fb-9ffd-a06634b915bd", headers={"x-api-key": "YOUR_API_KEY"}, ) ``` ## Response ### 200 — Success ### Workflow object | Field | Type | Description | | -------------------------- | ------ | ---------------------------------- | | `workflow` | object | The workflow record | | `workflow.id` | string | Unique workflow identifier | | `workflow.name` | string | Workflow name | | `workflow.organization_id` | string | Owning organization ID | | `workflow.status` | string | `active` or `inactive` | | `workflow.created_at` | string | ISO 8601 creation timestamp | | `workflow.updated_at` | string | ISO 8601 last-updated timestamp | | `workflow.steps` | array | Ordered list of verification steps | ### Step object | Field | Type | Description | | ---------------- | ------ | --------------------------------------------------------- | | `steps[].id` | string | Step identifier (e.g. `id-verification`, `face-liveness`) | | `steps[].config` | object | Step-specific configuration settings | To list sessions associated with a workflow, use the [List Sessions](/api-reference/sessions/list-sessions) endpoint with the `workflow_id` query parameter. ### Error responses | Status | Description | | ----------------------- | -------------------------------------------- | | `401 Unauthorized` | Missing or invalid API key | | `403 Forbidden` | Workflow belongs to a different organization | | `404 Not Found` | No workflow found with the given ID | | `429 Too Many Requests` | Rate limit exceeded | ```json 200 theme={null} { "workflow": { "id": "6d6da499-9225-40fb-9ffd-a06634b915bd", "name": "Everything", "status": "active", "organization_id": "da760e2f-2f7b-4f5d-b394-766ce9c4fad8", "created_at": "2026-03-01T17:30:24.573Z", "updated_at": "2026-03-01T17:30:24.573Z", "steps": [ { "id": "id-verification", "config": { "age_restriction_settings": { "minimum_age": { "lower": 18, "upper": 55 } }, "expiry_date_settings": { "expiry_date": 1 }, "face_scan_settings": { "face_front_photo_only": false }, "id_scan_face_settings": { "require_front_only": false }, "id_scan_settings": { "require_secondary_id": true, "require_tertiary_id": true }, "valid_id_types_settings": { "valid_id_types": { "driver_license_ca": true, "driver_license_us": true, "passport_ca": true, "passport_us": true, "pr_card_ca": true, "pr_card_us": true } }, "valid_states_settings": { "valid_states": { "BC": true, "CA": true, "NY": true, "ON": true } } } }, { "id": "face-liveness", "config": { "face_liveness_confidence_settings": { "confidence_threshold": 70 }, "face_liveness_settings": { "preferred_liveness_method": "FaceMovementChallenge" } } }, { "id": "age-estimation", "config": { "age_estimation_settings": { "minimum_age": 18 } } }, { "id": "pep-sanctions", "config": {} }, { "id": "adverse-media", "config": {} }, { "id": "bank-statement-upload", "config": { "bank_statement_settings": { "account_type": "checking", "statement_period": "12" } } }, { "id": "document-upload", "config": { "document_upload_instructions": { "document_upload_list": { "document1": "Articles of Incorporation" } } } }, { "id": "title-search", "config": { "title_search_settings": { "title_search_country": "usa" } } }, { "id": "custom-prompt", "config": { "custom_instructions": { "custom_prompts_list": [{ "text": "Hold up 3 fingers" }] } } }, { "id": "custom-form", "config": { "form_fields": { "form_fields_list": { "field1": { "label": "What is your Company Business Number", "options": {}, "type": "short-text" } } } } }, { "id": "ai-bank-statement-analysis", "config": {} } ] } } ``` # API Authentication Source: https://docs.deepidv.com/authentication Learn how to authenticate with the deepidv API > deepidv uses API keys to authenticate every request. Pass your secret key via HTTP header — missing or invalid keys return a 401 error. ## Finding Your API Key In deepidv, API keys are scoped to your **Organization**. Your organization is the workspace where your team manages workflows, verification sessions, and billing. An API key is generated when you first create your organization. To find or regenerate your key: Go to the [**deepidv Admin Console**](https://app.deepidv.com) and sign in. Navigate to [**Settings → API Keys**](https://app.deepidv.com/dashboard/api/api-keys) in the sidebar. Your **API Key** will be displayed here. Click to copy it. **Your API key is a secret — treat it like a password.** It grants full access to the API on behalf of your organization. Never expose it in frontend code, public repositories, or client-side bundles. Always keep it server-side only. *** ## Base URL All API requests are made against a single base URL: ``` https://api.deepidv.com ``` Every endpoint is prefixed with `/v1` — for example, `https://api.deepidv.com/v1/sessions`. *** ## Making Authenticated Requests Include your secret API key in the `x-api-key` HTTP header with every request. Here's an example of an authenticated request to the `Create Session` endpoint: ```bash cURL theme={null} curl -X POST https://api.deepidv.com/v1/sessions \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -d '{ "firstName": "Jane", "lastName": "Smith", "email": "jane.smith@example.com", "phone": "+14165557890", "workflowId": "your-workflow-id" }' ``` deepidv validates your API key using HMAC SHA256 to authenticate the request and identify your organization. *** ## Rate Limits The API enforces rate limits to ensure fair usage and platform stability: | Limit | Value | | ------------------- | ------ | | Requests per second | 25 | | Burst capacity | 35 | | Daily request quota | 10,000 | If you exceed these limits, the API returns a `429 Too Many Requests` response. Implement exponential backoff in your integration to handle rate limiting gracefully. *** ## Error Handling If your API key is missing or invalid, the API returns a `401 Unauthorized` response: ```json theme={null} { "detail": "Invalid or missing API key" } ``` | Status Code | Meaning | | ----------- | ------------------------------------------------------ | | **401** | API key is missing, invalid, or revoked | | **402** | Insufficient token balance in your organization | | **403** | API key does not have access to the requested resource | | **429** | Rate limit exceeded — back off and retry | If you receive a `401` error, double-check that you're using the correct API key for your organization and that it hasn't been regenerated since you last copied it. # Attestation Detail Pages Source: https://docs.deepidv.com/chain-layer/attestation-detail Reading the full six-step proof pipeline for a single verification > When you land on `proof.deepidv.com/a/{id}`, you're looking at the full proof of one specific verification. ## Reading the six-step pipeline Each step has a status badge: * **Verified** — the check passed. The cryptographic signature, the timestamp, the inclusion proof, the anchor all check out. * **Skipped** — applies only to TSA token verification in the SDK `verifyBundle()` path, because the SDKs do not ship X.509 chain validation in v1. The explorer always verifies all six steps; only the offline-SDK path skips TSA. * **Failed** — should never happen for a legitimate attestation. If you see it, something has been tampered with. ## What each step means SHA-256 of the canonicalized (JCS, RFC 8785) envelope JSON. Anchors the rest of the chain — every signature below is over this hash. ECDSA P-256 signature by the issuer's KMS-held private key over the envelope hash. Verified against the issuer's public key, which is in the bundle and on the issuer profile page. Independent timestamp tokens from DigiCert and Sectigo, each binding the envelope hash to a specific moment in time. Either alone is sufficient evidence; both together are belt-and-suspenders. The audit path from this envelope's leaf to the segment's STH root. Verifies the envelope is actually in the log — not just claimed to be. ECDSA P-256 signature by the chain-master key over the STH (root + tree size + timestamp). Verified against `master.pem` published on the log page. The Base L2 transaction hash committing the STH root. Click through to Basescan to confirm the root appears on-chain at the claimed block height. ## Label commitments Each envelope carries a list of labels — structured metadata like country, tier, document type, KYC level. In the bundle and on the detail page, you see: | name | value | status | | ------- | ------------------------------ | --------- | | country | `CA` | Revealed | | tier | `[committed but not revealed]` | Committed | A label is **Revealed** when the issuer has chosen to publish the salt alongside the value commitment. The value is then verifiable: take the value, append the salt, hash it, and compare to the commitment in the envelope. If they match, the label is real. A label is **Committed** when only the commitment is published. The issuer holds the salt off-chain. They can prove the label's value to a specific counterparty without revealing it publicly. Salts are **never** rendered in the explorer or included in proof bundles. # The Explorer Source: https://docs.deepidv.com/chain-layer/explorer A public, no-login-required view of the chain layer at proof.deepidv.com > The explorer lives at [proof.deepidv.com](https://proof.deepidv.com). It's a public, no-login-required view into the chain layer. ## Home **URL:** [proof.deepidv.com](https://proof.deepidv.com) Live attestation stream. Every mint appears in real time via Server-Sent Events. Below the fold: a rolling table of the most recent 50 attestations with ULID, record type, issuer, envelope hash prefix, mint timestamp, and segment number. ## Registry **URL:** [proof.deepidv.com/registry](https://proof.deepidv.com/registry) Paginated table of every attestation in the log. Filterable by: * **Record type.** `IDV` is the only active type at v1 launch. `BIO`, `DOC`, and `ADDR` appear as disabled "Phase 2" chips. `WIT` (witness attestations) and `AGT` (agent verifications) ship in Phase 3. * **Issuer.** Restrict the view to a single issuer. * **Segment.** View only attestations in a specific segment. Search by ULID, envelope hash prefix, or issuer ID. URL state is shareable — every filter combination has its own deep link. See [The Registry](/chain-layer/registry) for full details. ## Attestation Detail **URL:** `proof.deepidv.com/a/{attestation_id}` The single most important page in the explorer. Renders the full six-step proof pipeline for one attestation: 1. **Envelope hash.** The hash of the canonicalized envelope JSON. 2. **Issuer signature.** ECDSA P-256 signature by the issuer over the envelope hash. 3. **Dual TSA timestamps.** RFC 3161 timestamp tokens from DigiCert and Sectigo, proving the envelope existed at the claimed time. 4. **Merkle inclusion.** The audit path from this envelope's leaf to the segment's current STH root. 5. **Master STH signature.** ECDSA P-256 signature by the chain-master key over the STH. 6. **On-chain anchor.** The Base L2 transaction hash committing the STH root. Click through to Basescan. Each step is independently verifiable. If any step fails, the whole bundle fails — and the failure point tells you exactly where the integrity broke. From this page you can also: * Download the `.dpiv` proof bundle * Click through to the issuer's profile * Click through to the segment * See the label commitments table (names + revealed values where available; salts are never rendered) See [Attestation Detail Pages](/chain-layer/attestation-detail) for a deeper walkthrough. ## Issuer Profile **URL:** `proof.deepidv.com/issuer/{issuer_id}` Per-issuer page showing: * The issuer's P-256 public key (downloadable as PEM) * Rotation history (empty in v1) * Last 50 attestations issued ## Segment Profile **URL:** `proof.deepidv.com/segment/{segment_id}` Per-segment page showing: * Tree size over time (sparkline chart) * Full STH list — every hourly checkpoint plus the segment's closure STH * Base L2 transaction hashes for each anchored STH (linked to Basescan) * Consistency proofs between successive STHs (downloadable) ## Log **URL:** [proof.deepidv.com/log](https://proof.deepidv.com/log) Bird's-eye view of the entire transparency log. Recent STHs across all segments, tree-size visualization, and a downloadable copy of the master public key (`master.pem`) for offline verification of any STH. See [The Log](/chain-layer/log) for full details. ## Governance **URL:** [proof.deepidv.com/governance](https://proof.deepidv.com/governance) Single-sign-on gated. Unauthenticated visits redirect to sign-in. See [The Governance Console](/chain-layer/governance). # FAQ Source: https://docs.deepidv.com/chain-layer/faq Common questions about the chain layer ## Are these on-chain hashes tokens? **No.** The hashes published to Base L2 are cryptographic commitments to verification records. They cannot be transferred, traded, bought, or sold. They have no monetary value. Their only purpose is independent verification. ## Why two RFC 3161 TSAs? So no single timestamp authority can fabricate, revoke, or alter a timestamp. DigiCert and Sectigo are operationally independent. An attacker would need to compromise both — plus the chain-master key, plus the issuer key — to fake a fully verifiable attestation. The threshold scales with the number of independent parties. ## Why Base L2 instead of Ethereum L1? Cost. A Base L2 STH anchor costs roughly $0.003. The same anchor on Ethereum L1 would cost $1–5. Base inherits Ethereum security after a small soak, which is more than enough for hourly STH commitments. If a customer requires an L1 mirror, that's available as a Phase 4 enterprise add-on. ## What happens if deepidv disappears? The on-chain anchors persist on Base L2. The master public key is downloadable now from the [log page](/chain-layer/log); save it. The proof bundles you've already downloaded continue to verify offline forever using `verify.sh`. The SDKs are open-source on npm and PyPI and continue to function. The only thing that goes away is the live API for fetching new bundles; for that, future bundles would need to be retrieved another way (block explorers, archived snapshots). ## Can deepidv modify or delete an envelope after it's issued? **No.** The chain layer is append-only. Once an envelope's hash is included in an STH, modifying it would invalidate the Merkle root, the master signature, and the on-chain anchor. An invalid envelope can be marked as such by issuing a revocation envelope that references the original, but the original itself is permanent. ## What's the difference between the chain layer and deepidv's main verification engine? The verification engine actually performs the verification — runs the KYC check, validates the document, scores the liveness. The chain layer records the result in a public, tamper-evident log. Verification is what we do; the chain layer is the proof. ## What's coming in Phase 2 and Phase 3? **Phase 2** (in progress) wires per-attestation on-chain commitments and adds the foundation for the **\$DIDV token** — a service-consumption token that users earn for completing verifications and witnessing others. **Phase 3** launches the token, integrates with WorldID for sybil-resistant social attestations, and adds agent identity verification (`AGT` records) for AI agents acting on behalf of users. ## Is the chain layer GDPR-compliant? **Yes.** No personal data is ever published on-chain or in the public log. Pseudonymous subject IDs are derived such that they cannot be reverse-engineered without the per-tenant secret. Label values are committed by default and revealed only with explicit issuer-controlled disclosure. The right-to-erasure framework is supported through revocation envelopes; underlying personal data in our encrypted storage is fully erasable under your data agreement. # The Governance Console Source: https://docs.deepidv.com/chain-layer/governance SSO-gated operational control plane for the chain layer > The governance console at [proof.deepidv.com/governance](https://proof.deepidv.com/governance) is the operational control plane for the chain layer. Access is restricted via single-sign-on. ## Who has access The console is for authorized deepidv personnel and named compliance officers at enterprise tenants. There are three roles: * **Observer.** Read-only view of all chain layer state. Can see segments, STHs, anchor history, alarm states. Cannot make changes. * **Operator.** Observer plus the ability to invoke operational controls (anchor pause/resume, force-close a segment) with full audit trail. * **Auditor.** Read-only access to the append-only governance log. Used by external auditors and regulators. All console actions are logged to `chain-governance-log`, an append-only DynamoDB table. Writes use `PutItem` only — no updates, no deletes. Every action is attributed to a specific authenticated principal with a mandatory reason field. ## Anchor controls Three operational controls govern the on-chain anchoring pipeline. ### Anchor pause Halts on-chain broadcasting. New attestations continue to mint and STHs continue to sign, but the broadcast step is paused. The pause persists until manually resumed. Used for emergencies — suspected key compromise, suspected contract bug, operational maintenance. **To pause:** Governance → Anchor → Pause → enter a reason → confirm. Slack `#chain-alerts` notification fires immediately. **To resume:** Same path → Resume. Queued STHs broadcast in order on resume. ### Anchor mode toggle Sets the default `anchor` field on new attestations when issuers don't specify. Three modes: | Mode | Behavior | | ---------- | ---------------------------------------------------------------------------------------------------------------------------- | | `offchain` | STH anchored hourly (default). Per-attestation envelope hashes are not broadcast individually. | | `dual` | STH anchored hourly **and** envelope hashes broadcast in per-segment batches. | | `onchain` | Every individual attestation broadcasts its envelope hash on-chain. Higher cost; not exposed as a public pricing tier in v1. | ### Force-close segment Closes the named segment immediately, emits a final STH, and starts a new segment. Used in emergencies — suspected segment-level integrity issue, planned operational cutover, or clean-room replacement. Force-close is **irreversible**. The endpoint requires explicit confirmation plus a five-minute cooldown after first invocation before final commit. ## Reading the audit log Every governance action — every pause, every mode toggle, every force-close — is recorded in the governance log with timestamp, actor, action, target, and reason. Auditors with the Auditor role can read the log directly. For external compliance reviews, deepidv can export a signed copy of the governance log scoped to a specific time window. Contact your account team to request an export. ## What the console does NOT do (intentionally) * **Key rotation.** No rotation of `chain-master` or active issuer KMS keys is possible via the console in v1. Rotation requires the M10+ key-version capture support and a separately gated rotation ceremony. * **Envelope deletion.** The chain layer is append-only. Nothing is ever deleted. Mistaken or invalid envelopes remain in the log; a separate revocation envelope can be issued to mark the original as invalid, but the original persists for audit. * **Direct DDB writes.** The console invokes governance APIs that wrap the underlying datastores. No direct database access is exposed. # How It Works Source: https://docs.deepidv.com/chain-layer/how-it-works Envelopes, segments, STHs, and on-chain anchoring > The chain layer is built from five primitives: envelopes, issuers, segments, signed tree heads, and on-chain anchors. ## Envelopes An envelope is the atomic unit of the chain layer. It's a JSON document with a fixed schema, signed by an issuer's private key: ```json theme={null} { "schema_version": "idv/v1", "record_type": "IDV", "subject": "sub_01HZ8...", "issuer_id": "iss_acme_kyc", "envelope_hash": "0x...", "issued_at": "2026-05-21T14:32:01Z", "labels": [ { "name": "country", "value_commitment": "0x...", "salt_commitment": "0x..." }, { "name": "tier", "value_commitment": "0x...", "salt_commitment": "0x..." } ], "issuer_sig": "0x..." } ``` Key properties: * **Subject IDs are pseudonymous.** Derived via HKDF from the real user ID plus a tenant-scoped secret. Two different tenants verifying the same person produce different subject IDs. * **Labels carry commitments, not values.** The actual label value (e.g. `"Canada"`, `"Tier 3"`) is hashed with a random salt. The hash goes into the envelope; the salt stays off-chain with the issuer. Only with the salt can a third party verify what the label says. * **Canonical JSON.** The whole envelope is canonicalized via JCS (RFC 8785) before signing — so byte-identical signatures across any language or runtime. * **ECDSA P-256.** Signed with ECDSA P-256 against an issuer-scoped key held in AWS KMS. The private key never leaves KMS. ## Issuers Every envelope has exactly one issuer. Issuers are tenants of the deepidv platform — companies, organizations, or trusted entities that have been registered with the chain layer and granted minting rights. Each issuer has: * A unique `issuer_id` (e.g. `iss_acme_kyc`) * A P-256 public key, downloadable from their issuer profile * A rotation history (empty in v1 — issuer keys are pinned for the life of the segment) Anyone verifying a proof bundle pulls the issuer's public key from the bundle itself and re-validates the signature. Independent of deepidv. ## Segments and Merkle trees Envelopes don't sit individually on the chain. They're grouped into **segments** — append-only Merkle trees (RFC 6962, SHA-256, domain-separated). Each segment has: * An integer ID (starting at 0) * A tree size that grows as new envelopes land * A current Merkle root that updates with every mint * An hourly schedule of Signed Tree Heads (see below) Segments close on a regular cadence (operational decision; v1 closes when tree size reaches a threshold or 30 days elapse, whichever first). When a segment closes, its final STH is broadcast and the next segment starts fresh. ## Signed Tree Heads (STHs) Every hour, for every active segment, the chain layer: 1. Computes the current Merkle root over all envelopes in the segment 2. Signs the root + tree size + timestamp with the **chain-master key** (a single P-256 key held in KMS, separate from any issuer key) 3. Timestamps the signature with two independent RFC 3161 time-stamping authorities (DigiCert + Sectigo) 4. Broadcasts the STH on-chain to Base L2 via the `DeepIdvRegistry` smart contract The STH is the load-bearing object of the transparency log. As long as you trust one of the two RFC 3161 TSAs and the Base L2 chain itself, you have a cryptographically signed proof that this exact Merkle root existed at this exact moment in time. ## On-chain anchoring STH broadcasts land on Base L2 (an Ethereum Layer 2 chain) every hour. The `DeepIdvRegistry` contract emits an `AnchorCheckpoint` event containing the segment ID, tree size, root hash, timestamp, and master signature. Three things this gets you: * **Tamper-evidence.** If anyone modified an envelope after its STH was anchored, the recomputed root would no longer match the on-chain commitment. * **Independent third-party verification.** Anyone with a proof bundle can check the on-chain root via Basescan or any Base RPC provider, without touching deepidv infrastructure. * **Trustless long-term archive.** Even if deepidv ceased to exist tomorrow, the on-chain anchors persist as long as Ethereum does. **On-chain commitments are not tokens.** The envelope hashes published to Base L2 are cryptographic commitments to verification records. They are not transferable. They have no monetary value. They cannot be bought or sold. Their only purpose is independent verification. ## Proof bundles A proof bundle is a downloadable archive (`.dpiv` extension) containing everything needed to verify one specific attestation, end to end, offline. See [Proof Bundles](/chain-layer/proof-bundles) for the full contents. # The Log Source: https://docs.deepidv.com/chain-layer/log The macro view of the transparency log — STHs, tree growth, and the master public key > The log at [proof.deepidv.com/log](https://proof.deepidv.com/log) gives you the macro view of the transparency log as a whole. ## STH timeline Every hour, every active segment produces a Signed Tree Head. The log page lists the most recent STHs across all segments, with: * Segment ID * Tree size * Root hash (truncated, click to expand) * Master signature (truncated) * RFC 3161 timestamp tokens * Base L2 anchor tx hash (linked to Basescan) ## Tree size sparkline A visual chart showing how the log has grown over time. Useful for confirming activity, spotting anomalies, and understanding the cadence of attestations. ## Master public key The master STH key's public PEM is downloadable directly from the log page. Anyone verifying a proof bundle offline can use this to confirm the master signature on any STH. Save a local copy of `master.pem` today. If deepidv ever became unreachable, you'd still be able to verify any STH offline using only the master public key and the on-chain anchor. # Overview Source: https://docs.deepidv.com/chain-layer/overview The cryptographic transparency log behind every deepidv verification > The chain layer is the cryptographic transparency substrate underneath every deepidv verification — a public, append-only log anchored hourly to a blockchain. When a verification is minted — a KYC check, document validation, address proof, agent identity — the chain layer produces a signed envelope, places it in an append-only log, and emits hourly checkpoints anchored to a public blockchain. The result: anyone, anywhere, in any language, can verify that a specific verification happened, when it happened, who issued it, and that the record has not been changed since. Without trusting deepidv. You do not need a deepidv account to use the chain layer. The explorer at [proof.deepidv.com](https://proof.deepidv.com) is public. The SDKs are open. The proof bundles are downloadable, verifiable offline, and portable. ## Why it exists Trust in identity verification has always been "trust us" — the issuer says they verified you, and downstream parties accept the issuer's word. That works when the issuer is reputable, accountable, and reachable. It breaks the moment any of those three fail. The chain layer replaces "trust us" with "verify for yourself." Every attestation deepidv issues is committed to a public, append-only, cryptographically signed log. Auditors, regulators, counterparties, and consumers can verify the same record without going through deepidv at all. ## The 60-second mental model A deepidv tenant (the issuer) verifies something about a subject — a person, organization, or agent. The chain layer produces a signed envelope — a JSON record bound to the issuer's key, the verification result, and a set of label commitments that bind metadata without revealing it. The envelope is appended to a Merkle tree called a **segment**. Every hour, the segment's tree root is signed (a Signed Tree Head, or STH) and broadcast to the Base L2 blockchain. With the attestation ID, anyone can download a proof bundle (a `.dpiv` file) containing everything needed to verify the chain of signatures end to end. That's it. The rest of this documentation explains each piece. Envelopes, segments, STHs, and on-chain anchoring explained. Browse the public transparency log at proof.deepidv.com. Downloadable, offline-verifiable .dpiv archives. What you verify independently — and what you trust. # Proof Bundles Source: https://docs.deepidv.com/chain-layer/proof-bundles Downloadable .dpiv archives that verify one attestation end-to-end, offline > A proof bundle is a downloadable archive (`.dpiv` extension) containing everything needed to verify one attestation offline. ## What's inside a `.dpiv` file A proof bundle is a ZIP archive (with a custom `.dpiv` extension): ``` attestation.dpiv ├── envelope.json # The canonical envelope JSON ├── envelope.hash # SHA-256 of envelope.json (canonicalized) ├── issuer.pem # The issuer's public key ├── issuer.sig # The issuer signature over envelope.hash ├── tsa-digicert.tsr # RFC 3161 timestamp token from DigiCert ├── tsa-sectigo.tsr # RFC 3161 timestamp token from Sectigo ├── inclusion.json # Merkle inclusion proof (audit path) ├── sth.json # The signed tree head ├── master.pem # The chain-master public key ├── master.sig # Master signature over sth.json ├── onchain.json # Base L2 anchor metadata (if onchain mode) ├── verify.sh # POSIX-sh verification script ├── README.md # Human-readable explanation └── manifest.sha256sum # SHA-256 of every file above ``` ## How to download From any attestation detail page on the explorer, click **Download bundle**. The file streams as `{attestation_id}.dpiv`. Programmatically: ```bash theme={null} curl -L -o attestation.dpiv \ https://api.deepidv.com/v1/bundle/{attestation_id} ``` ## How to verify The bundle includes a POSIX shell script, `verify.sh`, that runs every check end to end. No internet connection required after download. ```bash theme={null} # Unzip the bundle unzip attestation.dpiv -d ./bundle # Run the verifier cd bundle && ./verify.sh # Output: # [OK] Envelope hash matches # [OK] Issuer signature valid # [OK] TSA tokens valid (DigiCert + Sectigo) # [OK] Merkle inclusion proof valid # [OK] Master STH signature valid # [OK] On-chain anchor present (tx 0x...) # # Bundle verified. ``` `verify.sh` uses only POSIX-standard tools (`openssl`, `sha256sum`, `jq`, `xxd`). It works on any Linux, macOS, or BSD system with these utilities installed. For programmatic verification, see the [SDKs](/chain-layer/sdks). # Reference Source: https://docs.deepidv.com/chain-layer/reference API endpoints, SDK sources, public surfaces, and standards ## Endpoints | Endpoint | Method | Purpose | | ----------------------- | --------- | ------------------------------------- | | `/v1/sth?segment={id}` | GET | Latest signed tree head for a segment | | `/v1/attestation/{id}` | GET | Full attestation detail | | `/v1/bundle/{id}` | GET | Download `.dpiv` proof bundle | | `/v1/registry` | GET | Paginated registry, with filters | | `/v1/issuer/{id}` | GET | Issuer profile + public key | | `/v1/segment/{id}` | GET | Segment profile + STH list | | `/v1/log` | GET | Recent STHs across all segments | | `/v1/stream` | GET (SSE) | Real-time attestation stream | | `/v1/proof/{from}/{to}` | GET | Consistency proof between STHs | ## SDK source * **Node.js:** [github.com/deep-identity-inc/deepidv-chain-node](https://github.com/deep-identity-inc/deepidv-chain-node) * **Python:** [github.com/deep-identity-inc/deepidv-chain-python](https://github.com/deep-identity-inc/deepidv-chain-python) ## Public surfaces * **Explorer:** [proof.deepidv.com](https://proof.deepidv.com) * **API:** [api.deepidv.com](https://api.deepidv.com) * **Docs:** [docs.deepidv.com](https://docs.deepidv.com) * **Status:** [status.deepidv.com](https://status.deepidv.com) ## Standards referenced * **RFC 6962** — Certificate Transparency (Merkle log structure) * **RFC 3161** — Time-Stamp Protocol * **RFC 8785** — JSON Canonicalization Scheme * **NIST FIPS 186-4** — ECDSA with P-256 curve * **NIST FIPS 180-4** — SHA-256 *** *deepidv — verification engine and agentic compliance suite. San Francisco.* # The Registry Source: https://docs.deepidv.com/chain-layer/registry Browse, filter, and search every attestation in the chain layer > The registry at [proof.deepidv.com/registry](https://proof.deepidv.com/registry) is the searchable surface across every public attestation. ## Browsing attestations Use the registry to: * Confirm a specific attestation exists (paste the ULID or envelope hash prefix) * Audit an issuer's volume and patterns * Inspect attestations across a specific segment ## Filters and search Every filter is URL-state-backed — you can share a link to a filtered view, bookmark it, or paste it into a report. | Filter | What it does | | -------------------- | --------------------------------------------------------------------------------------------------------- | | **Record type chip** | `IDV` active. `BIO` / `DOC` / `ADDR` show as "Phase 2" disabled. `WIT` / `AGT` not yet visible (Phase 3). | | **Search box** | ULID, envelope hash prefix, or issuer ID | | **Issuer dropdown** | Restrict to one issuer at a time | | **Segment number** | Restrict to one segment at a time | ## Privacy posture The registry surfaces only the public, non-identifying fields. You'll see: * The attestation ULID (random, not derivable to a user) * The pseudonymous subject ID * The issuer ID * The envelope hash prefix * The mint timestamp * The segment You will **never** see, in the registry or anywhere on the explorer: real names, email addresses, phone numbers, passport numbers, document numbers, or any other PII. Label values longer than 64 characters are server-side redacted with the placeholder `[committed but not revealed]`. Salt values are never published anywhere — not in the registry, not in attestation detail pages, not in proof bundles. # SDKs Source: https://docs.deepidv.com/chain-layer/sdks Officially supported Node.js and Python SDKs for the chain layer > Two officially supported SDKs at v1 launch. Both produce **byte-identical** cryptographic results against the same proof bundle — verified by cross-language parity tests in CI. ## Node.js / TypeScript Package: [`@deepidv/chain`](https://www.npmjs.com/package/@deepidv/chain) on npm. ```bash theme={null} npm install @deepidv/chain ``` ```ts theme={null} import { createClient, verifyBundle } from "@deepidv/chain"; // Read public registry data const client = createClient({ apiUrl: "https://api.deepidv.com", }); const attestation = await client.getAttestation("att_01HZ8..."); console.log(attestation.envelope_hash); // Verify a downloaded bundle import { readFile } from "node:fs/promises"; const bundleBytes = await readFile("./attestation.dpiv"); const result = verifyBundle(bundleBytes); if (result.ok) { console.log("Bundle verified."); } else { console.error("Verification failed:", result.checks); } ``` The Node SDK: * Has **zero runtime dependencies** (uses `node:crypto` and global `fetch`) * Ships as **dual ESM + CJS** * Requires **Node 20+** * **Skips RFC 3161 TSA verification** (returns `skipped`, not `verified`) — use `verify.sh` from the bundle for full TSA validation ## Python Package: [`deepidv-chain`](https://pypi.org/project/deepidv-chain/) on PyPI. ```bash theme={null} pip install deepidv-chain ``` ```python theme={null} from deepidv_chain import Client, verify_bundle # Sync client client = Client(api_url="https://api.deepidv.com") attestation = client.get_attestation("att_01HZ8...") print(attestation.envelope_hash) # Verify a bundle with open("./attestation.dpiv", "rb") as f: bundle_bytes = f.read() result = verify_bundle(bundle_bytes) if result.ok: print("Bundle verified.") else: print(f"Verification failed: {result.checks}") ``` An async variant is also available: ```python theme={null} from deepidv_chain import AsyncClient async with AsyncClient(api_url="https://api.deepidv.com") as client: async for event in client.stream_attestations(): print(event.attestation_id) ``` The Python SDK: * Supports **Python 3.9 through 3.13** * Sync and async clients with matching API surface * **Pydantic v2** for wire-validated types * Same TSA skip behavior as the Node SDK ## Cross-language parity Every cryptographic primitive in both SDKs — JCS canonicalization, envelope hashing, STH hashing, manifest computation — is tested against pinned constants from shared fixtures. The Node SDK and Python SDK produce byte-identical outputs against the same envelopes, on every supported runtime version. # Trust Model Source: https://docs.deepidv.com/chain-layer/trust-model What you verify independently of deepidv — and what you trust > What you trust, and what you don't, when verifying a deepidv attestation. ## What you can verify independently of deepidv * **The envelope is real.** Issuer signature → issuer public key. You don't need to trust deepidv; you need to trust the issuer's key custody, which is published on the issuer profile. * **The envelope existed at the claimed time.** Two independent RFC 3161 TSAs (DigiCert + Sectigo). Either one would have to collude with deepidv to fake a timestamp. Both colluding is the threat model only nation-states meet. * **The envelope is in the log.** Merkle inclusion proof verified against the STH root. * **The STH is authentic.** Master signature verified against the master public key on the log page. * **The STH was committed publicly.** Base L2 anchor transaction visible on Basescan, with the root hash in the event log. ## What you trust * **The issuer's key custody.** If the issuer's private key is stolen, an attacker could forge envelopes from that issuer. Deepidv mitigates this by holding all issuer keys in AWS KMS with per-tenant IAM scoping; the private key never leaves KMS. * **The chain-master key custody.** Same model — held in KMS, signs only via the STH signer service, never extracted. * **The Base L2 chain itself.** If Base L2 were to suffer a deep reorganization, on-chain anchors could be invalidated. In practice, Base inherits Ethereum L1 security after a small soak; v1 considers Base anchors final after one confirmation. ## What's intentionally NOT in the bundle * **The subject's identity.** Subject IDs are pseudonymous. The bundle proves an envelope was issued for some subject; it does not name them. * **Unrevealed label values.** If the issuer holds a label's salt, the value isn't in the bundle. The commitment is. The value can be revealed separately to a specific counterparty without going through the chain layer. * **The claim body.** Underlying verification artifacts — the actual ID photo, the actual liveness selfie, the actual document scan — are not included. They live in deepidv's encrypted storage, governed by your data agreement. * **Salt values.** Never. Not in the bundle, not on the explorer, not in any API response, not ever. # AI Agent Integration (MCP Server) Source: https://docs.deepidv.com/integrate/ai-agent-integration deepidv's hosted MCP server brings identity verification and anti-fraud workflows directly into your AI agents — no API wiring required. ## Overview Connect the power of deepidv to your AI workflows. The deepidv hosted MCP server lets your organization securely use deepidv from compatible AI clients. Once an account member authorizes access through OAuth, the client can interact with deepidv tools and perform supported actions with that member’s permissions. With this integration, you can: * Search applicants and inspect invitation status * List, inspect, create, and manage verification sessions * Create and reuse workflows with deepidv verification steps * List bank statement records and send bank statement requests * Run PEP/sanctions, title-check, and adverse-media screening tools ## Requirements You'll need the following before connecting a client: * An active deepidv account * Your deepidv login credentials * Your MFA device or authentication method, if enabled * An MCP client that supports remote HTTP servers with OAuth 2.0 Authorization Code + PKCE The hosted MCP flow now uses a shared public OAuth client. You do not create or manage a per-user OAuth client, and you do not need a `client_secret` for the standard connector flow. ## Server details | Setting | Value | | --------------------------- | -------------------------------------------------------------- | | Transport | Streamable HTTP | | Server URL | `https://mcp.deepidv.com/v1/mcp` | | Server manifest | `https://mcp.deepidv.com/mcp.json` | | Protected resource metadata | `https://mcp.deepidv.com/.well-known/oauth-protected-resource` | | OAuth client ID | `deepidv` | | Auth | OAuth 2.0 authorization code with PKCE (`S256`) | | Hosted login | deepidv email + password, with MFA when required | | Client secret | Not used for the shared public-client flow | | Local runtime required | No | ## Connection model Most clients only need the MCP server URL. deepidv exposes: * `mcp.json` discovery metadata * OAuth protected-resource metadata * a fixed shared OAuth client ID: `deepidv` In practice, the happy path is: 1. Add `https://mcp.deepidv.com/v1/mcp` as a remote MCP server. 2. Let the client discover the OAuth metadata automatically. 3. If the client explicitly asks for `client_id`, enter `deepidv`. 4. Do not provide a `client_secret`. 5. Complete the browser-based deepidv sign-in flow. If a client requires a static `client_secret` to connect a remote MCP server, that client is not compatible with deepidv's current hosted OAuth model. ## Setup by Client ### Any compatible remote MCP client Use these steps for clients that support remote MCP servers with OAuth discovery. Use `https://mcp.deepidv.com/v1/mcp` as the server URL. Use the full `/v1/mcp` path exactly as shown. Do not append any extra path after it. Most compatible clients can discover deepidv's OAuth and protected-resource metadata from the server automatically. If a client asks for a manifest URL, use `https://mcp.deepidv.com/mcp.json`. If the client asks for `client_id`, use `deepidv`. Do not enter a `client_secret`. deepidv's hosted MCP flow uses a shared public client with PKCE. Finish the browser-based OAuth flow by signing in with your deepidv email and password, then complete MFA if your account requires it. Ask your AI client to list tools, list workflows, or list verification sessions. ### Redirect URIs currently supported by the hosted OAuth server deepidv currently allows connector-native redirect URIs for these client families: * Claude web/desktop callbacks * Cursor * VS Code * ChatGPT connectors If your client uses a different fixed redirect URI, contact [deepidv support](https://www.deepidv.com/support) before rollout. ## Authentication Model deepidv uses hosted OAuth 2.0 with PKCE for MCP access. The browser approval flow authenticates the end user directly against deepidv's hosted login screens: * `client_id` is the fixed shared public client ID: `deepidv` * `client_secret` is not used * token exchange uses PKCE with `S256` * the user signs in with their deepidv email and password * MFA is enforced when required by the account Access is scoped to the authenticated deepidv user and organization. Each tool call is validated against active account state before it runs. ## Available Tools deepidv currently exposes the following MCP tools. ### Applicants | Tool | What it does | | ----------------------- | ---------------------------------------------------------------------------------- | | `search_applicants` | Find matching applicants across verification sessions and bank statement requests. | | `get_applicant` | Retrieve a consolidated applicant profile and history. | | `get_invitation_status` | Check invite delivery/open status for a session or bank statement request. | | `resend_invitation` | Resend a verification-session or bank-statement invitation. | ### Verification | Tool | What it does | | ------------------------------------ | ------------------------------------------------------------------------ | | `list_verification_sessions` | List verification sessions with filters and pagination. | | `get_session_stats` | Retrieve aggregated verification metrics and trends. | | `get_verification_session` | Retrieve full details for one verification session. | | `get_verification_session_artifacts` | Retrieve analysis outputs and resource links for a verification session. | | `get_session_timeline` | Retrieve a chronological session timeline and audit trail. | | `create_verification_session` | Create and send a new verification invitation. | | `update_verification_session_status` | Manually set a session outcome to `VERIFIED` or `REJECTED`. | | `expire_session` | Expire an active verification session and invalidate the applicant link. | ### Workflows | Tool | What it does | | ----------------- | ----------------------------------------------------------------------- | | `list_workflows` | List workflows available to the authenticated organization. | | `get_workflow` | Retrieve one workflow and its configured steps. | | `create_workflow` | Create a reusable workflow with one or more deepidv verification steps. | Current workflow step IDs exposed through MCP: * `ID_VERIFICATION` * `FACE_LIVENESS` * `AGE_ESTIMATION` * `PEP_SANCTIONS` * `ADVERSE_MEDIA` ### Financial | Tool | What it does | | ------------------------------------- | ---------------------------------------------------------------------- | | `list_bank_statements` | List bank statement records for the authenticated organization. | | `get_bank_statement_stats` | Retrieve aggregated bank statement metrics and trends. | | `get_bank_statement` | Retrieve a bank statement record and statement details when available. | | `list_bank_statements_by_external_id` | Retrieve bank statement records that match your external reference ID. | | `create_bank_statement_request` | Create and send a new bank statement request invitation. | ### Silent Screening | Tool | What it does | | ------------------------- | --------------------------------------------------------------------- | | `run_pep_sanctions_check` | Run a synchronous PEP and sanctions screening. | | `run_title_check` | Run a synchronous property title / ownership search. | | `run_adverse_media_check` | Queue an asynchronous adverse-media screening and return a `job_id`. | | `get_async_job` | Poll an async adverse-media job until it reaches `ready` or `failed`. | Some tools are read-only, while others are state-changing and may consume credits or trigger applicant delivery flows. Your client may ask for confirmation before running those actions. ## Example Prompts Once the server is connected, prompts like these should work: * "List my latest verification sessions." * "Find the applicant with email `jane@example.com`." * "Show the artifacts for verification session `SESSION_ID`." * "Create a workflow named Standard KYC with ID verification and face liveness." * "List my bank statement requests from the last 30 days." * "Run a PEP and sanctions check for Jane Doe born 1980-05-12." * "Queue an adverse media check for Jane Doe in the US, then poll the job." ## Troubleshooting ### `401 Unauthorized` or `invalid_token` * Re-run the OAuth flow * Confirm the client is pointing at `https://mcp.deepidv.com/v1/mcp` * If the client asks for `client_id`, confirm you entered `deepidv` * Confirm the deepidv user and organization are active ### The client asks for a `client_secret` deepidv's hosted MCP flow does not use a `client_secret`. If the client refuses to proceed without one, use a client that supports public OAuth + PKCE for remote MCP servers. ### Redirect URI not registered or not allowed Your MCP client may be using a redirect URI that is not currently allowed by the deepidv hosted OAuth server. Use a client with standard remote MCP OAuth support, or contact [deepidv support](https://www.deepidv.com/support). ### Opening the server URL shows an auth error That is expected. `POST /v1/mcp` is protected and requires a bearer token issued through OAuth. Use the docs page, `mcp.json`, or your client's connector flow for discovery. ### The client tries to install a local package or run a local server deepidv should connect as a hosted remote MCP server and should not require a local deepidv server package for Claude Web, Claude Desktop, Claude Code, or Cursor. ## Security Notes * All MCP requests use bearer-token authentication * Access is limited to the authenticated deepidv organization and user * Tool calls are rate-limited * State-changing tools may create records, resend applicant invites, or consume credits * Verification artifacts and returned links should be treated as sensitive customer data ## Related Links Browse the public repository for deepidv agent skills and MCP documentation. Read the public MCP server documentation and installation notes. Review REST API key authentication. This is separate from the hosted MCP OAuth flow. Contact deepidv if you need help with client compatibility or credentials. # deepAI Assistant Skill Source: https://docs.deepidv.com/integrate/deepai-assistant-skill Use the deepAI Assistant skill to help coding agents build deepidv integrations correctly with the TypeScript server SDK, REST API, and hosted MCP guidance. > Use the deepAI Assistant skill when you want an AI coding agent to help you > implement a deepidv integration correctly. Unlike the Verify skill, this > skill is not for running live verification actions on your behalf. It helps > the agent choose the right package, auth model, method namespace, and error > handling pattern while you build. ## Overview This guide is for teams using coding agents to write or debug deepidv integration code. The deepAI Assistant skill is designed to keep an agent aligned with deepidv's published integration docs, especially the backend TypeScript SDK: * `@deepidv/server` as the canonical TypeScript package * `DeepIDV` as the single SDK client entry point * `client.sessions`, `client.document`, `client.face`, `client.identity`, `client.screening`, and `client.asyncJobs` as the documented method surfaces * `x-api-key` auth for SDK and REST usage * hosted OAuth with PKCE for MCP usage Compatible agent environments currently include: * Claude Code * Codex * Cursor * Windsurf * OpenCode ## What Success Looks Like Once the skill is set up, your agent should help you: * install and initialize `@deepidv/server` correctly * keep the SDK on a trusted backend instead of suggesting browser-side usage * choose between hosted sessions, server-to-server primitives, screening, and MCP based on the integration goal * handle async adverse-media screening and typed SDK errors correctly * avoid inventing undocumented SDK namespaces or stale package names ## Before You Start Make sure you have: * a deepidv account with an active API key * an agent that supports repository-based skills * a backend TypeScript runtime if you're following the SDK path * a secure place to store the API key in your environment If you need a ready-made deepidv action skill that can call the verification API directly, use [Identity Verification Skill](/integrate/verify-skill) instead. ## Install the Skill If your agent uses the open agent skills ecosystem, install the deepAI Assistant skill with the Skills CLI: ```bash theme={null} npx skills add Deep-Identity-Inc/agent-skills@deepai-assistant -g -y ``` This command: * pulls the `deepai-assistant` skill from the public deepidv agent-skills repository * installs it at the user level with `-g` * skips the interactive confirmation prompt with `-y` After installation: 1. Restart your agent or editor if it does not reload automatically. 2. Confirm the skill is active by asking your agent to help with a deepidv TypeScript integration, or run `npx skills check` to verify the install. ## Use It With the TypeScript SDK The canonical TypeScript path uses `@deepidv/server`. ### Install the package ```bash theme={null} npm install @deepidv/server ``` ### Initialize the client ```typescript theme={null} import { DeepIDV } from '@deepidv/server'; const client = new DeepIDV({ apiKey: process.env.DEEPIDV_API_KEY!, }); ``` `@deepidv/server` is a backend-first SDK. Keep it in a trusted server, edge, or worker runtime. Do not ship it to a browser or mobile client. ### Use the documented namespaces | Namespace | Use it for | | ------------------ | ------------------------------------------ | | `client.sessions` | Hosted verification sessions | | `client.document` | Document OCR and extraction | | `client.face` | Face detection, matching, and age estimate | | `client.identity` | One-call identity verification | | `client.screening` | PEP/sanctions, title check, adverse media | | `client.asyncJobs` | Polling long-running async jobs | ## Start With the Right Flow The assistant should help you choose the integration path first, then write code against the matching docs. ### Hosted session flow Use this when you want deepidv's hosted verification UI. 1. Create a session with `client.sessions.create(...)`. 2. Send the user to `session.sessionUrl`. 3. Retrieve results with `client.sessions.retrieve(session.id)`. 4. Update the final status with `client.sessions.updateStatus(...)` if your review process requires it. ### Server-to-server flow Use this when you control the user experience and want to call primitives directly. * `client.document.scan(...)` * `client.face.detect(...)` * `client.face.compare(...)` * `client.face.estimateAge(...)` * `client.identity.verify(...)` ### Screening flow Use this routing: * `client.screening.pepSanctions(...)` for synchronous watchlist screening * `client.screening.titleCheck(...)` for synchronous title or ownership search * `client.screening.adverseMedia(...)` for async adverse-media screening * `client.asyncJobs.get(jobId)` or the returned handle for polling `pepSanctions` and `titleCheck` are synchronous. `adverseMedia` is async and returns a job handle rather than an immediate final result. ## Keep the Auth Models Separate ### TypeScript SDK and REST API Use `x-api-key` authentication. * REST base URL: `https://api.deepidv.com/v1` * SDK default `baseUrl`: `https://api.deepidv.com` ### Hosted MCP Use the hosted MCP endpoint: * server URL: `https://mcp.deepidv.com/v1/mcp` * `client_id`: `deepidv` only if the client explicitly asks for it * `client_secret`: not used * sign-in: deepidv email and password, with MFA when required Do not tell developers to reuse an API key as a hosted MCP `client_secret`. ## Avoid the Most Common Mistakes The assistant skill is useful when you want the agent to avoid stale or invented guidance. Watch for these mistakes: * suggesting `@deepidv/sdk` instead of `@deepidv/server` * treating the server SDK as a browser package * inventing undocumented SDK namespaces such as `client.workflows.*` * forgetting that adverse-media screening is async * retrying `pepSanctions` or `titleCheck` aggressively after a `503` * mixing REST API key auth with hosted MCP OAuth instructions ## Prompt Patterns That Work Well Good examples: * "Help me initialize `@deepidv/server` in a Node.js backend." * "Show me how to create a hosted verification session and read the result." * "Help me wire `client.identity.verify()` into our onboarding flow." * "How should I handle `RateLimitError` and `TimeoutError` from the SDK?" * "Show me the correct pattern for async adverse-media screening." * "Help me connect deepidv MCP in a client that supports remote OAuth." ## Choose the Right Integration Path **Use the deepAI Assistant skill when:** * you want a coding agent to help you implement or debug deepidv integration code * you want the agent to stay aligned with the published TypeScript SDK docs * you need help picking between SDK, REST, and MCP integration patterns **Use the Verify skill when:** * you want a compatible AI agent to call deepidv verification and screening APIs directly * you want skill-driven API execution rather than coding assistance **Use the MCP server when:** * your client supports remote MCP * you want hosted tools with OAuth-based access * you prefer structured remote tools over direct API integration code ## Related Links Read the public assistant skill definition and guidance. Use the canonical @deepidv/server docs as the source of truth for TypeScript. Use the Verify skill when you want direct AI-driven API actions. Use the hosted MCP server if your client supports remote MCP. # Authentication Source: https://docs.deepidv.com/integrate/sdks/typescript/server/authentication How the @deepidv/server SDK authenticates with an x-api-key header — env vars, key redaction, rotation, and custom fetch for proxies and mTLS. The SDK authenticates every request to `api.deepidv.com` with an `x-api-key` header. This guide covers setup, security, and advanced configurations. Need a key? Generate one from the [API Authentication](/authentication) page. Keys are owned by a user in your organization and carry that user's permissions. ## Basic setup `apiKey` is the only required configuration field: ```typescript theme={null} import { DeepIDV } from '@deepidv/server'; const client = new DeepIDV({ apiKey: process.env.DEEPIDV_API_KEY!, }); ``` Every HTTP request then automatically includes: ```http theme={null} x-api-key: your-api-key-here Accept: application/json ``` ## Security best practices ### Use environment variables Never hardcode API keys in source control: ```typescript theme={null} // Bad — key committed to source const client = new DeepIDV({ apiKey: 'sk_live_abc123...' }); // Good — key from the environment const client = new DeepIDV({ apiKey: process.env.DEEPIDV_API_KEY! }); ``` ### Never log the full key The SDK automatically redacts API keys in error output. If you need to log which key was used, log the redacted form from `AuthenticationError`: ```typescript theme={null} import { AuthenticationError } from '@deepidv/server'; try { await client.document.scan({ image }); } catch (err) { if (err instanceof AuthenticationError) { // Safe to log — only the last 4 characters are shown console.error(`Auth failed with key: ${err.redactedKey}`); // → "Auth failed with key: ****abcd" } } ``` ### Rotate keys If a key is compromised: 1. Generate a new API key in the deepidv dashboard. 2. Update your environment variable. 3. Revoke the old key. No code changes are needed — the key is externalized. ### Use a key per environment ```bash theme={null} # .env.development DEEPIDV_API_KEY=sk_test_dev_... # .env.production DEEPIDV_API_KEY=sk_live_prod_... ``` ## API key redaction When an `AuthenticationError` is thrown, the SDK stores only a redacted version of the key. When serialized with `JSON.stringify()`, the full key is **never** included: ```typescript theme={null} JSON.stringify(authError); // { // "type": "AuthenticationError", // "message": "Invalid API key", // "status": 401, // "redactedKey": "****abcd" // } ``` This makes it safe to forward SDK errors to Sentry, Datadog, or any error-tracking service. See [Error Handling](/integrate/sdks/typescript/server/error-handling) for the full error model. ## Custom fetch for proxy / mTLS To route requests through a proxy or attach mutual-TLS certificates, provide a custom `fetch` implementation: ```typescript theme={null} import { DeepIDV } from '@deepidv/server'; import { ProxyAgent } from 'undici'; const dispatcher = new ProxyAgent('https://proxy.internal:8080'); const client = new DeepIDV({ apiKey: process.env.DEEPIDV_API_KEY!, fetch: (url, init) => fetch(url, { ...init, dispatcher }), }); ``` ### Cloudflare Workers service binding ```typescript theme={null} const client = new DeepIDV({ apiKey: env.DEEPIDV_API_KEY, fetch: env.DEEPIDV_SERVICE.fetch.bind(env.DEEPIDV_SERVICE), }); ``` See [Configuration](/integrate/sdks/typescript/server/configuration) for more `fetch` injection patterns. ## Validation The SDK validates the API key synchronously at construction time, before any network call: ```typescript theme={null} // Throws ValidationError — apiKey cannot be empty new DeepIDV({ apiKey: '' }); // Throws ValidationError — apiKey is required new DeepIDV({} as any); ``` This surfaces configuration mistakes immediately rather than on the first request. # Configuration Source: https://docs.deepidv.com/integrate/sdks/typescript/server/configuration Every @deepidv/server client option — baseUrl, timeouts, retries, and custom fetch — with defaults, retry/timeout behavior, and validation rules. Every option accepted by the `DeepIDV` constructor, with defaults and behavior. ## Options | Option | Type | Default | Description | | ------------------- | -------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------ | | `apiKey` | `string` | **(required)** | API key for `x-api-key` authentication. Must be non-empty. | | `baseUrl` | `string` | `"https://api.deepidv.com"` | API base URL. Override for staging/testing environments. | | `timeout` | `number` | `30000` (30s) | Per-attempt timeout for API requests, in milliseconds. Each retry attempt gets its own fresh timer. | | `uploadTimeout` | `number` | `120000` (2 min) | Per-attempt timeout for S3 file uploads, in milliseconds. Separate from `timeout` because uploads are larger and slower. | | `maxRetries` | `number` | `3` | Maximum retry attempts for `429` and `5xx` responses. Set to `0` to disable retries. | | `initialRetryDelay` | `number` | `500` (ms) | Initial delay for exponential-backoff calculation. | | `fetch` | `typeof fetch` | `globalThis.fetch` | Custom fetch implementation. Useful for proxies, mTLS, service bindings, and testing. | ## Retry & timeout behavior **Retries.** The SDK automatically retries only **transient** failures — HTTP `429` (rate limited) and `5xx` (server errors). It never retries `4xx` client errors, since those indicate a bug in the request that a retry won't fix. After `maxRetries` attempts are exhausted, the original typed error (e.g. `RateLimitError`) is thrown. A few methods opt out of retries deliberately. `screening.pepSanctions` and `screening.titleCheck` are sent with `maxRetries: 0` because the server bounds an un-cancellable upstream and returns `503` on breach without billing — an immediate retry would just hit the same slow path. See [Screening](/integrate/sdks/typescript/server/reference/screening). **Backoff.** Delay between attempts uses exponential backoff with jitter: `random(0, initialRetryDelay * 2^attempt)`. When a `429` carries a `Retry-After` header, that value is respected. **Timeouts.** `timeout` and `uploadTimeout` are **per attempt**, not total — every retry gets a fresh timer. A single attempt exceeding its budget throws a [`TimeoutError`](/integrate/sdks/typescript/server/error-handling). You can observe retries and timing in real time via the [`retry` and `response` events](/integrate/sdks/typescript/server/reference/async-jobs-events). ## Examples ### Minimal ```typescript theme={null} const client = new DeepIDV({ apiKey: process.env.DEEPIDV_API_KEY!, }); ``` ### Full configuration ```typescript theme={null} const client = new DeepIDV({ apiKey: process.env.DEEPIDV_API_KEY!, baseUrl: 'https://api-staging.deepidv.com', timeout: 15_000, uploadTimeout: 60_000, maxRetries: 5, initialRetryDelay: 1_000, fetch: customFetch, }); ``` ### No retries ```typescript theme={null} const client = new DeepIDV({ apiKey: process.env.DEEPIDV_API_KEY!, maxRetries: 0, }); ``` ### Fast timeout (low-latency use case) ```typescript theme={null} const client = new DeepIDV({ apiKey: process.env.DEEPIDV_API_KEY!, timeout: 5_000, // 5s for API calls uploadTimeout: 30_000, // 30s for uploads maxRetries: 1, // only one retry }); ``` ### Custom fetch with a proxy ```typescript theme={null} import { ProxyAgent } from 'undici'; const dispatcher = new ProxyAgent('https://proxy.internal:8080'); const client = new DeepIDV({ apiKey: process.env.DEEPIDV_API_KEY!, fetch: (url, init) => fetch(url, { ...init, dispatcher }), }); ``` ### Cloudflare Workers service binding ```typescript theme={null} const client = new DeepIDV({ apiKey: env.DEEPIDV_API_KEY, fetch: env.DEEPIDV_SERVICE.fetch.bind(env.DEEPIDV_SERVICE), }); ``` ## Validation All options are validated synchronously at construction time with a Zod schema (`DeepIDVConfigSchema`). Validation failures throw a [`ValidationError`](/integrate/sdks/typescript/server/error-handling) before any network call: | Validation | Error message | | ------------------------------------ | ------------------------------------------- | | `apiKey` missing or empty | `apiKey is required` | | `baseUrl` not a valid URL | `Invalid url` | | `timeout` not positive | `Number must be greater than 0` | | `uploadTimeout` not positive | `Number must be greater than 0` | | `maxRetries` negative or non-integer | `Number must be greater than or equal to 0` | | `initialRetryDelay` not positive | `Number must be greater than 0` | ## Resolved configuration After construction, all defaults are applied. The internal resolved shape has every field required: ```typescript theme={null} interface ResolvedConfig { apiKey: string; // from user baseUrl: string; // "https://api.deepidv.com" (trailing slash stripped) timeout: number; // 30000 uploadTimeout: number; // 120000 maxRetries: number; // 3 initialRetryDelay: number; // 500 fetch: typeof fetch; // globalThis.fetch } ``` # Error Handling Source: https://docs.deepidv.com/integrate/sdks/typescript/server/error-handling Every error the @deepidv/server SDK throws is a typed DeepIDVError subclass — the full error catalog, a decision tree, retry semantics, and structured logging. Every error thrown by the SDK is an instance of `DeepIDVError` or one of its subclasses. No untyped exceptions escape the public API, so you can branch on `instanceof` and handle each case precisely. ## Error hierarchy ```mermaid theme={null} classDiagram class Error { +message: string +stack: string +cause: unknown } class DeepIDVError { +status: number | undefined +code: string | undefined +response: RawResponse | undefined +toJSON() Record } class ValidationError { +status: 400 } class InsufficientFundsError { +status: 402 } class AuthenticationError { +redactedKey: string
+status: 401 } class AuthorizationError { +status: 403 } class NotFoundError { +status: 404 } class RateLimitError { +retryAfter?: number
+status: 429 } class ServiceUnavailableError { +status: 503 } class NetworkError { +status: undefined } class TimeoutError { +status: undefined } class AdverseMediaFailedError { +jobId?: string } class PollTimeoutError { +timeoutMs?: number
+jobId?: string } Error <|-- DeepIDVError DeepIDVError <|-- ValidationError DeepIDVError <|-- InsufficientFundsError DeepIDVError <|-- AuthenticationError DeepIDVError <|-- AuthorizationError DeepIDVError <|-- NotFoundError DeepIDVError <|-- RateLimitError DeepIDVError <|-- ServiceUnavailableError DeepIDVError <|-- NetworkError DeepIDVError <|-- TimeoutError DeepIDVError <|-- AdverseMediaFailedError DeepIDVError <|-- PollTimeoutError ``` All error classes are exported from `@deepidv/server`: ```typescript theme={null} import { DeepIDVError, ValidationError, AuthenticationError, AuthorizationError, InsufficientFundsError, NotFoundError, RateLimitError, ServiceUnavailableError, NetworkError, TimeoutError, AdverseMediaFailedError, PollTimeoutError, } from '@deepidv/server'; ``` ## Error catalog ### `DeepIDVError` (base class) The base class for all SDK errors. Carries HTTP context when available. | Field | Type | Description | | ---------- | -------------------------- | ------------------------------------------------------------ | | `message` | `string` | Human-readable error description | | `status` | `number \| undefined` | HTTP status code (undefined for network/timeout errors) | | `code` | `string \| undefined` | Machine-readable error code from the API | | `response` | `RawResponse \| undefined` | Raw HTTP response with `status`, `headers`, and `body` | | `cause` | `unknown` | Original error that triggered this one (`Error.cause` chain) | Every subclass implements `toJSON()` for structured logging (see below). ### `ValidationError` — HTTP 400 Thrown on HTTP `400`, **or before any network call** when input fails Zod schema validation. The message names the offending field. ```typescript theme={null} try { // Missing required 'image' field await client.document.scan({} as any); } catch (err) { if (err instanceof ValidationError) { console.error(err.message); // "Required at 'image'" } } ``` ### `AuthenticationError` — HTTP 401 Your API key is invalid, expired, or missing. Carries `redactedKey` (last 4 characters only) — safe to log. ```typescript theme={null} catch (err) { if (err instanceof AuthenticationError) { console.error(`Invalid API key: ${err.redactedKey}`); // "****abcd" } } ``` ### `InsufficientFundsError` — HTTP 402 The funds / subscription gate failed — your account doesn't have enough balance for the requested operation. ### `AuthorizationError` — HTTP 403 The API key is valid but lacks permission for the requested resource. ### `NotFoundError` — HTTP 404 The requested resource doesn't exist — e.g. an unknown session ID, or an async job that has been pruned by TTL. ### `RateLimitError` — HTTP 429 Thrown **after all retries are exhausted**. The SDK already retried with exponential backoff, so you've hit a sustained rate limit. `retryAfter` (seconds, from the `Retry-After` header) tells you how long to wait. ```typescript theme={null} catch (err) { if (err instanceof RateLimitError) { console.error(`Rate limited. Retry after ${err.retryAfter}s`); } } ``` ### `ServiceUnavailableError` — HTTP 503 A transient upstream timeout. Some screening methods (`pepSanctions`, `titleCheck`) surface this immediately without retrying because the server bounds an un-cancellable upstream — back off and try again later. ### `NetworkError` A network-level failure: DNS resolution failure, connection refused, socket hangup. `status` is `undefined`. ### `TimeoutError` A single attempt exceeded the configured `timeout` (API requests) or `uploadTimeout` (uploads). Consider increasing the relevant timeout in [configuration](/integrate/sdks/typescript/server/configuration), or retrying. ### `AdverseMediaFailedError` An [adverse-media](/integrate/sdks/typescript/server/reference/screening#adversemediainput) async job terminated in the `failed` state. Carries the `jobId` that failed. ### `PollTimeoutError` `AdverseMediaHandle.wait()` exceeded its `timeoutMs` budget before the job completed. This does **not** mean the job died — it may still complete server-side. Carries `timeoutMs` and `jobId`; resume by polling [`client.asyncJobs.get(jobId)`](/integrate/sdks/typescript/server/reference/async-jobs-events). ## Retry semantics The SDK retries only **transient** failures — HTTP `429` and `5xx` — up to `maxRetries` times (default `3`) with exponential backoff and jitter. It **never** retries `4xx` client errors, since those are caller bugs a retry won't fix. By the time a `RateLimitError` reaches your `catch` block, the retries are already spent. See [Configuration → retry & timeout behavior](/integrate/sdks/typescript/server/configuration#retry--timeout-behavior) for tuning, and note that `screening.pepSanctions` / `screening.titleCheck` deliberately opt out of retries. ## Error decision tree ```mermaid theme={null} flowchart TD A[Caught an error] --> B{instanceof?} B -->|ValidationError| E["Check your input
err.message names the field"] B -->|AuthenticationError| C["Check your API key
err.redactedKey shows last 4"] B -->|AuthorizationError| K["Key lacks permission
for this resource"] B -->|InsufficientFundsError| L["Top up account balance
or subscription"] B -->|NotFoundError| M["Resource does not exist
check the ID"] B -->|RateLimitError| D["Over quota — SDK already retried
err.retryAfter has wait time"] B -->|ServiceUnavailableError| N["Transient upstream timeout
back off and retry"] B -->|TimeoutError| F["Too slow — increase timeout
or uploadTimeout"] B -->|NetworkError| G["Connection failed
check internet, DNS, baseUrl"] B -->|DeepIDVError| H["Other API error
check err.status and err.response.body"] ``` ## Structured logging with `toJSON()` Every `DeepIDVError` implements `toJSON()`, so `JSON.stringify()` produces a clean, log-safe object. The full API key is never serialized: ```typescript theme={null} try { await client.sessions.retrieve('invalid-id'); } catch (err) { if (err instanceof DeepIDVError) { console.log(JSON.stringify(err)); // { // "type": "DeepIDVError", // "message": "Not Found", // "status": 404, // "code": "not_found" // } } } ``` This makes it safe to forward errors directly to Sentry, Datadog, or any error-tracking service. ## `Error.cause` chaining All SDK errors preserve the original cause via the standard `Error.cause` property — useful for debugging the exact failure at each layer: ```typescript theme={null} catch (err) { if (err instanceof DeepIDVError) { console.error('SDK error:', err.message); console.error('Caused by:', err.cause); // original fetch error, ZodError, etc. } } ``` ## Recommended try/catch pattern ```typescript theme={null} import { DeepIDV, ValidationError, AuthenticationError, RateLimitError, TimeoutError, NetworkError, DeepIDVError, } from '@deepidv/server'; try { const result = await client.document.scan({ image: buffer }); console.log(result.fullName); } catch (err) { if (err instanceof ValidationError) { console.error('Invalid input:', err.message); // fix your code } else if (err instanceof AuthenticationError) { console.error('Auth failed:', err.redactedKey); // check config } else if (err instanceof RateLimitError) { console.error(`Rate limited, retry after ${err.retryAfter}s`); // back off } else if (err instanceof TimeoutError) { console.error('Timed out'); // retry or raise the timeout } else if (err instanceof NetworkError) { console.error('Network error:', err.message); // retry later } else if (err instanceof DeepIDVError) { console.error(`API error ${err.status}: ${err.message}`); // other API error } else { throw err; // not from the SDK } } ``` # Server SDK Overview Source: https://docs.deepidv.com/integrate/sdks/typescript/server/overview @deepidv/server is a typed, backend-first TypeScript SDK for the deepidv identity verification API — sessions, document and face primitives, identity verification, and silent screening. `@deepidv/server` is the official backend-first TypeScript SDK for the [deepidv](https://api.deepidv.com) identity verification API. It wraps every REST endpoint in a typed, autocompleting client so you can create hosted verification sessions, scan documents, compare faces, run full identity verification, and screen individuals — without hand-writing HTTP, auth, retries, or file uploads. This is the **server** SDK — it holds your API key and is meant to run in a trusted backend (Node.js, Deno, Bun, or an edge runtime). Never ship it to a browser or mobile client. ## Why use the SDK * **Typed end to end.** Every input and result is a TypeScript type derived from a Zod schema — your editor autocompletes fields and the compiler catches mistakes before runtime. * **Thin client.** The SDK validates inputs, manages `x-api-key` auth, retries transient failures, and orchestrates presigned file uploads. All verification logic runs server-side at `api.deepidv.com`. * **Web-standards-first.** It uses only native web APIs (`fetch`, `AbortController`, `ReadableStream`, `Uint8Array`), which is what lets it run across Node, Deno, Bun, and Cloudflare Workers. * **Single dependency.** The only production dependency is [zod](https://zod.dev) for runtime input validation. ## Install ```bash npm theme={null} npm install @deepidv/server ``` ```bash pnpm theme={null} pnpm add @deepidv/server ``` ```bash yarn theme={null} yarn add @deepidv/server ``` ```bash bun theme={null} bun add @deepidv/server ``` The package ships dual ESM + CJS builds with bundled TypeScript declarations, so `import` and `require` both work with no extra configuration. ## The `DeepIDV` client The `DeepIDV` class is the single entry point. Construct it once with your API key and reuse it — the constructor is cheap and each instance is independent. ```typescript theme={null} import { DeepIDV } from '@deepidv/server'; const client = new DeepIDV({ apiKey: process.env.DEEPIDV_API_KEY!, }); ``` See [Authentication](/integrate/sdks/typescript/server/authentication) for API key setup and [Configuration](/integrate/sdks/typescript/server/configuration) for all client options. ## Namespace map Methods are grouped by domain. Each namespace maps onto a part of the REST API: | Namespace | Methods | What it does | REST reference | | ------------------ | -------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------ | | `client.sessions` | `create`, `retrieve`, `list`, `updateStatus` | Hosted verification sessions | [Sessions](/api-reference/sessions/create-session) | | `client.document` | `scan` | Document OCR / data extraction | [Document Scan](/api-reference/server-to-server/document-scan) | | `client.face` | `detect`, `compare`, `estimateAge` | Face detection, matching, age estimate | [Face](/api-reference/server-to-server/face-detect) | | `client.identity` | `verify` | Orchestrated document + face verification | [Identity Verify](/api-reference/server-to-server/identity-verify) | | `client.screening` | `pepSanctions`, `adverseMedia`, `titleCheck` | Silent screening (PEP/sanctions, adverse media, title check) | [Silent Screening](/api-reference/silent-screening/pep-sanctions) | | `client.asyncJobs` | `get` | Poll long-running async jobs | [Get Async Job](/api-reference/async-jobs/get-async-job) | The client also exposes `client.on(event, listener)` for lifecycle events — see [Async Jobs & Events](/integrate/sdks/typescript/server/reference/async-jobs-events). ## Three service tiers | Tier | Pattern | Methods | | ----------------- | ----------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | | **Synchronous** | One call, one result. Image in, structured data out. | `document.scan`, `face.detect`, `face.compare`, `face.estimateAge`, `screening.pepSanctions`, `screening.titleCheck` | | **Orchestrated** | One call, multiple operations coordinated server-side. | `identity.verify` | | **Session-based** | Create a session, the user completes steps, you retrieve results. | `sessions.create`, `sessions.retrieve` | | **Async** | Kick off a job, poll for the result. | `screening.adverseMedia`, `asyncJobs.get` | ## Supported runtimes The SDK runs anywhere with native `fetch`: | Feature | Node.js 18+ | Deno | Bun | Cloudflare Workers | | ----------------------------- | ----------- | ---- | --- | ------------------ | | All API methods | Yes | Yes | Yes | Yes | | File path input | Yes | Yes | Yes | **No** | | `Uint8Array` / `Buffer` input | Yes | Yes | Yes | Yes | | `ReadableStream` input | Yes | Yes | Yes | Yes | | Base64 / data URL input | Yes | Yes | Yes | Yes | | ESM import | Yes | Yes | Yes | Yes | | CJS require | Yes | N/A | Yes | N/A | Node.js 18 is the minimum because it's the first LTS with stable native `fetch`. On edge runtimes there is no filesystem, so pass a `Uint8Array` or `ReadableStream` instead of a file path — see [Configuration](/integrate/sdks/typescript/server/configuration) for proxy, mTLS, and service-binding `fetch` setups. ## Next steps Install, initialize, and make your first call in two minutes. API key setup, env vars, redaction, and custom fetch. The hosted flow: create a session, let the user verify, read results. Build a custom pipeline from the document and face primitives. ## Resources * **npm:** [`@deepidv/server`](https://www.npmjs.com/package/@deepidv/server) * **REST API reference:** [docs.deepidv.com/api-reference](/api-reference/sessions/create-session) * **Admin console:** [app.deepidv.com](https://app.deepidv.com) # Quickstart Source: https://docs.deepidv.com/integrate/sdks/typescript/server/quickstart Install @deepidv/server, initialize the client, and get identity verification results in under two minutes. Get from zero to a verification result in under two minutes. ## 1. Install ```bash npm theme={null} npm install @deepidv/server ``` ```bash pnpm theme={null} pnpm add @deepidv/server ``` ```bash yarn theme={null} yarn add @deepidv/server ``` ```bash bun theme={null} bun add @deepidv/server ``` ## 2. Initialize the client Create one client and reuse it across requests. The API key is read from an environment variable — never hardcode it. ```typescript theme={null} import { DeepIDV } from '@deepidv/server'; const client = new DeepIDV({ apiKey: process.env.DEEPIDV_API_KEY!, }); ``` Get an API key from the [API Authentication](/authentication) page in your deepidv account. ## 3. Create a verification session The fastest path to a verified identity — create a hosted session and send the user to the returned URL: ```typescript theme={null} const session = await client.sessions.create({ firstName: 'Jane', lastName: 'Doe', email: 'jane@example.com', phone: '+15551234567', }); console.log(session.sessionUrl); // → "https://verify.deepidv.com/session/abc123" // Send this URL to your user ``` When the user finishes, retrieve the result with `client.sessions.retrieve(session.id)`. See the [Session Verification guide](/integrate/sdks/typescript/server/session-verification) for the full flow. ## Prefer to build your own flow? If you handle the applicant UI yourself, call the primitives directly. ### Scan a document ```typescript theme={null} import { readFileSync } from 'node:fs'; const result = await client.document.scan({ image: readFileSync('drivers-license.jpg'), }); console.log(result.fullName); // "Jane Doe" console.log(result.dateOfBirth); // "1990-01-15" console.log(result.documentNumber); // "D1234567" console.log(result.confidence); // 0.97 ``` ### Compare two faces ```typescript theme={null} const match = await client.face.compare({ source: readFileSync('id-photo.jpg'), target: readFileSync('selfie.jpg'), }); console.log(match.isMatch); // true console.log(match.confidence); // 94 (0–100 scale) ``` ### Full identity verification Document scan + face detection + face comparison in a single call: ```typescript theme={null} const verification = await client.identity.verify({ documentImage: readFileSync('passport.jpg'), faceImage: readFileSync('selfie.jpg'), }); console.log(verification.verified); // true console.log(verification.overallConfidence); // 96 console.log(verification.document.fullName); // "Jane Doe" console.log(verification.faceMatch.isMatch); // true ``` ## Next steps API key setup and security best practices. Timeouts, retries, base URL, and custom fetch. Build a custom verification pipeline. Typed errors, the decision tree, and retry semantics. # Reference: Async Jobs & Events Source: https://docs.deepidv.com/integrate/sdks/typescript/server/reference/async-jobs-events client.asyncJobs.get for polling long-running jobs, and client.on(...) lifecycle events for observability and APM integration. This page covers two cross-cutting surfaces: `client.asyncJobs` for polling long-running jobs, and `client.on(...)` for subscribing to SDK lifecycle events. ## Async Jobs Endpoints that kick off long-running work — currently [adverse media screening](/integrate/sdks/typescript/server/reference/screening#adversemediainput) — return a `jobId` immediately. Most callers use the typed handle from the originating method (`screening.adverseMedia(...)`), but `client.asyncJobs.get(jobId)` lets you resume polling a `jobId` you persisted earlier (for example, across process restarts). ### `get(jobId)` ```typescript theme={null} get(jobId: string): Promise ``` Fetch the current state of an async job by ID. | Parameter | Type | Required | Description | | --------- | -------- | -------- | --------------------------------------------------- | | `jobId` | `string` | Yes | Server-side job ID returned by the originating call | **Returns** `AsyncJobSnapshot` — a discriminated union on `status`. `result` is typed as `unknown` because the async-jobs surface is service-agnostic; re-parse it against the originating service's schema to narrow it. ```typescript theme={null} type AsyncJobSnapshot = | { jobId: string; createdAt: number; updatedAt: string; status: 'pending' } | { jobId: string; createdAt: number; updatedAt: string; status: 'processing' } | { jobId: string; createdAt: number; updatedAt: string; status: 'ready'; result: unknown } | { jobId: string; createdAt: number; updatedAt: string; status: 'failed'; error: string }; ``` `createdAt` is epoch **seconds** (a number); `updatedAt` is an ISO 8601 string. `ready` and `failed` are the terminal states. **Throws** `ValidationError` (empty `jobId`), `AuthenticationError` (401), `AuthorizationError` (403), `NotFoundError` (404 — unknown ID or pruned by TTL), `DeepIDVError`. ```typescript theme={null} const snapshot = await client.asyncJobs.get('job_abc123'); if (snapshot.status === 'ready') { console.log(snapshot.result); // unknown — re-parse with the service's schema } else if (snapshot.status === 'failed') { console.error(snapshot.error); } else { console.log(`Still ${snapshot.status}…`); } ``` **See also:** REST [Get Async Job](/api-reference/async-jobs/get-async-job). *** ## Events The SDK emits lifecycle events for observability, logging, and APM integration. Subscribe with `client.on(event, listener)`. ### `on(event, listener)` ```typescript theme={null} on( event: K, listener: (payload: SDKEventMap[K]) => void, ): () => void ``` Subscribe to an event. **Returns** an unsubscribe function — call it to remove the listener. ```typescript theme={null} const unsubscribe = client.on('request', ({ method, url }) => { console.log(`→ ${method} ${url}`); }); // Later unsubscribe(); ``` For one-shot behavior, unsubscribe from inside the listener: ```typescript theme={null} const unsub = client.on('response', (payload) => { console.log('First response:', payload.status); unsub(); }); ``` ### Event map ```typescript theme={null} type SDKEventMap = { request: { method: string; url: string }; response: { status: number; url: string; durationMs: number }; retry: { attempt: number; delayMs: number; error: unknown }; error: { error: unknown }; warning: { message: string; error: unknown }; 'upload:start': { url: string; bytes: number; contentType: string }; 'upload:complete': { url: string; contentType: string }; }; ``` | Event | Fired | Payload | | ----------------- | ----------------------------------------------- | ----------------------------- | | `request` | Before each HTTP request | `{ method, url }` | | `response` | After each successful response | `{ status, url, durationMs }` | | `retry` | Before each retry sleep | `{ attempt, delayMs, error }` | | `error` | When all retries are exhausted, before throwing | `{ error }` | | `warning` | When a listener itself throws | `{ message, error }` | | `upload:start` | Before each S3 PUT upload | `{ url, bytes, contentType }` | | `upload:complete` | After each S3 PUT upload | `{ url, contentType }` | ### Execution model Events dispatch **synchronously** within the request flow, in registration order. The SDK does not await listener return values. Therefore: * Listeners **should not** perform heavy blocking work — use async logging instead. * Listeners **cannot** modify the request or response — payloads are read-only. ### Listener error safety If a listener throws, the SDK catches the exception, emits a `warning` event with the details, and continues processing normally — a broken listener never crashes a request. If a `warning` listener itself throws, that exception is silently swallowed to prevent infinite recursion. ```typescript theme={null} // This broken listener won't crash your application: client.on('request', () => { throw new Error('oops'); }); // The SDK catches it and emits: // warning: { message: "Listener error in 'request'", error: Error('oops') } ``` ### APM integration example ```typescript theme={null} import { DeepIDV } from '@deepidv/server'; const client = new DeepIDV({ apiKey: process.env.DEEPIDV_API_KEY! }); client.on('request', ({ method, url }) => { tracer.trace('deepidv.request', { resource: `${method} ${url}` }); }); client.on('response', ({ status, durationMs }) => { metrics.histogram('deepidv.latency', durationMs); metrics.increment('deepidv.requests', { status: String(status) }); }); client.on('retry', ({ attempt }) => { metrics.increment('deepidv.retries', { attempt: String(attempt) }); }); client.on('error', ({ error }) => { errorTracker.captureException(error); }); ``` # Reference: Document, Face & Identity Source: https://docs.deepidv.com/integrate/sdks/typescript/server/reference/document-face-identity client.document, client.face, and client.identity reference — scan, detect, compare, estimateAge, and verify, with inline types and runnable examples. The server-to-server primitives. Access them via `client.document`, `client.face`, and `client.identity`. Each image parameter accepts a `FileInput`; the SDK handles presigned upload internally. For the full request/response schemas, follow the REST links per method. ```typescript theme={null} // Accepted input for every image parameter on this page type FileInput = Uint8Array | ReadableStream | string; // Uint8Array — raw bytes (Node Buffer extends Uint8Array) // ReadableStream — streaming input (materialized before upload) // string — data URL, base64, or file path (file path not supported on edge runtimes) type DocumentType = 'passport' | 'drivers_license' | 'national_id' | 'auto'; ``` *** ## Document ### `scan(input)` ```typescript theme={null} scan(input: DocumentScanInput): Promise ``` Scan a document image and extract structured OCR data. | Parameter | Type | Required | Default | Description | | -------------------- | -------------- | -------- | -------- | ------------------ | | `input.image` | `FileInput` | Yes | — | Document image | | `input.documentType` | `DocumentType` | No | `'auto'` | Document type hint | **Returns** `DocumentScanResult`: ```typescript theme={null} interface DocumentScanResult { documentType: string; fullName: string; firstName: string; lastName: string; dateOfBirth: string; gender: string; nationality: string; documentNumber: string; expirationDate: string; issuingCountry: string; address?: string; mrzData?: string; faceImage?: string; // extracted face, base64 rawFields: Record; confidence: number; // 0–1 } ``` **Throws** `ValidationError`, `AuthenticationError`, `RateLimitError`, `NetworkError`, `TimeoutError`, `DeepIDVError`. ```typescript theme={null} import { readFileSync } from 'node:fs'; const result = await client.document.scan({ image: readFileSync('passport.jpg'), documentType: 'passport', }); console.log(`${result.fullName} — expires ${result.expirationDate}`); ``` **See also:** REST [Document Scan](/api-reference/server-to-server/document-scan). *** ## Face ### `detect(input)` ```typescript theme={null} detect(input: FaceDetectInput): Promise ``` Detect a face in an image. | Parameter | Type | Required | Description | | ------------- | ----------- | -------- | ---------------- | | `input.image` | `FileInput` | Yes | Image to analyze | **Returns** `FaceDetectResult`: ```typescript theme={null} interface FaceDetectResult { faceDetected: boolean; confidence: number; // 0–1 boundingBox?: { top: number; left: number; width: number; height: number }; landmarks?: Array<{ type: string; x: number; y: number }>; } ``` ```typescript theme={null} const result = await client.face.detect({ image: readFileSync('photo.jpg') }); if (result.faceDetected) { console.log(`Confidence: ${result.confidence}`); } ``` **See also:** REST [Face Detect](/api-reference/server-to-server/face-detect). ### `compare(input)` ```typescript theme={null} compare(input: FaceCompareInput): Promise ``` Compare two face images. Both upload in parallel via batch presign. | Parameter | Type | Required | Description | | -------------- | ----------- | -------- | ------------------------ | | `input.source` | `FileInput` | Yes | Reference image | | `input.target` | `FileInput` | Yes | Image to compare against | **Returns** `FaceCompareResult`: ```typescript theme={null} interface FaceCompareResult { isMatch: boolean; // confidence >= threshold confidence: number; // 0–100 threshold: number; // 0–100 sourceFaceDetected: boolean; targetFaceDetected: boolean; } ``` ```typescript theme={null} const result = await client.face.compare({ source: readFileSync('id-photo.jpg'), target: readFileSync('selfie.jpg'), }); console.log(result.isMatch ? 'Same person' : 'Different people'); ``` **See also:** REST [Face Compare](/api-reference/server-to-server/face-compare). ### `estimateAge(input)` ```typescript theme={null} estimateAge(input: FaceEstimateAgeInput): Promise ``` Estimate age and gender from a face image. | Parameter | Type | Required | Description | | ------------- | ----------- | -------- | ---------------- | | `input.image` | `FileInput` | Yes | Image to analyze | **Returns** `FaceEstimateAgeResult`: ```typescript theme={null} interface FaceEstimateAgeResult { estimatedAge: number; ageRange: { low: number; high: number }; gender: 'male' | 'female'; genderConfidence: number; // 0–1 faceDetected: boolean; } ``` ```typescript theme={null} const result = await client.face.estimateAge({ image: readFileSync('photo.jpg') }); console.log(`Age: ${result.estimatedAge} (${result.ageRange.low}–${result.ageRange.high})`); ``` **See also:** REST [Face Estimate Age](/api-reference/server-to-server/face-estimate-age). *** ## Identity ### `verify(input)` ```typescript theme={null} verify(input: IdentityVerifyInput): Promise ``` Full identity verification: document scan + face detection + face comparison in one orchestrated call. Both images upload in parallel. | Parameter | Type | Required | Default | Description | | --------------------- | -------------- | -------- | ----------- | ------------------- | | `input.documentImage` | `FileInput` | Yes | — | Document image | | `input.faceImage` | `FileInput` | Yes | — | Selfie / face image | | `input.documentType` | `DocumentType` | No | auto-detect | Document type hint | **Returns** `IdentityVerificationResult`. All confidence and threshold values are reported on a **0–100** scale: ```typescript theme={null} interface IdentityVerificationResult { verified: boolean; overallConfidence: number; // 0–100 document: IdentityDocumentResult; faceDetection: { faceDetected: boolean; confidence: number }; faceMatch: { isMatch: boolean; confidence: number; threshold: number }; } interface IdentityDocumentResult { documentType: string; fullName: string; firstName: string; lastName: string; dateOfBirth: string; gender: string; nationality: string; documentNumber: string; expirationDate: string; issuingCountry: string; address?: string; faceImage?: string; confidence: number; // 0–100 } ``` All three sub-results (`document`, `faceDetection`, `faceMatch`) are always present on a 2xx response, even when `verified` is `false`. **Throws** `ValidationError`, `AuthenticationError`, `RateLimitError`, `NetworkError`, `TimeoutError`, `DeepIDVError`. ```typescript theme={null} const result = await client.identity.verify({ documentImage: readFileSync('passport.jpg'), faceImage: readFileSync('selfie.jpg'), documentType: 'passport', }); if (result.verified) { console.log(`Verified: ${result.document.fullName}`); console.log(`Confidence: ${result.overallConfidence}`); } else { console.log('Verification failed'); console.log(`Face match: ${result.faceMatch.isMatch}`); console.log(`Document confidence: ${result.document.confidence}`); } ``` **See also:** REST [Identity Verify](/api-reference/server-to-server/identity-verify). # Reference: Screening Source: https://docs.deepidv.com/integrate/sdks/typescript/server/reference/screening client.screening reference — pepSanctions, adverseMedia (async handle), and titleCheck, with inline types, the AdverseMediaHandle API, and runnable examples. Silent-screening operations. Access them via `client.screening`. `pepSanctions` and `titleCheck` are **synchronous**; `adverseMedia` is **async** and returns an `AdverseMediaHandle` you poll for the result. `pepSanctions` and `titleCheck` are sent with retries disabled (`maxRetries: 0`). The server bounds an un-cancellable upstream and returns `503` ([`ServiceUnavailableError`](/integrate/sdks/typescript/server/error-handling#serviceunavailableerror--http-503)) on breach without billing — an immediate retry would hit the same slow path, so the SDK fails fast. Back off and retry yourself. ## `pepSanctions(input)` ```typescript theme={null} pepSanctions(input: PepSanctionsInput): Promise ``` Run a synchronous PEP & sanctions screening against global watchlists. | Parameter | Type | Required | Description | | ------------------- | -------- | -------- | -------------------------------------- | | `input.email` | `string` | Yes | Subject's email address | | `input.firstName` | `string` | Yes | First name (1–255 chars) | | `input.lastName` | `string` | Yes | Last name (1–255 chars) | | `input.dateOfBirth` | `string` | Yes | Date of birth, ISO 8601 (`YYYY-MM-DD`) | **Returns** `PepSanctionsResult`: ```typescript theme={null} interface PepSanctionsResult { totalMatches: number; peps: PepSanctionsMatch[]; // matched as a politically exposed person sanctions: PepSanctionsMatch[]; // matched on a sanctions list both: PepSanctionsMatch[]; // matched as PEP *and* sanctioned searchedSources: string[]; // datasets/sources queried } interface PepSanctionsMatch { name: string; country: string | null; dateOfBirth: string | null; confidence: number; // 0–1 datasets: string[]; } ``` **Throws** `ValidationError` (400), `AuthenticationError` (401), `InsufficientFundsError` (402), `RateLimitError` (429), `ServiceUnavailableError` (503), `DeepIDVError`. ```typescript theme={null} const result = await client.screening.pepSanctions({ email: 'jane@example.com', firstName: 'Jane', lastName: 'Doe', dateOfBirth: '1980-05-12', }); console.log(`${result.totalMatches} matches across ${result.searchedSources.length} sources`); for (const match of result.sanctions) { console.log(`${match.name} — ${match.confidence} (${match.datasets.join(', ')})`); } ``` **See also:** REST [PEP & Sanctions](/api-reference/silent-screening/pep-sanctions). ## `adverseMedia(input)` ```typescript theme={null} adverseMedia(input: AdverseMediaInput): Promise ``` Queue an **async** adverse-media screening. The POST returns a `jobId` immediately; the SDK wraps it in an `AdverseMediaHandle` so you can `.wait()` for the result or `.refresh()` for a single snapshot. | Parameter | Type | Required | Description | | ---------------------- | -------- | -------- | ---------------------------------------------------------------------------------------- | | `input.email` | `string` | Yes | Subject's email address | | `input.firstName` | `string` | Yes | First name (1–255 chars) | | `input.lastName` | `string` | Yes | Last name (1–255 chars) | | `input.dateOfBirth` | `string` | Yes | Date of birth, ISO 8601 (`YYYY-MM-DD`) | | `input.country` | `string` | No | ISO 3166-1 alpha-2 country code (e.g. `'US'`). Case-insensitive; normalized to uppercase | | `input.idempotencyKey` | `string` | No | Sent as the `Idempotency-Key` header. Omit to let the SDK generate a UUID v4 per call | Leave `idempotencyKey` unset and the SDK auto-generates a UUID v4 for each call, so customer retries (network blips, batch restarts) are safe by default. Server-side dedup TTL is 24 hours — re-sending the same key replays the same job. **Returns** an `AdverseMediaHandle`: ```typescript theme={null} interface AdverseMediaHandle { readonly jobId: string; wait(options?: AdverseMediaWaitOptions): Promise; refresh(): Promise; } interface AdverseMediaWaitOptions { pollIntervalMs?: number; // default 2000 timeoutMs?: number; // default 180000 (3 min) } ``` * **`wait(options?)`** — auto-polls until the job reaches `ready` or `failed`, returning the typed `AdverseMediaResult`. Throws `AdverseMediaFailedError` if the job fails, or `PollTimeoutError` if `timeoutMs` elapses first. A poll timeout does **not** mean the job died — resume later with [`client.asyncJobs.get(jobId)`](/integrate/sdks/typescript/server/reference/async-jobs-events). * **`refresh()`** — performs a single non-blocking poll and returns the current `AdverseMediaJobSnapshot`. **Result and snapshot types:** ```typescript theme={null} interface AdverseMediaResult { totalHits: number; riskLevel: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL'; riskScore: number; // 0–100 summary: string; findings: AdverseMediaFinding[]; exposuresByCategory: Record; } type AdverseMediaJobSnapshot = | { status: 'pending' } | { status: 'processing' } | { status: 'ready'; result: AdverseMediaResult } | { status: 'failed'; error: string }; ``` **`adverseMedia()` throws** `ValidationError` (400), `AuthenticationError` (401), `InsufficientFundsError` (402), `RateLimitError` (429), `DeepIDVError`. The handle's `wait()` additionally throws `AdverseMediaFailedError` and `PollTimeoutError`. ```typescript theme={null} import { AdverseMediaFailedError, PollTimeoutError } from '@deepidv/server'; const handle = await client.screening.adverseMedia({ email: 'jane@example.com', firstName: 'Jane', lastName: 'Doe', dateOfBirth: '1980-05-12', country: 'US', }); console.log(`Queued job ${handle.jobId}`); try { // Block until the job completes (auto-polling every 2s, up to 3 min) const result = await handle.wait(); console.log(`Risk: ${result.riskLevel} (${result.riskScore}/100), ${result.totalHits} hits`); } catch (err) { if (err instanceof AdverseMediaFailedError) { console.error(`Job ${err.jobId} failed`); } else if (err instanceof PollTimeoutError) { // Not dead — keep the jobId and poll later console.warn(`Still running after ${err.timeoutMs}ms; resume with asyncJobs.get('${err.jobId}')`); } else { throw err; } } ``` For a non-blocking single check: ```typescript theme={null} const snapshot = await handle.refresh(); if (snapshot.status === 'ready') { console.log(snapshot.result.riskLevel); } ``` **See also:** REST [Adverse Media](/api-reference/silent-screening/adverse-media) and [Get Async Job](/api-reference/async-jobs/get-async-job). ## `titleCheck(input)` ```typescript theme={null} titleCheck(input: TitleCheckInput): Promise ``` Run a synchronous property title / address search. The server geocodes the address via Google Places (currently US-only). | Parameter | Type | Required | Description | | ----------------- | -------- | -------- | -------------------------------------- | | `input.email` | `string` | Yes | Subject's email address | | `input.firstName` | `string` | Yes | First name (1–255 chars) | | `input.lastName` | `string` | Yes | Last name (1–255 chars) | | `input.address` | `string` | Yes | Free-text postal address (1–500 chars) | **Returns** `TitleCheckResult` — a discriminated union on `status`: ```typescript theme={null} type TitleCheckResult = | { status: 'found'; subjectProperty: SubjectProperty | null; ownerInformation: OwnerInformation | null; /* ...location, transfer, last-sale */ } | { status: 'multiple_properties'; message: string; availableUnits: string[]; properties: Array<{ owner: string; apartmentOrUnit: string }> } | { status: 'unsupported_region'; message: string } | { status: 'not_found'; message: string }; ``` `unsupported_region` is a typed **result**, not an error — the server returns HTTP 200 for all four variants, including when the address falls outside the supported region. Branch on `status` rather than catching. **Throws** `ValidationError` (400), `AuthenticationError` (401), `InsufficientFundsError` (402), `RateLimitError` (429), `ServiceUnavailableError` (503), `DeepIDVError`. ```typescript theme={null} const result = await client.screening.titleCheck({ email: 'jane@example.com', firstName: 'Jane', lastName: 'Doe', address: '1600 Amphitheatre Parkway, Mountain View, CA', }); switch (result.status) { case 'found': console.log(result.subjectProperty?.PropertyFullStreetAddress); break; case 'multiple_properties': console.log(`Disambiguate: ${result.availableUnits.join(', ')}`); break; case 'unsupported_region': console.log(result.message); break; case 'not_found': console.log('No title record found'); break; } ``` **See also:** REST [Title Check](/api-reference/silent-screening/title-check). # Reference: Sessions Source: https://docs.deepidv.com/integrate/sdks/typescript/server/reference/sessions client.sessions reference — create, retrieve, list, and updateStatus, with inline types and runnable examples. Access these methods via `client.sessions`. They manage [hosted verification sessions](/integrate/sdks/typescript/server/session-verification). Request and response schemas are documented in full on the linked REST endpoints — this page covers the SDK signatures, types, and examples. ## `create(input)` ```typescript theme={null} create(input: SessionCreateInput): Promise ``` Create a hosted verification session. | Parameter | Type | Required | Description | | ----------------------- | --------- | -------- | ---------------------------------------------- | | `input.firstName` | `string` | Yes | Applicant's first name | | `input.lastName` | `string` | Yes | Applicant's last name | | `input.email` | `string` | Yes | Applicant's email address | | `input.phone` | `string` | Yes | Applicant's phone number (E.164) | | `input.externalId` | `string` | No | Your internal reference ID | | `input.workflowId` | `string` | No | Workflow to run | | `input.redirectUrl` | `string` | No | URL to redirect the user to after verification | | `input.sendEmailInvite` | `boolean` | No | Send an email invitation | | `input.sendPhoneInvite` | `boolean` | No | Send an SMS invitation | **Returns** `SessionCreateResult`: ```typescript theme={null} interface SessionCreateResult { id: string; sessionUrl: string; externalId?: string; links: Array<{ url: string; type: string }>; } ``` **Throws** `ValidationError`, `AuthenticationError`, `RateLimitError`, `DeepIDVError`. ```typescript theme={null} const session = await client.sessions.create({ firstName: 'Jane', lastName: 'Doe', email: 'jane@example.com', phone: '+15551234567', redirectUrl: 'https://yourapp.com/done', }); console.log(session.sessionUrl); ``` **See also:** REST [Create Session](/api-reference/sessions/create-session). ## `retrieve(sessionId)` ```typescript theme={null} retrieve(sessionId: string): Promise ``` Retrieve full session details, including analysis results. | Parameter | Type | Required | Description | | ----------- | -------- | -------- | -------------------------- | | `sessionId` | `string` | Yes | Session ID from `create()` | **Returns** `SessionRetrieveResult`: ```typescript theme={null} interface SessionRetrieveResult { sessionRecord: Session; user?: { id: string; email: string; firstName: string; lastName: string; phone: string; createdAt: string; updatedAt: string; }; senderUser?: { /* same shape as user */ }; resourceLinks?: Record; } ``` `sessionRecord` is the full `Session` object — it carries `status`, `sessionProgress`, and an `analysisData` block with the document OCR, face-match, and liveness results. See [Navigating analysis data](/integrate/sdks/typescript/server/session-verification#navigating-analysis-data) for the walkthrough. **Throws** `ValidationError`, `AuthenticationError`, `NotFoundError`, `DeepIDVError`. ```typescript theme={null} const result = await client.sessions.retrieve('session-id-123'); console.log(result.sessionRecord.status); // "SUBMITTED" ``` **See also:** REST [Retrieve Session](/api-reference/sessions/retrieve-session). ## `list(params?)` ```typescript theme={null} list(params?: SessionListParams): Promise> ``` List sessions with optional filtering and pagination. | Parameter | Type | Required | Description | | --------------- | --------------- | -------- | ---------------------------------------------------------------- | | `params.limit` | `number` | No | Max results per page | | `params.offset` | `number` | No | Starting offset | | `params.status` | `SessionStatus` | No | Filter: `PENDING`, `SUBMITTED`, `VERIFIED`, `REJECTED`, `VOIDED` | **Returns** `PaginatedResponse`: ```typescript theme={null} interface PaginatedResponse { data: T[]; total?: number; hasMore?: boolean; limit: number; offset: number; } ``` ```typescript theme={null} const page = await client.sessions.list({ status: 'SUBMITTED', limit: 10 }); for (const session of page.data) { console.log(`${session.id}: ${session.status}`); } ``` **See also:** REST [List Sessions](/api-reference/sessions/list-sessions). ## `updateStatus(sessionId, status)` ```typescript theme={null} updateStatus( sessionId: string, status: 'VERIFIED' | 'REJECTED' | 'VOIDED', ): Promise ``` Set the final review status. Only `VERIFIED`, `REJECTED`, and `VOIDED` are valid targets — `PENDING` and `SUBMITTED` are managed by the API. | Parameter | Type | Required | Description | | ----------- | -------------------------------------- | -------- | ----------- | | `sessionId` | `string` | Yes | Session ID | | `status` | `'VERIFIED' \| 'REJECTED' \| 'VOIDED'` | Yes | New status | **Returns** `SessionRetrieveResult` — the updated session details. **Throws** `ValidationError` (invalid status), `AuthenticationError`, `NotFoundError`, `DeepIDVError`. ```typescript theme={null} await client.sessions.updateStatus('session-id-123', 'VERIFIED'); ``` **See also:** REST [Update Session Status](/api-reference/sessions/update-session-status). ## Related types ```typescript theme={null} type SessionStatus = 'PENDING' | 'SUBMITTED' | 'VERIFIED' | 'REJECTED' | 'VOIDED'; type SessionStatusUpdate = 'VERIFIED' | 'REJECTED' | 'VOIDED'; interface SessionCreateInput { firstName: string; lastName: string; email: string; phone: string; externalId?: string; workflowId?: string; redirectUrl?: string; sendEmailInvite?: boolean; sendPhoneInvite?: boolean; } interface SessionListParams { limit?: number; offset?: number; status?: SessionStatus; } ``` # Server-to-Server Source: https://docs.deepidv.com/integrate/sdks/typescript/server/server-to-server Build a custom verification pipeline from the SDK primitives — scan documents, detect and compare faces, estimate age, or run the orchestrated identity.verify shortcut. Use the SDK's primitive methods to build your own verification flow — scan documents, detect faces, compare images, and estimate ages with full control over the pipeline. No hosted UI involved. ## When to use this vs. hosted sessions | Use case | Approach | | ------------------------------- | ----------------------------------------------------------------------------------------------- | | Standard KYC onboarding | Hosted [sessions](/integrate/sdks/typescript/server/session-verification) — easier, includes UI | | Custom verification UX | Server-to-server — full control | | Batch document processing | Server-to-server — no user interaction | | Backend automation / screening | Server-to-server — programmatic | | Quick integration, minimal code | Hosted sessions | ## Flow overview ```mermaid theme={null} sequenceDiagram participant App as Your Server participant SDK as @deepidv/server participant API as deepidv API participant S3 as S3 App->>SDK: document.scan({ image }) SDK->>API: presign + upload + scan API-->>SDK: DocumentScanResult SDK-->>App: { fullName, dateOfBirth, ... } App->>SDK: face.detect({ image: selfie }) SDK->>API: presign + upload + detect API-->>SDK: FaceDetectResult SDK-->>App: { faceDetected, confidence } App->>SDK: face.compare({ source: idPhoto, target: selfie }) SDK->>API: presign + parallel upload + compare API-->>SDK: FaceCompareResult SDK-->>App: { isMatch, confidence } Note over App: Apply your business logic App->>App: Decision: approve / reject ``` Each primitive accepts a `FileInput` — a `Uint8Array`/`Buffer`, a `ReadableStream`, a base64 / data-URL string, or (on Node, Deno, and Bun) a file path string. The SDK handles presigned upload internally. ## Document scan Extract structured OCR data from an identity document: ```typescript theme={null} import { readFileSync } from 'node:fs'; const result = await client.document.scan({ image: readFileSync('passport.jpg'), documentType: 'passport', // 'passport' | 'drivers_license' | 'national_id' | 'auto' }); ``` `documentType` defaults to `'auto'` — the API detects the type. Specifying it can improve accuracy. The result includes `fullName`, `dateOfBirth`, `documentNumber`, `expirationDate`, `issuingCountry`, an OCR `confidence` (0–1), and more. See the [Document Scan reference](/integrate/sdks/typescript/server/reference/document-face-identity#scaninput) for every field, and the REST [Document Scan](/api-reference/server-to-server/document-scan) endpoint for the full schema. ## Face detection Detect a face and get confidence, a bounding box, and landmarks: ```typescript theme={null} const result = await client.face.detect({ image: readFileSync('selfie.jpg'), }); if (result.faceDetected) { console.log(`Face confidence: ${result.confidence}`); if (result.boundingBox) { const { top, left, width, height } = result.boundingBox; console.log(`Face at (${left}, ${top}) — ${width}x${height}`); } } ``` REST reference: [Face Detect](/api-reference/server-to-server/face-detect). ## Face comparison Compare two face images to check if they're the same person. Both images upload in parallel for speed: ```typescript theme={null} const result = await client.face.compare({ source: readFileSync('id-photo.jpg'), target: readFileSync('selfie.jpg'), }); if (result.isMatch) { console.log(`Match! Confidence: ${result.confidence}`); } else { console.log(`No match. Confidence: ${result.confidence}, threshold: ${result.threshold}`); } ``` `confidence` and `threshold` are reported on a 0–100 scale. REST reference: [Face Compare](/api-reference/server-to-server/face-compare). ## Age estimation Estimate age and gender from a face image: ```typescript theme={null} const result = await client.face.estimateAge({ image: readFileSync('selfie.jpg'), }); console.log(`Estimated age: ${result.estimatedAge}`); console.log(`Age range: ${result.ageRange.low}–${result.ageRange.high}`); console.log(`Gender: ${result.gender} (${result.genderConfidence})`); ``` REST reference: [Face Estimate Age](/api-reference/server-to-server/face-estimate-age). ## Identity verification (shortcut) `identity.verify()` combines document scan + face detection + face comparison into a single orchestrated call. Both images upload in parallel: ```typescript theme={null} const result = await client.identity.verify({ documentImage: readFileSync('passport.jpg'), faceImage: readFileSync('selfie.jpg'), documentType: 'passport', // optional, defaults to auto-detect }); console.log(result.verified); // true / false console.log(result.overallConfidence); // 96 // Document data console.log(result.document.fullName); console.log(result.document.dateOfBirth); // Face detection console.log(result.faceDetection.faceDetected); // Face match console.log(result.faceMatch.isMatch); console.log(result.faceMatch.confidence); ``` All three sub-results (`document`, `faceDetection`, `faceMatch`) are always present on a 2xx response, even when `verified` is `false`. REST reference: [Identity Verify](/api-reference/server-to-server/identity-verify). ## Building a custom pipeline Combine primitives with your own business logic: ```typescript theme={null} async function verifyCustomer(idImage: Buffer, selfie: Buffer) { // 1. Scan the document const doc = await client.document.scan({ image: idImage }); // 2. Check document quality if (doc.confidence < 0.8) { return { approved: false, reason: 'Low document quality' }; } // Check expiration const expiry = new Date(doc.expirationDate); if (expiry < new Date()) { return { approved: false, reason: 'Document expired' }; } // 3. Compare faces const match = await client.face.compare({ source: idImage, target: selfie, }); if (!match.isMatch) { return { approved: false, reason: 'Face mismatch' }; } // 4. Age check (optional) const age = await client.face.estimateAge({ image: selfie }); if (age.estimatedAge < 18) { return { approved: false, reason: 'Under 18' }; } return { approved: true, name: doc.fullName, documentNumber: doc.documentNumber, faceConfidence: match.confidence, }; } ``` Wrap calls in `try/catch` to handle typed failures (bad input, auth, rate limits, timeouts) — see [Error Handling](/integrate/sdks/typescript/server/error-handling). # Session Verification Source: https://docs.deepidv.com/integrate/sdks/typescript/server/session-verification Use the SDK's hosted verification flow — create a session, send the user to a hosted page, and retrieve the analysis results when they finish. Hosted sessions are the easiest way to verify identities. You create a session, send the user to a hosted verification page, and retrieve the results when they're done — deepidv handles document upload, selfie capture, and liveness for you. ## Flow overview ```mermaid theme={null} sequenceDiagram participant Server as Your Server participant SDK as @deepidv/server participant API as deepidv API participant User as User's Browser Server->>SDK: client.sessions.create({ ... }) SDK->>API: POST /v1/sessions API-->>SDK: { id, sessionUrl, links } SDK-->>Server: SessionCreateResult Server->>User: Redirect to sessionUrl Note over User: User completes
document upload,
selfie capture,
liveness check User->>Server: Redirect back to redirectUrl
?status=SUBMITTED Server->>SDK: client.sessions.retrieve(sessionId) SDK->>API: GET /v1/sessions/{id} API-->>SDK: { sessionRecord, user, resourceLinks } SDK-->>Server: SessionRetrieveResult Note over Server: Process verification results ``` ## Step 1 — Create a session ```typescript theme={null} const session = await client.sessions.create({ // Required fields firstName: 'Jane', lastName: 'Doe', email: 'jane@example.com', phone: '+15551234567', // Optional: redirect the user back to your app when done redirectUrl: 'https://yourapp.com/verification-complete', // Optional: your internal reference ID externalId: 'user_abc123', // Optional: trigger email / SMS invitations sendEmailInvite: true, sendPhoneInvite: false, // Optional: run a specific workflow workflowId: 'wf_standard_kyc', }); ``` The result contains the URL to send your user to: | Field | Type | Description | | ------------ | ---------------------- | ---------------------------------------- | | `id` | `string` | Unique session identifier | | `sessionUrl` | `string` | URL to send the user to for verification | | `externalId` | `string?` | Your external reference ID (if provided) | | `links` | `Array<{ url, type }>` | Associated resource links | This maps to the REST [Create Session](/api-reference/sessions/create-session) endpoint — see it for the full request/response schema and the `redirect_url` callback parameters. ## Step 2 — Send the user to verify Redirect the user to `session.sessionUrl`. On the hosted page they: 1. Upload their identity document (passport, driver's license, national ID). 2. Take a selfie for face matching. 3. Complete liveness detection. ## Step 3 — Handle the callback When the user finishes (or abandons), they're redirected to your `redirectUrl` with query parameters appended: ``` https://yourapp.com/verification-complete?status=SUBMITTED&sessionId=abc123 ``` See [Create Session → Redirect URL](/api-reference/sessions/create-session#redirect-url) for the full list of `status` and `reason` values. ## Step 4 — Retrieve results ```typescript theme={null} const result = await client.sessions.retrieve(session.id); const record = result.sessionRecord; console.log(record.status); // "SUBMITTED" console.log(record.sessionProgress); // "COMPLETED" ``` ### Navigating analysis data The `analysisData` field carries all verification results: ```typescript theme={null} const analysis = record.analysisData; if (analysis) { // Face match between ID and selfie console.log(analysis.idMatchesSelfie); // true / false console.log(analysis.facelivenessScore); // 0.99 // Document OCR data const idData = analysis.idAnalysisData; if (idData) { for (const field of idData.idExtractedText) { console.log(`${field.type}: ${field.value} (${field.confidence})`); } // Compliance checks console.log(idData.expiryDatePass); // true = not expired console.log(idData.validStatePass); // true = valid jurisdiction console.log(idData.ageRestrictionPass); // true = meets age requirement } // Face comparison details const compare = analysis.compareFacesData; if (compare) { console.log(compare.faceMatchConfidence); // 0.94 } } ``` ### Resource links Presigned URLs for accessing uploaded documents and images: ```typescript theme={null} if (result.resourceLinks) { for (const [name, url] of Object.entries(result.resourceLinks)) { console.log(`${name}: ${url}`); // "id_front: https://s3.amazonaws.com/..." // "selfie: https://s3.amazonaws.com/..." } } ``` ## Step 5 — Update session status After reviewing the results, set the final status: ```typescript theme={null} // Approve the verification await client.sessions.updateStatus(session.id, 'VERIFIED'); // Or reject it await client.sessions.updateStatus(session.id, 'REJECTED'); // Or void it (e.g. a duplicate submission) await client.sessions.updateStatus(session.id, 'VOIDED'); ``` Only `VERIFIED`, `REJECTED`, and `VOIDED` are valid targets. You cannot set `PENDING` or `SUBMITTED` — those are managed by the API based on user activity. See the REST [Update Session Status](/api-reference/sessions/update-session-status) endpoint. ## Listing sessions ```typescript theme={null} // List all sessions const page = await client.sessions.list(); console.log(`Found ${page.data.length} sessions`); // Filter by status const verified = await client.sessions.list({ status: 'VERIFIED', limit: 10, offset: 0, }); for (const session of verified.data) { console.log(`${session.id}: ${session.status} (${session.createdAt})`); } ``` ### Pagination ```typescript theme={null} let offset = 0; const limit = 25; while (true) { const page = await client.sessions.list({ limit, offset }); for (const session of page.data) { processSession(session); } if (!page.hasMore || page.data.length < limit) break; offset += limit; } ``` The paginated response wraps `data` with `total`, `hasMore`, `limit`, and `offset`. See the REST [List Sessions](/api-reference/sessions/list-sessions) endpoint and the [Sessions reference](/integrate/sdks/typescript/server/reference/sessions) for the full types. ## Session statuses | Status | Meaning | Set by | | ----------- | ------------------------------------ | ------------------------ | | `PENDING` | Session created, user hasn't started | API | | `SUBMITTED` | User completed the verification flow | API | | `VERIFIED` | Approved by your team | You (via `updateStatus`) | | `REJECTED` | Rejected by your team | You (via `updateStatus`) | | `VOIDED` | Cancelled / invalidated | You (via `updateStatus`) | # Identity Verification Skill Source: https://docs.deepidv.com/integrate/verify-skill Use the deepidv Verify skill to let compatible AI agents call deepidv verification APIs directly > Use the deepidv Verify skill when you want an AI agent to work directly with deepidv's verification API. The skill gives the agent the correct endpoint choices, request patterns, and authentication behavior — so it can create sessions, inspect results, and manage workflows without you hand-writing integration logic each time. ## Overview This guide is for teams using AI agents that support repository-based skills. The deepidv Verify skill is designed for direct API-driven work, including: * Launching verification sessions * Listing and inspecting existing sessions * Creating reusable workflows * Running identity and compliance checks through guided agent prompts Compatible agent environments currently include: * Claude Code * Codex * Cursor * Windsurf * OpenCode If your client supports hosted remote tools over MCP, use [AI Agent Integration (MCP Server)](/integrate/ai-agent-integration) instead. ## What Success Looks Like Once the skill is set up, your agent should be able to: * Start a new applicant verification flow from a plain-English prompt * Find existing sessions by external ID, workflow, or time range * Retrieve the full result for a specific session * Create a workflow with deepidv verification steps * Help operators review outcomes before taking action ## Before You Start Make sure you have: * A deepidv account with an active API key * An agent that supports repository-based skills * A secure place to store the API key in your local environment If you only need the API key itself, see [API Authentication](/authentication). ## Install the Skill If your agent uses the open agent skills ecosystem, install the deepidv Verify skill with the Skills CLI: ```bash theme={null} npx skills add Deep-Identity-Inc/agent-skills@deepidv-verify -g -y ``` This command: * Pulls the `deepidv-verify` skill from the public deepidv agent-skills repository * Installs it at the user level with `-g` * Skips the interactive confirmation prompt with `-y` After installation: 1. Restart your agent or editor if it does not reload automatically. 2. Confirm the skill is active by asking your agent to use deepidv Verify, or run `npx skills check` to verify the install. Some compatible agents can discover the skill directly from the repository, but using the Skills CLI is the most reliable setup path. ## Set Up Credentials The Verify skill authenticates using the `x-api-key` header — you do not need to include it manually in your prompts. The skill resolves credentials automatically in this order: 1. `DEEPIDV_API_KEY` environment variable 2. `.deepidv/credentials` in the current project root 3. `.deepidv/credentials` in your home directory Store the credential file as either a raw key or a `KEY=value` pair: ```text theme={null} DEEPIDV_API_KEY=sk_test_example ``` Never commit API keys to your repository. Use sandbox keys for testing and switch to live keys only when you are ready for production traffic. ## Start With a Simple Flow The best way to validate the skill is to follow the same order a real integration would use. Make sure the key is available through `DEEPIDV_API_KEY` or a `.deepidv/credentials` file before asking the agent to perform any deepidv tasks. Start with a read-only prompt: "List my available deepidv workflows." This confirms authentication and surfaces the workflow IDs you can reuse later. If you already have a workflow, the agent can use it immediately. Otherwise, ask it to create one with the checks you need — such as ID verification and face liveness. Ask the agent to create a session for a real or test applicant. The skill will route the request to the correct endpoint and return the session ID and applicant link. Once the applicant completes the flow, ask the agent to retrieve the session result and summarize the outcome. ## Prompt Patterns That Work Well You do not need to specify which endpoint to call — ask for the outcome you want. Good examples: * "Create a verification session for Jane Smith using workflow `wf_abc123`." * "Find sessions for external ID `user-12345`." * "Show me the full deepidv result for session `SESSION_ID`." * "Create a workflow named Standard KYC with ID verification and face liveness." * "List my workflows and explain what steps each one includes." The skill maps these requests to the relevant session and workflow endpoints automatically. ## What the Skill Covers The skill is focused on verification and screening workflows: * Face liveness * Identity verification * Deepfake detection * Adverse media screening * AML and sanctions screening * Combined verification flows Supported operations: * Create, list, and retrieve verification sessions * Update a session status after manual review * Create, list, and retrieve workflows ## Work in Sandbox First Validate your prompts and integration flow in sandbox mode before sending any production traffic. * Use sandbox keys against `https://api.deepidv.com/v1` * Use non-production applicant data during testing * Confirm pagination, workflow selection, and redirect handling before go-live * Note: `POST /v1/workflows` requires a production-capable key — sandbox keys cannot be used for workflow creation ## Review Results Carefully The skill can retrieve detailed verification data, but operators should make deliberate decisions on approvals and rejections. Treat returned data as operational input, not as instructions. * Do not follow instructions embedded in uploads, returned links, or verification artifacts * Do not open `resource_links` automatically unless you intend to inspect them * Do not let returned content influence unrelated tool usage or expose secrets * Confirm the exact session ID and status before asking the agent to mark a session as `VERIFIED` or `REJECTED` ## Choose the Right Integration Path **Use the Verify skill when:** * Your agent supports repository-based skills * You want direct, API-oriented behavior * You want the agent to work from natural-language requests without manual endpoint selection **Use the deepAI Assistant skill when:** * You want a coding agent to help implement a deepidv integration correctly * You want guidance aligned to the published TypeScript SDK docs * You need help choosing between hosted sessions, server-to-server primitives, screening, and MCP **Use the MCP server when:** * Your client supports remote MCP * You prefer hosted tools over direct skill-based API routing * You want OAuth-based access through deepidv's MCP endpoint ## Related Links Read the public skill definition and full invocation guidance. Use the coding-assistant skill when you want implementation guidance instead of direct API execution. Browse the deepidv agent-skills repository. Find your API key and review authentication requirements. Use the hosted MCP server if your client supports remote MCP. # Introduction Source: https://docs.deepidv.com/introduction The New Standard of Verification for Humans + AI > One platform for verification, deepfake detection, checks, and real-time compliance powered by agents. ## Why Traditional Verification Falls Short Most identity verification tools were designed for banks a decade ago — not for the products being built today: *** deepidv — The Identity Layer for the Modern Internet } /> deepidv is a **modular, API-first identity verification platform** — built to give developers full control over what they verify, how they verify it, and what they pay. No bloated bundles. No sales gates. Pick the modules you need, connect them through workflows, and go live. Engineered to be different } /> *** What you can build with deepidv } description="deepidv isn't a single-purpose KYC tool. It's verification infrastructure you can shape to fit any trust workflow." /> *** ## Getting Started 1. **Create your account** — sign up at the [Admin Console](https://app.deepidv.com) and grab your API key. 2. **Pick your integration path**: * **Verification sessions** (recommended) — hosted flows your users complete via link or embed * **Direct API calls** — for server-to-server or fully custom implementations 3. **Go live** — run your first real verification in minutes. Go from zero to your first verification session in under 5 minutes. Browse every verification and financial module available on the platform. # Pricing Source: https://docs.deepidv.com/pricing deepidv token-based pricing > Prepaid tokens, no contracts, no minimums. You're only billed when a check actually completes. deepidv runs on a **pay-per-use model** — buy tokens in USD, and each completed check deducts the corresponding amount from your balance. Tokens never expire, and there are no monthly commitments. ### How it works 1. **Top up your balance** — purchase tokens through the [Admin Console](https://app.deepidv.com) under **Settings → Billing** 2. **Build a workflow** — combine the services you need in the [Workflows](/workflows/workflows) section 3. **Pay per check** — the total cost of a session is the sum of each enabled service *** ## Workflow Session Pricing Each service below can be toggled into a workflow. The session price is the total of every enabled service that runs.
Service What it does Price per use
ID Verification Scans and validates government-issued identity documents \$0.50
Face Liveness Confirms the applicant is a live person, not a photo or deepfake \$0.05
Age Estimation Predicts the applicant's age from facial analysis \$0.05
PEP & Sanctions Checks against global politically exposed persons and sanctions lists \$0.40
Adverse Media Scans for negative press, legal issues, and public media mentions \$0.40
Phone Verification Places a live call and verifies identity through a spoken voice prompt \$0.40
Address Verification AI-powered conversation to confirm the applicant's location \$0.50
Bank Statement Sync Pulls bank statements directly from the applicant's financial institution \$1.10
AI Bank Statement Analysis AI-driven breakdown of income, spending, and affordability \$0.95
Title Search Looks up property title records for ownership and lien data \$0.80
Document Fraud Analysis AI analysis of uploaded documents for tampering or forgery \$0.40
Document Upload Collects supporting documents from the applicant \$0.05
Custom Prompt Picture Requests a specific photo based on a prompt you define \$0.05
Custom Form Custom questions, fields, and file uploads in the verification flow \$0.05
E-Signature Collects a legally binding electronic signature from the applicant \$0.10
Credit Check Pulls credit reports from major bureaus Contact Sales
### Example A workflow with **ID Verification**, **Face Liveness**, and **PEP & Sanctions** costs: ``` $0.50 + $0.05 + $0.40 = $0.95 per session ``` *** ## Standalone API Pricing *(Coming Soon)* Direct server-to-server API calls — no session or workflow required. Pricing for standalone endpoints will be published when this integration path is available. *** ## Managing Your Balance * Check your current token balance in the [Admin Console](https://app.deepidv.com) * Top up anytime under **Settings → Billing** * If your balance hits zero, session creation requests return a `402 Payment Required` error *** ## Frequently Asked Questions No. Once you purchase tokens, they stay in your account indefinitely. Use them whenever you need — there's no time pressure or monthly reset. You're billed when a session is created and the enabled services are triggered. Each service in the workflow is charged at its listed rate. None. deepidv is fully pay-as-you-go. No annual contracts, no monthly minimums, no lock-ins. You control exactly how much you spend and when. Each workflow is made up of individual services. When a session runs, you're charged the sum of each enabled service that completes. Only toggle what you need — you're never paying for services you didn't use. Yes. For high-volume use cases or enterprise needs, reach out to [sales@deepidv.com](mailto:sales@deepidv.com) to discuss custom pricing and volume rates. No. There are no setup fees, no integration charges, and no hidden line items. The prices listed above are exactly what you pay per completed check. Prices listed above are subject to change. For custom pricing, volume discounts, or enterprise plans, contact [sales@deepidv.com](mailto:sales@deepidv.com). *** ### Get Started Sign up and start verifying — top up tokens when you're ready. Need custom pricing or volume rates? Let's chat. # Quick Start Source: https://docs.deepidv.com/quickstart Get up and running with deepidv in minutes > Get from zero to a live identity verification in minutes — create your account, set up a workflow, launch a session, and start receiving results. 1. Head to [app.deepidv.com](https://app.deepidv.com) and sign up 2. Create your **Organization** — this is the workspace where your team manages workflows, API credentials, and verification activity 3. Make sure your account has a sufficient **token balance** to run verifications Each verification service consumes tokens at its own rate. Check the [Pricing](/pricing) page for a full breakdown of per-check costs. Open **Workflows** in the sidebar and click **Create New**. A workflow defines which checks run during a session: | Service | What it does | | ---------------------------------------- | ----------------------------------------------------------------------------- | | **ID Verification** | Validates government-issued documents and extracts key fields | | **Face Liveness** | Confirms the applicant is a real, live person | | **Age Estimation** | Estimates the applicant's age from biometric data | | **PEP & Sanctions** | Screens against global watchlists and sanctions registries | | **Adverse Media** | Screens for negative news coverage and media mentions | | **Bank Statement Sync** | Retrieves bank statements via open banking | | **AI Bank Analysis** | Runs AI-powered analysis on financial data | | **Document Upload with Fraud Detection** | Collects uploaded documents and scans them for signs of tampering or fraud | | **Custom Prompt Picture** | Requests a photo from the applicant based on a custom prompt you define | | **Custom Forms** | Adds custom questions or data collection fields to the verification flow | | **Title Search** | Searches property title records for ownership and lien information | | **Address Verification** | Uses AI-driven prompts to verify the applicant's location in real time | | **Phone Verification** | Calls the applicant and verifies their identity through a spoken voice prompt | Toggle the services you need, name your workflow, and save it. You can create as many workflows as you like for different use cases. Go to **Settings → API Keys** in the Admin Console: 1. Click **Generate API Key** and copy it somewhere safe 2. Optionally, configure a **Webhook URL** to receive real-time status updates when sessions change Your API key is a secret. Keep it server-side only — never expose it in frontend code or commit it to version control. Import our pre-built Postman collection to start testing every endpoint immediately. Just set the `api_key` variable and go. **Option A — Verification sessions** (recommended) The fastest way to get started. Create a session through the Admin Console or programmatically via the API. Your applicant receives an email and/or SMS with a link to complete their verification. In the Console, click **"+"** → pick a workflow → enter applicant details. Or create sessions via code: ```bash cURL theme={null} curl -X POST https://api.deepidv.com/v1/sessions \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -d '{ "firstName": "Jane", "lastName": "Smith", "email": "jane.smith@example.com", "phone": "+14165557890", "workflowId": "your-workflow-id" }' ``` ```javascript Node.js theme={null} const response = await fetch("https://api.deepidv.com/v1/sessions", { method: "POST", headers: { "Content-Type": "application/json", "x-api-key": "YOUR_API_KEY", }, body: JSON.stringify({ firstName: "Jane", lastName: "Smith", email: "jane.smith@example.com", phone: "+14165557890", workflowId: "your-workflow-id", }), }); const session = await response.json(); console.log(session); ``` ```python Python theme={null} import requests response = requests.post( "https://api.deepidv.com/v1/sessions", headers={ "Content-Type": "application/json", "x-api-key": "YOUR_API_KEY", }, json={ "firstName": "Jane", "lastName": "Smith", "email": "jane.smith@example.com", "phone": "+14165557890", "workflowId": "your-workflow-id", }, ) print(response.json()) ``` Find your `workflowId` under **Workflows** in the Admin Console sidebar. A successful response returns a session ID and verification URL: ```json theme={null} { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "session_url": "https://app.deepidv.com", "links": [] } ``` **Option B — Direct API calls** Call individual verification endpoints directly from your backend — no session required. Ideal for server-to-server pipelines, batch processing, and fully custom verification flows where you control the applicant experience end-to-end. Five endpoints are available under `/v1`: | Endpoint | Purpose | | ---------------------------- | ---------------------------------------------------------------------- | | `POST /v1/document/scan` | Extract fields from a government-issued ID and run authenticity checks | | `POST /v1/face/detect` | Detect a face in an image and return bounding box + landmarks | | `POST /v1/face/compare` | Compare two faces and return a similarity score | | `POST /v1/face/estimate-age` | Estimate age range from a single face image | | `POST /v1/identity/verify` | Run document scan + face detect + face compare in a single call | Each endpoint accepts images either as `multipart/form-data` (raw bytes) or as `application/json` with a presigned `fileKey`. ```bash cURL theme={null} curl -X POST https://api.deepidv.com/v1/face/detect \ -H "x-api-key: YOUR_API_KEY" \ -F "image=@selfie.jpg" ``` See the [Server-to-Server overview](/api-reference/server-to-server/overview) for full details. Once an applicant completes their verification, deepidv automatically processes the results. Sessions move through these statuses: | Status | Meaning | | ----------- | ----------------------------------------------------------------------------- | | `PENDING` | Session created, awaiting applicant | | `SUBMITTED` | Applicant completed the flow, results are processing | | `VERIFIED` | All checks passed | | `REJECTED` | One or more checks failed | | `VOIDED` | Session manually cancelled by a reviewer | | `EXPIRED` | Session auto-expired before the applicant submitted | | `FAILED` | Session auto-failed after the workflow's configured number of failed attempts | If you configured a webhook, deepidv sends events to your endpoint automatically — no polling required. You can also retrieve session results at any time via the [Retrieve Session](/api-reference/sessions/retrieve-session) endpoint. Use the Admin Console to: * Monitor session progress in real time * Manually review and approve or reject flagged sessions * Export individual PDF reports or bulk CSV downloads * Filter sessions by status, date range, workflow, or applicant * View detailed analytics and token spend on the [Analytics](/settings/analytics) dashboard ## Need help? Reach our team at [support@deepidv.com](mailto:support@deepidv.com) Explore the full API documentation and endpoint specs. # Export to PDF or CSV Source: https://docs.deepidv.com/review/export Export verification data to PDF or CSV formats Export session data from the Admin Console for reporting, compliance, or record-keeping. ## Export formats Export individual session details as a formatted PDF document — ideal for compliance records, sharing with stakeholders, or archiving completed verifications. Export session data in bulk as a CSV file — ideal for analysis, importing into spreadsheets, or integrating with your internal systems. ## Exporting a single session as PDF 1. Open a session in the [Admin Console](https://app.deepidv.com) 2. Click **Export** → **PDF** 3. The PDF includes applicant details, verification results, document images, and status history Download PDF from a session in the Admin Console ## Exporting sessions in bulk as CSV 1. Navigate to **Sessions** in the [Admin Console](https://app.deepidv.com) 2. Apply any filters to narrow down the data you want to export (status, date range, workflow, etc.) 3. Click **Export** → **CSV** 4. The CSV file downloads with one row per session Export CSV dialog with date selection and field options ### CSV fields The exported CSV includes: | Field | Description | | -------------- | ------------------------------------- | | Session ID | Unique session identifier | | External ID | Your internal reference (if provided) | | Applicant Name | First and last name | | Email | Applicant's email address | | Phone | Applicant's phone number | | Status | Current session status | | Workflow | Workflow used for the session | | Created At | Session creation timestamp | | Completed At | Verification completion timestamp | # Manual Review Source: https://docs.deepidv.com/review/manual-review Manually review and manage verification sessions > Review flagged sessions, inspect verification badges, and make informed approve or reject decisions from the Admin Console. Not every verification is black and white. When deepidv's automated checks surface warnings or edge cases, sessions land in **Submitted** status for your team to review. This guide walks through the full manual review process — what to look at, how to evaluate it, and when to approve, reject, or void. *** ## Session Statuses Every session moves through a lifecycle: | Status | What it means | | ------------- | ----------------------------------------------------------------------- | | **Pending** | Session created — the applicant hasn't started yet | | **Submitted** | Applicant finished the flow — ready for your review | | **Verified** | Approved — all checks passed or were manually accepted | | **Rejected** | Denied — one or more checks failed your review criteria | | **Voided** | Invalidated — excluded from reporting (test sessions, duplicates, etc.) | *** ## Verification Badges When you open a session, deepidv displays a grid of **verification badges** — each one representing a check that ran during the session. Badges show a green checkmark for passing results or a red X for failures, giving you an at-a-glance summary before you dig deeper. deepidv verification badges showing check results Here's what each badge covers: | Badge | What it tells you | | ----------------------------- | ---------------------------------------------------------------------------------- | | **Face Matches ID** | Whether the applicant's selfie matches the photo on their ID document | | **Face Liveness** | Whether the applicant passed the liveness check (includes confidence score) | | **ID Verification** | Whether the submitted ID was validated and the selfie matches | | **Age Estimation** | The estimated age range based on facial analysis | | **PEP Sanctions Check** | Whether the applicant was found on any sanctions or PEP lists | | **Bank Statement Import** | Whether bank data was successfully retrieved (includes institution and date range) | | **ID Scanned Details** | Whether the ID was successfully scanned and key fields extracted | | **Name Matches ID** | Whether the name on the ID matches the name provided by the applicant | | **ID Type Matches Selection** | Whether the uploaded document matches the type the applicant selected | | **ID Expiry Date** | Whether the ID document is still valid and hasn't expired | | **Valid State/Province** | Whether the ID is from an allowed state or province | | **Age Restriction** | Whether the applicant meets the minimum age requirement based on their ID | Badges with a red X are the fastest way to identify what needs your attention. Start there and work outward. *** ## How to Conduct a Thorough Manual Review Open the session detail view and scan the verification badges at the top. Any red X badges are your starting point — they highlight exactly which checks raised concerns. deepidv already runs automated validation on every document — extracting text, checking expiry dates, and scanning for tampering. Your job is to evaluate the flagged items: 1. **Check the ID Details tab** — review extracted fields like name, date of birth, document number, and expiry. Confidence scores are shown next to each field. 2. **Compare the detected ID type** against what the applicant selected — a mismatch could indicate confusion or an intentional swap. 3. **Look for tampering signals** — if the document fraud analysis flagged anything, inspect the uploaded images closely for signs of editing, cropping, or physical manipulation. 4. **Verify the expiry date** — expired documents should generally be rejected unless your policy allows them. Check the face matching and liveness outcomes: 1. **Face match confidence** — deepidv compares the selfie to the ID photo and returns a similarity score. Low scores may indicate a mismatch, but poor lighting or camera quality can also be a factor. 2. **Liveness score** — a high score means the applicant was confirmed as a live person. Low scores may flag printed photos, replayed videos, or deepfake attempts. 3. **Age estimation** — if the estimated age range conflicts with the date of birth on the ID, investigate further. Review the risk-related tabs: **PEP & Sanctions** * **No matches** — low risk, no hits on global watchlists. * **Matches found** — review each match carefully. Check the match score, country, datasets, and whether it's a true positive or a false positive based on name similarity and date of birth. **Adverse Media** * Review any flagged media mentions, legal proceedings, or negative press tied to the applicant. **Title Search / Credit Check** * If these services were part of the workflow, review the results in their respective tabs for any red flags. The **Audit Trail** tab shows a chronological log of every event in the session — when it was created, when the applicant started, when each check completed, and any reviewer actions taken. Use this to understand the full timeline. *** ## Making a Decision After your review, you have three options: * Document appears authentic * Selfie matches the ID photo * Liveness score is acceptable * No relevant PEP/sanctions hits * All badges are green or explainable * Document looks tampered or forged * Selfie doesn't match the ID * Confirmed sanctions or PEP match * Strong indicators of fraud * Failed age or expiry restrictions * Test or duplicate session * Created in error * Applicant requested cancellation * Session should be excluded from reporting When rejecting a session, always add **review notes** explaining your reasoning. This supports internal quality assurance, compliance audits, and team training. *** ## Filtering and Search Use the Admin Console's filtering tools to narrow down sessions: | Filter | Options | | --------------- | ---------------------------------------------- | | **Status** | Pending, Submitted, Verified, Rejected, Voided | | **Date range** | Filter by creation or submission date | | **Applicant** | Search by name or email | | **External ID** | Filter by your internal reference ID | | **Workflow** | Show sessions from a specific workflow | Filter by **Submitted** status to see all sessions waiting for manual review. This is the queue you should be checking regularly. *** ## Review Best Practices The verification badge grid gives you an instant snapshot. Red X badges tell you exactly where the session has issues — start your review there instead of reading everything top to bottom. Apply the same criteria to every session. Document your reasoning in the review notes so other team members can follow the same logic and maintain consistency. Don't make decisions based on a single data point. A borderline liveness score combined with a name mismatch and a PEP hit paints a very different picture than any one of those alone. When inspecting uploaded IDs, zoom in on the images to check for subtle signs of editing, missing security features, or inconsistent fonts and spacing. Add clear, specific notes when rejecting a session. This builds an audit trail for compliance, helps with disputes, and gives your team a reference for similar cases in the future. Set a cadence for reviewing submitted sessions. The faster you review, the better the applicant experience — and the less likely they are to drop off while waiting. # Sandbox Mode Source: https://docs.deepidv.com/sandbox Test your deepidv integration without consuming tokens or creating real sessions > Sandbox mode lets you build and test your integration against the deepidv API using realistic dummy data — no tokens consumed, no real sessions created. ## How It Works Sandbox mode is controlled by a special **Sandbox API Key**. When you make requests with a sandbox key, the API returns realistic test data instead of hitting real services. This lets you: * Validate your integration logic end-to-end * Test how your app handles different session statuses (verified, rejected, pending, etc.) * Inspect the full response shape for every endpoint * Develop locally without consuming tokens or affecting production data Sandbox keys behave identically to live keys for authentication — the only difference is what the API returns. *** ## Generating a Sandbox Key Sandbox API keys are not created automatically. You need to generate one from the Admin Console: Navigate to [**Settings → API Keys**](https://app.deepidv.com/dashboard/api/api-keys) in the Admin Console. Find the **Sandbox API Key** card and click **Generate**. Your sandbox key will appear — copy it somewhere safe. Pass the sandbox key in the `x-api-key` header, exactly like a live key. The API handles the rest. Sandbox keys follow the same security rules as live keys — keep them server-side and never expose them in frontend code. *** ## What You Can Do ### GET requests — realistic test data All `GET` endpoints under `/v1/sessions`, `/v1/financial`, `/v1/workflows`, and `/v1/credit-checks` return realistic dummy data. You control which scenario you get by using **well-known test IDs**. ### POST, PATCH, PUT, DELETE — blocked Write operations are not available in sandbox mode. Any non-GET request to a sandboxed route returns: ```json theme={null} { "error": "This is a sandbox API key. Use a live API key to create or modify resources." } ``` This is intentional — sandbox mode is for reading and validating response shapes, not creating real records. ### Other routes Endpoints outside of sessions, financial, workflows, and credit-checks (e.g., `/health`) pass through normally. *** ## Well-Known Test IDs Use these IDs as the `:id` parameter in GET requests to control which scenario you receive. ### Sessions — `GET /v1/sessions/:id` | Test ID | Status | Progress | Analysis Data | | ---------------- | ----------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `test_verified` | `VERIFIED` | `COMPLETED` | Full — includes all analysis fields (ID analysis, face match, PEP/sanctions, adverse media, title search, document risk, custom form, secondary/tertiary ID) | | `test_rejected` | `REJECTED` | `COMPLETED` | Basic — ID analysis and face match with failing scores | | `test_submitted` | `SUBMITTED` | `COMPLETED` | Basic — ID analysis and face match (awaiting review) | | `test_pending` | `PENDING` | `PENDING` | None | | `test_voided` | `VOIDED` | `COMPLETED` | None | | Any other ID | Random | Matching | Varies | Use `test_verified` to see every possible field in the response — it's the "everything" scenario with all workflow steps, uploads, and analysis data populated. ### Financial — `GET /v1/financial/:id` | Test ID | Status | Statement Data | | ---------------- | ----------- | ------------------------------------------------------------ | | `test_completed` | `COMPLETED` | Full bank statement with accounts, transactions, holder info | | `test_pending` | `PENDING` | No statement data | | Any other ID | Random | Varies | ### Credit Checks — `GET /v1/credit-checks/:id` | Test ID | Type | Score | Score Status | Overall Risk | Insights | | --------------------- | ------ | ------ | ------------ | ------------ | ---------------------------------------------------------------- | | `test_soft_pass` | `SOFT` | `741` | `FAIR` | — | Score only | | `test_hard_pass` | `HARD` | `741` | `FAIR` | `LOW` | Full — clean profile, excellent payment history, low utilization | | `test_hard_review` | `HARD` | `612` | `POOR` | `MEDIUM` | Full — late payments, high utilization, paid collection | | `test_hard_high_risk` | `HARD` | `480` | `HIGH_RISK` | `HIGH` | Full — active consumer proposal, unpaid collections, fraud alert | | Any other ID | Random | Varies | Varies | Varies | Varies | Soft credit checks return only the credit score. Hard credit checks include the full AI-powered insights analysis with risk assessment, category breakdowns, and a plain-English summary. ### Workflows — `GET /v1/workflows/:id` | Test ID | Steps | | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `test_workflow` | Full workflow with all 11 steps (ID verification, face liveness, age estimation, PEP/sanctions, adverse media, bank statement upload, document upload, title search, custom prompt, custom form, AI bank statement analysis) | | Any other ID | Basic workflow with 2 steps (ID verification, face liveness) | *** ## List Endpoints List endpoints return multiple records without needing a test ID: | Endpoint | Returns | | ----------------------- | ----------------------------------------------------------------- | | `GET /v1/sessions` | 5 sessions (one per status), lightweight analysis data | | `GET /v1/financial` | 2 bank statements (one completed, one pending), no statement body | | `GET /v1/workflows` | 2 workflows (summary only — id, name, created\_at) | | `GET /v1/credit-checks` | 4 credit checks (1 soft, 3 hard — varying risk levels) | All list responses return `next_token: null` (no pagination in sandbox). *** ## Example Here's a quick example testing the full session response: ```bash cURL theme={null} curl -X GET https://api.deepidv.com/v1/sessions/test_verified \ -H "x-api-key: YOUR_SANDBOX_API_KEY" ``` ```javascript Node.js theme={null} const response = await fetch( "https://api.deepidv.com/v1/sessions/test_verified", { headers: { "x-api-key": "YOUR_SANDBOX_API_KEY" }, } ); const data = await response.json(); console.log(data.session_record.status); // "VERIFIED" console.log(data.session_record.analysis_data); // full analysis object ``` ```python Python theme={null} import requests response = requests.get( "https://api.deepidv.com/v1/sessions/test_verified", headers={"x-api-key": "YOUR_SANDBOX_API_KEY"}, ) data = response.json() print(data["session_record"]["status"]) # "VERIFIED" print(data["session_record"]["analysis_data"]) # full analysis object ``` *** ## Sandbox vs. Live | | Sandbox Key | Live Key | | ------------------------- | ----------------------- | ---------------------------------- | | **GET requests** | Returns test data | Returns real data | | **POST/PATCH/PUT/DELETE** | Returns 403 error | Creates/modifies real resources | | **Token consumption** | None | Tokens deducted per operation | | **Data persistence** | No data is stored | Sessions and records are persisted | | **Authentication** | Same `x-api-key` header | Same `x-api-key` header | See the full response schema for each endpoint. View the complete sandbox test data models and response examples. # Security & Compliance Source: https://docs.deepidv.com/security/security-compliance How deepidv protects your data and meets compliance standards deepidv is built with security at every layer. From how data is transmitted and stored, to how access is controlled and audited, we follow industry best practices to protect sensitive identity and financial data. ## Data encryption | Layer | Standard | | -------------- | ---------------------------------------------------------- | | **In transit** | All API traffic is encrypted via TLS 1.2+ (HTTPS enforced) | | **At rest** | All stored data is encrypted using AES-256 encryption | ## Infrastructure security * **Cloud-hosted on AWS** — deepidv runs on Amazon Web Services with enterprise-grade infrastructure * **Private networking** — backend services operate within a private VPC with no direct public access * **API Gateway** — all requests pass through AWS API Gateway with rate limiting and request validation * **Access controls** — HMAC-based API key authentication with organization-level isolation ## Data handling * **Multi-tenant isolation** — each organization's data is logically isolated; no cross-tenant access is possible * **Minimal data retention** — we only store what's necessary to deliver the service and meet compliance requirements * **Document storage** — uploaded identity documents and bank statements are stored in encrypted S3 buckets with restricted access * **Audit trail** — all actions are logged in [Audit Logs](/settings/audit-logs) for accountability and compliance reporting ## Access control * **API key management** — generate, rotate, and revoke API keys from the Admin Console * **Organization-level permissions** — API keys are scoped to your organization and cannot access other tenants' data * **Admin Console authentication** — access to the console is protected with secure authentication ## Compliance deepidv is designed to support compliance with applicable data protection regulations. If your organization requires specific certifications or compliance documentation, contact our team. For compliance documentation, security questionnaires, or data processing agreements, reach out to our team. # Terms of Service Source: https://docs.deepidv.com/security/terms-of-service deepidv Terms of Service and Privacy Policy Please review our Terms of Service and Privacy Policy for full details on data handling, acceptable use, and your rights. Read the full deepidv Privacy Policy and Terms of Service on our website. # Services Source: https://docs.deepidv.com/services Identity verification and financial data services offered by deepidv > A full breakdown of every module available on the deepidv platform — mix and match to build the verification flow your product needs. Every service below can be toggled on or off inside a [workflow](/workflows/workflows). Pick what you need, skip what you don't — you're only charged for what runs. See [Pricing](/pricing) for per-service rates. Know who you're dealing with } description="Scan IDs, detect document fraud, and pull property records — all automated." /> Make sure they're actually there } description="Stop spoofed selfies and deepfakes — confirm a living person is on the other side of the screen." /> Catch red flags early } description="Run applicants through sanctions lists and media databases before they become a compliance problem." /> Reach them for real } description="Go beyond form fields — verify phone numbers and addresses through live, interactive challenges." /> See the full financial picture } description="Pull bank data and let AI do the heavy lifting on income, spending, and affordability." /> Make it yours } description="Collect exactly what you need — custom photos, form fields, or file uploads — right inside the verification flow." /> Tools baked into the console } description="Handle document exchange and e-signatures without leaving the Admin Console." /> *** Going deeper? } description="Enterprise-grade services available through our sales team. Reach out to get set up." /> # Analytics Source: https://docs.deepidv.com/settings/analytics View and analyze verification metrics The Analytics dashboard in the Admin Console gives you a real-time overview of your verification activity, helping you monitor performance and identify trends. ## Accessing analytics 1. Log in to the [Admin Console](https://app.deepidv.com) 2. Navigate to **Analytics** from the main menu ## Key metrics The dashboard displays: | Metric | Description | | --------------------- | -------------------------------------------------------------- | | **Total sessions** | Number of verification sessions created in the selected period | | **Verification rate** | Percentage of sessions that were verified vs. rejected | | **Pending sessions** | Sessions awaiting applicant action or manual review | | **Service usage** | Breakdown of which services are being used and how often | | **Token spend** | Total tokens consumed over the selected period | ## Filtering Use filters to narrow the analytics view by: * **Date range** — view activity for a specific time period * **Workflow** — see metrics for a particular workflow * **Status** — focus on verified, rejected, or pending sessions ## Use cases * **Monitor conversion** — track how many sessions are completed vs. abandoned * **Optimize workflows** — identify which service combinations have the highest pass rates * **Budget planning** — forecast token usage based on historical trends * **Compliance reporting** — generate activity summaries for audit or regulatory requirements # Audit Logs Source: https://docs.deepidv.com/settings/audit-logs Track and review all activity in your organization Audit Logs provide a complete record of actions taken within your deepidv organization. Every significant event is logged with a timestamp, the user who performed it, and relevant details. ## Accessing audit logs 1. Log in to the [Admin Console](https://app.deepidv.com) 2. Navigate to **Settings** → **Audit Logs** ## What's logged | Event | Description | | --------------------- | --------------------------------------------------------------- | | **Session created** | A verification session was created via API or Admin Console | | **Session reviewed** | A session was manually verified, rejected, or voided | | **API key generated** | A new API key was created | | **API key revoked** | An existing API key was revoked | | **User invited** | A new team member was invited to the organization | | **User removed** | A team member was removed from the organization | | **Settings changed** | Organization settings were updated (white label, billing, etc.) | | **Workflow created** | A new workflow was created | | **Workflow updated** | An existing workflow was modified | ## Log details Each log entry includes: * **Timestamp** — when the action occurred * **Actor** — the user or API key that performed the action * **Action** — what was done * **Details** — relevant context (e.g., session ID, setting changed, etc.) ## Filtering and search Filter audit logs by: * **Date range** — focus on a specific time period * **Actor** — view actions by a particular team member * **Event type** — filter by category (sessions, API keys, settings, etc.) Audit logs are retained for compliance and cannot be deleted. They are read-only for all users. # White Label Source: https://docs.deepidv.com/settings/white-label Customize the verification experience with your brand White labeling lets you present the verification experience under your own brand. Your applicants see your logo, colors, and messaging — with no visible deepidv branding. ## What you can customize | Element | Description | | --------------------- | -------------------------------------------------------------------- | | **Logo** | Replace the default logo with your own on the verification page | | **Brand colors** | Set primary and accent colors to match your brand identity | | **Email templates** | Customize the email invitations sent to applicants | | **SMS templates** | Customize the SMS messages sent to applicants | | **Verification page** | Brand the page where applicants upload documents and complete checks | ## Setting up white labeling 1. Log in to the [Admin Console](https://app.deepidv.com) 2. Navigate to **Settings** → **White Label** 3. Upload your logo and set your brand colors 4. Preview the changes and save ## Email and SMS customization Customize the content and appearance of verification invitations: * **Email** — update the subject line, body text, and visual styling of the verification invite email * **SMS** — modify the text message content sent to applicants Preview your customized templates before going live to ensure they look correct across email clients and devices. ## Verification page branding When applicants open their verification link, they see a branded experience: * Your logo displayed prominently * Your brand colors applied to buttons, headers, and UI elements * No deepidv branding visible to the end user # Event Object Source: https://docs.deepidv.com/webhooks/event-object Structure of the webhook event payload sent to your endpoint When deepidv sends a webhook event to your endpoint, the request body contains an event object with the event type and the associated data. ## Event object structure Every webhook event follows this structure: ```json theme={null} { "type": "session.status.verified", "data": { // Session object — same structure as the Retrieve Session API response } } ``` | Field | Type | Description | | ------ | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `type` | string | The event type that triggered the webhook (e.g. `session.status.submitted`) | | `data` | object | The session object associated with the event. This is the same transformed session object returned by the [Retrieve Session](/api-reference/sessions/retrieve-session) API | ## Event types | Event | Trigger | | -------------------------- | -------------------------------------------------------------------------------------------------- | | `session.created` | A new verification session has been created | | `session.status.submitted` | An applicant has completed and submitted their verification session | | `session.status.verified` | A verification session has been marked as verified (via manual review or automatic rules) | | `session.status.rejected` | A verification session has been marked as rejected (via manual review or automatic rules) | | `session.status.failed` | A verification session has hit the workflow's configured failed-attempt limit and been auto-failed | ## Headers Every webhook request includes the following headers: | Header | Description | | ------------------- | -------------------------------------------------------------- | | `Content-Type` | `application/json` | | `deepidv-signature` | Your webhook's signing secret for verifying the request origin | ## The `data` object The `data` field contains the full session object. This is the same structure returned by the [Retrieve Session](/api-reference/sessions/retrieve-session) API endpoint. The key fields are outlined below. ### Session fields | Field | Type | Description | | ------------------ | --------- | ---------------------------------------------------------------------------------- | | `id` | string | Unique session identifier | | `organization_id` | string | Organization that owns this session | | `user_id` | string | User ID of the applicant | | `sender_user_id` | string | User ID of the person who created the session | | `external_id` | string | Your external reference ID (if provided at session creation) | | `status` | string | `PENDING`, `SUBMITTED`, `VERIFIED`, `REJECTED`, `VOIDED`, `EXPIRED`, or `FAILED` | | `type` | string | `session`, `verification`, `credit-application`, `silent-screening`, or `deep-doc` | | `session_progress` | string | `PENDING`, `STARTED`, or `COMPLETED` | | `created_at` | string | ISO 8601 timestamp of session creation | | `updated_at` | string | ISO 8601 timestamp of last update | | `submitted_at` | string | ISO 8601 timestamp when the applicant submitted | | `workflow_id` | string | Workflow ID used for this session | | `workflow_steps` | string\[] | List of workflow step IDs | | `uploads` | object | Boolean flags for each uploaded document type | | `meta_data` | object | Applicant submission metadata (IP, device, browser, location) | | `analysis_data` | object | Verification analysis results | ### `analysis_data` object Included when the session has been processed. Contains the full verification analysis: | Field | Type | Description | | ---------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | | `created_at` | string | When analysis was performed | | `id_analysis_data` | object | Primary ID document analysis (face detection, extracted text, expiry/age/state checks) | | `secondary_id_analysis_data` | object | Secondary ID document analysis (same structure) | | `tertiary_id_analysis_data` | object | Tertiary ID document analysis (same structure) | | `id_matches_selfie` | boolean | Rollup identity result — logical AND of `id_matches_face_capture` and `id_matches_liveness` (whichever ran). `false` if either present check failed | | `faceliveness_score` | number | Liveness confidence score (0–100) | | `id_matches_face_capture` | boolean | Whether the face/selfie capture matched the ID portrait. `null` if not evaluated | | `id_matches_liveness` | boolean | Whether the Face Liveness capture matched the ID portrait (Face Liveness workflows only). `null` if not evaluated | | `compare_faces_data` | object | Face comparison results with `face_match_confidence` score | | `pep_sanctions_data` | object | PEP & sanctions screening results | | `adverse_media_data` | object | Adverse media screening results | | `document_risk_data` | object | Document fraud/risk analysis | | `custom_form_data` | array | Custom form question/answer entries | For a full breakdown of all nested fields within `analysis_data` and `meta_data`, see the [Retrieve Session](/api-reference/sessions/retrieve-session) API reference. ## Example payloads ### `session.created` Sent when a new session is created. At this stage, the session has no analysis data or uploads. ```json theme={null} { "type": "session.created", "data": { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "organization_id": "org_123", "user_id": "usr_456", "sender_user_id": "usr_789", "external_id": "your-internal-id", "status": "PENDING", "type": "session", "session_progress": "PENDING", "workflow_id": "wf_abc123", "workflow_steps": ["ID_VERIFICATION", "FACE_LIVENESS"], "created_at": "2026-03-23T14:30:00.000Z", "updated_at": "2026-03-23T14:30:00.000Z" } } ``` ### `session.status.submitted` Sent when an applicant completes and submits their verification session. The `analysis_data` field contains the full verification results. ```json theme={null} { "type": "session.status.submitted", "data": { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "organization_id": "org_123", "user_id": "usr_456", "sender_user_id": "usr_789", "external_id": "your-internal-id", "status": "SUBMITTED", "type": "session", "session_progress": "COMPLETED", "workflow_id": "wf_abc123", "workflow_steps": ["ID_VERIFICATION", "FACE_LIVENESS"], "created_at": "2026-03-23T14:30:00.000Z", "updated_at": "2026-03-23T14:35:00.000Z", "submitted_at": "2026-03-23T14:35:00.000Z", "uploads": { "id_front": true, "id_back": true, "selfie_front": true, "selfie_right": true, "selfie_left": true, "faceliveness": true }, "meta_data": { "applicantSubmissionIp": "192.168.1.1", "applicantSubmissionDevice": "Mac", "applicantSubmissionBrowser": "Chrome", "applicantViewTime": "2026-03-23T14:30:05.000Z", "applicantSubmissionLocation": "Chicago, United States", "applicantSubmissionLocationDetails": { "accuracyRadius": 500, "continent": "North America", "country": "United States", "countryIsoCode": "US", "latitude": 41.8483, "longitude": -87.6517, "subdivision": "Illinois", "timeZone": "America/Chicago" } }, "analysis_data": { "created_at": "2026-03-23T14:35:00.000Z", "id_matches_selfie": true, "faceliveness_score": 95.42, "id_matches_face_capture": true, "id_matches_liveness": true, "id_analysis_data": { "id_extracted_text": [ { "type": "FIRST_NAME", "value": "JOHN", "confidence": 97.69 }, { "type": "LAST_NAME", "value": "DOE", "confidence": 85.61 }, { "type": "DATE_OF_BIRTH", "value": "1995/05/19", "confidence": 96.33 } ], "expiry_date_pass": true, "valid_state_pass": true, "age_restriction_pass": true }, "compare_faces_data": { "face_match_confidence": 95.69 }, "pep_sanctions_data": { "peps": null, "sanctions": null, "both": null }, "adverse_media_data": { "total_hits": 0, "timestamp": "2026-03-23T14:35:00.000Z" } } } } ``` ### `session.status.verified` Sent when a session is marked as verified. The payload structure is the same as `session.status.submitted`, with `status` updated to `VERIFIED`. ```json theme={null} { "type": "session.status.verified", "data": { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "status": "VERIFIED", "...": "same structure as session.status.submitted" } } ``` ### `session.status.rejected` Sent when a session is marked as rejected. The payload structure is the same as `session.status.submitted`, with `status` updated to `REJECTED`. ```json theme={null} { "type": "session.status.rejected", "data": { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "status": "REJECTED", "...": "same structure as session.status.submitted" } } ``` ### `session.status.failed` Sent when a session is auto-failed by the system after reaching the workflow's configured maximum number of failed attempts. The payload structure is the same as `session.status.submitted`, with `status` set to `FAILED`, `session_progress` set to `COMPLETED`, and `meta_data.failureData` populated with the recorded attempts. ```json theme={null} { "type": "session.status.failed", "data": { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "status": "FAILED", "session_progress": "COMPLETED", "meta_data": { "applicantSubmissionIp": "192.168.1.1", "failureData": { "failedAttempts": 3, "attempts": [ { "reason": "NO_FACE_DETECTED", "slot": "PRIMARY", "failedAt": "2026-03-23T14:30:10.000Z" }, { "reason": "ID_TYPE_LOW_CONFIDENCE", "slot": "PRIMARY", "failedAt": "2026-03-23T14:31:42.000Z" }, { "reason": "DOC_TYPE_NOT_ALLOWED", "slot": "SECONDARY", "failedAt": "2026-03-23T14:33:08.000Z" } ] } }, "...": "same structure as session.status.submitted" } } ``` See [`failureData`](/api-reference/sessions/retrieve-session#failure-data-object) for the full field reference. `reason` is intentionally a loose string — new values may be added over time, so consumers should default-handle unknown values. ## What to do with each event | Event | Common use cases | | -------------------------- | ------------------------------------------------------------------------------------------------------------ | | `session.created` | Log session creation, update your internal records, send a confirmation to your system | | `session.status.submitted` | Trigger your review workflow, notify reviewers, update applicant status in your system | | `session.status.verified` | Grant access, activate accounts, send confirmation emails, update CRM records | | `session.status.rejected` | Notify applicant, flag for manual review, trigger re-verification flow | | `session.status.failed` | Notify applicant, surface a "contact support" path, and log `meta_data.failureData.attempts` for diagnostics | # Webhooks Source: https://docs.deepidv.com/webhooks/overview Listen for events from deepidv so your integration can automatically trigger reactions Webhooks allow you to receive real-time notifications when events happen in your deepidv account. Instead of polling the API, deepidv pushes event data to your application's webhook endpoint as a JSON payload via HTTP POST. Receiving webhook events helps you respond to asynchronous events, such as when a verification session is submitted, verified, or rejected. ## Get started To start receiving webhook events in your app: Set up an HTTPS endpoint on your server to receive POST requests from deepidv. Add your webhook endpoint URL in the deepidv Admin Console under **Integrations > Webhooks**. Choose which event types you want to subscribe to. Use your signing secret to verify that incoming requests are from deepidv. ## Register your endpoint You can configure webhooks in the [Admin Console](https://app.deepidv.com) under **Integrations > Webhooks**. When creating a webhook, you'll provide: | Field | Required | Description | | --------------- | -------- | ------------------------------------------------------------------------- | | Name | Yes | A label to identify this webhook | | Destination URL | Yes | The HTTPS endpoint where events will be sent. Must be publicly accessible | | Events | Yes | One or more event types to subscribe to | | Description | No | Optional description for your reference | After creating a webhook, deepidv generates a **signing secret** (prefixed with `whsec_`). Store this secret securely — you'll use it to verify that incoming webhook requests are from deepidv. Your signing secret is only shown once at creation time. If you lose it, you can reset it from the webhook settings, but this will invalidate the previous secret. ## Event types deepidv sends the following event types: | Event | Description | | -------------------------- | --------------------------------------------------------------------------------------- | | `session.created` | A new verification session has been created | | `session.status.submitted` | An applicant has completed and submitted their verification session | | `session.status.verified` | A verification session has been marked as verified | | `session.status.rejected` | A verification session has been marked as rejected | | `session.status.failed` | A verification session has hit the configured failed-attempt limit and been auto-failed | You can subscribe to all events or only the ones relevant to your integration. See the [Event Object](/webhooks/event-object) reference for the full payload structure. ## Create a handler Set up an HTTP endpoint that accepts POST requests with a JSON body. Your handler should: 1. Parse the JSON request body containing the [event object](/webhooks/event-object) 2. Verify the `deepidv-signature` header using your signing secret 3. Return a `200` status code as quickly as possible 4. Process the event asynchronously (after responding) deepidv considers any `2xx` response a successful delivery. If your endpoint returns a non-`2xx` status or times out (after 10 seconds), deepidv will retry the delivery. ### Example endpoint ```javascript Node.js (Express) theme={null} const express = require("express"); const app = express(); app.post("/webhooks/deepidv", express.json(), (req, res) => { const signature = req.headers["deepidv-signature"]; const event = req.body; // 1. Verify the signature if (signature !== process.env.DEEPIDV_WEBHOOK_SECRET) { return res.status(401).json({ error: "Invalid signature" }); } // 2. Return 200 immediately res.status(200).json({ received: true }); // 3. Process the event asynchronously switch (event.type) { case "session.status.submitted": // Applicant completed verification — trigger your review flow console.log("Session submitted:", event.data.id); break; case "session.status.verified": // Session verified — grant access, update records, etc. console.log("Session verified:", event.data.id); break; case "session.status.rejected": // Session rejected — notify applicant, flag for review, etc. console.log("Session rejected:", event.data.id); break; case "session.status.failed": // Session auto-failed after too many failed attempts — // inspect event.data.meta_data.failureData.attempts for context console.log("Session failed:", event.data.id); break; case "session.created": // New session created console.log("Session created:", event.data.id); break; default: console.log("Unhandled event type:", event.type); } }); app.listen(4242, () => console.log("Webhook server running on port 4242")); ``` ```python Python (Flask) theme={null} from flask import Flask, request, jsonify import os app = Flask(__name__) @app.route("/webhooks/deepidv", methods=["POST"]) def webhook(): signature = request.headers.get("deepidv-signature") event = request.get_json() # 1. Verify the signature if signature != os.environ.get("DEEPIDV_WEBHOOK_SECRET"): return jsonify(error="Invalid signature"), 401 # 2. Process the event event_type = event.get("type") data = event.get("data") if event_type == "session.status.submitted": # Applicant completed verification print(f"Session submitted: {data['id']}") elif event_type == "session.status.verified": # Session verified print(f"Session verified: {data['id']}") elif event_type == "session.status.rejected": # Session rejected print(f"Session rejected: {data['id']}") elif event_type == "session.status.failed": # Session auto-failed after too many failed attempts — # inspect data["meta_data"]["failureData"]["attempts"] for context print(f"Session failed: {data['id']}") elif event_type == "session.created": # New session created print(f"Session created: {data['id']}") else: print(f"Unhandled event type: {event_type}") # 3. Return 200 immediately return jsonify(received=True), 200 if __name__ == "__main__": app.run(port=4242) ``` ## Verify signatures Every webhook request includes a `deepidv-signature` header containing your signing secret. Compare this value against the signing secret shown when you created the webhook to verify the request is from deepidv. ```javascript theme={null} const signature = req.headers["deepidv-signature"]; if (signature !== process.env.DEEPIDV_WEBHOOK_SECRET) { // Request is not from deepidv — reject it return res.status(401).json({ error: "Invalid signature" }); } ``` Always verify the `deepidv-signature` header before processing any webhook event. Without verification, an attacker could send fake events to your endpoint to trigger unintended actions. ## Test your webhook You can send a test event from the Admin Console to verify your endpoint is working correctly: 1. Go to **Integrations > Webhooks** in the [Admin Console](https://app.deepidv.com) 2. Select your webhook 3. Click **Send Test Event** 4. Choose the event type to test 5. Check your endpoint for the received event The test event contains sample data that matches the structure of a real event, so you can use it to validate your handler logic. ## Retry behavior If your endpoint doesn't return a `2xx` response or doesn't respond within **10 seconds**, deepidv will retry the delivery. Events are retried with exponential backoff. | Scenario | Behavior | | -------------------- | --------------------------------- | | `2xx` response | Delivered successfully — no retry | | Non-`2xx` response | Retried with exponential backoff | | Timeout (10 seconds) | Logged as HTTP 408 — retried | | Endpoint unreachable | Retried with exponential backoff | ## Best practices ### Return a 200 response quickly Your endpoint should return a `200` response before performing any complex logic. Defer processing to a background job or queue to avoid timeouts. ### Handle duplicate events Your endpoint may occasionally receive the same event more than once. Guard against this by logging processed event IDs and skipping duplicates. ### Only listen to events you need Subscribe only to the event types your integration requires. This reduces unnecessary load on your server. ### Process events asynchronously Use an asynchronous queue to process incoming events. This prevents large spikes in webhook deliveries from overwhelming your server. ### Use HTTPS Your webhook endpoint must use HTTPS to receive events. deepidv will not send events to HTTP endpoints. ### Keep your signing secret secure Store your signing secret in an environment variable or secret manager — never hardcode it in your application. If you suspect your secret has been compromised, reset it immediately from the Admin Console. # Verification Links Source: https://docs.deepidv.com/workflows/verification-links Generate and manage verification links > Send secure verification URLs to your applicants — via email, SMS, or any channel you choose. No frontend development needed. A verification link is a unique, secure URL that takes your applicant directly into a verification flow hosted by deepidv. You configure the checks in a [workflow](/workflows/workflows), and deepidv handles everything the applicant sees — document capture, liveness prompts, data collection, and more. deepidv supports two types of links: **session links** (one-time, per-applicant) and **permalinks** (reusable, persistent). *** ## How It Works In the [Admin Console](https://app.deepidv.com/dashboard/workflow), drag and drop the services you need into a workflow. Each workflow gets a unique `workflowId`. Create a session for your applicant — either **no-code** from the Admin Console, or **programmatically** via a single API call to the [Create Session](/api-reference/sessions/create-session) endpoint. Each session produces a unique verification URL. Send the URL to your applicant through any channel — email, SMS, in-app message, or hand it off directly. deepidv can also send the invite automatically via email and SMS. Once the applicant completes the flow, results are available immediately in the Admin Console and via the [Retrieve Session](/api-reference/sessions/retrieve-session) API. *** ## Session Links Every session created through the API or Admin Console generates a **unique, one-time verification link**. This link is tied to a single session and a single applicant. When you create a session, the link is returned in the response: ```json theme={null} { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "session_url": "https://app.deepidv.com", "links": [] } ``` If you enable email or SMS invites, deepidv automatically sends this link to the applicant for you. *** ## Generating Session Links ### Method 1: No-code (via Admin Console) The fastest way to get started — no developer resources required. 1. Navigate to **Sessions** in the [Admin Console](https://app.deepidv.com) 2. Click **+ Create Session** 3. Select the workflow you want to use 4. Enter the applicant's details (name, email, phone) 5. The session is created and the invite is sent automatically ### Method 2: Via API The standard approach for automating verification links inside your application. Send a `POST` request to the `/v1/sessions` endpoint with your API key and applicant details: ```bash theme={null} curl -X POST https://api.deepidv.com/v1/sessions \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -d '{ "firstName": "Jane", "lastName": "Smith", "email": "jane.smith@example.com", "phone": "+14165557890", "workflowId": "your-workflow-id" }' ``` The API returns the `id` and `session_url`: ```json theme={null} { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "session_url": "https://app.deepidv.com", "links": [] } ``` Full endpoint details are available in the [Create Session API Reference](/api-reference/sessions/create-session). *** ## Delivery Controls Control how the verification link reaches your applicant: | Parameter | Default | Description | | ----------------- | ------- | ------------------------------------------------------------ | | `sendEmailInvite` | `true` | Sends the verification link to the applicant's email address | | `sendPhoneInvite` | `true` | Sends the verification link to the applicant via SMS | Set either to `false` if you'd rather deliver the link yourself: ```bash theme={null} curl -X POST https://api.deepidv.com/v1/sessions \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -d '{ "firstName": "Jane", "lastName": "Smith", "email": "jane.smith@example.com", "phone": "+14165557890", "sendEmailInvite": false, "sendPhoneInvite": false, "workflowId": "your-workflow-id" }' ``` The `session_url` in the response gives you the link to share however you prefer — embed it in your app, paste it into a chat, or build your own email template. *** ## Permalinks Permalinks are **persistent, reusable verification links** tied to a specific workflow in your organization. Unlike session links, they don't expire after a single use — each time an applicant opens a permalink, a new session is created automatically. ### When to use permalinks * **Embed on your website** — place a link on a landing page so applicants can start verification on their own * **Onboarding flows** — include a consistent link in welcome emails or signup sequences * **Repeat verifications** — let applicants re-verify without your team generating a new session each time * **In-person scenarios** — display as a QR code at a branch, event, or physical location *** Ship verification without building UI } /> *** ## Next Steps * **Build your first workflow** — head to the [Admin Console](https://app.deepidv.com/dashboard/workflow) to design your verification flow * **Set up your API key** — see the [Authentication](/authentication) guide to start creating sessions programmatically * **Review session results** — learn how to review and manage sessions in the [Manual Review](/review/manual-review) guide # Workflows Source: https://docs.deepidv.com/workflows/workflows Configure and manage verification workflows > Design multi-step verification flows by combining the exact services you need — then trigger them with a single API call or verification link. Workflows are the core of deepidv. Instead of configuring individual services every time, you build a reusable flow once — pick your services and reference that workflow by ID whenever you create a session. deepidv intelligently determines the optimal step order to minimize friction for the applicant. Head to [**app.deepidv.com/dashboard/workflow**](https://app.deepidv.com/dashboard/workflow) to create workflows and browse templates. *** ## How Workflows Work A workflow defines: * **Which services to run** — any combination of the services available on the platform * **The total session cost** — the sum of all enabled services When you create a session with a `workflowId`, deepidv automatically runs every check in that workflow. The platform intelligently orders the steps to reduce friction and maximize completion rates — results come back as a single session. *** ## Available Workflow Services Drag and drop any of these into your workflow: The total cost of a workflow session is the sum of each enabled service. Only add what you actually need — see [Pricing](/pricing) for per-service rates. *** ## Creating a Workflow ### From the Admin Console 1. Go to [**Workflows**](https://app.deepidv.com/dashboard/workflow) in the sidebar 2. Click **Create Workflow** 3. Pick a name and drag and drop the services you want to design your workflow 4. Save — deepidv handles the step ordering automatically You can also start from a **template** — pre-configured workflows for common use cases that you can customize to fit your needs. ### Via the API Manage workflows programmatically through the [Workflows API](/api-reference/workflows/list-workflows). *** ## Workflow Templates Templates are pre-built starting points designed for the most common verification scenarios. Pick one, tweak the services, and you're live. ### KYC Onboarding The go-to template for full identity verification during user signup. * **Starts with:** ID Verification * **Commonly added:** * `[+]` **Face Liveness** — confirm the applicant is physically present * `[+]` **PEP & Sanctions** — screen against global watchlists * `[+]` **Adverse Media** — surface negative press or legal mentions * `[+]` **Phone Verification** — verify phone ownership via live call * `[+]` **Custom Forms** — collect additional data points specific to your onboarding *** ### Lending & Financial Services Built for lenders, brokers, and financial service providers who need both identity and financial data. * **Starts with:** ID Verification * **Commonly added:** * `[+]` **Face Liveness** — stop spoofed applications * `[+]` **Bank Statement Sync** — pull transaction data directly from the applicant's bank * `[+]` **AI Bank Analysis** — automated income, spending, and affordability breakdown * `[+]` **PEP & Sanctions** — compliance screening * `[+]` **Title Search** — property ownership and lien lookups * `[+]` **Document Upload** — collect pay stubs, tax returns, or other supporting docs with fraud detection *** ### Age-Gated Access A lightweight flow for services that need to confirm the applicant meets a minimum age. * **Starts with:** Age Estimation via selfie * **Fallback logic:** If the estimate is borderline or below your threshold, the workflow can trigger a full **ID Verification** step to confirm date of birth from the document * **Commonly added:** * `[+]` **Face Liveness** — prevent photo or video spoofing *** ### Property & Real Estate Designed for real estate transactions, property management, and title companies. * **Starts with:** ID Verification * **Commonly added:** * `[+]` **Title Search** — pull ownership history, liens, and encumbrances * `[+]` **Address Verification** — AI-powered location confirmation * `[+]` **Document Upload** — collect lease agreements, contracts, or proof of ownership with fraud detection * `[+]` **Custom Forms** — capture property details or tenant information *** ### Custom Workflow Start from scratch and build exactly what your use case requires. Drag and drop any combination of services to design your workflow and save. Every template can be fully customized after creation. Add or remove services at any time from the Admin Console. *** ## Using a Workflow Once you've built a workflow, reference it by `workflowId` when creating sessions: ```bash theme={null} curl -X POST https://api.deepidv.com/v1/sessions \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -d '{ "firstName": "Jane", "lastName": "Smith", "email": "jane.smith@example.com", "phone": "+14165557890", "workflowId": "wf_abc123" }' ``` Find your `workflowId` in the [Admin Console](https://app.deepidv.com/dashboard/workflow) under **Workflows**. *** ## Integration Flow Your backend creates a session with deepidv, receives a verification URL, and redirects the applicant. deepidv handles the entire user-facing experience and notifies your server when results are ready. ```mermaid theme={null} sequenceDiagram participant Applicant participant Your Server participant deepidv Your Server->>deepidv: POST /v1/sessions (with workflowId) deepidv-->>Your Server: Returns session ID + verification URL Your Server->>Applicant: Send verification link (email/SMS) Applicant->>deepidv: Opens link, completes verification steps deepidv-->>Your Server: Webhook with status update Your Server->>Applicant: Notify of result ``` *** ## Common Use Cases | Use Case | Recommended Template | Key Services | | ------------------------------ | ---------------------- | --------------------------------------------------------- | | Standard user onboarding | KYC Onboarding | ID Verification, Face Liveness, PEP & Sanctions | | Mortgage or loan application | Lending & Financial | ID, Bank Sync, AI Analysis, Title Search, Doc Upload | | Age-restricted product/content | Age-Gated Access | Age Estimation, Face Liveness, ID fallback | | Tenant screening | Property & Real Estate | ID, Address Verification, Custom Forms, Doc Upload | | Compliance-heavy onboarding | KYC Onboarding | ID, Face Liveness, PEP, Adverse Media, Phone Verification | | Quick document collection | Custom | Document Upload, Custom Forms | *** ## Managing Workflows ### List all workflows ```bash theme={null} curl -X GET https://api.deepidv.com/v1/workflows \ -H "x-api-key: YOUR_API_KEY" ``` ### Retrieve a specific workflow ```bash theme={null} curl -X GET https://api.deepidv.com/v1/workflows/wf_abc123 \ -H "x-api-key: YOUR_API_KEY" ``` ### View sessions for a workflow ```bash theme={null} curl -X GET "https://api.deepidv.com/v1/sessions?workflow_id=wf_abc123" \ -H "x-api-key: YOUR_API_KEY" ``` See the full [Workflows API Reference](/api-reference/workflows/list-workflows) and [Sessions API Reference](/api-reference/sessions/list-sessions) for complete endpoint details. *** ## Getting Started Open the workflow builder and start configuring. End-to-end walkthrough from account setup to first session.