Factuarea APIDevelopers

Quickstart

Your first invoice in 5 minutes — verify your key, grab a series and a tax, create a contact, 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/me introspects the credential. It needs no scope: any valid key can read itself, and it is how you learn the opaque id of the tax IDs the key reaches. That id is the {company} segment every company route carries.

curl https://api.factuarea.com/v1/me \
  -H "Authorization: Bearer $FACTUAREA_API_KEY"

This is the answer of a live key of an account provider, so every block is filled. With your fact_test_ key, api_key.prefix and api_key.environment read test.

{
  "data": {
    "object": "me",
    "account": {
      "id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a00",
      "name": "Asesoría Martínez",
      "environment": "live",
      "is_provider": true,
      "privileged_scopes": [
        "accounts:read",
        "accounts:write"
      ]
    },
    "api_key": {
      "id": "019e81ef-e1d4-7208-a51d-94c2c2a54a84",
      "name": "Producción",
      "prefix": "fact_live_1OSf9KdP",
      "scopes": [
        "account:read",
        "series:read",
        "series:write"
      ],
      "tier": "scale",
      "environment": "live",
      "created_at": "2026-01-15T09:30:00Z",
      "last_used_at": "2026-05-25T18:00:00Z",
      "expires_at": null
    },
    "scope": [
      {
        "id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a01",
        "tax_id": "B12345674",
        "name": "Acme Corporation"
      },
      {
        "id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a02",
        "tax_id": "B87654323",
        "name": "Acme Logística S.L."
      }
    ],
    "client_accounts": [
      {
        "id": "0199a1f0-1c22-7a10-b111-5a5b5c5d5e5f",
        "name": "Construcciones Ruiz",
        "origin": "granted",
        "access_level": "view",
        "status": "active",
        "paid_by_provider": false,
        "environment": "live"
      },
      {
        "id": "0199a1f0-1c22-7a10-b111-6a6b6c6d6e6f",
        "name": "Talleres García",
        "origin": "provisioned",
        "access_level": "operate",
        "status": "provisioned",
        "paid_by_provider": true,
        "environment": "live"
      }
    ],
    "client_accounts_visible": true,
    "client_accounts_has_more": false
  }
}

A 200 here means the key is valid. Read the answer like this:

FieldWhat it tells you
accountThe account of the key. id is the value of the {account} segment of the account routes, is_provider says whether the account provisions managed accounts for its clients and privileged_scopes lists the privileged scopes Factuarea granted it.
api_keyThe key itself. scopes are the effective scopes: a scope that the plan of the tax IDs it reaches does not allow is not listed.
scopeThe tax IDs the key reaches, each with its id, tax_id and name. It is always enumerated, also when there is exactly one.
client_accountsThe accounts of your clients that your account reaches through a relation. origin is granted when they gave you access and provisioned when you provisioned them and still manage them. It is filled only when the key carries the privileged accounts:read scope; otherwise it is empty and client_accounts_visible is false.

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. The key in this example only carries account:read, series:read and series:write: every step below names the scope it needs, so compare it with the scopes of your own key. Accounts and tax IDs describes every field.

Choose the {company}

The tax IDs a key reaches can be of three kinds, and all of them go in the same {company} segment:

The tax ID is…Where you read its id
Your ownIn scope.
Granted by another accountIn scope, once the scope of the key names it. The account that granted it appears in client_accounts with origin: "granted". See granted access.
Of a managed account you provisionedIn scope, once the scope of the key names it and your level on the account is not none. The account appears in client_accounts with origin: "provisioned", and companies[] of GET /v1/accounts/{account} lists its tax IDs, with that id as {account}. See managed accounts.

Whichever kind you choose, the routes below are the same. A tax ID your key does not reach answers 404 profile_not_found, the same as one that does not exist. On a tax ID where your level is view you can read, but any write — the contact and the invoice below, for example — answers 403 company_read_only. With a fact_test_ key, client_accounts lists only the accounts of the test environment; Test managed accounts walks through them. Reach a tax ID through the company segment explains the three kinds, their levels and every answer.

Export the tax ID you're going to invoice for — every company route below carries its opaque id in the path. Here, the first entry of scope:

export COMPANY_ID="01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a01"

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/companies/{company}/series returns your numbering series. Pick one whose document_type is invoice and invoice_kind is complete (the is_default one is a safe choice). Needs series:read.

curl "https://api.factuarea.com/v1/companies/$COMPANY_ID/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",
      "invoice_kind": "complete",
      "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. The field is optional when you create an invoice: without it the invoice goes to your default series of the right purpose, which is created on the spot if you have none.

A tax rate id

GET /v1/companies/{company}/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/companies/$COMPANY_ID/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 customer contact

Create one contact with name, kind, roles and a valid fiscal identity. For this Spanish company, use kind: "company", roles: ["customer"] and tax_id (NIF/CIF/NIE). Needs contacts:write.

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

curl -X POST https://api.factuarea.com/v1/companies/$COMPANY_ID/contacts \
  -H "Authorization: Bearer $FACTUAREA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "name": "Cliente Demo SL",
    "kind": "company",
    "roles": ["customer"],
    "tax_id": "B76543214"
  }'
{
  "data": {
    "id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a03",
    "object": "contact",
    "kind": "company",
    "name": "Cliente Demo SL",
    "business_name": null,
    "commercial_name": null,
    "tax_id": "B76543214",
    "vat_id": null,
    "alternative_id": null,
    "address": {
      "line1": null,
      "line2": null,
      "number": null,
      "floor": null,
      "door": null,
      "staircase": null,
      "city": null,
      "province": null,
      "postal_code": null,
      "country": "ES"
    },
    "contact_person": null,
    "email": null,
    "phone": null,
    "mobile": null,
    "fax": null,
    "website": null,
    "billing_emails": [],
    "tags": [],
    "coordinates": null,
    "notes": null,
    "metadata": {},
    "accumulate_347": true,
    "external_id": null,
    "roles": [
      {
        "role": "customer",
        "status": "active",
        "assigned_at": "2026-08-29T10:00:00+00:00",
        "deactivated_at": null
      }
    ],
    "bank_accounts": [],
    "customer_profile": null,
    "supplier_profile": null,
    "financial_summary": {
      "sales": {
        "invoiced_amount": "0.00",
        "collected_amount": "0.00",
        "receivable_amount": "0.00",
        "currency": "EUR"
      },
      "purchases": {
        "purchased_amount": "0.00",
        "paid_amount": "0.00",
        "payable_amount": "0.00",
        "currency": "EUR"
      }
    },
    "latest_activity": null,
    "is_archived": false,
    "archived_at": null,
    "created_at": "2026-08-29T10:00:00+00:00",
    "updated_at": "2026-08-29T10:00:00+00:00"
  }
}

(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-3c5d6e7f8a03) — that's the contact you bill, and the value the invoice takes as client_id.

The document field remains client_id: pass the contact’s canonical id with its active customer role.

Create the invoice

Now combine the three ids. POST /v1/companies/{company}/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/companies/$COMPANY_ID/invoices \
  -H "Authorization: Bearer $FACTUAREA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "client_id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a03",
    "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-3c5d6e7f8a03",
      "name": "Cliente Demo SL"
    },
    "status": "draft",
    "issued_on": "2026-06-02",
    "due_on": "2026-07-02",
    "subtotal": "1000.00",
    "taxes_total": "210.00",
    "total": "1210.00",
    "currency": "EUR",
    "notes": null,
    "lines": [
      {
        "object": "invoice_line",
        "description": "Consultoría — junio 2026",
        "product": null,
        "quantity": 10,
        "unit_price": "100.0000",
        "tax_rate": 21,
        "discount_percent": 0,
        "subtotal": "1000.00",
        "taxes": "210.00",
        "total": "1210.00"
      }
    ],
    "metadata": {},
    "operation_regime": "general",
    "verifactu_status": "not_applicable",
    "is_corrective": false,
    "corrective": null,
    "payment": null,
    "public_link": null,
    "substituted_by": null,
    "recurring": null,
    "paid_recorded_at": null,
    "paid_date": 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. Amounts come back as decimal strings — two decimals in a total ("1000.00") and four in the unit_price of a line ("100.0000") — and the unit_price you send can be a number or that same string. 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/companies/{company}/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/companies/$COMPANY_ID/invoices/01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a42/pdf \
  -H "Authorization: Bearer $FACTUAREA_API_KEY" \
  -o invoice.pdf

POST /v1/companies/{company}/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/companies/$COMPANY_ID/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 Corporation"
  }'

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 contact, 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

Need a hand?Contact support