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
UBL 2.1 normalization and XAdES signing run synchronously during creation: the invoice returned by POST is already SIGNED. Only DGI transmission is asynchronous.
| Status | Description |
|---|---|
PENDING | Invoice created, editable, not yet signed |
SIGNED | UBL 2.1 XML generated and XAdES signed - content locked |
TRANSMITTED | Signed invoice submitted to DGI, awaiting clearance |
ACCEPTED | DGI accepted the invoice - dgiReference and QR code available |
REJECTED | DGI rejected the invoice - inspect dgiErrorCode / dgiErrorMessage |
FAILED | Technical processing or transmission failure |
CANCELLED | Cancelled 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
POST /v1/invoices
Authorization: Bearer efact_sk_xxx
Content-Type: application/json{
"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
| Field | Type | Required | Description |
|---|---|---|---|
invoiceNumber | string | Yes | Invoice number, unique per organization (max 64 characters) |
invoiceDate | string | Yes | Issue date, format YYYY-MM-DD |
currency | string | Yes | ISO 4217 code (3 uppercase letters), e.g. MAD |
paymentMethod | string | Yes | BANK_TRANSFER, CASH, CHECK, CARD, or OTHER |
lines | array | Yes | Invoice line items (minimum 1) |
buyerIce | string | No | Buyer ICE - exactly 15 digits if provided |
buyerName | string | No | Buyer legal business name |
buyerAddress | string | No | Buyer address |
dueDate | string | No | Due date, format YYYY-MM-DD |
lines[]
| Field | Type | Required | Description |
|---|---|---|---|
lineNumber | integer | Yes | Line number (1-based, unique in invoice) |
description | string | Yes | Product or service description (max 512 characters) |
quantity | number | Yes | Quantity (positive, decimals supported) |
unitPrice | number | Yes | Unit price excl. VAT in invoice currency (≥ 0) |
vatRate | number | Yes | VAT rate as percentage: 20 for 20% (0, 7, 10, 14, 20) |
unit | string | No | Unit 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
{
"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 code | Error code | Cause |
|---|---|---|
400 | VALIDATION_ERROR | Missing or malformed field (buyerIce ≠ 15 digits, empty lines...) - field details in errors[] |
409 | INVOICE_NUMBER_EXISTS | invoiceNumber already used in your organization |
409 | MISSING_LEGAL_MENTIONS | Organization legal details incomplete (ICE, IF, RC...) - complete them in the dashboard |
GET/v1/invoices/:id
Retrieves complete invoice details.
Request
GET /v1/invoices/550e8400-e29b-41d4-a716-446655440000
Authorization: Bearer efact_sk_xxxResponse 200 OK
Once the invoice is cleared:
{
"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
GET /v1/invoices/550e8400-e29b-41d4-a716-446655440000/status
Authorization: Bearer efact_sk_xxxResponse 200 OK
{
"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:
{
"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
GET /v1/invoices?status=ACCEPTED&page=0&size=20&sort=createdAt,desc
Authorization: Bearer efact_sk_xxxParameters
| Parameter | Type | Description |
|---|---|---|
status | string | Filter by status: PENDING, SIGNED, TRANSMITTED, ACCEPTED, REJECTED, FAILED, CANCELLED |
page | integer | Page index, zero-based (default: 0) |
size | integer | Results per page (default: 20) |
sort | string | property,asc|desc (default: createdAt,desc) |
Response 200 OK
{
"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
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
DELETE /v1/invoices/550e8400-e29b-41d4-a716-446655440000
Authorization: Bearer efact_sk_xxxDownloadable artifacts
Every processed invoice exposes its tax artifacts for direct download:
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.