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

KYC tiers

A customer's kycLevel is a computed property — you don't set it directly, it's derived from their verification history.

TierPromotion requirement
none(default) Customer record exists, no verifications
basicA verified row with method: "nik_format" (NIK is 16 digits and self-consistent)
standardA verified row with method: "ktp_capture" (anti-spoof + tamper passed) OR method: "nik_dukcapil"
premiumstandard requirements + a verified face_liveness + a verified face_match to an enrolled reference

Promotion is automatic — every call to a verification endpoint re-evaluates the tier and writes a KycTierEvent row if it changed.

Read tier state

GET/api/identity/kyc/{customerId}
Auth · API keyScope · customers:view
curl https://api.quantumelixir.tech/identity/api/identity/kyc/cus_01HXY... \
  -H "Authorization: Bearer $QE_API_KEY"
{
  "customer": {
    "id": "cus_01HXY...",
    "fullName": "Budi Santoso",
    "kycLevel": "premium",
    "kycLastVerifiedAt": "2026-05-24T08:18:01.122Z",
    "kycExpiresAt": "2027-05-24T08:18:01.122Z"
  },
  "eligible": "premium",
  "verifications": [
    { "id": "ver_01HXY1...", "method": "nik_format",   "status": "verified", "score": 100, "createdAt": "2026-05-24T08:14:30Z" },
    { "id": "ver_01HXY2...", "method": "nik_dukcapil", "status": "verified", "score": 100, "createdAt": "2026-05-24T08:15:01Z" },
    { "id": "ver_01HXY3...", "method": "ktp_capture",  "status": "verified", "score": 87,  "createdAt": "2026-05-24T08:15:12Z" },
    { "id": "ver_01HXY4...", "method": "face_liveness","status": "verified", "score": 91,  "createdAt": "2026-05-24T08:17:42Z" },
    { "id": "ver_01HXY5...", "method": "face_match",   "status": "verified", "score": 92,  "createdAt": "2026-05-24T08:17:58Z" }
  ],
  "history": [
    { "id": "kte_01HXY...", "fromTier": "none",     "toTier": "basic",    "actorUserId": null, "reason": "automatic on nik_format verify", "createdAt": "2026-05-24T08:14:30Z" },
    { "id": "kte_01HXZ...", "fromTier": "basic",    "toTier": "standard", "actorUserId": null, "reason": "automatic on nik_dukcapil verify", "createdAt": "2026-05-24T08:15:01Z" },
    { "id": "kte_01HXA...", "fromTier": "standard", "toTier": "premium",  "actorUserId": null, "reason": "automatic on face_match verify",   "createdAt": "2026-05-24T08:17:58Z" }
  ]
}

Field reference

FieldTypeDescription
customer.kycLevelenumCurrent tier (none · basic · standard · premium)
customer.kycLastVerifiedAtISO-8601When the current tier was last reaffirmed
customer.kycExpiresAtISO-8601When the tier auto-decays back to standard (typically 1 year for premium)
eligibleenumThe tier the customer could be on given their verification history
verificationsarrayAll verification rows for this customer, newest first
historyarrayKycTierEvent rows — every tier change

If eligible is higher than kycLevel, an analyst (or system) once downgraded the customer. history tells you when and why.

Analyst override

POST/api/identity/kyc/{customerId}/upgrade
Auth · API keyScope · kyc:manage

Sometimes a customer's identity is verified through a channel that's outside Quantum Elixir (in-branch visit, manual document review, regulator notice). An admin can upgrade them manually:

{
  "toTier": "premium",
  "reason": "Manually verified by branch officer (ticket #2026-1245). Original KTP + face seen."
}

Constraints:

  • You can only jump one tier above the customer's eligible — e.g. if eligible: "standard", you can upgrade to premium but not skip up from basic to premium.
  • reason is required, minimum 20 characters, free-text. Stored verbatim on the KycTierEvent.
  • The override appears in history with actorUserId = <your-admin-id> so compliance can audit it.

Downgrades happen automatically (e.g. when kycExpiresAt lapses) or by admin action — there is no public API for forced downgrades; use the dashboard for that.

Expiry

TierDefault expiryWhat happens on expiry
basicNever
standard2 yearsDowngrades to basic
premium1 yearDowngrades to standard

Configurable per org. Customers below expiry get a customer.kyc_updated webhook 30 days before, 7 days before, and on the day of downgrade.

Re-verifying without re-collecting biometrics

If a customer is about to expire, a single fresh face_match (live selfie against existing enrollment) is enough to reset kycLastVerifiedAt. You don't need to re-capture KTP or re-run Dukcapil unless the underlying biometric reference has changed.