Morocco E-Invoicing REST API: Overview
The EFact API is a versioned REST API. All responses return in JSON format, wrapped in a consistent envelope.
In short
The EFact API is a REST JSON API accessible from any programming language. An invoice follows the lifecycle PENDING, NORMALIZING, SIGNING, SUBMITTED, and finishes as ACCEPTED, REJECTED, or FAILED. Errors return standard HTTP status codes with a structured JSON payload.
Base URL
| Environment | URL |
|---|---|
| Production | https://api.efact.ma |
Versioning
The API is versioned via URL path prefix. The current version is v1.
/v1/invoicesOpenAPI Specification
The complete API contract for public endpoints (invoices and webhooks) is available in OpenAPI 3.1 format. Import it into Postman, Insomnia, or Bruno to explore the API, or generate client SDKs in your language using openapi-generator. Official SDKs are generated from this exact specification.
Authentication
All requests must include your secret API key in the Authorization header:
Authorization: Bearer efact_sk_xxxThe key resolves your organization server-side: no organization ID is ever passed in request bodies. See Authentication for complete details.
Request format
- Content-Type:
application/json - Encoding: UTF-8
- Amounts: decimal numbers in the invoice currency, e.g.
150.00= 150.00 MAD. Totals (HT, VAT, TTC) are calculated server-side from line items. - Invoice dates:
YYYY-MM-DDformat (e.g.2026-04-07); returned timestamps are in ISO 8601 UTC. - IDs: UUIDs
Response format
Every response, whether success or error, uses the same envelope: { code, message, data, errors }. Official SDKs automatically unwrap data and throw typed exceptions on errors.
Success
{
"code": "OK",
"message": "Invoice created",
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"invoiceNumber": "INV-2026-042",
"status": "SIGNED",
"currency": "MAD",
"amountHt": 12000.00,
"amountVat": 2400.00,
"amountTtc": 14400.00,
"createdAt": "2026-04-07T10:00:00Z"
},
"errors": null
}Error
{
"code": "VALIDATION_ERROR",
"message": "Validation failed",
"data": null,
"errors": [
{
"field": "buyerIce",
"message": "buyerIce must be exactly 15 digits"
}
]
}HTTP status codes
| Code | Meaning |
|---|---|
200 OK | Successful request (GET, PUT, DELETE) |
201 Created | Resource created successfully (POST) |
400 Bad Request | Invalid payload or missing field (detailed per-field in errors) |
401 Unauthorized | Missing, invalid, or revoked API key |
404 Not Found | Resource not found (or not belonging to your organization) |
409 Conflict | State conflict: invoice number already used, or modification of an already signed invoice |
429 Too Many Requests | Rate limit exceeded - inspect the Retry-After header |
500 Internal Server Error | Server-side error - contact support |
Business error codes
The code field in the response envelope provides a machine-readable error code. Key codes:
| Code | HTTP | Description |
|---|---|---|
API_KEY_MISSING | 401 | Authorization header missing or empty |
API_KEY_INVALID | 401 | API key unrecognized or revoked |
API_KEY_RATE_LIMITED | 429 | Exceeded 300 requests/minute limit |
VALIDATION_ERROR | 400 | Missing field or invalid format (details in errors[]) |
INVOICE_NOT_FOUND | 404 | Invoice not found or outside organization scope |
INVOICE_NUMBER_EXISTS | 409 | Invoice number already used in your organization |
INVOICE_NOT_EDITABLE | 409 | Attempted edit/cancel on an invoice that is no longer PENDING |
MISSING_LEGAL_MENTIONS | 409 | Organization legal details incomplete (ICE, IF, RC...) - complete them in the dashboard before billing |
Idempotency
Idempotency on creation relies on the invoiceNumber, which is unique per organization:
// invoiceNumber is unique per organization: replaying the same
// creation returns 409 INVOICE_NUMBER_EXISTS instead of creating a duplicate.
POST /v1/invoices
{ "invoiceNumber": "INV-2026-042", ... }If uncertain following a network timeout, replay the request: either it succeeds or you receive 409 INVOICE_NUMBER_EXISTS - meaning the invoice exists and can be queried. SDKs never replay a POST automatically.
Rate limiting
| Scope | Limit |
|---|---|
| Per secret API key | 300 requests / minute |
Beyond that, the API returns 429 with a Retry-After header (in seconds). Official SDKs handle this automatically using exponential back-off.
Pagination
List endpoints use zero-based page pagination:
GET /v1/invoices?status=ACCEPTED&page=0&size=20&sort=createdAt,descParameters
| Parameter | Type | Description |
|---|---|---|
page | integer | Page index, zero-based (default: 0) |
size | integer | Results per page (default: 20) |
sort | string | property,asc|desc (default: createdAt,desc) |
status | string | Filter by status (PENDING, ACCEPTED...) |
Response
{
"code": "OK",
"message": "Invoices retrieved",
"data": {
"content": [ ... ],
"page": 0,
"size": 20,
"totalElements": 42,
"totalPages": 3,
"first": true,
"last": false
},
"errors": null
}Available endpoints
| Method | Endpoint | Description |
|---|---|---|
POST | /v1/invoices | Create an invoice (normalized + signed on the fly) |
GET | /v1/invoices | List organization invoices |
GET | /v1/invoices/:id | Retrieve an invoice |
GET | /v1/invoices/:id/status | DGI clearance status (lightweight, designed for polling) |
PUT | /v1/invoices/:id | Update a PENDING invoice |
DELETE | /v1/invoices/:id | Cancel a PENDING invoice |
GET | /v1/invoices/:id/pdf | Download invoice PDF |
GET | /v1/invoices/:id/xml | Download signed UBL 2.1 XML (legal original) |
GET | /v1/invoices/:id/qr | Download DGI clearance QR code (PNG) |
POST | /v1/webhooks | Create a webhook endpoint |
GET | /v1/webhooks | List webhook endpoints |
GET | /v1/webhooks/:id | Retrieve a webhook endpoint |
PATCH | /v1/webhooks/:id | Update a webhook endpoint |
DELETE | /v1/webhooks/:id | Delete a webhook endpoint |
POST | /v1/webhooks/:id/test | Send a test ping event |
POST | /v1/webhooks/:id/rotate-secret | Rotate signing secret |
GET | /v1/webhooks/:id/deliveries | Delivery log |
POST | /v1/webhooks/deliveries/:deliveryId/retry | Retry a delivery |
Frequently asked questions
What are the possible statuses of an EFact invoice?
An invoice follows the lifecycle PENDING, NORMALIZING, SIGNING, SUBMITTED, and then a final status of ACCEPTED, REJECTED, or FAILED. ACCEPTED means the invoice received its DGI validation (visa fiscal); REJECTED indicates a DGI refusal with a reason; FAILED indicates a technical error. Every transition is recorded in the audit trail.
Is the EFact API compatible with all programming languages?
Yes. The API is a REST JSON API callable from any programming language capable of sending HTTP requests. The official Node.js / TypeScript SDK (@itzenata/efact-node) simplifies integration - Python, Java, PHP, and .NET SDKs are in development - but the SDK is optional: you can call HTTP endpoints directly.
How are errors handled by the EFact API?
The API returns standard HTTP status codes and a structured JSON body with an error code and message. Validation errors return 400, authentication errors return 401, locked invoices return 409, and rate limits return 429. DGI rejections are distinct and reflected in the invoice's REJECTED status.