Factuarea APIDevelopers

Resumen de los SDKs

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.

Los SDK oficiales de Factuarea ofrecen reintentos automáticos, idempotencia, paginación por cursor, errores tipados, verificación de webhooks y descargas binarias para su catálogo de métodos publicado. REST expone actualmente 718 operaciones en 53 recursos; consulta la cobertura de versiones al final.

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/sdk

Requiere 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-php

Requiere 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 prefix

Configuració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 contacto con rol de cliente y una factura, y descarga su PDF con los métodos publicados del SDK que aparecen a continuación. Para otras operaciones, comprueba tu SDK instalado o usa el ejemplo HTTP de la referencia de la API.

La creación del contacto en PHP usa HTTP con Guzzle, que el SDK PHP ya requiere. Así funciona sin asumir que tu versión PHP instalada tiene métodos de contactos generados. El SDK de facturas conserva clientId: pasa el id del contacto con rol customer activo.

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/companies/' . getenv('FACTUAREA_COMPANY_ID') . '/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 ?? '');

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 (429 y 5xx, además de errores de red en TypeScript) se reintentan con backoff exponencial y jitter, respetando el header Retry-After. Los errores de cliente deterministas (p. ej. validación 422) nunca se reintentan.
  • Idempotencia automática — cada mutación recibe una Idempotency-Key generada 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_id y status. 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.

Modelo de cuentas

Cada credencial pertenece a una cuenta, y la cuenta es titular de sus NIF. El eje de cuenta cuelga de /v1/accounts/{account}/…, junto al eje de empresa (/v1/companies/{company}/…). Los dos SDK lo cubren con estos nombres:

  • Cartera de NIF de la cuenta. TypeScript: factuarea.companies.list, factuarea.companies.create y factuarea.companies.stats. PHP: $factuarea->companies->publicApiV1CompaniesList, publicApiV1CompaniesCreate y publicApiV1CompaniesStats.
  • Traspaso de NIF, que sustituye al vale de reclamación. TypeScript: factuarea.account.nifTransfers.create, .list, .accept y .revoke. PHP: $factuarea->account->nifTransfers->publicApiV1AccountNifTransfersCreate, publicApiV1AccountNifTransfersList, publicApiV1AccountNifTransfersAccept y publicApiV1AccountNifTransfersRevoke.
  • Cuentas gestionadas. TypeScript: factuarea.managedAccounts.list, .create, .show, más .accessLevel, .plan y .management. PHP: $factuarea->managedAccounts->publicApiV1ManagedAccountsList, publicApiV1ManagedAccountsCreate y publicApiV1ManagedAccountsShow, más los grupos accessLevel, plan y management.
  • Importación masiva de cuentas gestionadas, enviada como multipart/form-data: accounts_file (obligatorio), customers_file (opcional) y las options send_email, apply_customers_to_own_company y confirm_plan_changes. TypeScript: factuarea.managedAccounts.imports.preview, .create y .template; los dos primeros reciben un FormData estándar. PHP: $factuarea->managedAccounts->imports->publicApiV1ManagedAccountsImportsPreview, publicApiV1ManagedAccountsImportsCreate y publicApiV1ManagedAccountsImportsTemplate, con un Components\ManagedAccountImportV1Request que lleva un Components\AccountsFile.
  • Webhooks y registros de peticiones de la cuenta. TypeScript: factuarea.account.webhooks (create, list, show, update, delete, rotateSecret, test, y deliveries.list, .show y .retry) y factuarea.account.requestLogs (list, show). PHP: $factuarea->account->webhooks (publicApiV1AccountWebhooksCreate y sus hermanos, con ->deliveries) y $factuarea->account->requestLogs (publicApiV1AccountRequestLogsList, publicApiV1AccountRequestLogsShow). Escribir un webhook de cuenta exige una clave fact_live_.

El argumento de cuenta es posicional. En TypeScript, los métodos del eje de cuenta reciben el id de la cuenta como primer argumento, antes de cualquier otro id y antes del cuerpo. Quien llama desde TypeScript recibe un error de compilación, pero quien llama desde JavaScript puro desplazaría cada argumento una posición sin ningún error: revisa cada llamada al actualizar. El id de la cuenta es el que la credencial informa de sí misma:

// `factuarea.account.show()` llama a GET /v1/me; el id de la cuenta es `data.account.id`.
const me = (await factuarea.account.show()) as { data: { account: { id: string } } };
const account = me.data.account.id;
// `publicApiV1AccountShow()` llama a GET /v1/me; el id de la cuenta es `data->account->id`.
$account = $factuarea->account->publicApiV1AccountShow()->object->data->account->id;

Los ejemplos completos están en las páginas de TypeScript y de PHP.

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.

Entregas de cuenta

Una entrega de un webhook de cuenta se verifica con el mismo verificador y la misma firma que una de empresa, así que tu manejador no cambia. Su sobre añade tres campos: account ({ "id": "<uuid de la cuenta>" }), account_relationship (own o managed, nunca all) y company_id (el NIF al que se refiere el evento, o null en los eventos account.* que no son de un NIF). La entrega lleva además la cabecera Factuarea-Account-Relationship, con el mismo valor own o managed.

const event = factuarea.webhooks.verify(
  req.body.toString("utf8"),
  req.headers[SIGNATURE_HEADER.toLowerCase()] as string,
  process.env.FACTUAREA_WEBHOOK_SECRET!,
) as unknown as {
  type: string;
  account: { id: string };
  account_relationship: "own" | "managed";
  company_id: string | null;
};

if (event.company_id === null) {
  // Un evento de la propia cuenta, como account.member_added.
}
$event = $verifier->verify($rawBody, $signature, getenv('FACTUAREA_WEBHOOK_SECRET'));

$accountId    = $event['account']['id'];
$relationship = $event['account_relationship']; // "own" or "managed"
$nifId        = $event['company_id'];           // null en un evento de la propia cuenta

Snippets por endpoint

Cada página de la referencia de la API ofrece ejemplos HTTP en TypeScript, PHP y cURL generados desde el contrato OpenAPI documentado. Cubren la petición, sus cabeceras necesarias y el formato de respuesta sin depender de la cobertura de una versión del SDK.

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.

Consulta el contrato OpenAPI documentado, exportado del backend y versionado con este portal.

npx openapi-typescript https://docs.factuarea.com/api/openapi \
  -o src/factuarea.d.ts
openapi-python-client generate \
  --url https://docs.factuarea.com/api/openapi
openapi-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.

Cobertura de las versiones

La referencia de septiembre de 2026 es 0.2.0 para ambos SDK. Los specs de sus ramas principales contienen 413 entradas y todavía les faltan 39 endpoints REST. Es necesario actualizar el paquete para disponer de nuevos métodos generados. La referencia de la API ofrece ejemplos HTTP completos con fetch de TypeScript, cURL de PHP y terminal para el contrato documentado; estos ejemplos no presuponen que exista un método del SDK.

El modelo de cuentas (la cartera de NIF, el traspaso de NIF, las cuentas gestionadas con su importación masiva y los webhooks y registros de peticiones de la cuenta) lo trae PHP 0.5.0 y Node 0.7.0. Son las versiones objetivo: esta página no significa que estén publicadas, así que consulta el registro del paquete antes de actualizar. La referencia de la API cubre las mismas operaciones con ejemplos HTTP que no presuponen que exista un método del SDK.

En esta página

¿Te echamos una mano?Contactar con soporte