BeeL Node.js SDK

Official Node.js/TypeScript SDK for the BeeL API — Spanish invoicing for self-employed professionals with VeriFactu compliance.
Node.js 18+. Single dependency (openapi-fetch).
Features
- Full TypeScript types auto-generated from OpenAPI spec
- Automatic retries with exponential backoff on 429 and 5xx
- Auto idempotency keys on every POST request
- Typed errors — catch
BeeLNotFoundError instead of checking status codes
- PDF download as Buffer
- Webhook signature verification with HMAC-SHA256
- Instance-based client — multiple API keys, no global state
- ESM + CommonJS
Installation
npm install @beel_es/sdk
Quick Start
import { BeeL } from '@beel_es/sdk';
const beel = new BeeL({ apiKey: 'beel_sk_live_...' });
const company = beel.company('company-uuid');
const invoice = await company.invoices.create({
type: 'STANDARD',
recipient: { customer_id: 'customer-uuid' },
lines: [
{ line_type: 'NORMAL', description: 'Consulting', quantity: 1, unit_price: 100, discount_percentage: 0 },
],
});
console.log(invoice.id);
const issued = await company.invoices.issue(invoice.id);
console.log(issued.invoice_number);
Companies (multi-NIF)
Every operation on beel.company(companyId) addresses the company in the URL path, so it
works with any number of NIFs and never depends on a session focus:
const company = beel.company('company-uuid');
const { invoices, pagination } = await company.invoices.list({ status: 'ISSUED' });
await company.invoices.issue('invoice-uuid');
await company.invoices.void('invoice-uuid', { reason: 'Billing error' });
await company.invoices.createCorrective('invoice-uuid', { ... });
const pdf = await company.invoices.getPdf('invoice-uuid');
const customer = await company.customers.create({ ... });
const product = await company.products.create({ ... });
await company.series.ensureDefaults();
await company.recurringInvoices.create({ ... });
await company.recurringInvoices.setStatus('rec-uuid', { status: 'PAUSED' });
await company.invoices.schedule.set('invoice-uuid', { scheduled_for: '2026-12-01' });
await company.invoices.schedule.clear('invoice-uuid');
const copy = await company.invoices.derive({ from_invoice_id: 'invoice-uuid' });
const verifactu = await company.verifactuConfiguration.get();
await company.verifactuConfiguration.update({ enabled: true });
const taxes = await company.taxConfiguration.get();
const summary = await company.fiscalSummary({ year: 2026 });
const readiness = await company.issuingReadiness();
Whether an invoice reaches AEAT is a fact of the NIF, resolved at issue time — not a
per-invoice choice. enabled is the only writable field of the VeriFactu configuration.
Payment connections (Stripe per NIF)
A connection links Stripe to one NIF so its charges auto-generate invoices under it. It is
addressed by its own UUID, not by the provider slug — a NIF can hold several
connections of the same provider. The slug only appears when you start an authorization.
const { connections } = await company.paymentConnections.list();
const { authorization_url } = await company.paymentConnections.authorize({
provider: 'stripe',
});
const events = company.paymentConnections.events(connections[0].id);
const { events: pending } = await events.list({ needs_action: true });
await events.retry('event-uuid');
const { invoice_id } = await events.draft('event-uuid');
await events.resolve('event-uuid');
await company.paymentConnections.disconnect(connections[0].id);
Catalogs and preferences
const taxTypes = await beel.catalogs.taxTypes();
const options = await beel.catalogs.invoiceCustomizationOptions();
await beel.catalogs.updateMe({ language: 'es' });
Any endpoint: beel.raw
A route reaches the generated types as soon as the contract is synced, but its ergonomic
wrapper is written by hand. beel.raw lets you call anything in the contract today,
without waiting for a release:
const { data } = await beel.raw.GET('/v1/companies/{company_id}/logo', {
params: { path: { company_id: 'company-uuid' } },
});
It is the same client the resources use — authentication, retries and Idempotency-Key
still apply — and it stays typed: a path, parameter or body the contract does not have
will not compile. What you give up is the sugar and the naming stability of the wrappers.
Accounts (managed accounts, members & webhooks)
For integrators managing several accounts (provisioning, gestorías, platforms):
const { accounts } = await beel.accounts.list();
const created = await beel.accounts.provision({ external_ref: 'client-42' });
const account = beel.account(created.account_id);
const { companies } = await account.companies.list();
await account.companies.create({ nif: 'B12345678', ... });
const members = await account.members.list();
await account.members.putGrant('member-id', 'company-id', { permissions: [...] });
await account.invitations.create({ invited_email: 'user@example.com', account_role: 'MEMBER' });
const webhook = await account.webhooks.create({ url: 'https://...', events: ['invoice.issued'] });
await account.webhooks.test(webhook.id);
await account.webhooks.rotateSecret(webhook.id);
const usage = await account.usage();
await account.createClaimToken();
Resources (deprecated session-focus surface)
Deprecated: the API contract marks the whole session-focus surface (beel.invoices,
beel.customers, beel.products, beel.series, beel.configuration) as deprecated.
Prefer the company-scoped equivalents above. These keep working during the sunset window.
const { invoices, pagination } = await beel.invoices.list({ status: 'ISSUED', limit: 20 });
const invoice = await beel.invoices.get('invoice-uuid');
const created = await beel.invoices.create({ ... });
const updated = await beel.invoices.update('id', { ... });
await beel.invoices.delete('id');
await beel.invoices.issue('id');
await beel.invoices.markPaid('id');
await beel.invoices.markSent('id');
await beel.invoices.void('id', { reason: 'Duplicate invoice sent in error' });
await beel.invoices.schedule('id', { scheduled_for: '2026-04-15' });
await beel.invoices.unschedule('id');
await beel.invoices.duplicate('id');
await beel.invoices.createCorrective('id', { ... });
const { customers, pagination } = await beel.customers.list({ search: 'Acme' });
const customer = await beel.customers.create({
legal_name: 'Acme SL',
nif: 'B86561412',
address: { street: 'Calle Mayor', number: '1', postal_code: '28001', city: 'Madrid', province: 'Madrid', country: 'Spain', country_code: 'ES' },
});
const { products } = await beel.products.list();
const results = await beel.products.search('consulting');
const taxConfig = await beel.configuration.getTaxConfig();
const series = await beel.series.list();
const result = await beel.nif.validate('B12345678');
Typed Errors
Every API error maps to a specific error class:
import { BeeL, BeeLNotFoundError, BeeLValidationError } from '@beel_es/sdk';
try {
await beel.invoices.get('nonexistent-id');
} catch (error) {
if (error instanceof BeeLNotFoundError) {
console.log(error.message);
console.log(error.requestId);
}
if (error instanceof BeeLValidationError) {
console.log(error.message);
console.log(error.details);
}
}
BeeLAuthError | 401, 403 | Invalid or missing API key |
BeeLNotFoundError | 404 | Resource doesn't exist |
BeeLValidationError | 422 | Invalid data |
BeeLConflictError | 409 | Duplicate (e.g. same NIF) |
BeeLRateLimitError | 429 | Too many requests (auto-retried) |
BeeLApiError | 5xx | Server error (auto-retried) |
Automatic Retries
429 and 5xx errors are retried with exponential backoff. Client errors (400, 401, 404, 422) are never retried.
const beel = new BeeL({
apiKey: 'beel_sk_live_...',
maxRetries: 5,
retryDelayMs: 1000,
maxRetryDelayMs: 60000,
});
PDF Download
import fs from 'fs';
const { buffer, fileName } = await beel.downloadPdf('invoice-uuid');
fs.writeFileSync(fileName, buffer);
Webhooks
Verify webhook signatures before processing:
import express from 'express';
import { WebhookVerifier } from '@beel_es/sdk';
const verifier = new WebhookVerifier(process.env.BEEL_WEBHOOK_SECRET!);
app.post('/webhooks/beel', express.raw({ type: 'application/json' }), (req, res) => {
try {
const event = verifier.verify(
req.body.toString('utf8'),
req.headers['beel-signature'] as string,
);
if (event.type === 'verifactu.status.updated') {
console.log(event.data.new_status);
}
res.status(200).send('OK');
} catch {
res.status(400).send('Invalid signature');
}
});
Builders
Optional fluent builders for common operations:
import { InvoiceBuilder, CustomerBuilder } from '@beel_es/sdk';
const customer = CustomerBuilder.create()
.name('Acme SL')
.nif('B12345678')
.email('contact@acme.com')
.address('Calle Mayor', '1', '28001', 'Madrid', 'Madrid', 'Spain')
.build();
const invoice = InvoiceBuilder.create()
.forCustomer('customer-uuid')
.addLine('Service A', 5, 100.0)
.addLine('Service B', 3, 150.0)
.metadata({ stripe_id: 'pi_abc123' })
.build();
Environments
const sandbox = new BeeL({ apiKey: 'beel_sk_test_...' });
const prod = new BeeL({ apiKey: 'beel_sk_live_...' });
Full Configuration
const beel = new BeeL({
apiKey: 'beel_sk_live_...',
baseUrl: 'https://app.beel.es/api',
maxRetries: 3,
retryDelayMs: 500,
maxRetryDelayMs: 30000,
});
Documentation
Full API docs: docs.beel.es
License
MIT