openapi: 3.1.0
info:
  title: AGORASIGN API
  version: 1.0.7
  description: >-
    API V1 AGORASIGN non qualifiée : workflow complet, scellement PDF,
    certificat de preuve, horodatage cryptographique local, conservation,
    remise enregistrée, notifications documentaires et webhooks HTTPS signés.
    Les providers de confiance restent remplaçables. Aucune revendication de
    qualification eIDAS, de certificat qualifié ou de QSCD.
servers:
  - url: /api/v1
components:
  securitySchemes:
    ApiBearer:
      type: http
      scheme: bearer
      description: Credential machine-to-machine AGORASIGN.
    SignerSession:
      type: apiKey
      in: header
      name: Authorization
      description: Valeur "Signer <jeton-session>".
  schemas:
    Problem:
      type: object
      properties:
        type: { type: string }
        title: { type: string }
        status: { type: integer }
        detail: { type: string }
        code: { type: string }
    CreateRequest:
      type: object
      required: [externalId, title, signingMode]
      properties:
        externalId: { type: string, maxLength: 255 }
        title: { type: string, maxLength: 500 }
        signingMode:
          type: string
          enum: [SEQUENTIAL, PARALLEL]
        expiresAt: { type: string, format: date-time }
        metadata: { type: object, additionalProperties: true }
        notificationSettings:
          type: object
          properties:
            issuerName: { type: string, maxLength: 255 }
            notifySigners: { type: boolean, default: true }
            finalRecipients:
              type: array
              maxItems: 20
              items: { type: string, format: email }
    AddDocument:
      type: object
      required: [externalId, title, fileName, mimeType, contentBase64]
      properties:
        externalId: { type: string, maxLength: 255 }
        title: { type: string, maxLength: 500 }
        fileName: { type: string }
        mimeType: { type: string, const: application/pdf }
        contentBase64:
          type: string
          contentEncoding: base64
    AddSigner:
      type: object
      required: [externalId, displayName, email, phone, signingOrder]
      properties:
        externalId: { type: string }
        displayName: { type: string }
        email: { type: string, format: email }
        phone:
          type: string
          description: Numéro international E.164.
        signingOrder: { type: integer, minimum: 1 }
    RequestSummary:
      type: object
      required:
        - id
        - externalId
        - title
        - status
        - signingMode
        - documentCount
        - signerCount
        - signedCount
        - qualificationStatus
      properties:
        id: { type: string, format: uuid }
        externalId: { type: string }
        title: { type: string }
        status:
          type: string
          enum: [DRAFT, SENT, IN_PROGRESS, COMPLETED, REFUSED, EXPIRED, CANCELLED, FAILED]
        signingMode: { type: string }
        expiresAt: { type: string, format: date-time }
        createdAt: { type: string, format: date-time }
        sentAt: { type: string, format: date-time }
        completedAt: { type: string, format: date-time }
        documentCount: { type: integer }
        signerCount: { type: integer }
        signedCount: { type: integer }
        qualificationStatus:
          type: string
          const: NON_QUALIFIED
    CreateClientApplication:
      type: object
      required: [name, clientType, externalId]
      properties:
        name: { type: string, maxLength: 255 }
        clientType:
          type: string
          enum: [AGORAFACT, AGORAGED, AGORASIGN_APP, AGORAIN, THIRD_PARTY]
        externalId: { type: string, maxLength: 255 }
    CreateCredential:
      type: object
      required: [scopes]
      properties:
        scopes:
          type: array
          minItems: 1
          uniqueItems: true
          items: { type: string }
        expiresAt: { type: string, format: date-time }
    VerifyEvidence:
      type: object
      required: [packageBase64]
      properties:
        packageBase64:
          type: string
          contentEncoding: base64
  parameters:
    RequestId:
      in: path
      name: requestId
      required: true
      schema: { type: string, format: uuid }
    InvitationToken:
      in: path
      name: token
      required: true
      schema: { type: string, minLength: 32, maxLength: 256 }
paths:
  /system/status:
    get:
      operationId: getSystemStatus
      summary: État non sensible du service
      responses:
        "200":
          description: État AGORASIGN
  /signature-requests:
    get:
      operationId: listSignatureRequests
      security: [{ ApiBearer: [] }]
      responses:
        "200": { description: Cent dernières demandes du client }
    post:
      operationId: createSignatureRequest
      security: [{ ApiBearer: [] }]
      parameters:
        - in: header
          name: Idempotency-Key
          required: true
          schema: { type: string, minLength: 16, maxLength: 255 }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/CreateRequest" }
      responses:
        "201":
          description: Demande créée
          content:
            application/json:
              schema: { $ref: "#/components/schemas/RequestSummary" }
        "409":
          description: Conflit d’idempotence
  /signature-requests/{requestId}:
    get:
      operationId: getSignatureRequest
      security: [{ ApiBearer: [] }]
      parameters:
        - $ref: "#/components/parameters/RequestId"
      responses:
        "200":
          description: État de la demande
          content:
            application/json:
              schema: { $ref: "#/components/schemas/RequestSummary" }
  /signature-requests/{requestId}/documents:
    post:
      operationId: addSignatureRequestDocument
      security: [{ ApiBearer: [] }]
      parameters:
        - $ref: "#/components/parameters/RequestId"
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/AddDocument" }
      responses:
        "201": { description: Document PDF figé et empreinte calculée }
  /signature-requests/{requestId}/signers:
    post:
      operationId: addSignatureRequestSigner
      security: [{ ApiBearer: [] }]
      parameters:
        - $ref: "#/components/parameters/RequestId"
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/AddSigner" }
      responses:
        "201": { description: Signataire ajouté }
    get:
      operationId: listSignatureRequestSigners
      security: [{ ApiBearer: [] }]
      parameters:
        - $ref: "#/components/parameters/RequestId"
      responses:
        "200": { description: Statut horodaté de chaque signataire }
  /signature-requests/{requestId}/send:
    post:
      operationId: sendSignatureRequest
      security: [{ ApiBearer: [] }]
      parameters:
        - $ref: "#/components/parameters/RequestId"
      responses:
        "200":
          description: Invitations émises
          content:
            application/json:
              schema: { $ref: "#/components/schemas/RequestSummary" }
  /signature-requests/{requestId}/original-document:
    get:
      operationId: downloadOriginalDocument
      security: [{ ApiBearer: [] }]
      parameters: [{ $ref: "#/components/parameters/RequestId" }]
      responses:
        "200":
          description: Document original figé
          content:
            application/pdf: {}
  /signature-requests/{requestId}/artifacts:
    get:
      operationId: getSignatureRequestArtifacts
      security: [{ ApiBearer: [] }]
      parameters: [{ $ref: "#/components/parameters/RequestId" }]
      responses:
        "200": { description: Empreintes, tailles et liens de restitution }
  /signature-requests/{requestId}/signers/{signerId}/receipt:
    get:
      operationId: downloadSignerOperationReceipt
      security: [{ ApiBearer: [] }]
      parameters:
        - $ref: "#/components/parameters/RequestId"
        - in: path
          name: signerId
          required: true
          schema: { type: string, format: uuid }
      responses:
        "200":
          description: Justificatif individuel de l’opération de signature
          content:
            application/pdf: {}
  /signature-requests/{requestId}/events:
    get:
      operationId: getSignatureRequestEvents
      security: [{ ApiBearer: [] }]
      parameters:
        - $ref: "#/components/parameters/RequestId"
      responses:
        "200": { description: Événements chaînés }
  /signature-requests/{requestId}/final-document:
    get:
      operationId: downloadFinalDocument
      security: [{ ApiBearer: [] }]
      parameters:
        - $ref: "#/components/parameters/RequestId"
      responses:
        "200":
          description: Document final
          content:
            application/pdf: {}
  /signature-requests/{requestId}/evidence:
    get:
      operationId: downloadEvidencePackage
      security: [{ ApiBearer: [] }]
      parameters:
        - $ref: "#/components/parameters/RequestId"
      responses:
        "200":
          description: Dossier de preuve ZIP
          content:
            application/zip: {}
  /signing/invitations/{token}:
    get:
      operationId: openSignerInvitation
      parameters:
        - $ref: "#/components/parameters/InvitationToken"
      responses:
        "200": { description: Invitation valide et document identifié }
  /signing/invitations/{token}/otp:
    post:
      operationId: issueSignerOtp
      parameters:
        - $ref: "#/components/parameters/InvitationToken"
      responses:
        "200": { description: OTP émis sans valeur OTP dans la réponse }
        "429": { description: Limite atteinte }
  /signing/invitations/{token}/authenticate:
    post:
      operationId: authenticateSigner
      parameters:
        - $ref: "#/components/parameters/InvitationToken"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [otp]
              properties:
                otp: { type: string, pattern: "^[0-9]{6}$" }
      responses:
        "200": { description: Session signataire créée }
        "401": { description: OTP invalide ou expiré }
  /signing/session/context:
    get:
      operationId: getSignerContext
      security: [{ SignerSession: [] }]
      responses:
        "200": { description: Document et texte exact de consentement }
  /signing/session/document:
    get:
      operationId: viewSignerDocument
      security: [{ SignerSession: [] }]
      responses:
        "200":
          description: PDF présenté et événement de consultation
          content:
            application/pdf: {}
  /signing/session/consent:
    post:
      operationId: consentToElectronicSignature
      security: [{ SignerSession: [] }]
      responses:
        "200": { description: Consentement exact enregistré }
  /signing/session/sign:
    post:
      operationId: signExplicitly
      security: [{ SignerSession: [] }]
      responses:
        "200":
          description: Action explicite scellée et demande éventuellement finalisée
  /verification/evidence:
    post:
      operationId: verifyEvidencePackage
      security: [{ ApiBearer: [] }]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/VerifyEvidence" }
      responses:
        "200": { description: Résultat de vérification indépendante }

  /signature-requests/{requestId}/cancel:
    post:
      operationId: cancelSignatureRequest
      security: [{ ApiBearer: [] }]
      parameters: [{ $ref: "#/components/parameters/RequestId" }]
      responses:
        "200": { description: Demande annulée et accès révoqués }
  /signature-requests/{requestId}/remind:
    post:
      operationId: remindSignatureRequest
      security: [{ ApiBearer: [] }]
      parameters: [{ $ref: "#/components/parameters/RequestId" }]
      responses:
        "200": { description: Nouvelle invitation locale émise }
  /signature-requests/{requestId}/evidence-certificate:
    get:
      operationId: downloadHumanEvidenceCertificate
      security: [{ ApiBearer: [] }]
      parameters: [{ $ref: "#/components/parameters/RequestId" }]
      responses:
        "200":
          description: Certificat de preuve lisible
          content:
            application/pdf: {}
  /signature-requests/{requestId}/trust-services:
    get:
      operationId: getTrustServicesStatus
      security: [{ ApiBearer: [] }]
      parameters: [{ $ref: "#/components/parameters/RequestId" }]
      responses:
        "200": { description: Horodatages, conservation et remises enregistrées }
  /signing/session/refuse:
    post:
      operationId: refuseSignature
      security: [{ SignerSession: [] }]
      responses:
        "200": { description: Refus enregistré et demande arrêtée }
  /client-applications:
    get:
      operationId: listClientApplications
      security: [{ ApiBearer: [] }]
      responses:
        "200": { description: Applications clientes de l’organisation }
    post:
      operationId: createClientApplication
      security: [{ ApiBearer: [] }]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/CreateClientApplication" }
      responses:
        "201": { description: Application cliente créée }
  /client-applications/{applicationId}/credentials:
    get:
      operationId: listClientCredentials
      security: [{ ApiBearer: [] }]
      responses:
        "200": { description: Credentials sans secret ni hash }
    post:
      operationId: createClientCredential
      security: [{ ApiBearer: [] }]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/CreateCredential" }
      responses:
        "201": { description: Secret retourné une seule fois }
  /client-applications/{applicationId}/credentials/{credentialId}/rotate:
    post:
      operationId: rotateClientCredential
      security: [{ ApiBearer: [] }]
      responses:
        "200": { description: Ancien credential révoqué et nouveau secret retourné une fois }
  /client-applications/{applicationId}/credentials/{credentialId}:
    delete:
      operationId: revokeClientCredential
      security: [{ ApiBearer: [] }]
      responses:
        "204": { description: Credential révoqué }
  /webhooks:
    get:
      operationId: listWebhooks
      security: [{ ApiBearer: [] }]
      responses:
        "200": { description: Abonnements sans secrets }
    post:
      operationId: createWebhook
      security: [{ ApiBearer: [] }]
      responses:
        "201": { description: Abonnement créé; secret retourné une seule fois }
  /webhooks/{webhookId}:
    delete:
      operationId: revokeWebhook
      security: [{ ApiBearer: [] }]
      parameters:
        - in: path
          name: webhookId
          required: true
          schema: { type: string, format: uuid }
      responses:
        "204": { description: Abonnement révoqué }
  /webhooks/{webhookId}/deliveries:
    get:
      operationId: listWebhookDeliveries
      security: [{ ApiBearer: [] }]
      parameters:
        - in: path
          name: webhookId
          required: true
          schema: { type: string, format: uuid }
      responses:
        "200": { description: Historique idempotent des tentatives }
