TypeScript
Instal·la @factuarea/sdk per a Node.js, autentica't i crea la teva primera factura. ESM + CommonJS dual, declaracions de tipus completes, Node 20+.
L'SDK oficial de TypeScript és @factuarea/sdk
a npm — ESM + CommonJS dual amb declaracions de tipus completes. Codi font:
github.com/factuarea/factuarea-node.
Embolcalla l'API REST v1 amb reintents automàtics, idempotency keys,
auto-paginació per cursor, una jerarquia d'errors tipada i verificació de
webhooks — tot cobert a la introducció a l'SDK.
Instal·lació
npm install @factuarea/sdkRequereix Node 20 o superior. L'SDK està construït sobre l'estàndard Web
fetch, així que també funciona a Deno, Bun i Cloudflare Workers.
Autenticació
Passa la teva API key. El prefix de la clau selecciona l'entorn — no hi ha
un flag a part: una clau fact_test_… sempre s'executa contra el
sandbox aïllat, i una clau fact_live_… contra producció.
import { Factuarea } from "@factuarea/sdk";
const factuarea = new Factuarea({ apiKey: process.env.FACTUAREA_API_KEY! });
factuarea.environment; // "test" or "live", derived from the key prefixConfiguració opcional:
new Factuarea({
apiKey: "fact_live_…", // required
baseUrl: "https://api.factuarea.com/v1", // override for staging
timeout: 60_000, // per-request ms (default 60s)
maxRetries: 2, // attempts after the first try
factuareaVersion: "2026-06-04", // pinned API version header
defaultHeaders: {}, // extra headers on every request
});Només al servidor. La teva API key és un secret. No distribueixis mai l'SDK amb una clau live a un navegador, app mòbil o qualsevol client públic — fes-la servir des del teu backend.
Inici ràpid
Crea un contacte amb rol de client i una factura, i descarrega’n el PDF amb els mètodes publicats de l’SDK que apareixen a continuació. Per a altres operacions, comprova l’SDK instal·lat o fes servir l’exemple HTTP de la referència de l’API.
import { writeFile } from "node:fs/promises";
import { Factuarea } from "@factuarea/sdk";
const factuarea = new Factuarea({ apiKey: process.env.FACTUAREA_API_KEY! });
// Company axis: every company resource hangs off `/v1/companies/{company}/…`, so the
// company id is the FIRST argument of each call (`data.scope[].id` of GET /v1/me).
const company = process.env.FACTUAREA_COMPANY_ID!;
// Responses are the API's `{ data: … }` envelope — read the resource off `.data`.
// 1. Create a customer contact.
const { data: contact } = await factuarea.contacts.create(company, {
name: "Cliente Demo SL",
kind: "company",
roles: ["customer"],
tax_id: "B12345674",
});
// 2. Create an invoice (the API computes the totals).
const { data: invoice } = await factuarea.invoices.create(company, {
client_id: contact.id,
series_id: "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a0e",
issued_on: "2026-06-05",
due_on: "2026-07-05",
lines: [
{
description: "Consultoría — junio 2026",
quantity: 10,
unit_price: 100,
tax_rate_id: "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a0f",
},
],
});
// 3. Download the PDF. It is a BinaryOrJsonResponse: narrow it on `kind` first.
const pdf = await factuarea.invoices.pdf(company, invoice.id);
if (pdf.kind === "binary") {
await writeFile("invoice.pdf", pdf.toBuffer());
} else {
// 202 (`kind: "json"`): still being generated. Ask again after `pdf.retryAfter` seconds.
console.log("The PDF is not ready yet:", pdf.status);
}Executa-ho tot primer amb una clau fact_test_ — els efectes del sandbox
(VeriFactu → AEAT, FACe, email, webhooks) estan desactivats. Quan el teu flux
funcioni d'extrem a extrem, canvia el prefix a fact_live_. Consulta
Mode de prova i sandbox.
Model de comptes
L'eix de compte (/v1/accounts/{account}/…) el cobreixen factuarea.companies (la cartera de NIF), factuarea.account.nifTransfers, factuarea.managedAccounts i, per a webhooks i registres de peticions, factuarea.account.webhooks i factuarea.account.requestLogs. Arriben amb 0.7.0, la versió objectiu del model de comptes; encara no està publicada.
L'argument de compte és posicional. Els mètodes de l'eix de compte reben
l'id del compte com a primer argument, abans de qualsevol altre id i abans del
cos. Qui crida des de TypeScript rep un error de compilació, però qui crida des
de JavaScript pur desplaçaria cada argument una posició sense cap error: revisa
cada crida a account.*, companies.* i managedAccounts.* en actualitzar.
L'exemple llista els NIF del compte, aplica una importació massiva de comptes gestionats amb un FormData (accounts_file és obligatori, customers_file és opcional i les opcions viatgen com a camps options[...]) i crea un webhook de compte:
import { readFile } from "node:fs/promises";
import { Factuarea } from "@factuarea/sdk";
const factuarea = new Factuarea({ apiKey: process.env.FACTUAREA_API_KEY! });
// L'id del compte és `data.account.id` de `factuarea.account.show()` (GET /v1/me).
const me = (await factuarea.account.show()) as { data: { account: { id: string } } };
const account = me.data.account.id;
// 1. Llista els NIF del compte (l'id del compte va PRIMER).
const nifs = await factuarea.companies.list(account, { limit: 50 });
for await (const nif of nifs) {
console.log((nif as { id: string }).id);
}
// 2. Previsualitza i aplica una importació massiva de comptes gestionats (multipart/form-data).
const form = new FormData();
form.append(
"accounts_file",
new Blob([await readFile("accounts.csv")], { type: "text/csv" }),
"accounts.csv",
);
form.append("options[send_email]", "false");
await factuarea.managedAccounts.imports.preview(form); // no escriu res
await factuarea.managedAccounts.imports.create(form); // l'aplica
// 3. Crea un webhook de compte (exigeix una clau fact_live_).
const created = (await factuarea.account.webhooks.create(account, {
url: "https://example.com/webhooks/factuarea",
enabled_events: ["account.nif_added", "invoice.paid"],
account_relationship: "all",
})) as { data: { id: string; secret: string | null } };
// `secret` (whsec_…) només es retorna ara: guarda'l per verificar els lliuraments.
console.log(created.data.id, created.data.secret);El traspàs de NIF té la mateixa forma: factuarea.account.nifTransfers.create(account, body), .list(account), .accept(account, body) i .revoke(account, nifTransfer). Un lliurament d'un webhook de compte es verifica amb factuarea.webhooks.verify, igual que un d'empresa; consulta Verificació de webhooks.
Plantilles PDF
La biblioteca de plantilles PDF té els seus propis recursos: factuarea.pdfTemplates (plantilles, revisions, valors per defecte de l'empresa i de cada contacte, imatges privades), factuarea.pdfCommercialIdentities, factuarea.pdfTemplatePreviews i factuarea.pdfArtifacts (prepare, render, show i download). Les factures, els pressupostos, les factures proforma i els albarans guanyen getPresentation, listPresentations, assignPresentation i revisePresentation, i factuarea.series guanya getPdfTemplate i assignPdfTemplate. Arriben amb la pròxima versió de @factuarea/sdk; fins que actualitzis, la referència de l'API té l'exemple HTTP de cada operació.
Com tot recurs d'empresa, pengen de /v1/companies/{company}/…, així que l'id de l'empresa és el primer argument de cada crida, abans dels altres ids i del cos. És l'id d'una entrada de data.scope a GET /v1/me; consulta el segment d'empresa. Els recursos de la biblioteca arriben com l'objecte tal qual, sense l'embolcall data. Els seus llistats paginen per page i per_page: cada crida és una petició i retorna { items, meta } tal qual, no un iterador que pagina sol.
import { Factuarea } from "@factuarea/sdk";
const factuarea = new Factuarea({ apiKey: process.env.FACTUAREA_API_KEY! });
const company = process.env.FACTUAREA_COMPANY_ID!; // `data.scope[].id` of GET /v1/me
const invoiceId = "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a9b";
// 1. Create a template: only `name` is required.
const template = await factuarea.pdfTemplates.create(company, {
name: "Factura clásica",
base_preset: "classic",
});
// 2. List the library, one page per call.
const page = await factuarea.pdfTemplates.list(company, { page: 1, per_page: 20 });
for (const item of page.items) {
console.log(item.id, item.name);
}
// 3. Pin the template on an invoice. `expected_version` is the `selection_version`
// of the presentation state; `template_id: null` hands the choice back to the defaults.
const state = await factuarea.invoices.getPresentation(company, invoiceId);
await factuarea.invoices.assignPresentation(company, invoiceId, {
template_id: template.id,
expected_version: state.data.selection_version,
});Consulta la biblioteca de plantilles PDF, Presentació de documents i Vistes prèvies i artefactes PDF per al model.
Peticions condicionals i descàrregues
Cada mètode de recurs rep les opcions de la petició (RequestConfig) com a últim argument. A més d'idempotencyKey, timeout, maxRetries i headers, aquestes opcions cobreixen la concurrència, les descàrregues i els reintents. Arriben amb la mateixa versió que els recursos de plantilles:
| Opció | Què fa |
|---|---|
ifMatch | Envia If-Match tal qual: l'ETag de l'última lectura del recurs que canvies. Un tag desfasat respon 412; una escriptura que exigeix precondició i no la rep respon 428. |
ifNoneMatch | Envia If-None-Match tal qual: l'ETag d'una còpia que ja tens. Si aquesta còpia continua vigent, la descàrrega respon 304. |
onResponse | Callback que rep el ResponseMeta de la resposta final, amb èxit o sense: status, headers, requestId, etag, retryAfter (segons, o null) i idempotentReplayed. |
noRetry | Desactiva els reintents automàtics només per a aquesta crida; el client compartit no canvia. |
signal | Un AbortSignal. No es fa cap intent més i la crida llança un ConnectionError; una escriptura que el servidor ja ha acceptat no es desfà. |
Llegeix l'ETag i retorna'l
Els mètodes de recurs retornen només el cos descodificat, així que llegeixes l'ETag amb onResponse. L'expected_version d'un cos és una comprovació a part que respon 409 quan està desfasat. Un null al cos s'envia com a null; només s'omet una clau que deixes fora (o que val undefined). Els fragments reutilitzen factuarea, company i invoiceId de l'exemple anterior.
const templateId = "0199f3b2-5c1d-7e8f-9a0b-1c2d3e4f5a6b";
// Read the template and keep the ETag of the response.
let etag: string | null = null;
await factuarea.pdfTemplates.find(company, templateId, {
onResponse: (meta) => {
etag = meta.etag;
},
});
// Send it back as If-Match. `null` clears the description; `name` is not sent, so it is kept.
await factuarea.pdfTemplates.update(
company,
templateId,
{ description: null },
{ ifMatch: etag ?? undefined },
);Consulta Concurrència per als codis d'error i com recuperar-te'n.
Descàrregues: bytes, 202 o 304
invoices.pdf, quotes.pdf, proformas.pdf, deliveryNotes.pdf, pdfArtifacts.download i pdfTemplates.assetGet retornen un BinaryOrJsonResponse, una unió que acotes per kind abans de fer servir el resultat:
kind | Quan | Què porta |
|---|---|---|
"binary" | 200 amb els bytes. | toBuffer(), toBlob(), body, contentType i etag. |
"json" | Un document JSON, com el 202 d'un PDF de document que encara s'està generant. | data, status i retryAfter (segons, o null). |
"not_modified" | 304: has enviat ifNoneMatch i la teva còpia està vigent. | Sense cos; status, headers i etag. |
import { writeFile } from "node:fs/promises";
import { setTimeout as sleep } from "node:timers/promises";
// `cachedEtag` is the ETag of the copy you already hold, if any.
let cachedEtag: string | undefined;
for (let attempt = 0; attempt < 5; attempt++) {
const pdf = await factuarea.invoices.pdf(company, invoiceId, undefined, { ifNoneMatch: cachedEtag });
if (pdf.kind === "binary") {
await writeFile("invoice.pdf", pdf.toBuffer());
cachedEtag = pdf.etag ?? undefined;
break;
}
if (pdf.kind === "not_modified") {
break; // 304: your copy is current.
}
await sleep((pdf.retryAfter ?? 1) * 1000); // 202: still being generated.
}pdfArtifacts.download d'un artefacte que encara no està llest respon 503 pdf_artifact_not_ready. L'SDK el reintenta com qualsevol 5xx, respectant Retry-After; si es rendeix, l'error que llança porta retryAfter. pdfTemplates.assetGet respon per defecte amb les metadades JSON de la imatge; demana els bytes amb { headers: { Accept: "image/png" } }. Consulta Com responen les descàrregues.
Reintents que desactives
L'enviament i la cancel·lació a FACe (invoices.faceSubmissions.submit i faceSubmissions.cancel) arriben a un tercer, així que l'SDK mai els reintenta automàticament, digui el que digui maxRetries. Després d'un error de connexió el resultat és desconegut: consulta els enviaments abans de tornar a enviar. Passa noRetry: true per tenir el mateix comportament en qualsevol altra crida.
Pròxims passos
El comportament en temps d'execució — reintents, idempotència, auto-paginació per cursor, la jerarquia d'errors tipada i la verificació de webhooks — és comú a tots dos SDK i està documentat una sola vegada a la introducció a l'SDK:
Cobertura de les versions
La referència de setembre de 2026 és 0.2.0 per a tots dos SDK. Els specs de les seves branques principals contenen 413 entrades i encara els falten 39 endpoints REST. Cal actualitzar el paquet per disposar de nous mètodes generats. La referència de l’API ofereix exemples HTTP complets amb fetch de TypeScript, cURL de PHP i terminal per al contracte documentat; aquests exemples no pressuposen que existeixi cap mètode de l’SDK.