Factuarea API

Authentication

API keys with fact_live_ / fact_test_ prefixes, fine-grained scopes, grace-period rotation and IP allowlist.

The Factuarea API authenticates every request with an API key. Keys are opaque tokens generated in the developer dashboard (app.factuarea.com/settings/developers/api-keys) and bound to a specific company. Every request to https://api.factuarea.com/v1/* must include a valid key in one of the two supported formats.

API key format

fact_live_<24 alphanumeric characters>
fact_test_<24 alphanumeric characters>

Example:

fact_live_8KqW3pXnR2VbY7TcA9eFmN5z
fact_test_3pXnR2VbY7TcA9eFmN5z8KqW
  • Prefix: determines the environment. fact_live_ operates on your real company (production); fact_test_ operates on an isolated sandbox company with external effects (VeriFactu → AEAT, FACe, emails, webhooks) switched off. The prefix lets you identify the environment without decoding the key. See Test mode & sandbox.
  • Secret: 24 base62 characters → ~143 bits of entropy. Shown only once at creation in the dashboard. If you lose it, you must rotate.
  • DB hash: the backend stores only the bcrypt cost-12 hash of the secret. There's no way to recover it.

Every example in this guide uses a fact_live_ key, but the exact same request works with a fact_test_ key — just swap the prefix to operate on sandbox data. Build and validate your integration in test first. See Test mode & sandbox.

Sending the key on each request

The API accepts two equivalent formats. Pick the one that fits your client:

curl https://api.factuarea.com/v1/clients \
  -H "Authorization: Bearer fact_live_8KqW3pXnR2VbY7TcA9eFmN5z"

X-API-Key header

curl https://api.factuarea.com/v1/clients \
  -H "X-API-Key: fact_live_8KqW3pXnR2VbY7TcA9eFmN5z"

Send only one of the two headers. If both are present, the Authorization: Bearer header takes precedence.

Examples per language

$client = new GuzzleHttp\Client([
    'base_uri' => 'https://api.factuarea.com/v1/',
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('FACTUAREA_API_KEY'),
        'Accept' => 'application/json',
    ],
]);

$response = $client->get('clients?limit=10');
$body = json_decode((string) $response->getBody(), true);
const res = await fetch('https://api.factuarea.com/v1/clients?limit=10', {
  headers: {
    Authorization: `Bearer ${process.env.FACTUAREA_API_KEY}`,
    Accept: 'application/json',
  },
});
const data = await res.json();
import os
import requests

resp = requests.get(
    'https://api.factuarea.com/v1/clients',
    params={'limit': 10},
    headers={
        'Authorization': f"Bearer {os.environ['FACTUAREA_API_KEY']}",
        'Accept': 'application/json',
    },
)
resp.raise_for_status()
data = resp.json()

Scopes

Each API key is created with one or more scopes that limit which endpoints it can invoke. Scopes are strings of the form <resource>:<action>. The catalog is closed: any scope outside the listed set raises invalid_scope when creating the key.

Clients and catalog

ScopeAllows
clients:readList and retrieve clients.
clients:writeCreate and update clients.
clients:deleteDelete clients.
products:readList and retrieve products.
products:writeCreate and update products.
products:deleteDelete products.
suppliers:readList and retrieve suppliers.
suppliers:writeCreate and update suppliers.
suppliers:deleteDelete suppliers.

Sales documents

ScopeAllows
invoices:readList and retrieve invoices.
invoices:writeCreate and update invoices (includes duplicate and corrective).
invoices:deleteDelete invoice drafts.
invoices:sendSend invoice by email to the client.
invoices:voidVoid an issued invoice.
quotes:readList and retrieve quotes.
quotes:writeCreate and update quotes.
quotes:deleteDelete quotes.
quotes:sendSend quote by email.
quotes:transitionAccept, reject or convert quotes.
proformas:readList and retrieve pro-forma invoices.
proformas:writeCreate and update pro-forma invoices.
proformas:deleteDelete pro-forma invoices.
proformas:sendSend pro-forma invoice by email.
proformas:transitionConvert pro-forma invoice to invoice.
delivery_notes:readList and retrieve delivery notes.
delivery_notes:writeCreate and update delivery notes.
delivery_notes:deleteDelete delivery notes.
delivery_notes:transitionMark as delivered/cancelled, sign, convert.
delivery_notes:gdpr_forgetErase signature-audit PII (GDPR Art. 17).

Purchases and recurring

ScopeAllows
purchase_invoices:readList and retrieve vendor bills.
purchase_invoices:writeCreate and update vendor bills.
purchase_invoices:deleteDelete vendor bills.
purchase_invoices:transitionMark as paid, received, accounted.
recurring_invoices:readList and retrieve recurring templates.
recurring_invoices:writeCreate and update recurring templates.
recurring_invoices:deleteDelete recurring templates.
recurring_invoices:transitionPause, resume and emit manually.

Catalogs and export

ScopeAllows
taxes:readRead the (global) tax rates catalog.
taxes:writeCreate and update tax rates.
taxes:deleteDelete tax rates.
series:readList invoice numbering series.
series:writeCreate and update invoice numbering series.
pdfs:readDownload PDFs of any document with the matching :read scope.
tax_reports:readRead tax reports (Modelo 303/347, etc.).
tax_reports:writeGenerate tax reports.
account:readRead the authenticated account (GET /v1/account).

VeriFactu & FacturaE

ScopeAllows
verifactu:readRead VeriFactu records, events, certificates and config.
verifactu:writeManage VeriFactu certificates, settings and retries.
facturae:readDownload the FacturaE XML of an invoice and read its FACe submissions.
facturae:writeSubmit invoices to FACe and request submission cancellations.

Webhooks and events

ScopeAllows
webhooks:readList webhook endpoints and deliveries.
webhooks:writeCreate, update, rotate and ping webhook endpoints.
webhooks:deleteDelete webhook endpoints.
events:readRead the event catalog and individual events.

Super-scope

ScopeAllows
*Full access — equivalent to having every other scope above. Reserved for owner keys / one-off migrations. Avoid using in production integrations.

If a request uses an endpoint that requires a scope not granted to the key, the response is 403 with type: authorization_error and code: insufficient_scope.

{
  "error": {
    "type": "authorization_error",
    "code": "insufficient_scope",
    "message": "La API key no tiene el scope requerido para esta operación.",
    "request_id": "req_01JBVH7..."
  }
}

Key management

API keys are managed from the developer dashboard (app.factuarea.com/settings/developers/api-keys), not via the public API. From there you can create keys, rotate their secret, revoke them, configure scopes, an optional expires_at, and an IP allowlist.

The authenticated key's metadata (id, name, prefix, scopes, tier, last_used_at, expires_at) is readable via GET /v1/account — but the secret is never returned.

There is no endpoint to "view" the secret. It is shown only once at creation. If you lose the value you must rotate the key in the dashboard and redeploy the new secret. This is deliberate: it minimizes the exposure window.

Rotation and revocation

  • Rotate from the dashboard to issue a new secret. Both the old and new secrets stay valid for a grace period so you can roll out the new one with zero downtime.
  • Revoke to invalidate a key instantly. Any subsequent request with it fails with 401 api_key_revoked. Useful when you suspect a leak.

IP allowlist

Each API key can be restricted to a list of IPs or CIDR ranges from the dashboard. If the request comes from an IP outside the allowlist, the response is 401 and the incident is recorded in the audit log. Leave the allowlist empty to allow any IP.

Authentication errors

Failures related to the API key respond with HTTP 401 (or 403 for insufficient_scope) and the standard error envelope. The code field distinguishes the case:

codeHTTPCause
missing_api_key401No authentication header sent.
invalid_api_key401The key does not exist, has the wrong format, or the secret doesn't match the stored hash.
api_key_revoked401The key was revoked or has expired. Create a new one in the dashboard.
too_many_auth_failures429Too many failed authentication attempts; back off.
insufficient_scope403The key lacks the scope the endpoint requires.

Every response includes a unique request_id (also in the X-Request-Id header) you can pass to support when investigating.

{
  "error": {
    "type": "authentication_error",
    "code": "invalid_api_key",
    "message": "La API key proporcionada no es válida.",
    "request_id": "req_01JBVH7K9Y4N3CDQ2EHJB1AGSV",
    "doc_url": "https://docs.factuarea.com/guides/errors#invalid_api_key"
  }
}

Best practices

  • Never commit API keys to repositories — use environment variables or a secret manager (AWS Secrets Manager, Doppler, 1Password Service Accounts).
  • Create one key per integration: makes rotating and auditing access easier without affecting the rest.
  • Limit scopes to the minimum required. An export script only needs specific :read scopes.
  • Enable IP allowlist for server-to-server integrations with stable IPs.
  • Configure expires_at for temporary keys (e.g. consultancies, demos).
  • Audit usage from the dashboard: Developers > API Keys > Activity shows IPs, paths and errors per key.

On this page