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
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-nodeInitialization
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
efact.invoices.create(input: CreateInvoiceRequest): Promise<InvoiceResponse>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
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-sideefact.invoices.retrieve(invoiceId)
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)
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 timestampefact.invoices.list(params?)
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)
// 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)
// 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:
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
| Class | HTTP code | When |
|---|---|---|
EFactConfigError | - | Missing API key or invalid configuration option |
EFactAuthError | 401 / 403 | Missing or invalid API key |
EFactNotFoundError | 404 | Resource not found (including cross-tenant requests) |
EFactValidationError | 400 / 422 | Invalid request payload |
EFactConflictError | 409 | Conflict, e.g. modifying or cancelling an invoice that is no longer PENDING |
EFactApiError | 5xx | Unclassified 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
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:
| 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
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.