Install any skill in seconds. Free to start, no credit card required.
Get Started Free →End-to-end KYC (Know Your Customer) identity verification for onboarding real users. Use when someone needs to perform KYC, onboard users with identity verification, verify a person's identity with ID scan and selfie, run a full KYC flow, create a verification session for a user, set up ID + liveness + face match verification, or implement user onboarding with document and biometric checks. Creates a KYC workflow, generates a verification URL, and retrieves the decision.
.claude/skills/didit-protocol-didit-kyc-onboarding/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-09 | ✗→✓ | ▲ Improved | 217% | 0% |
| case-01 | ✗→✓ | ▲ Improved | 179% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 138% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 55% | 0% |
| case-04 | ✗→✓ | ▲ Improved | 375% | 0% |
End-to-end Know Your Customer (KYC) verification. This skill creates a KYC workflow, generates a session URL where a real user completes ID scan + selfie + face match, and retrieves the verification decision.
What the user experiences:
API Reference:
All requests require x-api-key header. Get your key from Didit Business Console → API & Webhooks, or via programmatic registration (see below).
If you don't have a Didit API key, create one in 2 API calls:
POST https://apx.didit.me/auth/v2/programmatic/register/ with {"email": "you@gmail.com", "password": "MyStr0ng!Pass"}POST https://apx.didit.me/auth/v2/programmatic/verify-email/ with {"email": "you@gmail.com", "code": "A3K9F2"} → response includes api_keyTo add credits: GET /v3/billing/balance/ to check, POST /v3/billing/top-up/ with {"amount_in_dollars": 50} for a Stripe checkout link.
See the didit-verification-management skill for full platform management (workflows, sessions, users, billing).
pythonimport requests, time API_KEY = "your_api_key" headers = {"x-api-key": API_KEY, "Content-Type": "application/json"} BASE = "https://verification.didit.me/v3" # 1. Create a KYC workflow (one-time setup — reuse the workflow_id for all users) workflow = requests.post(f"{BASE}/workflows/", headers=headers, json={ "workflow_label": "KYC Onboarding", "max_retry_attempts": 3, "features": [ {"feature": "OCR"}, {"feature": "LIVENESS", "config": {"face_liveness_method": "PASSIVE"}}, {"feature": "FACE_MATCH", "config": {"face_match_score_decline_threshold": 50}}, ], }).json() workflow_id = workflow["uuid"] # 2. Create a session for a specific user session = requests.post(f"{BASE}/session/", headers=headers, json={ "workflow_id": workflow_id, "vendor_data": "user-abc-123", "callback": "https://yourapp.com/verification-done", "language": "en", }).json() print(f"Send user to: {session['url']}") # User opens this URL → scans ID → takes selfie → done # 3. Poll for the decision (or use webhooks) while True: decision = requests.get( f"{BASE}/session/{session['session_id']}/decision/", headers={"x-api-key": API_KEY}, ).json() status = decision["status"] if status in ("Approved", "Declined", "In Review"): break time.sleep(10) print(f"Result: {status}") if status == "Approved": id_data = decision["id_verifications"][0] print(f"Name: {id_data['first_name']} {id_data['last_name']}") print(f"DOB: {id_data['date_of_birth']}") print(f"Document: {id_data['document_type']} ({id_data['issuing_country']})")
A workflow defines what checks run. Create one per use case and reuse it for all users.
POST https://verification.didit.me/v3/workflows/API Reference: https://docs.didit.me/management-api/workflows/create
The workflow body uses a features array (run in order). Put OCR first so downstream features can use the document data.
| Field | Value | Why | |---|---|---| | workflow_label | "KYC Onboarding" | Display name (max 50 chars) | | features[].feature | "OCR", "LIVENESS", "FACE_MATCH" | Full KYC: ID + selfie + match | | LIVENESS → config.face_liveness_method | "PASSIVE" | Anti-spoofing method (uppercase) | | FACE_MATCH → config.face_match_score_decline_threshold | 50 | Match below 50% → auto-decline | | (add) {"feature": "AML"} | — | Sanctions/PEP screening (+cost) | | max_retry_attempts | 3 | User can retry 3 times on failure |
json{ "uuid": "d8d2fa2d-c69c-471c-b7bc-bc71512b43ef", "workflow_label": "KYC Onboarding", "features": ["ocr", "liveness", "face_match"], "total_price": "0.10", "workflow_url": "https://verify.didit.me/..." }
Save uuid as your workflow_id.
Each user gets their own session. The session generates a unique URL where they complete verification.
POST https://verification.didit.me/v3/session/API Reference: https://docs.didit.me/sessions-api/create-session
| Parameter | Type | Required | Description | |---|---|---|---| | workflow_id | uuid | Yes | From Step 1 | | vendor_data | string | Recommended | Your user ID — links the session to your system | | callback | url | Recommended | Redirect URL after verification. Didit appends ?verificationSessionId=...&status=... | | language | string | No | UI language (ISO 639-1). Auto-detected if omitted | | contact_details.email | string | No | Pre-fill email for notification | | expected_details.first_name | string | No | Triggers mismatch warning if document name differs | | expected_details.date_of_birth | string | No | YYYY-MM-DD format | | metadata | JSON string | No | Custom data stored with session |
json{ "session_id": "11111111-2222-3333-4444-555555555555", "session_token": "abcdef123456", "url": "https://verify.didit.me/session/abcdef123456", "status": "Not Started", "workflow_id": "d8d2fa2d-..." }
Send the user to url — this is where they complete verification (web or mobile).
After the user completes verification, retrieve the results.
GET https://verification.didit.me/v3/session/{sessionId}/decision/API Reference: https://docs.didit.me/sessions-api/retrieve-session
Option A: Webhooks (recommended for production) Configure a webhook URL in Business Console → API & Webhooks. Didit sends a POST with session_id and status when the decision is ready.
Option B: Polling Poll GET /v3/session/{id}/decision/ every 10–30 seconds. Check status — stop when it's Approved, Declined, or In Review.
json{ "session_id": "...", "status": "Approved", "features": ["ID_VERIFICATION", "LIVENESS", "FACE_MATCH"], "id_verifications": [{ "status": "Approved", "document_type": "PASSPORT", "issuing_country": "USA", "first_name": "John", "last_name": "Doe", "date_of_birth": "1990-01-15", "document_number": "ABC123456", "expiry_date": "2030-06-01", "gender": "M", "nationality": "USA", "mrz": "P<USADOE<<JOHN<<<<<<<<<..." }], "liveness_checks": [{ "status": "Approved", "method": "PASSIVE", "score": 92.5 }], "face_matches": [{ "status": "Approved", "score": 97.3 }], "aml_screenings": [], "warnings": [] }
| Status | Meaning | Action | |---|---|---| | Approved | All checks passed | User is verified | | Declined | One or more checks failed | Check warnings for details | | In Review | Borderline result | Manual review needed, or auto-decide via API | | Not Started | User hasn't opened the link yet | Wait or remind user | | In Progress | User is completing verification | Wait | | Expired | Session expired (default: 7 days) | Create a new session |
PATCH https://verification.didit.me/v3/session/{sessionId}/update-status/API Reference: https://docs.didit.me/sessions-api/update-status
pythonrequests.patch(f"{BASE}/session/{session_id}/update-status/", headers=headers, json={"new_status": "Approved", "comment": "Manual review passed"})
If the ID photo was blurry, ask the user to redo just that step:
pythonrequests.patch(f"{BASE}/session/{session_id}/update-status/", headers=headers, json={ "new_status": "Resubmitted", "nodes_to_resubmit": [{"node_id": "feature_ocr", "feature": "OCR"}], "send_email": True, "email_address": "user@example.com", })
Add an entry to a system blocklist via the Lists API. Reference the session to auto-extract and block the underlying face/document/phone/email:
python# Find the face blocklist (auto-provisioned), then add the session's face/document lists = requests.get(f"{BASE}/lists/", headers=headers, params={"list_type": "blocklist", "entry_type": "face"}).json() list_uuid = lists["results"][0]["uuid"] requests.post(f"{BASE}/lists/{list_uuid}/entries/", headers=headers, json={"reference_session_id": session_id})
API Reference: https://docs.didit.me/management-api/lists/create-entry
pythonresponse = requests.get(f"{BASE}/session/{session_id}/generate-pdf", headers={"x-api-key": API_KEY})
API Reference: https://docs.didit.me/sessions-api/generate-pdf
Add sanctions/PEP screening to catch high-risk individuals:
pythonrequests.post(f"{BASE}/workflows/", headers=headers, json={ "workflow_label": "KYC + AML", "features": [ {"feature": "OCR"}, {"feature": "LIVENESS", "config": {"face_liveness_method": "PASSIVE"}}, {"feature": "FACE_MATCH"}, {"feature": "AML"}, ], })
Add contact verification to the flow:
pythonrequests.post(f"{BASE}/workflows/", headers=headers, json={ "workflow_label": "KYC + Contact", "features": [ {"feature": "OCR"}, {"feature": "LIVENESS", "config": {"face_liveness_method": "PASSIVE"}}, {"feature": "FACE_MATCH"}, {"feature": "PHONE_VERIFICATION"}, {"feature": "EMAIL_VERIFICATION"}, ], })
For passports with NFC chips — highest assurance:
pythonrequests.post(f"{BASE}/workflows/", headers=headers, json={ "workflow_label": "KYC + NFC", "features": [ {"feature": "OCR"}, {"feature": "NFC"}, {"feature": "LIVENESS", "config": {"face_liveness_method": "PASSIVE"}}, {"feature": "FACE_MATCH"}, ], })
bash# Requires: pip install requests export DIDIT_API_KEY="your_api_key" # Create a KYC workflow (one-time) python scripts/run_kyc.py setup --label "My KYC" --liveness --face-match # Create a session for a user python scripts/run_kyc.py session --workflow-id <uuid> --vendor-data user-123 # Get the decision python scripts/run_kyc.py decision <session_id> # Full flow: create workflow + session in one command python scripts/run_kyc.py full --vendor-data user-123 --callback https://myapp.com/done
Can also be imported:
pythonfrom scripts.run_kyc import setup_kyc_workflow, create_kyc_session, get_decision
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-09 | fail→pass | 6,334 | 4,288 | -32% | 1 | 1 | 0% | 1,399 | 4,440 | +217% | 0 | 0 | — |
case-10 | pass→pass | 6,370 | 3,798 | -40% | 1 | 1 | 0% | 1,241 | 4,259 | +243% | 0 | 0 | — |
case-20 | pass→pass | 13,306 | 7,678 | -42% | 1 | 1 | 0% | 2,553 | 5,183 | +103% | 0 | 0 | — |
case-01 | fail→pass | 9,077 | 7,072 | -22% | 1 | 1 | 0% | 1,830 | 5,108 | +179% | 0 | 0 | — |
case-02 | fail→pass | 10,072 | 6,604 | -34% | 1 | 1 | 0% | 2,085 | 4,964 | +138% | 0 | 0 | — |
case-03 | fail→pass | 15,802 | 9,129 | -42% | 1 | 1 | 0% | 3,701 | 5,732 | +55% | 0 | 0 | — |
case-04 | fail→pass | 12,631 | 9,350 | -26% | 1 | 1 | 0% | 1,179 | 5,595 | +375% | 0 | 0 | — |
case-05 | fail→pass | 11,198 | 4,725 | -58% | 1 | 1 | 0% | 2,123 | 4,650 | +119% | 0 | 0 | — |
case-06 | pass→pass | 15,088 | 6,030 | -60% | 1 | 1 | 0% | 2,849 | 4,840 | +70% | 0 | 0 | — |
case-07 | fail→pass | 13,285 | 5,210 | -61% | 1 | 1 | 0% | 2,514 | 4,663 | +85% | 0 | 0 | — |
case-08 | fail→pass | 17,254 | 7,211 | -58% | 1 | 1 | 0% | 3,593 | 5,251 | +46% | 0 | 0 | — |
case-11 | fail→pass | 8,005 | 3,141 | -61% | 1 | 1 | 0% | 1,547 | 4,203 | +172% | 0 | 0 | — |
case-12 | fail→pass | 11,397 | 7,611 | -33% | 1 | 1 | 0% | 2,358 | 5,196 | +120% | 0 | 0 | — |
case-13 | fail→pass | 10,878 | 5,128 | -53% | 1 | 1 | 0% | 2,302 | 4,633 | +101% | 0 | 0 | — |
case-14 | fail→pass | 4,372 | 1,513 | -65% | 1 | 1 | 0% | 758 | 3,872 | +411% | 0 | 0 | — |
case-21 | pass→pass | 10,471 | 7,814 | -25% | 1 | 1 | 0% | 2,113 | 5,235 | +148% | 0 | 0 | — |
case-15 | fail→pass | 4,399 | 2,795 | -36% | 1 | 1 | 0% | 779 | 3,902 | +401% | 0 | 0 | — |
case-16 | fail→pass | 5,126 | 3,406 | -34% | 1 | 1 | 0% | 938 | 4,196 | +347% | 0 | 0 | — |
case-17 | fail→pass | 6,929 | 3,237 | -53% | 1 | 1 | 0% | 1,378 | 4,223 | +206% | 0 | 0 | — |
case-18 | fail→pass | 17,140 | 4,986 | -71% | 1 | 1 | 0% | 3,369 | 4,544 | +35% | 0 | 0 | — |
case-19 | fail→pass | 11,871 | 5,128 | -57% | 1 | 1 | 0% | 2,341 | 4,650 | +99% | 0 | 0 | — |
case-22 | fail→fail | 9,683 | 7,581 | -22% | 1 | 1 | 0% | 1,844 | 5,047 | +174% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +77 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
| Model | Method | Date | Lift |
|---|---|---|---|
| gemini-3.6-flash | verified | 8/6/2026 | +77% |
Other measured skills in the registry, with their headline benchmark lift.