# 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

```mermaid
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](/multi-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](/multi-nif/members-and-grants),
   [Invitations](/multi-nif/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](/multi-nif/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](/multi-nif/invitations) 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](/multi-nif/managed-accounts). Your bill; they can claim the account
  later without you losing your access.

> **Rules that apply here:** [REC-005 · One chain per NIF, one company per NIF](/rules/records#rec-005)

## 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`](/errors/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](/auth/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](/auth)).
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:

| Operation | Test key | Live key |
| --- | --- | --- |
| Read members, grants and invitations | Yes | Yes |
| Invite, revoke an invitation, change a role, remove a member, set or remove a grant | `403` [`LIVE_CREDENTIAL_REQUIRED`](/errors/LIVE_CREDENTIAL_REQUIRED) | Yes |
| Provision accounts, issue claim tokens, import accounts, list them, read usage | Yes | Yes |
| Change your `access_level` over a managed account, end its management | `403` [`LIVE_CREDENTIAL_REQUIRED`](/errors/LIVE_CREDENTIAL_REQUIRED) | Yes |
| Switch a NIF on or off in Test or Live | Yes — the mode travels in the body | Yes |
| Submit a signed VeriFactu representation | `400` [`SIGNING_ONLY_IN_LIVE`](/errors/SIGNING_ONLY_IN_LIVE) | Yes |
| Hand ownership of the account over | `403` [`OPERATION_REQUIRES_SESSION`](/errors/OPERATION_REQUIRES_SESSION) | `403` [`OPERATION_REQUIRES_SESSION`](/errors/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](/multi-nif/members-and-grants#hand-over-ownership).

## Two identifiers

| | What it is | The call that returns it |
| --- | --- | --- |
| **`account_id`** | The account: subscription, API keys, members, webhooks. | `GET /v1/me/identity` → `data.account_id` |
| **`company_id`** | One NIF. | `GET /v1/accounts/{account_id}/companies` → `data.companies[].id` |

<Callout type="warn">
  **`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`.
</Callout>

```bash
# 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:

```text
/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.

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

<Callout type="warn">
  **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.
</Callout>

## When it does not add up

| When | Error | Status |
|---|---|---|
| The key lacks a scope. `error.details.missing_scopes` names them, comma-separated. | [`INSUFFICIENT_SCOPE`](/errors/INSUFFICIENT_SCOPE) | `403` |
| The `{account_id}` is not yours to reach — the same answer as *does not exist*, so existence is never disclosed. | [`ACCOUNT_NOT_ACCESSIBLE`](/errors/ACCOUNT_NOT_ACCESSIBLE) | `403` |
| 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_ACCESSIBLE`](/errors/ACTIVE_COMPANY_NOT_ACCESSIBLE) | `403` |
| 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_ONLY`](/errors/COMPANY_READ_ONLY) | `403` |
| Members and invitations need `OWNER` or `ADMIN` of that account. A provisioner is neither on the accounts it manages. | [`MEMBER_MANAGEMENT_FORBIDDEN`](/errors/MEMBER_MANAGEMENT_FORBIDDEN) | `403` |
| Account settings (API keys, webhooks) need `OWNER` or `ADMIN` of that account. | [`ACCOUNT_MANAGEMENT_FORBIDDEN`](/errors/ACCOUNT_MANAGEMENT_FORBIDDEN) | `403` |
| A control-plane write made with a `beel_sk_test_` key. See [Test and live keys](#test-and-live-keys). | [`LIVE_CREDENTIAL_REQUIRED`](/errors/LIVE_CREDENTIAL_REQUIRED) | `403` |
| Handing ownership over. Dashboard only. | [`OPERATION_REQUIRES_SESSION`](/errors/OPERATION_REQUIRES_SESSION) | `403` |
| Provisioning without the managed-accounts capability, which BeeL. enables on request. | [`FEATURE_NOT_AVAILABLE`](/errors/FEATURE_NOT_AVAILABLE) | `403` |
| A member grant names a NIF of a different account — including one of the accounts you manage. | [`GRANT_COMPANY_NOT_IN_ACCOUNT`](/errors/GRANT_COMPANY_NOT_IN_ACCOUNT) | `422` |
| 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_REQUIRED`](/errors/ACTIVE_COMPANY_REQUIRED) | `403` |

<Callout type="info">
  **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`](/errors/COMPANY_READ_ONLY) or, on NIF routes,
  [`ACTIVE_COMPANY_NOT_ACCESSIBLE`](/errors/ACTIVE_COMPANY_NOT_ACCESSIBLE). Neither is fixed by retrying: raise the
  access, or read instead.
</Callout>

## Is a NIF ready to invoice?

```bash
curl https://app.beel.es/api/v1/companies/550e8400-e29b-41d4-a716-446655440000/issuing-readiness \
  -H "Authorization: Bearer beel_sk_live_xxx"
```

```json
{
  "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.

| Blocker | What it means | How to clear it |
|---|---|---|
| `COMPANY_HAS_NO_NIF` | The company has no tax identification number on file. | Set the NIF on the company. |
| `SERIES_DEFAULT_NOT_FOUND` | No 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_INCOMPLETE` | The 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_ACTIVATED` | The company is not activated in the environment you are calling, and it issues without Veri*Factu. | Activate the company in that environment. |
| `ENV_MISMATCH` | The 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_REGISTERED` | The NIF is not registered with AEAT for Veri*Factu. | Complete the Veri*Factu configuration for the NIF. |
| `NIF_REPRESENTATION_REQUIRED` | A 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](/companies/getCompanyIssuingReadiness).

## Related

<Related>

- [Companies](/multi-nif/companies) — create, switch on, update and delete NIFs; the VeriFactu representation
- [Payment connections](/multi-nif/payment-connections) — Connect Stripe per NIF, and turn charges into invoices
- [Members & grants](/multi-nif/members-and-grants) — roles, per-NIF access, and handing ownership over
- [Invitations](/multi-nif/invitations) — add people to the account with a role and initial grants
- [Managed accounts](/multi-nif/managed-accounts) — build an accounting-firm or platform integration: provision, claim, operate their invoicing

</Related>

---

Full OpenAPI spec: https://docs.beel.es/api/openapi