didit-verification-management
Didit Identity Verification Platform
The single skill for the entire Didit verification platform. Covers account creation, session management, workflow configuration, questionnaires, user management, billing, blocklist, and webhook configuration — 45+ endpoints across 9 categories.
For standalone verification APIs (ID scan, liveness, face match, AML, etc.), see the individual didit-* skills.
API Reference Links:
- Account Setup: Register | Verify Email | Login | Get Credentials
- Sessions: Create | Retrieve | List | Delete | Update Status | PDF | Share | Import
- Workflows: Create | List | Get | Update | Delete
- Questionnaires: Create | List | Get | Update | Delete
- Users: List | Get | Update | Delete
- Billing: Balance | Top Up
- Blocklist: Add | Remove | List
- Session Operations: Batch Delete | List Reviews | Create Review
- Webhook Config: Get | Update
- Guides: Programmatic Registration | Webhooks | AI Agent Integration | API Overview
Getting Started — Zero to Verifying
Go from nothing to a live verification link in 4 API calls, no browser needed:
import requests
# 1. Register (any email, no business email required)
requests.post("https://apx.didit.me/auth/v2/programmatic/register/",
json={"email": "you@gmail.com", "password": "MyStr0ng!Pass"})
# 2. Check email for 6-char OTP, then verify → get api_key
resp = requests.post("https://apx.didit.me/auth/v2/programmatic/verify-email/",
json={"email": "you@gmail.com", "code": "A3K9F2"})
api_key = resp.json()["application"]["api_key"]
headers = {"x-api-key": api_key, "Content-Type": "application/json"}
# 3. Create a KYC workflow
wf = requests.post("https://verification.didit.me/v3/workflows/",
headers=headers,
json={"workflow_label": "My KYC", "workflow_type": "kyc",
"is_liveness_enabled": True, "is_face_match_enabled": True}).json()
# 4. Create a session → send user to the URL
session = requests.post("https://verification.didit.me/v3/session/",
headers=headers,
json={"workflow_id": wf["uuid"], "vendor_data": "user-123"}).json()
print(f"Send user to: {session['url']}")
To add credits: GET /v3/billing/balance/ to check, POST /v3/billing/top-up/ with {"amount_in_dollars": 50} for a Stripe checkout link.
Authentication
Two auth schemes are used across the platform:
| Endpoints | Auth | Header |
|---|---|---|
| Register, Verify Email, Login | None | (unauthenticated) |
| List Organizations, Get Credentials | Bearer | Authorization: Bearer <access_token> |
| Everything else (sessions, workflows, etc.) | API Key | x-api-key: <api_key> |
Get your api_key via programmatic registration (above) or from Didit Business Console → API & Webhooks.
Account Setup
Base URL: https://apx.didit.me/auth/v2
1. Register
POST /programmatic/register/
| Body | Type | Required | Description |
|---|---|---|---|
email |
string | Yes | Any email address |
password |
string | Yes | Min 8 chars, 1 upper, 1 lower, 1 digit, 1 special |
Response (201): {"message": "Registration successful...", "email": "..."}
Rate limit: 5 per IP per hour.
2. Verify Email & Get Credentials
POST /programmatic/verify-email/
| Body | Type | Required | Description |
|---|---|---|---|
email |
string | Yes | Same email from register |
code |
string | Yes | 6-character alphanumeric OTP from email |
Response (200):
{
"access_token": "eyJ...",
"refresh_token": "eyJ...",
"expires_in": 86400,
"organization": {"uuid": "...", "name": "..."},
"application": {"uuid": "...", "client_id": "...", "api_key": "YOUR_KEY_HERE"}
}
application.api_key is the x-api-key for all subsequent calls.
3. Login (Existing Accounts)
POST /programmatic/login/
| Body | Type | Required | Description |
|---|---|---|---|
email |
string | Yes | Account email |
password |
string | Yes | Account password |
Response (200): {"access_token": "...", "refresh_token": "...", "expires_in": 86400}
Progressive lockout: 5 fails = 15min, 10 = 1hr, 20 = 24hr.
4. List Organizations
GET /organizations/me/
Auth: Authorization: Bearer <access_token>
Response (200): Array of {"uuid": "...", "name": "...", "contact_email": "..."}
5. Get Application Credentials
GET /organizations/me/{org_id}/applications/{app_id}/
Auth: Authorization: Bearer <access_token>
Response (200): {"uuid": "...", "client_id": "...", "api_key": "..."}
Workflows
Base URL: https://verification.didit.me/v3
Workflows define verification steps, thresholds, and accepted documents. Each has a UUID used as workflow_id when creating sessions.
Workflow Types:
| Type | Purpose | Typical Features |
|---|---|---|
kyc |
Full identity verification (ID + selfie) | ID Verification, Liveness, Face Match, AML, NFC |
adaptive_age_verification |
Age gating with ID fallback for borderline cases | Age Estimation, Liveness, per-country age restrictions |
biometric_authentication |
Re-verify returning users (no document) | Liveness, Face Match against stored portrait |
address_verification |
Verify proof of address documents | Proof of Address, geocoding, name matching |
questionnaire_verification |
Custom form/questionnaire verification | Questionnaire, optional ID/liveness add-ons |
email_verification |
Email OTP verification as a workflow | Email send/check, breach/disposable detection |
phone_verification |
Phone OTP verification as a workflow | Phone send/check, carrier/VoIP detection |
Features (toggleable per workflow): ID Verification, Liveness, Face Match, NFC, AML, Phone, Email, Proof of Address, Database Validation, IP Analysis, Age Estimation, Questionnaire.
1. List Workflows
GET /v3/workflows/
Response (200): Array of workflow objects with uuid, workflow_label, workflow_type, is_default, features, total_price.
2. Create Workflow
POST /v3/workflows/
| Body | Type | Default | Description |
|---|---|---|---|
workflow_label |
string | auto | Display name |
workflow_type |
string | kyc |
Workflow template type |
is_default |
boolean | false |
Set as default |
is_liveness_enabled |
boolean | false |
Liveness detection |
face_liveness_method |
string | passive |
"passive", "active_3d", "flashing" |
face_liveness_score_decline_threshold |
integer | 50 |
Below this → auto-decline |
is_face_match_enabled |
boolean | false |
Selfie-to-document match |
face_match_score_decline_threshold |
integer | 50 |
Below this → auto-decline |
face_match_score_review_threshold |
integer | 70 |
Below this → manual review |
is_aml_enabled |
boolean | false |
AML/PEP/sanctions screening |
aml_decline_threshold |
integer | 80 |
Above this → auto-decline |
is_phone_verification_enabled |
boolean | false |
Phone verification step |
is_email_verification_enabled |
boolean | false |
Email verification step |
is_database_validation_enabled |
boolean | false |
Gov database validation |
is_ip_analysis_enabled |
boolean | false |
IP risk analysis |
is_nfc_enabled |
boolean | false |
NFC chip reading (mobile only, ePassports) |
is_age_restrictions_enabled |
boolean | false |
Enable per-country age restrictions (for adaptive_age_verification) |
documents_allowed |
object | {} |
Restrict accepted countries/doc types (empty = accept all) |
duplicated_user_action |
string | no_action |
no_action, review, decline (set after creation via update) |
max_retry_attempts |
integer | 3 |
Max retries per session |
retry_window_days |
integer | 7 |
Days within which retries are allowed |
Response (201): Workflow object with uuid.
wf = requests.post("https://verification.didit.me/v3/workflows/",
headers={"x-api-key": API_KEY, "Content-Type": "application/json"},
json={"workflow_label": "KYC + AML", "workflow_type": "kyc",
"is_liveness_enabled": True, "is_face_match_enabled": True,
"is_aml_enabled": True}).json()
3. Get Workflow
GET /v3/workflows/{settings_uuid}/
4. Update Workflow
PATCH /v3/workflows/{settings_uuid}/
Partial update — only send fields to change.
5. Delete Workflow
DELETE /v3/workflows/{settings_uuid}/
Response: 204 No Content. Existing sessions are not affected.
Sessions
Base URL: https://verification.didit.me/v3
Sessions are the core unit of verification. Every verification starts by creating a session linked to a workflow.
Lifecycle: Create → User verifies at URL → Webhook/poll decision → Optionally update status
Statuses: Not Started, In Progress, In Review, Approved, Declined, Abandoned, Expired, Resubmitted
Rate limits: 300 req/min per method. Session creation: 600/min. Decision polling: 100/min.
1. Create Session
POST /v3/session/
| Body | Type | Required | Description |
|---|---|---|---|
workflow_id |
uuid | Yes | Workflow UUID |
vendor_data |
string | No | Your user identifier |
callback |
url | No | Redirect URL (Didit appends verificationSessionId + status) |
callback_method |
string | No | "initiator", "completer", or "both" |
metadata |
JSON string | No | Custom data stored with session |
language |
string | No | ISO 639-1 UI language |
contact_details.email |
string | No | Pre-fill email for email verification step |
contact_details.phone |
string | No | Pre-fill phone (E.164) for phone verification step |
contact_details.send_notification_emails |
boolean | No | Send status update emails to user |
contact_details.email_lang |
string | No | Language for email notifications (ISO 639-1) |
expected_details.first_name |
string | No | Triggers mismatch warning if different (fuzzy match) |
expected_details.last_name |
string | No | Expected last name (fuzzy match) |
expected_details.date_of_birth |
string | No | YYYY-MM-DD |
expected_details.gender |
string | No | "M", "F", or null |
expected_details.nationality |
string | No | ISO 3166-1 alpha-3 country code |
expected_details.id_country |
string | No | ISO alpha-3 for expected ID document country (overrides nationality) |
expected_details.poa_country |
string | No | ISO alpha-3 for expected PoA document country |
expected_details.address |
string | No | Expected address (human-readable, for PoA matching) |
expected_details.identification_number |
string | No | Expected document/personal/tax number |
expected_details.ip_address |
string | No | Expected IP address (logs warning if different) |
portrait_image |
base64 | No | Reference portrait for Biometric Auth (max 1MB) |
Response (201):
{
"session_id": "...",
"session_number": 1234,
"session_token": "abcdef123456",
"url": "https://verify.didit.me/session/abcdef123456",
"status": "Not Started",
"workflow_id": "..."
}
Send the user to url to complete verification.
2. Retrieve Session (Get Decision)
GET /v3/session/{sessionId}/decision/
Returns all verification results. Image URLs expire after 60 minutes.
Response (200): Full decision with status, features, id_verifications, liveness_checks, face_matches, aml_screenings, phone_verifications, email_verifications, poa_verifications, database_validations, ip_analyses, reviews.
3. List Sessions
GET /v3/sessions/
| Query | Type | Default | Description |
|---|---|---|---|
vendor_data |
string | — | Filter by your user identifier |
status |
string | — | Filter by status (e.g. Approved, Declined, In Review) |
country |
string | — | Filter by ISO 3166-1 alpha-3 country code |
workflow_id |
string | — | Filter by workflow UUID |
offset |
integer | 0 |
Number of items to skip |
limit |
integer | 20 |
Max items to return |
Response (200): Paginated list with count, next, previous, results[].
4. Delete Session
DELETE /v3/session/{sessionId}/delete/
Response: 204 No Content. Permanently deletes all associated data.
5. Batch Delete Sessions
POST /v3/sessions/delete/
| Body | Type | Description |
|---|---|---|
session_numbers |
array | List of session numbers to delete |
delete_all |
boolean | Delete all sessions (use with caution) |
6. Update Session Status
PATCH /v3/session/{sessionId}/update-status/
| Body | Type | Required | Description |
|---|---|---|---|
new_status |
string | Yes | "Approved", "Declined", or "Resubmitted" |
comment |
string | No | Reason for change |
send_email |
boolean | No | Send notification email |
email_address |
string | Conditional | Required when send_email is true |
email_language |
string | No | Email language (default: "en") |
nodes_to_resubmit |
array | No | For Resubmitted: [{"node_id": "feature_ocr", "feature": "OCR"}] |
Resubmit requires session to be Declined, In Review, or Abandoned.
7. Generate PDF Report
GET /v3/session/{sessionId}/generate-pdf
Rate limit: 100 req/min.
8. Share Session
POST /v3/session/{sessionId}/share/
Generates a share_token for B2B KYC sharing. Only works for finished sessions.
9. Import Shared Session
POST /v3/session/import-shared/
| Body | Type | Required | Description |
|---|---|---|---|
share_token |
string | Yes | Token from sharing partner |
trust_review |
boolean | Yes | true: keep original status; false: set to "In Review" |
workflow_id |
string | Yes | Your workflow ID |
vendor_data |
string | No | Your user identifier |
A session can only be imported once per partner application.
10. List Session Reviews
GET /v3/sessions/{session_id}/reviews/
Response (200): Array of review activity items:
[
{
"id": 1,
"action": "status_change",
"old_status": "In Review",
"new_status": "Approved",
"note": "Document verified manually",
"created_at": "2025-06-01T15:00:00Z"
}
]
11. Create Session Review
POST /v3/sessions/{session_id}/reviews/
| Body | Type | Required | Description |
|---|---|---|---|
new_status |
string | Yes | "Approved", "Declined", or "In Review" |
comment |
string | No | Review note |
Response (201): The created review item.
Blocklist
Block entities from a session to auto-decline future matches.
1. Add to Blocklist
POST /v3/blocklist/add/
| Body | Type | Required | Description |
|---|---|---|---|
session_id |
uuid | Yes | Session to blocklist items from |
blocklist_face |
boolean | No | Block biometric face template |
blocklist_document |
boolean | No | Block document fingerprint |
blocklist_phone |
boolean | No | Block phone number |
blocklist_email |
boolean | No | Block email address |
Warning tags on match: FACE_IN_BLOCKLIST, ID_DOCUMENT_IN_BLOCKLIST, PHONE_NUMBER_IN_BLOCKLIST, EMAIL_IN_BLOCKLIST
2. Remove from Blocklist
POST /v3/blocklist/remove/
Same structure with unblock_face, unblock_document, unblock_phone, unblock_email.
3. List Blocklist
GET /v3/blocklist/
| Query | Type | Description |
|---|---|---|
item_type |
string | "face", "document", "phone", "email". Omit for all. |
Questionnaires
Custom forms attached to verification workflows. Support 7 element types: short_text, long_text, multiple_choice, checkbox, file_upload, date, number.
1. List Questionnaires
GET /v3/questionnaires/
2. Create Questionnaire
POST /v3/questionnaires/
| Body | Type | Required | Description |
|---|---|---|---|
title |
string | Yes | Display title |
description |
string | No | Description shown to users |
default_language |
string | No | Default language code |
languages |
array | No | Supported languages |
form_elements |
array | Yes | Question objects |
Form element:
| Field | Type | Required | Description |
|---|---|---|---|
element_type |
string | Yes | One of the 7 types above |
label |
object | Yes | Translations: {"en": "Question?", "es": "¿Pregunta?"} |
is_required |
boolean | No | Mandatory answer |
options |
array | Conditional | Required for multiple_choice/checkbox |
requests.post("https://verification.didit.me/v3/questionnaires/",
headers=headers,
json={
"title": "Employment Details",
"default_language": "en",
"form_elements": [
{"element_type": "short_text",
"label": {"en": "Occupation?"}, "is_required": True},
{"element_type": "multiple_choice",
"label": {"en": "Employment status"},
"options": [{"label": {"en": "Employed"}}, {"label": {"en": "Student"}}]},
]
})
3. Get Questionnaire
GET /v3/questionnaires/{questionnaire_uuid}/
4. Update Questionnaire
PATCH /v3/questionnaires/{questionnaire_uuid}/
5. Delete Questionnaire
DELETE /v3/questionnaires/{questionnaire_uuid}/
Response: 204 No Content.
Users
Manage verified individuals identified by vendor_data.
1. List Users
GET /v3/users/
| Query | Type | Description |
|---|---|---|
status |
string | Approved, Declined, In Review, Pending |
search |
string | Search by name or identifier |
country |
string | ISO 3166-1 alpha-3 |
limit |
integer | Results per page (max 200) |
offset |
integer | Pagination offset |
Response (200): Paginated list with vendor_data, full_name, status, session_count, issuing_states, approved_emails, approved_phones.
2. Get User
GET /v3/users/{vendor_data}/
3. Update User
PATCH /v3/users/{vendor_data}/
| Body | Type | Description |
|---|---|---|
display_name |
string | Custom display name |
status |
string | Manual override: Approved, Declined, In Review |
metadata |
object | Custom JSON metadata |
4. Batch Delete Users
POST /v3/users/delete/
| Body | Type | Description |
|---|---|---|
vendor_data_list |
array | List of vendor_data strings |
delete_all |
boolean | Delete all users |
Billing
1. Get Credit Balance
GET /v3/billing/balance/
Response (200):
{
"balance": "142.5000",
"auto_refill_enabled": true,
"auto_refill_amount": "100.0000",
"auto_refill_threshold": "10.0000"
}
2. Top Up Credits
POST /v3/billing/top-up/
| Body | Type | Required | Description |
|---|---|---|---|
amount_in_dollars |
number | Yes | Minimum $50 |
success_url |
string | No | Redirect after payment |
cancel_url |
string | No | Redirect on cancel |
Response (200):
{
"checkout_session_id": "cs_live_...",
"checkout_session_url": "https://checkout.stripe.com/..."
}
Present checkout_session_url to the user for payment.
Webhook Configuration
Set up webhooks programmatically — no console needed.
1. Get Webhook Configuration
GET /v3/webhook/
Response (200):
{
"webhook_url": "https://myapp.com/webhooks/didit",
"webhook_version": "v3",
"secret_shared_key": "whsec_a1b2c3d4e5f6g7h8i9j0...",
"capture_method": "both",
"data_retention_months": null
}
| Field | Type | Description |
|---|---|---|
webhook_url |
string/null | URL where notifications are sent (null if not configured) |
webhook_version |
string | "v1", "v2", or "v3" (v3 recommended) |
secret_shared_key |
string | HMAC secret for verifying webhook signatures |
capture_method |
string | "mobile", "desktop", or "both" |
data_retention_months |
integer/null | Months to retain session data (null = unlimited) |
2. Update Webhook Configuration
PATCH /v3/webhook/
| Body | Type | Required | Description |
|---|---|---|---|
webhook_url |
string/null | No | URL for notifications (set null to disable) |
webhook_version |
string | No | "v1", "v2", or "v3" |
rotate_secret_key |
boolean | No | true to generate a new secret (old one immediately invalidated) |
capture_method |
string | No | "mobile", "desktop", or "both" |
data_retention_months |
integer/null | No | 1–120 months, or null for unlimited |
Example — set webhook URL:
requests.patch(
"https://verification.didit.me/v3/webhook/",
headers={"x-api-key": API_KEY, "Content-Type": "application/json"},
json={"webhook_url": "https://myapp.com/webhooks/didit", "webhook_version": "v3"},
)
Example — rotate secret:
r = requests.patch(
"https://verification.didit.me/v3/webhook/",
headers={"x-api-key": API_KEY, "Content-Type": "application/json"},
json={"rotate_secret_key": True},
)
new_secret = r.json()["secret_shared_key"]
Example — disable webhooks:
requests.patch(
"https://verification.didit.me/v3/webhook/",
headers={"x-api-key": API_KEY, "Content-Type": "application/json"},
json={"webhook_url": None},
)
Response (200): Same shape as GET — returns the updated configuration.
Webhook Events & Signatures
Didit sends POST requests to your webhook URL when session status changes. Retries up to 2 times with exponential backoff (1 min, 4 min).
Payload
{
"session_id": "...",
"status": "Approved",
"webhook_type": "status.updated",
"vendor_data": "user-123",
"timestamp": 1627680000,
"decision": { ... }
}
Event types: status.updated (status change), data.updated (KYC/POA data manually updated).
Signature Verification (V2 — recommended)
Two headers: X-Signature-V2 (HMAC-SHA256 hex) and X-Timestamp (Unix seconds).
import hashlib, hmac, time, json
def verify_webhook_v2(body_dict: dict, signature: str, timestamp: str, secret: str) -> bool:
if abs(time.time() - int(timestamp)) > 300:
return False
def process_value(v):
if isinstance(v, float) and v == int(v):
return int(v)
if isinstance(v, dict):
return {k: process_value(val) for k, val in v.items()}
if isinstance(v, list):
return [process_value(i) for i in v]
return v
canonical = json.dumps(process_value(body_dict), sort_keys=True, ensure_ascii=False, separators=(",", ":"))
message = f"{timestamp}:{canonical}"
expected = hmac.new(secret.encode(), message.encode(), hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)
Simple Signature (Fallback)
Header: X-Signature-Simple — HMAC of key fields only.
def verify_webhook_simple(session_id, status, webhook_type, timestamp, signature, secret):
message = f"{timestamp}:{session_id}:{status}:{webhook_type}"
expected = hmac.new(secret.encode(), message.encode(), hashlib.sha256).hexdigest()
return hmac.compare_digest(signature, expected)
Error Responses (All Endpoints)
| Code | Meaning | Action |
|---|---|---|
400 |
Invalid request | Check required fields and formats |
401 |
Invalid or missing API key | Verify x-api-key header |
403 |
Insufficient credits or no permission | Check balance, API key permissions |
404 |
Resource not found | Verify IDs |
429 |
Rate limited | Check Retry-After header, exponential backoff |
Common Workflows
Full KYC Onboarding
1. POST /programmatic/register/ → register
2. POST /programmatic/verify-email/ → get api_key
3. POST /v3/workflows/ → create KYC workflow
4. PATCH /v3/webhook/ → set webhook_url + webhook_version "v3"
5. POST /v3/session/ → create session → get URL
6. User completes verification at URL
7. Webhook fires → GET /v3/session/{id}/decision/ → read results
Programmatic Review + Blocklist
1. Webhook: status "In Review"
2. GET /v3/session/{id}/decision/ → inspect results
3. If fraud: PATCH update-status → Declined + POST /v3/blocklist/add/
If legit: PATCH update-status → Approved
B2B KYC Sharing
Service A: POST /v3/session/{id}/share/ → get share_token
Service B: POST /v3/session/import-shared/ → import with trust_review=true
Check Balance Before Sessions
1. GET /v3/billing/balance/ → check if balance > 0
2. If low: POST /v3/billing/top-up/ → get Stripe checkout URL
3. POST /v3/session/ → create session
Questionnaire + Workflow
1. POST /v3/questionnaires/ → create form → save uuid
2. POST /v3/workflows/ → questionnaire_verification type
3. POST /v3/session/ → session with workflow_id
Utility Scripts
setup_account.py — Register and verify accounts
pip install requests
python scripts/setup_account.py register you@gmail.com 'MyStr0ng!Pass'
# (check email for code)
python scripts/setup_account.py verify you@gmail.com A3K9F2
# Prints api_key, org_uuid, app_uuid
python scripts/setup_account.py login you@gmail.com 'MyStr0ng!Pass'
manage_workflows.py — CRUD workflows
export DIDIT_API_KEY="your_key"
python scripts/manage_workflows.py list
python scripts/manage_workflows.py create --label "My KYC" --type kyc --liveness --face-match
python scripts/manage_workflows.py get <uuid>
python scripts/manage_workflows.py update <uuid> --enable-aml --aml-threshold 75
python scripts/manage_workflows.py delete <uuid>
create_session.py — Create verification sessions
export DIDIT_API_KEY="your_key"
python scripts/create_session.py --workflow-id <uuid> --vendor-data user-123
python scripts/create_session.py --workflow-id <uuid> --vendor-data user-123 --callback https://myapp.com/done
All scripts can be imported as libraries:
from scripts.setup_account import register, verify_email, login
from scripts.manage_workflows import list_workflows, create_workflow
from scripts.create_session import create_session