Python SDK: coming soon officially. This documentation describes the API as it will be available; official release is being finalized.
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
Morocco E-Invoicing Python SDK
efactOfficial EFact SDK for Python ≥ 3.9, typed with Pydantic v2. All invoice operations are exposed under efact.invoices.*.
In short
The official EFact Python SDK (efact) creates and tracks DGI-compliant electronic invoices in Python. Once published on PyPI, install via pip install efact, initialize with your secret API key efact_sk_ loaded from the environment, and issue invoices: EFact manages UBL 2.1 XML generation, XAdES signatures, and DGI transmission server-side.
Installation
pip install efactInitialization
Basic
import os
from efact import EFact
# The secret key identifies AND authenticates your organization.
# Never commit it: load it from the environment.
efact = EFact(os.environ["EFACT_SECRET_KEY"])efact.invoices.create(data)
Signature
efact.invoices.create(data: CreateInvoiceRequest | dict) -> InvoiceResponseComplete Example
invoice = efact.invoices.create({
"invoiceNumber": "INV-2026-001",
"invoiceDate": "2026-06-24",
"currency": "MAD",
"paymentMethod": "BANK_TRANSFER",
"buyerIce": "001234567890123",
"buyerName": "Client SARL",
"buyerAddress": "Casablanca",
"lines": [
{
"lineNumber": 1,
"description": "Consulting services",
"quantity": 1,
"unitPrice": 1000,
"vatRate": 20,
},
],
})
print(invoice.id, invoice.status)efact.invoices.retrieve(invoice_id)
invoice = efact.invoices.retrieve("550e8400-e29b-41d4-a716-446655440000")
print(invoice.status, invoice.invoice_number)Throws EFactNotFoundError (HTTP 404) if the invoice does not exist or does not belong to the tenant.
efact.invoices.retrieve_status(invoice_id)
status = efact.invoices.retrieve_status("550e8400-e29b-41d4-a716-446655440000")
print(status.status, status.dgi_reference)efact.invoices.list(...)
page = efact.invoices.list(page=0, size=20, status="ACCEPTED")
for inv in page.content:
print(inv.invoice_number, inv.status)Pagination uses page/size parameters rather than cursor pagination.
efact.invoices.update(invoice_id, data)
# Only for an invoice at PENDING status - otherwise 409
updated = efact.invoices.update("550e8400-e29b-41d4-a716-446655440000", {
"dueDate": "2026-07-31",
"buyerAddress": "New address, Casablanca",
})efact.invoices.cancel(invoice_id)
# Only for an invoice at PENDING status
cancelled = efact.invoices.cancel("550e8400-e29b-41d4-a716-446655440000")
print(cancelled.status) # 'CANCELLED'Error handling
The SDK throws typed errors, all inheriting from EFactError. Always wrap calls in a try/except block:
from efact import (
EFactApiError,
EFactAuthError,
EFactValidationError,
EFactNotFoundError,
EFactConflictError,
EFactConnectionError,
EFactTimeoutError,
)
try:
efact.invoices.create(data)
except EFactValidationError as e:
print("Invalid fields:", e.errors)
except EFactAuthError:
print("API key missing or invalid.")
except EFactConflictError:
print("Invoice not editable (status ≠ PENDING).")
except EFactApiError as e:
print(e.code, e.http_status, e)
except EFactTimeoutError:
print("Timed out.")
except EFactConnectionError:
print("Network failure.")Error hierarchy
| Class | HTTP code | When |
|---|---|---|
EFactConfigError | - | Configuration error (before any network call) |
EFactAuthError | 401 / 403 | API key missing or invalid |
EFactNotFoundError | 404 | Resource not found (including cross-tenant requests) |
EFactValidationError | 400 / 422 | Invalid request payload |
EFactConflictError | 409 | E.g. modifying or cancelling an invoice that is no longer PENDING |
EFactApiError | 5xx | Server-side API error |
EFactTimeoutError | - | Request timed out before response was received |
EFactConnectionError | - | Network failure |
Idempotent requests (GET, PUT, DELETE) are retried automatically with exponential back-off on timeout, network failure, or HTTP 408/425/429/500/502/503/504 statuses, up to max_retries times. POST (creation) is never retried automatically.
Django Integration
# views.py
import os
from django.http import JsonResponse
from django.views.decorators.http import require_POST
from efact import EFact
efact = EFact(os.environ["EFACT_SECRET_KEY"])
@require_POST
def create_invoice(request):
import json
body = json.loads(request.body)
invoice = efact.invoices.create({
"invoiceNumber": body["invoiceNumber"],
"invoiceDate": body["invoiceDate"],
"currency": "MAD",
"paymentMethod": "BANK_TRANSFER",
"buyerIce": body.get("buyerIce"),
"buyerName": body.get("buyerName"),
"buyerAddress": body.get("buyerAddress"),
"lines": body["lines"],
})
return JsonResponse({"id": invoice.id, "status": invoice.status})Invoice statuses
The status reflects the complete lifecycle, aligned with the backend:
| Status | Meaning |
|---|---|
PENDING | Created, editable, not yet transmitted |
SIGNED | Signed (XAdES), awaiting transmission |
TRANSMITTED | Submitted to the DGI platform, awaiting clearance |
ACCEPTED | Validated by the DGI - reference and QR code available |
REJECTED | Rejected by the DGI - inspect errorCode / errorMessage |
FAILED | Technical processing failure |
CANCELLED | Cancelled (only possible while PENDING) |
Frequently asked questions
How do I install the EFact Python SDK?
The efact package is currently being published to PyPI; once available, install it with pip install efact and import the client (from efact import EFact). Initialize it with your secret key loaded from the environment, then create and track invoices via its methods. The SDK manages UBL 2.1 generation, signing, and transmission via the EFact server.
Is the EFact Python SDK compatible with web frameworks?
Yes. The Python SDK works in any application, including web frameworks such as Django or FastAPI: instantiate the client at startup, load the API key from an environment variable, and call its methods from views or background tasks to create and track invoices.
How do I track an invoice status with the Python SDK?
After creation, query the invoice status using the client's dedicated method with a polling interval of 2 to 5 seconds. Once the status is ACCEPTED, you can retrieve the DGI reference and artifacts. In production, webhooks replace polling for final status notifications.