Skip to main content
A permission is a fine-grained capability. A role is a named set of them. A membership gives one user one role in one organization. The split that makes this work: the catalogue lives in Go, because the set of things the system can do is a fact of the codebase. Which role holds which lives in the database, so an organization can define its own roles without waiting for a deploy.

The catalogue

permission
Rename the organization, change its logo, close onboarding, set an environment’s auth mode.
permission
Destroy the organization and everything under it.
permission
List members and pending invitations.
permission
Invite, revoke invitations, change roles, remove members.
permission
List roles and read the permission catalogue.
permission
Create, edit and delete roles. This one can grant any permission, including itself — whoever holds it can self-escalate, which is why admin does not get it by default.
permission
List API keys and publishable keys.
permission
Create and revoke API keys, set allowed origins, read and rotate an environment’s identity secret.
permission
Read contacts in the panel.
permission
Merge contacts. Split from read because a merge is destructive in the “no un-merge” sense.
Read it from the API instead of hard-coding it — that is what the role editor does:

The system roles

Seeded into every organization at creation. They are marked is_system. admin runs the operation but cannot delete the organization nor manage roles. member is read-only.
Role names are stored in English — owner, admin, member — because the API matches those literals: the last-owner guard and the seeding both compare on them. Translate for display only.

Custom roles

Custom roles are never is_system, so they stay renamable and deletable.
Both need roles:manage.
PUT replaces the permission set, it does not merge. The editor sends the full set every time — a merge would make removing a permission impossible.
An unknown permission is refused with 400 unknown_permission rather than silently dropped. Stored as-is it would sit in the database looking like access nobody ever grants.

Editing system roles

A system role’s permissions can be edited — that is how an organization tailors admin to itself. Its name cannot: 403 system_role. One rule is absolute: the owner role must keep roles:manage. Removing it answers 403 owner_locked. Without it an owner could edit themselves out of role management and leave the organization with nobody able to grant it back.

Deleting

A role that still has members is refused with 409 role_in_use rather than cascaded — those people would silently lose every permission. Move them first. System roles cannot be deleted at all.

How enforcement works

Permissions load with the session, at no extra round-trip, and every gated route checks them before the handler runs:
403
Because they are resolved per request from the membership, a permission change takes effect on that user’s very next request. Nobody has to sign out and back in. The panel hides what a role cannot do. That is courtesy for the person using it — the API is what actually refuses.