JavaScript and TypeScript
@useyona/einvoice-js is the official SDK for Node.js 18 and later. It has no runtime dependencies, ships as ESM and CommonJS, and is written in TypeScript, so every request and response is typed.
Install
npm install @useyona/einvoice-jsQuick start
Create one in the Yona app under API keys, with the invoicing preset; it starts with sk_test_. The SDK needs nothing else: no host and no environment setting, because the key chooses the sandbox or the live service.
import { EInvoice } from '@useyona/einvoice-js';
const client = new EInvoice({ apiKey: process.env.YONA_API_KEY! });
// 1. A buyer
const buyer = await client.buyers.create({
name: 'Acme Nigeria Ltd',
taxId: '12345678-0001',
email: 'accounts@acme.ng',
partyType: 'company',
address: { line1: '1 Marina', city: 'Lagos', country: 'NG' },
});
// 2. A saved item
const item = await client.items.create({
name: 'Laptop',
itemType: 'goods',
hsnCode: '8471.30',
productCategory: 'Machinery',
unitCode: 'EA',
unitPriceMinor: '45000000',
currency: 'NGN',
taxCategory: 'STANDARD_VAT',
});
// 3. A draft invoice
const invoice = await client.invoices.create({
invoiceKind: 'B2B',
invoiceDate: '2026-10-05',
currency: 'NGN',
buyerId: buyer.id,
lineItems: [{ itemId: item.id, quantity: 2 }],
});
// 4. Finalise it and report it to the tax authority
await client.invoices.finalise(invoice.id);
await client.submissions.submit(invoice.id);
const status = await client.submissions.getStatus(invoice.id);Submitting is asynchronous: poll submissions.getStatus or listen for the invoice.accepted webhook. The states are explained in Invoice lifecycle.
Sandbox and live
An sk_test_ key works in the sandbox and an sk_live_ key in live, on the same host; client.mode tells you which. To make a deployment refuse a key of the wrong kind:
new EInvoice({ apiKey, assertMode: 'live' });A key of the other kind, or a malformed one, is refused at construction with EInvoiceConfigError; the key is never echoed.
Responses and pagination
Methods return the API’s data. List methods return a page:
const page = await client.invoices.list({ status: 'accepted', limit: 50 });page.data holds the invoices and page.pagination carries total, page, pageSize, totalPages, hasNext and hasPrevious.
paginate walks every page for you:
import { paginate } from '@useyona/einvoice-js';
for await (const buyer of paginate((q) => client.buyers.list(q), { limit: 100 })) {
console.log(buyer.name);
}Received invoices and issued history answer an object with the items under data.items and pagination beside it. PDF downloads (output.downloadPdf, issuedHistory.downloadPdf) answer data as an ArrayBuffer with contentType and fileName. Amounts are strings or numbers in major units on input and integer minor units on output, as the API defines them.
Errors
Every refusal is an EInvoiceApiError subclass chosen by status:
| Class | Status |
|---|---|
EInvoiceValidationError | 400, 422 |
EInvoiceAuthenticationError | 401 |
EInvoiceInsufficientCreditsError | 402 |
EInvoicePermissionError | 403 |
EInvoiceNotFoundError | 404 |
EInvoiceConflictError | 409 |
EInvoiceRateLimitError | 429 |
EInvoiceServerError | 5xx |
Each carries status, errorCode, errors (per field), requestId and retryAfter:
import { EInvoiceApiError, EInvoiceValidationError } from '@useyona/einvoice-js';
try {
await client.invoices.create(params);
} catch (err) {
if (err instanceof EInvoiceValidationError) {
for (const e of err.errors) console.error(e.field, e.message);
} else if (err instanceof EInvoiceApiError) {
console.error(err.status, err.errorCode, err.requestId);
}
}Branch on errorCode, never on message, and quote requestId when you write to support. retryAfter is set, in seconds, when the API sent Retry-After. Problems before the API answers are EInvoiceTimeoutError, EInvoiceConnectionError and EInvoiceConfigError; a webhook that fails verification is EInvoiceWebhookError. All extend EInvoiceError. The codes are listed on each page of the API Reference.
Retries and idempotency
Reads, and writes that carry an Idempotency-Key, are retried on network errors, timeouts, 408, 429 and 5xx: two retries by default, with exponential backoff and jitter. On 429 and 503 the SDK waits for Retry-After, up to 60 seconds by default; a longer wait is thrown at once with retryAfter set. Other writes are never retried.
Where the API accepts an Idempotency-Key (creating an invoice, submitting, sending, downloading and so on) the SDK generates one per call and reuses it across its own retries, so a retry is never charged twice. Pass your own to make a retry safe across process restarts, and to make buyers.update and items.update retryable:
await client.invoices.create(params, { idempotencyKey: `order-${order.id}` });Per-request options are timeout, signal, headers, idempotencyKey and maxRetries. timeout covers each attempt from sending the request to reading the whole body; aborting signal stops the call at once, including while it waits to retry.
Webhooks
import express from 'express';
import { verifyWebhook } from '@useyona/einvoice-js/webhooks';
const rawJson = express.raw({ type: 'application/json' });
app.post('/webhooks/yona', rawJson, (req, res) => {
try {
const secret = process.env.YONA_WEBHOOK_SECRET!;
const event = verifyWebhook(req.body, req.headers, secret);
if (event.type === 'invoice.accepted') {
// …
}
res.sendStatus(200);
} catch {
res.sendStatus(400);
}
});verifyWebhook checks the Yona-Signature header over the raw body, accepts either signature while a rotated secret overlaps, enforces a tolerance of five minutes (toleranceSeconds to change it) and returns the event (id, type, createdAt, mode, data). Pass the raw body: a re-serialised object will not verify. Deduplicate on id: a redelivery carries the same one. signWebhookPayload(body, secret) builds a valid header for testing your handler.
Endpoints are created and their secrets rotated in the Yona app; the SDK can list and test endpoints and read and redeliver deliveries and events. See Webhooks for the events.
Configuration
new EInvoice({ apiKey, … }) accepts:
| Option | Purpose |
|---|---|
assertMode | 'sandbox' or 'live': refuse a key of the other kind |
timeout | per-attempt timeout in milliseconds (default 30000) |
retry | maxRetries (2), baseDelay (500 ms), maxDelay (8 s), maxRetryAfter (60 s) |
headers | headers sent on every request |
fetch | your own fetch implementation |
baseUrl | another gateway, for local development only; the key decides sandbox or live |
What it covers
Every module mirrors a tag of the API Reference, and the method names are the reference’s operation names in the casing of the language (issueCreditNote, getStatus). Every method takes an optional RequestOptions last. Each method needs the capability shown on the key; the full_integration preset has them all.
| Module | What it does | Reference |
|---|---|---|
client.invoices, client.submissions, client.output, client.shareLinks | Create, edit and finalise drafts; submit, retry and track them; credit and debit notes; PDFs, sending and share links | Invoices |
client.buyers | The businesses and people you invoice, with tax-number checks and reachability | Buyers |
client.items | What you sell, with codes, units, prices and tax categories | Items |
client.reference | HS codes, reference lists, tax-ID lookup and invoice validation | Reference data |
client.sellers | Your organisation as the seller, read-only | Sellers |
client.inboundInvoices, client.issuedHistory | Invoices other businesses sent you, and your issued history retrieved from the authority | Received invoices |
client.organization, client.invoiceSettings, client.taxConnection | Your organisation and its readiness, invoice settings, the tax connection | Organizations, Tax connection |
client.billing | Credits and usage, payments, statements and your subscription, in accounts, payments, sandbox, statements, subscriptions, transactions | Billing |
client.webhooks | List and test endpoints; read and redeliver deliveries and events, in endpoints, deliveries, events, eventTypes | Webhooks |
Account management (users, roles, API keys, purchases, webhook endpoint settings) is done in the Yona app, not through the API key.
Next steps
- Invoice lifecycle: the states a submission climbs through, and what each one means
- Webhooks: the events, and how deliveries are retried
- Going live: the checklist before you deploy a live key
- API Reference: every field, status code and error code