Groups are how IntelliAuth implements role-style access control. A user joins a group; the group carries the capabilities you've assigned to it; the user inherits those capabilities on every access token they're issued. (At the wire level the capabilities surface as scope strings in the token — see the scopes field on the JSON examples below.) Groups are flat by default (no group-of-groups); membership is the only nesting.
/api/v1/groups/* is scoped to groups:read and groups:write.
For richer relationship-based authorization (resource-owns-user, document-shared-with-team), see the Resources API.
List groups
GET /api/v1/groups
Authorization: Bearer <access-token>
Required scope: groups:read
Query parameters:
q — free-text search across name + description
has_user — filter to groups containing this user_id
cursor
limit
{
"data": [
{
"id": "grp_01HZX...",
"slug": "finance-admins",
"name": "Finance Admins",
"description": "Full admin over the finance vertical.",
"member_count": 7,
"scopes": ["payments:read", "payments:approve", "audit:read"],
"created_at": "2026-02-10T10:00:00Z"
}
],
"meta": { "next_cursor": null, "limit": 50 }
}
Create a group
POST /api/v1/groups
Content-Type: application/json
Required scope: groups:write
{
"slug": "finance-admins",
"name": "Finance Admins",
"description": "Full admin over the finance vertical.",
"scopes": ["payments:read", "payments:approve", "audit:read"]
}
Slugs are immutable. Pick carefully — renames mean delete + recreate.
scopes must all be on the tenant's registered scope list; the API rejects an unknown scope at create time.
Look up
GET /api/v1/groups/{group_id}
Required scope: groups:read
Returns the full group record plus a sample of members:
{
"data": {
"id": "grp_01HZX...",
"slug": "finance-admins",
"name": "Finance Admins",
"scopes": [...],
"members": [
{ "id": "usr_01HZX...", "email": "anita@cymmetri.com", "name": "Anita Singh" }
],
"member_count": 7,
"can_delete": true,
"audit_summary": { "last_change_at": "2026-05-15T10:00:00Z" }
}
}
The members array shows up to 50 entries inline; for full membership use the list members endpoint.
Update
PATCH /api/v1/groups/{group_id}
Content-Type: application/json
Required scope: groups:write
{
"name": "Finance Administrators",
"scopes": ["payments:read", "payments:approve", "audit:read", "users:read"]
}
slug is immutable. Other fields PATCH-merge.
Delete
DELETE /api/v1/groups/{group_id}
Required scope: groups:write
Hard delete. Users previously in the group lose the group's scopes from their next access token onwards. Existing tokens are not retroactively narrowed — they remain valid until expiry.
List members
GET /api/v1/groups/{group_id}/members
Required scope: groups:read
Query parameters:
cursor
limit
{
"data": [
{
"id": "usr_01HZX...",
"email": "anita@cymmetri.com",
"name": "Anita Singh",
"added_at": "2026-03-05T10:00:00Z",
"added_by": "usr_01HZA..."
}
],
"meta": { "next_cursor": "...", "limit": 50 }
}
Add a member
POST /api/v1/groups/{group_id}/members
Content-Type: application/json
Required scope: groups:write
{
"user_id": "usr_01HZX..."
}
Returns the updated member entry. Adding a user who is already a member is a no-op (200 OK, idempotent).
For bulk additions:
POST /api/v1/groups/{group_id}/members/bulk
Content-Type: application/json
{
"user_ids": ["usr_01HZX...", "usr_01HZY...", "usr_01HZZ..."]
}
Returns per-user results — succeeded, already-member, or error.
Remove a member
DELETE /api/v1/groups/{group_id}/members/{user_id}
Required scope: groups:write
Returns 204. The user's next access token will lack the group's scopes.
Bulk remove
POST /api/v1/groups/{group_id}/members/bulk-remove
Content-Type: application/json
{
"user_ids": [...]
}
Patterns
"Promote this user"
To make a user a Finance Admin:
await fetch(`/api/v1/groups/${financeAdminsId}/members`, {
method: 'POST',
headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ user_id: userId }),
})
The next access token they receive (next sign-in or next silent refresh) includes the group's scopes.
"Sync from an external source"
If your HR system is the source of truth for group membership, sync nightly:
Fetch external memberships.
For each group, compute the diff against current IntelliAuth membership.
Apply additions via
bulkand removals viabulk-remove.
The platform handles concurrent membership changes correctly (each operation is independently atomic); you do not need locking on your end.
Common errors
Error | When |
|---|---|
| The id is wrong or you can't see it |
| Another group already uses this slug |
| A scope in the create / update isn't registered in the tenant |
| The user_id in an add doesn't exist |
| (only on non-bulk add) the user is already in the group; status is 200 with this code instead of an error in bulk mode |