Visión general
SDKs oficiales de TypeScript y PHP para la API de Factuarea — instala @factuarea/sdk o factuarea/factuarea-php y obtén reintentos, idempotencia, paginación por cursor, errores tipados y verificación de webhooks de serie.
Factuarea ofrece SDKs oficiales que envuelven toda la API REST v1 (234 operaciones repartidas en 17 recursos) con un runtime premium para que no tengas que escribir HTTP a mano: reintentos automáticos, idempotency keys automáticas, auto‑paginación por cursor transparente, una jerarquía de errores tipada, verificación de webhooks tipada y descargas binarias (PDF).
TypeScript / Node.js
@factuarea/sdk en npm. ESM + CommonJS dual, declaraciones de tipos completas.
Código fuente: github.com/factuarea/factuarea-node.
PHP
factuarea/factuarea-php en Packagist. PSR‑4, basado en Guzzle, PHP 8.2+.
Código fuente: github.com/factuarea/factuarea-php.
Pre‑GA (0.x). Ambos SDKs están en 0.x. La superficie pública de
métodos es estable y sigue el
contrato de nomenclatura de métodos del SDK,
protegido por SemVer — pero mientras esté en 0.x, las versiones minor
pueden incluir cambios incompatibles hasta 1.0.0, que coincide con la GA
de la API. Cada release fija una
Factuarea-Version y la envía en cada request, de
modo que el comportamiento de la API se mantiene estable hasta que
actualizas el SDK.
Solo en el servidor. Tu API key es un secreto. Nunca incluyas un SDK con una clave live en un navegador, app móvil o cualquier cliente público — usa el SDK desde tu backend.
Instalación
npm install @factuarea/sdkRequiere Node 20 o superior. El SDK está construido sobre el estándar
Web fetch, así que también funciona en Deno, Bun y Cloudflare Workers.
composer require factuarea/factuarea-phpRequiere PHP 8.2 o superior con las extensiones json y mbstring
(ambas incluidas en las builds estándar de PHP).
Autenticación y entornos
Pasa tu API key. El prefijo de la clave selecciona el entorno — no hay
un flag aparte: una clave fact_test_… siempre se ejecuta contra el
sandbox aislado, y una clave fact_live_… contra
producción.
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ón 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
});<?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() es el punto de entrada recomendado: conecta la
autenticación Bearer y registra por ti el comportamiento automático de
Idempotency-Key. Para configuración avanzada (cliente Guzzle
personalizado, política de reintentos personalizada, base URL de staging)
el builder generado sigue disponible:
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();Inicio rápido
Crea un cliente y una factura, y luego descarga su PDF. Cada operación es
accesible como <resource>.<method> (TypeScript) o
->{resource}->publicApiV1{Resource}{Action} (PHP) siguiendo el contrato de
nomenclatura — los snippets por endpoint de la
referencia de la API muestran la llamada exacta para cada
operación.
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 client.
const { data: client } = await factuarea.clients.create({
name: "Cliente Demo SL",
tax_id: "B98765432",
});
// 2. Create an invoice (the API computes the totals).
const { data: invoice } = await factuarea.invoices.create({
client_id: client.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 client.
$client = $factuarea->clients->publicApiV1ClientsCreate(
new Components\CreateClientRequest(
name: 'Cliente Demo SL',
taxId: 'B98765432',
),
);
// 2. Create an invoice (the API computes the totals).
$invoice = $factuarea->invoices->publicApiV1InvoicesCreate(
new Components\CreateInvoiceRequest(
clientId: $client->object->data->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 ?? '');Ejecuta todo primero con una clave fact_test_ — los efectos del
sandbox (VeriFactu → AEAT, FACe, email, webhooks) están desactivados. Cuando tu
flujo funcione de extremo a extremo, cambia el prefijo a fact_live_. La
superficie de la API es idéntica en ambos. Consulta Test mode &
sandbox.
Funciones en tiempo de ejecución
Ambos SDKs comparten el mismo runtime escrito a mano sobre la superficie tipada generada:
- Reintentos automáticos — los fallos transitorios (
429y5xx, además de errores de red en TypeScript) se reintentan con backoff exponencial y jitter, respetando el headerRetry-After. Los errores de cliente deterministas (p. ej. validación422) nunca se reintentan. - Idempotencia automática — cada mutación recibe una
Idempotency-Keygenerada para que un request reintentado nunca cree un recurso por duplicado. Anúlala por llamada cuando quieras deduplicación a nivel de app. Consulta Idempotency. - Auto‑paginación por cursor — los métodos de listado devuelven un
iterable que recorre todas las páginas por ti, gestionando
next_cursor/has_more. Consulta Paginar con el SDK. - Errores tipados — el envoltorio de error de la API se
mapea a una jerarquía de excepciones tipada que expone
code,type,request_idystatus. Tu API key nunca se incluye en ningún mensaje de error. Consulta Gestionar errores. - Verificación de webhooks — un verificador HMAC‑SHA256 de tiempo constante que respeta la ventana de gracia de rotación de secreto. Consulta Verificar webhooks.
- Descargas binarias — los endpoints de PDF y de ficheros devuelven una respuesta binaria que conviertes en un Buffer / stream, no JSON.
Paginación con el SDK
Los métodos de listado devuelven un Page, que es en sí mismo un 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();El helper PageIterator transmite cada elemento a través de todas las
páginas sin gestión manual del cursor:
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;
}Consulta Pagination para conocer la semántica de cursor subyacente.
Gestión de errores
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
}Ramifica según el code estable, nunca según el message en español
orientado a personas. El catálogo completo está en Errors.
Verificación de webhooks
Pasa el cuerpo crudo del request (no un objeto re‑serializado), el header
Factuarea-Signature y el secreto del endpoint:
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);
}La verificación usa HMAC‑SHA256 con una comparación de tiempo constante y una tolerancia de timestamp configurable (5 minutos por defecto) para rechazar replays, y acepta ambas firmas durante una ventana de gracia de rotación de secreto. Consulta Webhooks.
Snippets por endpoint
Cada página de la referencia de la API muestra un snippet de TypeScript, PHP y cURL listo para copiar para esa operación exacta, generado a partir del spec para que nunca se desvíen de la superficie real.
Genera tu propio cliente
Si tu lenguaje todavía no está cubierto, o prefieres un cliente que tú controles y guardes en tu repo, el contrato canónico legible por máquina es el spec OpenAPI 3.1 — apunta cualquier generador a él.
El spec vive en
https://docs.factuarea.com/api/openapi. Se genera a partir
del mismo backend que sirve la API, así que nunca se desvía de la superficie
real.
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> puede ser cualquier
generador soportado —
Go, Java, C#, Ruby, Rust y más.
Un cliente generado no incluirá el runtime del SDK oficial (reintentos, idempotencia, paginación, verificación de webhooks) — eso lo conectas tú mismo siguiendo las guías de conceptos clave.
¿Construyes con un asistente de IA?
Si quieres que un agente de IA opere Factuarea directamente en lugar de
generar código de cliente, conéctalo al servidor MCP — la API pública
expuesta como tools, con autenticación OAuth y por API key. Para Claude Code,
el plugin oficial factuarea-mcp lo configura en
dos comandos.