npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@elyonar/einvoice-js

v0.4.3

Published

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

Downloads

389

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

Recent Updates

v0.4.3

  • Invitations — New InvitationService with create, list, resend, revoke, verify (public), and accept (public)
  • Organization HierarchygetCreditPoolConfig(), updateCreditPoolConfig(), getChildren() for primary/secondary org management
  • Credit Transfersbilling.transferCredits() for PAYG credit transfers between parent and child orgs
  • Organization Types — Organizations now have type: 'primary' | 'secondary' with parentOrganizationId and creditPoolConfig

v0.3.0 – v0.4.1

  • New Services — Webhooks, API Keys, Organizations, Users, Notifications
  • B2B2B Partner HelpersforOrganization(), 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-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: '[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 this

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

// 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). | | 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

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

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

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 data

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(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 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.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 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.getSetup()                          // List available event types
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.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 data

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
client.users.getSetup()                       // Available roles and reference data

Notifications — 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 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), 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 payload

The 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 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