JSPM

@elyonar/einvoice-js

0.6.1
  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 271
  • Score
    100M100P100Q103347F
  • License MIT

Official TypeScript/JavaScript SDK for the E-Invoice platform — streamlining electronic invoicing and tax compliance

Package Exports

  • @elyonar/einvoice-js
  • @elyonar/einvoice-js/webhooks

Readme

@elyonar/einvoice-js

Official TypeScript/JavaScript SDK for the E-Invoice Nigeria platform — streamlining electronic invoicing, tax compliance, and B2B2B partner integrations.

npm version License: MIT

Features

  • Full API coverage — Invoices, sellers, buyers, billing, subscriptions, organizations, users, API keys, webhooks, invitations, and roles (117 methods across 10 services)
  • 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-After headers, throws EInvoiceRateLimitError when exhausted
  • Secure webhooks — HMAC-SHA256 signature verification with timing-safe comparison and replay protection
  • B2B2B partner supportforOrganization(), forApiKey(), and createOrganizationWithApiKey() for multi-org management
  • Zero dependencies — Uses native fetch, crypto, and AbortController
  • 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 fetch and crypto.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-js

Getting 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 submissions
  • sk_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: 'billing@acme.ng',
  phoneNumber: '+2348012345678',
  taxNumber: '12345678-0001',
  postalAddress: {
    line1: '123 Commerce Street',
    city: 'Lagos',
    state: 'Lagos',
    country: 'NG',
  },
});

console.log(seller.data.id); // 'sel_abc123' — save this

Step 3 — Register a buyer (your customer)

const buyer = await client.buyers.create({
  partyName: 'John Doe Enterprises',
  email: 'john@example.com',
  phoneNumber: '+2349087654321',
  postalAddress: {
    line1: '456 Market Road',
    city: 'Abuja',
    country: 'NG',
  },
});

console.log(buyer.data.id); // 'buy_xyz789' — save this

Step 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' | 'failed'

// 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_here

Key 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, invitations, roles).
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 deliveries
  • client.apiKeys — API key lifecycle
  • client.users — user management within an org
  • client.invitations — team invitations (except verify and accept which are public)
  • client.roles — custom role and RBAC management

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(); // OK

Response 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; // true

Pagination

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.getHsnCodes(params?)    // Search HSN/tax classification codes
client.invoices.getHsnCategories()      // List HSN code categories

// Invoice Resources (jurisdiction-agnostic)
client.invoices.getResources()              // Bundled resources (countries, currencies, tax categories, etc.)
client.invoices.getResourceByType(type)     // Single resource collection by type
client.invoices.lookupTaxId(taxId)          // Look up taxpayer by tax ID (TIN/CNPJ/PIN)
client.invoices.validateReference(params)   // Validate an invoice reference from the tax authority
client.invoices.submitTaxReport(id, params) // Submit post-payment tax report for an invoice

CreateInvoiceParams (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: draftqueuedpendingaccepted | rejected | failed — and acceptedcancelled via cancel()

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

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

CreateBuyerParams — 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(accountId, credits) // Check if account has enough credits
client.billing.getAccountStats(accountId)      // Account statistics (lifetime totals, averages)

// 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(params?)         // 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.subscribeAndPay(params)     // Subscribe and initiate payment in one step (returns checkout URL)
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
client.billing.getRenewalHistory(params?)  // Subscription renewal ledger
client.billing.getRenewalStats()           // Renewal statistics (totals, averages)

// 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.getTransaction(id)          // Get a single transaction by ID
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 endpoint

Organizations — 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.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 configuration

Invitations — 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 account

Webhooks — 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.listDeliveries(webhookId, params?)  // List delivery attempts for an endpoint
client.webhooks.retryDelivery(deliveryId)           // Retry a failed delivery

Available webhook event types:

Event Description
invoice.created Invoice draft created
invoice.updated Invoice updated
invoice.submitted Invoice submitted to tax authority
invoice.approved Invoice accepted by tax authority
invoice.rejected Invoice rejected by tax authority
invoice.cancelled Invoice cancelled (credit note created)
invoice.status.changed Invoice status changed
invoice.submission_failed Invoice submission failed
invoice.retry_scheduled Invoice retry scheduled
seller.created Seller created
seller.updated Seller updated
seller.tax_number_verified Seller TIN verified
seller.tax_number_verification_failed Seller TIN verification failed
buyer.created Buyer created
buyer.updated Buyer updated
buyer.deleted Buyer deleted
buyer.tax_number_verified Buyer TIN verified
buyer.tax_number_verification_failed Buyer TIN verification failed
user.created User created
user.updated User updated
user.deleted User deleted
user.role_changed User role changed
user.status_changed User status changed
organization.created Organization created
organization.updated Organization updated
organization.status_changed Organization status changed
organization.tax_number_verified Organization TIN verified
organization.onboarding_completed Organization onboarding completed
organization.subscription_tier_changed Organization subscription tier changed
api_key.created API key created
api_key.revoked API key revoked
api_key.rotated API key rotated
api_key.permissions_changed API key permissions changed
payment.created Payment created
payment.succeeded Payment completed successfully
payment.failed Payment attempt failed
billing_account.status_changed Billing account status changed
billing_account.low_balance Credit balance below threshold
subscription.created Subscription created
subscription.renewed Subscription renewed
subscription.cancelled Subscription cancelled
subscription.expired Subscription expired
subscription.plan_changed Subscription plan changed
subscription.payment_failed Subscription payment failed
subscription.payment_failed_final Subscription payment failed (final attempt)
credit.balance_updated Credit balance updated
credit.purchased Credits purchased
webhook.test Test webhook event
* Subscribe to all events

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

Important: create() and rotate() are the only methods that return the raw API key. Store it securely (encrypted at rest) immediately — it cannot be retrieved again. If lost, use rotate() 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

// Phone Verification
client.users.sendPhoneVerification(id, { phoneNumber })  // Send OTP to verify phone
client.users.verifyPhone(id, { phoneNumber, otp })       // Verify phone with 6-digit OTP

Roles — client.roles

Requires organizationId in constructor. Manage custom RBAC roles for your organization.

client.roles.list()                        // List all roles (system + custom)
client.roles.create(params)                // Create a custom role with specific permissions
client.roles.get(roleId)                   // Get role details with assignment counts
client.roles.update(roleId, params)        // Update a custom role (system roles are immutable)
client.roles.delete(roleId)                // Delete a custom role (removes all assignments)
client.roles.getAvailablePermissions()     // Get available permissions catalog by category

CreateRoleParams:

{
  name: string;           // Internal identifier (e.g. 'invoice_manager')
  displayName: string;    // Display name (e.g. 'Invoice Manager')
  description?: string;   // Optional description
  permissions: string[];  // Array of permission strings (e.g. ['invoice.create', 'invoice.read'])
}

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: 'admin@acme.ng' },
  { 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 Acme

Warning: 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 via forApiKey().

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)
const event = verifyWebhook(rawBody, requestHeaders, process.env.WEBHOOK_SECRET!);

console.log(event.type);         // 'invoice.approved'
console.log(event.id);           // 'evt_abc123'
console.log(event.environment);  // 'sandbox' | 'production'
console.log(event.data);         // Event payload

The function parses the body, extracts the data field, and verifies the HMAC-SHA256 signature against it (timing-safe comparison). 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.

// 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 failed

Automatic Retries

The SDK automatically retries on:

  • 5xx errors (server errors) — up to maxAttempts with exponential backoff
  • 429 errors (rate limiting) — waits for Retry-After header, 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