SDKs overview
Official TypeScript and PHP SDKs for the Factuarea API — install @factuarea/sdk or factuarea/factuarea-php and get retries, idempotency, cursor pagination, typed errors and webhook verification out of the box.
Factuarea’s official SDKs provide automatic retries, idempotency, cursor pagination, typed errors, webhook verification and binary downloads for their released method catalog. REST currently exposes 469 operations across 41 resources; see the release coverage below.
TypeScript / Node.js
@factuarea/sdk on npm. Dual ESM + CommonJS, full type declarations.
Source: github.com/factuarea/factuarea-node.
PHP
factuarea/factuarea-php on Packagist. PSR‑4, Guzzle‑based, PHP 8.2+.
Source: github.com/factuarea/factuarea-php.
Pre‑GA (0.x). Both SDKs are at 0.x. The public method surface is
stable and follows the
SDK method‑naming contract, protected by
SemVer — but while in 0.x, minor versions may include breaking changes
until 1.0.0, which tracks the API's GA. Each release pins one
Factuarea-Version and sends it on every request, so
the API's behaviour stays stable until you upgrade the SDK.
Server‑side only. Your API key is a secret. Never ship an SDK with a live key to a browser, mobile app or any public client — use the SDK from your backend.
Installation
npm install @factuarea/sdkRequires Node 20 or newer. The SDK is built on the Web fetch
standard, so it also runs on Deno, Bun and Cloudflare Workers.
composer require factuarea/factuarea-phpRequires PHP 8.2 or newer with the json and mbstring extensions
(both bundled with standard PHP builds).
Authentication & environments
Pass your API key. The key prefix selects the environment — there is no
separate flag: a fact_test_… key always runs against the isolated
sandbox, a fact_live_… key against production.
import { Factuarea } from "@factuarea/sdk";
const factuarea = new Factuarea({ apiKey: process.env.FACTUAREA_API_KEY! });
factuarea.environment; // "test" or "live", derived from the key prefixOptional configuration:
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
});<?php
require 'vendor/autoload.php';
use Factuarea\Sdk\Custom\FactuareaClient;
// The key prefix selects the environment:
// fact_test_… → sandbox fact_live_… → production
$factuarea = FactuareaClient::create(getenv('FACTUAREA_API_KEY'));FactuareaClient::create() is the recommended entry point: it wires
Bearer authentication and registers the automatic Idempotency-Key
behaviour for you. For advanced configuration (custom Guzzle client,
custom retry policy, staging base URL) the generated builder is still
available:
use Factuarea\Sdk\Factuarea;
use Factuarea\Sdk\Models\Components\Security;
$factuarea = Factuarea::builder()
->setSecurity(new Security(bearerAuth: getenv('FACTUAREA_API_KEY')))
->setServerURL('https://api.factuarea.com/v1')
->build();Quickstart
Create a customer contact and an invoice, then download its PDF using the released SDK methods below. For other operations, check your installed SDK or use the HTTP snippet in the API Reference.
The PHP contact creation below uses HTTP with Guzzle, already required by the PHP SDK. It works without assuming that your installed PHP release has generated contact methods. The invoice SDK keeps clientId; pass the canonical contact id with an active customer role.
import { Factuarea } from "@factuarea/sdk";
const factuarea = new Factuarea({ apiKey: process.env.FACTUAREA_API_KEY! });
// Responses are the API's `{ data: … }` envelope — read the resource off `.data`.
// 1. Create a customer contact.
const { data: contact } = await factuarea.contacts.create({
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({
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 (a BinaryResponse, not JSON).
const pdf = await factuarea.invoices.pdf(invoice.id);
await import("node:fs/promises").then((fs) =>
fs.writeFile("invoice.pdf", pdf.toBuffer()),
);<?php
require 'vendor/autoload.php';
use Factuarea\Sdk\Custom\FactuareaClient;
use Factuarea\Sdk\Models\Components;
use Brick\DateTime\LocalDate;
$factuarea = FactuareaClient::create(getenv('FACTUAREA_API_KEY'));
// 1. Create a customer contact.
$http = new \GuzzleHttp\Client();
$response = $http->post('https://api.factuarea.com/v1/contacts', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('FACTUAREA_API_KEY'),
'Idempotency-Key' => bin2hex(random_bytes(16)),
],
'json' => [
'name' => 'Cliente Demo SL',
'kind' => 'company',
'roles' => ['customer'],
'tax_id' => 'B12345674',
],
]);
$contact = json_decode((string) $response->getBody(), true, 512, JSON_THROW_ON_ERROR)['data'];
// 2. Create an invoice (the API computes the totals).
$invoice = $factuarea->invoices->publicApiV1InvoicesCreate(
new Components\CreateInvoiceRequest(
clientId: $contact['id'],
seriesId: '01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a0e',
issuedOn: LocalDate::parse('2026-06-05'),
dueOn: LocalDate::parse('2026-07-05'),
lines: [
new Components\CreateInvoiceRequestLine(
description: 'Consultoría — junio 2026',
quantity: 10,
unitPrice: 100,
taxRateId: '01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a0f',
),
],
),
);
// 3. Download the PDF.
$pdf = $factuarea->invoices->publicApiV1InvoicesPdf($invoice->object->data->id);
file_put_contents('invoice.pdf', $pdf->bytes ?? '');Run everything with a fact_test_ key first — sandbox effects
(VeriFactu → AEAT, FACe, email, webhooks) are switched off. When your flow works
end‑to‑end, swap the prefix to fact_live_. The API surface is identical
in both. See Test mode & sandbox.
Runtime features
Both SDKs share the same hand‑written runtime on top of the generated typed surface:
- Automatic retries — transient failures (
429and5xx, plus network errors in TypeScript) are retried with exponential backoff and jitter, honouring theRetry-Afterheader. Deterministic client errors (e.g.422validation) are never retried. - Automatic idempotency — every mutation gets a generated
Idempotency-Keyso a retried request never double‑creates a resource. Override it per call when you want app‑level deduplication. See Idempotency. - Cursor auto‑pagination — list methods return an iterable that walks
every page for you, managing
next_cursor/has_more. See Paginating with the SDK. - Typed errors — the API's error envelope maps to a
typed exception hierarchy exposing
code,type,request_idandstatus. Your API key is never included in any error message. See Handling errors. - Webhook verification — a constant‑time HMAC‑SHA256 verifier that honours the secret‑rotation grace window. See Verifying webhooks.
- Binary downloads — PDF and file endpoints return a binary response you turn into a Buffer / stream, not JSON.
Paginating with the SDK
List methods return a Page, which is itself an async iterable:
const page = await factuarea.invoices.list({ status: "paid", limit: 50 });
// (a) iterate every item across every page
for await (const invoice of page) {
console.log(invoice.id);
}
// (b) page by page
page.data; // items on this page
page.hasMore; // boolean
page.nextCursor; // opaque cursor or null
const next = await page.getNextPage(); // Page | null
// (c) collect everything into an array
const all = await page.toArray();The PageIterator helper streams every item across all pages without
manual cursor handling:
use Factuarea\Sdk\Custom\Pagination\PageIterator;
use Factuarea\Sdk\Models\Operations\PublicApiV1InvoicesListRequest;
$pages = new PageIterator(
fn (?string $cursor) => $factuarea->invoices->publicApiV1InvoicesList(
new PublicApiV1InvoicesListRequest(startingAfter: $cursor),
)->rawResponse,
);
// items() yields each item as a decoded associative array.
foreach ($pages->items() as $invoice) {
echo $invoice['id'], PHP_EOL;
}See Pagination for the underlying cursor semantics.
Handling errors
import {
FactuareaError,
ValidationError,
RateLimitError,
} from "@factuarea/sdk";
try {
await factuarea.invoices.create(body);
} catch (error) {
if (error instanceof ValidationError) {
console.error(error.fields); // { tax_id: ["NIF inválido"], … }
} else if (error instanceof RateLimitError) {
console.error(error.retryAfter); // seconds to wait
} else if (error instanceof FactuareaError) {
console.error(error.code, error.requestId);
}
}use Factuarea\Sdk\Models\Errors\ErrorThrowable;
try {
$factuarea->invoices->publicApiV1InvoicesCreate($body);
} catch (ErrorThrowable $e) {
$error = $e->container->error;
echo $error->type->value; // e.g. "invalid_request_error"
echo $error->code; // e.g. "parameter_invalid"
echo $error->param; // e.g. "client_id"
echo $error->requestId; // quote this to support
}Branch on the stable code, never on the human‑facing Spanish message.
The full catalog is in Errors.
Verifying webhooks
Pass the raw request body (not a re‑serialized object), the
Factuarea-Signature header and the endpoint secret:
import { Factuarea, WebhookSignatureError, SIGNATURE_HEADER } from "@factuarea/sdk";
const factuarea = new Factuarea({ apiKey: process.env.FACTUAREA_API_KEY! });
// Express, with express.raw({ type: "application/json" }) on the route:
app.post("/webhooks/factuarea", (req, res) => {
try {
const event = factuarea.webhooks.verify(
req.body.toString("utf8"),
req.headers[SIGNATURE_HEADER.toLowerCase()] as string,
process.env.FACTUAREA_WEBHOOK_SECRET!,
);
if (event.type === "invoice.paid") { /* … */ }
res.sendStatus(200);
} catch (e) {
if (e instanceof WebhookSignatureError) return res.sendStatus(400);
throw e;
}
});use Factuarea\Sdk\Custom\Webhooks\WebhookVerifier;
use Factuarea\Sdk\Custom\Webhooks\WebhookSignatureException;
$verifier = new WebhookVerifier();
$rawBody = file_get_contents('php://input');
$signature = $_SERVER['HTTP_FACTUAREA_SIGNATURE'] ?? '';
try {
$event = $verifier->verify($rawBody, $signature, getenv('FACTUAREA_WEBHOOK_SECRET'));
// $event is the decoded, authenticated payload
} catch (WebhookSignatureException $e) {
http_response_code(400);
}Verification uses HMAC‑SHA256 with a constant‑time comparison and a configurable timestamp tolerance (default 5 minutes) to reject replays, and accepts both signatures during a secret‑rotation grace window. See Webhooks.
Per‑endpoint snippets
Each API Reference page provides TypeScript, PHP and cURL HTTP examples generated from the documented OpenAPI contract. They cover the request, required headers and response format without relying on SDK release coverage.
Generate your own client
If your language isn't covered yet, or you prefer a client you own and check into your repo, the canonical machine contract is the OpenAPI 3.1 spec — point any generator at it.
Read the documented OpenAPI contract, exported from the backend and versioned with this portal.
npx openapi-typescript https://docs.factuarea.com/api/openapi \
-o src/factuarea.d.tsopenapi-python-client generate \
--url https://docs.factuarea.com/api/openapiopenapi-generator-cli generate \
-i https://docs.factuarea.com/api/openapi \
-g <language> -o ./factuarea-client<language> can be any
supported generator —
Go, Java, C#, Ruby, Rust and more.
A generated client won't include the official SDK's runtime (retries, idempotency, pagination, webhook verification) — you wire those yourself following the core concept guides.
Building with an AI assistant?
If you want an AI agent to operate Factuarea directly rather than generate
client code, connect it to the MCP server — the public API exposed as
tools, with OAuth and API-key auth. For Claude Code, the official factuarea-mcp
plugin wires it up in two commands.
Release coverage
The September 2026 baseline is 0.2.0 for both SDKs. Their main-branch specs contain 413 entries, with 39 REST endpoints still absent. A package upgrade is needed before newly generated SDK methods become available. The API Reference provides complete TypeScript fetch, PHP cURL and shell HTTP examples for the documented contract; these examples do not assume an SDK method exists.