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.
Every path on this page hangs off the account — /v1/accounts/{account_id}/members/… —
because a membership belongs to the tenant, not to a NIF. Your own {account_id} comes
from GET /v1/me/identity (data.account_id). All of them require OWNER or ADMIN
of that account, plus the members:read (read) or members:write (write)
scope.
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:
| access_level | What the holder can do |
|---|---|
NONE | Cover their subscription. No access to their data. This is the default. |
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 is one { company_id, access_level } pair. NONE is not accepted in a grant —
a grant that grants nothing is not a grant, so you remove access by deleting the grant,
not by downgrading it.
OWNER/ADMIN cannot receive grants. They already reach everything, so their
grant list is always empty. Grants apply to MEMBERs only — trying otherwise is
422 GRANTS_ONLY_FOR_MEMBER.
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.
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_idis the handle for every call on this page. It identifies this person in this account.person_idis 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. The company is in the path, so the body carries only the level:
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" }'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.
Revoking is the mirror image — DELETE on the same path, 204:
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 and
Revoke a member's access.
Two 422s are worth branching on: GRANTS_ONLY_FOR_MEMBER (the target is not a
MEMBER) and GRANT_COMPANY_NOT_IN_ACCOUNT (the NIF hangs off another account).
Change a member's role
PATCH /v1/accounts/{account_id}/members/{member_id} — members:write, body
{ "account_role": "ADMIN" }. See Change a member's role.
The only assignable roles are ADMIN and MEMBER. OWNER is never accepted here —
it returns 422 OWNER_ROLE_NOT_ASSIGNABLE, because ownership moves only through the
handover below, never by setting a role.
Promoting a MEMBER to ADMIN makes their grants irrelevant — they now reach every
NIF implicitly. Demoting back to MEMBER leaves them with 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. To step down, hand ownership over
first.
Hand over ownership
PUT /v1/accounts/{account_id}/owner — members:write, callable only by an
OWNER. The member in the body becomes OWNER and the caller drops to ADMIN:
curl -X PUT https://app.beel.es/api/v1/accounts/3fa85f64-5717-4562-b3fc-2c963f66afa6/owner \
-H "Authorization: Bearer beel_sk_live_xxx" \
-H "Content-Type: application/json" \
-d '{ "member_id": "6f8c1e10-3b2a-4c9d-8e7f-1a2b3c4d5e6f" }'It states the desired owner rather than performing a transfer. That is why it is a
PUT on the account's owner and not a POST on a member: repeating the call once
that member already owns the account returns the same 204 instead of failing, so a
retried request after a timeout is safe.
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 you to 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.
Next steps
Connect Stripe
Connect Stripe per company (NIF) so its payments auto-generate VeriFactu-compliant invoices under the right NIF — for the companies you own and, white-label, for the managed accounts you provision.
Invitations
Invite a person to the account by copy-link or email, assign a role and initial grants, and revoke pending invitations.