NewProvince is only required for addresses in Spain
BeeL
Get startedMulti-NIFVeriFactuRulesStripeAPI referenceChangelog

Overview

The multi-NIF model in one place — accounts, companies (NIFs), members with grants and managed accounts; which credential acts on what, what needs a live key, and what the errors mean.


Your client is a freelancer who also runs an S.L., so they invoice under two tax IDs. An accounting firm (gestoría) runs the invoicing of forty clients. Both are the same model, and this page explains it once; every other page in this section links back here.

The model

graph LR
A["Your account<br/>account_id"] -->|holds 1..N| C["Company = one NIF<br/>company_id"]
A -->|has| M["Members<br/>OWNER · ADMIN · MEMBER"]
M -.->|"grant VIEW / OPERATE<br/>(MEMBER only)"| C
A -->|"manages, at an access_level"| B["Managed account<br/>provisioned by you"]
B -->|holds 1..N| D["Company = one NIF"]

Four pieces, in this order:

  1. Account — who pays and authenticates. It owns the subscription, the API keys, the webhooks and the people. Identified by account_id.
  2. Companies — each NIF/CIF the account invoices under, with its own invoices, customers, products and series, kept apart in Test and Live. Identified by company_id. Throughout the docs and the API reference, Company always means one NIF. → Companies
  3. Members and grants — the people with access to the account. An OWNER or ADMIN reaches every NIF; a MEMBER reaches only the NIFs they hold a grant on, at VIEW or OPERATE. → Members & grants, Invitations
  4. Managed accounts — separate accounts that you provisioned for someone else. You pay for them and hold one access_level over each (NONE, VIEW or OPERATE); the holder can claim theirs later. → Managed accounts

An account is not a NIF: the account is who pays and authenticates, the NIF is who invoices. A freelancer with one NIF has one account and one company — the distinction only shows up at two.

Member or managed account?

Both give you access to somebody else's NIFs, and they are not interchangeable:

  • The taxpayer signs up and pays for themselves, and wants your help → they invite you as a member of their account. Their account, their bill; you hold a role in it.
  • You onboard them by API and you pay → you provision a managed account. Your bill; they can claim the account later without you losing your access.

Which credential acts on what

  • An API key belongs to one account and is created by an OWNER or ADMIN of it (a MEMBER cannot create keys: 403 ACCOUNT_MANAGEMENT_FORBIDDEN). It reaches every NIF of that account, plus the NIFs of the accounts it manages, at the access_level held over each.
  • Grants govern people in the dashboard. They decide what a MEMBER sees when signed in; they never apply to an API key.
  • Scopes decide which operations a key may call; access decides where it may write. A key with invoices:write still cannot issue for a NIF you only hold at VIEW. The full scope catalog, including the two provisioning scopes, is in Scopes.
  • The target is always the id in the path, never the credential. An API key is stateless and carries no notion of a "current" NIF.

Test and live keys

The base URL is the same; the key prefix picks the environment (Authentication). For everything that belongs to a NIF — invoices, customers, series, readiness, payment connections — a beel_sk_test_ key works on Test data and a beel_sk_live_ key on Live data.

The account's control plane is different: members, grants, invitations and your management of an account are not per environment. A collaborator is the same person with the same grants in Test and Live, so a change there is real whichever key makes it. That is why these writes refuse a test key:

OperationTest keyLive key
Read members, grants and invitationsYesYes
Invite, revoke an invitation, change a role, remove a member, set or remove a grant403 LIVE_CREDENTIAL_REQUIREDYes
Provision accounts, issue claim tokens, import accounts, list them, read usageYesYes
Change your access_level over a managed account, end its management403 LIVE_CREDENTIAL_REQUIREDYes
Switch a NIF on or off in Test or LiveYes — the mode travels in the bodyYes
Submit a signed VeriFactu representation400 SIGNING_ONLY_IN_LIVEYes
Hand ownership of the account over403 OPERATION_REQUIRES_SESSION403 OPERATION_REQUIRES_SESSION

Handing ownership over is dashboard-only. No API key can do it, a live one included, so a leaked key can never take the account. See Hand over ownership.

Two identifiers

What it isThe call that returns it
account_idThe account: subscription, API keys, members, webhooks.GET /v1/me/identity → data.account_id
company_idOne NIF.GET /v1/accounts/{account_id}/companies → data.companies[].id

company_id is a UUID, never the tax number itself. It is the id the companies endpoint returns — 550e8400-e29b-41d4-a716-446655440000, not B12345674. Sending a NIF where a company_id is expected returns 400.

# 1. Which account is this key?
curl https://app.beel.es/api/v1/me/identity \
  -H "Authorization: Bearer beel_sk_live_xxx"

# 2. Which NIFs does it hold?
curl https://app.beel.es/api/v1/accounts/3fa85f64-5717-4562-b3fc-2c963f66afa6/companies \
  -H "Authorization: Bearer beel_sk_live_xxx"

Where each identifier goes

Both identifiers travel in the path. Which one you use depends on whether the thing you are touching belongs to a NIF or to the account.

{company_id} — everything that belongs to one NIF, both configuring it and operating on it:

/v1/companies/{company_id}/invoices
/v1/companies/{company_id}/customers
/v1/companies/{company_id}/products
/v1/companies/{company_id}/series
/v1/companies/{company_id}/tax-configuration

The account that owns the NIF is derived from it, so there is no pair to keep coherent.

{account_id} — everything that belongs to the account: its list of NIFs, members, invitations, the accounts it manages, webhooks and request logs.

/v1/accounts/{account_id}/companies
/v1/accounts/{account_id}/members
/v1/accounts/{account_id}/webhooks

Migrating from the flat routes? If you integrated against /v1/invoices, /v1/customers, /v1/products or /v1/configuration/series — routes with no company_id — those pick the NIF from a BeeL-Active-Company request header. They are deprecated: they answer with Deprecation and Sunset headers and stop working on the date the Sunset header announces. Move the NIF out of the header and into the path; the behaviour is otherwise identical.

When it does not add up

WhenErrorStatus
The key lacks a scope. error.details.missing_scopes names them, comma-separated.INSUFFICIENT_SCOPE403
The {account_id} is not yours to reach — the same answer as does not exist, so existence is never disclosed.ACCOUNT_NOT_ACCESSIBLE403
The same, for the {company_id}. Today it is also the answer to a write under /v1/companies/{company_id}/… when you only hold VIEW over that NIF; reads still work.ACTIVE_COMPANY_NOT_ACCESSIBLE403
A write on an account-scoped route while you hold VIEW — for example adding a NIF to an account you manage at VIEW.COMPANY_READ_ONLY403
Members and invitations need OWNER or ADMIN of that account. A provisioner is neither on the accounts it manages.MEMBER_MANAGEMENT_FORBIDDEN403
Account settings (API keys, webhooks) need OWNER or ADMIN of that account.ACCOUNT_MANAGEMENT_FORBIDDEN403
A control-plane write made with a beel_sk_test_ key. See Test and live keys.LIVE_CREDENTIAL_REQUIRED403
Handing ownership over. Dashboard only.OPERATION_REQUIRES_SESSION403
Provisioning without the managed-accounts capability, which BeeL. enables on request.FEATURE_NOT_AVAILABLE403
A member grant names a NIF of a different account — including one of the accounts you manage.GRANT_COMPANY_NOT_IN_ACCOUNT422
Only on the deprecated flat routes: several NIFs and no BeeL-Active-Company header. It cannot happen once the NIF is in the path.ACTIVE_COMPANY_REQUIRED403

Treat both read-only answers the same way. A write you are not allowed to make with VIEW access comes back as COMPANY_READ_ONLY or, on NIF routes, ACTIVE_COMPANY_NOT_ACCESSIBLE. Neither is fixed by retrying: raise the access, or read instead.

Is a NIF ready to invoice?

curl https://app.beel.es/api/v1/companies/550e8400-e29b-41d4-a716-446655440000/issuing-readiness \
  -H "Authorization: Bearer beel_sk_live_xxx"
{
  "success": true,
  "data": {
    "ready": true,
    "blockers": [],
    "verifactu": { "ready": false, "blockers": ["NIF_NOT_REGISTERED"] }
  }
}

ready is true only when blockers is empty. Two things the answer depends on:

  • The environment of your key. A beel_sk_test_ key asks about Test, a beel_sk_live_ key about Live, so the same NIF can be ready in one and not in the other — a NIF provisioned in Test is ready with a test key and reports COMPANY_NOT_ACTIVATED with a live one until you switch it on there.
  • Which question you ask. ready / blockers say whether the NIF can issue. verifactu.ready / verifactu.blockers say whether its invoices would pass VeriFactu registration — a separate question. A Live NIF can be ready: true while verifactu.blockers still lists what AEAT registration needs.
BlockerWhat it meansHow to clear it
COMPANY_HAS_NO_NIFThe company has no tax identification number on file.Set the NIF on the company.
SERIES_DEFAULT_NOT_FOUNDNo default invoice series exists for the environment.Switch the company on in that environment (it creates the default series), or mark an existing series as default.
PROFILE_INCOMPLETEThe company's fiscal identity is not complete enough to write an invoice header (entity type, legal name or fiscal address).Complete the entity type, legal name and fiscal address on the company.
COMPANY_NOT_ACTIVATEDThe company is not activated in the environment you are calling, and it issues without Veri*Factu.Activate the company in that environment.
ENV_MISMATCHThe NIF is not switched on in the environment you are calling, and its invoices go to VeriFactu there.Activate the company in that environment.
NIF_NOT_REGISTEREDThe NIF is not registered with AEAT for Veri*Factu.Complete the Veri*Factu configuration for the NIF.
NIF_REPRESENTATION_REQUIREDA signed fiscal representation from the account holder is missing.Generate the representation, have the holder sign it, and submit it.

Add ?include=readiness to GET /v1/accounts/{account_id}/companies to get the same answer for every NIF in one call. See Get the issuing readiness of a company.