@elyonar/einvoice-js
v0.4.3
Published
Official TypeScript/JavaScript SDK for the E-Invoice platform — streamlining electronic invoicing and tax compliance
Downloads
389
Maintainers
Readme
@elyonar/einvoice-js
Official TypeScript/JavaScript SDK for the E-Invoice Nigeria platform — streamlining electronic invoicing, tax compliance, and B2B2B partner integrations.
Recent Updates
v0.4.3
- Invitations — New
InvitationServicewith create, list, resend, revoke, verify (public), and accept (public) - Organization Hierarchy —
getCreditPoolConfig(),updateCreditPoolConfig(),getChildren()for primary/secondary org management - Credit Transfers —
billing.transferCredits()for PAYG credit transfers between parent and child orgs - Organization Types — Organizations now have
type: 'primary' | 'secondary'withparentOrganizationIdandcreditPoolConfig
v0.3.0 – v0.4.1
- New Services — Webhooks, API Keys, Organizations, Users, Notifications
- B2B2B Partner Helpers —
forOrganization(),forApiKey(),createOrganizationWithApiKey() - Expanded Billing — Subscription management, payment methods, credit transfers
- Type System Overhaul — Field renames, enum casing, PostalAddress rewrite
- See CHANGELOG.md for full details
Features
- Full API coverage — Invoices, sellers, buyers, billing, subscriptions, organizations, users, API keys, webhooks, and notifications
- Type-safe — First-class TypeScript support with comprehensive type definitions
- Resilient — Automatic retry with exponential backoff and jitter for 5xx and 429 errors
- Rate limit aware — Respects
Retry-Afterheaders, throwsEInvoiceRateLimitErrorwhen exhausted - Secure webhooks — HMAC-SHA256 signature verification with timing-safe comparison and replay protection
- B2B2B partner support —
forOrganization(),forApiKey(), andcreateOrganizationWithApiKey()for multi-org management - Zero dependencies — Uses native
fetch,crypto, andAbortController - Dual builds — ESM and CommonJS
- Modern — Node.js 18+ with environment auto-detection from API key prefix
Requirements
- Node.js 18.0.0 or higher — The SDK uses native
fetchandcrypto.timingSafeEqual, which require Node 18+. - An E-Invoice API key — Get one from your E-Invoice dashboard or from your platform administrator.
Installation
npm install @elyonar/einvoice-jsGetting Started
This walks you through the minimum steps to submit your first invoice. Every SDK method returns an ApiResponse<T> envelope — your data is always in .data.
Step 1 — Initialize the client
import { EInvoice } from '@elyonar/einvoice-js';
const client = new EInvoice({ apiKey: 'sk_test_your_key_here' });The SDK auto-detects your environment from the key prefix:
sk_test_*→ Sandbox (https://local-api.einvoice.ng) — no real tax submissionssk_live_*→ Production (https://gateway.einvoice.ng) — real tax authority submissions
Step 2 — Register a seller (your business)
Before creating invoices, you need a seller (the entity issuing the invoice):
const seller = await client.sellers.create({
partyName: 'Acme Nigeria Ltd',
email: '[email protected]',
phoneNumber: '+2348012345678',
taxNumber: '12345678-0001',
postalAddress: {
line1: '123 Commerce Street',
city: 'Lagos',
state: 'Lagos',
country: 'NG',
},
});
console.log(seller.data.id); // 'sel_abc123' — save thisStep 3 — Register a buyer (your customer)
const buyer = await client.buyers.create({
partyName: 'John Doe Enterprises',
email: '[email protected]',
phoneNumber: '+2349087654321',
postalAddress: {
line1: '456 Market Road',
city: 'Abuja',
country: 'NG',
},
});
console.log(buyer.data.id); // 'buy_xyz789' — save thisStep 4 — Create and submit an invoice
// Create a draft invoice
const invoice = await client.invoices.create({
sellerId: seller.data.id,
buyerId: buyer.data.id,
invoiceNumber: 'INV-2026-001',
invoiceDate: '2026-04-19',
lineItems: [
{
description: 'Consulting services — April 2026',
quantity: 10,
unitPrice: 50000,
taxPercent: 7.5,
hsnCode: '9983', // HSN code for professional services
unitCode: 'HUR', // Hours
},
],
});
// Submit to tax authority (irreversible in production)
const submission = await client.invoices.submit(invoice.data.id);
console.log(submission.data.jobId); // 'job_abc123'
console.log(submission.data.status); // 'queued'Step 5 — Check the status
// Local DB status (fast)
const status = await client.invoices.getStatus(invoice.data.id);
console.log(status.data.status); // 'queued' | 'pending' | 'accepted' | 'rejected'
// Real-time query to tax authority (slower, use sparingly)
const liveStatus = await client.invoices.queryStatus(invoice.data.id);Authentication
API Keys
Every request is authenticated with an API key passed as a Bearer token. The SDK handles this automatically — just pass your key to the constructor.
Authorization: Bearer sk_test_your_key_hereKey Types
| Prefix | Environment | Base URL | Behavior |
|--------|-------------|----------|----------|
| sk_test_* | Sandbox | https://local-api.einvoice.ng | Invoices are NOT submitted to the tax authority. Safe for testing. |
| sk_live_* | Production | https://gateway.einvoice.ng | Invoices ARE submitted to the tax authority. Irreversible. |
Key Scoping
API keys are scoped to the organization that created them. A key can only access data (invoices, sellers, buyers, billing, etc.) belonging to its own organization. It cannot access data from other organizations, including parent or sibling orgs.
This is critical for B2B2B partners — see the B2B2B Partner Guide below.
Configuration
const client = new EInvoice({
apiKey: 'sk_test_...', // Required
organizationId: 'org_uuid', // Required for org-scoped services
options: {
baseUrl: 'https://custom.api.com', // Override auto-detected URL
timeout: 60000, // Request timeout in ms (default: 30000)
retry: {
maxAttempts: 5, // Max retries for 5xx/429 (default: 3)
baseDelay: 2000, // Base delay in ms (default: 1000)
},
headers: {
'X-Custom-Header': 'value', // Additional headers on every request
},
},
});| Option | Type | Default | Description |
|--------|------|---------|-------------|
| apiKey | string | — | Required. Your API key. Determines environment automatically. |
| organizationId | string | — | Required for org-scoped services (webhooks, API keys, users). |
| options.baseUrl | string | Auto-detected | Override the API gateway URL. |
| options.timeout | number | 30000 | Request timeout in milliseconds. |
| options.retry.maxAttempts | number | 3 | Max retry attempts for 5xx and 429 errors. 4xx errors are never retried. |
| options.retry.baseDelay | number | 1000 | Base delay between retries in ms. Actual delay uses exponential backoff with ±10% jitter. |
| options.headers | Record<string, string> | — | Additional headers included in every request. |
Organization-Scoped Services
Some services manage resources that belong to a specific organization. These require organizationId in the constructor:
client.webhooks— webhook endpoints and deliveriesclient.apiKeys— API key lifecycleclient.users— user management within an org
If you call an org-scoped method without organizationId, the SDK throws EInvoiceConfigError with a clear message.
// This will throw EInvoiceConfigError:
const client = new EInvoice({ apiKey: 'sk_test_...' });
await client.webhooks.list(); // Error: organizationId is required
// This works:
const client = new EInvoice({ apiKey: 'sk_test_...', organizationId: 'org_123' });
await client.webhooks.list(); // OKResponse Format
Every SDK method returns ApiResponse<T>:
interface ApiResponse<T> {
meta: {
statusCode: number;
success: boolean;
message: string;
errors: Array<{ field?: string; message: string; code?: string }>;
timestamp: string;
pagination?: {
total: number;
page: number;
pageSize: number;
totalPages: number;
hasNext: boolean;
hasPrevious: boolean;
};
};
data: T;
}Always access your data via .data:
const response = await client.invoices.create({ ... });
const invoice = response.data; // The actual invoice object
const success = response.meta.success; // truePagination
List methods accept page and pageSize parameters. Pagination info is in meta.pagination:
const page1 = await client.invoices.list({ page: 1, pageSize: 20 });
console.log(page1.meta.pagination.total); // 85
console.log(page1.meta.pagination.totalPages); // 5
console.log(page1.meta.pagination.hasNext); // true
// Get next page
const page2 = await client.invoices.list({ page: 2, pageSize: 20 });API Reference
Invoices — client.invoices
Full invoice lifecycle: create, validate, submit, track, download, and cancel.
// CRUD
client.invoices.create(params) // Create a draft invoice
client.invoices.get(id) // Get invoice by ID
client.invoices.list(params?) // List with filters (status, date range, seller, buyer)
client.invoices.update(id, params) // Update a draft (sellerId/buyerId/invoiceNumber are immutable)
client.invoices.delete(id) // Delete a draft invoice
// Tax Authority Submission
client.invoices.submit(id) // Submit to tax authority (irreversible, returns jobId)
client.invoices.batchSubmit(ids) // Submit 1–100 invoices at once
client.invoices.retry(id) // Retry a failed submission
client.invoices.cancel(id, params) // Cancel an accepted invoice (creates credit note)
// Status
client.invoices.getStatus(id) // Status from local DB (fast)
client.invoices.queryStatus(id) // Real-time status from tax authority (slower)
client.invoices.validate(params) // Validate invoice data without creating
// Download & Analytics
client.invoices.download(id, format?) // Download as 'pdf' or 'xml' (default: pdf)
client.invoices.getStatistics(params?) // Invoice counts and totals by status
// Reference Data
client.invoices.getSetup() // Available currencies, types, statuses
client.invoices.getHsnCodes(params?) // Search HSN/tax classification codes
client.invoices.getHsnCategories() // List HSN code categoriesCreateInvoiceParams (required fields):
{
sellerId: string; // Seller ID from sellers.create()
buyerId: string; // Buyer ID from buyers.create()
invoiceNumber: string; // Your invoice number (unique per org)
invoiceDate: string; // ISO date: '2026-04-19'
lineItems: [{
description: string; // Line item description
quantity: number; // Quantity
unitPrice: number; // Price per unit (in smallest currency unit or decimal)
taxPercent: number; // Tax rate as percentage (e.g. 7.5 for 7.5%)
hsnCode: string; // HSN/SAC code for tax classification
unitCode: string; // UBL unit code (e.g. 'EA', 'KGM', 'HUR')
}];
}Invoice status lifecycle: draft → queued → pending → accepted | rejected | failed
Sellers — client.sellers
Register and manage the entities that issue invoices.
client.sellers.create(params) // Register a seller
client.sellers.get(id) // Get seller by ID
client.sellers.list(params?) // List sellers (paginated)
client.sellers.update(id, params) // Update seller details
client.sellers.delete(id) // Delete seller
client.sellers.bulkDelete(ids) // Bulk delete multiple sellers
client.sellers.verifyTin(id) // Initiate TIN verification with tax authority
client.sellers.getVerificationStatus(id) // Check TIN verification progress
client.sellers.search(query) // Search by name or TIN
client.sellers.getSetup() // Reference data (party types, statuses)CreateSellerParams (required fields):
{
partyName: string; // Business name
email: string; // Contact email
phoneNumber: string; // Contact phone (with country code)
taxNumber: string; // Tax Identification Number
postalAddress: {
line1: string; // Street address
city: string; // City
country: string; // ISO country code (e.g. 'NG')
line2?: string; // Additional address line
state?: string; // State/province
postalCode?: string; // Postal/ZIP code
};
partyType?: string; // 'company' (default), 'individual', 'partnership', etc.
}Buyers — client.buyers
Register and manage the entities that receive invoices. Same methods as sellers:
client.buyers.create(params) // Register a buyer
client.buyers.get(id) // Get buyer by ID
client.buyers.list(params?) // List buyers (paginated)
client.buyers.update(id, params) // Update buyer details
client.buyers.delete(id) // Delete buyer
client.buyers.bulkDelete(ids) // Bulk delete
client.buyers.verifyTin(id) // Initiate TIN verification
client.buyers.getVerificationStatus(id) // Check TIN verification progress
client.buyers.search(query) // Search by name or TIN
client.buyers.getSetup() // Reference dataCreateBuyerParams — Same shape as CreateSellerParams except taxNumber is optional (buyers may not have a TIN).
Billing — client.billing
Manage your organization's billing account, subscriptions, credits, and payment methods.
// Account & Balance
client.billing.getAccount() // Credit balance, status, billing mode
client.billing.checkBalance(credits) // Check if you have enough credits (returns boolean)
client.billing.getSetup() // Billing configuration overview
// Plans & Packages
client.billing.getPlans(params?) // Available subscription plans
client.billing.getPlan(code) // Get plan by code (e.g. 'starter', 'pro')
client.billing.getPackages(params?) // Available one-time credit packages
client.billing.getPackage(id) // Get package by ID
client.billing.getAllPlans() // All plans (subscriptions + packages combined)
// Subscriptions
client.billing.getActiveSubscription() // Current active subscription
client.billing.getSubscriptionHistory() // Past subscriptions
client.billing.getSubscription(id) // Get subscription by ID
client.billing.createSubscription(params) // Subscribe to a plan
client.billing.cancelSubscription(id, params?) // Cancel (immediate or at period end)
client.billing.resumeSubscription(id) // Resume a cancelled subscription
client.billing.changePlan(id, params) // Upgrade/downgrade plan
// Credits & Payments
client.billing.purchaseCredits(params) // Buy a credit package (returns checkout URL)
client.billing.getPayments(params?) // Payment history
client.billing.getTransactions(params?) // Credit transaction history (purchases, consumption, refunds)
client.billing.transferCredits(params) // Transfer PAYG credits between parent and child orgs
// Payment Methods
client.billing.getPaymentMethods() // List saved methods
client.billing.addPaymentMethod(params) // Add a method (returns setup URL)
client.billing.removePaymentMethod(provider, id) // Remove a method
client.billing.setDefaultPaymentMethod(provider, params) // Set default
// Analytics
client.billing.getUsageAnalytics(params?) // Submission counts, credit usage, daily breakdown
client.billing.getConsumptionBreakdown() // Credit consumption grouped by API endpointOrganizations — client.organizations
Manage your organization's profile, onboarding, and verification. These methods operate on your own organization (the one associated with your API key).
client.organizations.create(params) // Create a sub-organization (B2B2B partners only)
client.organizations.list() // List your organizations
client.organizations.get(id?) // Get org by ID, or your own org if no ID
client.organizations.update(params) // Update your org profile
client.organizations.getSetup() // Onboarding status and reference data
client.organizations.completeOnboarding(params) // Complete onboarding (businessName, taxNo, email)
client.organizations.verifyTaxNumber({ taxNo }) // Verify your TIN with tax authority
client.organizations.sendPhoneVerification({ phoneNumber }) // Send phone verification OTP
client.organizations.verifyPhone({ otp }) // Verify phone with OTP code
// Organization hierarchy (primary orgs only)
client.organizations.getChildren() // List child (secondary) organizations
client.organizations.getCreditPoolConfig() // Get credit pool configuration
client.organizations.updateCreditPoolConfig(params) // Update credit pool configurationInvitations — client.invitations
Requires organizationId in constructor (except verify and accept which are public).
client.invitations.create(params) // Invite user to organization
client.invitations.list(params?) // List invitations
client.invitations.resend(id) // Resend invitation email
client.invitations.revoke(id) // Revoke an invitation
// Public endpoints (no auth required)
client.invitations.verify(params) // Verify an invitation code is valid
client.invitations.accept(params) // Accept invitation and create accountWebhooks — client.webhooks
Requires organizationId in constructor.
client.webhooks.create(params) // Create webhook endpoint
client.webhooks.list(params?) // List endpoints
client.webhooks.get(id) // Get endpoint by ID
client.webhooks.update(id, params) // Update endpoint (URL, events, etc.)
client.webhooks.delete(id) // Delete endpoint
client.webhooks.test(id) // Send a test event to the endpoint
client.webhooks.getSetup() // List available event types
client.webhooks.listDeliveries(webhookId, params?) // List delivery attempts for an endpoint
client.webhooks.retryDelivery(deliveryId) // Retry a failed deliveryAvailable webhook event types:
| Event | Description |
|-------|-------------|
| invoice.created | Invoice draft created |
| invoice.submitted | Invoice submitted to tax authority |
| invoice.approved | Invoice accepted by tax authority |
| invoice.rejected | Invoice rejected by tax authority |
| invoice.failed | Invoice submission failed |
| invoice.cancelled | Invoice cancelled (credit note created) |
| billing.payment_succeeded | Payment completed successfully |
| billing.payment_failed | Payment attempt failed |
| billing.low_credit_alert | Credit balance below threshold |
| billing.subscription_created | New subscription created |
| billing.subscription_renewed | Subscription renewed |
| billing.subscription_cancelled | Subscription cancelled |
| billing.trial_expiring_soon | Trial period ending soon |
| auth.user_invited | User invitation sent |
| auth.user_joined | User accepted invitation |
| auth.api_key_created | New API key created |
| auth.api_key_revoked | API key revoked |
API Keys — client.apiKeys
Requires organizationId in constructor.
client.apiKeys.create(params) // Create API key (returns raw key ONCE — store immediately)
client.apiKeys.list(params?) // List API keys (raw key is never shown again)
client.apiKeys.get(id) // Get API key metadata
client.apiKeys.revoke(id) // Revoke an API key (irreversible)
client.apiKeys.rotate(id) // Rotate key (returns new raw key, old key expires)
client.apiKeys.updateRole(id, params) // Change the role assigned to a key
client.apiKeys.getPermissions(id) // Get the key's effective permissions
client.apiKeys.getSetup() // Available roles and reference dataImportant:
create()androtate()are the only methods that return the raw API key. Store it securely (encrypted at rest) immediately — it cannot be retrieved again. If lost, userotate()to generate a new one.
Users — client.users
Requires organizationId in constructor. Manages users within the specified organization.
client.users.list(params?) // List users (paginated, filterable)
client.users.get(id) // Get user by ID
client.users.getMe() // Get the current authenticated user
client.users.update(id, params) // Update user profile
client.users.updateRole(id, { roleId }) // Change user's role
client.users.updateStatus(id, { status }) // Activate, suspend, or deactivate user
client.users.getPermissions(id) // Get user's roles + effective permissions
client.users.addPermissions(id, { permissions }) // Add custom permissions
client.users.removePermissions(id, { permissions }) // Remove custom permissions
client.users.getSetup() // Available roles and reference dataNotifications — client.notifications
Does not require organizationId. Operates on the authenticated user's notifications.
client.notifications.list(params?) // List notifications (filterable by category, read status)
client.notifications.getUnreadCount() // Get unread notification count
client.notifications.getByCategory() // Notifications grouped by category
client.notifications.markAsRead(id) // Mark a single notification as read
client.notifications.markAllAsRead(params?) // Mark all as read (optionally filter by category)B2B2B Partner Guide
If you're a platform (like an ERP, POS, or commerce system) that onboards multiple businesses onto E-Invoice, this section is for you.
How It Works
Your platform is a partner organization on E-Invoice. Each business you onboard becomes a child organization with its own isolated data, API key, and billing account.
┌─────────────────────────────────────────┐
│ Your Platform (Partner) │
│ API Key: sk_live_partner_... │
│ │
│ Can: create child orgs, manage their │
│ users and API keys │
│ │
│ Cannot: access child org invoices, │
│ sellers, buyers, or billing │
├─────────┬─────────┬─────────┬──────────┤
│ Child A │ Child B │ Child C │ ... │
│ Own key │ Own key │ Own key │ │
│ Own data│ Own data│ Own data│ │
└─────────┴─────────┴─────────┴──────────┘Step 1 — Initialize your partner client
const partner = new EInvoice({
apiKey: 'sk_live_partner_key',
organizationId: 'your_partner_org_id', // Your own org ID
});Step 2 — Onboard a child organization
const { organization, apiKey } = await partner.createOrganizationWithApiKey(
{ businessName: 'Acme Nigeria', email: '[email protected]' },
{ mode: 'production', name: 'acme-primary-key' },
);
// IMPORTANT: apiKey.key is the raw key — shown ONCE.
// Store it encrypted (e.g. AES-256-GCM) in your database immediately.
// If lost, use apiKeys.rotate() on a partner-scoped client to generate a new one.
console.log(apiKey.key); // 'sk_live_child_...'
console.log(organization.id); // 'org_child_123'Step 3 — Operate as the child org (invoices, sellers, buyers, billing)
Use forApiKey() with the child org's own API key. This creates a new client that inherits your config (timeout, retry, baseUrl) but authenticates as the child org.
const acme = partner.forApiKey(apiKey.key, organization.id);
// Now all operations are scoped to Acme's data
const seller = await acme.sellers.create({ ... });
const buyer = await acme.buyers.create({ ... });
const invoice = await acme.invoices.create({ ... });
await acme.invoices.submit(invoice.data.id);
await acme.billing.getAccount();Step 4 — Manage child org resources with your partner key
Use forOrganization() with the child org's ID. This creates a client using your partner key but scoped to the child org's ID in the URL path.
const acmeAdmin = partner.forOrganization(organization.id);
await acmeAdmin.apiKeys.list(); // List Acme's API keys
await acmeAdmin.users.list(); // List Acme's users
await acmeAdmin.webhooks.create({ ... }); // Create webhook for AcmeWarning:
forOrganization()uses your partner's API key, NOT the child org's key. This only works for endpoints where the partner has cross-org authority: API keys, users, and webhooks. It does NOT work for invoices, sellers, buyers, or billing — those require the child org's own key viaforApiKey().
When to use which method
| Method | API Key Used | Org Scope | Use For |
|--------|-------------|-----------|---------|
| new EInvoice({ apiKey, organizationId }) | Your own | Your own org | Partner self-management |
| partner.forOrganization(childOrgId) | Your partner key | Child org (URL path) | Managing child's API keys, users, webhooks |
| partner.forApiKey(childKey, childOrgId) | Child org's key | Child org | Invoices, sellers, buyers, billing for the child |
| partner.createOrganizationWithApiKey(...) | Your partner key | Creates new child | One-time child org onboarding |
Credit Pool Configuration
Primary organizations control how credits flow to their child (secondary) orgs via creditPoolConfig:
// Get current config
const config = await partner.organizations.getCreditPoolConfig();
// Update config
await partner.organizations.updateCreditPoolConfig({
primaryCoversSecondary: true,
minimumReserve: 500,
});| Config | Behavior |
|--------|----------|
| primaryCoversSecondary: true | Primary's pool is debited for all child org usage. Child's balance is unchanged. |
| primaryCoversSecondary: false, fallbackToPrimary: false | Child uses its own pool only. Blocked when empty. |
| primaryCoversSecondary: false, fallbackToPrimary: true | Child's pool first, then primary covers the shortfall (respecting minimumReserve). |
Credit Transfers
Transfer PAYG credits between parent and child organizations:
// Primary → child: fund the child org
await partner.billing.transferCredits({
targetOrganizationId: organization.id,
amount: 1000,
description: 'Monthly credit allocation',
});
// List child orgs and their balances
const children = await partner.organizations.getChildren();Transfers use the paygCredits pool only. Both orgs must be in payg or hybrid billing mode. Primary-to-child transfers respect the minimumReserve setting.
Error Recovery
If createOrganizationWithApiKey() succeeds in creating the org but fails when creating the API key (e.g. network error), the org will exist without a key. Recover by creating the key manually:
const scoped = partner.forOrganization(organization.id);
const newKey = await scoped.apiKeys.create({ mode: 'production', name: 'retry-key' });Webhook Verification
Verify incoming webhook signatures using the standalone verifyWebhook function:
import { verifyWebhook } from '@elyonar/einvoice-js/webhooks';
// Pass the RAW request body (string or Buffer), NOT parsed JSON
const event = verifyWebhook(rawBody, requestHeaders, process.env.WEBHOOK_SECRET!);
console.log(event.eventType); // 'invoice.approved'
console.log(event.eventId); // 'evt_abc123'
console.log(event.data); // Event payloadThe function verifies the HMAC-SHA256 signature (timing-safe comparison) and rejects payloads older than 5 minutes (replay protection). Throws EInvoiceWebhookError on failure.
Important: Pass the raw request body as a string or Buffer — not
JSON.parse()'d. Parsed JSON will produce a different string, causing signature mismatch.
// Custom timestamp tolerance (default: 300 seconds)
const event = verifyWebhook(body, headers, secret, { toleranceSeconds: 600 });Error Handling
The SDK uses a structured error hierarchy. All errors extend EInvoiceError.
import {
EInvoiceApiError,
EInvoiceRateLimitError,
EInvoiceTimeoutError,
EInvoiceConfigError,
EInvoiceWebhookError,
} from '@elyonar/einvoice-js';
try {
await client.invoices.submit('inv_123');
} catch (err) {
if (err instanceof EInvoiceRateLimitError) {
// HTTP 429 — rate limit exceeded after all retries
console.log(`Rate limited. Retry after ${err.retryAfter} seconds`);
} else if (err instanceof EInvoiceApiError) {
// HTTP 4xx/5xx from the API
console.log(err.statusCode); // 422
console.log(err.errorCode); // 'VAL001'
console.log(err.message); // 'Validation failed'
// Field-level errors — useful for form validation
console.log(err.errors);
// [
// { field: 'lineItems', message: 'At least one line item is required' },
// { field: 'invoiceDate', message: 'Invoice date cannot be in the future' },
// ]
} else if (err instanceof EInvoiceTimeoutError) {
// Request took longer than the configured timeout
console.log(`Timed out after ${err.timeoutMs}ms`);
} else if (err instanceof EInvoiceConfigError) {
// SDK misconfiguration (missing apiKey, missing organizationId, etc.)
console.log(err.message);
} else if (err instanceof EInvoiceWebhookError) {
// Webhook signature verification failed
console.log(err.message); // 'Webhook signature verification failed'
}
}Error Hierarchy
EInvoiceError (base)
├── EInvoiceApiError — API returned 4xx/5xx
│ └── EInvoiceRateLimitError — API returned 429 (has retryAfter)
├── EInvoiceTimeoutError — Request exceeded timeout (has timeoutMs)
├── EInvoiceConfigError — Invalid SDK configuration
└── EInvoiceWebhookError — Webhook signature verification failedAutomatic Retries
The SDK automatically retries on:
- 5xx errors (server errors) — up to
maxAttemptswith exponential backoff - 429 errors (rate limiting) — waits for
Retry-Afterheader, then retries
The SDK does NOT retry:
- 4xx errors (client errors like 400, 401, 403, 404, 422) — these indicate a problem with your request
Per-Request Options
Every method accepts an optional RequestOptions parameter as the last argument:
const controller = new AbortController();
const invoices = await client.invoices.list(
{ status: 'draft', page: 1 },
{
timeout: 5000, // Override timeout for this request only
signal: controller.signal, // AbortSignal for cancellation
headers: { 'X-Request-Id': 'req_123' }, // Additional headers for this request
},
);
// Cancel the request
controller.abort();Development
npm install # Install dependencies
npm test # Run tests with coverage
npm run build # Build ESM + CJS
npm run lint # Type check
npm run dev # Watch mode (ESM only)License
MIT
