Identity Platform · Quickstart
This walks through a full KYC onboarding — from creating a customer to reaching the premium tier — using the REST API. In production you'd use our mobile SDKs (Swift / Kotlin / React Native / Flutter) to do steps 3–4 on-device; here we use raw API calls so you can see exactly what's happening.
Total time: ~20 minutes with sandbox data, ~5 minutes with the SDK.
Prerequisites
- A sandbox API key with scopes:
customers:write,verifications:view,kyc:manage,webhooks:manage. - A test KTP image (JPEG or PNG). Don't have one? The sandbox accepts our fixture: download from
https://sandbox.quantumelixir.tech/identity/fixtures/ktp-test.jpg.
1. Create the customer
/api/customerscurl -X POST https://sandbox.quantumelixir.tech/identity/api/customers \
-H "Authorization: Bearer $QE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"fullName": "Budi Santoso",
"externalRef": "user-12345"
}'Response:
{
"data": {
"id": "cus_01HXY3...",
"fullName": "Budi Santoso",
"kycLevel": "none",
"createdAt": "2026-05-24T08:14:22.481Z"
}
}Save customer.id.
2. Issue a flash challenge for KTP capture
/api/identity/document/ktp/challengeThe flash challenge defeats replay attacks: the SDK shows a sequence of colors on the screen, and the camera captures the KTP under that exact light pattern. We later verify the reflected colors match the challenge.
curl -X POST https://sandbox.quantumelixir.tech/identity/api/identity/document/ktp/challenge \
-H "Authorization: Bearer $QE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "customerId": "cus_01HXY3...", "steps": 5 }'Response:
{
"flashChallenge": {
"id": "fc_01HXY...",
"purpose": "ktp",
"nonce": "9f8e7d6c-...",
"colors": ["#FF0000", "#00FF00", "#0000FF", "#FFFFFF", "#000000"],
"holdMs": [500, 300, 400, 500, 400],
"expiresAt": "2026-05-24T08:15:52.481Z",
"hmacSecret": "qe_whsec_..."
}
}The hmacSecret is single-use — the SDK uses it to sign the capture bundle. Don't log it.
3. Submit the KTP capture
/api/identity/document/ktp/captureThis is the heavyweight call. Body shape:
{
"customerId": "cus_01HXY3...",
"bundle": {
"version": "1.0",
"sdkVersion": "qe-identity-sdk-rn@0.1.0",
"platform": "rn-ios",
"frames": ["<base64-encoded JPEG>"],
"capturedAt": "2026-05-24T08:15:12.481Z",
"signature": "<HMAC-SHA256 of bundle>",
"flashChallengeNonce": "9f8e7d6c-...",
"flashSamples": [
{ "tMs": 100, "meanR": 235, "meanG": 30, "meanB": 30 },
{ "tMs": 800, "meanR": 30, "meanG": 235, "meanB": 30 }
],
"ocrFields": {
"nik": "3201234567890001",
"fullName": "BUDI SANTOSO",
"dateOfBirth": "1990-05-15"
},
"ocrConfidence": {
"nik": 0.99,
"fullName": 0.92,
"dateOfBirth": 0.95
}
},
"hmacKey": "qe_whsec_..."
}Response (success):
{
"verification": {
"id": "ver_01HXY...",
"status": "verified",
"score": 87
},
"ktpBundle": {
"id": "ktpb_01HXY...",
"verdict": "passed",
"assuranceTier": "high",
"framesStored": 1
},
"antiSpoof": {
"fused": "live",
"assuranceTier": "high",
"voters": [
{ "name": "flash_reflectance", "passed": true },
{ "name": "screen_moire", "passed": true },
{ "name": "specular_check", "passed": true }
],
"passed": 4,
"present": 6
},
"tamper": {
"fused": "authentic",
"voters": [
{ "name": "nik_consistency", "passed": true, "reason": "checksum ok" },
{ "name": "font_match", "passed": true }
]
},
"faceEnrolment": {
"id": "fe_01HXY...",
"fallbackStub": false,
"modelId": "qe-face-v3"
}
}At this point: customer has KYC tier standard, a ktp_extract face enrollment is created from the face crop on the KTP, and the KTP bundle is sealed at rest.
4. Bump to premium with face liveness + match
This is what gives you confidence the customer is physically present and matches the KTP photo.
First, another challenge (same shape, different purpose):
curl -X POST .../api/identity/face/flash-challenge \
-H "Authorization: Bearer $QE_API_KEY" \
-d '{ "customerId": "cus_01HXY3...", "steps": 5 }'Then submit the live selfie:
/api/identity/face/liveness{
"customerId": "cus_01HXY3...",
"bundle": {
"frames": ["<base64 selfie>"],
"challenges": ["blink", "turn_left", "smile"],
"responses": [
{ "challenge": "blink", "satisfied": true },
{ "challenge": "turn_left", "satisfied": true },
{ "challenge": "smile", "satisfied": true }
],
"passiveLivenessScores": [0.95, 0.93, 0.94],
"flashChallengeNonce": "...",
"flashSamples": [...],
"signature": "..."
}
}Returns a captureBundle.id. Now match it against the KTP face:
/api/identity/face/match{
"customerId": "cus_01HXY3...",
"captureBundleId": "cb_01HXY..."
}Response:
{
"verification": { "id": "ver_01HXY...", "status": "verified", "score": 92 },
"match": true,
"verdict": "match",
"similarity": 0.71,
"threshold": 0.45,
"referenceSource": "enrollment"
}5. Confirm tier
curl https://sandbox.quantumelixir.tech/identity/api/identity/kyc/cus_01HXY3... \
-H "Authorization: Bearer $QE_API_KEY"{
"customer": {
"id": "cus_01HXY3...",
"kycLevel": "premium",
"kycLastVerifiedAt": "2026-05-24T08:18:01.122Z"
},
"eligible": "premium",
"verifications": [
{ "method": "ktp_capture", "status": "verified", "score": 87 },
{ "method": "face_liveness", "status": "verified", "score": 91 },
{ "method": "face_match", "status": "verified", "score": 92 }
]
}Done. The customer is fully verified.
Next steps
- KTP capture → — full schema reference for the capture bundle
- Continuous step-up auth → — how to use this on every sensitive action
- Dukcapil verdict ingest → — for the highest-assurance flow
- Webhooks → — get notified instead of polling