openapi: 3.1.0
info:
  title: EFact API
  description: Compliance-as-a-service backend for Moroccan DGI electronic invoicing
  contact:
    name: Itzenata
    email: contact@itzenata.com
  license:
    name: Proprietary
    url: https://itzenata.com
  version: 1.0.0
servers:
- url: https://api.efact.ma
  description: Production
tags:
- name: Invoices
  description: "Create, read, update and cancel invoices. Authenticated via API key."
- name: Webhooks
  description: Manage outbound event notification endpoints. Authenticated via API
    key.
paths:
  /v1/invoices/{id}:
    get:
      tags:
      - Invoices
      summary: Get an invoice
      description: Returns the full invoice by ID. Returns 404 if the invoice does
        not exist or belongs to a different organization.
      operationId: getById
      parameters:
      - name: id
        in: path
        description: Invoice UUID
        required: true
        schema:
          type: string
          format: uuid
        example: 550e8400-e29b-41d4-a716-446655440000
      responses:
        "200":
          description: Invoice found
          content:
            '*/*':
              schema:
                $ref: "#/components/schemas/ApiResponseInvoiceResponse"
        "401":
          description: Missing or invalid API key
          content:
            '*/*':
              schema:
                $ref: "#/components/schemas/ApiResponseInvoiceResponse"
        "404":
          description: Invoice not found
          content:
            '*/*':
              schema:
                $ref: "#/components/schemas/ApiResponseInvoiceResponse"
      security:
      - ApiKeyAuth: []
    put:
      tags:
      - Invoices
      summary: Update an invoice
      description: Updates a DRAFT invoice. Once an invoice has been submitted to
        DGI it is immutable - update attempts on non-DRAFT invoices return 409.
      operationId: update
      parameters:
      - name: id
        in: path
        description: Invoice UUID
        required: true
        schema:
          type: string
          format: uuid
        example: 550e8400-e29b-41d4-a716-446655440000
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateInvoiceRequest"
        required: true
      responses:
        "200":
          description: Invoice updated
          content:
            '*/*':
              schema:
                $ref: "#/components/schemas/ApiResponseInvoiceResponse"
        "400":
          description: Validation error - invalid request body
          content:
            '*/*':
              schema:
                $ref: "#/components/schemas/ApiResponseInvoiceResponse"
        "401":
          description: Missing or invalid API key
          content:
            '*/*':
              schema:
                $ref: "#/components/schemas/ApiResponseInvoiceResponse"
        "404":
          description: Invoice not found
          content:
            '*/*':
              schema:
                $ref: "#/components/schemas/ApiResponseInvoiceResponse"
        "409":
          description: Invoice is not in DRAFT status and cannot be modified
          content:
            '*/*':
              schema:
                $ref: "#/components/schemas/ApiResponseInvoiceResponse"
      security:
      - ApiKeyAuth: []
    delete:
      tags:
      - Invoices
      summary: Cancel an invoice
      description: Cancels a DRAFT invoice. Submitted or accepted invoices cannot
        be cancelled.
      operationId: cancel
      parameters:
      - name: id
        in: path
        description: Invoice UUID
        required: true
        schema:
          type: string
          format: uuid
        example: 550e8400-e29b-41d4-a716-446655440000
      responses:
        "200":
          description: Invoice cancelled
          content:
            '*/*':
              schema:
                $ref: "#/components/schemas/ApiResponseInvoiceResponse"
        "401":
          description: Missing or invalid API key
          content:
            '*/*':
              schema:
                $ref: "#/components/schemas/ApiResponseInvoiceResponse"
        "404":
          description: Invoice not found
          content:
            '*/*':
              schema:
                $ref: "#/components/schemas/ApiResponseInvoiceResponse"
        "409":
          description: Invoice is not in DRAFT status and cannot be cancelled
          content:
            '*/*':
              schema:
                $ref: "#/components/schemas/ApiResponseInvoiceResponse"
      security:
      - ApiKeyAuth: []
  /v1/webhooks:
    get:
      tags:
      - Webhooks
      summary: List webhook endpoints
      description: Returns endpoints belonging to the organization associated with
        the API key. The signing secret is never included.
      operationId: list
      parameters:
      - name: page
        in: query
        description: Zero-based page index (0..N)
        required: false
        schema:
          type: integer
          default: 0
          minimum: 0
      - name: size
        in: query
        description: The size of the page to be returned
        required: false
        schema:
          type: integer
          default: 20
          minimum: 1
      - name: sort
        in: query
        description: "Sorting criteria in the format: property,(asc|desc). Default\
          \ sort order is ascending. Multiple sort criteria are supported."
        required: false
        schema:
          type: array
          default:
          - "createdAt,DESC"
          items:
            type: string
      responses:
        "200":
          description: OK
          content:
            '*/*':
              schema:
                $ref: "#/components/schemas/ApiResponsePageResponseWebhookEndpointResponse"
      security:
      - ApiKeyAuth: []
    post:
      tags:
      - Webhooks
      summary: Create a webhook endpoint
      description: Registers a URL to receive invoice lifecycle events. The signing
        secret (`data.secret`) is returned ONCE in this response and never again - store it to verify the `efact-webhook-signature` header on incoming deliveries.
        Max 5 active endpoints per organization.
      operationId: create
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WebhookEndpointRequest"
        required: true
      responses:
        "201":
          description: Endpoint created
          content:
            '*/*':
              schema:
                $ref: "#/components/schemas/ApiResponseCreateWebhookEndpointResponse"
        "401":
          description: Missing or invalid API key
          content:
            '*/*':
              schema:
                $ref: "#/components/schemas/ApiResponseCreateWebhookEndpointResponse"
        "409":
          description: Active endpoint limit reached
          content:
            '*/*':
              schema:
                $ref: "#/components/schemas/ApiResponseCreateWebhookEndpointResponse"
        "422":
          description: URL is invalid or not allowed
          content:
            '*/*':
              schema:
                $ref: "#/components/schemas/ApiResponseCreateWebhookEndpointResponse"
      security:
      - ApiKeyAuth: []
  /v1/webhooks/{id}/test:
    post:
      tags:
      - Webhooks
      summary: Send a test event
      description: "Synchronously sends a signed `test.ping` event to the endpoint's\
        \ URL and returns the HTTP outcome directly - use this to verify your receiver\
        \ (signature check, reachability) before relying on real invoice events."
      operationId: test
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        "200":
          description: OK
          content:
            '*/*':
              schema:
                $ref: "#/components/schemas/ApiResponseWebhookTestResult"
      security:
      - ApiKeyAuth: []
  /v1/webhooks/{id}/rotate-secret:
    post:
      tags:
      - Webhooks
      summary: Rotate the signing secret
      description: "Generates a new signing secret, returned ONCE. The previous secret\
        \ keeps signing deliveries for 24h (both signatures are sent in efact-webhook-signature)\
        \ so the new one can be deployed without dropping events."
      operationId: rotateSecret
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        "200":
          description: OK
          content:
            '*/*':
              schema:
                $ref: "#/components/schemas/ApiResponseCreateWebhookEndpointResponse"
      security:
      - ApiKeyAuth: []
  /v1/webhooks/deliveries/{deliveryId}/retry:
    post:
      tags:
      - Webhooks
      summary: Manually redeliver an event
      description: "Resets a delivery to PENDING with next_attempt_at=now, so the\
        \ dispatcher retries it on its next tick regardless of its previous status\
        \ (including EXHAUSTED)."
      operationId: retryDelivery
      parameters:
      - name: deliveryId
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        "200":
          description: OK
          content:
            '*/*':
              schema:
                $ref: "#/components/schemas/ApiResponseWebhookDeliveryResponse"
      security:
      - ApiKeyAuth: []
  /v1/invoices:
    get:
      tags:
      - Invoices
      summary: List invoices
      description: Returns a paginated list of invoices belonging to the organization
        associated with the API key. Optionally filter by status.
      operationId: list_1
      parameters:
      - name: status
        in: query
        description: Filter by invoice status
        required: false
        schema:
          type: string
          enum:
          - PENDING
          - SIGNED
          - TRANSMITTED
          - ACCEPTED
          - REJECTED
          - FAILED
          - CANCELLED
        example: ACCEPTED
      - name: page
        in: query
        description: Zero-based page index (0..N)
        required: false
        schema:
          type: integer
          default: 0
          minimum: 0
      - name: size
        in: query
        description: The size of the page to be returned
        required: false
        schema:
          type: integer
          default: 20
          minimum: 1
      - name: sort
        in: query
        description: "Sorting criteria in the format: property,(asc|desc). Default\
          \ sort order is ascending. Multiple sort criteria are supported."
        required: false
        schema:
          type: array
          default:
          - "createdAt,DESC"
          items:
            type: string
      responses:
        "200":
          description: Paginated invoice list
          content:
            '*/*':
              schema:
                $ref: "#/components/schemas/ApiResponsePageResponseInvoiceResponse"
        "401":
          description: Missing or invalid API key
          content:
            '*/*':
              schema:
                $ref: "#/components/schemas/ApiResponsePageResponseInvoiceResponse"
      security:
      - ApiKeyAuth: []
    post:
      tags:
      - Invoices
      summary: Create an invoice
      description: "Creates a new invoice in DRAFT status for the organization associated\
        \ with the API key. The invoice is queued for UBL 2.1 generation, XAdES-B-B\
        \ signing, and DGI clearance."
      operationId: create_1
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateInvoiceRequest"
        required: true
      responses:
        "201":
          description: Invoice created successfully
          content:
            '*/*':
              schema:
                $ref: "#/components/schemas/ApiResponseInvoiceResponse"
        "400":
          description: Validation error - invalid request body
          content:
            '*/*':
              schema:
                $ref: "#/components/schemas/ApiResponseInvoiceResponse"
        "401":
          description: Missing or invalid API key
          content:
            '*/*':
              schema:
                $ref: "#/components/schemas/ApiResponseInvoiceResponse"
      security:
      - ApiKeyAuth: []
  /v1/webhooks/{id}:
    get:
      tags:
      - Webhooks
      summary: Get a webhook endpoint
      description: Returns 404 if the endpoint does not exist or belongs to a different
        organization.
      operationId: getById_1
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        "200":
          description: OK
          content:
            '*/*':
              schema:
                $ref: "#/components/schemas/ApiResponseWebhookEndpointResponse"
      security:
      - ApiKeyAuth: []
    delete:
      tags:
      - Webhooks
      summary: Delete a webhook endpoint
      description: Soft-deletes the endpoint; queued deliveries are not sent.
      operationId: delete
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        "200":
          description: OK
          content:
            '*/*':
              schema:
                $ref: "#/components/schemas/ApiResponseVoid"
      security:
      - ApiKeyAuth: []
    patch:
      tags:
      - Webhooks
      summary: Update a webhook endpoint
      description: Updates url/description/events/active. Only provided fields are
        changed. Setting active=true on an auto-disabled endpoint clears the disable
        reason and resets its failure count.
      operationId: update_1
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WebhookEndpointUpdateRequest"
        required: true
      responses:
        "200":
          description: OK
          content:
            '*/*':
              schema:
                $ref: "#/components/schemas/ApiResponseWebhookEndpointResponse"
      security:
      - ApiKeyAuth: []
  /v1/webhooks/{id}/deliveries:
    get:
      tags:
      - Webhooks
      summary: List an endpoint's delivery log
      description: "Paginated delivery attempts (status, attempt count, last error)\
        \ for one endpoint."
      operationId: deliveries
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      - name: page
        in: query
        description: Zero-based page index (0..N)
        required: false
        schema:
          type: integer
          default: 0
          minimum: 0
      - name: size
        in: query
        description: The size of the page to be returned
        required: false
        schema:
          type: integer
          default: 20
          minimum: 1
      - name: sort
        in: query
        description: "Sorting criteria in the format: property,(asc|desc). Default\
          \ sort order is ascending. Multiple sort criteria are supported."
        required: false
        schema:
          type: array
          default:
          - "createdAt,DESC"
          items:
            type: string
      responses:
        "200":
          description: OK
          content:
            '*/*':
              schema:
                $ref: "#/components/schemas/ApiResponsePageResponseWebhookDeliveryResponse"
      security:
      - ApiKeyAuth: []
  /v1/invoices/{id}/xml:
    get:
      tags:
      - Invoices
      summary: Download the signed UBL XML
      description: Returns the signed UBL 2.1 XML file - the legally binding invoice
        under the DGI clearance model (XAdES-B-B signature embedded). Available once
        the invoice has been signed; before that the download returns 409.
      operationId: downloadSignedXml
      parameters:
      - name: id
        in: path
        description: Invoice UUID
        required: true
        schema:
          type: string
          format: uuid
        example: 550e8400-e29b-41d4-a716-446655440000
      responses:
        "200":
          description: Signed XML file
          content:
            application/xml:
              schema:
                type: string
                format: byte
        "401":
          description: Missing or invalid API key
          content:
            application/xml:
              schema:
                type: string
                format: byte
        "404":
          description: Invoice not found
          content:
            application/xml:
              schema:
                type: string
                format: byte
        "409":
          description: Invoice has no signed XML yet
          content:
            application/xml:
              schema:
                type: string
                format: byte
      security:
      - ApiKeyAuth: []
  /v1/invoices/{id}/status:
    get:
      tags:
      - Invoices
      summary: Get invoice status
      description: "Returns a lightweight status object for polling DGI clearance\
        \ progress. Includes DGI reference, QR payload, and any error details."
      operationId: getStatus
      parameters:
      - name: id
        in: path
        description: Invoice UUID
        required: true
        schema:
          type: string
          format: uuid
        example: 550e8400-e29b-41d4-a716-446655440000
      responses:
        "200":
          description: Status retrieved
          content:
            '*/*':
              schema:
                $ref: "#/components/schemas/ApiResponseInvoiceStatusResponse"
        "401":
          description: Missing or invalid API key
          content:
            '*/*':
              schema:
                $ref: "#/components/schemas/ApiResponseInvoiceStatusResponse"
        "404":
          description: Invoice not found
          content:
            '*/*':
              schema:
                $ref: "#/components/schemas/ApiResponseInvoiceStatusResponse"
      security:
      - ApiKeyAuth: []
  /v1/invoices/{id}/qr:
    get:
      tags:
      - Invoices
      summary: Download the DGI clearance QR code
      description: "Returns the DGI clearance QR code as a PNG image. Only available\
        \ for ACCEPTED invoices - the QR is assigned by the DGI at clearance; before\
        \ that the download returns 409. Note: until the DGI platform goes live, the\
        \ image is rendered by EFact from the DGI QR payload (verification URL); the\
        \ endpoint contract will not change when the official DGI image replaces it."
      operationId: downloadQrCode
      parameters:
      - name: id
        in: path
        description: Invoice UUID
        required: true
        schema:
          type: string
          format: uuid
        example: 550e8400-e29b-41d4-a716-446655440000
      responses:
        "200":
          description: QR code PNG
          content:
            image/png:
              schema:
                type: string
                format: byte
        "401":
          description: Missing or invalid API key
          content:
            image/png:
              schema:
                type: string
                format: byte
        "404":
          description: Invoice not found
          content:
            image/png:
              schema:
                type: string
                format: byte
        "409":
          description: Invoice is not ACCEPTED yet
          content:
            image/png:
              schema:
                type: string
                format: byte
      security:
      - ApiKeyAuth: []
  /v1/invoices/{id}/pdf:
    get:
      tags:
      - Invoices
      summary: Download the PDF representation
      description: Returns the human-readable PDF representation of the invoice. The
        legally binding document is the signed UBL XML; the PDF states this in its
        footer and carries the DGI reference and clearance QR code once the invoice
        is ACCEPTED. Invoices not yet cleared are watermarked "NON VALIDÉE PAR LA
        DGI".
      operationId: downloadPdf
      parameters:
      - name: id
        in: path
        description: Invoice UUID
        required: true
        schema:
          type: string
          format: uuid
        example: 550e8400-e29b-41d4-a716-446655440000
      responses:
        "200":
          description: Invoice PDF
          content:
            application/pdf:
              schema:
                type: string
                format: byte
        "401":
          description: Missing or invalid API key
          content:
            application/pdf:
              schema:
                type: string
                format: byte
        "404":
          description: Invoice not found
          content:
            application/pdf:
              schema:
                type: string
                format: byte
      security:
      - ApiKeyAuth: []
components:
  schemas:
    UpdateInvoiceRequest:
      type: object
      description: Request body to update an existing invoice. All fields are optional - only provided fields are applied.
      properties:
        invoiceNumber:
          type: string
          description: Updated invoice number
          example: INV-2026-001
          maxLength: 64
          minLength: 0
        status:
          type: string
          description: Updated invoice status
          enum:
          - PENDING
          - SIGNED
          - TRANSMITTED
          - ACCEPTED
          - REJECTED
          - FAILED
          - CANCELLED
          example: DRAFT
        buyerIce:
          type: string
          description: Buyer's ICE - 15-digit Moroccan business identifier
          example: "123456789012345"
          pattern: "\\d{15}"
        buyerName:
          type: string
          description: Buyer's legal company name
          example: Acme SARL
        buyerAddress:
          type: string
          description: Buyer's registered address
          example: "123 Rue Mohammed V, Casablanca"
        invoiceDate:
          type: string
          format: date
          description: Date the invoice was issued
          example: 2026-01-15
        dueDate:
          type: string
          format: date
          description: Payment due date
          example: 2026-02-15
        currency:
          type: string
          description: 3-letter ISO 4217 currency code
          example: MAD
          pattern: "[A-Z]{3}"
        paymentMethod:
          type: string
          description: Payment method
          enum:
          - BANK_TRANSFER
          - CASH
          - CHECK
          - CARD
          - OTHER
          example: BANK_TRANSFER
          pattern: BANK_TRANSFER|CASH|CHECK|CARD|OTHER
        dgiReference:
          type: string
          description: DGI clearance reference assigned after submission
          example: DGI-2026-00123
        errorCode:
          type: string
          description: Error code if processing failed
          example: INVALID_ICE
        errorMessage:
          type: string
          description: Human-readable error message if processing failed
          example: Buyer ICE does not match DGI records
        ublXmlPath:
          type: string
          description: Storage path of the generated UBL 2.1 XML
          example: invoices/INV-2026-001/invoice.xml
        signedXmlPath:
          type: string
          description: Storage path of the XAdES-B-B signed XML
          example: invoices/INV-2026-001/invoice-signed.xml
    ApiResponseInvoiceResponse:
      type: object
      properties:
        code:
          type: string
        message:
          type: string
        data:
          $ref: "#/components/schemas/InvoiceResponse"
        errors:
          type: array
          items:
            $ref: "#/components/schemas/FieldError"
    FieldError:
      type: object
      properties:
        field:
          type: string
        message:
          type: string
    InvoiceResponse:
      type: object
      description: Full invoice representation returned by the API
      properties:
        id:
          type: string
          format: uuid
          description: Unique invoice identifier
          example: 550e8400-e29b-41d4-a716-446655440000
        organizationId:
          type: string
          format: uuid
          description: Organization that owns this invoice
          example: 550e8400-e29b-41d4-a716-446655440001
        invoiceNumber:
          type: string
          description: Unique invoice number
          example: INV-2026-001
        status:
          type: string
          description: Current processing status of the invoice
          enum:
          - PENDING
          - SIGNED
          - TRANSMITTED
          - ACCEPTED
          - REJECTED
          - FAILED
          - CANCELLED
          example: DRAFT
        issuerIce:
          type: string
          description: Issuer's ICE - 15-digit Moroccan business identifier
          example: "123456789012345"
        issuerName:
          type: string
          description: Issuer's legal company name
          example: Itzenata SARL
        issuerIf:
          type: string
          description: Issuer's Identifiant Fiscal (IF) - 8 digits
          example: "12345678"
        issuerRc:
          type: string
          description: Issuer's Registre de Commerce (RC)
          example: RC-CAS-2020-001
        issuerPatente:
          type: string
          description: Issuer's Patente number
          example: PAT-12345
        issuerAddress:
          type: string
          description: Issuer's registered address
          example: "45 Rue Hassan II, Rabat"
        buyerIce:
          type: string
          description: Buyer's ICE - 15-digit Moroccan business identifier
          example: "987654321098765"
        buyerName:
          type: string
          description: Buyer's legal company name
          example: Acme SARL
        buyerAddress:
          type: string
          description: Buyer's registered address
          example: "123 Rue Mohammed V, Casablanca"
        currency:
          type: string
          description: 3-letter ISO 4217 currency code
          example: MAD
        paymentMethod:
          type: string
          description: Payment method
          example: BANK_TRANSFER
        amountHt:
          type: number
          description: Total amount before tax (HT) in the invoice currency
          example: 1500.0
        amountVat:
          type: number
          description: Total VAT amount in the invoice currency
          example: 300.0
        amountTtc:
          type: number
          description: Total amount including tax (TTC) in the invoice currency
          example: 1800.0
        invoiceDate:
          type: string
          format: date
          description: Date the invoice was issued
          example: 2026-01-15
        dueDate:
          type: string
          format: date
          description: Payment due date
          example: 2026-02-15
        dgiReference:
          type: string
          description: DGI clearance reference assigned after successful submission
          example: DGI-2026-00123
        dgiQrCode:
          type: string
          description: QR code image URL assigned by DGI after clearance
        dgiQrPayload:
          type: string
          description: Raw QR code payload content assigned by DGI after clearance
        errorCode:
          type: string
          description: Error code if processing failed
          example: INVALID_ICE
        errorMessage:
          type: string
          description: Human-readable error message if processing failed
        dgiSubmittedAt:
          type: string
          format: date-time
          description: Timestamp when the invoice was submitted to DGI
        dgiValidatedAt:
          type: string
          format: date-time
          description: Timestamp when DGI validated (cleared) the invoice
        submittedAt:
          type: string
          format: date-time
          description: Timestamp when the invoice was submitted for processing
        acceptedAt:
          type: string
          format: date-time
          description: Timestamp when the invoice was accepted
        rejectedAt:
          type: string
          format: date-time
          description: Timestamp when the invoice was rejected
        createdAt:
          type: string
          format: date-time
          description: Timestamp when the invoice was created
        updatedAt:
          type: string
          format: date-time
          description: Timestamp of the last update
    WebhookEndpointRequest:
      type: object
      properties:
        url:
          type: string
          maxLength: 2048
          minLength: 0
        description:
          type: string
          maxLength: 255
          minLength: 0
        events:
          type: array
          items:
            type: string
        active:
          type: boolean
      required:
      - url
    ApiResponseCreateWebhookEndpointResponse:
      type: object
      properties:
        code:
          type: string
        message:
          type: string
        data:
          $ref: "#/components/schemas/CreateWebhookEndpointResponse"
        errors:
          type: array
          items:
            $ref: "#/components/schemas/FieldError"
    CreateWebhookEndpointResponse:
      type: object
      properties:
        id:
          type: string
          format: uuid
        organizationId:
          type: string
          format: uuid
        url:
          type: string
        description:
          type: string
        events:
          type: array
          items:
            type: string
        active:
          type: boolean
        secret:
          type: string
        createdAt:
          type: string
          format: date-time
        previousSecretExpiresAt:
          type: string
          format: date-time
    ApiResponseWebhookTestResult:
      type: object
      properties:
        code:
          type: string
        message:
          type: string
        data:
          $ref: "#/components/schemas/WebhookTestResult"
        errors:
          type: array
          items:
            $ref: "#/components/schemas/FieldError"
    WebhookTestResult:
      type: object
      properties:
        success:
          type: boolean
        responseStatus:
          type: integer
          format: int32
        message:
          type: string
    ApiResponseWebhookDeliveryResponse:
      type: object
      properties:
        code:
          type: string
        message:
          type: string
        data:
          $ref: "#/components/schemas/WebhookDeliveryResponse"
        errors:
          type: array
          items:
            $ref: "#/components/schemas/FieldError"
    WebhookDeliveryResponse:
      type: object
      properties:
        id:
          type: string
          format: uuid
        webhookEndpointId:
          type: string
          format: uuid
        eventType:
          type: string
        invoiceId:
          type: string
          format: uuid
        status:
          type: string
          enum:
          - PENDING
          - RETRYING
          - DELIVERED
          - EXHAUSTED
        attemptCount:
          type: integer
          format: int32
        nextAttemptAt:
          type: string
          format: date-time
        lastAttemptAt:
          type: string
          format: date-time
        lastResponseStatus:
          type: integer
          format: int32
        lastError:
          type: string
        deliveredAt:
          type: string
          format: date-time
        createdAt:
          type: string
          format: date-time
    CreateInvoiceLineRequest:
      type: object
      description: A single line item on an invoice
      properties:
        lineNumber:
          type: integer
          format: int32
          description: "Line number (1-based, must be unique within the invoice)"
          example: 1
        description:
          type: string
          description: Description of the product or service
          example: Consulting services - January 2026
          maxLength: 512
          minLength: 0
        quantity:
          type: number
          description: Quantity of units
          example: 10
        unit:
          type: string
          description: Unit of measure
          example: HOUR
          maxLength: 32
          minLength: 0
        unitPrice:
          type: number
          description: "Price per unit before tax (HT), in the invoice currency"
          example: 150.0
        vatRate:
          type: number
          description: VAT rate as a decimal (e.g. 0.20 for 20%)
          example: 0.2
      required:
      - description
      - lineNumber
      - quantity
      - unitPrice
      - vatRate
    CreateInvoiceRequest:
      type: object
      description: Request body to create a new invoice
      properties:
        invoiceNumber:
          type: string
          description: Unique invoice number
          example: INV-2026-001
          maxLength: 64
          minLength: 0
        buyerIce:
          type: string
          description: Buyer's ICE - 15-digit Moroccan business identifier
          example: "123456789012345"
          pattern: "\\d{15}"
        buyerName:
          type: string
          description: Buyer's legal company name
          example: Acme SARL
        buyerAddress:
          type: string
          description: Buyer's registered address
          example: "123 Rue Mohammed V, Casablanca"
        invoiceDate:
          type: string
          format: date
          description: Date the invoice was issued
          example: 2026-01-15
        dueDate:
          type: string
          format: date
          description: Payment due date
          example: 2026-02-15
        currency:
          type: string
          description: 3-letter ISO 4217 currency code
          example: MAD
          minLength: 1
          pattern: "[A-Z]{3}"
        paymentMethod:
          type: string
          description: Payment method
          enum:
          - BANK_TRANSFER
          - CASH
          - CHECK
          - CARD
          - OTHER
          example: BANK_TRANSFER
          minLength: 1
          pattern: BANK_TRANSFER|CASH|CHECK|CARD|OTHER
        lines:
          type: array
          description: Invoice line items (at least one required)
          items:
            $ref: "#/components/schemas/CreateInvoiceLineRequest"
          minItems: 1
      required:
      - currency
      - invoiceDate
      - invoiceNumber
      - lines
      - paymentMethod
    WebhookEndpointUpdateRequest:
      type: object
      properties:
        url:
          type: string
          maxLength: 2048
          minLength: 0
        description:
          type: string
          maxLength: 255
          minLength: 0
        events:
          type: array
          items:
            type: string
        active:
          type: boolean
    ApiResponseWebhookEndpointResponse:
      type: object
      properties:
        code:
          type: string
        message:
          type: string
        data:
          $ref: "#/components/schemas/WebhookEndpointResponse"
        errors:
          type: array
          items:
            $ref: "#/components/schemas/FieldError"
    WebhookEndpointResponse:
      type: object
      properties:
        id:
          type: string
          format: uuid
        organizationId:
          type: string
          format: uuid
        url:
          type: string
        description:
          type: string
        events:
          type: array
          items:
            type: string
        active:
          type: boolean
        disabledReason:
          type: string
        lastSuccessAt:
          type: string
          format: date-time
        lastFailureAt:
          type: string
          format: date-time
        consecutiveFailures:
          type: integer
          format: int32
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
        previousSecretExpiresAt:
          type: string
          format: date-time
    ApiResponsePageResponseWebhookEndpointResponse:
      type: object
      properties:
        code:
          type: string
        message:
          type: string
        data:
          $ref: "#/components/schemas/PageResponseWebhookEndpointResponse"
        errors:
          type: array
          items:
            $ref: "#/components/schemas/FieldError"
    PageResponseWebhookEndpointResponse:
      type: object
      properties:
        content:
          type: array
          items:
            $ref: "#/components/schemas/WebhookEndpointResponse"
        page:
          type: integer
          format: int32
        size:
          type: integer
          format: int32
        totalElements:
          type: integer
          format: int64
        totalPages:
          type: integer
          format: int32
        first:
          type: boolean
        last:
          type: boolean
    ApiResponsePageResponseWebhookDeliveryResponse:
      type: object
      properties:
        code:
          type: string
        message:
          type: string
        data:
          $ref: "#/components/schemas/PageResponseWebhookDeliveryResponse"
        errors:
          type: array
          items:
            $ref: "#/components/schemas/FieldError"
    PageResponseWebhookDeliveryResponse:
      type: object
      properties:
        content:
          type: array
          items:
            $ref: "#/components/schemas/WebhookDeliveryResponse"
        page:
          type: integer
          format: int32
        size:
          type: integer
          format: int32
        totalElements:
          type: integer
          format: int64
        totalPages:
          type: integer
          format: int32
        first:
          type: boolean
        last:
          type: boolean
    ApiResponsePageResponseInvoiceResponse:
      type: object
      properties:
        code:
          type: string
        message:
          type: string
        data:
          $ref: "#/components/schemas/PageResponseInvoiceResponse"
        errors:
          type: array
          items:
            $ref: "#/components/schemas/FieldError"
    PageResponseInvoiceResponse:
      type: object
      properties:
        content:
          type: array
          items:
            $ref: "#/components/schemas/InvoiceResponse"
        page:
          type: integer
          format: int32
        size:
          type: integer
          format: int32
        totalElements:
          type: integer
          format: int64
        totalPages:
          type: integer
          format: int32
        first:
          type: boolean
        last:
          type: boolean
    ApiResponseInvoiceStatusResponse:
      type: object
      properties:
        code:
          type: string
        message:
          type: string
        data:
          $ref: "#/components/schemas/InvoiceStatusResponse"
        errors:
          type: array
          items:
            $ref: "#/components/schemas/FieldError"
    InvoiceStatusResponse:
      type: object
      description: Lightweight status response for polling invoice processing state
      properties:
        id:
          type: string
          format: uuid
          description: Unique invoice identifier
          example: 550e8400-e29b-41d4-a716-446655440000
        status:
          type: string
          description: Current processing status
          enum:
          - PENDING
          - SIGNED
          - TRANSMITTED
          - ACCEPTED
          - REJECTED
          - FAILED
          - CANCELLED
          example: ACCEPTED
        dgiReference:
          type: string
          description: DGI clearance reference assigned after successful submission
          example: DGI-2026-00123
        dgiQrPayload:
          type: string
          description: Raw QR code payload content assigned by DGI after clearance
        dgiErrorCode:
          type: string
          description: DGI error code if submission was rejected
          example: ICE_MISMATCH
        dgiErrorMessage:
          type: string
          description: DGI human-readable error description if submission was rejected
        dgiSubmittedAt:
          type: string
          format: date-time
          description: Timestamp when the invoice was submitted to DGI
        dgiValidatedAt:
          type: string
          format: date-time
          description: Timestamp when DGI validated (cleared) the invoice
        dgiRejectedAt:
          type: string
          format: date-time
          description: Timestamp when DGI rejected the invoice
    ApiResponseVoid:
      type: object
      properties:
        code:
          type: string
        message:
          type: string
        data: {}
        errors:
          type: array
          items:
            $ref: "#/components/schemas/FieldError"
  securitySchemes:
    ApiKeyAuth:
      type: http
      description: "EFact API key - format: `efact_sk_...`. Pass it as: Authorization:\
        \ Bearer efact_sk_..."
      scheme: bearer
