# Invitations

Invite a person to the account by copy-link or email, assign a role and initial grants, and revoke pending invitations.

To add a person to an account you create an **invitation** — its own resource, separate
from [members](/multi-nif/members-and-grants). It is a single-use, expiring token that
carries the target role and, for a `MEMBER`, the initial company grants. The person becomes
a member **only when they accept**; a revoked or expired invitation never produces one.

Invitations hang off the account — `/v1/accounts/{account_id}/invitations` — and require
**OWNER or ADMIN** plus **`members:read`** / **`members:write`**.

<Callout type="warn">
  **Creating and revoking need your live key.** Accepting an invitation grants real access
  in Test and Live alike, so both writes answer
  `403` [`LIVE_CREDENTIAL_REQUIRED`](/errors/LIVE_CREDENTIAL_REQUIRED) with a `beel_sk_test_` key. Listing
  and reading work with either key. See [Test and live keys](/multi-nif#test-and-live-keys).
</Callout>

## Invite a person

`POST /v1/accounts/{account_id}/invitations` — **`members:write`**, live key. Full field
list and types: [Invite a person to the account](/invitations/createAccountInvitation).

```bash
curl -X POST https://app.beel.es/api/v1/accounts/3fa85f64-5717-4562-b3fc-2c963f66afa6/invitations \
  -H "Authorization: Bearer beel_sk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "invited_email": "colleague@example.com",
    "account_role": "MEMBER",
    "send_email": false,
    "grants": [
      { "company_id": "550e8400-e29b-41d4-a716-446655440000", "access_level": "OPERATE" }
    ]
  }'
```

The `201` carries `invitation_id`, `expires_at`, `token` and `invitation_url`. An
invitation expires 7 days after it is created.

Things about the body that are easy to get wrong:

- **`OWNER` cannot be invited.** `account_role` accepts `ADMIN` or `MEMBER`; `OWNER` is
  `422` [`OWNER_ROLE_NOT_ASSIGNABLE`](/errors/OWNER_ROLE_NOT_ASSIGNABLE) whoever asks. Ownership only moves by
  [handover from the dashboard](/multi-nif/members-and-grants#hand-over-ownership).
- **Send `grants` explicitly.** `[]` invites somebody with no NIF access yet; grant it after
  they accept. An explicit `null` is rejected with
  `422` [`VALIDATION_ERROR`](/errors/VALIDATION_ERROR) — the API will not guess whether you
  meant "none" or "forgot".
- **Grants only make sense for `account_role: "MEMBER"`.** An `ADMIN` reaches every NIF
  implicitly; sending grants with one is `422` [`GRANTS_ONLY_FOR_MEMBER`](/errors/GRANTS_ONLY_FOR_MEMBER).
  A grant naming a NIF outside the account is
  `422` [`GRANT_COMPANY_NOT_IN_ACCOUNT`](/errors/GRANT_COMPANY_NOT_IN_ACCOUNT).
- **One pending invitation per email.** Inviting an email that already has a `PENDING`
  invitation replaces it: the new one is created and the old one becomes `REVOKED`, so its
  link stops working. Inviting someone who is already a member is
  `409` [`MEMBER_ALREADY_IN_ACCOUNT`](/errors/MEMBER_ALREADY_IN_ACCOUNT).

<Callout type="warn">
  **`token` and `invitation_url` are shown once and never again.** Not by the list
  endpoint, not by the get. Capture them from the `201` and deliver the link; lose them and
  your only option is to revoke the invitation and issue a new one.
</Callout>

### Copy-link or email

- **Copy-link (default, `send_email: false`)** — you get a ready-to-share `invitation_url`
  plus the raw `token`, and deliver it however you like: your own email, chat, in-app. Full
  control over the message and the branding.
- **Email (`send_email: true`)** — BeeL. also sends the invitation email for you. You still
  receive the token and URL in the response, so you can follow up yourself.

Both mint the same single-use token; `invitation_url` is just that token wrapped in the
acceptance link for the current environment, so you never assemble it by hand.

### Acceptance happens outside the API

The invitee opens the link and accepts on BeeL. — setting a password if they are new, and
accepting BeeL.'s terms and privacy policy. There is **no public endpoint for you to
call**: acceptance is a session action on BeeL.'s side, the same way a provisioned account
is [claimed](/multi-nif/managed-accounts#hand-the-account-over-claim-tokens). You observe the
result, you do not drive it: the invitation flips to `ACCEPTED` and the person shows up in
`GET /v1/accounts/{account_id}/members` with the grants the invitation carried.

## Track and revoke

`GET /v1/accounts/{account_id}/invitations` — **`members:read`**, paginated with `page` and
`limit`. Every invitation stays readable for its whole life:

| `status` | Meaning |
|----------|---------|
| `PENDING` | Active and not yet expired. Revocable. |
| `ACCEPTED` | The invitee accepted; they are now a member. |
| `REVOKED` | Cancelled before acceptance, by you or by a newer invitation to the same email. |
| `EXPIRED` | Passed its `expires_at` unaccepted. |

<Callout type="info">
  **`ACCEPTED`, `REVOKED` and `EXPIRED` invitations are not deleted.** The record is the
  audit trail of who was ever granted access to the account's fiscal data — revoking one
  does not erase it. So a `404` from the get means exactly one thing: no invitation with
  that id exists in this account.
</Callout>

`DELETE /v1/accounts/{account_id}/invitations/{invitation_id}` — **`members:write`**, live
key, `204`. Only a `PENDING` invitation can be revoked; one already accepted, revoked or
expired returns `404` [`PENDING_INVITATION_NOT_FOUND`](/errors/PENDING_INVITATION_NOT_FOUND) rather than
disclosing which of the three it is. See
[Revoke a member invitation](/invitations/deleteAccountInvitation).

## Related

<Related>

- [Members & grants](/multi-nif/members-and-grants) — what the role and the access level each decide, once they are in
- [The model](/multi-nif#member-or-managed-account) — member or managed account: which one you need

</Related>

---

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