API de vérification d'identité — v1

Adresse de base : https://getsanno.com/api/v1

API REST de vérification d'identité. Le principe : vous créez un dossier pour votre utilisateur, vous lui transmettez son lien privé, il capture sa pièce d'identité, un selfie et les mouvements de tête demandés (puis, selon le parcours, un justificatif de domicile et l'origine de ses fonds), et vous recevez la décision par webhook.

Authentification

Chaque requête porte l'en-tête Authorization: Bearer <clé>. Les clés se créent dans le tableau de bord et ne sont montrées qu'une fois :

Préfixe Environnement Usage
kyc_live_… production vrais utilisateurs
kyc_test_… bac à sable intégration et tests (simulation des décisions)

Une clé ne voit que les données de son compte et de son environnement : toute autre ressource répond 404. Une clé révoquée répond 401.

Cycle de vie d'un dossier

Statut Signification
created lien émis, l'utilisateur n'a pas commencé
in_progress capture en cours
submitted captures envoyées, analyse planifiée : la décision arrive au plus tard à due_at (délai réglé dans le tableau de bord, de l'instantané à 72 h)
processing analyse en cours
approved identité vérifiée
declined refusé pour un motif robuste (pièce expirée confirmée par la MRZ, âge insuffisant, spécimen)
review à examiner par un analyste (doublon, incertitude, contrôle à confirmer)
resubmission_requested photo à refaire (floue, illisible, non prise en direct) : le même lien permet de recommencer, son échéance est repoussée d'au moins 24 h
expired lien expiré avant la fin de la capture
cancelled annulé par vous (ou effacement de la personne)

Une incertitude de lecture ou une photo médiocre ne mène jamais à un refus, seulement à une revue ou à une reprise. La validation automatique n'est activée qu'après calibrage du moteur sur un banc indépendant : d'ici là, les dossiers conformes passent en review (raison auto_approval_disabled). Vos réglages de décision (tableau de bord) fixent le délai, le refus et la validation automatiques et les seuils des mesures du moteur ; une personne politiquement exposée déclarée ou une origine des fonds à risque élevé passe toujours en revue (pep_declared, source_of_funds_high_risk).

Raisons

reasons liste des codes stables en snake_case, par exemple : document_expired, underage, specimen_or_fake_document (refus) ; duplicate_document, name_mismatch, date_of_birth_mismatch, face_mismatch, face_match_uncertain, liveness_unverified, mrz_unverified, expiry_unverified, address_mismatch, document_too_old, documents_missing, engine_unavailable, auto_approval_disabled, auto_decline_disabled, face_similarity_below_threshold (et les autres *_below_threshold), pep_declared, source_of_funds_high_risk (revue) ; capture_quality avec identity_document_quality, selfie_quality, address_document_quality (reprise) ; cancelled_by_client, link_expired, applicant_erased.

Erreurs

Toutes les erreurs ont la même forme, sans donnée du dossier :

{"error": {"code": "validation_failed", "message": "Les données envoyées sont invalides.", "fields": {"applicant.email": ["…"]}}}
HTTP Codes
401 missing_api_key, invalid_api_key
403 organization_inactive, sandbox_only
404 not_found, document_unavailable
409 invalid_state, idempotency_key_reused, idempotency_request_in_progress, verification_processing
422 validation_failed (détail dans fields), no_flow_configured, invalid_idempotency_key
429 rate_limited (en-tête Retry-After)

Idempotence

Les requêtes POST acceptent l'en-tête Idempotency-Key (1 à 255 caractères ASCII). Rejouée avec la même clé et le même corps pendant 24 h, la requête renvoie la réponse d'origine (en-tête Idempotent-Replayed: true) sans rien recréer. La même clé avec un autre corps répond 409.

Limitation de débit

120 requêtes par minute et par clé par défaut (en-têtes X-RateLimit-Limit et X-RateLimit-Remaining). Au-delà : 429 avec Retry-After (secondes).

Pagination

Les listes renvoient {"data": [...], "has_more": true, "next_cursor": "…"} : passez next_cursor dans le paramètre cursor pour la page suivante (limit : 1 à 100, 25 par défaut).

Webhooks

Déclarez vos adresses de réception dans le tableau de bord (HTTPS obligatoire en production ; les adresses privées ou internes sont refusées et les redirections ne sont jamais suivies). Chaque changement de statut envoie un POST JSON :

{
  "id": "01jb3c9w4q8t2n6m5r7k1d0f3e",
  "type": "verification.approved",
  "created_at": "2026-10-24T09:30:00Z",
  "data": {
    "verification_id": "01jb2y8q5z3x4c6v7b8n9m0k1j",
    "applicant_id": "01jb2y7k4m3n2b1v0c9x8z7a6s",
    "external_id": "user-48151623",
    "status": "approved",
    "reasons": [],
    "environment": "live"
  }
}

Types : verification.<statut> (par exemple verification.submitted, verification.review, verification.approved) et webhook.test (bouton d'essai du tableau de bord). Répondez 2xx en moins de 10 secondes ; sinon l'envoi est retenté après 1 min, 5 min, 30 min, 2 h puis 6 h. Le même événement peut arriver deux fois : dédoublonnez sur id.

Vérifiez toujours la signature de l'en-tête Kyc-Signature: t=<horodatage>,v1=<signature> : v1 vaut HMAC-SHA256(secret du point d'arrivée, <horodatage>.<corps brut>), en hexadécimal. Refusez une signature invalide ou un horodatage de plus de 5 minutes (rejeu).

function kycSignatureValid(?string $header, string $body, string $secret): bool
{
    if ($header === null || ! preg_match('/^t=(\d+),/', $header, $t) || abs(time() - (int) $t[1]) > 300) {
        return false;
    }
    $expected = hash_hmac('sha256', $t[1].'.'.$body, $secret);
    preg_match_all('/(?:^|,)v1=([0-9a-f]{64})/', $header, $signatures);
    foreach ($signatures[1] as $v1) {
        if (hash_equals($expected, $v1)) {
            return true;
        }
    }

    return false;
}
// Corps brut : file_get_contents('php://input'). Signature invalide : répondre 401.
function kycSignatureValid(header, rawBody, secret) {
  const t = /^t=(\d+),/.exec(header || '');
  if (!t || Math.abs(Date.now() / 1000 - Number(t[1])) > 300) return false;
  const expected = Buffer.from(crypto.createHmac('sha256', secret).update(`${t[1]}.${rawBody}`).digest('hex'));
  return [...(header.matchAll(/(?:^|,)v1=([0-9a-f]{64})/g))]
    .some(([, v1]) => crypto.timingSafeEqual(expected, Buffer.from(v1)));
}

Dossiers

Création et suivi des dossiers de vérification.

Lister les dossiers

GET /api/v1/verifications

Du plus récent au plus ancien, pagination par curseur (next_cursor). Les éléments ne contiennent ni données extraites ni contrôles : demandez le détail par GET /verifications/{id}.

Paramètres

NomOùTypeDescription
status requête string | null
external_id requête string | null
created_after requête string | null
limit requête integer | null
cursor requête string | null

Réponses

200 Succès
{
    "data": [
        {
            "id": "string",
            "object": "verification",
            "status": "string",
            "environment": "string",
            "flow": "string",
            "applicant_id": "string",
            "external_id": "string",
            "reasons": [
                null
            ],
            "risk": {
                "score": 0,
                "level": "string"
            },
            "expires_at": "string",
            "created_at": "string",
            "completed_at": "string",
            "updated_at": "string"
        }
    ],
    "has_more": true,
    "next_cursor": "string"
}
401 Clé API absente (missing_api_key), invalide ou révoquée (invalid_api_key).
{
    "error": {
        "code": "invalid_api_key",
        "message": "Clé API invalide ou révoquée."
    }
}
422 Données invalides (validation_failed, détail par champ dans fields), aucun parcours configuré (no_flow_configured) ou en-tête Idempotency-Key invalide.
{
    "error": {
        "code": "validation_failed",
        "message": "Les données envoyées sont invalides.",
        "fields": {
            "applicant.email": [
                "The applicant.email field must be a valid email address."
            ]
        }
    }
}
429 Trop de requêtes pour cette clé (rate_limited) : réessayez après Retry-After secondes.
{
    "error": {
        "code": "rate_limited",
        "message": "Trop de requêtes : réessayez plus tard."
    }
}

Créer un dossier

POST /api/v1/verifications

Crée (ou met à jour, par external_id) la personne à vérifier, puis ouvre un dossier dans l'environnement de la clé. Transmettez url à l'utilisateur final : c'est son lien privé vers le parcours de capture (pièce, selfie et vivacité, domicile…). Le lien n'est montré qu'ici : il ne peut être que renouvelé (POST /verifications/{id}/link).

Rejouer la requête avec le même en-tête Idempotency-Key et le même corps renvoie la même réponse pendant 24 h, sans créer de second dossier.

Paramètres

NomOùTypeDescription
Idempotency-Key en-tête string Clé unique choisie par vous (1 à 255 caractères) pour rejouer la requête sans doublon. Ex. : 7f1c9a52-3c1e-4c8e-9b3a-2f4d5e6a7b8c

Corps de la requête (JSON)

ChampTypeDescription
applicant requis object Personne à vérifier, retrouvée par external_id (créée si elle n'existe pas, mise à jour sinon : seuls les champs envoyés sont modifiés, null efface).
applicant.external_id requis string
max. 255
Identifiant de l'utilisateur dans votre système.
applicant.email string | null
format email · max. 255
applicant.first_name string | null
max. 100
applicant.last_name string | null
max. 100
applicant.date_of_birth string | null
format date
Date de naissance déclarée (AAAA-MM-JJ), comparée à celle lue sur la pièce.
applicant.nationality string | null
min. 3 · max. 3
Nationalité, code ISO 3166-1 alpha-3.
applicant.country string | null
min. 3 · max. 3
Pays de résidence, code ISO 3166-1 alpha-3.
applicant.address object Adresse déclarée, comparée au justificatif de domicile.
applicant.address.line1 string | null
max. 255
applicant.address.line2 string | null
max. 255
applicant.address.city string | null
max. 100
applicant.address.postal_code string | null
max. 20
applicant.address.state string | null
max. 100
flow string | null
max. 100
Identifiant (slug) du parcours à suivre ; le parcours par défaut du compte sinon.
redirect_url string | null
format uri · max. 2048
Adresse où renvoyer l'utilisateur à la fin du parcours (HTTPS obligatoire en production).
locale string | null
24 valeurs
Langue du parcours (code ISO 639-1 d'une langue officielle de l'UE).
metadata array<string> | null
max. 20
Vos données libres, rendues telles quelles : objet plat de 20 clés au plus (clé ≤ 40 caractères, valeur scalaire ≤ 500 caractères).
expires_in integer | null
≥ 15 · ≤ 43200
Durée de validité du lien, en minutes (15 à 43200 ; 1440 par défaut).
Exemple de corps
{
    "applicant": {
        "external_id": "user-48151623",
        "email": "[email protected]",
        "first_name": "Jeanne",
        "last_name": "Martin",
        "date_of_birth": "1990-04-12",
        "nationality": "FRA",
        "country": "FRA",
        "address": {
            "line1": "12 rue de la Paix",
            "line2": "Bâtiment B",
            "city": "Paris",
            "postal_code": "75002",
            "state": "Île-de-France"
        }
    },
    "flow": "standard",
    "redirect_url": "https://www.example.com/kyc/retour",
    "locale": "fr",
    "metadata": {
        "order_id": "A-1042"
    },
    "expires_in": 1440
}

Réponses

201 Succès
{
    "id": "01jb2y8q5z3x4c6v7b8n9m0k1j",
    "object": "verification",
    "status": "created",
    "environment": "live",
    "url": "https://kyc.example.com/v/Xr4b0Yq0m2c3VJ5q9yQwXr4b0Yq0m2c3VJ5q9yQwXr4b0Yq0",
    "flow": "standard",
    "applicant_id": "01jb2y7k4m3n2b1v0c9x8z7a6s",
    "external_id": "user-48151623",
    "decision": {
        "status": "approved",
        "source": "auto",
        "decided_at": "2026-10-24T09:42:00Z"
    },
    "reasons": [
        "string"
    ],
    "risk": {
        "score": 0,
        "level": "low"
    },
    "checks": [
        {
            "code": "identity.face_match",
            "status": "passed",
            "reason": "string",
            "source": "string"
        }
    ],
    "extracted": {},
    "documents": [
        {
            "id": 0,
            "kind": "identity_front",
            "document_type": "string",
            "country": "string",
            "mime": "string",
            "size": 0,
            "width": 0,
            "height": 0,
            "source": "string",
            "attempt": 0,
            "created_at": "string",
            "download_url": "string"
        }
    ],
    "metadata": {},
    "redirect_url": "string",
    "locale": "fr",
    "expires_at": "2026-10-25T09:30:00Z",
    "created_at": "2026-10-24T09:30:00Z",
    "started_at": "2026-10-24T09:31:00Z",
    "submitted_at": "2026-10-24T09:38:00Z",
    "due_at": "2026-10-25T09:38:00Z",
    "completed_at": "2026-10-24T09:42:00Z",
    "updated_at": "2026-10-24T09:42:00Z"
}
401 Clé API absente (missing_api_key), invalide ou révoquée (invalid_api_key).
{
    "error": {
        "code": "invalid_api_key",
        "message": "Clé API invalide ou révoquée."
    }
}
409 État incompatible (invalid_state, verification_processing) ou clé d'idempotence déjà employée (idempotency_key_reused, idempotency_request_in_progress).
{
    "error": {
        "code": "invalid_state",
        "message": "Ce dossier ne peut plus être annulé (statut « approved »)."
    }
}
422 Données invalides (validation_failed, détail par champ dans fields), aucun parcours configuré (no_flow_configured) ou en-tête Idempotency-Key invalide.
{
    "error": {
        "code": "validation_failed",
        "message": "Les données envoyées sont invalides.",
        "fields": {
            "applicant.email": [
                "The applicant.email field must be a valid email address."
            ]
        }
    }
}
429 Trop de requêtes pour cette clé (rate_limited) : réessayez après Retry-After secondes.
{
    "error": {
        "code": "rate_limited",
        "message": "Trop de requêtes : réessayez plus tard."
    }
}

Consulter un dossier

GET /api/v1/verifications/{verification}

Statut, décision, raisons, risque, résumé des contrôles, données extraites normalisées, métadonnées des pièces (sans le fichier) et horodatages.

Paramètres

NomOùTypeDescription
verification requis chemin string

Réponses

200 Succès
{
    "id": "01jb2y8q5z3x4c6v7b8n9m0k1j",
    "object": "verification",
    "status": "created",
    "environment": "live",
    "url": "https://kyc.example.com/v/Xr4b0Yq0m2c3VJ5q9yQwXr4b0Yq0m2c3VJ5q9yQwXr4b0Yq0",
    "flow": "standard",
    "applicant_id": "01jb2y7k4m3n2b1v0c9x8z7a6s",
    "external_id": "user-48151623",
    "decision": {
        "status": "approved",
        "source": "auto",
        "decided_at": "2026-10-24T09:42:00Z"
    },
    "reasons": [
        "string"
    ],
    "risk": {
        "score": 0,
        "level": "low"
    },
    "checks": [
        {
            "code": "identity.face_match",
            "status": "passed",
            "reason": "string",
            "source": "string"
        }
    ],
    "extracted": {},
    "documents": [
        {
            "id": 0,
            "kind": "identity_front",
            "document_type": "string",
            "country": "string",
            "mime": "string",
            "size": 0,
            "width": 0,
            "height": 0,
            "source": "string",
            "attempt": 0,
            "created_at": "string",
            "download_url": "string"
        }
    ],
    "metadata": {},
    "redirect_url": "string",
    "locale": "fr",
    "expires_at": "2026-10-25T09:30:00Z",
    "created_at": "2026-10-24T09:30:00Z",
    "started_at": "2026-10-24T09:31:00Z",
    "submitted_at": "2026-10-24T09:38:00Z",
    "due_at": "2026-10-25T09:38:00Z",
    "completed_at": "2026-10-24T09:42:00Z",
    "updated_at": "2026-10-24T09:42:00Z"
}
401 Clé API absente (missing_api_key), invalide ou révoquée (invalid_api_key).
{
    "error": {
        "code": "invalid_api_key",
        "message": "Clé API invalide ou révoquée."
    }
}
404 Ressource inexistante pour ce compte et cet environnement (not_found).
{
    "error": {
        "code": "not_found",
        "message": "Ressource introuvable."
    }
}
429 Trop de requêtes pour cette clé (rate_limited) : réessayez après Retry-After secondes.
{
    "error": {
        "code": "rate_limited",
        "message": "Trop de requêtes : réessayez plus tard."
    }
}

Annuler un dossier

POST /api/v1/verifications/{verification}/cancel

Possible tant qu'aucune décision n'est prise (created, in_progress, submitted, resubmission_requested). Le lien de l'utilisateur cesse de fonctionner.

Paramètres

NomOùTypeDescription
verification requis chemin string
Idempotency-Key en-tête string Clé unique pour rejouer la requête.

Réponses

200 Succès
{
    "id": "01jb2y8q5z3x4c6v7b8n9m0k1j",
    "object": "verification",
    "status": "created",
    "environment": "live",
    "url": "https://kyc.example.com/v/Xr4b0Yq0m2c3VJ5q9yQwXr4b0Yq0m2c3VJ5q9yQwXr4b0Yq0",
    "flow": "standard",
    "applicant_id": "01jb2y7k4m3n2b1v0c9x8z7a6s",
    "external_id": "user-48151623",
    "decision": {
        "status": "approved",
        "source": "auto",
        "decided_at": "2026-10-24T09:42:00Z"
    },
    "reasons": [
        "string"
    ],
    "risk": {
        "score": 0,
        "level": "low"
    },
    "checks": [
        {
            "code": "identity.face_match",
            "status": "passed",
            "reason": "string",
            "source": "string"
        }
    ],
    "extracted": {},
    "documents": [
        {
            "id": 0,
            "kind": "identity_front",
            "document_type": "string",
            "country": "string",
            "mime": "string",
            "size": 0,
            "width": 0,
            "height": 0,
            "source": "string",
            "attempt": 0,
            "created_at": "string",
            "download_url": "string"
        }
    ],
    "metadata": {},
    "redirect_url": "string",
    "locale": "fr",
    "expires_at": "2026-10-25T09:30:00Z",
    "created_at": "2026-10-24T09:30:00Z",
    "started_at": "2026-10-24T09:31:00Z",
    "submitted_at": "2026-10-24T09:38:00Z",
    "due_at": "2026-10-25T09:38:00Z",
    "completed_at": "2026-10-24T09:42:00Z",
    "updated_at": "2026-10-24T09:42:00Z"
}
401 Clé API absente (missing_api_key), invalide ou révoquée (invalid_api_key).
{
    "error": {
        "code": "invalid_api_key",
        "message": "Clé API invalide ou révoquée."
    }
}
404 Ressource inexistante pour ce compte et cet environnement (not_found).
{
    "error": {
        "code": "not_found",
        "message": "Ressource introuvable."
    }
}
409 État incompatible (invalid_state, verification_processing) ou clé d'idempotence déjà employée (idempotency_key_reused, idempotency_request_in_progress).
{
    "error": {
        "code": "invalid_state",
        "message": "Ce dossier ne peut plus être annulé (statut « approved »)."
    }
}
429 Trop de requêtes pour cette clé (rate_limited) : réessayez après Retry-After secondes.
{
    "error": {
        "code": "rate_limited",
        "message": "Trop de requêtes : réessayez plus tard."
    }
}

Télécharger une pièce

GET /api/v1/verifications/{verification}/documents/{document}

Renvoie le fichier déchiffré (image ou PDF) d'une pièce du dossier. Chaque téléchargement est inscrit au journal d'audit. La réponse ne doit pas être mise en cache.

Paramètres

NomOùTypeDescription
verification requis chemin string
document requis chemin string

Réponses

200 Contenu du fichier (Content-Type de la pièce).
{}
401 Clé API absente (missing_api_key), invalide ou révoquée (invalid_api_key).
{
    "error": {
        "code": "invalid_api_key",
        "message": "Clé API invalide ou révoquée."
    }
}
404 Ressource inexistante pour ce compte et cet environnement (not_found).
{
    "error": {
        "code": "not_found",
        "message": "Ressource introuvable."
    }
}
429 Trop de requêtes pour cette clé (rate_limited) : réessayez après Retry-After secondes.
{
    "error": {
        "code": "rate_limited",
        "message": "Trop de requêtes : réessayez plus tard."
    }
}

Personnes

Personnes vérifiées (retrouvées par votre identifiant external_id) et effacement RGPD.

Lister les personnes

GET /api/v1/applicants

Filtre facultatif external_id (votre identifiant). Pagination par curseur.

Paramètres

NomOùTypeDescription
external_id requête string | null
limit requête integer | null
cursor requête string | null

Réponses

200 Succès
{
    "data": [
        {
            "id": "01jb2y7k4m3n2b1v0c9x8z7a6s",
            "object": "applicant",
            "environment": "live",
            "external_id": "user-48151623",
            "email": "string",
            "first_name": "string",
            "last_name": "string",
            "date_of_birth": null,
            "nationality": null,
            "country": "string",
            "address": {
                "line1": "string",
                "line2": "string",
                "city": "string",
                "postal_code": "string",
                "state": "string"
            },
            "risk_level": "low",
            "verifications": [
                {
                    "id": "string",
                    "status": "string",
                    "created_at": "string"
                }
            ],
            "created_at": "string",
            "updated_at": "string"
        }
    ],
    "has_more": true,
    "next_cursor": "string"
}
401 Clé API absente (missing_api_key), invalide ou révoquée (invalid_api_key).
{
    "error": {
        "code": "invalid_api_key",
        "message": "Clé API invalide ou révoquée."
    }
}
422 Données invalides (validation_failed, détail par champ dans fields), aucun parcours configuré (no_flow_configured) ou en-tête Idempotency-Key invalide.
{
    "error": {
        "code": "validation_failed",
        "message": "Les données envoyées sont invalides.",
        "fields": {
            "applicant.email": [
                "The applicant.email field must be a valid email address."
            ]
        }
    }
}
429 Trop de requêtes pour cette clé (rate_limited) : réessayez après Retry-After secondes.
{
    "error": {
        "code": "rate_limited",
        "message": "Trop de requêtes : réessayez plus tard."
    }
}

Consulter une personne

GET /api/v1/applicants/{applicant}

Données déclarées et derniers dossiers (50 au plus).

Paramètres

NomOùTypeDescription
applicant requis chemin string

Réponses

200 Succès
{
    "id": "01jb2y7k4m3n2b1v0c9x8z7a6s",
    "object": "applicant",
    "environment": "live",
    "external_id": "user-48151623",
    "email": "string",
    "first_name": "string",
    "last_name": "string",
    "date_of_birth": null,
    "nationality": null,
    "country": "string",
    "address": {
        "line1": "string",
        "line2": "string",
        "city": "string",
        "postal_code": "string",
        "state": "string"
    },
    "risk_level": "low",
    "verifications": [
        {
            "id": "string",
            "status": "string",
            "created_at": "string"
        }
    ],
    "created_at": "string",
    "updated_at": "string"
}
401 Clé API absente (missing_api_key), invalide ou révoquée (invalid_api_key).
{
    "error": {
        "code": "invalid_api_key",
        "message": "Clé API invalide ou révoquée."
    }
}
404 Ressource inexistante pour ce compte et cet environnement (not_found).
{
    "error": {
        "code": "not_found",
        "message": "Ressource introuvable."
    }
}
429 Trop de requêtes pour cette clé (rate_limited) : réessayez après Retry-After secondes.
{
    "error": {
        "code": "rate_limited",
        "message": "Trop de requêtes : réessayez plus tard."
    }
}

Effacer une personne (RGPD)

DELETE /api/v1/applicants/{applicant}

Efface les pièces, les rapports d'analyse, les données lues et déclarées de la personne et de tous ses dossiers. Les dossiers qui attendent encore l'utilisateur (created, in_progress, submitted, resubmission_requested) sont annulés ; un dossier en revue ou tranché garde son statut, sans plus aucune donnée. Restent la coquille des dossiers (statuts, dates) et la trace d'audit, nécessaires pour prouver la conformité. Irréversible. Refusé (409) pendant l'analyse d'un dossier : réessayez quelques minutes plus tard.

Paramètres

NomOùTypeDescription
applicant requis chemin string

Réponses

200 Succès
{
    "id": "string",
    "object": "applicant",
    "deleted": true
}
401 Clé API absente (missing_api_key), invalide ou révoquée (invalid_api_key).
{
    "error": {
        "code": "invalid_api_key",
        "message": "Clé API invalide ou révoquée."
    }
}
404 Ressource inexistante pour ce compte et cet environnement (not_found).
{
    "error": {
        "code": "not_found",
        "message": "Ressource introuvable."
    }
}
409 État incompatible (invalid_state, verification_processing) ou clé d'idempotence déjà employée (idempotency_key_reused, idempotency_request_in_progress).
{
    "error": {
        "code": "invalid_state",
        "message": "Ce dossier ne peut plus être annulé (statut « approved »)."
    }
}
429 Trop de requêtes pour cette clé (rate_limited) : réessayez après Retry-After secondes.
{
    "error": {
        "code": "rate_limited",
        "message": "Trop de requêtes : réessayez plus tard."
    }
}

Bac à sable

Outils réservés aux clés de test (kyc_test_…) pour éprouver votre intégration.

Simuler une décision

POST /api/v1/verifications/{verification}/simulate

Bac à sable uniquement : mène le dossier jusqu'à l'issue demandée par les transitions normales (in_progress, submitted, processing, puis l'issue), avec les événements et webhooks correspondants, sans capture ni analyse. Le dossier reçoit des données extraites et des contrôles fictifs. Une clé live reçoit 403.

Paramètres

NomOùTypeDescription
verification requis chemin string
Idempotency-Key en-tête string Clé unique pour rejouer la requête.

Corps de la requête (JSON)

ChampTypeDescription
outcome requis string
approved, declined, review, resubmission_requested
Issue à simuler.
reasons array<string> | null
max. 10
Raisons à renvoyer (codes en snake_case) ; une valeur par défaut selon l'issue sinon.
Exemple de corps
{
    "outcome": "approved",
    "reasons": [
        "document_expired"
    ]
}

Réponses

200 Succès
{
    "id": "01jb2y8q5z3x4c6v7b8n9m0k1j",
    "object": "verification",
    "status": "created",
    "environment": "live",
    "url": "https://kyc.example.com/v/Xr4b0Yq0m2c3VJ5q9yQwXr4b0Yq0m2c3VJ5q9yQwXr4b0Yq0",
    "flow": "standard",
    "applicant_id": "01jb2y7k4m3n2b1v0c9x8z7a6s",
    "external_id": "user-48151623",
    "decision": {
        "status": "approved",
        "source": "auto",
        "decided_at": "2026-10-24T09:42:00Z"
    },
    "reasons": [
        "string"
    ],
    "risk": {
        "score": 0,
        "level": "low"
    },
    "checks": [
        {
            "code": "identity.face_match",
            "status": "passed",
            "reason": "string",
            "source": "string"
        }
    ],
    "extracted": {},
    "documents": [
        {
            "id": 0,
            "kind": "identity_front",
            "document_type": "string",
            "country": "string",
            "mime": "string",
            "size": 0,
            "width": 0,
            "height": 0,
            "source": "string",
            "attempt": 0,
            "created_at": "string",
            "download_url": "string"
        }
    ],
    "metadata": {},
    "redirect_url": "string",
    "locale": "fr",
    "expires_at": "2026-10-25T09:30:00Z",
    "created_at": "2026-10-24T09:30:00Z",
    "started_at": "2026-10-24T09:31:00Z",
    "submitted_at": "2026-10-24T09:38:00Z",
    "due_at": "2026-10-25T09:38:00Z",
    "completed_at": "2026-10-24T09:42:00Z",
    "updated_at": "2026-10-24T09:42:00Z"
}
401 Clé API absente (missing_api_key), invalide ou révoquée (invalid_api_key).
{
    "error": {
        "code": "invalid_api_key",
        "message": "Clé API invalide ou révoquée."
    }
}
403 Action interdite pour cette clé : compte inactif (organization_inactive), simulation hors bac à sable (sandbox_only).
{
    "error": {
        "code": "sandbox_only",
        "message": "La simulation n'est disponible qu'avec une clé de bac à sable (kyc_test_…)."
    }
}
404 Ressource inexistante pour ce compte et cet environnement (not_found).
{
    "error": {
        "code": "not_found",
        "message": "Ressource introuvable."
    }
}
409 État incompatible (invalid_state, verification_processing) ou clé d'idempotence déjà employée (idempotency_key_reused, idempotency_request_in_progress).
{
    "error": {
        "code": "invalid_state",
        "message": "Ce dossier ne peut plus être annulé (statut « approved »)."
    }
}
422 Données invalides (validation_failed, détail par champ dans fields), aucun parcours configuré (no_flow_configured) ou en-tête Idempotency-Key invalide.
{
    "error": {
        "code": "validation_failed",
        "message": "Les données envoyées sont invalides.",
        "fields": {
            "applicant.email": [
                "The applicant.email field must be a valid email address."
            ]
        }
    }
}
429 Trop de requêtes pour cette clé (rate_limited) : réessayez après Retry-After secondes.
{
    "error": {
        "code": "rate_limited",
        "message": "Trop de requêtes : réessayez plus tard."
    }
}