
# Invitations

`POST /api/auth/admin/create-user` makes an admin choose the new user's password and sends them nothing — workable for a script, unusable for onboarding a customer. The invitation flow closes that gap: the admin supplies `{ email, name, role }` with **no password**, Sovrium emails a single-use link, and the invitee sets their own password and lands in an authenticated session.

```yaml
auth:
  strategies:
    - type: emailAndPassword
  invitationTokenExpiry: '72h' # default; accepts '30s' / '15m' / '72h' / '7d' / ms
  emailTemplates:
    invitation:
      subject: 'You are invited to join, $name'
      text: |
        Hi $name,
        $inviterName invited you to join.
        Set your password: $url
        This invitation expires in 72 hours.
```

## The Two Endpoints

| Endpoint                                 | Behavior                                                                                                                                                                                                                      |
| ---------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `POST /api/auth/admin/invite-user`       | Accepts `{ email, name, role }` (no password). Returns `200` with `{ user, invitationSent: true }`. `401`/`403` for unauth / non-admin; `400` for invalid input; `422` when the email already maps to a fully-onboarded user. |
| `POST /api/auth/admin/accept-invitation` | Backs the public `/accept-invitation?token=...` page. The invitee sets a password and lands authenticated. `400` for an invalid token, `410` for an expired one.                                                              |

Tokens reuse the `auth.verification` table (the same shape Better Auth uses for password reset), expire after [`invitationTokenExpiry`](/en/docs/auth-registration) — `72h` by default — and are single-use, consumed on the first successful accept. A replay is rejected rather than silently re-onboarding.

The email body is rendered from the `invitation` [template](/en/docs/auth-email-templates) and substitutes `$name`, `$url`, `$email` and `$inviterName`.

:::callout
**`allowSignUp: false` does not block invitations.** Closing public self-registration is precisely when you need this flow. Admin-driven user creation stays available whenever auth is configured — see [Registration Control](/en/docs/auth-registration).
:::

## Role Assignment

Sovrium ships three default roles — `admin`, `member`, `viewer` — with hierarchy levels 80, 40 and 10, and accepts custom roles declared in the `auth` block.

An invitation carries a `role` explicitly. When one is not supplied at creation, a new user receives `auth.defaultRole`, which itself falls back to `member`:

```yaml
auth:
  strategies:
    - type: emailAndPassword
  defaultRole: viewer
  roles:
    - name: editor
      description: Can edit content
      level: 30
```

`defaultRole` is validated against the built-in roles plus your declared `roles[]`, so a typo fails `sovrium validate` rather than quietly assigning nothing. The first bootstrap admin is always created with the `admin` role and a verified email, regardless of `defaultRole`.

Roles drive every authorization decision in the platform: table [permissions](/en/docs/table-permissions), per-field access, page access, and the admin API itself. A role can be changed after the fact with `POST /api/auth/admin/set-role`.

## Invitation vs. Create-User

| You want                                       | Use                                                                    |
| ---------------------------------------------- | ---------------------------------------------------------------------- |
| A customer to choose their own password        | `invite-user` — they receive a link and never see an admin-set secret. |
| A service or seed account, no mailbox involved | `create-user` — you set the password, nothing is sent.                 |
| To onboard when SMTP is not configured         | `create-user` — an invitation email would never be delivered.          |

:::callout
**Onboarding is decoupled from access assignment.** Inviting a user creates the account; it grants no tenant scope. Wiring a user to the data they may reach is a separate step through the records API, so an admin can invite first and assign later, or the reverse. See [Assignments & Landing Guards](/en/docs/auth-assignments).
:::

## Related Pages

- [User Management](/en/docs/user-management) — admin bootstrap and the create-user API.
- [Registration Control](/en/docs/auth-registration) — `allowSignUp` and `invitationTokenExpiry`.
- [Roles & RBAC](/en/docs/auth-roles-rbac) — the full role model and field-level permissions.
- [Email Templates](/en/docs/auth-email-templates) — the `invitation` subject and body.
- [Assignments & Landing Guards](/en/docs/auth-assignments) — granting a user their tenant scope.
