openapi: 3.1.0
info:
  title: SignCloud Identity API
  version: "1.0.0"
  license:
    name: Licence propriétaire SignCloud
    identifier: LicenseRef-SignCloud-Proprietary
  description: >
    API autonome de vérification d'identité SignCloud Identity. Elle ne crée aucun document de signature.
    Le parcours natif vérifie la puce NFC (DG1, DG2, EF.SOD, certificat signataire et chaîne CSCA),
    compare le portrait à trois captures vivantes, contrôle le mouvement, la présentation, l'intégrité de
    l'application et le rejeu inter-session, puis archive la preuve chiffrée hors du serveur Web.
servers:
  - url: https://signcloud.fr/api/identity/v1
security:
  - apiKey: []
components:
  securitySchemes:
    apiKey:
      type: http
      scheme: bearer
      description: Clé API de l'espace SignCloud. X-Api-Key est également accepté.
  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      schema: { type: string, minLength: 8, maxLength: 200 }
      description: Clé stable obligatoire pour toute création. Un rejeu identique restitue la réponse initiale.
  schemas:
    VerificationCreate:
      type: object
      required: [reference]
      properties:
        reference: { type: string, maxLength: 190, description: Référence métier unique dans l'espace client. }
        subject:
          type: object
          description: Identité déclarée facultative, chiffrée au repos et distincte du verdict lu sur la puce.
          properties:
            name: { type: string, maxLength: 180 }
            email: { type: string, format: email, maxLength: 190 }
            phone: { type: string, maxLength: 30 }
        metadata:
          type: object
          maxProperties: 20
          additionalProperties: { type: [string, number, integer, boolean, "null"] }
        return_url: { type: string, format: uri, description: URL HTTPS proposée à l'usager après le contrôle. }
        retention_years: { type: integer, enum: [10, 20, 50], default: 10 }
    Verification:
      type: object
      required: [id, reference, status, provider, created_at, expires_at, retention_years, result]
      properties:
        id: { type: string, format: uuid }
        reference: { type: string }
        status: { type: string, enum: [PENDING, PROCESSING, VERIFIED, FAILED, EXPIRED, CANCELED] }
        provider: { type: string, enum: [signcloud_nfc] }
        subject:
          type: object
          description: Identité déclarée par le client, distincte du verdict lu sur la puce.
          additionalProperties: { type: string }
        metadata:
          type: object
          maxProperties: 20
          additionalProperties: { type: [string, number, integer, boolean, "null"] }
        created_at: { type: string }
        expires_at: { type: string }
        completed_at: { type: [string, "null"] }
        return_url: { type: string }
        retention_years: { type: integer, enum: [10, 20, 50] }
        verification_url: { type: string, format: uri, description: Page Web dédiée et personnalisée à ouvrir pour l'usager. }
        app_url: { type: string, description: Deep link éphémère vers l'application native. Présent uniquement avant le verdict terminal. }
        result:
          type: object
          description: Verdict minimisé. Aucun numéro complet de titre, jeton ni image brute n'est exposé.
          properties:
            id: { type: string, format: uuid }
            status: { type: string }
            method: { type: string }
            channel: { type: string, enum: [nfc] }
            provider: { type: string }
            authentic: { type: [boolean, "null"] }
            liveness: { type: [boolean, "null"] }
            holder:
              type: object
              properties:
                given_name: { type: string }
                family_name: { type: string }
            document:
              type: object
              properties:
                type: { type: string }
                issuer: { type: string }
                number_masked: { type: string }
                expiry: { type: string }
            face_comparison: { type: [object, "null"], additionalProperties: true }
            presentation_attack_detection: { type: [object, "null"], additionalProperties: true }
            capture_integrity: { type: [object, "null"], additionalProperties: true }
            archive_status: { type: [string, "null"] }
            verified_at: { type: [string, "null"] }
            failure_code: { type: string }
    Assertion:
      type: object
      description: Assertion finale disponible uniquement après verdict VERIFIED et dépôt externe de la preuve.
      properties:
        schema: { type: string, enum: [signcloud.identity.api.assertion.v1] }
        id: { type: string, format: uuid }
        reference: { type: string }
        status: { type: string, enum: [VERIFIED] }
        provider: { type: string }
        method: { type: string }
        channel: { type: string }
        authentic: { type: boolean }
        authenticity:
          type: object
          description: Résumé des contrôles d’authenticité et métadonnées X.509 du certificat du titre, sans certificat brut.
          additionalProperties: true
        holder: { type: object, additionalProperties: true }
        document: { type: object, description: Numéro masqué uniquement., additionalProperties: true }
        liveness: { type: boolean }
        face_comparison: { type: object, additionalProperties: true }
        presentation_attack_detection: { type: object, additionalProperties: true }
        capture_integrity: { type: object, additionalProperties: true }
        cross_session_replay_checked: { type: boolean }
        evidence_sha256: { type: string }
        proof_assertion_sha256: { type: string }
        assertion_sha256: { type: string }
        identity_archive: { type: object, additionalProperties: true }
        verified_at: { type: string }
    Error:
      type: object
      properties:
        error: { type: string }
        message: { type: string }
  responses:
    Unauthorized:
      description: Clé API manquante ou invalide.
      content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } }
    Forbidden:
      description: Espace suspendu, option Identity absente ou clé en lecture seule.
      content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } }
    RateLimited:
      description: Limite de requêtes atteinte. Consulter Retry-After.
      headers:
        Retry-After: { schema: { type: integer } }
      content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } }
paths:
  /verifications:
    post:
      operationId: createIdentityVerification
      summary: Créer une vérification autonome
      parameters: [{ $ref: '#/components/parameters/IdempotencyKey' }]
      requestBody:
        required: true
        content:
          application/json: { schema: { $ref: '#/components/schemas/VerificationCreate' } }
      responses:
        "201": { description: Créée, content: { application/json: { schema: { $ref: '#/components/schemas/Verification' } } } }
        "200": { description: Rejeu métier ou idempotent, content: { application/json: { schema: { $ref: '#/components/schemas/Verification' } } } }
        "409": { description: Référence réutilisée avec un autre corps ou moteur indisponible. }
        "422": { description: Paramètres ou clé d'idempotence invalides. }
        "401": { $ref: '#/components/responses/Unauthorized' }
        "403": { $ref: '#/components/responses/Forbidden' }
        "429": { $ref: '#/components/responses/RateLimited' }
    get:
      operationId: listIdentityVerifications
      summary: Lister les vérifications
      parameters:
        - { name: status, in: query, schema: { type: string, enum: [PENDING, PROCESSING, VERIFIED, FAILED, EXPIRED, CANCELED] } }
        - { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 100, default: 50 } }
      responses:
        "200":
          description: Collection
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { type: array, items: { $ref: '#/components/schemas/Verification' } }
                  count: { type: integer }
        "401": { $ref: '#/components/responses/Unauthorized' }
        "403": { $ref: '#/components/responses/Forbidden' }
        "422": { description: Filtre invalide., content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } }
        "429": { $ref: '#/components/responses/RateLimited' }
  /verifications/{id}:
    parameters: [{ name: id, in: path, required: true, schema: { type: string, format: uuid } }]
    get:
      operationId: getIdentityVerification
      summary: Lire le statut et le verdict minimisé
      responses:
        "200": { description: État courant, content: { application/json: { schema: { $ref: '#/components/schemas/Verification' } } } }
        "404": { description: Introuvable dans cet espace. }
        "401": { $ref: '#/components/responses/Unauthorized' }
        "403": { $ref: '#/components/responses/Forbidden' }
        "429": { $ref: '#/components/responses/RateLimited' }
    delete:
      operationId: cancelIdentityVerification
      summary: Annuler une vérification non finalisée
      responses:
        "200": { description: Annulée, content: { application/json: { schema: { $ref: '#/components/schemas/Verification' } } } }
        "409": { description: Une identité déjà vérifiée ne peut pas être annulée. }
        "401": { $ref: '#/components/responses/Unauthorized' }
        "403": { $ref: '#/components/responses/Forbidden' }
        "404": { description: Introuvable dans cet espace. }
        "429": { $ref: '#/components/responses/RateLimited' }
  /verifications/{id}/assertion:
    get:
      operationId: getIdentityAssertion
      summary: Récupérer l'assertion finale contrôlée
      parameters: [{ name: id, in: path, required: true, schema: { type: string, format: uuid } }]
      responses:
        "200":
          description: Assertion
          content:
            application/json:
              schema:
                type: object
                properties: { assertion: { $ref: '#/components/schemas/Assertion' } }
        "409": { description: Preuve non encore déposée ou vérification non validée. }
        "401": { $ref: '#/components/responses/Unauthorized' }
        "403": { $ref: '#/components/responses/Forbidden' }
        "404": { description: Introuvable dans cet espace. }
        "429": { $ref: '#/components/responses/RateLimited' }
  /verifications/{id}/attestation:
    get:
      operationId: downloadIdentityAttestation
      summary: Télécharger l'attestation PDF minimisée
      description: >
        Produit une attestation PDF uniquement après verdict VERIFIED et dépôt externe de la preuve.
        Le document contient les contrôles, les empreintes et les données d'identité minimisées ; il
        n'expose jamais les images DG2/selfie ni le numéro complet du titre.
      parameters: [{ name: id, in: path, required: true, schema: { type: string, format: uuid } }]
      responses:
        "200":
          description: Attestation SignCloud Identity
          content:
            application/pdf:
              schema: { type: string, format: binary }
        "409": { description: Preuve non encore déposée ou vérification non validée. }
        "401": { $ref: '#/components/responses/Unauthorized' }
        "403": { $ref: '#/components/responses/Forbidden' }
        "404": { description: Introuvable dans cet espace. }
        "429": { $ref: '#/components/responses/RateLimited' }
webhooks:
  identityEvent:
    post:
      operationId: receiveIdentityEvent
      summary: Événements Identity envoyés aux webhooks configurés sur l'espace
      description: >
        Événements identity.api.created, identity.api.processing, identity.api.verified,
        identity.api.failed, identity.api.expired et identity.api.canceled. Le corps est signé par
        X-SignCloud-Signature: sha256=<HMAC> et livré via l'outbox avec reprises.
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                event: { type: string }
                sent_at: { type: string }
                tenant: { type: string }
                data:
                  type: object
                  properties:
                    id: { type: string, format: uuid }
                    reference: { type: string }
                    status: { type: string }
                    provider: { type: string }
                    verified_at: { type: [string, "null"] }
      responses: { "200": { description: Accusé de réception du client. } }
