Skip to content

RBAC & Team Management

AnvilBase has three distinct role systems for three distinct questions. Keeping them separate is important.

SystemAnswersRoles
Platform operator roleswho can administer the deploymentsuper_admin, project_admin, developer, viewer
Project membership roleswho can work on a projectowner, admin, editor, read_only
Data-plane scopeswhat an application request can doanon, 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.

RoleManagement-API scopesCapabilities
super_adminadmin (global)full access; create/delete projects and platform users; the sole global-admin session role
project_adminread (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)
developerdeploy + 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
viewerread (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_admin platform 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 receives 403. super_admin and the bootstrap ANVILBASE_ADMIN_TOKEN remain global. GET /api/v1/projects returns only the caller’s member projects for a non-super_admin session.

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:

Terminal window
# Create a platform user
curl -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 / delete
curl 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 (admindeployread, unknown routes fail closed to admin) and the platform-auth middleware rejects under-scoped credentials with a 403 naming the missing scope. This applies uniformly to PATs (their scopes column), platform sessions (role mapping above), and the bootstrap ANVILBASE_ADMIN_TOKEN (always admin). Mint least-privilege PATs with anvilbase token mint --scope read|deploy for 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.

RoleIn-project scopeEnforced capabilities on /api/v1/projects/{id}/…
owneradminfull in-project admin — everything, including deletion, team management, rotation, secrets
adminadminfull in-project admin (same route scope as owner)
editordeploy + readthe deploy surface (db push, functions deploy, secrets set, schema DDL) plus all in-project reads
read_onlyreadread-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

Terminal window
# CLI
anvilbase 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

Terminal window
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:

Terminal window
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_only for stakeholders who only need to look.
  • Keep the number of super_admin operators small, and rotate PATs that are no longer needed.
  • Every membership and role change is recorded in the audit log.

Next: Encryption.