openapi: 3.1.0
info:
  title: Toutatis IDV — Public API
  version: 1.0.0
  description: |
    API publique de vérification d'identité, exposée via le proxy hub
    `toutatis.eu/erp/custom/storecoveadmin/api_proxy_server/idv/proxy.php`.

    Authentification : `client_id` + `client_api_key` (body ou headers X-Client-ID / X-Client-Api-Key).

    Voir aussi :
    - Webhooks sortants : section `webhooks`
    - Contrat détaillé : docs/architecture/idv/API_CONTRACT.md
  contact:
    name: Toutatis Limited
    email: idv@toutatis.eu
    url: https://idv.toutatis.eu
  license:
    name: Proprietary
servers:
  - url: https://toutatis.eu/erp/custom/storecoveadmin/api_proxy_server/idv
    description: Production

security:
  - saasCredentials: []

paths:
  /proxy.php:
    get:
      summary: Health check
      security: []
      parameters:
        - name: action
          in: query
          required: true
          schema:
            type: string
            enum: [health]
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Health'
    post:
      summary: Endpoint principal (multiplexé par ?action=)
      parameters:
        - name: action
          in: query
          required: true
          schema:
            type: string
            enum: [session.create, session.status, session.cancel]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              oneOf:
                - $ref: '#/components/schemas/CreateSessionRequest'
                - $ref: '#/components/schemas/SessionStatusRequest'
                - $ref: '#/components/schemas/CancelSessionRequest'
      responses:
        '200':
          description: Succès
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/CreateSessionResponse'
                  - $ref: '#/components/schemas/SessionStatusResponse'
                  - $ref: '#/components/schemas/CancelSessionResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
        '502':
          $ref: '#/components/responses/BadGateway'

components:
  securitySchemes:
    saasCredentials:
      type: apiKey
      in: header
      name: X-Client-Api-Key
      description: Fournir aussi X-Client-ID (ou dans le body JSON)

  responses:
    BadRequest:
      description: Requête invalide (paramètres manquants, format incorrect)
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    Unauthorized:
      description: Auth manquante ou invalide
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    Forbidden:
      description: Client sans droits (service_type ne contient pas 'idv', quota, etc.)
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    RateLimited:
      description: Trop de requêtes ou quota mensuel épuisé
      content:
        application/json:
          schema:
            allOf:
              - $ref: '#/components/schemas/Error'
              - type: object
                properties:
                  used: { type: integer }
                  limit: { type: integer }
    BadGateway:
      description: Pipeline IDV indisponible
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }

  schemas:
    Health:
      type: object
      properties:
        ok: { type: boolean }
        service: { type: string }
        version: { type: string }
        api_url_configured: { type: boolean }
        internal_secret_configured: { type: boolean }
        webhook_secret_configured: { type: boolean }

    Error:
      type: object
      required: [error]
      properties:
        error: { type: string }
        message: { type: string }

    Flow:
      type: string
      enum:
        - document_only
        - kyc_full
        - age_verify
        - eidas_wallet

    Decision:
      type: string
      enum: [APPROVED, REJECTED, REVIEW]

    SessionStatus:
      type: string
      enum: [CREATED, CAPTURED, PROCESSING, REVIEW, APPROVED, REJECTED, EXPIRED, CANCELLED]

    Credentials:
      type: object
      required: [client_id, client_api_key]
      properties:
        client_id:
          type: string
          example: client_abc123
        client_api_key:
          type: string
          format: password

    CreateSessionRequest:
      allOf:
        - $ref: '#/components/schemas/Credentials'
        - type: object
          required: [flow]
          properties:
            flow: { $ref: '#/components/schemas/Flow' }
            reference:
              type: string
              maxLength: 255
              description: Référence interne côté client (ex ID customer)
              example: customer-42
            metadata:
              type: object
              additionalProperties: true
            redirect_url:
              type: string
              format: uri
              description: URL de retour après complétion parcours end-user
            locale:
              type: string
              enum: [fr, en, nl, de]
              default: fr
            country_hint:
              type: string
              pattern: '^[A-Z]{2}$'
              example: FR
            expires_in_seconds:
              type: integer
              minimum: 60
              maximum: 3600
              default: 1800

    CreateSessionResponse:
      type: object
      required: [session_id, session_url, expires_at, status]
      properties:
        session_id: { type: string, example: cljxxxx }
        session_url:
          type: string
          format: uri
          example: https://idv.toutatis.eu/v/eyJhbGci...
        expires_at:
          type: string
          format: date-time
        status:
          type: string
          enum: [CREATED]

    SessionStatusRequest:
      allOf:
        - $ref: '#/components/schemas/Credentials'
        - type: object
          required: [session_id]
          properties:
            session_id: { type: string }

    SessionStatusResponse:
      type: object
      properties:
        session_id: { type: string }
        tenant_id: { type: string }
        flow: { $ref: '#/components/schemas/Flow' }
        status: { $ref: '#/components/schemas/SessionStatus' }
        reference: { type: string, nullable: true }
        decision:
          type: object
          nullable: true
          properties:
            outcome: { $ref: '#/components/schemas/Decision' }
            score: { type: number, minimum: 0, maximum: 1 }
            reasons:
              type: array
              items: { type: string }
            reviewed_by: { type: string, nullable: true }
            reviewed_at: { type: string, format: date-time, nullable: true }
        checks:
          type: object
          additionalProperties:
            type: object
            properties:
              passed: { type: boolean, nullable: true }
              score: { type: number, nullable: true }
        extracted_data:
          type: object
          additionalProperties: true
        assets:
          type: object
          properties:
            report_pdf_url: { type: string, format: uri }
            report_pdf_hash: { type: string, example: 'sha256:...' }
            front_image_hash: { type: string }
            back_image_hash: { type: string }
            selfie_hash: { type: string }
        timestamps:
          type: object
          properties:
            created_at: { type: string, format: date-time }
            captured_at: { type: string, format: date-time, nullable: true }
            completed_at: { type: string, format: date-time, nullable: true }

    CancelSessionRequest:
      allOf:
        - $ref: '#/components/schemas/Credentials'
        - type: object
          required: [session_id]
          properties:
            session_id: { type: string }
            reason:
              type: string
              maxLength: 500

    CancelSessionResponse:
      type: object
      properties:
        session_id: { type: string }
        status: { type: string, enum: [CANCELLED] }
        cancelled_at: { type: string, format: date-time }

webhooks:
  session.decision:
    post:
      summary: Décision de vérification (APPROVED / REJECTED / REVIEW)
      description: |
        Envoyé par le pipeline IDV vers `idv_webhook_url` configurée sur
        `llx_saas_clients`. Signé HMAC-SHA256 dans le header `X-Idv-Signature`.
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required: [event_id, event_type, occurred_at, tenant_id, session_id]
              properties:
                event_id: { type: string }
                event_type:
                  type: string
                  enum: [session.decision]
                occurred_at: { type: string, format: date-time }
                tenant_id: { type: string }
                session_id: { type: string }
                flow: { $ref: '#/components/schemas/Flow' }
                reference: { type: string, nullable: true }
                status: { type: string, example: approved }
                decision:
                  type: object
                  properties:
                    outcome: { $ref: '#/components/schemas/Decision' }
                    score: { type: number }
                    reasons: { type: array, items: { type: string } }
                    reviewed_by: { type: string, nullable: true }
                    reviewed_at: { type: string, format: date-time, nullable: true }
                checks:
                  type: object
                assets:
                  type: object
                timestamps:
                  type: object
      responses:
        '200':
          description: Reçu et traité (idempotent via event_id)

  session.expired:
    post:
      summary: Session expirée sans capture / traitement
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                event_id: { type: string }
                event_type: { type: string, enum: [session.expired] }
                session_id: { type: string }
                tenant_id: { type: string }
                occurred_at: { type: string, format: date-time }
      responses:
        '200':
          description: Reçu
