📘 Public beta · Endpoints are stable; OpenAPI specs and SDKs ship monthly. See changelog →
Products
Identity Platform
Quickstart

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

POST/api/customers
Auth · API keyScope · customers:write
curl -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

POST/api/identity/document/ktp/challenge
Auth · API keyScope · verifications:viewRate limit · 30/min

The 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

POST/api/identity/document/ktp/capture
Auth · API keyScope · verifications:viewRate limit · 20/min

This 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:

POST/api/identity/face/liveness
Auth · API keyScope · verifications:viewRate limit · 20/min
{
  "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:

POST/api/identity/face/match
Auth · API keyScope · verifications:viewRate limit · 60/min
{
  "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