---
title: "Create an API key"
method: POST
path: "/accounts/{account}/api-keys"
tags: ["Account"]
---

# Create an API key

`POST /accounts/{account}/api-keys`

## Request

- Base URL: `https://api.factuarea.com/v1`
- URL: `https://api.factuarea.com/v1/accounts/{account}/api-keys`
- Auth: one of:
  - HTTP bearer
  - API key in header `X-API-Key`

Create an API key in your account; its plaintext `secret` is returned exactly once.
- Without `scoped_company_ids` it reaches the creator’s tax IDs, never more; a scope above the plan returns 422.
- `environment: test` mints a sandbox `fact_test_` key; otherwise `fact_live_`.
- An uncovered scope returns 403 `insufficient_scope`; `*` excludes `accounts:*` and admin scopes.

## Path parameters

- `account` string, uuid, required

## Headers

- `Idempotency-Key` string
- `Factuarea-Version` string, date

## Request body

- CreateApiKeyV1Request: Create an API key for your own tenant. The `tier` is never accepted from the body — it is derived from the holder plan and add-ons. Requested `scopes` must belong to the closed v1 catalog and stay within the holder plan; a scope above the plan returns 422. The optional `scoped_company_ids` is the list of companies of your account (company IDs, UUID v7) the key will be able to operate on; when omitted the key reaches the same companies as the credential that creates it, and it can never reach more. It is rejected with `environment: test`: a test key only reaches the sandbox company. Every requested scope must fit the plan of EVERY company in that list, so mixing companies on different plans returns 422. To change the scope of a key you already issued, use the dedicated scope operation.
  - `name` string, required: Human-readable name for the API key (1-120 characters).
  - `scopes` string[], required: List of scopes from the closed v1 catalog (at least one).
  - `expires_at` string, date-time, nullable: Future ISO 8601 date after which the key stops authenticating.
  - `ip_allowlist` string[], nullable: Optional list of allowed IPs / CIDR ranges (IPv4, IPv6, /N).
  - `environment` 'live' | 'test' | 'null', nullable: Key environment: `live` (production) or `test` (sandbox). Defaults to `live`.
  - `scoped_company_ids` string[], nullable: Optional public identifiers (UUID v7) of the companies of your account the key will reach; when omitted, the same companies as the credential that creates it. Not accepted with `environment: test`.

## Response `201`

API key created. The `Location` header contains the canonical URL of the new key.

- object
  - `data` ApiKeyWithSecret, required: An API key returned once at creation or after secret rotation. Includes the plaintext `secret` — store it now, it cannot be retrieved later.
    - `id` string, uuid, required: Opaque identifier (UUID v7) of the API key.
    - `object` 'api_key', required: Always `api_key`.
    - `name` string, required: Human-friendly label assigned at creation time.
    - `prefix` string, required: First chars of the key (e.g. `fact_live_1N0Fnyhh`) — safe to log. Does NOT authenticate.
    - `scopes` string[], required: Authorized scopes. `*` means super scope (full access).
    - `tier` string, required: Rate-limit tier (`free`, `starter`, `pro`, `scale`). Derived from the company plan (or preserved by historical grandfathering), never set from the request body.
    - `environment` 'live' | 'test', required: Key environment: `live` (`fact_live_`, real side effects) or `test` (`fact_test_`, sandbox company, no real-world effects).
    - `created_at` string, date-time, required
    - `last_used_at` string, date-time, nullable, required: Timestamp of the last authenticated request with this key, or `null` if never used.
    - `expires_at` string, date-time, nullable, required: Expiry instant (ISO 8601), or `null` if the key does not expire.
    - `revoked_at` string, date-time, nullable, required: Revocation instant (ISO 8601), or `null` if the key is not revoked.
    - `is_active` boolean, required: `true` when the key is usable (not revoked and not expired).
    - `is_revoked` boolean, required: `true` once the key has been revoked.
    - `ip_allowlist` string[], nullable, required: IP addresses or CIDR ranges the key is restricted to, or `null` when it has no IP restriction.
    - `allowed_origins` string[], nullable, required: Browser origins allowed to use a browser-published key, or `null` (or empty) when the key is server-side only.
    - `last_used_ip` string, nullable, required: IP address of the last authenticated request with this key, or `null` if never used.
    - `nif_scope` ApiKeyNifScopeItem[], required: Tax IDs the key reaches, each with its opaque identifier, its fiscal identifier and the access level stored for the key on it.
      - `id` string, uuid, required: Opaque identifier of the company (UUID v7) — the one the company axis expects in the path.
      - `tax_id` string, required: Spanish fiscal identifier (NIF, CIF, NIE). Structural format: NIF `^\d{8}[A-Z]$`, NIE `^[XYZ]\d{7}[A-Z]$`, CIF `^[A-Z]\d{7}[A-Z0-9]$`. AEAT control-digit (checksum) validation is enforced when the request opts in via the `Factuarea-Version` header on or after the activation version; without that opt-in the legacy permissive behaviour is preserved.
      - `name` string, required
      - `access_level` 'view' | 'operate': Access level the key has on that tax ID: `view` is read-only, `operate` can also write. Present on the scope of an API key.
    - `created_by` object, nullable, required: The person who created the key. `null` when that person no longer belongs to the account, or for keys created before this field existed.
      - `id` string, uuid, required: Opaque identifier (UUID v7) of the person.
      - `name` string, required: Full name of the person.
    - `revoked_by` object, nullable, required: The person who revoked the key. `null` when the key is not revoked, when that person no longer belongs to the account, or for keys revoked before this field existed.
      - `id` string, uuid, required: Opaque identifier (UUID v7) of the person.
      - `name` string, required: Full name of the person.
    - `revoke_reason` string, nullable, required: Reason stored when the key was revoked, or `null`.
    - `secret_rotated_at` string, date-time, nullable, required: Instant of the last secret rotation, or `null` if the secret was never rotated.
    - `secret_rotated_by` object, nullable, required: The person who last rotated the secret. Always `null` when the rotation was made with an API credential or the MCP server instead of a person.
      - `id` string, uuid, required: Opaque identifier (UUID v7) of the person.
      - `name` string, required: Full name of the person.
    - `previous_secret_valid_until` string, date-time, nullable, required: End of the 24-hour grace window in which the previous secret still authenticates after a rotation, or `null` outside that window.
    - `secret` string, nullable, required: Plaintext secret, returned only at creation or after rotation — never again. `null` only in an idempotent replay of that creation or rotation, because the secret is never stored.

## Other responses

- `400`: `parameter_unknown` with `subcode` `retired_active_profile_header`: the retired `X-Active-Profile` header was sent; the company goes in the path. `parameter_invalid_format` on `Factuarea-Version`: the header is not a `YYYY-MM-DD` date. `unsupported_api_version`: the `Factuarea-Version` date is not a supported version.
- `401`: Missing or invalid API key.
- `403`: The request is authenticated but not allowed: the API key lacks the required scope (`insufficient_scope`), the plan does not include the public API or this feature (`addon_not_active`, `feature_not_available_in_plan`), or the module is not available with a test key (`module_not_available_in_sandbox`).
- `404`: The requested resource does not exist or belongs to another company. `account_not_found`: the account in the path does not exist or is not the account of the API key.
- `422`: Validation failed — e.g. the API key plan limit was reached, or an invoice language outside the allowed catalog (`es`, `en`, `ca`). The `error.param` field identifies which input is invalid, if any.
- `429`: Rate limit exceeded. Retry after the duration in `Retry-After`.
- `500`: Unexpected server error.

---

[API](https://skmtc.dev/factuarea/apis/factuarea-public-api.md) · [All operations](https://skmtc.dev/factuarea/apis/factuarea-public-api/llms.txt) · [OpenAPI document](https://skmtc.dev/factuarea/apis/factuarea-public-api/revisions/718e77934422?raw)
