Groups & Permissions
Permissions are never granted to a person directly — only to a group. A person's real access is the union of every grant held by every group they belong to. This page is the task-oriented walkthrough: creating a group, granting a permission, and reading the result back in the Authorization Matrix. For the full permission-string catalog and the resolver's precedence rules, see Security → Permissions Reference.
Creating a group
From the admin navigation, open Groups, then New Group. A group needs only a name and an optional description.
The same action is available over the API:
/api/groups🔒 authBody: {name: string, description?: string}. Requires org.admin. Returns the created group.
A group can also be LDAP-mapped instead of local, syncing its membership from a directory server rather than the membership endpoints below — see Security → LDAP Configuration for the current status of that path.
Granting a permission
Open a group's detail page and add a permission, or call the API directly:
/api/groups/:id/permissions🔒 authBody: {permission_type: string, target_id?: string}. Requires org.admin.
permission_type is a <resource>.<action> string — dashboard.view, project.edit, and so on. The full catalog, with what each string unlocks, lives on the
Permissions Reference page.
target_id is what decides org-wide vs. per-resource:
- Omit it (or send
null) — the grant is organization-wide. Every group member gets that permission on every matching resource, present and future. - Set it to a specific resource id (e.g. one
dashboard_id) — the grant applies to that resource only. A group can hold the samepermission_typescoped to several different resources, one row each.
The resolver checks the specific target_id first and falls back to an
org-wide (target_id IS NULL) grant, so a narrower and a broader grant for
the same permission can coexist without conflicting.
Revoke a grant the same way, in reverse:
/api/groups/:id/permissions/:permission_id🔒 authRemoves one specific permission grant from a group.
Membership
/api/groups/:id/members🔒 authBody: {user_id: string}. Adds a user to the group.
/api/groups/:id/members/:user_id🔒 authRemoves a user from the group. They lose any permission granted only through it.
A user's total access is always the union across every group they're a member of — there's no "deny" grant that subtracts access, so adding someone to a second group can only add capability, never remove it.
The Authorization Matrix
Open the Authorization Matrix page from the admin navigation for a single grid view of every group against every permission type it holds — rows are groups, columns are permission types, and a check in a cell means that group holds the permission, either organization-wide or on at least one specific target.
A few things worth knowing before you use it:
- Reading it is open to any org member; only an org admin sees the controls to change anything.
- Only the org-wide toggle is editable from the matrix itself. Clicking
a cell grants or revokes the
target_id IS NULLrow for that group/permission pair. Per-resource grants (scoped to one specific dashboard, project, or dataset) are added from the group's own detail page, where you pick the target explicitly. - New permission-type columns appear automatically. To introduce a permission string the matrix hasn't seen before, grant it once from a group's detail page — it shows up as a column on the matrix's next load.
- Columns are colour-coded by permission family (
org.*,project.*,dashboard.*,dataset.*,feature.*) purely as a reading aid — the colour carries no access-control meaning of its own.
Resolution order, in brief
When a request needs dashboard.edit on dashboard 7, the resolver checks,
in order: superadmin → admin-role shortcut → the requesting user's
implicit seat-level grant → a group grant scoped to target_id='7' → a
group grant with target_id IS NULL → otherwise, 403. The full walkthrough,
including the exact 403 body shape and how legacy roles migrate onto this
model, lives on Permissions Reference.