{
  "openapi": "3.1.0",
  "info": {
    "title": "Kyckyde API — préouverture",
    "version": "0.5.0",
    "description": "Lecture, préparation et sessions d’identité détenues par Kyckyde. Numid est un client API. Capture hébergée, état durable, médias chiffrés et revue privée intégrés. Les modèles biométriques évalués sont requis avant toute capture ; aucune approbation automatique. Paiement et émission commerciale de clés en préparation."
  },
  "servers": [
    {
      "url": "/",
      "description": "Même origine que le site"
    }
  ],
  "paths": {
    "/api/sandbox": {
      "post": {
        "operationId": "runSandbox",
        "summary": "Retourne un exemple fictif prédéfini sans document ni débit",
        "x-availability": "available",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SandboxInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Exemple fictif ; identity_assurance vaut not_performed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SandboxResult"
                }
              }
            }
          },
          "400": {
            "description": "JSON invalide, opération inconnue ou champ supplémentaire"
          },
          "413": {
            "description": "Corps supérieur à 512 octets"
          },
          "415": {
            "description": "Seul application/json est accepté, aucun upload"
          }
        }
      }
    },
    "/v1/read": {
      "post": {
        "operationId": "readDocument",
        "summary": "Lecture synchrone de documents — intégration serveur",
        "description": "Disponible après configuration du moteur privé et de la clé d’intégration. Sans configuration, répond 503 sans lire le corps ; une clé absente ou invalide répond 401 avant lecture du corps. Corps multipart limité à 4 Mio, enveloppe comprise. JPEG, PNG, WebP et PDF sont contrôlés par signature ; le MIME peut être détecté s’il est absent ou application/octet-stream, mais un MIME déclaré contradictoire est rejeté. Les profils sont choisis par type et format ; aucun profil, instructions, modèle ou URL arbitraire n’est accepté. Un back facultatif n’est autorisé que pour un profil recto-verso. La réponse synchrone est bornée à 170 secondes et n’est pas facturée. Pas de file de tâches, webhook ou idempotence commerciale.",
        "x-availability": "integration-requires-activation",
        "x-max-request-bytes": 4194304,
        "x-timeout-ms": 170000,
        "security": [
          {
            "ClientApiKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/ReadInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Résultats par face envoyée, sans assurance d’identité ni facturation",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReadResult"
                }
              }
            }
          },
          "400": {
            "description": "Multipart invalide, paramètre inconnu ou dupliqué, type/format non pris en charge, front manquant ou back interdit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReadError"
                }
              }
            }
          },
          "401": {
            "description": "Clé d’intégration absente ou invalide ; corps non lu",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReadError"
                }
              }
            }
          },
          "413": {
            "description": "Corps multipart complet supérieur à 4194304 octets",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReadError"
                }
              }
            }
          },
          "415": {
            "description": "Content-Type multipart requis ou signature de fichier/MIME non accepté",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReadError"
                }
              }
            }
          },
          "422": {
            "description": "La préparation ou la lecture du document n’a pas abouti",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReadError"
                }
              }
            }
          },
          "500": {
            "description": "Erreur interne de lecture, sans diagnostic personnel",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReadError"
                }
              }
            }
          },
          "502": {
            "description": "Moteur privé injoignable ou réponse invalide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReadError"
                }
              }
            }
          },
          "503": {
            "description": "Intégration non configurée ou moteur privé indisponible",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReadError"
                }
              }
            }
          },
          "504": {
            "description": "Délai total de traitement dépassé",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReadError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/document-profiles": {
      "get": {
        "operationId": "listDocumentProfiles",
        "summary": "Catalogue des profils et champs par type, format et face",
        "description": "Décrit le périmètre des profils intégrés, sans garantie de précision ni validation commerciale pour chaque scan. Les identifiants de champs du moteur sont conservés. Le catalogue ne contient ni instructions internes, ni données personnelles.",
        "x-availability": "available",
        "responses": {
          "200": {
            "description": "Profils déclarés et limites de la lecture d’intégration",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DocumentProfiles"
                }
              }
            }
          }
        }
      }
    },
    "/v1/verify": {
      "post": {
        "operationId": "verifyIdentity",
        "summary": "Comparer le portrait et la vidéo du défi",
        "description": "Intégration serveur après activation explicite, clé de vérification dédiée et worker privé doté de SQLite persistant et de modèles évalués. Sans configuration, 503 avant lecture du corps. Le jeton signé reste dans le backend marchand. Tous les résultats portent automaticApproval:false ; authenticité documentaire unknown. Aucun débit commercial. Le transfert QR et l’historique privés ne sont pas fournis par ces endpoints stateless. Cache-Control:no-store.",
        "security": [
          {
            "VerificationApiKey": []
          }
        ],
        "x-availability": "integration-requires-activation",
        "x-max-request-bytes": 3469312,
        "x-timeout-ms": 20000,
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/IdentityVerifyInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Résultat lié, avec preuves séparées et aucune approbation automatique",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentityVerifyResponse"
                }
              }
            }
          },
          "400": {
            "description": "Formulaire ou champs invalides",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentityError"
                }
              }
            }
          },
          "401": {
            "description": "Clé ou jeton invalide, corps non traité sans clé valide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentityError"
                }
              }
            }
          },
          "409": {
            "description": "Portrait substitué ou défi déjà consommé avec une autre tentative",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentityError"
                }
              }
            }
          },
          "410": {
            "description": "Défi expiré",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentityError"
                }
              }
            }
          },
          "413": {
            "description": "Corps ou média trop volumineux",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentityError"
                }
              }
            }
          },
          "415": {
            "description": "Type ou signature de média invalide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentityError"
                }
              }
            }
          },
          "422": {
            "description": "Média impossible à analyser",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentityError"
                }
              }
            }
          },
          "500": {
            "description": "Erreur interne sans diagnostic personnel",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentityError"
                }
              }
            }
          },
          "502": {
            "description": "Moteur inaccessible ou résultat incohérent/non lié",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentityError"
                }
              }
            }
          },
          "503": {
            "description": "Activation, registre persistant ou modèles évalués manquants",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentityError"
                }
              }
            }
          },
          "504": {
            "description": "Délai total de vingt secondes dépassé ou requête interrompue",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentityError"
                }
              }
            }
          }
        }
      }
    },
    "/api/merchant-demo/summary": {
      "get": {
        "operationId": "merchantDemoSummary",
        "summary": "Résumé public fictif du compte de démonstration",
        "x-availability": "demo",
        "security": [],
        "description": "Données fictives fixes. Aucun compte, clé, document ou paiement. Réponse Cache-Control: no-store. Paramètre period unique uniquement ; tout paramètre supplémentaire est rejeté.",
        "parameters": [
          {
            "name": "period",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": ["7d", "30d", "90d"],
              "default": "30d"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Résumé fictif, mode demo et environment test",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MerchantSummary"
                }
              }
            }
          },
          "400": {
            "description": "Période invalide, champ supplémentaire ou dupliqué, requête trop longue"
          }
        }
      }
    },
    "/v1/account/summary": {
      "get": {
        "operationId": "merchantAccountSummary",
        "summary": "Compte marchand — activation commerciale requise",
        "x-availability": "closed",
        "description": "Répond 503 sans lire le corps. Comptes authentifiés, portefeuille durable et paiements ne sont pas activés. Le contrat commercial sera publié avant activation.",
        "responses": {
          "503": {
            "description": "Comptes et paiements marchands non ouverts",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MerchantUnavailable"
                }
              }
            }
          }
        }
      }
    },
    "/v1/billing/topups": {
      "post": {
        "operationId": "merchantTopup",
        "summary": "Recharge de crédit — paiement non ouvert",
        "x-availability": "closed",
        "description": "Répond 503 sans lire le corps. Comptes authentifiés, portefeuille durable et paiements ne sont pas activés. Le contrat commercial sera publié avant activation.",
        "responses": {
          "503": {
            "description": "Comptes et paiements marchands non ouverts",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MerchantUnavailable"
                }
              }
            }
          }
        }
      }
    },
    "/v1/verify/challenge": {
      "post": {
        "operationId": "createIdentityChallenge",
        "summary": "Créer un défi vidéo lié au portrait et au compte",
        "description": "Intégration serveur après activation explicite, clé de vérification dédiée et worker privé doté de SQLite persistant et de modèles évalués. Sans configuration, 503 avant lecture du corps. Le jeton signé reste dans le backend marchand. Tous les résultats portent automaticApproval:false ; authenticité documentaire unknown. Aucun débit commercial. Le transfert QR et l’historique privés ne sont pas fournis par ces endpoints stateless. Cache-Control:no-store.",
        "security": [
          {
            "VerificationApiKey": []
          }
        ],
        "x-availability": "integration-requires-activation",
        "x-max-request-bytes": 315392,
        "x-timeout-ms": 20000,
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/IdentityChallengeInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Défi signé et instructions publiques, dix minutes",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentityChallengeResponse"
                }
              }
            }
          },
          "400": {
            "description": "Formulaire ou champs invalides",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentityError"
                }
              }
            }
          },
          "401": {
            "description": "Clé ou jeton invalide, corps non traité sans clé valide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentityError"
                }
              }
            }
          },
          "409": {
            "description": "Portrait substitué ou défi déjà consommé avec une autre tentative",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentityError"
                }
              }
            }
          },
          "410": {
            "description": "Défi expiré",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentityError"
                }
              }
            }
          },
          "413": {
            "description": "Corps ou média trop volumineux",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentityError"
                }
              }
            }
          },
          "415": {
            "description": "Type ou signature de média invalide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentityError"
                }
              }
            }
          },
          "422": {
            "description": "Média impossible à analyser",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentityError"
                }
              }
            }
          },
          "500": {
            "description": "Erreur interne sans diagnostic personnel",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentityError"
                }
              }
            }
          },
          "502": {
            "description": "Moteur inaccessible ou résultat incohérent/non lié",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentityError"
                }
              }
            }
          },
          "503": {
            "description": "Activation, registre persistant ou modèles évalués manquants",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentityError"
                }
              }
            }
          },
          "504": {
            "description": "Délai total de vingt secondes dépassé ou requête interrompue",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentityError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/identity/session": {
      "post": {
        "operationId": "createIdentitySession",
        "summary": "Créer une session de capture hébergée",
        "servers": [
          {
            "url": "https://api.kyckyde.com",
            "description": "Passerelle API dédiée ; clés côté serveur"
          }
        ],
        "security": [
          {
            "ClientApiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Résultat privé du marchand ; Cache-Control: no-store",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentitySessionCreated"
                }
              }
            }
          },
          "400": {
            "description": "Entrée invalide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentitySessionError"
                }
              }
            }
          },
          "401": {
            "description": "Clé absente ou invalide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentitySessionError"
                }
              }
            }
          },
          "403": {
            "description": "Acteur ou rôle non autorisé",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentitySessionError"
                }
              }
            }
          },
          "404": {
            "description": "Session absente",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentitySessionError"
                }
              }
            }
          },
          "409": {
            "description": "Source, capacité ou état incompatible",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentitySessionError"
                }
              }
            }
          },
          "413": {
            "description": "Corps trop volumineux",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentitySessionError"
                }
              }
            }
          },
          "429": {
            "description": "Limite persistante atteinte",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentitySessionError"
                }
              }
            }
          },
          "503": {
            "description": "Service ou modèles évalués indisponibles",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentitySessionError"
                }
              }
            }
          }
        },
        "description": "Portrait PNG issu de la pièce : 300 Kio, 32 à 1024 pixels par axe. metadata est un fichier JSON de 8192 octets maximum. Acteur et représentation autorisés par le marchand. Les modèles évalués doivent être disponibles ; sinon 503 sans preuve positive.",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": ["metadata", "portrait"],
                "properties": {
                  "metadata": {
                    "$ref": "#/components/schemas/IdentitySessionCreation"
                  },
                  "portrait": {
                    "type": "string",
                    "format": "binary",
                    "contentMediaType": "image/png",
                    "maxLength": 307200
                  }
                }
              },
              "encoding": {
                "metadata": {
                  "contentType": "application/json"
                },
                "portrait": {
                  "contentType": "image/png"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "getIdentitySession",
        "summary": "Consulter une session ou la dernière session du titulaire",
        "servers": [
          {
            "url": "https://api.kyckyde.com",
            "description": "Passerelle API dédiée ; clés côté serveur"
          }
        ],
        "security": [
          {
            "ClientApiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Résultat privé du marchand ; Cache-Control: no-store",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/IdentitySession"
                    },
                    {
                      "type": "null"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Entrée invalide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentitySessionError"
                }
              }
            }
          },
          "401": {
            "description": "Clé absente ou invalide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentitySessionError"
                }
              }
            }
          },
          "403": {
            "description": "Acteur ou rôle non autorisé",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentitySessionError"
                }
              }
            }
          },
          "404": {
            "description": "Session absente",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentitySessionError"
                }
              }
            }
          },
          "409": {
            "description": "Source, capacité ou état incompatible",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentitySessionError"
                }
              }
            }
          },
          "413": {
            "description": "Corps trop volumineux",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentitySessionError"
                }
              }
            }
          },
          "429": {
            "description": "Limite persistante atteinte",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentitySessionError"
                }
              }
            }
          },
          "503": {
            "description": "Service ou modèles évalués indisponibles",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentitySessionError"
                }
              }
            }
          }
        },
        "description": "actorReference et exactement un sélecteur : sessionId ou subjectReference. Avec subjectReference, retourne le DTO directement ou null, sans enveloppe attempt. Aucun nouveau token de capture.",
        "parameters": [
          {
            "name": "actorReference",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]{1,128}$"
            }
          },
          {
            "name": "sessionId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^iv_[a-f0-9]{32}$"
            }
          },
          {
            "name": "subjectReference",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]{1,128}$"
            }
          }
        ]
      },
      "patch": {
        "operationId": "updateIdentitySessionSource",
        "summary": "Révoquer la capacité si les empreintes de la source ont changé",
        "servers": [
          {
            "url": "https://api.kyckyde.com",
            "description": "Passerelle API dédiée ; clés côté serveur"
          }
        ],
        "security": [
          {
            "ClientApiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Résultat privé du marchand ; Cache-Control: no-store",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentitySession"
                }
              }
            }
          },
          "400": {
            "description": "Entrée invalide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentitySessionError"
                }
              }
            }
          },
          "401": {
            "description": "Clé absente ou invalide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentitySessionError"
                }
              }
            }
          },
          "403": {
            "description": "Acteur ou rôle non autorisé",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentitySessionError"
                }
              }
            }
          },
          "404": {
            "description": "Session absente",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentitySessionError"
                }
              }
            }
          },
          "409": {
            "description": "Source, capacité ou état incompatible",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentitySessionError"
                }
              }
            }
          },
          "413": {
            "description": "Corps trop volumineux",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentitySessionError"
                }
              }
            }
          },
          "429": {
            "description": "Limite persistante atteinte",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentitySessionError"
                }
              }
            }
          },
          "503": {
            "description": "Service ou modèles évalués indisponibles",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentitySessionError"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IdentitySessionSourceUpdate"
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "cancelIdentitySession",
        "summary": "Annuler et supprimer les médias de la session",
        "servers": [
          {
            "url": "https://api.kyckyde.com",
            "description": "Passerelle API dédiée ; clés côté serveur"
          }
        ],
        "security": [
          {
            "ClientApiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Résultat privé du marchand ; Cache-Control: no-store",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentitySession"
                }
              }
            }
          },
          "400": {
            "description": "Entrée invalide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentitySessionError"
                }
              }
            }
          },
          "401": {
            "description": "Clé absente ou invalide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentitySessionError"
                }
              }
            }
          },
          "403": {
            "description": "Acteur ou rôle non autorisé",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentitySessionError"
                }
              }
            }
          },
          "404": {
            "description": "Session absente",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentitySessionError"
                }
              }
            }
          },
          "409": {
            "description": "Source, capacité ou état incompatible",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentitySessionError"
                }
              }
            }
          },
          "413": {
            "description": "Corps trop volumineux",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentitySessionError"
                }
              }
            }
          },
          "429": {
            "description": "Limite persistante atteinte",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentitySessionError"
                }
              }
            }
          },
          "503": {
            "description": "Service ou modèles évalués indisponibles",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentitySessionError"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "actorReference",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]{1,128}$"
            }
          },
          {
            "name": "sessionId",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^iv_[a-f0-9]{32}$"
            }
          }
        ]
      }
    },
    "/v1/identity/operator-handoff": {
      "post": {
        "operationId": "createIdentityOperatorHandoff",
        "summary": "Ouvrir la console Kyckyde avec un accès à usage unique",
        "servers": [
          {
            "url": "https://api.kyckyde.com",
            "description": "Passerelle API dédiée ; clés côté serveur"
          }
        ],
        "security": [
          {
            "IdentityOperatorKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Résultat privé du marchand ; Cache-Control: no-store",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": ["handoffUrl", "expiresInSeconds"],
                  "properties": {
                    "handoffUrl": {
                      "type": "string",
                      "format": "uri"
                    },
                    "expiresInSeconds": {
                      "const": 60
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Entrée invalide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentitySessionError"
                }
              }
            }
          },
          "401": {
            "description": "Clé absente ou invalide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentitySessionError"
                }
              }
            }
          },
          "403": {
            "description": "Acteur ou rôle non autorisé",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentitySessionError"
                }
              }
            }
          },
          "404": {
            "description": "Session absente",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentitySessionError"
                }
              }
            }
          },
          "409": {
            "description": "Source, capacité ou état incompatible",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentitySessionError"
                }
              }
            }
          },
          "413": {
            "description": "Corps trop volumineux",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentitySessionError"
                }
              }
            }
          },
          "429": {
            "description": "Limite persistante atteinte",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentitySessionError"
                }
              }
            }
          },
          "503": {
            "description": "Service ou modèles évalués indisponibles",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentitySessionError"
                }
              }
            }
          }
        },
        "description": "operatorReference identifie l’opérateur réel. canReview est intersecté avec les permissions du registre serveur. La capacité dans le fragment expire après 60 secondes et ne peut être échangée qu’une fois.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": ["operatorReference", "canReview"],
                "properties": {
                  "operatorReference": {
                    "type": "string",
                    "pattern": "^[A-Za-z0-9_-]{1,128}$"
                  },
                  "canReview": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ClientApiKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "Clé secrète d’intégration côté serveur, distincte du secret moteur. L’émission commerciale de clés clients n’est pas activée."
      },
      "VerificationApiKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "Clé secrète serveur dédiée à la vérification. Ne jamais transmettre au composant navigateur."
      },
      "IdentityOperatorKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "Clé opérateur serveur distincte de la clé marchande. Permissions plafonnées par le registre serveur. Jamais envoyée au navigateur."
      }
    },
    "schemas": {
      "SandboxInput": {
        "type": "object",
        "additionalProperties": false,
        "required": ["operation", "sample"],
        "properties": {
          "operation": {
            "type": "string",
            "enum": ["read", "verify"]
          },
          "sample": {
            "type": "string",
            "const": "identity-card"
          }
        }
      },
      "SandboxResult": {
        "type": "object",
        "required": [
          "mode",
          "request_id",
          "operation",
          "document",
          "fields",
          "checks",
          "billing",
          "notice"
        ],
        "properties": {
          "mode": {
            "type": "string",
            "const": "sandbox"
          },
          "request_id": {
            "type": "string"
          },
          "operation": {
            "type": "string",
            "enum": ["read", "verify"]
          },
          "document": {
            "type": "object",
            "properties": {
              "type": {
                "const": "identity_card"
              },
              "country": {
                "const": "FR"
              },
              "sides": {
                "type": "array",
                "items": {
                  "type": "string",
                  "enum": ["front", "back"]
                }
              }
            }
          },
          "fields": {
            "type": "object",
            "properties": {
              "last_name": {
                "const": "MARTIN"
              },
              "first_names": {
                "type": "array",
                "items": {
                  "const": "Camille"
                }
              },
              "birth_date": {
                "const": "1992-04-18"
              },
              "document_number": {
                "const": "SPECIMEN-000001"
              },
              "expiry_date": {
                "const": "2032-04-18"
              }
            }
          },
          "checks": {
            "type": "object",
            "properties": {
              "readability": {
                "const": "passed"
              },
              "document_consistency": {
                "const": "passed"
              },
              "identity_assurance": {
                "const": "not_performed"
              }
            }
          },
          "billing": {
            "type": "object",
            "required": ["charged"],
            "properties": {
              "charged": {
                "type": "boolean",
                "const": false
              }
            }
          },
          "notice": {
            "type": "string"
          }
        }
      },
      "DocumentType": {
        "type": "string",
        "enum": [
          "identity_card",
          "passport",
          "driving_licence",
          "health_card",
          "residence_permit"
        ]
      },
      "DocumentFormat": {
        "type": "string",
        "enum": ["current", "legacy"],
        "description": "legacy est admis uniquement pour identity_card, driving_licence et residence_permit."
      },
      "FieldId": {
        "type": "string",
        "enum": [
          "nom",
          "prenoms",
          "prenom",
          "nom_usage",
          "sexe",
          "nationalite",
          "date_naissance",
          "lieu_naissance",
          "numero_document",
          "date_delivrance",
          "date_expiration",
          "autorite",
          "adresse",
          "can",
          "taille",
          "categories",
          "droits_categories",
          "restrictions",
          "nir",
          "cle_nir",
          "numero_carte",
          "date_emission",
          "beneficiaires",
          "numero_personnel",
          "mentions",
          "mrz_ligne_1",
          "mrz_ligne_2",
          "mrz_ligne_3"
        ]
      },
      "ProfileId": {
        "type": "string",
        "enum": [
          "cni_fr_recto",
          "cni_fr_verso",
          "cni_fr_ancienne_recto",
          "cni_fr_ancienne_verso",
          "passeport_td3",
          "permis_fr_recto",
          "permis_fr_verso",
          "permis_fr_ancien",
          "vitale_fr_recto",
          "vitale_fr_verso",
          "titre_sejour_fr_recto",
          "titre_sejour_fr_verso",
          "titre_sejour_ancien"
        ]
      },
      "ReadInput": {
        "type": "object",
        "additionalProperties": false,
        "required": ["front", "document_type"],
        "properties": {
          "front": {
            "type": "string",
            "format": "binary",
            "description": "Fichier déclaré recto obligatoire, JPEG, PNG, WebP ou PDF. Le côté n’est pas détecté indépendamment. Un PDF est lu en entier et les champs peuvent provenir de plusieurs pages."
          },
          "back": {
            "type": "string",
            "format": "binary",
            "description": "Fichier déclaré verso facultatif. Interdit pour passport, driving_licence legacy et residence_permit legacy. Un verso non envoyé n’est pas analysé. Un PDF est lu en entier."
          },
          "document_type": {
            "$ref": "#/components/schemas/DocumentType"
          },
          "format": {
            "$ref": "#/components/schemas/DocumentFormat",
            "default": "current"
          }
        }
      },
      "ReadField": {
        "type": "object",
        "additionalProperties": false,
        "required": ["id", "status", "value", "source_text"],
        "properties": {
          "id": {
            "$ref": "#/components/schemas/FieldId"
          },
          "status": {
            "type": "string",
            "enum": ["found", "absent", "unreadable"]
          },
          "value": {
            "type": ["string", "null"],
            "maxLength": 8192,
            "description": "Valeur dans le format du profil ; les dates ne sont pas systématiquement ISO."
          },
          "source_text": {
            "type": ["string", "null"],
            "maxLength": 8192,
            "description": "Transcription imprimée conservée par le lecteur."
          }
        },
        "if": {
          "properties": {
            "status": {
              "const": "found"
            }
          }
        },
        "then": {
          "properties": {
            "value": {
              "type": "string",
              "minLength": 1,
              "pattern": "\\S"
            },
            "source_text": {
              "type": "string",
              "minLength": 1,
              "pattern": "\\S"
            }
          }
        },
        "else": {
          "properties": {
            "value": {
              "type": "null"
            },
            "source_text": {
              "type": "null"
            }
          }
        }
      },
      "ReadFace": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "side",
          "profile_id",
          "source_sha256",
          "fields",
          "timing",
          "reader"
        ],
        "properties": {
          "side": {
            "type": "string",
            "enum": ["front", "back"],
            "description": "Côté déclaré par l’appelant, sans détection indépendante. Un PDF fourni peut contenir plusieurs pages."
          },
          "profile_id": {
            "$ref": "#/components/schemas/ProfileId"
          },
          "source_sha256": {
            "type": "string",
            "pattern": "^[a-f0-9]{64}$"
          },
          "fields": {
            "type": "array",
            "maxItems": 29,
            "items": {
              "$ref": "#/components/schemas/ReadField"
            },
            "description": "Champs du profil de cette face, sans candidats ni diagnostic OCR interne."
          },
          "timing": {
            "type": "object",
            "additionalProperties": false,
            "required": ["reading_ms", "preparation_ms"],
            "properties": {
              "reading_ms": {
                "type": "number",
                "minimum": 0
              },
              "preparation_ms": {
                "type": "number",
                "minimum": 0
              }
            }
          },
          "reader": {
            "type": "object",
            "additionalProperties": false,
            "required": ["id", "revision"],
            "properties": {
              "id": {
                "type": "string"
              },
              "revision": {
                "type": "string"
              }
            }
          }
        }
      },
      "ReadResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "mode",
          "request_id",
          "document_type",
          "format",
          "faces",
          "checks",
          "billing"
        ],
        "properties": {
          "mode": {
            "type": "string",
            "const": "integration"
          },
          "request_id": {
            "type": "string"
          },
          "document_type": {
            "$ref": "#/components/schemas/DocumentType"
          },
          "format": {
            "$ref": "#/components/schemas/DocumentFormat"
          },
          "faces": {
            "type": "array",
            "minItems": 1,
            "maxItems": 2,
            "items": {
              "$ref": "#/components/schemas/ReadFace"
            }
          },
          "checks": {
            "type": "object",
            "additionalProperties": false,
            "required": ["identity_assurance"],
            "properties": {
              "identity_assurance": {
                "type": "string",
                "const": "not_performed"
              }
            }
          },
          "billing": {
            "type": "object",
            "additionalProperties": false,
            "required": ["charged"],
            "properties": {
              "charged": {
                "type": "boolean",
                "const": false
              }
            }
          }
        }
      },
      "DocumentProfiles": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "version",
          "mode",
          "document_types",
          "constraints",
          "notice"
        ],
        "properties": {
          "version": {
            "type": "integer",
            "const": 1
          },
          "mode": {
            "type": "string",
            "const": "integration"
          },
          "document_types": {
            "type": "array",
            "maxItems": 5,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": ["id", "label", "formats"],
              "properties": {
                "id": {
                  "$ref": "#/components/schemas/DocumentType"
                },
                "label": {
                  "type": "string"
                },
                "formats": {
                  "type": "array",
                  "minItems": 1,
                  "maxItems": 2,
                  "items": {
                    "type": "object",
                    "additionalProperties": false,
                    "required": ["id", "faces"],
                    "properties": {
                      "id": {
                        "$ref": "#/components/schemas/DocumentFormat"
                      },
                      "faces": {
                        "type": "array",
                        "minItems": 1,
                        "maxItems": 2,
                        "items": {
                          "type": "object",
                          "additionalProperties": false,
                          "required": ["side", "profile_id", "fields"],
                          "properties": {
                            "side": {
                              "type": "string",
                              "enum": ["front", "back"]
                            },
                            "profile_id": {
                              "$ref": "#/components/schemas/ProfileId"
                            },
                            "fields": {
                              "type": "array",
                              "maxItems": 29,
                              "items": {
                                "type": "object",
                                "additionalProperties": false,
                                "required": ["id", "label", "type"],
                                "properties": {
                                  "id": {
                                    "$ref": "#/components/schemas/FieldId"
                                  },
                                  "label": {
                                    "type": "string"
                                  },
                                  "type": {
                                    "type": "string",
                                    "enum": ["text", "date", "number"]
                                  }
                                }
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "constraints": {
            "type": "object",
            "additionalProperties": false,
            "required": ["max_request_bytes", "mime_types", "timeout_ms"],
            "properties": {
              "max_request_bytes": {
                "type": "integer",
                "const": 4194304
              },
              "mime_types": {
                "type": "array",
                "items": {
                  "type": "string",
                  "enum": [
                    "image/jpeg",
                    "image/png",
                    "image/webp",
                    "application/pdf"
                  ]
                }
              },
              "timeout_ms": {
                "type": "integer",
                "const": 170000
              }
            }
          },
          "notice": {
            "type": "string"
          }
        }
      },
      "ReadError": {
        "type": "object",
        "additionalProperties": false,
        "required": ["request_id", "error"],
        "properties": {
          "request_id": {
            "type": "string"
          },
          "error": {
            "type": "object",
            "additionalProperties": false,
            "required": ["code", "message"],
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              }
            }
          }
        }
      },
      "Unavailable": {
        "type": "object",
        "required": ["error"],
        "properties": {
          "error": {
            "type": "object",
            "required": ["code", "message"],
            "properties": {
              "code": {
                "const": "service_not_open"
              },
              "message": {
                "type": "string"
              }
            }
          }
        }
      },
      "MerchantUnavailable": {
        "type": "object",
        "additionalProperties": false,
        "required": ["error"],
        "properties": {
          "error": {
            "type": "object",
            "additionalProperties": false,
            "required": ["code", "message"],
            "properties": {
              "code": {
                "type": "string",
                "const": "merchant_service_not_open"
              },
              "message": {
                "type": "string"
              }
            }
          }
        }
      },
      "MerchantSummary": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "mode",
          "environment",
          "currency",
          "period",
          "usage",
          "wallet",
          "services",
          "usageByBucket",
          "recentOperations",
          "ledger"
        ],
        "properties": {
          "mode": {
            "type": "string",
            "enum": ["demo", "live"]
          },
          "environment": {
            "type": "string",
            "enum": ["test", "live"]
          },
          "currency": {
            "type": "string",
            "const": "EUR"
          },
          "period": {
            "type": "object",
            "additionalProperties": false,
            "required": ["key", "label", "from", "to"],
            "properties": {
              "key": {
                "type": "string",
                "enum": ["7d", "30d", "90d"]
              },
              "label": {
                "type": "string"
              },
              "from": {
                "type": "string",
                "format": "date-time"
              },
              "to": {
                "type": "string",
                "format": "date-time",
                "description": "Fin exclusive ; dates UTC"
              }
            }
          },
          "usage": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "apiRequests",
              "documentReads",
              "documentChecks",
              "identityVerifications",
              "billedOperations",
              "technicalFailures",
              "pendingOperations",
              "distinctSubjects"
            ],
            "properties": {
              "apiRequests": {
                "type": "integer",
                "minimum": 0
              },
              "documentReads": {
                "type": "integer",
                "minimum": 0
              },
              "documentChecks": {
                "type": "integer",
                "minimum": 0
              },
              "identityVerifications": {
                "type": "integer",
                "minimum": 0
              },
              "billedOperations": {
                "type": "integer",
                "minimum": 0
              },
              "technicalFailures": {
                "type": "integer",
                "minimum": 0
              },
              "pendingOperations": {
                "type": "integer",
                "minimum": 0
              },
              "distinctSubjects": {
                "type": ["integer", "null"],
                "minimum": 0,
                "description": "Références opaques distinctes du marchand, pas des identités certifiées"
              }
            }
          },
          "wallet": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "availableCents",
              "reservedCents",
              "spentCents",
              "lowBalanceThresholdCents"
            ],
            "properties": {
              "availableCents": {
                "type": "integer",
                "minimum": 0,
                "description": "Centimes d’euro entiers"
              },
              "reservedCents": {
                "type": "integer",
                "minimum": 0,
                "description": "Centimes d’euro entiers"
              },
              "spentCents": {
                "type": "integer",
                "minimum": 0,
                "description": "Centimes d’euro entiers"
              },
              "lowBalanceThresholdCents": {
                "type": "integer",
                "minimum": 0,
                "description": "Centimes d’euro entiers"
              }
            }
          },
          "services": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "kind",
                "label",
                "completed",
                "billed",
                "unitPriceCents",
                "availability"
              ],
              "properties": {
                "kind": {
                  "type": "string",
                  "enum": ["document_read", "document_check", "identity_verify"]
                },
                "label": {
                  "type": "string"
                },
                "completed": {
                  "type": "integer",
                  "minimum": 0
                },
                "billed": {
                  "type": "integer",
                  "minimum": 0
                },
                "unitPriceCents": {
                  "type": ["integer", "null"],
                  "minimum": 0,
                  "description": "Null si tarif non défini"
                },
                "availability": {
                  "type": "string",
                  "enum": ["demo", "planned", "active"]
                }
              }
            }
          },
          "usageByBucket": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "label",
                "from",
                "to",
                "documentReads",
                "documentChecks",
                "identityVerifications"
              ],
              "properties": {
                "label": {
                  "type": "string"
                },
                "from": {
                  "type": "string",
                  "format": "date-time"
                },
                "to": {
                  "type": "string",
                  "format": "date-time"
                },
                "documentReads": {
                  "type": "integer",
                  "minimum": 0
                },
                "documentChecks": {
                  "type": "integer",
                  "minimum": 0
                },
                "identityVerifications": {
                  "type": "integer",
                  "minimum": 0
                }
              }
            }
          },
          "recentOperations": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "id",
                "subjectReference",
                "kind",
                "createdAt",
                "state",
                "priceCents"
              ],
              "properties": {
                "id": {
                  "type": "string"
                },
                "subjectReference": {
                  "type": ["string", "null"]
                },
                "kind": {
                  "type": "string",
                  "enum": ["document_read", "document_check", "identity_verify"]
                },
                "createdAt": {
                  "type": "string",
                  "format": "date-time"
                },
                "state": {
                  "type": "string",
                  "enum": ["completed", "failed", "pending"]
                },
                "priceCents": {
                  "type": "integer",
                  "minimum": 0,
                  "description": "Centimes d’euro entiers"
                }
              }
            }
          },
          "ledger": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "id",
                "kind",
                "createdAt",
                "amountCents",
                "description"
              ],
              "properties": {
                "id": {
                  "type": "string"
                },
                "kind": {
                  "type": "string",
                  "enum": ["topup", "debit", "refund"]
                },
                "createdAt": {
                  "type": "string",
                  "format": "date-time"
                },
                "amountCents": {
                  "type": "integer",
                  "description": "Montant signé en centimes entiers"
                },
                "description": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "IdentityPublicChallenge": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "verificationId",
          "challengeId",
          "actions",
          "issuedAt",
          "expiresAt",
          "maxVideoBytes",
          "minDurationSeconds",
          "maxDurationSeconds"
        ],
        "properties": {
          "verificationId": {
            "type": "string",
            "pattern": "^[A-Za-z0-9_-]{1,128}$"
          },
          "challengeId": {
            "type": "string",
            "pattern": "^[A-Za-z0-9_-]{1,128}$"
          },
          "actions": {
            "type": "array",
            "minItems": 2,
            "maxItems": 2,
            "uniqueItems": true,
            "items": {
              "type": "string",
              "enum": ["turn_left", "turn_right"]
            }
          },
          "issuedAt": {
            "type": "string",
            "format": "date-time"
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time",
            "description": "Expiration exacte dix minutes après création"
          },
          "maxVideoBytes": {
            "type": "integer",
            "const": 3145728
          },
          "minDurationSeconds": {
            "type": "integer",
            "const": 3
          },
          "maxDurationSeconds": {
            "type": "integer",
            "const": 15
          }
        }
      },
      "IdentityError": {
        "type": "object",
        "additionalProperties": false,
        "required": ["request_id", "error"],
        "properties": {
          "request_id": {
            "type": "string"
          },
          "error": {
            "type": "object",
            "additionalProperties": false,
            "required": ["code", "message"],
            "properties": {
              "code": {
                "type": "string",
                "pattern": "^[a-z0-9_]+$"
              },
              "message": {
                "type": "string"
              }
            }
          }
        }
      },
      "IdentityChallengeInput": {
        "type": "object",
        "additionalProperties": false,
        "required": ["portrait", "document_hash", "subject_reference"],
        "properties": {
          "portrait": {
            "type": "string",
            "format": "binary",
            "description": "PNG extrait de la pièce par le marchand, ≤300 Kio, 32–1024 pixels par côté"
          },
          "document_hash": {
            "type": "string",
            "pattern": "^[a-f0-9]{64}$",
            "description": "SHA-256 de la pièce source ; lien document/portrait sous responsabilité du marchand"
          },
          "subject_reference": {
            "type": "string",
            "pattern": "^[A-Za-z0-9_-]{1,128}$"
          }
        }
      },
      "IdentityChallengeResponse": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "mode",
          "request_id",
          "challenge_token",
          "challenge",
          "document_binding",
          "billing"
        ],
        "properties": {
          "mode": {
            "type": "string",
            "const": "integration"
          },
          "request_id": {
            "type": "string"
          },
          "challenge_token": {
            "type": "string",
            "description": "Jeton signé conservé dans le backend marchand, jamais dans les props de capture"
          },
          "challenge": {
            "$ref": "#/components/schemas/IdentityPublicChallenge"
          },
          "document_binding": {
            "type": "string",
            "const": "merchant_supplied"
          },
          "billing": {
            "type": "object",
            "additionalProperties": false,
            "required": ["charged"],
            "properties": {
              "charged": {
                "type": "boolean",
                "const": false
              }
            }
          }
        }
      },
      "IdentityVerifyInput": {
        "type": "object",
        "additionalProperties": false,
        "required": ["challenge_token", "portrait", "video"],
        "properties": {
          "challenge_token": {
            "type": "string",
            "maxLength": 2048,
            "pattern": "^[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]{43}$"
          },
          "portrait": {
            "type": "string",
            "format": "binary",
            "description": "PNG strictement identique à celui du défi"
          },
          "video": {
            "type": "string",
            "format": "binary",
            "description": "WebM VP8/VP9 ou MP4 H.264 ≤3 Mio, sans audio, 3–15 secondes ; contenu vérifié par le worker"
          }
        }
      },
      "IdentityScoredCheck": {
        "oneOf": [
          {
            "type": "object",
            "additionalProperties": false,
            "required": ["status", "reason", "score", "threshold"],
            "properties": {
              "status": {
                "type": "string",
                "enum": ["passed", "failed"]
              },
              "reason": {
                "type": "string"
              },
              "score": {
                "type": "number",
                "minimum": 0,
                "maximum": 1
              },
              "threshold": {
                "type": "number",
                "exclusiveMinimum": 0,
                "exclusiveMaximum": 1
              }
            }
          },
          {
            "type": "object",
            "additionalProperties": false,
            "required": ["status", "reason"],
            "properties": {
              "status": {
                "type": "string",
                "const": "unavailable"
              },
              "reason": {
                "type": "string"
              }
            }
          }
        ]
      },
      "IdentityResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "verificationId",
          "challengeId",
          "status",
          "reason",
          "checks",
          "automaticApproval"
        ],
        "properties": {
          "verificationId": {
            "type": "string",
            "pattern": "^[A-Za-z0-9_-]{1,128}$"
          },
          "challengeId": {
            "type": "string",
            "pattern": "^[A-Za-z0-9_-]{1,128}$"
          },
          "status": {
            "type": "string",
            "enum": ["review", "declined", "unavailable"]
          },
          "reason": {
            "type": "string",
            "enum": [
              "document_authenticity_not_established",
              "proof_failed",
              "analysis_inconclusive"
            ]
          },
          "checks": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "faceMatch",
              "presence",
              "challenge",
              "documentAuthenticity"
            ],
            "properties": {
              "faceMatch": {
                "$ref": "#/components/schemas/IdentityScoredCheck"
              },
              "presence": {
                "$ref": "#/components/schemas/IdentityScoredCheck"
              },
              "challenge": {
                "type": "object",
                "additionalProperties": false,
                "required": ["status", "reason"],
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": ["passed", "failed", "unavailable"]
                  },
                  "reason": {
                    "type": "string"
                  }
                }
              },
              "documentAuthenticity": {
                "type": "string",
                "const": "unknown"
              }
            }
          },
          "automaticApproval": {
            "type": "boolean",
            "const": false
          }
        }
      },
      "IdentityVerifyResponse": {
        "type": "object",
        "additionalProperties": false,
        "required": ["mode", "request_id", "result", "billing"],
        "properties": {
          "mode": {
            "type": "string",
            "const": "integration"
          },
          "request_id": {
            "type": "string"
          },
          "result": {
            "$ref": "#/components/schemas/IdentityResult"
          },
          "billing": {
            "type": "object",
            "additionalProperties": false,
            "required": ["charged"],
            "properties": {
              "charged": {
                "type": "boolean",
                "const": false
              }
            }
          }
        }
      },
      "IdentitySessionCreation": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "actorReference",
          "subjectReference",
          "sourceReference",
          "documentHash",
          "sourceSnapshotHash",
          "minor",
          "mode",
          "authorization",
          "consent",
          "consentVersion"
        ],
        "properties": {
          "actorReference": {
            "type": "string",
            "pattern": "^[A-Za-z0-9_-]{1,128}$"
          },
          "subjectReference": {
            "type": "string",
            "pattern": "^[A-Za-z0-9_-]{1,128}$"
          },
          "sourceReference": {
            "type": "string",
            "pattern": "^[A-Za-z0-9_-]{1,128}$"
          },
          "documentHash": {
            "type": "string",
            "pattern": "^[a-f0-9]{64}$"
          },
          "sourceSnapshotHash": {
            "type": "string",
            "pattern": "^[a-f0-9]{64}$"
          },
          "minor": {
            "type": "boolean"
          },
          "mode": {
            "enum": ["self", "assisted"]
          },
          "authorization": {
            "type": "object",
            "additionalProperties": false,
            "required": ["self", "representative"],
            "properties": {
              "self": {
                "type": "boolean"
              },
              "representative": {
                "type": "boolean"
              }
            }
          },
          "consent": {
            "const": true
          },
          "consentVersion": {
            "const": "identity-verification-v1"
          },
          "representativeConsent": {
            "type": "boolean"
          },
          "representativeConsentVersion": {
            "const": "identity-representative-v1"
          }
        }
      },
      "IdentitySession": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "id",
          "status",
          "reason",
          "expiresAt",
          "challenge",
          "checks",
          "manualReview",
          "engine",
          "maxVideoBytes",
          "maxVideoSeconds"
        ],
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^iv_[a-f0-9]{32}$"
          },
          "status": {
            "enum": [
              "awaiting_authorization",
              "collecting",
              "uploading",
              "processing",
              "review",
              "refused",
              "expired",
              "revoked"
            ]
          },
          "reason": {
            "type": "string",
            "maxLength": 300
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time"
          },
          "challenge": {
            "type": "object",
            "additionalProperties": false,
            "required": ["id", "actions", "issuedAt", "expiresAt"],
            "properties": {
              "id": {
                "type": "string"
              },
              "actions": {
                "type": "array",
                "minItems": 2,
                "maxItems": 2,
                "items": {
                  "enum": ["turn_left", "turn_right"]
                }
              },
              "issuedAt": {
                "type": "string",
                "format": "date-time"
              },
              "expiresAt": {
                "type": "string",
                "format": "date-time"
              }
            }
          },
          "checks": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "document",
              "faceMatch",
              "presence",
              "authenticity",
              "challenge"
            ],
            "properties": {
              "document": {
                "enum": ["passed", "failed", "unavailable", "unknown"]
              },
              "faceMatch": {
                "enum": ["passed", "failed", "unavailable", "unknown"]
              },
              "presence": {
                "enum": ["passed", "failed", "unavailable", "unknown"]
              },
              "authenticity": {
                "enum": ["passed", "failed", "unavailable", "unknown"]
              },
              "challenge": {
                "enum": ["passed", "failed", "unavailable", "unknown"]
              }
            }
          },
          "manualReview": {
            "type": "object",
            "additionalProperties": false,
            "required": ["state", "reviewedAt"],
            "properties": {
              "state": {
                "enum": ["none", "accepted", "refused"]
              },
              "reviewedAt": {
                "type": ["string", "null"],
                "format": "date-time"
              }
            }
          },
          "engine": {
            "type": "object",
            "additionalProperties": false,
            "required": ["available", "reason"],
            "properties": {
              "available": {
                "type": "boolean"
              },
              "reason": {
                "type": "string"
              }
            }
          },
          "maxVideoBytes": {
            "type": "integer",
            "const": 3145728
          },
          "maxVideoSeconds": {
            "type": "integer",
            "const": 15
          },
          "actorReference": {
            "type": "string",
            "pattern": "^[A-Za-z0-9_-]{1,128}$"
          },
          "subjectReference": {
            "type": "string",
            "pattern": "^[A-Za-z0-9_-]{1,128}$"
          },
          "sourceReference": {
            "type": "string",
            "pattern": "^[A-Za-z0-9_-]{1,128}$"
          },
          "documentHash": {
            "type": "string",
            "pattern": "^[a-f0-9]{64}$"
          },
          "sourceSnapshotHash": {
            "type": "string",
            "pattern": "^[a-f0-9]{64}$"
          },
          "mode": {
            "enum": ["self", "assisted"]
          },
          "minor": {
            "type": "boolean"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "representativeAuthorization": {
            "enum": ["declared", "none"]
          }
        }
      },
      "IdentitySessionCreated": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "id",
          "status",
          "reason",
          "expiresAt",
          "challenge",
          "checks",
          "manualReview",
          "engine",
          "maxVideoBytes",
          "maxVideoSeconds"
        ],
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^iv_[a-f0-9]{32}$"
          },
          "status": {
            "enum": [
              "awaiting_authorization",
              "collecting",
              "uploading",
              "processing",
              "review",
              "refused",
              "expired",
              "revoked"
            ]
          },
          "reason": {
            "type": "string",
            "maxLength": 300
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time"
          },
          "challenge": {
            "type": "object",
            "additionalProperties": false,
            "required": ["id", "actions", "issuedAt", "expiresAt"],
            "properties": {
              "id": {
                "type": "string"
              },
              "actions": {
                "type": "array",
                "minItems": 2,
                "maxItems": 2,
                "items": {
                  "enum": ["turn_left", "turn_right"]
                }
              },
              "issuedAt": {
                "type": "string",
                "format": "date-time"
              },
              "expiresAt": {
                "type": "string",
                "format": "date-time"
              }
            }
          },
          "checks": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "document",
              "faceMatch",
              "presence",
              "authenticity",
              "challenge"
            ],
            "properties": {
              "document": {
                "enum": ["passed", "failed", "unavailable", "unknown"]
              },
              "faceMatch": {
                "enum": ["passed", "failed", "unavailable", "unknown"]
              },
              "presence": {
                "enum": ["passed", "failed", "unavailable", "unknown"]
              },
              "authenticity": {
                "enum": ["passed", "failed", "unavailable", "unknown"]
              },
              "challenge": {
                "enum": ["passed", "failed", "unavailable", "unknown"]
              }
            }
          },
          "manualReview": {
            "type": "object",
            "additionalProperties": false,
            "required": ["state", "reviewedAt"],
            "properties": {
              "state": {
                "enum": ["none", "accepted", "refused"]
              },
              "reviewedAt": {
                "type": ["string", "null"],
                "format": "date-time"
              }
            }
          },
          "engine": {
            "type": "object",
            "additionalProperties": false,
            "required": ["available", "reason"],
            "properties": {
              "available": {
                "type": "boolean"
              },
              "reason": {
                "type": "string"
              }
            }
          },
          "maxVideoBytes": {
            "type": "integer",
            "const": 3145728
          },
          "maxVideoSeconds": {
            "type": "integer",
            "const": 15
          },
          "actorReference": {
            "type": "string",
            "pattern": "^[A-Za-z0-9_-]{1,128}$"
          },
          "subjectReference": {
            "type": "string",
            "pattern": "^[A-Za-z0-9_-]{1,128}$"
          },
          "sourceReference": {
            "type": "string",
            "pattern": "^[A-Za-z0-9_-]{1,128}$"
          },
          "documentHash": {
            "type": "string",
            "pattern": "^[a-f0-9]{64}$"
          },
          "sourceSnapshotHash": {
            "type": "string",
            "pattern": "^[a-f0-9]{64}$"
          },
          "mode": {
            "enum": ["self", "assisted"]
          },
          "minor": {
            "type": "boolean"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "representativeAuthorization": {
            "enum": ["declared", "none"]
          },
          "token": {
            "type": "string",
            "pattern": "^[A-Za-z0-9_-]{43}$"
          },
          "captureUrl": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "IdentitySessionError": {
        "type": "object",
        "additionalProperties": false,
        "required": ["error"],
        "properties": {
          "error": {
            "type": "object",
            "additionalProperties": false,
            "required": ["code", "message"],
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              }
            }
          }
        }
      },
      "IdentitySessionSourceUpdate": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "sessionId",
          "actorReference",
          "sourceSnapshotHash",
          "documentHash"
        ],
        "properties": {
          "sessionId": {
            "type": "string",
            "pattern": "^iv_[a-f0-9]{32}$"
          },
          "actorReference": {
            "type": "string",
            "pattern": "^[A-Za-z0-9_-]{1,128}$"
          },
          "sourceSnapshotHash": {
            "type": "string",
            "pattern": "^[a-f0-9]{64}$"
          },
          "documentHash": {
            "type": "string",
            "pattern": "^[a-f0-9]{64}$"
          }
        }
      }
    }
  }
}
