# Members & grants

Account roles (owner/admin/member), per-company grants with an access level (view/operate), and handing over ownership.

**Members** are the people with access to an account. Each member has one **account
role**; a `MEMBER` additionally has per-company **grants** that decide which NIFs they can
see and operate. Where members sit in the model, and when you want a managed account
instead, is in the [Overview](/multi-nif#the-model).

Every path on this page hangs off the account — `/v1/accounts/{account_id}/members/…` —
because a membership belongs to the account, not to a NIF. All of them require **OWNER or
ADMIN** of that account (`403` [`MEMBER_MANAGEMENT_FORBIDDEN`](/errors/MEMBER_MANAGEMENT_FORBIDDEN) otherwise),
plus the **`members:read`** or **`members:write`** scope.

<Callout type="warn">
  **Changing people needs your live key.** Members and grants are shared between Test and
  Live, so every write on this page made with a `beel_sk_test_` key answers
  `403` [`LIVE_CREDENTIAL_REQUIRED`](/errors/LIVE_CREDENTIAL_REQUIRED). Reading works with either key. See
  [Test and live keys](/multi-nif#test-and-live-keys).
</Callout>

## Two independent axes

Role and access are not the same question, and confusing them is the usual mistake:

- **Account role** — *who administers the account*. Billing, API keys, inviting people.
- **Access level** — *how much someone can do with one NIF*. Read, or read and write.

| account_role | Administers | Reaches |
|---|---|---|
| `OWNER` | Full control: billing, API keys, members, and handing ownership over. Exactly one per account. | Every NIF, implicitly. |
| `ADMIN` | Everything an OWNER can do except handing ownership over. | Every NIF, implicitly. |
| `MEMBER` | Nothing. No billing, no keys, no member management. | Only the NIFs granted to them, at the level of each grant. |

`OWNER` and `ADMIN` reach **every** NIF of the account implicitly. Only a `MEMBER` is
scoped NIF by NIF, through grants, at one of these levels:

| access_level | What the holder can do |
|---|---|
| `VIEW` | Read their invoices, customers, products, series and fiscal data. |
| `OPERATE` | Everything in VIEW, plus creating and editing them. Issuing on their behalf additionally requires a signed fiscal representation from the account holder. |

A grant takes `VIEW` or `OPERATE`. `NONE` only exists for [managed
accounts](/multi-nif/managed-accounts#access-levels): a grant that grants nothing is not a
grant, so you remove access by deleting the grant, not by downgrading it.

Grants govern people who use the dashboard. API keys are created by an `OWNER` or `ADMIN`
and act with that reach; a `MEMBER` cannot create keys.

<Callout type="info">
  **`OWNER`/`ADMIN` cannot receive grants.** They already reach everything, so their grant
  list is always empty. Grants apply to `MEMBER`s only — trying otherwise is
  `422` [`GRANTS_ONLY_FOR_MEMBER`](/errors/GRANTS_ONLY_FOR_MEMBER).
</Callout>

## Who is in the account

`GET /v1/accounts/{account_id}/members` lists them; `GET …/members/{member_id}` returns
one, in the same shape. Fields and types: [List account members](/members/listAccountMembers).

```bash
curl https://app.beel.es/api/v1/accounts/3fa85f64-5717-4562-b3fc-2c963f66afa6/members \
  -H "Authorization: Bearer beel_sk_live_xxx"
```

Two ids come back and they are not interchangeable:

- **`member_id`** is the handle for every call on this page. It identifies this person
  *in this account*.
- **`person_id`** is the stable identity of the human, the same across every account they
  belong to. Use it to recognise the same person across the accounts you manage — you never
  pass it to an endpoint.

Each member also carries a `permissions` block (`can_change_role`, `assignable_roles`,
`can_remove`, `can_transfer_ownership`, `can_edit_grants`). Drive your UI from it instead
of re-implementing the role policy; the API enforces the same rules regardless of what you
render.

## Grant a member access to one NIF

`PUT /v1/accounts/{account_id}/members/{member_id}/grants/{company_id}` —
**`members:write`**, live key. The company is in the path, so the body carries only the
level. Returns `200` with the grant:

```bash
curl -X PUT \
  https://app.beel.es/api/v1/accounts/3fa85f64-5717-4562-b3fc-2c963f66afa6/members/6f8c1e10-3b2a-4c9d-8e7f-1a2b3c4d5e6f/grants/550e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer beel_sk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{ "access_level": "OPERATE" }'
```

<Callout type="info">
  **One call touches one NIF.** This `PUT` creates the grant or changes its level, and
  leaves the member's other grants exactly as they were. To set up access to three NIFs you
  make three calls — there is no endpoint that replaces the whole set at once, so a
  concurrent edit by a colleague can never be silently wiped by your request.
</Callout>

Revoking is the mirror image — `DELETE` on the same path, `204`:

```bash
curl -X DELETE \
  https://app.beel.es/api/v1/accounts/3fa85f64-5717-4562-b3fc-2c963f66afa6/members/6f8c1e10-3b2a-4c9d-8e7f-1a2b3c4d5e6f/grants/550e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer beel_sk_live_xxx"
```

`GET …/members/{member_id}/grants` lists what a member currently holds — empty for
`OWNER`/`ADMIN`. See [Grant a member access to a company](/members/putAccountMemberGrant) and
[Revoke a member's access](/members/deleteAccountMemberGrant).

| When | Error | Status |
|---|---|---|
| The target is an `OWNER` or `ADMIN`. | [`GRANTS_ONLY_FOR_MEMBER`](/errors/GRANTS_ONLY_FOR_MEMBER) | `422` |
| The NIF hangs off another account — including one you manage. | [`GRANT_COMPANY_NOT_IN_ACCOUNT`](/errors/GRANT_COMPANY_NOT_IN_ACCOUNT) | `422` |
| `access_level` is `NONE` or anything other than `VIEW` / `OPERATE`. | [`VALIDATION_ERROR`](/errors/VALIDATION_ERROR) | `422` |

## Change a member's role

`PATCH /v1/accounts/{account_id}/members/{member_id}` — **`members:write`**, live key,
body `{ "account_role": "ADMIN" }`. See [Change a member's role](/members/patchAccountMember).

The only assignable roles are `ADMIN` and `MEMBER`. `OWNER` is never accepted here — it
returns `422` [`OWNER_ROLE_NOT_ASSIGNABLE`](/errors/OWNER_ROLE_NOT_ASSIGNABLE), because ownership moves only
through the handover below (from the dashboard), never by setting a role.

Promoting a `MEMBER` to `ADMIN` makes their grants irrelevant — they now reach every NIF
implicitly. Demoting back to `MEMBER` restores whatever grants they had, so check them
after the change.

**The last OWNER cannot be demoted**, and `DELETE …/members/{member_id}` cannot remove them
either: the account always keeps exactly one (`422` [`LAST_OWNER_PROTECTED`](/errors/LAST_OWNER_PROTECTED)).
To step down, hand ownership over first.

## Hand over ownership

`PUT /v1/accounts/{account_id}/owner` is **dashboard-only**. No API key can hand an account
over, a live one included: the call answers
`403` [`OPERATION_REQUIRES_SESSION`](/errors/OPERATION_REQUIRES_SESSION), so a leaked key can never take the
account. The current `OWNER` does it from **Settings → Members**; the member becomes
`OWNER` and the caller drops to `ADMIN`. An `ADMIN` who tries gets
`403` [`OWNER_ROLE_RESERVED`](/errors/OWNER_ROLE_RESERVED).

Repeating it once that member already owns the account returns the same `204`, so a retry
after a timeout is safe. See [Set the account owner](/members/putAccountOwner).

<Callout type="info">
  **Provisioning accounts for others?** You do not hand them over with this call: the
  holder takes ownership by redeeming a [claim token](/multi-nif/managed-accounts#hand-the-account-over-claim-tokens).
</Callout>

## Related

<Related>

- [Invite a new member](/multi-nif/invitations) — add people to the account by copy-link or email, with initial grants
- [Scopes reference](/auth/scopes) — the exact scopes for members and companies

</Related>

---

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