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

EnvironmentURL
Productionhttps://api.efact.ma

Versioning

The API is versioned via URL path prefix. The current version is v1.

Examples
/v1/invoices

OpenAPI 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:

Secret Key Auth
Authorization: Bearer efact_sk_xxx

The 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-DD format (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

Success Response
{
  "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

Error Response
{
  "code": "VALIDATION_ERROR",
  "message": "Validation failed",
  "data": null,
  "errors": [
    {
      "field": "buyerIce",
      "message": "buyerIce must be exactly 15 digits"
    }
  ]
}

HTTP status codes

CodeMeaning
200 OKSuccessful request (GET, PUT, DELETE)
201 CreatedResource created successfully (POST)
400 Bad RequestInvalid payload or missing field (detailed per-field in errors)
401 UnauthorizedMissing, invalid, or revoked API key
404 Not FoundResource not found (or not belonging to your organization)
409 ConflictState conflict: invoice number already used, or modification of an already signed invoice
429 Too Many RequestsRate limit exceeded - inspect the Retry-After header
500 Internal Server ErrorServer-side error - contact support

Business error codes

The code field in the response envelope provides a machine-readable error code. Key codes:

CodeHTTPDescription
API_KEY_MISSING401Authorization header missing or empty
API_KEY_INVALID401API key unrecognized or revoked
API_KEY_RATE_LIMITED429Exceeded 300 requests/minute limit
VALIDATION_ERROR400Missing field or invalid format (details in errors[])
INVOICE_NOT_FOUND404Invoice not found or outside organization scope
INVOICE_NUMBER_EXISTS409Invoice number already used in your organization
INVOICE_NOT_EDITABLE409Attempted edit/cancel on an invoice that is no longer PENDING
MISSING_LEGAL_MENTIONS409Organization 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:

Idempotency
// 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

ScopeLimit
Per secret API key300 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:

Pagination Request
GET /v1/invoices?status=ACCEPTED&page=0&size=20&sort=createdAt,desc

Parameters

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

Response

Pagination Response
{
  "code": "OK",
  "message": "Invoices retrieved",
  "data": {
    "content": [ ... ],
    "page": 0,
    "size": 20,
    "totalElements": 42,
    "totalPages": 3,
    "first": true,
    "last": false
  },
  "errors": null
}

Available endpoints

MethodEndpointDescription
POST/v1/invoicesCreate an invoice (normalized + signed on the fly)
GET/v1/invoicesList organization invoices
GET/v1/invoices/:idRetrieve an invoice
GET/v1/invoices/:id/statusDGI clearance status (lightweight, designed for polling)
PUT/v1/invoices/:idUpdate a PENDING invoice
DELETE/v1/invoices/:idCancel a PENDING invoice
GET/v1/invoices/:id/pdfDownload invoice PDF
GET/v1/invoices/:id/xmlDownload signed UBL 2.1 XML (legal original)
GET/v1/invoices/:id/qrDownload DGI clearance QR code (PNG)
POST/v1/webhooksCreate a webhook endpoint
GET/v1/webhooksList webhook endpoints
GET/v1/webhooks/:idRetrieve a webhook endpoint
PATCH/v1/webhooks/:idUpdate a webhook endpoint
DELETE/v1/webhooks/:idDelete a webhook endpoint
POST/v1/webhooks/:id/testSend a test ping event
POST/v1/webhooks/:id/rotate-secretRotate signing secret
GET/v1/webhooks/:id/deliveriesDelivery log
POST/v1/webhooks/deliveries/:deliveryId/retryRetry 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.