Create E-Invoice API: UBL 2.1 & XAdES Reference

An Invoice represents an electronic invoice in processing. It is the central resource of the EFact API.

In short

To create an invoice, post its data as JSON (number, date, currency, buyer ICE, lines): EFact calculates totals, generates UBL 2.1 XML, applies the XAdES signature, and submits to the DGI. Once signed or transmitted, the invoice is locked; modifications return a 409 error and adjustments require a credit note.

DGI simulator: the national DGI e-invoicing platform is not yet open to integrations. Clearance (ACCEPTED / REJECTED status, DGI reference, QR code) currently runs on our simulator, which reproduces the official flow. Moving to the official platform requires no change on the client side. Learn more

Invoice lifecycle

1
Pending
Created
2
Signed
XAdES signed
3
Transmitted
DGI
Accepted
Validated by DGI
Rejected
Rejected by DGI

UBL 2.1 normalization and XAdES signing run synchronously during creation: the invoice returned by POST is already SIGNED. Only DGI transmission is asynchronous.

StatusDescription
PENDINGInvoice created, editable, not yet signed
SIGNEDUBL 2.1 XML generated and XAdES signed - content locked
TRANSMITTEDSigned invoice submitted to DGI, awaiting clearance
ACCEPTEDDGI accepted the invoice - dgiReference and QR code available
REJECTEDDGI rejected the invoice - inspect dgiErrorCode / dgiErrorMessage
FAILEDTechnical processing or transmission failure
CANCELLEDCancelled by caller (only possible while PENDING)

POST/v1/invoices

Creates a new Invoice, generates UBL 2.1 XML and signs it synchronously, then dispatches DGI transmission in the background.

Request

Request
POST /v1/invoices
Authorization: Bearer efact_sk_xxx
Content-Type: application/json
Body
{
  "invoiceNumber": "INV-2026-042",
  "invoiceDate": "2026-04-07",
  "dueDate": "2026-05-07",
  "currency": "MAD",
  "paymentMethod": "BANK_TRANSFER",
  "buyerIce": "002345678901234",
  "buyerName": "Example Client SARL",
  "buyerAddress": "12 Hassan II Ave, Casablanca 20000",
  "lines": [
    {
      "lineNumber": 1,
      "description": "Web application development",
      "quantity": 1,
      "unitPrice": 12000.00,
      "vatRate": 20
    },
    {
      "lineNumber": 2,
      "description": "Monthly hosting",
      "quantity": 1,
      "unitPrice": 2000.00,
      "vatRate": 20
    }
  ]
}

Request fields

Root

FieldTypeRequiredDescription
invoiceNumberstringYesInvoice number, unique per organization (max 64 characters)
invoiceDatestringYesIssue date, format YYYY-MM-DD
currencystringYesISO 4217 code (3 uppercase letters), e.g. MAD
paymentMethodstringYesBANK_TRANSFER, CASH, CHECK, CARD, or OTHER
linesarrayYesInvoice line items (minimum 1)
buyerIcestringNoBuyer ICE - exactly 15 digits if provided
buyerNamestringNoBuyer legal business name
buyerAddressstringNoBuyer address
dueDatestringNoDue date, format YYYY-MM-DD

lines[]

FieldTypeRequiredDescription
lineNumberintegerYesLine number (1-based, unique in invoice)
descriptionstringYesProduct or service description (max 512 characters)
quantitynumberYesQuantity (positive, decimals supported)
unitPricenumberYesUnit price excl. VAT in invoice currency (≥ 0)
vatRatenumberYesVAT rate as percentage: 20 for 20% (0, 7, 10, 14, 20)
unitstringNoUnit of measure string, e.g. HOUR (max 32 characters)

Totals (amountHt, amountVat, amountTtc) are calculated server-side from line items - they are never sent in the request.

Response 201 Created

Response
{
  "code": "OK",
  "message": "Invoice created",
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "organizationId": "550e8400-e29b-41d4-a716-446655440001",
    "invoiceNumber": "INV-2026-042",
    "status": "SIGNED",
    "issuerIce": "123456789012345",
    "issuerName": "Your Company SARL",
    "buyerIce": "002345678901234",
    "buyerName": "Example Client SARL",
    "buyerAddress": "12 Hassan II Ave, Casablanca 20000",
    "currency": "MAD",
    "paymentMethod": "BANK_TRANSFER",
    "amountHt": 14000.00,
    "amountVat": 2800.00,
    "amountTtc": 16800.00,
    "invoiceDate": "2026-04-07",
    "dueDate": "2026-05-07",
    "dgiReference": null,
    "dgiQrCode": null,
    "createdAt": "2026-04-07T10:00:00Z",
    "updatedAt": "2026-04-07T10:00:00Z"
  },
  "errors": null
}

Specific errors

HTTP codeError codeCause
400VALIDATION_ERRORMissing or malformed field (buyerIce ≠ 15 digits, empty lines...) - field details in errors[]
409INVOICE_NUMBER_EXISTSinvoiceNumber already used in your organization
409MISSING_LEGAL_MENTIONSOrganization legal details incomplete (ICE, IF, RC...) - complete them in the dashboard

GET/v1/invoices/:id

Retrieves complete invoice details.

Request

Request
GET /v1/invoices/550e8400-e29b-41d4-a716-446655440000
Authorization: Bearer efact_sk_xxx

Response 200 OK

Once the invoice is cleared:

Accepted Response
{
  "code": "OK",
  "message": "Invoice found",
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "invoiceNumber": "INV-2026-042",
    "status": "ACCEPTED",
    "buyerIce": "002345678901234",
    "buyerName": "Example Client SARL",
    "currency": "MAD",
    "amountTtc": 16800.00,
    "dgiReference": "DGI-2026-00042",
    "dgiQrCode": "...",
    "dgiQrPayload": "...",
    "dgiSubmittedAt": "2026-04-07T10:00:05Z",
    "dgiValidatedAt": "2026-04-07T10:04:23Z",
    "createdAt": "2026-04-07T10:00:00Z",
    "updatedAt": "2026-04-07T10:04:23Z"
  },
  "errors": null
}

Returns 404 INVOICE_NOT_FOUND if the invoice does not exist or belongs to another organization.

GET/v1/invoices/:id/status

Lightweight status object, designed for polling DGI clearance.

Request

Request
GET /v1/invoices/550e8400-e29b-41d4-a716-446655440000/status
Authorization: Bearer efact_sk_xxx

Response 200 OK

Accepted Status
{
  "code": "OK",
  "message": "Status retrieved",
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "ACCEPTED",
    "dgiReference": "DGI-2026-00042",
    "dgiQrPayload": "...",
    "dgiErrorCode": null,
    "dgiErrorMessage": null,
    "dgiSubmittedAt": "2026-04-07T10:00:05Z",
    "dgiValidatedAt": "2026-04-07T10:04:23Z",
    "dgiRejectedAt": null
  },
  "errors": null
}

If the invoice is REJECTED:

Rejected Status
{
  "code": "OK",
  "message": "Status retrieved",
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "REJECTED",
    "dgiReference": null,
    "dgiErrorCode": "ICE_MISMATCH",
    "dgiErrorMessage": "Customer ICE not found in DGI registry",
    "dgiRejectedAt": "2026-04-07T10:04:23Z"
  },
  "errors": null
}

Poll this endpoint until reaching a terminal status (ACCEPTED, REJECTED, or FAILED). An interval of a few seconds is recommended; the SDK handles back-off on rate limits.

GET/v1/invoices

List invoices for your organization, with page-based pagination.

Request

Request
GET /v1/invoices?status=ACCEPTED&page=0&size=20&sort=createdAt,desc
Authorization: Bearer efact_sk_xxx

Parameters

ParameterTypeDescription
statusstringFilter by status: PENDING, SIGNED, TRANSMITTED, ACCEPTED, REJECTED, FAILED, CANCELLED
pageintegerPage index, zero-based (default: 0)
sizeintegerResults per page (default: 20)
sortstringproperty,asc|desc (default: createdAt,desc)

Response 200 OK

Response
{
  "code": "OK",
  "message": "Invoices retrieved",
  "data": {
    "content": [
      { "id": "550e8400-...", "invoiceNumber": "INV-2026-042", "status": "ACCEPTED", ... },
      { "id": "660f9511-...", "invoiceNumber": "INV-2026-041", "status": "ACCEPTED", ... }
    ],
    "page": 0,
    "size": 20,
    "totalElements": 42,
    "totalPages": 3,
    "first": true,
    "last": false
  },
  "errors": null
}

PUT/v1/invoices/:id

Updates a PENDING invoice. All fields are optional - only provided fields are updated.

Request

Request
PUT /v1/invoices/550e8400-e29b-41d4-a716-446655440000
Authorization: Bearer efact_sk_xxx
Content-Type: application/json

{
  "dueDate": "2026-06-30",
  "buyerAddress": "New address, Casablanca"
}

Once signed or transmitted, an invoice is immutable: 409 INVOICE_NOT_EDITABLE on any modification attempt.

DELETE/v1/invoices/:id

Cancels a PENDING invoice (status CANCELLED). A signed or transmitted invoice can no longer be cancelled (409).

Request

Request
DELETE /v1/invoices/550e8400-e29b-41d4-a716-446655440000
Authorization: Bearer efact_sk_xxx

Downloadable artifacts

Every processed invoice exposes its tax artifacts for direct download:

Artifacts
GET /v1/invoices/{id}/pdf   # Invoice PDF (application/pdf)
GET /v1/invoices/{id}/xml   # Signed UBL 2.1 XML - legal original (application/xml)
GET /v1/invoices/{id}/qr    # DGI clearance QR code (image/png)

Signed XML is the legal original document; all artifacts are archived for the statutory 10-year period.

Frequently asked questions

How do I create an electronic invoice with the EFact API?

Send a POST request to the creation endpoint with JSON data: invoice number, date, currency, buyer ICE, and invoice line items. EFact recalculates totals, generates UBL 2.1 XML, applies the XAdES signature, and submits it to the DGI. The response contains the invoice ID and initial status.

Can an invoice be modified after creation?

An invoice can be modified as long as it has not been signed or transmitted. Once signed or submitted to the DGI, its content is locked to guarantee tax integrity: any modification attempt returns a 409 error. To correct a transmitted invoice, you must issue a credit note or corrective invoice.

How do I cancel an invoice with EFact?

An invoice can be cancelled via the cancellation endpoint as long as its status permits it. The cancellation is logged in the audit trail. An invoice already accepted by the DGI cannot be deleted: adjustments require a credit note, in accordance with Moroccan tax rules.