Aller au contenu
Zoi ID

Développez avec Zoi ID

Deux API sur un même hôte, des clients typés dans six langages, un parcours hébergé et une sandbox. Le guide complet est déjà consultable, et les clés sandbox arrivent avec l'accès anticipé.

URL de basehttps://kyc.zoi.network

Démarrage rapide

  1. 1

    Créez une session

    Votre backend appelle POST /v1/partner/sessions avec votre clé partenaire.

  2. 2

    Lancez le parcours

    Passez le jeton client au SDK, ou ouvrez verificationUrl dans un navigateur.

  3. 3

    Recevez le résultat

    Vérifiez la signature du webhook, puis enregistrez l'identifiant de l'identité et son statut.

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" }'

Deux API

API partenaire

De serveur à serveur, sous /v1/partner. Envoyez votre clé partenaire comme jeton Bearer. Ne la mettez jamais dans une appli.

API client

Utilisée par le SDK, sous /v1/client. Elle envoie un jeton client valable pour une session pendant 60 minutes.

Conventions

  • Les heures sont en millisecondes epoch, dans des champs dont le nom se termine par At.
  • Les dates sont au format YYYY-MM-DD. Les parties inconnues sont XX, par exemple 1990-XX-XX.
  • Les requêtes POST qui créent ou décident quelque chose acceptent un en-tête Idempotency-Key. Les clés sont gardées 24 heures.
  • Les changements sur une session envoient la révision attendue. Si elle a changé, vous recevez revision_conflict.

Déroulé de session

Votre backend crée la session. Le SDK fait les étapes. Le serveur vérifie l'ordre et répond step_not_allowed quand une étape arrive trop tôt.

1. Créez une session depuis votre backend

Un nouvel appel pour le même utilisateur reprend la session ouverte et donne un nouveau jeton client.

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. Les étapes du SDK

  1. 01

    Lire la session et les étapes encore nécessaires.

    GET /v1/client/session

  2. 02

    Enregistrer l'accord, avec le choix séparé et facultatif sur la comparaison du visage.

    POST /v1/client/consent

  3. 03

    Vérifier l'appli et l'appareil. Le résultat compte dans la décision.

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

  4. 04

    Envoyer le document : lire la puce après un scan de la MRZ, ou envoyer des photos.

    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

    Filmer une courte vidéo avec la caméra avant pendant que l'appli montre les mouvements de tête.

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

  6. 06

    Envoyer le pays de résidence, et l'adresse au niveau renforcé.

    POST /v1/client/profile

  7. 07

    Seulement avec l'exigence adresse : envoyer un document, puis confirmer l'adresse. Étape sautée quand la pièce d'identité l'a déjà fournie.

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

  8. 08

    Seulement avec une exigence sur les fonds : enregistrer les réponses, ajouter des documents si la politique les demande, puis terminer l'étape.

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

  9. 09

    Répondre à deux questions : personne politiquement exposée, et agit pour son propre compte.

    POST /v1/client/declarations

  10. 10

    Soumettre avec la révision actuelle.

    POST /v1/client/submit

L'utilisateur peut aussi demander qu'une personne examine un résultat automatique (POST /review-request) ou retirer son accord, ce qui annule la session et supprime ses fichiers tout de suite (POST /withdraw).

Justificatif de domicile et origine des fonds

Ajoutez requirements à POST /v1/partner/sessions pour vérifier plus que l'identité. address demande un justificatif de domicile. source_of_funds demande d'où vient l'argent, et source_of_wealth demande aussi le patrimoine total et la façon dont il s'est constitué. Les deux valeurs sur les fonds forment ensemble une seule étape, et toutes deux se combinent avec 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" }
}

Politiques facultatives

Sans politique, votre réglage partenaire par défaut s'applique, sinon le nôtre. La politique est copiée sur la session à sa création.

  • addressPolicy : maxAgeDays (90 par défaut, de 1 à 366), acceptedTypes (par défaut : documents d'énergie, de télécom et de banque, documents fiscaux, courriers officiels et certificats de résidence) et signedOnly (false par défaut).
  • fundsPolicy : questions, evidence (none, optional ou required, none par défaut), acceptedTypes, maxAgeDays (180 par défaut), currency (ISO 4217, USD par défaut) et brackets pour les tranches.
  • Une session ouverte avec d'autres exigences, pas encore soumise, est annulée et une nouvelle démarre, comme lors d'un changement de niveau.
  • Testez chaque résultat avec des scénarios de sandbox comme address_signed_pdf, address_document_review, funds_signed_documents et funds_review.

Sessions de suivi

Vérifiez uniquement les exigences pour un utilisateur dont l'identité est déjà active chez vous. Envoyez reuseIdentity: true avec requirements.

  • L'utilisateur donne son consentement et ne fait que les nouvelles étapes. Aucun document, selfie ou déclaration n'est redemandé.
  • En cas d'approbation, l'identité garde son identifiant, sa date de vérification et sa validité, et reçoit le nouveau résultat. Vous recevez session.approved et identity.updated.
  • Sans identité active, l'appel répond 409 step_not_allowed.
  • Déclenchez-la depuis vos propres règles de risque, comme un dépôt important, un signalement PEP ou un pays à risque. Vos règles restent dans votre backend, au même endroit.
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", ... } }

Résultats et permissions

Les sessions approuvées portent address et funds, et l'identité garde le plus récent de chacun. Les champs de synthèse viennent avec toutes les permissions. Les détails exigent une permission.

  • Permission address : les lignes de l'adresse. Sans elle, vous recevez verified, method, evidenceLevel, documentType, documentDate, verifiedAt et country.
  • Permission financial_data : answers, documents et findings. Sans elle, vous recevez kinds, evidenceLevel, reviewed, documentCount et verifiedAt. Elle ne fait jamais partie de eligibility.
  • evidenceLevel vaut high, medium, low ou none. Méthodes d'adresse, de la plus solide à la plus faible : wallet, id_document, signed_pdf, official_code, document_review et declared.
  • Les documents ne passent pas par l'API. Les membres qui ont le rôle conformité peuvent consulter les pages des documents sur l'origine des fonds dans le tableau de bord, si l'environnement a la permission financial_data. Chaque consultation est journalisée.
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": []
  }
}

Parcours web

Pas de SDK ? Envoyez l'utilisateur sur notre page hébergée. Elle utilise la même API, les mêmes contrôles et la même file de revue.

1. Obtenir le lien

POST /v1/partner/sessions renvoie verificationUrl. Passez redirectUrl pour renvoyer l'utilisateur chez vous à la fin. L'URL doit commencer par une de vos origines de redirection enregistrées.

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",
  ...
}
  • Le jeton client est dans le fragment de l'URL, après #. Les navigateurs n'envoient jamais le fragment à un serveur, donc le jeton n'apparaît ni dans les journaux ni dans l'en-tête Referer. La page le lit, le retire de la barre d'adresse et le garde seulement en mémoire.
  • Ajoutez lang au fragment pour choisir une des 19 langues. Sans lui, la page utilise la langue du navigateur.
  • Un rechargement perd le jeton. Créez un nouveau lien pour la même session depuis votre backend.
  • La page envoie channel: "web" avec le consentement. Les navigateurs ne peuvent pas lire les puces des pièces d'identité : les sessions web utilisent donc des photos du document ou un portefeuille d'identité numérique. Elles sautent le contrôle de l'appareil, et les photos de documents envoyées depuis le web sont toujours tranchées par un examinateur formé.
  • Sur un ordinateur, la page affiche un code QR pour que l'utilisateur continue sur un téléphone.

2. Quand l'utilisateur a fini

Quand le parcours se termine (envoyé, annulé ou statut final), la page publie ce message vers chacun de ces objets qui existe : window.ReactNativeWebView, window.webkit.messageHandlers.zoiId, window.ZoiIdAndroid et window.opener. Puis elle ouvre redirectUrl avec zoiIdSession et zoiIdStatus ajoutés.

Ne faites jamais confiance au message ni aux paramètres de redirection. Faites confiance seulement à votre webhook ou à 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. Réglages de la webview

Android WebView

Activez javaScriptEnabled et domStorageEnabled, et mettez mediaPlaybackRequiresUserGesture à false. Dans un WebChromeClient, accordez onPermissionRequest pour RESOURCE_VIDEO_CAPTURE une fois que votre app a la permission CAMERA. Ajoutez un sélecteur de fichiers pour la solution de secours par photo.

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 ou plus récent. Mettez allowsInlineMediaPlayback à true et ajoutez NSCameraUsageDescription à votre app. Depuis iOS 15, vous pouvez décider de la permission caméra dans le délégué UI.

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

Définissez mediaCapturePermissionGrantType="grant", allowsInlineMediaPlayback et javaScriptEnabled. Votre app a besoin de la permission caméra.

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.
    }
  }}
/>

Les iframes ne sont pas prises en charge, à cause des permissions caméra et du clickjacking. Utilisez un onglet, une fenêtre popup ou une webview.

Statuts

Session

created
La session existe, rien n'est encore envoyé.
in_progress
Accord donné, les étapes sont en cours d'envoi.
processing
Soumise, les contrôles tournent.
review
En attente d'un examinateur formé.
approved
Final. L'identité est vérifiée.
rejected
Final. Voir les codes de motif.
resubmit
L'utilisateur doit refaire les étapes de requiredSteps.
expired
Pas de décision finale à temps.
cancelled
Annulée par le partenaire, l'utilisateur ou une suppression.

Identité

active
Utilisable.
suspended
En attente, par exemple après une nouvelle correspondance avec une liste de sanctions.
expired
Le document ou la période de vérification a pris fin. Une nouvelle session vérifie à nouveau.
revoked
Final.

SDK

Le SDK React Native (Expo) vous donne un client API typé, la lecture de puce NFC et le contrôle de l'appareil. Il est headless : votre appli garde le contrôle total de ses écrans et de sa caméra. Côté serveur, utilisez les clients typés pour TypeScript, Python, Go, PHP, Java et 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;
}

Webhooks

Nous envoyons un webhook quand une session ou une identité change. Vérifiez toujours la signature avant de faire confiance au contenu.

  • Chaque webhook porte un en-tête Zoi-Id-Signature : t=unix seconds,v1=hex.
  • v1 est le HMAC-SHA256 du texte t.raw body avec votre secret de webhook. Utilisez le corps brut, avant toute lecture du JSON.
  • Pendant un changement de secret, l'en-tête porte un v1 par secret actif. Acceptez le webhook si l'un d'eux correspond.
  • Refusez-le si t est à plus de 300 secondes de maintenant.
  • Les envois ratés sont refaits pendant 72 heures. Chaque nouvel essai est signé à nouveau avec un nouveau t.
  • Les identités portent une revision. Appliquez un événement seulement si sa révision est plus récente que celle que vous avez gardée.
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"
  }
}

Types d'événements

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

Vérifier la signature en 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 });
}

Vérifier la signature en 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
}

Flux d'événements

GET /v1/partner/events renvoie tous les événements des 30 derniers jours, du plus ancien au plus récent. Passez after avec le dernier nextCursor pour lire la page suivante. Utilisez-le après une longue panne, ou pour vérifier qu'il ne vous manque rien.

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
}

Signalements de fraude

Prévenez-nous quand une session tranchée s'avère frauduleuse, par exemple après une rétrofacturation. Les signalements alimentent notre mesure des approbations erronées. Ils ne changent ni la session, ni l'identité, ni le résultat de l'utilisateur.

  • Permission sessions:write. Seulement pour les sessions tranchées (approuvées ou refusées). Avant une décision, l'appel répond 409.
  • Un corps vide signale confirmed_fraud avec la catégorie other. status peut aussi valoir suspected_fraud, ou cleared pour retirer votre propre signalement.
  • Catégories : forged_document, impostor, deepfake_or_replay, synthetic_identity, account_takeover et other. Pas de catégorie avec cleared.
  • note est un texte libre pour nos analystes, de 1000 caractères au plus. N'y mettez jamais de noms, de numéros de documents ni d'autres données personnelles.
  • Un nouveau signalement répond 201. Le même signalement envoyé à nouveau renvoie celui déjà enregistré, avec 200. Envoyez une Idempotency-Key pour réessayer sans risque. 20 signalements au plus par session.
  • Le même signalement est à un clic, sur la page de la session dans le tableau de bord.
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
}

Suppression

DELETE /v1/partner/users/:externalUserId supprime les données d'un utilisateur quand il ferme son compte ou demande la suppression. La réponse indique ce qui a été supprimé maintenant, et ce qui est gardé, pourquoi et jusqu'à quand.

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
}

Erreurs

Chaque erreur a la même forme et un statut HTTP fixe par code.

error
{
  "error": {
    "code": "step_not_allowed",
    "message": "This step is not allowed now.",
    "requestId": "req_..."
  }
}
CodeHTTPSens
bad_request400La requête ne respecte pas le contrat.
unauthorized401Clé ou jeton manquant ou incorrect.
token_expired401Le jeton client a expiré. Créez-en un nouveau depuis votre backend.
forbidden403Cette clé n'a pas le droit de faire cela.
not_found404Cette session ou cet utilisateur n'existe pas.
session_closed409La session est finale. Plus aucun envoi.
step_not_allowed409Mauvais ordre, ou l'étape n'est pas nécessaire maintenant.
revision_conflict409La révision de la session a changé. Relisez-la.
idempotency_conflict409La même Idempotency-Key a servi pour une autre requête.
challenge_expired410Le défi a expiré ou a déjà servi. Demandez-en un nouveau.
payload_too_large413L'envoi est trop gros.
unsupported_media_type415Ce type de fichier n'est pas accepté.
rate_limited429Trop de requêtes. Réessayez plus tard.
internal500Un problème est survenu de notre côté.
dependency_unavailable503Un service dont nous avons besoin est en panne. Réessayez plus tard.

Codes de motif

Les sessions portent des reasonCodes quand il faut renvoyer quelque chose ou qu'elles sont refusées. Affichez votre propre message traduit pour chaque code. Ne montrez jamais le code brut aux utilisateurs.

  • 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