Create Workflow
curl --request POST \
--url https://api.deepidv.com/v1/workflowsimport requests
url = "https://api.deepidv.com/v1/workflows"
response = requests.post(url)
print(response.text)const options = {method: 'POST'};
fetch('https://api.deepidv.com/v1/workflows', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.deepidv.com/v1/workflows",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.deepidv.com/v1/workflows"
req, _ := http.NewRequest("POST", url, nil)
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.deepidv.com/v1/workflows")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.deepidv.com/v1/workflows")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
response = http.request(request)
puts response.read_body{
"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": {} }
]
}
}
Management API
Create Workflow
Create a new verification workflow
POST
/
v1
/
workflows
Create Workflow
curl --request POST \
--url https://api.deepidv.com/v1/workflowsimport requests
url = "https://api.deepidv.com/v1/workflows"
response = requests.post(url)
print(response.text)const options = {method: 'POST'};
fetch('https://api.deepidv.com/v1/workflows', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.deepidv.com/v1/workflows",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.deepidv.com/v1/workflows"
req, _ := http.NewRequest("POST", url, nil)
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.deepidv.com/v1/workflows")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.deepidv.com/v1/workflows")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
response = http.request(request)
puts response.read_body{
"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": {} }
]
}
}
POST /v1/workflows
ID_VERIFICATION, FACE_LIVENESS, AGE_ESTIMATION, PEP_SANCTIONS, ADVERSE_MEDIA, IP_JURISDICTION, VPN_DETECTION, INJECTION_DETECTION, ANTI_CHEAT.
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) |
steps[].config | object | No | Step-specific configuration overrides (see 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 |
IP_JURISDICTION | iGaming: block/allow by the applicant’s resolved country + datacenter-IP handling |
VPN_DETECTION | iGaming: detect VPN / Tor / datacenter (anonymizer) connections |
INJECTION_DETECTION | iGaming: detect injected media, virtual cameras, and emulators |
ANTI_CHEAT | iGaming: biometric duplicate detection, multi-accounting linkage, and self-exclusion |
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.IP_JURISDICTION config
Config keys are snake_case. Enforcement actions are one ofblock, flag, step-up, allow.
| Field | Type | Default | Description |
|---|---|---|---|
allowed_jurisdictions | string[] | [] | Jurisdictions that are allowed (allowlist) — ISO 3166 country codes (CA) or country-subdivision codes (CA-ON, US-WA). When non-empty, locations outside the list are out-of-jurisdiction |
blocked_jurisdictions | string[] | [] | Jurisdictions to block, same code format. Checked before the allowlist |
out_of_jurisdiction_action | string | "block" | Action when the resolved location is blocked (or not in a non-empty allowlist) |
datacenter_ip_action | string | "flag" | Action when an in-jurisdiction IP resolves to a datacenter / hosting network. allow skips the datacenter rule |
trusted_ip_list | string[] | [] | CIDR ranges or ASNs (AS64496) exempt from the datacenter rule only — they do not bypass the jurisdiction lists |
escalation_type | string | "none" | Escalation type to run when the action is step-up |
VPN_DETECTION config
| Field | Type | Default | Description |
|---|---|---|---|
vpn_enabled | boolean | true | Detect commercial VPN ranges |
vpn_action | string | "step-up" | Action when a VPN is detected |
datacenter_enabled | boolean | true | Detect datacenter / hosting ranges |
datacenter_action | string | "block" | Action when a datacenter IP is detected |
tor_enabled | boolean | true | Detect Tor exit nodes |
tor_action | string | "block" | Action when a Tor exit node is detected |
trusted_ip_list | string[] | [] | IPs or CIDR ranges that bypass the check |
trusted_asn_list | string[] | [] | ASNs that bypass the check |
confidence_threshold | integer | 70 | Minimum confidence (1–100) to treat a hit as positive. Matches carry fixed confidences — Tor 95, datacenter 90, VPN 85 — so a threshold above 85 disables VPN hits, above 90 disables datacenter hits |
escalation_type | string | "none" | Escalation type to run when the action is step-up |
INJECTION_DETECTION config
| Field | Type | Default | Description |
|---|---|---|---|
action_on_detection | string | "block" | Action when injection/spoofing is detected. One of block, flag, step-up, rechallenge, allow |
automation_handling | string | "block" | Action when automation/emulator signals fire. One of block, flag, step-up, ignore |
confidence_threshold | integer | 70 | Minimum trust confidence (1–100) for a capture to pass. The client-reported media_source and frame_timing scores (higher = cleaner) are combined 0.6 / 0.4; a result below this threshold is a detection |
escalation_type | string | "none" | Escalation type to run when the action is step-up |
Standalone
POST /v1/igaming/injection calls apply these defaults even when the workflow has no INJECTION_DETECTION step — the other three iGaming checks skip when their step is absent. See Injection detection.step-up requires a configured escalation. For IP jurisdiction, VPN detection, and injection detection, an action that resolves to step-up is only emitted when the step also sets an escalation_type (other than none). If a step-up would fire with no escalation configured, the check downgrades it to flag — so it never returns a step-up with nothing for you to step up to. (Anti-cheat uses manual-review instead of step-up.)ANTI_CHEAT config
Enforcement actions are one offlag, manual-review, block, allow. The step has three groups: biometric duplicate detection, multi-accounting linkage, and self-exclusion.
| Field | Type | Default | Description |
|---|---|---|---|
duplicate_enabled | boolean | true | Enable biometric face-dedup |
match_threshold | integer | 85 | Face-match similarity (1–100) at or above which two faces are a duplicate |
action_on_duplicate | string | "flag" | Action when the face matches an existing enrollment |
multi_account_enabled | boolean | true | Enable multi-accounting linkage |
signal_ip_enabled | boolean | true | Use a shared submission IP as a linkage signal |
signal_ip_weight | number | 0.2 | Weight of the IP signal (contributes to the score only — see note) |
signal_biometric_enabled | boolean | true | Use a biometric face match as a linkage signal |
signal_biometric_weight | number | 1.0 | Weight of the biometric signal |
signal_document_enabled | boolean | true | Use a shared document number as a linkage signal |
signal_document_weight | number | 0.9 | Weight of the document signal |
signal_namedob_enabled | boolean | true | Use a shared name + date of birth as a linkage signal |
signal_namedob_weight | number | 0.1 | Weight of the name/DOB signal |
signal_device_enabled | boolean | true | Use a shared device fingerprint as a linkage signal |
signal_device_weight | number | 0.5 | Weight of the device signal |
link_sensitivity | integer | 2 | How many distinct signal types must match another account before they are linked (1–5) |
action_on_multi_account | string | "flag" | Action when accounts are linked |
self_exclusion_enabled | boolean | true | Enforce the self-exclusion registry — block applicants whose document number or matched face is excluded |
Multi-accounting fires on the count of distinct matched signal types, not on the
weighted score: two accounts link when they share at least
link_sensitivity
signal types (IP, biometric, document, name/DOB, device). The *_weight values
only affect the reported link score — raising a weight does not make a single
signal trigger a link on its own. To make linkage stricter or looser, change
link_sensitivity, not the weights.The IP multi-accounting signal reads the applicant’s submission IP from the
session. In deepidv’s hosted verification flow this is captured automatically;
in a pure server-to-server integration it may not be set, in which case the IP
signal simply doesn’t contribute (the other signals still apply).
Fraud Analysis & Escalation
TheID_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:
{
"id": "ID_VERIFICATION",
"config": {
"enable_fraud_analysis": true
}
}
{
"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
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" }
]
}'
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();
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.| 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 |
{
"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": {} }
]
}
}