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 Node.js SDK (TypeScript)

Published version of @itzenata/efact-node

Official EFact SDK for Node.js. Works in plain JavaScript (ESM or CommonJS) as well as native TypeScript - types are bundled for both. All invoice operations are exposed under efact.invoices.*.

In short

The official EFact Node.js SDK (@itzenata/efact-node), written in native TypeScript, creates and tracks DGI-compliant electronic invoices in Node.js. Install via npm, initialize with your secret API key efact_sk_, and call efact.invoices.*: EFact handles UBL 2.1 XML generation, XAdES signatures, and DGI transmission server-side.

Installation

npm install @itzenata/efact-node

Initialization

Basic

import { EFact } from '@itzenata/efact-node'

// The secret key identifies AND authenticates your organization.
// Never commit it: load it from the environment.
const efact = new EFact(process.env.EFACT_SECRET_KEY!)

efact.invoices.create(input)

Signature

Method Signature
efact.invoices.create(input: CreateInvoiceRequest): Promise<InvoiceResponse>

Parameters

Parameters
interface CreateInvoiceLineRequest {
  lineNumber: number     // Required, positive, unique within the invoice
  description: string    // Required, <= 512 characters
  quantity: number       // Required, positive
  unitPrice: number      // Required, >= 0
  vatRate: number        // Required, >= 0 (percentage)
  unit?: string          // Optional, <= 32 characters, unit of measure (e.g. "HOUR")
}

interface CreateInvoiceRequest {
  invoiceNumber: string                // Required, <= 64 characters
  invoiceDate: string                  // Required, format YYYY-MM-DD
  currency: string                     // Required, ISO 3 uppercase letters (e.g. "MAD")
  paymentMethod: CreateInvoiceRequestPaymentMethodEnum
                                       // Required: 'BANK_TRANSFER' | 'CASH' | 'CHECK' | 'CARD' | 'OTHER'
  buyerIce?: string                    // Optional, exactly 15 digits if provided
  buyerName?: string                   // Optional, buyer legal entity name
  buyerAddress?: string                // Optional, buyer address
  dueDate?: string                     // Optional, format YYYY-MM-DD
  lines: CreateInvoiceLineRequest[]    // Non-empty
}

Complete Example

Complete Example
import { EFact, CreateInvoiceRequestPaymentMethodEnum } from '@itzenata/efact-node'

const efact = new EFact(process.env.EFACT_SECRET_KEY!)

const invoice = await efact.invoices.create({
  invoiceNumber: 'INV-2026-043',
  invoiceDate: '2026-06-19',
  currency: 'MAD',
  paymentMethod: CreateInvoiceRequestPaymentMethodEnum.BankTransfer,
  buyerIce: '002345678901234',
  buyerName: 'Client SARL',
  buyerAddress: '12 Allal Ben Abdellah St, Rabat',
  lines: [
    {
      lineNumber: 1,
      description: 'Annual software license',
      quantity: 2,
      unitPrice: 100,
      vatRate: 20,
    },
    {
      lineNumber: 2,
      description: 'Training (VAT exempt)',
      quantity: 1,
      unitPrice: 40,
      vatRate: 0,
    },
  ],
})

console.log(invoice.id)          // internal identifier
console.log(invoice.status)      // 'SIGNED' (normalized and signed upon creation)
console.log(invoice.amountTtc)   // total amount incl. VAT, calculated server-side

efact.invoices.retrieve(invoiceId)

Retrieve Invoice
const invoice = await efact.invoices.retrieve('550e8400-e29b-41d4-a716-446655440000')

if (invoice.status === InvoiceResponseStatusEnum.Accepted) {
  console.log('DGI reference:', invoice.dgiReference)
  console.log('DGI QR code:', invoice.dgiQrCode)
}

if (invoice.status === InvoiceResponseStatusEnum.Rejected) {
  console.log('Error code:', invoice.errorCode)
  console.log('Message:', invoice.errorMessage)
}

Throws EFactNotFoundError (HTTP 404) if the invoice does not exist or belongs to another organization.

efact.invoices.retrieveStatus(invoiceId)

Retrieve Status
const result = await efact.invoices.retrieveStatus('550e8400-e29b-41d4-a716-446655440000')

console.log(result.status)           // InvoiceStatusResponseStatusEnum
console.log(result.dgiReference)     // DGI clearance reference, if available
console.log(result.dgiQrPayload)     // QR code payload, if available
console.log(result.dgiErrorCode)     // DGI error code, if rejected
console.log(result.dgiErrorMessage)  // DGI error message, if rejected
console.log(result.dgiSubmittedAt)   // DGI submission timestamp
console.log(result.dgiValidatedAt)   // DGI validation timestamp

efact.invoices.list(params?)

List Invoices
import { ListStatusEnum } from '@itzenata/efact-node'

const page = await efact.invoices.list({
  status: ListStatusEnum.Accepted,
  page: 0,
  size: 50,
  sort: 'createdAt,desc',
})

for (const invoice of page.content) {
  console.log(invoice.id, invoice.invoiceNumber)
}

Pagination uses page/size parameters rather than cursor pagination. The response provides content: InvoiceResponse[] alongside backend pagination metadata. Filter by status using ListStatusEnum.

efact.invoices.update(invoiceId, input)

Update Invoice
// Only for an invoice at PENDING status - otherwise 409 (INVOICE_NOT_EDITABLE)
// All fields are optional - only provided fields are applied.
const updated = await efact.invoices.update('550e8400-e29b-41d4-a716-446655440000', {
  dueDate: '2026-07-31',
  buyerAddress: 'New address, Casablanca',
})

efact.invoices.cancel(invoiceId)

Cancel Invoice
// Only for an invoice at PENDING status
const cancelled = await efact.invoices.cancel('550e8400-e29b-41d4-a716-446655440000')
console.log(cancelled.status) // 'CANCELLED'

Error handling

The SDK throws typed errors, all inheriting from EFactError. Always wrap calls in a try/catch block:

Error Handling
import {
  EFact,
  EFactError,
  EFactConfigError,
  EFactConnectionError,
  EFactTimeoutError,
  EFactApiError,
  EFactAuthError,
  EFactValidationError,
  EFactNotFoundError,
  EFactConflictError,
} from '@itzenata/efact-node'

try {
  const invoice = await efact.invoices.create({ /* ... */ })
} catch (error) {
  if (error instanceof EFactAuthError) {
    // API key missing or invalid - HTTP 401/403
    console.error('Authentication:', error.message)
  } else if (error instanceof EFactValidationError) {
    // Invalid payload - HTTP 400/422
    console.error('Validation:', error.code, error.errors)
  } else if (error instanceof EFactNotFoundError) {
    // Invoice not found or cross-tenant - HTTP 404
    console.error('Not found:', error.message)
  } else if (error instanceof EFactConflictError) {
    // E.g. modifying an invoice that is no longer PENDING - HTTP 409
    console.error('Conflict:', error.message)
  } else if (error instanceof EFactTimeoutError) {
    console.error('Timeout after', error.timeoutMs, 'ms')
  } else if (error instanceof EFactConnectionError) {
    console.error('Network unavailable:', error.cause)
  } else if (error instanceof EFactApiError) {
    // Any other server-side API error (5xx, etc.)
    console.error('API error:', error.code, error.httpStatus)
  } else {
    throw error
  }
}

Error hierarchy

ClassHTTP codeWhen
EFactConfigError-Missing API key or invalid configuration option
EFactAuthError401 / 403Missing or invalid API key
EFactNotFoundError404Resource not found (including cross-tenant requests)
EFactValidationError400 / 422Invalid request payload
EFactConflictError409Conflict, e.g. modifying or cancelling an invoice that is no longer PENDING
EFactApiError5xxUnclassified server-side API error
EFactTimeoutError-Request timed out before response was received
EFactConnectionError-Network failure (DNS lookup, connection refused, TLS handshake)

Idempotent requests (GET, HEAD, PUT, DELETE) are retried automatically with exponential back-off and jitter on timeout, network failure, or HTTP 408/425/429/500/502/503/504 statuses, up to maxRetries times. POST (creation) is never retried automatically.

Next.js App Router Integration

app/api/invoices/route.ts
// app/api/invoices/route.ts
import { EFact, CreateInvoiceRequestPaymentMethodEnum } from '@itzenata/efact-node'
import { NextRequest, NextResponse } from 'next/server'

const efact = new EFact(process.env.EFACT_SECRET_KEY!)

export async function POST(req: NextRequest) {
  const body = await req.json()

  const invoice = await efact.invoices.create({
    invoiceNumber: body.invoiceNumber,
    invoiceDate: body.invoiceDate,
    currency: 'MAD',
    paymentMethod: CreateInvoiceRequestPaymentMethodEnum.BankTransfer,
    buyerIce: body.buyerIce,
    buyerName: body.buyerName,
    buyerAddress: body.buyerAddress,
    lines: body.lines,
  })

  return NextResponse.json({ id: invoice.id, status: invoice.status })
}

Invoice statuses

The status reflects the complete lifecycle, aligned with the backend:

StatusMeaning
PENDINGCreated, editable, not yet transmitted
SIGNEDSigned (XAdES), awaiting transmission
TRANSMITTEDSubmitted to the DGI platform, awaiting clearance
ACCEPTEDValidated by the DGI - reference and QR code available
REJECTEDRejected by the DGI - inspect errorCode / errorMessage
FAILEDTechnical processing failure
CANCELLEDCancelled (only possible while PENDING)

Frequently asked questions

Does the EFact Node.js SDK work in plain JavaScript without TypeScript?

Yes. The @itzenata/efact-node SDK works identically in plain JavaScript: examples remain valid by removing type annotations. Both module systems are supported, ESM (import) and CommonJS (require). Even in JavaScript, your editor benefits from autocomplete via embedded TypeScript definitions.

How do I install the EFact Node.js SDK?

Install the official @itzenata/efact-node package from npm (npm install @itzenata/efact-node, or pnpm/yarn). The SDK is written in native TypeScript and ships bundled types, but works seamlessly in plain JavaScript. Initialize it with your secret key: new EFact(process.env.EFACT_SECRET_KEY). All invoice operations are exposed under efact.invoices.*.

How do I handle errors with the EFact Node.js SDK?

The SDK throws typed errors inheriting from EFactError: EFactAuthError (401/403), EFactValidationError (400/422), EFactNotFoundError (404), EFactConflictError (409), EFactTimeoutError, and EFactConnectionError. Wrap calls in try/catch and inspect error types with instanceof to handle each scenario precisely.

Does the Node.js SDK automatically retry requests?

Yes, but only for idempotent requests (GET, HEAD, PUT, DELETE). These are retried with exponential back-off and jitter on timeout, network error, or HTTP 408/425/429/500/502/503/504 statuses, up to maxRetries times. Invoice creation (POST) is never retried automatically to prevent duplicates.