Factuarea APIDevelopers

API keys scoped to part of your account

Issue, narrow, rotate and revoke API keys that reach only some of the tax IDs of your account — scoped_company_ids, the level ceiling per tax ID and what happens when the person who issued a key loses access.

An API key belongs to the account, never to one of its tax IDs, and what decides which tax IDs it can drive is its scope: the scoped_company_ids you declare when you issue it. A key scoped to a single tax ID authenticates requests that reach that one: the {company} segment of the path can name no other, and the rest of the account stays unreachable for it. The scope can hold your own tax IDs and also the ones you reach through granted access or a managed account.

It is the alternative to driving the whole account with one credential and naming the tax ID per request: a scoped key is fixed to its tax IDs when it is issued, rather than switched per request. Each tax ID in the scope also has a level, view or operate, that can never exceed the one of the person who issued the key.

Six endpoints under /v1/accounts/{account}/api-keys cover the lifecycle.

OperationEndpointScope
List keysGET /v1/accounts/{account}/api-keysaccount:read
Issue a keyPOST /v1/accounts/{account}/api-keysaccount:write
Retrieve a keyGET /v1/accounts/{account}/api-keys/{api_key}account:read
Change the tax IDs of a keyPATCH /v1/accounts/{account}/api-keys/{api_key}/nif-scopeaccount:write
Rotate the secretPOST /v1/accounts/{account}/api-keys/{api_key}/rotate-secretaccount:write
Revoke a keyPOST /v1/accounts/{account}/api-keys/{api_key}/revokeaccount:write

This is the very same self-service described in API keys: this page is about the tax ID scope you give a key. Both {account} and {api_key} are opaque UUID v7 values, and you read account.id from GET /v1/me. The subject is always the account, and who sees and manages a key explains what a key sees of it.

Issue a scoped key

POST /v1/accounts/{account}/api-keys issues a key and returns its plaintext secret exactly once. Declare scoped_company_ids with the tax IDs the key will be able to operate on. Omit it and the key reaches the same tax IDs as the key that issues it, and never more. Requires the account:write scope.

curl -X POST \
  https://api.factuarea.com/v1/accounts/$ACCOUNT_ID/api-keys \
  -H "Authorization: Bearer $FACTUAREA_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "name": "Producción Talleres García",
    "scopes": ["invoices:read", "invoices:write"],
    "scoped_company_ids": ["01931b3e-7c4a-7f2e-9a8b-4d6e7f8a9b0c"]
  }'

Response (201), with a Location header that carries the URL of the new key:

{
  "data": {
    "object": "api_key",
    "id": "0190f2c0-77aa-7b21-8c33-1d2e3f405162",
    "name": "Producción Talleres García",
    "prefix": "fact_live_1OSf9KdP",
    "scopes": ["invoices:read", "invoices:write"],
    "tier": "scale",
    "environment": "live",
    "created_at": "2026-01-15T09:30:00Z",
    "last_used_at": null,
    "expires_at": null,
    "revoked_at": null,
    "is_active": true,
    "is_revoked": false,
    "secret": "fact_live_1OSf9KdPR2VbY7TcA9eFmN5z"
  }
}

The secret is shown only in this 201 response and after a rotation, and an idempotent replay of either returns it as null. No endpoint returns it later. Persist it to a secret manager the moment you receive it, never to a log or a repository. If you lose it, rotate the key.

FieldRequiredNotes
nameyesHuman-readable label (1–120 characters).
scopesyesOne or more scopes of the closed v1 catalog, each one within the plan of every tax ID in the scope.
scoped_company_idsnoThe tax IDs (id, UUID v7) the key will reach, at least one when present. Omitted, the key reaches the same tax IDs as the key that issues it. Not accepted with environment: test.
expires_atnoFuture ISO 8601 instant after which the key stops authenticating.
ip_allowlistnoOptional list of allowed IPs or CIDR ranges (IPv4, IPv6, /N).
environmentnolive (default) or test for a sandbox key.

Your tier caps the active keys. Issuing one above the cap of your tier answers 422 max_api_keys_exceeded, and once the account holds more than one tax ID, the whole account has a cap of its own that answers 422 portfolio_max_api_keys_exceeded. The second cap is flat inside a tier: registering more tax IDs never raises it.

The scope can never exceed the issuing key

The tax IDs you request must be within the scope of the key making the call. Requesting one it does not reach answers 422 business_rule_violation with the subcode nif_scope_exceeds_issuer. There is no silent narrowing: the key is not created reaching the tax IDs that do fit, the whole request fails, and the error lists only the identifiers you sent that the key does not reach.

{
  "error": {
    "type": "invalid_request_error",
    "code": "business_rule_violation",
    "subcode": "nif_scope_exceeds_issuer",
    "message": "La credencial nueva no puede alcanzar más NIFs que la credencial que la emite. Fuera del ámbito de la emisora: 01931b3e-8d5b-7a1f-9c2d-5e6f7a8b9c0d.",
    "param": "scoped_company_ids"
  }
}

A tax ID that does not resolve, or that belongs to another account, answers 422 invalid_param_value on the same scoped_company_ids field, and so does one of an account that gave you no access: the cases are deliberately indistinguishable, so the field cannot be used to probe which tax IDs exist. A tax ID of a granted access or of a managed account is accepted only while the relation is in force and your key already reaches it.

The level of each tax ID

Each tax ID in the scope carries a level, view or operate, that is the ceiling of what the key can do there. It is fixed when the tax ID enters the key, and it is the lowest of:

  • the level on that tax ID of the person who issues the key: an owner or admin of the account holds operate on every tax ID of it, and a member holds the level of its grant;
  • for a tax ID reached through a relation between accounts, the level of the relation, and the internal grant of the person when it is a member of the receiving account.

What follows from it:

  • A key that writes only includes tax IDs with operate. Any scope that is not a :read one, the * scope included, needs operate on every tax ID the key names, and a key with only :read scopes takes view or operate. Naming a tax ID on which you only hold view together with such a scope answers 422 business_rule_violation with the subcode nif_access_level_exceeds_emitter and creates nothing. If you declare no scoped_company_ids, the tax IDs below the level the scopes need are left out, and when none is left the answer is the same 422.
  • Tax IDs of a relation stay under their ceiling. A granted tax ID or one of a managed account enters with at most the level of the relation. It enters only if the person who issues the key belongs to your account, and a member with no grant on it does not reach it. Granted access explains it from the receiving side.
  • The level is checked on every request. The level that counts is the lower of the one stored in the key and the one that the person who issued it holds today, so a key never writes where its issuer no longer operates.
{
  "error": {
    "type": "invalid_request_error",
    "code": "business_rule_violation",
    "subcode": "nif_access_level_exceeds_emitter",
    "message": "No puedes dar a la credencial más acceso del que tienes. Sin acceso suficiente en: 01931b3e-8d5b-7a1f-9c2d-5e6f7a8b9c0d. Una credencial con permisos de escritura solo alcanza NIF en los que operas.",
    "param": "scoped_company_ids"
  }
}

When the person who issued a key loses access

The stored level of a key only goes down. It is recalculated downward whenever the person who issued the key loses level: their grant is lowered or revoked, their role goes from admin to member, they are removed from the account, a tax ID leaves the account or is transferred, or the access that reaches a tax ID narrows or ends. Each of these takes effect on the next request:

  • A tax ID left with no level leaves the scope of the key.
  • A level lowered to view turns the writes of that key on that tax ID into 403 company_read_only.
  • A key left with an empty scope is revoked.

Nothing goes up on its own. Raising a grant or a role, or widening an access, does not touch the keys that already exist: issue a new key, or change the tax IDs of the existing one, to get the new level.

Scopes are granted by intersection of the plans

A requested scope is granted only if every tax ID in the scope enables it by its plan. Your own tax IDs share the plan of your account, and a tax ID reached through a relation brings the plan of its own account, so mixing them can answer 422 scope_not_allowed_by_plan. The excess is never trimmed in silence. 422 scope_not_allowed_in_sandbox covers a scope that a test key cannot hold. The scope list is fixed at issuance: widening it means issuing another key. The environment and the tier are never taken from the body, and the tier is derived from the plan of the account.

Test keys

A key with environment: test reaches only the sandbox company of your account, so declaring scoped_company_ids with it answers 422 invalid_param_value on that field: issue it without scoped_company_ids. A fact_test_ credential cannot issue, re-scope, rotate or revoke keys: those four operations answer 403 live_credential_required with nothing written, so a test key never mints a live key. To get a test key, issue it with a live key and environment: test, or create it in the web app. Test managed accounts covers the rest of what a test key can do.

{
  "error": {
    "type": "authorization_error",
    "code": "live_credential_required",
    "message": "Esta operación modifica la cuenta y exige una clave de producción (fact_live_)",
    "doc_url": "https://docs.factuarea.com/guides/errors#live_credential_required",
    "request_id": "req_01J8Y3M2Q40000000000000009"
  }
}

Change the tax IDs of a key

PATCH /v1/accounts/{account}/api-keys/{api_key}/nif-scope replaces the tax IDs of a key that is already deployed, and it does not change its secret. scoped_company_ids is the complete resulting list, not a delta, with at least one tax ID. Use it to add the tax IDs of your clients to a key you already issued, or to narrow it. Requires the account:write scope.

curl -X PATCH \
  https://api.factuarea.com/v1/accounts/$ACCOUNT_ID/api-keys/$API_KEY_ID/nif-scope \
  -H "Authorization: Bearer $FACTUAREA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "scoped_company_ids": [
      "01931b3e-7c4a-7f2e-9a8b-4d6e7f8a9b0c",
      "01931b3e-8d5b-7a1f-9c2d-5e6f7a8b9c0d"
    ]
  }'

Response (200), the scope the key is left with:

{
  "data": {
    "id": "0190f2c0-77aa-7b21-8c33-1d2e3f405162",
    "object": "api_key_nif_scope",
    "scope": [
      {
        "id": "01931b3e-7c4a-7f2e-9a8b-4d6e7f8a9b0c",
        "tax_id": "B12345674",
        "name": "Talleres García SL"
      },
      {
        "id": "01931b3e-8d5b-7a1f-9c2d-5e6f7a8b9c0d",
        "tax_id": "B87654323",
        "name": "Distribuciones Norte SL"
      }
    ]
  }
}

The same ceilings as when you issue a key apply:

  • Your own reach. You can only change a key whose current scope is within yours, and the new scope can never exceed yours: nif_scope_exceeds_issuer otherwise. Every tax ID must be in your account or reached through a relation in force, and in the environment of the key.
  • Levels. A tax ID that enters takes the level that the person who issued the key holds now, and is rejected with nif_access_level_exceeds_emitter if that person does not reach it at the level the key needs. The tax IDs that stay keep their stored level: changing the scope never raises one.
  • Plans. Every scope the key already has is checked again against the plan of all the tax IDs of the new scope. A scope that one of them does not allow answers 422 scope_not_allowed_by_plan and nothing changes.
  • What cannot change. A key published in a browser, because it has allowed origins, keeps its scope frozen and answers 422 resource_immutable. A revoked key answers 422 api_key_already_revoked, and a test key cannot change its scope. Sending the same scope again changes nothing.

Who sees and manages a key

A key lists, retrieves, rotates, revokes and changes the scope of another key of its account only if the whole scope of that other key is contained in its own. Anything else answers 404 api_key_not_found, the same as a key that does not exist or that belongs to another account. It is containment and not intersection because managing a key gives its secret, and the secret gives its whole scope: a key scoped to {A} could otherwise rotate a key scoped to {A, B} and reach B.

So a key scoped to one tax ID cannot see the keys of its siblings, and a key sees every key whose tax IDs it also reaches. The list includes the revoked keys, so you keep the history, and it never returns a secret. The containment counts the tax IDs a key reaches today: the ones left behind by an access that ended do not count.

Rotate the secret

POST /v1/accounts/{account}/api-keys/{api_key}/rotate-secret generates a fresh prefix and 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 the new one out with no downtime. It requires an Idempotency-Key and the account:write scope.

curl -X POST \
  https://api.factuarea.com/v1/accounts/$ACCOUNT_ID/api-keys/$API_KEY_ID/rotate-secret \
  -H "Authorization: Bearer $FACTUAREA_KEY" \
  -H "Idempotency-Key: $(uuidgen)"

During the grace window both secrets authenticate. Once it expires the previous one is rejected, and a second rotation inside the window keeps only the secret that was current before it and drops the older one at once. Roll the new secret out within those 24 hours. Rotation is irreversible: API keys describes the warning that a request on the previous secret carries.

Revoke a key

POST /v1/accounts/{account}/api-keys/{api_key}/revoke revokes a key permanently. Subsequent requests authenticated with it fail with 401. An optional reason, up to 500 characters, stays in the audit log. It requires an Idempotency-Key and the account:write scope. You may revoke the key currently in use, and doing so cuts off your own access.

curl -X POST \
  https://api.factuarea.com/v1/accounts/$ACCOUNT_ID/api-keys/$API_KEY_ID/revoke \
  -H "Authorization: Bearer $FACTUAREA_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{ "reason": "Rotated out of the deploy pipeline" }'

Revocation is irreversible. Once revoked, the key cannot be restored: issue a new one if the integration still needs API access.

Scopes and isolation

Key management is gated by the account scopes:

  • account:read lists and retrieves the keys of your account that you see.
  • account:write issues, re-scopes, rotates and revokes them.

Every operation is limited to your account. An {account} that is not yours answers 404 account_not_found, never a 403, and it is indistinguishable from an account that does not exist. The same isolation governs the tax ID portfolio and naming a tax ID in the path: a key can only ever see and act on the tax IDs of its scope.

Store permissions

Store management uses stores:read and stores:write ; provider-specific scopes are woocommerce_store:read , woocommerce_store:write , shopify_store:read and shopify_store:write . Connection-test operations require the provider write scope. These scopes are assigned to API keys and are outside OAuth consent. See ecommerce stores for the operation matrix.

On this page

Need a hand?Contact support