RBAC & Team Management
AnvilBase has three distinct role systems for three distinct questions. Keeping them separate is important.
| System | Answers | Roles |
|---|---|---|
| Platform operator roles | who can administer the deployment | super_admin, project_admin, developer, viewer |
| Project membership roles | who can work on a project | owner, admin, editor, read_only |
| Data-plane scopes | what an application request can do | anon, authenticated, service_role |
The data-plane scopes are covered in API Keys & Scopes; this page covers the two human-facing role systems.
Platform operators
Platform users are operator accounts for the whole deployment (separate from any project’s end users). Passwords are Argon2-hashed.
| Role | Management-API scopes | Capabilities |
|---|---|---|
super_admin | admin (global) | full access; create/delete projects and platform users; the sole global-admin session role |
project_admin | read (global) | administers only the projects it is a member of — its global scope is read-only; in-project power comes from its project_members role (owner/admin → in-project admin) |
developer | deploy + read (global) | the CI surface (db push, functions deploy, secrets set, schema DDL) plus all reads on projects it is a member of; no platform-admin mutations |
viewer | read (global) | read-only (GET routes, minus sensitive reads: secret reveals, bulk audit export, platform-user listing) |
Per-project boundary (sessions). On
/api/v1/projects/{id}/…routes a non-super_adminplatform session is authorized by its project membership role for that project (see Project membership & invites), not by its global platform scope — and a non-member receives403.super_adminand the bootstrapANVILBASE_ADMIN_TOKENremain global.GET /api/v1/projectsreturns only the caller’s member projects for a non-super_adminsession.
Platform users sign in with POST /api/v1/platform/login (email + password) and
receive a 12-hour session JWT whose role maps to the scopes above — see the
Management API auth model.
Note: a project-scoped Personal Access Token (a PAT minted with --project <id>) provides a per-project management credential bound by its project_id
column — see
Management API › Project-scoped tokens.
Platform sessions are bound per project by their project_members role
instead (see the per-project boundary note above): the two mechanisms are
orthogonal and compose.
Manage them via the admin API:
# Create a platform usercurl -X POST http://localhost:39001/api/v1/admin/users \ -H "Authorization: Bearer $ANVILBASE_TOKEN" -H "Content-Type: application/json" \ -d '{"email":"ops@example.com","password":"changeme123","role":"developer","display_name":"Ops"}'
# List / update / deletecurl http://localhost:39001/api/v1/admin/users -H "Authorization: Bearer $ANVILBASE_TOKEN"curl -X PATCH http://localhost:39001/api/v1/admin/users/<uid> -d '{"role":"project_admin"}' ...curl -X DELETE http://localhost:39001/api/v1/admin/users/<uid> ...The platform refuses to delete the last active super_admin (400) so you
can’t lock yourself out.
Scope enforcement. Every management route is classified into a required scope (
admin⊇deploy⊇read, unknown routes fail closed toadmin) and the platform-auth middleware rejects under-scoped credentials with a403naming the missing scope. This applies uniformly to PATs (theirscopescolumn), platform sessions (role mapping above), and the bootstrapANVILBASE_ADMIN_TOKEN(alwaysadmin). Mint least-privilege PATs withanvilbase token mint --scope read|deployfor CI and automation — see Scopes (RBAC).
Project membership & invites
Project membership is enforced on /api/v1/projects/{id}/… for platform
sessions: a non-super_admin session’s membership role for a project defines its
scope on that project (not its global platform role), and a non-member is
403. super_admin and the bootstrap ANVILBASE_ADMIN_TOKEN remain global.
| Role | In-project scope | Enforced capabilities on /api/v1/projects/{id}/… |
|---|---|---|
owner | admin | full in-project admin — everything, including deletion, team management, rotation, secrets |
admin | admin | full in-project admin (same route scope as owner) |
editor | deploy + read | the deploy surface (db push, functions deploy, secrets set, schema DDL) plus all in-project reads |
read_only | read | read-only in-project access (GET routes) |
Membership roles are load-bearing for access control — not informational.
Initial membership is seeded by a super_admin (or by an existing owner/admin
via PATCH /api/v1/projects/{id}/members/{user_id}); see the invite flow below.
Invite a teammate
# CLIanvilbase auth invites create --project <id> --email teammate@example.com --role admin
# API — returns an invite_token (shown once)curl -X POST http://localhost:39001/api/v1/projects/<id>/invites \ -H "Authorization: Bearer $ANVILBASE_TOKEN" -H "Content-Type: application/json" \ -d '{"email":"teammate@example.com","role":"admin"}'The default role is editor. A 409 means a pending invite already exists for
that email on the project. Invites carry a secure token and an expiry.
List & revoke
anvilbase auth invites list --project <id>
curl -X DELETE http://localhost:39001/api/v1/projects/<id>/invites/<invite_id> \ -H "Authorization: Bearer $ANVILBASE_TOKEN"Accept an invite
The invite token is the authorization — any authenticated platform user
(logged in via POST /api/v1/platform/login, regardless of their platform role)
can accept an invite they hold the token for. The new project_members row is
always bound to the authenticated session user, so a low-privilege account
can onboard itself without a super_admin:
curl -X POST http://localhost:39001/api/v1/invites/accept \ -H "Authorization: Bearer <platform_session_token>" \ -H "Content-Type: application/json" \ -d '{"token":"<invite_token>"}'user_id in the body is optional and, if supplied, must equal the session
user (a mismatch returns 403) — you can never grant membership to a different
account this way. The user-less bootstrap admin token cannot self-onboard; seed
membership for someone else with PATCH /api/v1/projects/{id}/members/{user_id}.
On success the invite is marked accepted and a project_members row is created.
An invalid, expired, or already-used token returns 404.
In the console
Project → Team lists members and pending invites, lets you invite by email with
a role, and revoke invites. Platform-user management lives in the deployment admin
area (visible to super_admin).
Best practices
- Grant the lowest role that lets someone do their job; promote deliberately.
- Use
read_onlyfor stakeholders who only need to look. - Keep the number of
super_adminoperators small, and rotate PATs that are no longer needed. - Every membership and role change is recorded in the audit log.
Next: Encryption.