Lewati ke konten
Zoi ID

Bangun dengan Zoi ID

Dua API di satu host, klien bertipe dalam enam bahasa, alur terhosting, dan sandbox. Panduan lengkap sudah bisa dibaca, dan kunci sandbox diberikan bersama akses awal.

Base URLhttps://kyc.zoi.network

Mulai cepat

  1. 1

    Buat sesi

    Backend Anda memanggil POST /v1/partner/sessions dengan kunci mitra Anda.

  2. 2

    Jalankan alur

    Berikan token klien ke SDK, atau buka verificationUrl di browser.

  3. 3

    Terima hasilnya

    Periksa tanda tangan webhook, lalu simpan ID identitas dan statusnya.

shell
curl https://kyc.zoi.network/v1/partner/sessions \
  -H "Authorization: Bearer $ZOI_ID_PARTNER_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: signup-user_42" \
  -d '{ "externalUserId": "user_42", "level": "standard", "appId": "com.example.app" }'

Dua API

API mitra

Server ke server, di bawah /v1/partner. Kirim kunci mitra Anda sebagai token Bearer. Jangan pernah menaruhnya di aplikasi.

API klien

Dipakai oleh SDK, di bawah /v1/client. SDK mengirim token klien yang berlaku untuk satu sesi selama 60 menit.

Konvensi

  • Waktu ditulis dalam milidetik epoch, di field yang namanya berakhir dengan At.
  • Tanggal ditulis YYYY-MM-DD. Bagian yang tidak diketahui ditulis XX, misalnya 1990-XX-XX.
  • Request POST yang membuat atau memutuskan sesuatu menerima header Idempotency-Key. Kunci disimpan selama 24 jam.
  • Perubahan pada sesi mengirim revisi yang diharapkan. Jika revisinya sudah berubah, Anda mendapat revision_conflict.

Alur sesi

Backend Anda membuat sesi. SDK menjalankan langkah-langkahnya. Server memeriksa urutan dan menjawab step_not_allowed jika suatu langkah datang terlalu cepat.

1. Buat sesi dari backend Anda

Memanggilnya lagi untuk pengguna yang sama akan melanjutkan sesi yang terbuka dan memberi token klien baru.

POST /v1/partner/sessions
POST /v1/partner/sessions
Authorization: Bearer <partner key>
Content-Type: application/json
Idempotency-Key: 6f1c2a9e-signup-42

{
  "externalUserId": "user_42",
  "level": "standard",
  "locale": "en",
  "appId": "com.example.app"
}

200 OK
{
  "sessionId": "ses_...",
  "clientToken": "...",
  "clientTokenExpiresAt": 1767225600000,
  "status": "created",
  "apiBaseUrl": "https://kyc.zoi.network",
  "resumed": false
}

2. Langkah yang dijalankan SDK

  1. 01

    Membaca sesi dan langkah yang masih diperlukan.

    GET /v1/client/session

  2. 02

    Mencatat persetujuan, termasuk pilihan terpisah dan opsional untuk perbandingan wajah.

    POST /v1/client/consent

  3. 03

    Memeriksa aplikasi dan perangkat. Hasilnya menjadi bagian dari keputusan.

    POST /v1/client/attestation/challenge, POST /v1/client/attestation

  4. 04

    Mengirim dokumen: membaca chip setelah memindai MRZ, atau mengirim foto.

    POST /v1/client/document/mrz-scan, POST /v1/client/document/chip/challenge, POST /v1/client/document/chip or POST /v1/client/document/photos

  5. 05

    Merekam satu video singkat dengan kamera depan sambil aplikasi menampilkan gerakan kepala.

    POST /v1/client/liveness/challenge, POST /v1/client/liveness

  6. 06

    Mengirim negara tempat tinggal, dan alamat di level lanjutan.

    POST /v1/client/profile

  7. 07

    Hanya dengan requirement alamat: mengunggah dokumen, lalu mengonfirmasi alamat. Dilewati bila dokumen identitas sudah memberikan alamat.

    POST /v1/client/address/document, POST /v1/client/address/confirm

  8. 08

    Hanya dengan requirement dana: menyimpan jawaban, menambahkan dokumen bila kebijakan memintanya, lalu menyelesaikan langkah.

    POST /v1/client/funds/answers, POST /v1/client/funds/document, POST /v1/client/funds/remove, POST /v1/client/funds/complete

  9. 09

    Menjawab dua pertanyaan: orang yang terpapar politik, dan bertindak atas nama sendiri.

    POST /v1/client/declarations

  10. 10

    Mengirim dengan revisi saat ini.

    POST /v1/client/submit

Pengguna juga bisa meminta petugas meninjau hasil otomatis (POST /review-request) atau menarik persetujuan, yang membatalkan sesi dan langsung menghapus file-nya (POST /withdraw).

Bukti alamat dan sumber dana

Tambahkan requirements ke POST /v1/partner/sessions untuk memverifikasi lebih dari sekadar identitas. address meminta bukti alamat. source_of_funds menanyakan asal uang, dan source_of_wealth juga menanyakan total kekayaan serta cara memperolehnya. Kedua nilai dana itu bersama-sama menjadi satu langkah, dan keduanya bisa digabung dengan address.

POST /v1/partner/sessions
POST /v1/partner/sessions
Authorization: Bearer <partner key>
Content-Type: application/json

{
  "externalUserId": "user_42",
  "level": "standard",
  "appId": "com.example.app",
  "requirements": ["address", "source_of_funds"],
  // Optional. Without them your partner default applies, else ours.
  "addressPolicy": { "maxAgeDays": 90, "signedOnly": false },
  "fundsPolicy": { "evidence": "optional", "currency": "EUR" }
}

Kebijakan opsional

Tanpa kebijakan, default mitra Anda yang berlaku, jika tidak ada, default kami. Kebijakan disalin ke sesi saat sesi dibuat.

  • addressPolicy: maxAgeDays (default 90, 1 sampai 366), acceptedTypes (default: dokumen utilitas, telekomunikasi, dan bank, dokumen pajak, surat instansi pemerintah, dan surat keterangan domisili) dan signedOnly (default false).
  • fundsPolicy: questions, evidence (none, optional, atau required, default none), acceptedTypes, maxAgeDays (default 180), currency (ISO 4217, default USD) dan brackets untuk rentangnya.
  • Sesi terbuka dengan requirement lain yang belum dikirim akan dibatalkan dan sesi baru dimulai, sama seperti saat level berubah.
  • Uji setiap hasil dengan skenario sandbox seperti address_signed_pdf, address_document_review, funds_signed_documents, dan funds_review.

Sesi lanjutan

Verifikasi hanya requirement-nya untuk pengguna yang identitasnya sudah aktif di layanan Anda. Kirim reuseIdentity: true bersama requirements.

  • Pengguna memberi persetujuan dan hanya menyelesaikan langkah baru. Dokumen, selfie, atau pernyataan tidak diminta lagi.
  • Saat disetujui, identitas tetap memakai ID, tanggal verifikasi, dan masa berlakunya, lalu mendapat hasil baru. Anda menerima session.approved dan identity.updated.
  • Tanpa identitas aktif, panggilan menjawab 409 step_not_allowed.
  • Mulai dari aturan risiko Anda sendiri, misalnya setoran besar, tanda PEP, atau negara berisiko. Aturan Anda tetap di backend Anda, di satu tempat.
POST /v1/partner/sessions
POST /v1/partner/sessions
Authorization: Bearer <partner key>
Content-Type: application/json
Idempotency-Key: edd-user_42-2026-11

{
  "externalUserId": "user_42",
  "appId": "com.example.app",
  "requirements": ["source_of_funds", "source_of_wealth"],
  "reuseIdentity": true,
  "fundsPolicy": { "evidence": "required" }
}

// No active identity for user_42:
409 { "error": { "code": "step_not_allowed", ... } }

Hasil dan cakupan

Sesi yang disetujui membawa address dan funds, dan identitas menyimpan yang terbaru dari masing-masing. Kolom ringkasan tersedia di setiap cakupan. Detailnya memerlukan cakupan.

  • Cakupan address: baris alamat. Tanpa cakupan ini, Anda mendapat verified, method, evidenceLevel, documentType, documentDate, verifiedAt, dan country.
  • Cakupan financial_data: answers, documents, dan findings. Tanpa cakupan ini, Anda mendapat kinds, evidenceLevel, reviewed, documentCount, dan verifiedAt. Tidak pernah menjadi bagian dari eligibility.
  • evidenceLevel bernilai high, medium, low, atau none. Metode alamat, dari yang terkuat: wallet, id_document, signed_pdf, official_code, document_review, dan declared.
  • Dokumen tidak dikirim lewat API. Anggota dengan peran kepatuhan bisa melihat halaman dokumen sumber dana di dasbor bila environment memiliki cakupan financial_data. Setiap tampilan dicatat.
GET /v1/partner/sessions/:id
"session": {
  "id": "ses_...",
  "status": "approved",
  "requirements": ["address", "source_of_funds"],
  "reusesIdentity": false,
  "address": {
    "verified": true,
    "method": "signed_pdf",
    "evidenceLevel": "high",
    "documentType": "bank_statement",
    "documentDate": "2026-10-14",
    "verifiedAt": 1767225600000,
    "country": "DE",
    // scope "address"
    "address": { "line1": "Musterstrasse 12", "city": "Berlin", "postalCode": "10115", "country": "DE" }
  },
  "funds": {
    "kinds": ["source_of_funds"],
    "evidenceLevel": "medium",
    "reviewed": true,
    "documentCount": 2,
    "verifiedAt": 1767225600000,
    // scope "financial_data"
    "answers": {
      "employmentStatus": "employed",
      "income": { "min": 50000, "max": 100000, "currency": "EUR" },
      "sources": ["salary", "savings"],
      ...
    },
    "documents": [
      { "documentType": "payslip", "documentDate": "2026-10-31", "amount": 5200, "currency": "EUR",
        "signature": "valid", "signedByKnownIssuer": true, "nameMatch": "match", "findings": [], ... },
      ...
    ],
    "findings": []
  }
}

Alur web

Tidak memakai SDK? Arahkan pengguna ke halaman yang kami sediakan. Halaman ini memakai API, pemeriksaan, dan antrean peninjauan yang sama.

1. Dapatkan tautannya

POST /v1/partner/sessions mengembalikan verificationUrl. Kirim redirectUrl agar pengguna kembali ke Anda saat selesai. URL itu harus diawali salah satu origin pengalihan yang sudah Anda daftarkan.

POST /v1/partner/sessions
POST /v1/partner/sessions
Authorization: Bearer <partner key>
Content-Type: application/json

{
  "externalUserId": "user_42",
  "level": "standard",
  "locale": "en",
  "appId": "com.example.app",
  "redirectUrl": "https://example.com/kyc/done"
}

200 OK
{
  "sessionId": "ses_...",
  "clientToken": "...",
  "verificationUrl": "https://kyc.zoi.network/verify/#token=...&lang=en",
  ...
}
  • Token klien ada di fragmen URL, setelah #. Browser tidak pernah mengirim fragmen ke server, jadi token tidak masuk log atau header Referer. Halaman membacanya, menghapusnya dari bilah alamat, dan hanya menyimpannya di memori.
  • Tambahkan lang ke fragmen untuk memilih salah satu dari 19 bahasa. Tanpa itu, halaman memakai bahasa browser.
  • Memuat ulang halaman menghilangkan token. Buat tautan baru untuk sesi yang sama dari backend Anda.
  • Halaman mengirim channel: "web" bersama persetujuan. Browser tidak bisa membaca chip dokumen identitas, jadi sesi web memakai foto dokumen atau dompet ID digital. Sesi ini melewati pemeriksaan perangkat, dan foto dokumen dari web selalu diputuskan oleh pemeriksa terlatih.
  • Di komputer, halaman menampilkan kode QR agar pengguna bisa melanjutkan di ponsel.

2. Saat pengguna selesai

Saat alur berakhir (terkirim, dibatalkan, atau status akhir), halaman mengirim pesan ini ke setiap objek berikut yang ada: window.ReactNativeWebView, window.webkit.messageHandlers.zoiId, window.ZoiIdAndroid, dan window.opener. Lalu halaman membuka redirectUrl dengan tambahan zoiIdSession dan zoiIdStatus.

Jangan pernah memercayai pesan atau parameter pengalihan. Percayai hanya webhook Anda atau GET /v1/partner/sessions/:id.

postMessage
// Sent to each bridge that exists:
//   window.ReactNativeWebView.postMessage(JSON string)
//   window.webkit.messageHandlers.zoiId.postMessage(object)
//   window.ZoiIdAndroid.postMessage(JSON string)
//   window.opener.postMessage(object, "<origin of your redirectUrl>")
//     (popups only, and only when the session has a redirectUrl)
{
  "source": "zoi-id",
  "type": "finished",        // or "cancelled"
  "sessionId": "ses_...",
  "status": "processing"     // the session status at that moment
}

// Then, if redirectUrl was set (the page uses the one stored with the
// session, never one from the address bar):
https://example.com/kyc/done?zoiIdSession=ses_...&zoiIdStatus=processing

3. Pengaturan webview

Android WebView

Aktifkan javaScriptEnabled dan domStorageEnabled, lalu setel mediaPlaybackRequiresUserGesture ke false. Di WebChromeClient, izinkan onPermissionRequest untuk RESOURCE_VIDEO_CAPTURE setelah aplikasi Anda punya izin CAMERA. Tambahkan pemilih file untuk cadangan foto.

Kotlin
webView.settings.javaScriptEnabled = true
webView.settings.domStorageEnabled = true
webView.settings.mediaPlaybackRequiresUserGesture = false
webView.webChromeClient = object : WebChromeClient() {
  override fun onPermissionRequest(request: PermissionRequest) {
    // Only after your app holds Manifest.permission.CAMERA.
    if (request.resources.contains(PermissionRequest.RESOURCE_VIDEO_CAPTURE)) {
      request.grant(arrayOf(PermissionRequest.RESOURCE_VIDEO_CAPTURE))
    } else {
      request.deny()
    }
  }
  override fun onShowFileChooser(
    view: WebView, callback: ValueCallback<Array<Uri>>, params: FileChooserParams,
  ): Boolean {
    // Open a picker and pass the result to callback (photo fallback).
    return true
  }
}

iOS WKWebView

iOS 14.3 atau lebih baru. Setel allowsInlineMediaPlayback ke true dan tambahkan NSCameraUsageDescription ke aplikasi Anda. Di iOS 15 ke atas, Anda bisa memutuskan izin kamera di UI delegate.

Swift
let config = WKWebViewConfiguration()
config.allowsInlineMediaPlayback = true
let webView = WKWebView(frame: .zero, configuration: config)
webView.uiDelegate = self // iOS 15+: decide camera access below

func webView(_ webView: WKWebView,
             requestMediaCapturePermissionFor origin: WKSecurityOrigin,
             initiatedByFrame frame: WKFrameInfo,
             type: WKMediaCaptureType,
             decisionHandler: @escaping (WKPermissionDecision) -> Void) {
  decisionHandler(origin.host == "kyc.zoi.network" ? .grant : .deny)
}
// Info.plist: NSCameraUsageDescription

React Native WebView

Setel mediaCapturePermissionGrantType="grant", allowsInlineMediaPlayback, dan javaScriptEnabled. Aplikasi Anda perlu izin kamera.

TSX
<WebView
  source={{ uri: verificationUrl }}
  javaScriptEnabled
  allowsInlineMediaPlayback
  mediaCapturePermissionGrantType="grant"
  onMessage={(event) => {
    const message = JSON.parse(event.nativeEvent.data);
    if (message.source === "zoi-id") {
      // Close the webview, then ask your backend for the real status.
    }
  }}
/>

iframe tidak didukung, karena izin kamera dan clickjacking. Gunakan tab browser, popup, atau webview.

Status

Sesi

created
Sesi sudah ada, belum ada yang diunggah.
in_progress
Persetujuan sudah diberikan, langkah sedang diunggah.
processing
Sudah dikirim, pemeriksaan sedang berjalan.
review
Menunggu pemeriksa terlatih.
approved
Final. Identitas terverifikasi.
rejected
Final. Lihat kode alasan.
resubmit
Pengguna harus mengulang langkah di requiredSteps.
expired
Tidak ada keputusan final tepat waktu.
cancelled
Dibatalkan oleh mitra, pengguna, atau penghapusan.

Identitas

active
Bisa dipakai.
suspended
Ditahan, misalnya setelah ada kandidat sanksi baru.
expired
Masa berlaku dokumen atau verifikasi sudah habis. Sesi baru akan memverifikasi ulang.
revoked
Final.

SDK

SDK React Native (Expo) memberi Anda klien API bertipe, pembacaan chip lewat NFC, dan pemeriksaan perangkat. SDK ini headless, jadi aplikasi Anda tetap memegang kendali penuh atas layar dan kameranya. Untuk server Anda, pakai klien bertipe untuk TypeScript, Python, Go, PHP, Java, dan Ruby.

@zoi/id-sdk
import { ZoiIdClient, nextStep } from "@zoi/id-sdk";

// clientToken and apiBaseUrl come from your backend (POST /v1/partner/sessions).
const client = new ZoiIdClient({ baseUrl: apiBaseUrl, clientToken });

const session = await client.getSession();
switch (nextStep(session)) {
  case "consent":   /* show the notice and the biometric choice */ break;
  case "document":  /* chip read or photos */ break;
  case "liveness":  /* record the video */ break;
  case "address":   /* upload a document, then confirm the address */ break;
  case "funds":     /* answers, then documents when the policy asks */ break;
  case "submit":    await client.submit(session.revision); break;
  case "done":      /* wait for your backend to get the webhook */ break;
}

Webhook

Kami mengirim webhook saat sesi atau identitas berubah. Selalu periksa tanda tangannya sebelum memercayai isinya.

  • Setiap webhook membawa header Zoi-Id-Signature: t=unix seconds,v1=hex.
  • v1 adalah HMAC-SHA256 dari teks t.raw body dengan secret webhook Anda. Pakai body mentah, sebelum JSON diurai.
  • Saat secret sedang diganti, header membawa satu v1 untuk setiap secret aktif. Terima webhook jika salah satunya cocok.
  • Tolak jika t berselisih lebih dari 300 detik dari sekarang.
  • Pengiriman yang gagal diulang selama 72 jam. Setiap pengulangan ditandatangani lagi dengan t baru.
  • Identitas membawa revision. Terapkan peristiwa hanya jika revisinya lebih baru dari yang Anda simpan.
identity.suspended
POST <your webhook URL>
Zoi-Id-Signature: t=1767225600,v1=5f2b...e9

{
  "v": 1,
  "eventId": "evt_...",
  "type": "identity.suspended",
  "createdAt": 1767225600000,
  "object": "identity",
  "externalUserId": "user_42",
  "identity": {
    "identityId": "idn_...",
    "revision": 7,
    "status": "suspended",
    "level": "standard",
    "verifiedAt": 1764547200000,
    "validUntil": 1859155200000,
    "ageOver18": true,
    "captureMethod": "nfc"
  }
}

Jenis peristiwa

  • session.processing
  • session.review
  • session.approved
  • session.rejected
  • session.resubmit
  • session.expired
  • session.cancelled
  • identity.updated
  • identity.suspended
  • identity.reinstated
  • identity.expired
  • identity.revoked

Periksa tanda tangan di TypeScript

verify.ts
import { createHmac, timingSafeEqual } from "node:crypto";

const TOLERANCE_SECONDS = 300;

export function verifyZoiIdSignature(
  rawBody: string,
  header: string | null,
  secret: string,
  nowSeconds = Math.floor(Date.now() / 1000),
): boolean {
  if (!header) return false;

  let t: string | undefined;
  const signatures: Buffer[] = [];
  for (const part of header.split(",")) {
    const [key, value] = part.trim().split("=");
    if (!value) continue;
    if (key === "t") t = value;
    if (key === "v1" && /^[0-9a-f]+$/i.test(value)) signatures.push(Buffer.from(value, "hex"));
  }
  if (!t || !/^\d+$/.test(t) || signatures.length === 0) return false;
  if (Math.abs(nowSeconds - Number(t)) > TOLERANCE_SECONDS) return false;

  const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest();
  return signatures.some((sig) => sig.length === expected.length && timingSafeEqual(sig, expected));
}

// Example handler: read the raw body first, parse JSON only after the check.
export async function POST(request: Request) {
  const rawBody = await request.text();
  const ok = verifyZoiIdSignature(rawBody, request.headers.get("Zoi-Id-Signature"), process.env.ZOI_ID_WEBHOOK_SECRET!);
  if (!ok) return new Response("bad signature", { status: 400 });

  const event = JSON.parse(rawBody);
  // Store event.eventId to skip duplicates, then apply it if the revision is newer.
  return new Response(null, { status: 204 });
}

Periksa tanda tangan di Go

verify.go
package zoiid

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"strconv"
	"strings"
	"time"
)

const toleranceSeconds = 300

// VerifySignature checks the Zoi-Id-Signature header against the raw body.
func VerifySignature(rawBody []byte, header, secret string, now time.Time) bool {
	var t string
	var sigs [][]byte
	for _, part := range strings.Split(header, ",") {
		key, value, ok := strings.Cut(strings.TrimSpace(part), "=")
		if !ok || value == "" {
			continue
		}
		switch key {
		case "t":
			t = value
		case "v1":
			if b, err := hex.DecodeString(value); err == nil {
				sigs = append(sigs, b)
			}
		}
	}
	ts, err := strconv.ParseInt(t, 10, 64)
	if err != nil || len(sigs) == 0 {
		return false
	}
	if d := now.Unix() - ts; d > toleranceSeconds || d < -toleranceSeconds {
		return false
	}

	mac := hmac.New(sha256.New, []byte(secret))
	mac.Write([]byte(t + "."))
	mac.Write(rawBody)
	expected := mac.Sum(nil)
	for _, sig := range sigs {
		if hmac.Equal(sig, expected) {
			return true
		}
	}
	return false
}

Feed peristiwa

GET /v1/partner/events mengembalikan semua peristiwa 30 hari terakhir, dari yang paling lama. Kirim after dengan nextCursor terakhir untuk membaca halaman berikutnya. Gunakan setelah gangguan panjang, atau untuk memastikan tidak ada yang terlewat.

GET /v1/partner/events
GET /v1/partner/events?after=<nextCursor>&limit=100
Authorization: Bearer <partner key>

200 OK
{
  "items": [ /* webhook payloads, oldest first */ ],
  "nextCursor": "..."   // null when there is nothing more
}

Laporan penipuan

Beri tahu kami bila sesi yang sudah diputuskan ternyata penipuan, misalnya setelah chargeback. Laporan membantu kami mengukur persetujuan yang keliru. Laporan tidak mengubah sesi, identitas, atau hasil pengguna.

  • Cakupan sessions:write. Hanya untuk sesi yang sudah diputuskan (disetujui atau ditolak). Sebelum ada keputusan, panggilan menjawab 409.
  • Body kosong melaporkan confirmed_fraud dengan kategori other. status juga bisa suspected_fraud, atau cleared untuk menarik kembali laporan Anda sendiri.
  • Kategori: forged_document, impostor, deepfake_or_replay, synthetic_identity, account_takeover, dan other. Tidak ada kategori untuk cleared.
  • note adalah teks bebas untuk analis kami, maksimal 1000 karakter. Jangan pernah mengisinya dengan nama, nomor dokumen, atau data pribadi lainnya.
  • Laporan baru menjawab 201. Laporan yang sama bila dikirim lagi mengembalikan laporan tersimpan dengan 200. Kirim Idempotency-Key agar aman saat mencoba ulang. Maksimal 20 laporan per sesi.
  • Laporan yang sama bisa dibuat dengan satu klik di halaman sesi pada dasbor.
POST /v1/partner/sessions/:id/fraud-reports
POST /v1/partner/sessions/ses_.../fraud-reports
Authorization: Bearer <partner key>
Content-Type: application/json
Idempotency-Key: chargeback-8812

{
  "status": "confirmed_fraud",
  "category": "impostor",
  "note": "Chargeback on the first deposit"
}

201 Created
{
  "id": "flb_...",
  "sessionId": "ses_...",
  "status": "confirmed_fraud",
  "category": "impostor",
  "noteKept": true,
  "createdAt": 1767225600000
}

Penghapusan

DELETE /v1/partner/users/:externalUserId menghapus data pengguna saat ia menutup akun atau meminta penghapusan. Jawabannya mencantumkan apa yang sudah dihapus sekarang, dan apa yang disimpan, alasannya, dan sampai kapan.

DELETE /v1/partner/users/:externalUserId
DELETE /v1/partner/users/user_42
Authorization: Bearer <partner key>

200 OK
{
  "deleted": ["images", "chip_files", "video", "personal_data", "address"],
  "retained": [
    { "category": "dedup_keys", "reason": "fraud_prevention", "until": 1924992000000 },
    { "category": "sanctions_screening_records", "reason": "legal_obligation", "until": 2082758400000 }
  ],
  "backupsPurgedWithinDays": 30
}

Error

Setiap error punya bentuk yang sama dan status HTTP tetap untuk setiap kode.

error
{
  "error": {
    "code": "step_not_allowed",
    "message": "This step is not allowed now.",
    "requestId": "req_..."
  }
}
KodeHTTPArti
bad_request400Request tidak sesuai kontrak.
unauthorized401Kunci atau token tidak ada atau salah.
token_expired401Token klien sudah kedaluwarsa. Buat yang baru dari backend Anda.
forbidden403Kunci ini tidak boleh melakukan ini.
not_found404Sesi atau pengguna tersebut tidak ada.
session_closed409Sesi sudah final. Tidak bisa unggah lagi.
step_not_allowed409Urutan salah, atau langkah ini tidak diperlukan sekarang.
revision_conflict409Revisi sesi sudah berubah. Baca ulang.
idempotency_conflict409Idempotency-Key yang sama dipakai untuk request yang berbeda.
challenge_expired410Tantangan sudah kedaluwarsa atau sudah dipakai. Minta yang baru.
payload_too_large413Unggahan terlalu besar.
unsupported_media_type415Jenis file tidak diterima.
rate_limited429Terlalu banyak request. Coba lagi nanti.
internal500Ada masalah di pihak kami.
dependency_unavailable503Layanan yang kami butuhkan sedang tidak berjalan. Coba lagi nanti.

Kode alasan

Sesi membawa reasonCodes saat perlu dikirim ulang atau ditolak. Tampilkan pesan terjemahan Anda sendiri untuk setiap kode. Jangan pernah menampilkan kode mentah kepada pengguna.

  • consent_missing
  • consent_withdrawn
  • attestation_failed
  • document_expired
  • document_unreadable
  • document_not_supported
  • document_suspected_tampering
  • document_number_visible
  • chip_verification_failed
  • chip_proof_missing
  • chip_data_mismatch
  • mrz_invalid
  • photo_quality
  • face_not_found
  • face_mismatch
  • liveness_failed
  • underage
  • location_not_supported
  • residence_not_supported
  • sanctions_match
  • pep_review
  • duplicate_identity
  • possible_duplicate
  • declaration_requires_review
  • user_requested_review
  • reviewer_other
  • address_unreadable
  • address_document_too_old
  • address_document_not_accepted
  • address_name_mismatch
  • address_signature_required
  • funds_document_unreadable
  • funds_document_too_old
  • funds_document_not_accepted
  • funds_more_evidence
  • funds_evidence_rejected