openapi: 3.0.3
info:
  title: API ExcelID – identification et signature
  version: 1.0.0
  description: |
    Identification, signature et activation ExcelID, rattachées à l'identité d'État (CNIB, puis WURI).
    Les textes affichés à l'utilisateur sont encodés en UTF-8 puis en Base64.
servers:
  - url: https://www.excelidapi.aikinggroup.com/v1
    description: Environnement de test
security:
  - apiKey: []
paths:
  /auth:
    post:
      summary: Crée une demande d'identification
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/OrderRequest' }
      responses:
        '200':
          description: Requête réussie
          content:
            application/json:
              schema: { $ref: '#/components/schemas/OrderCreated' }
        default: { $ref: '#/components/responses/Error' }
  /sign:
    post:
      summary: Crée une demande de signature (userVisibleData obligatoire)
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/OrderRequest' }
      responses:
        '200':
          description: Requête réussie
          content:
            application/json:
              schema: { $ref: '#/components/schemas/OrderCreated' }
        default: { $ref: '#/components/responses/Error' }
  /payment:
    post:
      summary: Crée une demande de signature pour un paiement
      requestBody:
        required: true
        content:
          application/json:
            schema:
              allOf:
                - { $ref: '#/components/schemas/OrderRequest' }
                - type: object
                  required: [transaction]
                  properties:
                    transaction:
                      type: object
                      required: [amount, currency, recipient]
                      properties:
                        amount: { type: string, example: '450000' }
                        currency: { type: string, example: XOF }
                        recipient:
                          type: object
                          required: [name]
                          properties:
                            name: { type: string }
                            account: { type: string }
                        reference: { type: string }
      responses:
        '200':
          description: Requête réussie
          content:
            application/json:
              schema: { $ref: '#/components/schemas/OrderCreated' }
        default: { $ref: '#/components/responses/Error' }
  /phone/auth:
    post:
      summary: Identification pendant un appel téléphonique
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/PhoneRequest' }
      responses:
        '200': { $ref: '#/components/responses/OrderRefOnly' }
        default: { $ref: '#/components/responses/Error' }
  /phone/sign:
    post:
      summary: Signature pendant un appel téléphonique (userVisibleData obligatoire)
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/PhoneRequest' }
      responses:
        '200': { $ref: '#/components/responses/OrderRefOnly' }
        default: { $ref: '#/components/responses/Error' }
  /collect:
    post:
      summary: État et résultat d'une demande (à interroger toutes les 2 secondes)
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/OrderRefRequest' }
      responses:
        '200':
          description: Requête réussie
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Collected' }
        default: { $ref: '#/components/responses/Error' }
  /cancel:
    post:
      summary: Annule une demande en cours
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/OrderRefRequest' }
      responses:
        '200':
          description: Requête réussie
          content:
            application/json:
              schema: { type: object }
        default: { $ref: '#/components/responses/Error' }
  /enrollments:
    post:
      summary: Active ExcelID pour un client, après vérification en personne
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [fullName, personalNumber, documentVerified]
              properties:
                fullName: { type: string }
                personalNumber: { type: string, pattern: '^[A-Za-z0-9]{6,20}$' }
                documentVerified: { type: boolean, description: 'Doit valoir true' }
      responses:
        '200':
          description: Requête réussie
          content:
            application/json:
              schema:
                type: object
                properties:
                  userId: { type: string, format: uuid }
                  enrollmentId: { type: string, format: uuid }
                  activationCode: { type: string, example: ACX7-RSD4 }
                  activationLink: { type: string, example: 'excelid://activate?code=ACX7-RSD4' }
                  expiresAt: { type: string, format: date-time }
        default: { $ref: '#/components/responses/Error' }
    get:
      summary: Liste les derniers enrôlements de l'organisation
      responses:
        '200':
          description: Requête réussie
          content:
            application/json:
              schema:
                type: array
                items: { $ref: '#/components/schemas/Enrollment' }
  /enrollments/{id}:
    get:
      summary: État d'une activation
      parameters:
        - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
      responses:
        '200':
          description: Requête réussie
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Enrollment' }
        default: { $ref: '#/components/responses/Error' }
  /idcard/verify:
    post:
      summary: Vérifie le code QR animé de la carte numérique
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [qrData]
              properties:
                qrData: { type: string }
                endUserIp: { type: string }
      responses:
        '200':
          description: Requête réussie
          content:
            application/json:
              schema: { $ref: '#/components/schemas/IdCardVerification' }
        default: { $ref: '#/components/responses/Error' }
components:
  securitySchemes:
    apiKey:
      type: apiKey
      in: header
      name: X-Api-Key
      description: Environnement de test. En production, authentification par certificat client (mTLS).
  responses:
    OrderRefOnly:
      description: Requête réussie
      content:
        application/json:
          schema:
            type: object
            required: [orderRef]
            properties:
              orderRef: { type: string, format: uuid }
    Error:
      description: Erreur
      content:
        application/json:
          schema:
            type: object
            properties:
              errorCode:
                type: string
                enum: [alreadyInProgress, invalidParameters, unauthorized, notFound, methodNotAllowed, requestTimeout, unsupportedMediaType, internalError, maintenance, qrCodeExpired, qrCodeUsed]
              details: { type: string }
  schemas:
    OrderRequest:
      type: object
      required: [endUserIp]
      properties:
        endUserIp: { type: string, description: "Adresse IP de l'utilisateur, telle que votre service la voit" }
        returnUrl: { type: string, description: Doit contenir un nonce lié à la session }
        returnRisk: { type: boolean }
        userVisibleData: { type: string, description: "UTF-8 puis Base64. 1 500 caractères (auth), 40 000 (sign)" }
        userVisibleDataFormat: { type: string, enum: [plaintext, simpleMarkdownV1] }
        userNonVisibleData: { type: string, description: Base64 }
        requirement:
          type: object
          properties:
            personalNumber: { type: string }
            stepUp: { type: array, items: { type: string, enum: [document] } }
        app: { type: object }
        web: { type: object }
    PhoneRequest:
      type: object
      required: [personalNumber, callInitiator]
      properties:
        personalNumber: { type: string }
        callInitiator: { type: string, enum: [user, RP] }
        userVisibleData: { type: string }
        userVisibleDataFormat: { type: string, enum: [plaintext, simpleMarkdownV1] }
        userNonVisibleData: { type: string }
        requirement: { type: object }
    OrderCreated:
      type: object
      required: [orderRef, autoStartToken, qrStartToken, qrStartSecret]
      properties:
        orderRef: { type: string, format: uuid }
        autoStartToken: { type: string, format: uuid }
        qrStartToken: { type: string, format: uuid }
        qrStartSecret: { type: string, description: Ne jamais l'envoyer au frontend }
    OrderRefRequest:
      type: object
      required: [orderRef]
      properties:
        orderRef: { type: string, format: uuid }
    Collected:
      type: object
      required: [orderRef, status]
      properties:
        orderRef: { type: string, format: uuid }
        status: { type: string, enum: [pending, complete, failed] }
        hintCode: { type: string }
        completionData: { $ref: '#/components/schemas/CompletionData' }
    CompletionData:
      type: object
      properties:
        user:
          type: object
          properties:
            personalNumber: { type: string }
            name: { type: string }
            givenName: { type: string }
            surname: { type: string }
        device:
          type: object
          properties:
            ipAddress: { type: string }
        issuer:
          type: object
          properties:
            partner: { type: string }
            activatedAt: { type: string, format: date-time }
        authenticationMethod: { type: string, enum: [fingerprint, pin] }
        stepUp: { type: object }
        risk: { type: string, enum: [low, moderate, high] }
        signature: { type: string, description: 'ECDSA P-256 SHA-256, DER puis Base64' }
        signedPayload: { type: string, example: 'excelid:v1:{orderRef}:{userVisibleData}:APPROVE' }
        devicePublicKey: { type: string, description: 'X.509 SubjectPublicKeyInfo, Base64' }
        completionTime: { type: string, format: date-time }
        userNonVisibleData: { type: string, nullable: true }
    Enrollment:
      type: object
      properties:
        enrollmentId: { type: string, format: uuid }
        fullName: { type: string }
        personalNumber: { type: string }
        status: { type: string, enum: [pending, activated, expired] }
        createdAt: { type: string, format: date-time }
        expiresAt: { type: string, format: date-time }
        activatedAt: { type: string, format: date-time, nullable: true }
        deviceLabel: { type: string, nullable: true }
    IdCardVerification:
      type: object
      properties:
        verificationId: { type: string, format: uuid }
        user: { type: object }
        issuer: { type: object }
        identifiedAt: { type: string, format: date-time }
        verifiedAt: { type: string, format: date-time }
        proof: { type: object }
