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:
Authorization Bearer (recommended)
curl https://api.factuarea.com/v1/contacts \
-H "Authorization: Bearer fact_live_8KqW3pXnR2VbY7TcA9eFmN5z"X-API-Key header
curl https://api.factuarea.com/v1/contacts \
-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('contacts?limit=10');
$body = json_decode((string) $response->getBody(), true);const res = await fetch('https://api.factuarea.com/v1/contacts?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/contacts',
params={'limit': 10},
headers={
'Authorization': f"Bearer {os.environ['FACTUAREA_API_KEY']}",
'Accept': 'application/json',
},
)
resp.raise_for_status()
data = resp.json()OAuth 2.1
For agent integrations and third-party apps that act on behalf of a
Factuarea user, the API also supports the OAuth 2.1 authorization-code
flow with PKCE (code_challenge_method=S256) as an alternative to a
static API key. OAuth consent exposes 68 simple dotted scopes plus the
factuarea.read, factuarea.write and factuarea.full macros; they are mapped
to the fine-grained colon scopes that gate each endpoint and MCP tool. The
rotation policy applies to OAuth client secrets too.
The consent catalog includes price_lists.read/write,
facturae.read/write and tax_reports.read/write. The screen offers only the
scopes requested by the client, never pre-selects sensitive scopes, and the
issued token contains exactly the subset the user approved.
Discovery metadata (RFC 8414) is published at
/.well-known/oauth-authorization-server, so OAuth clients can resolve
the authorization and token endpoints automatically:
curl https://api.factuarea.com/.well-known/oauth-authorization-serverThe OAuth2 security scheme — including the authorization and token URLs
and the full scope list — is described in the
API Reference.
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.
The valid catalog is append-only for existing credentials, while the subset
assignable to a new key follows the currently released API surface. The four
reserved GoCardless/MONEI scopes below remain recognizable for historical
compatibility, but are omitted from CreateApiKeyV1Request and cannot be
assigned until those providers are released. Plan and sandbox restrictions are
then enforced separately with scope_not_allowed_by_plan and
scope_not_allowed_in_sandbox.
Clients and catalog
| Scope | Allows |
|---|---|
contacts:read | List and retrieve contacts. |
contacts:write | Create and update contacts, roles and profiles. |
contacts:delete | Archive contacts and remove unreferenced roles. |
products:read | List and retrieve products. |
products:write | Create and update products. |
products:delete | Delete products. |
price_lists:read | List price lists, their items and resolve catalog prices. |
price_lists:write | Create, update and delete price lists and their items. |
Sales documents
| Scope | Allows |
|---|---|
invoices:read | List and retrieve invoices. |
invoices:write | Create and update invoices (includes duplicate and corrective). |
invoices:delete | Delete invoice drafts. |
invoices:send | Send invoice by email to the client. |
invoices:void | Void an issued invoice. |
quotes:read | List and retrieve quotes. |
quotes:write | Create and update quotes. |
quotes:delete | Delete quotes. |
quotes:send | Send quote by email. |
quotes:transition | Accept, reject or convert quotes. |
proformas:read | List and retrieve pro-forma invoices. |
proformas:write | Create and update pro-forma invoices. |
proformas:delete | Delete pro-forma invoices. |
proformas:send | Send pro-forma invoice by email. |
proformas:transition | Convert pro-forma invoice to invoice. |
delivery_notes:read | List and retrieve delivery notes. |
delivery_notes:write | Create and update delivery notes. |
delivery_notes:delete | Delete delivery notes. |
delivery_notes:transition | Mark as delivered/cancelled, sign, convert. |
delivery_notes:gdpr_forget | Erase signature-audit PII (GDPR Art. 17). |
Purchases and recurring
| Scope | Allows |
|---|---|
purchase_invoices:read | List and retrieve vendor bills. |
purchase_invoices:write | Create and update vendor bills. |
purchase_invoices:delete | Delete vendor bills. |
purchase_invoices:transition | Mark as paid, received, accounted. |
recurring_invoices:read | List and retrieve recurring templates. |
recurring_invoices:write | Create and update recurring templates. |
recurring_invoices:delete | Delete recurring templates. |
recurring_invoices:transition | Pause, resume and emit manually. |
Catalogs and export
| Scope | Allows |
|---|---|
taxes:read | Read the (global) tax rates catalog. |
taxes:write | Create and update tax rates. |
taxes:delete | Delete tax rates. |
series:read | List invoice numbering series. |
series:write | Create and update invoice numbering series. |
pdfs:read | Download PDFs of any document with the matching :read scope. |
tax_reports:read | Read tax reports (Modelo 303/347, etc.). |
tax_reports:write | Generate tax reports. |
account:read | Read the authenticated account (GET /v1/account). |
account:write | Manage the account's own API keys (create, rotate, revoke) and update account personalization. |
VeriFactu & FacturaE
| Scope | Allows |
|---|---|
verifactu:read | Read VeriFactu records, events, certificates and config. |
verifactu:write | Manage VeriFactu certificates, settings and retries. |
facturae:read | Download the FacturaE XML of an invoice and read its FACe submissions. |
facturae:write | Submit invoices to FACe and request submission cancellations. |
Webhooks and events
| Scope | Allows |
|---|---|
webhooks:read | List webhook endpoints and deliveries. |
webhooks:write | Create, update, rotate and ping webhook endpoints. |
webhooks:delete | Delete webhook endpoints. |
events:read | Read the event catalog and individual events. |
Integrations and observability
| Scope | Allows |
|---|---|
stripe_autoinvoicing:read | Read Stripe auto-invoicing status, configuration and connected accounts. |
stripe_autoinvoicing:write | Configure Stripe auto-invoicing and connected accounts. |
payouts:read | Read Stripe payouts and reconciliation status. |
integration_events:read | Inspect received payment-gateway events and typed discard reasons. |
integration_events:write | Replay parked integration events. |
developers:read | Inspect the authenticated company's API request log. |
emails:read | Inspect sent-email status and history. |
gocardless_autoinvoicing:read | Reserved for the unreleased GoCardless integration; no endpoint or tool currently requires it. |
gocardless_autoinvoicing:write | Reserved for the unreleased GoCardless integration; no endpoint or tool currently requires it. |
monei_autoinvoicing:read | Reserved for the unreleased MONEI integration; no endpoint or tool currently requires it. |
monei_autoinvoicing:write | Reserved for the unreleased MONEI integration; no endpoint or tool currently requires it. |
Workforce (control horario)
These scopes require the control_horario plan module. Some read scopes are
OAuth-grantable; privileged writes and transitions remain API-key-only.
| Scope | Allows |
|---|---|
employees:read | Read employees and invitation state. |
employees:write | Create, update and manage employees and employee seats. |
employees:delete | Permanently delete employees. |
time_entries:read | Read time entries, balances and monthly registers. |
time_entries:write | Clock, correct and close/reopen time registers. |
absences:read | Read absence types, policies, requests and balances. |
absences:write | Create and update absence configuration and requests. |
absences:transition | Approve, reject and cancel absence requests. |
work_schedules:read | Read schedules and assignments. |
work_schedules:write | Create, update, assign and archive schedules. |
presence:read | Read live and daily presence. |
holidays:read | Read company and regional holidays. |
payroll_exports:read | Read generated payroll exports. |
payroll_exports:write | Generate payroll exports. |
Managed companies (gestoría)
Fine-grained scopes for the gestoría model, where a master account manages
child sub-companies and their API keys. Reachable only with an API key (no OAuth
consent equivalent); companies:* also requires the gestoría plan module.
| Scope | Allows |
|---|---|
companies:read | List and retrieve managed companies (child sub-accounts). |
companies:write | Create, update, activate and deactivate managed companies. |
companies:delete | Archive managed companies. |
api_keys:read | List and retrieve API keys of managed companies. |
api_keys:write | Create, rotate and revoke API keys of managed companies. |
api_keys:delete | Permanently delete API keys of managed companies. |
Super-scope
| Scope | Allows |
|---|---|
* | 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 can be managed from the developer dashboard (app.factuarea.com/settings/developers/api-keys) or through the self-service v1 endpoints. Both surfaces let you create, rotate and revoke keys; the dashboard also configures the 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 policy
API keys and OAuth client secrets are long-lived credentials and must be rotated on a schedule and immediately after any suspected leak.
- Prefixes are the source of truth for the environment:
fact_live_(production) andfact_test_(sandbox). Never mix them across environments. - Rotate from the dashboard (or via the self-service
account:writeendpoints) to issue a brand-new secret. The new secret is returned once — store it immediately, it is never shown again. - Grace window (dual-secret). After a rotation the previous secret
keeps working for a 24-hour grace window, so you can roll out the
new secret with zero downtime. During that window both the new and the
previous secret are accepted; once the window expires the previous
secret is rejected and purged. A request still using the previous
secret receives a
199Warningheader telling you how many hours remain before it stops working. - When to rotate: on a regular schedule (e.g. every 90 days), whenever a teammate with access leaves, and immediately if a secret is ever exposed in logs, source control or a public client.
- Revoke to invalidate a key permanently. Any subsequent request with
it fails with
401. Revocation has no grace window — it is instant and irreversible.
Secrets are bound to a single company (tenant) and must never be embedded in browsers, mobile apps or any public client — keep them server-side only.
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:
code | HTTP | Cause |
|---|---|---|
missing_api_key | 401 | No authentication header sent. |
invalid_api_key | 401 | The key does not exist, has the wrong format, is revoked/expired, or the secret doesn't match the stored hash. |
too_many_auth_failures | 429 | Too many failed authentication attempts; back off. |
insufficient_scope | 403 | The 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
:readscopes. - Enable IP allowlist for server-to-server integrations with stable IPs.
- Configure
expires_atfor temporary keys (e.g. consultancies, demos). - Audit usage from the dashboard:
Developers > API Keys > Activityshows IPs, paths and errors per key.
Credentials for integrations
Store REST and MCP operations require an API key with the store scopes. OAuth consent does not grant these scopes. Authorize the provider connection separately: a Factuarea key is not a WooCommerce or Shopify credential. MCP authentication also requires a client compatible with revision 2026-07-28.