Invoices

Une Invoice représente une facture électronique en cours de traitement. C'est l'objet central de l'API EFact.

Cycle de vie d'une facture

1
Pending
Créé
2
Signing
Signature
3
Transmitted
DGI
Accepted
Validé
Rejected
StatutDescription
pendingFacture créée, en attente de normalisation XML
signingXML UBL généré, signature XAdES en cours
transmittedFacture signée envoyée à la DGI
acceptedDGI a accepté la facture - dgi_id disponible
rejectedDGI a rejeté la facture - rejection_reason disponible

POST/v1/invoices

Crée une nouvelle Invoice et lance le pipeline de traitement de manière asynchrone.

Request

Request
POST /v1/invoices
Authorization: Bearer sk_live_xxx
Content-Type: application/json
Idempotency-Key: INV-2026-042   (optionnel)
Body
{
  "amount": 14400,
  "currency": "MAD",
  "customer": {
    "ice": "002345678901234",
    "name": "Société Exemple SARL",
    "address": "12 Rue Hassan II, Casablanca 20000",
    "email": "comptabilite@exemple.ma"
  },
  "lines": [
    {
      "description": "Développement application web",
      "quantity": 1,
      "unit_price": 12000,
      "tax_rate": 20
    },
    {
      "description": "Hébergement mensuel",
      "quantity": 1,
      "unit_price": 2000,
      "tax_rate": 20
    }
  ],
  "metadata": {
    "internal_id": "INV-2026-042",
    "project_ref": "PROJ-007"
  }
}

Champs de la requête

Racine

ChampTypeRequisDescription
amountintegerOuiMontant total TTC en centimes DH
currencystringOuiDevise - seul MAD est supporté en V0.1
customerobjectOuiInformations sur le client destinataire
linesarrayOuiLignes de facturation (minimum 1)
metadataobjectNonDonnées libres du client (clés/valeurs string)

customer

ChampTypeRequisDescription
icestringOuiIdentifiant Commun Entreprise (15 chiffres)
namestringOuiRaison sociale
addressstringNonAdresse complète
emailstringNonEmail pour envoi automatique du PDF signé

lines[]

ChampTypeRequisDescription
descriptionstringOuiDésignation de la prestation ou du produit
quantityintegerOuiQuantité (entier positif)
unit_priceintegerOuiPrix unitaire HT en centimes DH
tax_rateintegerNonTaux de TVA en % (0, 7, 10, 14, 20) - défaut : 20

Response 201 Created

Response
{
  "id": "inv_01H8XYZABC",
  "object": "invoice",
  "client_secret": "inv_01H8XYZABC_secret_b7e3f2a1c9d4",
  "status": "pending",
  "amount": 14400,
  "currency": "MAD",
  "customer": {
    "ice": "002345678901234",
    "name": "Société Exemple SARL"
  },
  "lines": [
    {
      "description": "Développement application web",
      "quantity": 1,
      "unit_price": 12000,
      "tax_rate": 20,
      "amount": 12000
    },
    {
      "description": "Hébergement mensuel",
      "quantity": 1,
      "unit_price": 2000,
      "tax_rate": 20,
      "amount": 2000
    }
  ],
  "metadata": {
    "internal_id": "INV-2026-042",
    "project_ref": "PROJ-007"
  },
  "xml_url": null,
  "pdf_url": null,
  "dgi_id": null,
  "rejection_reason": null,
  "created_at": "2026-04-07T10:00:00Z",
  "updated_at": "2026-04-07T10:00:00Z"
}

Erreurs spécifiques

Code HTTPCode erreurCause
400invalid_requestChamp ice absent ou mal formaté
400invalid_requestlines vide
422amount_too_smallamount inférieur à 100 centimes
422invalid_iceice ne correspond pas à un format valide (15 chiffres numériques)
409idempotency_conflictIdempotency-Key déjà utilisée avec un payload différent

GET/v1/invoices/:id

Récupère le statut et les détails d'une facture.

Request

Request
GET /v1/invoices/inv_01H8XYZABC
Authorization: Bearer sk_live_xxx

Alternative côté frontend - utiliser le client_secret :

Alternative Request
GET /v1/invoices/inv_01H8XYZABC
Authorization: Bearer inv_01H8XYZABC_secret_b7e3f2a1c9d4

Le client_secretdonne accès en lecture seule à cette unique facture - aucune donnée d'autres factures n'est accessible.

Response 200 OK

Une fois la facture acceptée :

Accepted Response
{
  "id": "inv_01H8XYZABC",
  "object": "invoice",
  "status": "accepted",
  "amount": 14400,
  "currency": "MAD",
  "customer": {
    "ice": "002345678901234",
    "name": "Société Exemple SARL"
  },
  "xml_url": "https://storage.googleapis.com/efact-invoices/tenant_id/2026/04/inv_01H8XYZABC.xml",
  "pdf_url": "https://storage.googleapis.com/efact-invoices/tenant_id/2026/04/inv_01H8XYZABC.pdf",
  "dgi_id": "DGI-2026-0042-XYZABC",
  "rejection_reason": null,
  "created_at": "2026-04-07T10:00:00Z",
  "updated_at": "2026-04-07T10:04:23Z"
}

Si la facture est rejected :

Rejected Response
{
  "status": "rejected",
  "dgi_id": null,
  "rejection_reason": "ICE client inconnu dans le registre DGI"
}

GET/v1/invoices

Liste les factures du tenant courant, avec pagination par curseur.

Request

Request
GET /v1/invoices?limit=20&status=accepted
Authorization: Bearer sk_live_xxx

Paramètres de filtre

ParamètreTypeDescription
statusstringFiltrer par statut : pending, signing, transmitted, accepted, rejected
limitintegerNombre de résultats (1-100, défaut : 20)
starting_afterstringCurseur de pagination (ID du dernier objet)
created_afterstringISO 8601 - factures créées après cette date
created_beforestringISO 8601 - factures créées avant cette date

Response 200 OK

Response
{
  "object": "list",
  "data": [
    { "id": "inv_01H...", "status": "accepted", ... },
    { "id": "inv_01G...", "status": "accepted", ... }
  ],
  "has_more": true,
  "next_cursor": "inv_01G..."
}

Accès temps réel via SSE

Pour suivre les transitions de statut sans polling, EFact expose un endpoint Server-Sent Events scopé au client_secret :

TypeScript
const eventSource = new EventSource(
  `https://api.efact.itzenata.com/v1/invoices/${invoiceId}/stream`,
  {
    headers: { Authorization: `Bearer ${clientSecret}` }
  }
)

eventSource.onmessage = (event) => {
  const data = JSON.parse(event.data)
  console.log('Nouveau statut :', data.status) // "signing", "transmitted", "accepted"
}

Le composant <InvoiceStatus /> gère cette connexion automatiquement.