Factuarea APIDevelopers

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.

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

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

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

Optional 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 (429 and 5xx, plus network errors in TypeScript) are retried with exponential backoff and jitter, honouring the Retry-After header. Deterministic client errors (e.g. 422 validation) are never retried.
  • Automatic idempotency — every mutation gets a generated Idempotency-Key so 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_id and status. 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.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> 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.

On this page

Need a hand?Contact support