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/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
| Scope | Allows |
|---|---|
clients:read | List and retrieve clients. |
clients:write | Create and update clients. |
clients:delete | Delete clients. |
products:read | List and retrieve products. |
products:write | Create and update products. |
products:delete | Delete products. |
suppliers:read | List and retrieve suppliers. |
suppliers:write | Create and update suppliers. |
suppliers:delete | Delete suppliers. |
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). |
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. |
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 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:
code | HTTP | Cause |
|---|---|---|
missing_api_key | 401 | No authentication header sent. |
invalid_api_key | 401 | The key does not exist, has the wrong format, or the secret doesn't match the stored hash. |
api_key_revoked | 401 | The key was revoked or has expired. Create a new one in the dashboard. |
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.
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.
Test mode & sandbox
Build your integration safely with fact_test_ keys — isolated sandbox data and AEAT, email and webhooks switched off.