{
  "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
          },
          "amountVat": {
            "type": "number",
            "description": "Total VAT amount in the invoice currency",
            "example": 300
          },
          "amountTtc": {
            "type": "number",
            "description": "Total amount including tax (TTC) in the invoice currency",
            "example": 1800
          },
          "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
          },
          "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"
      }
    }
  }
}
