Factuarea API

Quickstart

Your first invoice in 5 minutes — verify your key, grab a series and a tax, create a client, issue an invoice and send it. One copy-paste sequence against a fact_test_ key.

This guide takes you from a fresh API key to a real (sandbox) invoice in five steps. Every call below is copy-paste runnable against a fact_test_ key — no real emails, no AEAT submission, no production numbering consumed. See Test mode & sandbox for what "test" switches off.

Prefer an SDK? If you're on TypeScript/Node or PHP, the official SDKs wrap this whole flow with built-in retries, idempotency, cursor pagination and typed errors — npm install @factuarea/sdk or composer require factuarea/factuarea-php. The raw HTTP steps below work in any language and show exactly what the SDK sends under the hood.

Run everything with a fact_test_ key first. The API surface is identical in live and test — when your flow works end-to-end, swap the prefix to fact_live_ to go to production. Get a test key from Settings → Developers → API Keys.

Export your key once so every snippet picks it up:

export FACTUAREA_API_KEY="fact_test_3pXnR2VbY7TcA9eFmN5z8KqW"

The base URL is https://api.factuarea.com/v1. Authenticate with Authorization: Bearer (or the equivalent X-API-Key header). Identifiers are opaque id values (UUID v7); you copy them from one response into the next.

Verify your key

GET /v1/account introspects the credential: the company it belongs to, the plan, the developer addon status and the scopes and rate-limit tier of the key itself. It needs the account:read scope.

curl https://api.factuarea.com/v1/account \
  -H "Authorization: Bearer $FACTUAREA_API_KEY"
{
  "data": {
    "object": "account",
    "company": {
      "id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a01",
      "name": "Acme Soluciones SL",
      "tax_id": "B12345678"
    },
    "plan": {
      "slug": "empresario",
      "name": "Empresario"
    },
    "addon": {
      "active": true,
      "in_grace": false,
      "expires_at": null
    },
    "api_key": {
      "id": "01931b3e-7c4a-7f2e-9a8b-4d6e7f8a9b0c",
      "name": "Sandbox integration",
      "prefix": "fact_test_3pXnR2Vb",
      "scopes": [
        "account:read",
        "series:read",
        "taxes:read",
        "clients:write",
        "invoices:write",
        "invoices:send",
        "pdfs:read"
      ],
      "tier": "starter",
      "created_at": "2026-05-01T09:30:00Z",
      "last_used_at": "2026-06-02T08:12:00Z",
      "expires_at": null
    }
  }
}

A 200 here means the key is valid and you can see exactly which scopes it carries. If you get 401 invalid_api_key, re-check the value; if a later step fails with 403 insufficient_scope, the scopes array above tells you what's missing.

Grab the ids you'll need

An invoice references a series (its numbering) and each line references a tax rate. Both are existing resources you list once and reuse.

A series id

GET /v1/series returns your numbering series. Pick one whose document_type is invoice (the is_default one is a safe choice). Needs series:read.

curl "https://api.factuarea.com/v1/series?document_type=invoice" \
  -H "Authorization: Bearer $FACTUAREA_API_KEY"
{
  "data": [
    {
      "id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a0e",
      "object": "series",
      "code": "F-2026",
      "name": "Facturas 2026",
      "document_type": "invoice",
      "prefix": "F-2026-",
      "next_number": 46,
      "year_reset": true,
      "is_default": true,
      "is_active": true,
      "created_at": "2026-01-01T00:00:00Z",
      "updated_at": "2026-01-20T11:30:00Z"
    }
  ],
  "has_more": false,
  "next_cursor": null
}

Copy the id (01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a0e) — that's your series_id.

A tax rate id

GET /v1/taxes returns the tax catalog (global system taxes + your custom ones). For a standard Spanish invoice line you want the VAT 21% rate — look for type: "vat" and rate: 21. Needs taxes:read.

curl "https://api.factuarea.com/v1/taxes?type=vat" \
  -H "Authorization: Bearer $FACTUAREA_API_KEY"
{
  "data": [
    {
      "id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a0f",
      "object": "tax",
      "name": "IVA general 21%",
      "code": "IVA21",
      "rate": 21,
      "type": "vat",
      "applies_to": "both",
      "country": "ES",
      "is_default": true,
      "is_active": true,
      "is_system": true
    }
  ],
  "has_more": false,
  "next_cursor": null
}

Copy this id — on an invoice line it goes into tax_rate_id.

tax_rate_id vs tax_rate. On a line you can reference a catalog rate by tax_rate_id, or skip the lookup and pass the numeric percentage directly as tax_rate (e.g. "tax_rate": 21). Use one or the other per line — tax_rate_id keeps the line tied to your catalog, tax_rate is a quick inline override.

Create a client

The invoice needs someone to bill. The minimal client body is name plus tax_id (the Spanish fiscal identifier — NIF/CIF/NIE). Needs clients:write.

This is a write — send an Idempotency-Key so a retried request never creates a duplicate client. See Idempotency.

curl -X POST https://api.factuarea.com/v1/clients \
  -H "Authorization: Bearer $FACTUAREA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "name": "Cliente Demo SL",
    "tax_id": "B98765432"
  }'
{
  "data": {
    "id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a01",
    "object": "client",
    "name": "Cliente Demo SL",
    "commercial_name": null,
    "tax_id": "B98765432",
    "vat_id": null,
    "email": null,
    "phone": null,
    "contact_person": null,
    "billing_emails": [],
    "address": {
      "line1": null,
      "postal_code": null,
      "city": null,
      "province": null,
      "country": null
    },
    "coordinates": null,
    "notes": null,
    "metadata": {},
    "is_active": true,
    "created_at": "2026-06-02T10:30:00Z",
    "updated_at": "2026-06-02T10:30:00Z"
  }
}

(Optional fields you didn't send come back as null; address is always an object whose sub-keys are filled in as you provide them.) Copy the returned id (01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a01) — that's your client_id.

Create the invoice

Now combine the three ids. POST /v1/invoices requires client_id, series_id, issued_on, due_on and at least one line. Each line needs description, quantity and unit_price; add tax_rate_id (or tax_rate) to apply VAT. Optional per line: discount_percent and product_id. Needs invoices:write.

curl -X POST https://api.factuarea.com/v1/invoices \
  -H "Authorization: Bearer $FACTUAREA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "client_id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a01",
    "series_id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a0e",
    "issued_on": "2026-06-02",
    "due_on": "2026-07-02",
    "lines": [
      {
        "description": "Consultoría — junio 2026",
        "quantity": 10,
        "unit_price": 100,
        "tax_rate_id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a0f",
        "discount_percent": 0
      }
    ]
  }'

The API computes the line and document totals for you and returns the invoice envelope. A freshly created invoice starts as a draft: no definitive number yet (is_number_assigned: false).

{
  "data": {
    "id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a42",
    "object": "invoice",
    "number": null,
    "is_number_assigned": false,
    "type": "F1",
    "series": {
      "id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a0e",
      "code": "F-2026"
    },
    "client": {
      "id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a01",
      "name": "Cliente Demo SL"
    },
    "status": "draft",
    "issued_on": "2026-06-02",
    "due_on": "2026-07-02",
    "subtotal": 1000,
    "taxes_total": 210,
    "total": 1210,
    "currency": "EUR",
    "notes": null,
    "lines": [
      {
        "object": "invoice_line",
        "description": "Consultoría — junio 2026",
        "product": null,
        "quantity": 10,
        "unit_price": 100,
        "tax_rate": 21,
        "discount_percent": 0,
        "subtotal": 1000,
        "taxes": 210,
        "total": 1210
      }
    ],
    "metadata": {},
    "operation_regime": "general",
    "verifactu_status": "not_applicable",
    "is_corrective": false,
    "corrective": null,
    "payment": null,
    "public_link": null,
    "substituted_by": null,
    "recurring": null,
    "paid_at": null,
    "paid_on": null,
    "sent_at": null,
    "voided_at": null,
    "void_reason": null,
    "created_at": "2026-06-02T10:31:00Z",
    "updated_at": "2026-06-02T10:31:00Z"
  }
}

Notice the computed money fields: the line subtotal (10 × 100 = 1000), its taxes (21% of 1000 = 210) and total (1210), aggregated into the invoice's subtotal / taxes_total / total. Copy the invoice id (01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a42) for the next step.

Get the PDF and send it

With the invoice id you can download its PDF and email it to the client.

GET /v1/invoices/{id}/pdf streams the binary PDF (application/pdf). Needs pdfs:read. Save it straight to a file with curl's -o:

curl https://api.factuarea.com/v1/invoices/01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a42/pdf \
  -H "Authorization: Bearer $FACTUAREA_API_KEY" \
  -o invoice.pdf

POST /v1/invoices/{id}/send emails the invoice to the client. With no body it uses the client's email on file; you can override the recipient and copy with to, cc, bcc, subject and body. Needs invoices:send.

Because you're on a fact_test_ key, the email is not delivered to any real recipient (sandbox effects are off). The call still succeeds and the invoice transitions as it would in production — perfect for wiring up your flow without spamming anyone.

curl -X POST https://api.factuarea.com/v1/invoices/01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a42/send \
  -H "Authorization: Bearer $FACTUAREA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "to": "demo@example.com",
    "subject": "Tu factura de Acme Soluciones SL"
  }'

The response is the updated invoice envelope (same shape as above), now with sent_at populated.

That's it

You verified a key, discovered the ids it needs, created a client, issued an invoice with server-computed totals, and sent it — all against an isolated sandbox. From here, point the same code at a fact_live_ key to operate on your real company.

On this page