API keys (self-service)
List, create, rotate and revoke your API keys over the v1 API — the secret is shown once, environments are live/test, and the tier comes from your plan.
Beyond the developer dashboard, Factuarea exposes the full lifecycle of your
API keys over the public v1 API, so you can provision and rotate credentials
programmatically. Five endpoints under /v1/account/api-keys cover listing,
creating, retrieving, rotating the secret and revoking — all scoped to the
authenticated company.
| Operation | Endpoint | Scope |
|---|---|---|
| List keys | GET /v1/account/api-keys | account:read |
| Create a key | POST /v1/account/api-keys | account:write |
| Retrieve a key | GET /v1/account/api-keys/{api_key} | account:read |
| Rotate the secret | POST /v1/account/api-keys/{api_key}/rotate_secret | account:write |
| Revoke a key | POST /v1/account/api-keys/{api_key}/revoke | account:write |
{api_key} is the key's id — an opaque UUID v7, not its prefix or
secret. See the full schemas in the
API Reference.
List your keys
GET /v1/account/api-keys returns your keys with cursor
pagination. Each key exposes its prefix, scopes,
tier, environment and lifecycle timestamps — never the secret.
curl https://api.factuarea.com/v1/account/api-keys \
-H "Authorization: Bearer fact_live_8KqW3pXnR2VbY7TcA9eFmN5z"{
"data": [
{
"object": "api_key",
"id": "0190f2b1-1c4e-7a3d-9f10-0a1b2c3d4e5f",
"name": "Production sync",
"prefix": "fact_live_8KqW3pXn",
"scopes": ["invoices:read", "invoices:write"],
"tier": "scale",
"environment": "live",
"active": true,
"revoked": false,
"last_used_at": "2026-06-23T18:04:11Z",
"expires_at": null,
"revoked_at": null
}
],
"has_more": false,
"next_cursor": null
}prefix is the first chars of the key — safe to log, it does not
authenticate. Use it to recognize a key in your own dashboards without ever
storing the secret.
Create a key
POST /v1/account/api-keys mints a new key and returns its plaintext
secret exactly once. Store it the moment you receive it — there is no
endpoint to read it back later.
curl -X POST https://api.factuarea.com/v1/account/api-keys \
-H "Authorization: Bearer fact_live_8KqW3pXnR2VbY7TcA9eFmN5z" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"name": "Reporting export",
"scopes": ["invoices:read", "pdfs:read"],
"environment": "test"
}'Response (201):
{
"data": {
"object": "api_key",
"id": "0190f2c0-77aa-7b21-8c33-1d2e3f405162",
"name": "Reporting export",
"prefix": "fact_test_1N0Fnyhh",
"secret": "fact_test_1N0FnyhhR2VbY7TcA9eFmN5z",
"scopes": ["invoices:read", "pdfs:read"],
"tier": "scale",
"environment": "test"
}
}The secret field appears only in this 201 response (and after a
rotation). It is never returned by the list, retrieve or any other
endpoint. If you lose it you must rotate the key. Persist it to a secret
manager immediately — never to a log or a repository.
Request body
| Field | Required | Notes |
|---|---|---|
name | yes | Human-readable label (1–120 chars). |
scopes | yes | One or more scopes from the closed catalog. At least one. |
environment | no | live (default) or test. See below. |
expires_at | no | Future ISO 8601 instant after which the key stops authenticating. |
allowed_ips | no | Optional list of allowed IPs / CIDR ranges (IPv4, IPv6, /N). |
The tier is derived from your company plan (or from an active
capacity boost when higher) — it is not
settable from the body. If you send a tier, it is ignored. Requesting a
scope outside the closed catalog, or a scope above your plan, returns 422
with per-field errors.
The environment field
Every key belongs to one of two environments, fixed at creation and visible on the key object:
environment | Prefix | Operates on |
|---|---|---|
live | fact_live_ | Your real company, with real side effects (VeriFactu → AEAT, emails, webhooks). |
test | fact_test_ | An isolated sandbox company with external effects switched off. |
Pass environment: test when creating a key to mint a sandbox credential;
omit it for a live key. The prefix mirrors the environment, so you can tell
them apart without decoding the key. See Test mode &
sandbox for what is switched off in test.
Rotate the secret
POST /v1/account/api-keys/{api_key}/rotate_secret generates a fresh
prefix + secret and returns the new secret in plaintext exactly
once. The previous secret keeps working for a 24-hour grace
window so you can roll out the new one with zero downtime.
curl -X POST \
https://api.factuarea.com/v1/account/api-keys/0190f2b1-1c4e-7a3d-9f10-0a1b2c3d4e5f/rotate_secret \
-H "Authorization: Bearer fact_live_8KqW3pXnR2VbY7TcA9eFmN5z"{
"data": {
"object": "api_key",
"id": "0190f2b1-1c4e-7a3d-9f10-0a1b2c3d4e5f",
"prefix": "fact_live_Zq7mP4xV",
"secret": "fact_live_Zq7mP4xVnR2VbY7TcA9eFmN5z",
"scopes": ["invoices:read", "invoices:write"],
"environment": "live"
}
}During the 24-hour grace window both the new and the previous secret
authenticate; a request still using the previous one receives a 199
Warning header counting down the hours left. Once the window expires
the previous secret is rejected and purged. Roll the new secret out
within those 24 hours. Rotation is irreversible.
Revoke a key
POST /v1/account/api-keys/{api_key}/revoke invalidates a key permanently.
Subsequent requests authenticated with it fail with 401. An optional
reason (max 500 chars) is recorded in the audit log.
curl -X POST \
https://api.factuarea.com/v1/account/api-keys/0190f2b1-1c4e-7a3d-9f10-0a1b2c3d4e5f/revoke \
-H "Authorization: Bearer fact_live_8KqW3pXnR2VbY7TcA9eFmN5z" \
-H "Content-Type: application/json" \
-d '{"reason": "Rotated out of the deploy pipeline"}'Revocation is irreversible and is not restricted to other keys: you may revoke the key you are authenticating the request with, cutting off your own access. Make sure another valid key is in place first if you still need API access.
After revocation, requests with that key return 401 with the generic
invalid_api_key code — not a distinct "revoked" code:
{
"error": {
"type": "authentication_error",
"code": "invalid_api_key",
"message": "La API key proporcionada no es válida.",
"request_id": "req_01JBVH7K9Y4N3CDQ2EHJB1AGSV"
}
}This is deliberate anti-enumeration: the API never reveals whether a key
was revoked, expired or never existed — every unusable key looks the same to
an attacker. Branch your own logic on the 200/401 outcome, not on a
revoked-specific code.
Scopes and isolation
The five endpoints are gated by the account scopes:
account:read— list and retrieve keys.account:write— create, rotate and revoke keys.
All operations are scoped to the authenticated company. A key id belonging
to another company returns 404 api_key_not_found (again, anti-enumeration
— it never reveals the key exists), never 403.
Managing keys still requires an existing key with the right scopes. Create your first key in the developer dashboard (app.factuarea.com/settings/developers/api-keys), then use these endpoints to provision the rest programmatically. See Authentication for the key format and headers.