{
    "openapi": "3.1.2",
    "info": {
        "title": "API de vérification d'identité — v1",
        "version": "1.0.0",
        "description": "API REST de vérification d'identité. Le principe : vous créez un **dossier** pour votre utilisateur,\nvous lui transmettez son **lien privé**, il capture sa pièce d'identité, un selfie et les mouvements de\ntête demandés (puis, selon le parcours, un justificatif de domicile et l'origine de ses fonds), et vous\nrecevez la **décision par webhook**.\n\n## Authentification\n\nChaque requête porte l'en-tête `Authorization: Bearer <clé>`. Les clés se créent dans le tableau de\nbord et ne sont montrées qu'une fois :\n\n| Préfixe | Environnement | Usage |\n|---|---|---|\n| `kyc_live_…` | production | vrais utilisateurs |\n| `kyc_test_…` | bac à sable | intégration et tests (simulation des décisions) |\n\nUne clé ne voit que les données de son compte **et de son environnement** : toute autre ressource\nrépond `404`. Une clé révoquée répond `401`.\n\n## Cycle de vie d'un dossier\n\n| Statut | Signification |\n|---|---|\n| `created` | lien émis, l'utilisateur n'a pas commencé |\n| `in_progress` | capture en cours |\n| `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) |\n| `processing` | analyse en cours |\n| `approved` | identité vérifiée |\n| `declined` | refusé pour un motif robuste (pièce expirée confirmée par la MRZ, âge insuffisant, spécimen) |\n| `review` | à examiner par un analyste (doublon, incertitude, contrôle à confirmer) |\n| `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 |\n| `expired` | lien expiré avant la fin de la capture |\n| `cancelled` | annulé par vous (ou effacement de la personne) |\n\nUne incertitude de lecture ou une photo médiocre ne mène jamais à un refus, seulement à une revue ou\nà une reprise. La validation automatique n'est activée qu'après calibrage du moteur sur un banc\nindépendant : d'ici là, les dossiers conformes passent en `review` (raison `auto_approval_disabled`).\nVos réglages de décision (tableau de bord) fixent le délai, le refus et la validation automatiques et\nles seuils des mesures du moteur ; une personne politiquement exposée déclarée ou une origine des\nfonds à risque élevé passe toujours en revue (`pep_declared`, `source_of_funds_high_risk`).\n\n## Raisons\n\n`reasons` liste des codes stables en `snake_case`, par exemple : `document_expired`, `underage`,\n`specimen_or_fake_document` (refus) ; `duplicate_document`, `name_mismatch`, `date_of_birth_mismatch`,\n`face_mismatch`, `face_match_uncertain`, `liveness_unverified`, `mrz_unverified`, `expiry_unverified`,\n`address_mismatch`, `document_too_old`, `documents_missing`, `engine_unavailable`,\n`auto_approval_disabled`, `auto_decline_disabled`, `face_similarity_below_threshold` (et les autres\n`*_below_threshold`), `pep_declared`, `source_of_funds_high_risk` (revue) ; `capture_quality` avec `identity_document_quality`,\n`selfie_quality`, `address_document_quality` (reprise) ; `cancelled_by_client`,\n`link_expired`, `applicant_erased`.\n\n## Erreurs\n\nToutes les erreurs ont la même forme, sans donnée du dossier :\n\n```json\n{\"error\": {\"code\": \"validation_failed\", \"message\": \"Les données envoyées sont invalides.\", \"fields\": {\"applicant.email\": [\"…\"]}}}\n```\n\n| HTTP | Codes |\n|---|---|\n| 401 | `missing_api_key`, `invalid_api_key` |\n| 403 | `organization_inactive`, `sandbox_only` |\n| 404 | `not_found`, `document_unavailable` |\n| 409 | `invalid_state`, `idempotency_key_reused`, `idempotency_request_in_progress`, `verification_processing` |\n| 422 | `validation_failed` (détail dans `fields`), `no_flow_configured`, `invalid_idempotency_key` |\n| 429 | `rate_limited` (en-tête `Retry-After`) |\n\n## Idempotence\n\nLes requêtes `POST` acceptent l'en-tête `Idempotency-Key` (1 à 255 caractères ASCII). Rejouée avec la\nmême clé et le même corps pendant 24 h, la requête renvoie la réponse d'origine (en-tête\n`Idempotent-Replayed: true`) sans rien recréer. La même clé avec un autre corps répond `409`.\n\n## Limitation de débit\n\n120 requêtes par minute et par clé par défaut (en-têtes `X-RateLimit-Limit` et\n`X-RateLimit-Remaining`). Au-delà : `429` avec `Retry-After` (secondes).\n\n## Pagination\n\nLes listes renvoient `{\"data\": [...], \"has_more\": true, \"next_cursor\": \"…\"}` : passez `next_cursor`\ndans le paramètre `cursor` pour la page suivante (`limit` : 1 à 100, 25 par défaut).\n\n## Webhooks\n\nDéclarez vos adresses de réception dans le tableau de bord (HTTPS obligatoire en production ; les\nadresses privées ou internes sont refusées et les redirections ne sont jamais suivies). Chaque\nchangement de statut envoie un `POST` JSON :\n\n```json\n{\n  \"id\": \"01jb3c9w4q8t2n6m5r7k1d0f3e\",\n  \"type\": \"verification.approved\",\n  \"created_at\": \"2026-10-24T09:30:00Z\",\n  \"data\": {\n    \"verification_id\": \"01jb2y8q5z3x4c6v7b8n9m0k1j\",\n    \"applicant_id\": \"01jb2y7k4m3n2b1v0c9x8z7a6s\",\n    \"external_id\": \"user-48151623\",\n    \"status\": \"approved\",\n    \"reasons\": [],\n    \"environment\": \"live\"\n  }\n}\n```\n\nTypes : `verification.<statut>` (par exemple `verification.submitted`, `verification.review`,\n`verification.approved`) et `webhook.test` (bouton d'essai du tableau de bord). Répondez `2xx` en\nmoins de 10 secondes ; sinon l'envoi est retenté après 1 min, 5 min, 30 min, 2 h puis 6 h. Le même\névénement peut arriver deux fois : dédoublonnez sur `id`.\n\n**Vérifiez toujours la signature** de l'en-tête `Kyc-Signature: t=<horodatage>,v1=<signature>` :\n`v1` vaut HMAC-SHA256(secret du point d'arrivée, `<horodatage>.<corps brut>`), en hexadécimal.\nRefusez une signature invalide ou un horodatage de plus de 5 minutes (rejeu).\n\n```php\nfunction kycSignatureValid(?string $header, string $body, string $secret): bool\n{\n    if ($header === null || ! preg_match('/^t=(\\d+),/', $header, $t) || abs(time() - (int) $t[1]) > 300) {\n        return false;\n    }\n    $expected = hash_hmac('sha256', $t[1].'.'.$body, $secret);\n    preg_match_all('/(?:^|,)v1=([0-9a-f]{64})/', $header, $signatures);\n    foreach ($signatures[1] as $v1) {\n        if (hash_equals($expected, $v1)) {\n            return true;\n        }\n    }\n\n    return false;\n}\n// Corps brut : file_get_contents('php://input'). Signature invalide : répondre 401.\n```\n\n```js\nfunction kycSignatureValid(header, rawBody, secret) {\n  const t = /^t=(\\d+),/.exec(header || '');\n  if (!t || Math.abs(Date.now() / 1000 - Number(t[1])) > 300) return false;\n  const expected = Buffer.from(crypto.createHmac('sha256', secret).update(`${t[1]}.${rawBody}`).digest('hex'));\n  return [...(header.matchAll(/(?:^|,)v1=([0-9a-f]{64})/g))]\n    .some(([, v1]) => crypto.timingSafeEqual(expected, Buffer.from(v1)));\n}\n```"
    },
    "servers": [
        {
            "url": "https://getsanno.com/api/v1",
            "description": "API v1"
        }
    ],
    "tags": [
        {
            "name": "Dossiers",
            "description": "Création et suivi des dossiers de vérification."
        },
        {
            "name": "Personnes",
            "description": "Personnes vérifiées (retrouvées par votre identifiant `external_id`) et effacement RGPD."
        },
        {
            "name": "Bac à sable",
            "description": "Outils réservés aux clés de test (kyc_test_…) pour éprouver votre intégration."
        }
    ],
    "paths": {
        "/verifications": {
            "post": {
                "operationId": "v1.verifications.store",
                "description": "Crée (ou met à jour, par `external_id`) la personne à vérifier, puis ouvre un dossier dans\nl'environnement de la clé. Transmettez `url` à l'utilisateur final : c'est son lien privé vers le\nparcours de capture (pièce, selfie et vivacité, domicile…). Le lien n'est montré qu'ici : il ne peut\nêtre que renouvelé (`POST /verifications/{id}/link`).\n\nRejouer la requête avec le même en-tête `Idempotency-Key` et le même corps renvoie la même réponse\npendant 24 h, sans créer de second dossier.",
                "summary": "Créer un dossier",
                "tags": [
                    "Dossiers"
                ],
                "parameters": [
                    {
                        "name": "Idempotency-Key",
                        "in": "header",
                        "description": "Clé unique choisie par vous (1 à 255 caractères) pour rejouer la requête sans doublon.",
                        "schema": {
                            "type": "string"
                        },
                        "example": "7f1c9a52-3c1e-4c8e-9b3a-2f4d5e6a7b8c"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/StoreVerificationRequest"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "`VerificationResource`",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "allOf": [
                                        {
                                            "$ref": "#/components/schemas/VerificationResource"
                                        },
                                        {
                                            "type": "object",
                                            "required": [
                                                "checks",
                                                "documents"
                                            ]
                                        }
                                    ]
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthenticated"
                    },
                    "409": {
                        "$ref": "#/components/responses/Conflict"
                    },
                    "422": {
                        "$ref": "#/components/responses/ValidationFailed"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "get": {
                "operationId": "v1.verifications.index",
                "description": "Du plus récent au plus ancien, pagination par curseur (`next_cursor`). Les éléments ne contiennent\nni données extraites ni contrôles : demandez le détail par `GET /verifications/{id}`.",
                "summary": "Lister les dossiers",
                "tags": [
                    "Dossiers"
                ],
                "parameters": [
                    {
                        "name": "status",
                        "in": "query",
                        "schema": {
                            "type": [
                                "string",
                                "null"
                            ],
                            "maxLength": 300
                        }
                    },
                    {
                        "name": "external_id",
                        "in": "query",
                        "schema": {
                            "type": [
                                "string",
                                "null"
                            ],
                            "maxLength": 255
                        }
                    },
                    {
                        "name": "created_after",
                        "in": "query",
                        "schema": {
                            "type": [
                                "string",
                                "null"
                            ],
                            "format": "date-time"
                        }
                    },
                    {
                        "name": "limit",
                        "in": "query",
                        "schema": {
                            "type": [
                                "integer",
                                "null"
                            ],
                            "minimum": 1,
                            "maximum": 100
                        }
                    },
                    {
                        "name": "cursor",
                        "in": "query",
                        "schema": {
                            "type": [
                                "string",
                                "null"
                            ],
                            "maxLength": 1000
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/VerificationSummaryResource"
                                            }
                                        },
                                        "has_more": {
                                            "type": "boolean"
                                        },
                                        "next_cursor": {
                                            "type": [
                                                "string",
                                                "null"
                                            ]
                                        }
                                    },
                                    "required": [
                                        "data",
                                        "has_more",
                                        "next_cursor"
                                    ]
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthenticated"
                    },
                    "422": {
                        "$ref": "#/components/responses/ValidationFailed"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/verifications/{verification}": {
            "get": {
                "operationId": "v1.verifications.show",
                "description": "Statut, décision, raisons, risque, résumé des contrôles, données extraites normalisées,\nmétadonnées des pièces (sans le fichier) et horodatages.",
                "summary": "Consulter un dossier",
                "tags": [
                    "Dossiers"
                ],
                "parameters": [
                    {
                        "name": "verification",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "`VerificationResource`",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/VerificationResource"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthenticated"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/verifications/{verification}/link": {
            "post": {
                "operationId": "v1.verifications.link",
                "description": "Émet un nouveau lien pour l'utilisateur final (lien perdu ou proche de son échéance) ; l'ancien cesse\naussitôt de fonctionner. Possible tant que le dossier attend des captures (`created`, `in_progress`,\n`resubmission_requested`) et avant `expires_at` : un dossier dont le lien a expiré passe en `expired`\ndans les 5 minutes, il faut alors en créer un nouveau.",
                "summary": "Renouveler le lien",
                "tags": [
                    "Dossiers"
                ],
                "parameters": [
                    {
                        "name": "verification",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "Idempotency-Key",
                        "in": "header",
                        "description": "Clé unique pour rejouer la requête sans émettre un second lien.",
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "requestBody": {
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "expires_in": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "description": "Durée de validité du nouveau lien, en minutes.",
                                        "examples": [
                                            1440
                                        ],
                                        "minimum": 15,
                                        "maximum": 43200
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "`VerificationResource`",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "allOf": [
                                        {
                                            "$ref": "#/components/schemas/VerificationResource"
                                        },
                                        {
                                            "type": "object",
                                            "required": [
                                                "checks",
                                                "documents"
                                            ]
                                        }
                                    ]
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthenticated"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "409": {
                        "$ref": "#/components/responses/Conflict"
                    },
                    "422": {
                        "$ref": "#/components/responses/ValidationFailed"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/verifications/{verification}/cancel": {
            "post": {
                "operationId": "v1.verifications.cancel",
                "description": "Possible tant qu'aucune décision n'est prise (`created`, `in_progress`, `submitted`,\n`resubmission_requested`). Le lien de l'utilisateur cesse de fonctionner.",
                "summary": "Annuler un dossier",
                "tags": [
                    "Dossiers"
                ],
                "parameters": [
                    {
                        "name": "verification",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "Idempotency-Key",
                        "in": "header",
                        "description": "Clé unique pour rejouer la requête.",
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "`VerificationResource`",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "allOf": [
                                        {
                                            "$ref": "#/components/schemas/VerificationResource"
                                        },
                                        {
                                            "type": "object",
                                            "required": [
                                                "checks",
                                                "documents"
                                            ]
                                        }
                                    ]
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthenticated"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "409": {
                        "$ref": "#/components/responses/Conflict"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/applicants": {
            "get": {
                "operationId": "v1.applicants.index",
                "description": "Filtre facultatif `external_id` (votre identifiant). Pagination par curseur.",
                "summary": "Lister les personnes",
                "tags": [
                    "Personnes"
                ],
                "parameters": [
                    {
                        "name": "external_id",
                        "in": "query",
                        "schema": {
                            "type": [
                                "string",
                                "null"
                            ],
                            "maxLength": 255
                        }
                    },
                    {
                        "name": "limit",
                        "in": "query",
                        "schema": {
                            "type": [
                                "integer",
                                "null"
                            ],
                            "minimum": 1,
                            "maximum": 100
                        }
                    },
                    {
                        "name": "cursor",
                        "in": "query",
                        "schema": {
                            "type": [
                                "string",
                                "null"
                            ],
                            "maxLength": 1000
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/ApplicantResource"
                                            }
                                        },
                                        "has_more": {
                                            "type": "boolean"
                                        },
                                        "next_cursor": {
                                            "type": [
                                                "string",
                                                "null"
                                            ]
                                        }
                                    },
                                    "required": [
                                        "data",
                                        "has_more",
                                        "next_cursor"
                                    ]
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthenticated"
                    },
                    "422": {
                        "$ref": "#/components/responses/ValidationFailed"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/applicants/{applicant}": {
            "get": {
                "operationId": "v1.applicants.show",
                "description": "Données déclarées et derniers dossiers (50 au plus).",
                "summary": "Consulter une personne",
                "tags": [
                    "Personnes"
                ],
                "parameters": [
                    {
                        "name": "applicant",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "`ApplicantResource`",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "allOf": [
                                        {
                                            "$ref": "#/components/schemas/ApplicantResource"
                                        },
                                        {
                                            "type": "object",
                                            "required": [
                                                "verifications"
                                            ]
                                        }
                                    ]
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthenticated"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "operationId": "v1.applicants.destroy",
                "description": "Efface les pièces, les rapports d'analyse, les données lues et déclarées de la personne et de tous\nses dossiers. Les dossiers qui attendent encore l'utilisateur (`created`, `in_progress`, `submitted`,\n`resubmission_requested`) sont annulés ; un dossier en revue ou tranché garde son statut, sans plus\naucune donnée. Restent la coquille des dossiers (statuts, dates) et la trace d'audit, nécessaires pour\nprouver la conformité. Irréversible. Refusé (409) pendant l'analyse d'un dossier : réessayez quelques\nminutes plus tard.",
                "summary": "Effacer une personne (RGPD)",
                "tags": [
                    "Personnes"
                ],
                "parameters": [
                    {
                        "name": "applicant",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "id": {
                                            "type": "string"
                                        },
                                        "object": {
                                            "type": "string",
                                            "const": "applicant"
                                        },
                                        "deleted": {
                                            "type": "boolean"
                                        }
                                    },
                                    "required": [
                                        "id",
                                        "object",
                                        "deleted"
                                    ]
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthenticated"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "409": {
                        "$ref": "#/components/responses/Conflict"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/verifications/{verification}/simulate": {
            "post": {
                "operationId": "v1.verifications.simulate",
                "description": "Bac à sable uniquement : mène le dossier jusqu'à l'issue demandée par les transitions normales\n(`in_progress`, `submitted`, `processing`, puis l'issue), avec les événements et webhooks\ncorrespondants, sans capture ni analyse. Le dossier reçoit des données extraites et des contrôles\nfictifs. Une clé live reçoit 403.",
                "summary": "Simuler une décision",
                "tags": [
                    "Bac à sable"
                ],
                "parameters": [
                    {
                        "name": "verification",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "Idempotency-Key",
                        "in": "header",
                        "description": "Clé unique pour rejouer la requête.",
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "outcome": {
                                        "type": "string",
                                        "description": "Issue à simuler.",
                                        "enum": [
                                            "approved",
                                            "declined",
                                            "review",
                                            "resubmission_requested"
                                        ],
                                        "examples": [
                                            "approved"
                                        ]
                                    },
                                    "reasons": {
                                        "type": [
                                            "array",
                                            "null"
                                        ],
                                        "description": "Raisons à renvoyer (codes en snake_case) ; une valeur par défaut selon l'issue sinon.",
                                        "examples": [
                                            [
                                                "document_expired"
                                            ]
                                        ],
                                        "items": {
                                            "type": "string",
                                            "pattern": "^[a-z][a-z0-9_]{0,63}$"
                                        },
                                        "maxItems": 10
                                    }
                                },
                                "required": [
                                    "outcome"
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "`VerificationResource`",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "allOf": [
                                        {
                                            "$ref": "#/components/schemas/VerificationResource"
                                        },
                                        {
                                            "type": "object",
                                            "required": [
                                                "checks",
                                                "documents"
                                            ]
                                        }
                                    ]
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthenticated"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "409": {
                        "$ref": "#/components/responses/Conflict"
                    },
                    "422": {
                        "$ref": "#/components/responses/ValidationFailed"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/verifications/{verification}/documents/{document}": {
            "get": {
                "operationId": "v1.verifications.documents.show",
                "description": "Renvoie le fichier déchiffré (image ou PDF) d'une pièce du dossier. Chaque téléchargement est\ninscrit au journal d'audit. La réponse ne doit pas être mise en cache.",
                "summary": "Télécharger une pièce",
                "tags": [
                    "Dossiers"
                ],
                "parameters": [
                    {
                        "name": "verification",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "document",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Contenu du fichier (Content-Type de la pièce).",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object"
                                }
                            },
                            "application/octet-stream": {
                                "schema": {
                                    "type": "string",
                                    "format": "binary"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthenticated"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "ApplicantResource": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "string",
                        "examples": [
                            "01jb2y7k4m3n2b1v0c9x8z7a6s"
                        ]
                    },
                    "object": {
                        "type": "string",
                        "const": "applicant"
                    },
                    "environment": {
                        "type": "string",
                        "enum": [
                            "live",
                            "sandbox"
                        ]
                    },
                    "external_id": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "examples": [
                            "user-48151623"
                        ]
                    },
                    "email": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "first_name": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "last_name": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "date_of_birth": {
                        "__PHP_Incomplete_Class_Name": "stdClass"
                    },
                    "nationality": {
                        "__PHP_Incomplete_Class_Name": "stdClass"
                    },
                    "country": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "address": {
                        "type": "object",
                        "properties": {
                            "line1": {
                                "type": "string"
                            },
                            "line2": {
                                "type": "string"
                            },
                            "city": {
                                "type": "string"
                            },
                            "postal_code": {
                                "type": "string"
                            },
                            "state": {
                                "type": "string"
                            }
                        },
                        "required": [
                            "line1",
                            "line2",
                            "city",
                            "postal_code",
                            "state"
                        ]
                    },
                    "risk_level": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "enum": [
                            "low",
                            "medium",
                            "high",
                            null
                        ]
                    },
                    "verifications": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "id": {
                                    "type": "string"
                                },
                                "status": {
                                    "type": "string"
                                },
                                "created_at": {
                                    "type": [
                                        "string",
                                        "null"
                                    ]
                                }
                            },
                            "required": [
                                "id",
                                "status",
                                "created_at"
                            ]
                        }
                    },
                    "created_at": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "updated_at": {
                        "type": [
                            "string",
                            "null"
                        ]
                    }
                },
                "required": [
                    "id",
                    "object",
                    "environment",
                    "external_id",
                    "email",
                    "first_name",
                    "last_name",
                    "date_of_birth",
                    "nationality",
                    "country",
                    "address",
                    "risk_level",
                    "created_at",
                    "updated_at"
                ],
                "title": "ApplicantResource"
            },
            "StoreVerificationRequest": {
                "type": "object",
                "description": "Création d'un dossier de vérification (POST /api/v1/verifications).",
                "properties": {
                    "applicant": {
                        "type": "object",
                        "description": "Personne à vérifier, retrouvée par `external_id` (créée si elle n'existe pas, mise à jour sinon :\nseuls les champs envoyés sont modifiés, `null` efface).",
                        "properties": {
                            "external_id": {
                                "type": "string",
                                "description": "Identifiant de l'utilisateur dans votre système.",
                                "examples": [
                                    "user-48151623"
                                ],
                                "maxLength": 255
                            },
                            "email": {
                                "type": [
                                    "string",
                                    "null"
                                ],
                                "format": "email",
                                "examples": [
                                    "jeanne.martin@example.com"
                                ],
                                "maxLength": 255
                            },
                            "first_name": {
                                "type": [
                                    "string",
                                    "null"
                                ],
                                "examples": [
                                    "Jeanne"
                                ],
                                "maxLength": 100
                            },
                            "last_name": {
                                "type": [
                                    "string",
                                    "null"
                                ],
                                "examples": [
                                    "Martin"
                                ],
                                "maxLength": 100
                            },
                            "date_of_birth": {
                                "type": [
                                    "string",
                                    "null"
                                ],
                                "format": "date",
                                "description": "Date de naissance déclarée (AAAA-MM-JJ), comparée à celle lue sur la pièce.",
                                "examples": [
                                    "1990-04-12"
                                ]
                            },
                            "nationality": {
                                "type": [
                                    "string",
                                    "null"
                                ],
                                "description": "Nationalité, code ISO 3166-1 alpha-3.",
                                "examples": [
                                    "FRA"
                                ],
                                "minLength": 3,
                                "maxLength": 3
                            },
                            "country": {
                                "type": [
                                    "string",
                                    "null"
                                ],
                                "description": "Pays de résidence, code ISO 3166-1 alpha-3.",
                                "examples": [
                                    "FRA"
                                ],
                                "minLength": 3,
                                "maxLength": 3
                            },
                            "address": {
                                "type": "object",
                                "description": "Adresse déclarée, comparée au justificatif de domicile.",
                                "properties": {
                                    "line1": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "examples": [
                                            "12 rue de la Paix"
                                        ],
                                        "maxLength": 255
                                    },
                                    "line2": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "examples": [
                                            "Bâtiment B"
                                        ],
                                        "maxLength": 255
                                    },
                                    "city": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "examples": [
                                            "Paris"
                                        ],
                                        "maxLength": 100
                                    },
                                    "postal_code": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "examples": [
                                            "75002"
                                        ],
                                        "maxLength": 20
                                    },
                                    "state": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "examples": [
                                            "Île-de-France"
                                        ],
                                        "maxLength": 100
                                    }
                                }
                            }
                        },
                        "required": [
                            "external_id"
                        ]
                    },
                    "flow": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Identifiant (slug) du parcours à suivre ; le parcours par défaut du compte sinon.",
                        "examples": [
                            "standard"
                        ],
                        "maxLength": 100
                    },
                    "redirect_url": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "uri",
                        "description": "Adresse où renvoyer l'utilisateur à la fin du parcours (HTTPS obligatoire en production).",
                        "examples": [
                            "https://www.example.com/kyc/retour"
                        ],
                        "maxLength": 2048
                    },
                    "locale": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Langue du parcours (code ISO 639-1 d'une langue officielle de l'UE).",
                        "enum": [
                            "bg",
                            "cs",
                            "da",
                            "de",
                            "el",
                            "en",
                            "es",
                            "et",
                            "fi",
                            "fr",
                            "ga",
                            "hr",
                            "hu",
                            "it",
                            "lt",
                            "lv",
                            "mt",
                            "nl",
                            "pl",
                            "pt",
                            "ro",
                            "sk",
                            "sl",
                            "sv",
                            null
                        ],
                        "examples": [
                            "fr"
                        ]
                    },
                    "metadata": {
                        "type": [
                            "array",
                            "null"
                        ],
                        "description": "Vos données libres, rendues telles quelles : objet plat de 20 clés au plus\n(clé ≤ 40 caractères, valeur scalaire ≤ 500 caractères).",
                        "examples": [
                            {
                                "order_id": "A-1042"
                            }
                        ],
                        "items": {
                            "type": "string"
                        },
                        "maxItems": 20
                    },
                    "expires_in": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Durée de validité du lien, en minutes (15 à 43200 ; 1440 par défaut).",
                        "examples": [
                            1440
                        ],
                        "minimum": 15,
                        "maximum": 43200
                    }
                },
                "required": [
                    "applicant"
                ],
                "title": "StoreVerificationRequest"
            },
            "VerificationResource": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "string",
                        "examples": [
                            "01jb2y8q5z3x4c6v7b8n9m0k1j"
                        ]
                    },
                    "object": {
                        "type": "string",
                        "const": "verification"
                    },
                    "status": {
                        "type": "string",
                        "enum": [
                            "created",
                            "in_progress",
                            "submitted",
                            "processing",
                            "approved",
                            "declined",
                            "review",
                            "resubmission_requested",
                            "expired",
                            "cancelled"
                        ]
                    },
                    "environment": {
                        "type": "string",
                        "enum": [
                            "live",
                            "sandbox"
                        ]
                    },
                    "url": {
                        "type": "string",
                        "description": "Lien à transmettre à l'utilisateur final (présent seulement à la création et au renouvellement).",
                        "examples": [
                            "https://kyc.example.com/v/Xr4b0Yq0m2c3VJ5q9yQwXr4b0Yq0m2c3VJ5q9yQwXr4b0Yq0"
                        ]
                    },
                    "flow": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "examples": [
                            "standard"
                        ]
                    },
                    "applicant_id": {
                        "type": "string",
                        "examples": [
                            "01jb2y7k4m3n2b1v0c9x8z7a6s"
                        ]
                    },
                    "external_id": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "examples": [
                            "user-48151623"
                        ]
                    },
                    "decision": {
                        "type": "object",
                        "properties": {
                            "status": {
                                "type": [
                                    "string",
                                    "null"
                                ],
                                "enum": [
                                    "approved",
                                    "declined",
                                    "review",
                                    "resubmission_requested",
                                    null
                                ]
                            },
                            "source": {
                                "type": [
                                    "string",
                                    "null"
                                ],
                                "enum": [
                                    "auto",
                                    "manual",
                                    null
                                ]
                            },
                            "decided_at": {
                                "type": [
                                    "string",
                                    "null"
                                ],
                                "examples": [
                                    "2026-10-24T09:42:00Z"
                                ]
                            }
                        },
                        "required": [
                            "status",
                            "source",
                            "decided_at"
                        ]
                    },
                    "reasons": {
                        "type": "array",
                        "description": "Codes stables expliquant la décision (ex. document_expired, capture_quality, duplicate_document).",
                        "items": {
                            "type": "string"
                        }
                    },
                    "risk": {
                        "type": "object",
                        "properties": {
                            "score": {
                                "type": [
                                    "integer",
                                    "null"
                                ],
                                "description": "Score de 0 (faible) à 100 (élevé)."
                            },
                            "level": {
                                "type": [
                                    "string",
                                    "null"
                                ],
                                "enum": [
                                    "low",
                                    "medium",
                                    "high",
                                    null
                                ]
                            }
                        },
                        "required": [
                            "score",
                            "level"
                        ]
                    },
                    "checks": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "code": {
                                    "type": "string",
                                    "examples": [
                                        "identity.face_match"
                                    ]
                                },
                                "status": {
                                    "type": "string",
                                    "enum": [
                                        "passed",
                                        "failed",
                                        "review",
                                        "unavailable"
                                    ]
                                },
                                "reason": {
                                    "type": [
                                        "string",
                                        "null"
                                    ]
                                },
                                "source": {
                                    "type": "string"
                                }
                            },
                            "required": [
                                "code",
                                "status",
                                "reason",
                                "source"
                            ]
                        }
                    },
                    "extracted": {
                        "type": "object",
                        "description": "Données lues sur les pièces : identity (document_type, issuing_country, document_number, first_name,\nlast_name, date_of_birth, issue_date, expiry_date, nationality, sex, mrz_valid), face (similarity\ncosinus de 0 à 1 et threshold : même personne au-delà du seuil ; liveness), address (full_name,\naddress_line1, postal_code, city, country, issuer, document_date), funds (liste par justificatif).\nToute clé peut manquer.",
                        "additionalProperties": {
                            "type": "object",
                            "additionalProperties": {
                                "__PHP_Incomplete_Class_Name": "stdClass"
                            }
                        }
                    },
                    "documents": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "id": {
                                    "type": "integer"
                                },
                                "kind": {
                                    "type": "string",
                                    "enum": [
                                        "identity_front",
                                        "identity_back",
                                        "selfie",
                                        "liveness_frame",
                                        "address",
                                        "funds"
                                    ]
                                },
                                "document_type": {
                                    "type": [
                                        "string",
                                        "null"
                                    ]
                                },
                                "country": {
                                    "type": [
                                        "string",
                                        "null"
                                    ]
                                },
                                "mime": {
                                    "type": "string"
                                },
                                "size": {
                                    "type": "integer"
                                },
                                "width": {
                                    "type": [
                                        "integer",
                                        "null"
                                    ]
                                },
                                "height": {
                                    "type": [
                                        "integer",
                                        "null"
                                    ]
                                },
                                "source": {
                                    "type": "string"
                                },
                                "attempt": {
                                    "type": "integer"
                                },
                                "created_at": {
                                    "type": [
                                        "string",
                                        "null"
                                    ]
                                },
                                "download_url": {
                                    "type": "string",
                                    "description": "Téléchargement déchiffré, avec la même clé API (consultation tracée dans l'audit) ; adresse\npublique configurée, jamais l'hôte de la requête."
                                }
                            },
                            "required": [
                                "id",
                                "kind",
                                "document_type",
                                "country",
                                "mime",
                                "size",
                                "width",
                                "height",
                                "source",
                                "attempt",
                                "created_at",
                                "download_url"
                            ]
                        }
                    },
                    "metadata": {
                        "type": "object",
                        "description": "Données libres envoyées à la création.",
                        "additionalProperties": {
                            "type": [
                                "string",
                                "number",
                                "boolean",
                                "null"
                            ]
                        }
                    },
                    "redirect_url": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "locale": {
                        "type": "string",
                        "examples": [
                            "fr"
                        ]
                    },
                    "expires_at": {
                        "type": "string",
                        "examples": [
                            "2026-10-25T09:30:00Z"
                        ]
                    },
                    "created_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "examples": [
                            "2026-10-24T09:30:00Z"
                        ]
                    },
                    "started_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "examples": [
                            "2026-10-24T09:31:00Z"
                        ]
                    },
                    "submitted_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "examples": [
                            "2026-10-24T09:38:00Z"
                        ]
                    },
                    "due_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Échéance de la décision : envoi + délai réglé dans votre tableau de bord (0 à 72 h). Le\nrésultat part par webhook dès qu'il est prêt, au plus tard à cette date.",
                        "examples": [
                            "2026-10-25T09:38:00Z"
                        ]
                    },
                    "completed_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "examples": [
                            "2026-10-24T09:42:00Z"
                        ]
                    },
                    "updated_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "examples": [
                            "2026-10-24T09:42:00Z"
                        ]
                    }
                },
                "required": [
                    "id",
                    "object",
                    "status",
                    "environment",
                    "flow",
                    "applicant_id",
                    "external_id",
                    "decision",
                    "reasons",
                    "risk",
                    "extracted",
                    "metadata",
                    "redirect_url",
                    "locale",
                    "expires_at",
                    "created_at",
                    "started_at",
                    "submitted_at",
                    "due_at",
                    "completed_at",
                    "updated_at"
                ],
                "title": "VerificationResource"
            },
            "VerificationSummaryResource": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "string"
                    },
                    "object": {
                        "type": "string",
                        "const": "verification"
                    },
                    "status": {
                        "type": "string"
                    },
                    "environment": {
                        "type": "string"
                    },
                    "flow": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "applicant_id": {
                        "type": "string"
                    },
                    "external_id": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "reasons": {
                        "type": "array",
                        "items": {
                            "__PHP_Incomplete_Class_Name": "stdClass"
                        }
                    },
                    "risk": {
                        "type": "object",
                        "properties": {
                            "score": {
                                "type": [
                                    "integer",
                                    "null"
                                ]
                            },
                            "level": {
                                "type": [
                                    "string",
                                    "null"
                                ]
                            }
                        },
                        "required": [
                            "score",
                            "level"
                        ]
                    },
                    "expires_at": {
                        "type": "string"
                    },
                    "created_at": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "completed_at": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "updated_at": {
                        "type": [
                            "string",
                            "null"
                        ]
                    }
                },
                "required": [
                    "id",
                    "object",
                    "status",
                    "environment",
                    "flow",
                    "applicant_id",
                    "external_id",
                    "reasons",
                    "risk",
                    "expires_at",
                    "created_at",
                    "completed_at",
                    "updated_at"
                ],
                "title": "VerificationSummaryResource"
            },
            "Error": {
                "type": "object",
                "description": "Erreur de l'API, toujours sous cette forme et sans donnée du dossier.",
                "required": [
                    "error"
                ],
                "properties": {
                    "error": {
                        "type": "object",
                        "required": [
                            "code",
                            "message"
                        ],
                        "properties": {
                            "code": {
                                "type": "string",
                                "description": "Code stable, à utiliser dans votre intégration.",
                                "examples": [
                                    "validation_failed"
                                ]
                            },
                            "message": {
                                "type": "string",
                                "description": "Explication lisible.",
                                "examples": [
                                    "Les données envoyées sont invalides."
                                ]
                            },
                            "fields": {
                                "type": "object",
                                "description": "Erreurs par champ (422 validation_failed seulement).",
                                "additionalProperties": {
                                    "type": "array",
                                    "items": {
                                        "type": "string"
                                    }
                                }
                            }
                        }
                    }
                }
            }
        },
        "responses": {
            "Unauthenticated": {
                "description": "Clé API absente (missing_api_key), invalide ou révoquée (invalid_api_key).",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/Error"
                        },
                        "example": {
                            "error": {
                                "code": "invalid_api_key",
                                "message": "Clé API invalide ou révoquée."
                            }
                        }
                    }
                }
            },
            "Forbidden": {
                "description": "Action interdite pour cette clé : compte inactif (organization_inactive), simulation hors bac à sable (sandbox_only).",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/Error"
                        },
                        "example": {
                            "error": {
                                "code": "sandbox_only",
                                "message": "La simulation n'est disponible qu'avec une clé de bac à sable (kyc_test_…)."
                            }
                        }
                    }
                }
            },
            "NotFound": {
                "description": "Ressource inexistante pour ce compte et cet environnement (not_found).",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/Error"
                        },
                        "example": {
                            "error": {
                                "code": "not_found",
                                "message": "Ressource introuvable."
                            }
                        }
                    }
                }
            },
            "Conflict": {
                "description": "État incompatible (invalid_state, verification_processing) ou clé d'idempotence déjà employée (idempotency_key_reused, idempotency_request_in_progress).",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/Error"
                        },
                        "example": {
                            "error": {
                                "code": "invalid_state",
                                "message": "Ce dossier ne peut plus être annulé (statut « approved »)."
                            }
                        }
                    }
                }
            },
            "ValidationFailed": {
                "description": "Données invalides (validation_failed, détail par champ dans fields), aucun parcours configuré (no_flow_configured) ou en-tête Idempotency-Key invalide.",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/Error"
                        },
                        "example": {
                            "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."
                                    ]
                                }
                            }
                        }
                    }
                }
            },
            "RateLimited": {
                "description": "Trop de requêtes pour cette clé (rate_limited) : réessayez après Retry-After secondes.",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/Error"
                        },
                        "example": {
                            "error": {
                                "code": "rate_limited",
                                "message": "Trop de requêtes : réessayez plus tard."
                            }
                        }
                    }
                },
                "headers": {
                    "Retry-After": {
                        "description": "Secondes avant de réessayer.",
                        "schema": {
                            "type": "integer"
                        }
                    }
                }
            }
        },
        "securitySchemes": {
            "cleApi": {
                "type": "http",
                "scheme": "bearer",
                "bearerFormat": "kyc_live_… ou kyc_test_…",
                "description": "Clé API du compte, en-tête « Authorization: Bearer <clé> » : kyc_live_… pour la production, kyc_test_… pour le bac à sable."
            }
        }
    },
    "security": [
        {
            "cleApi": []
        }
    ]
}