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
Section titled “List groups”GET /api/v1/groupsAuthorization: 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
Section titled “Create a group”POST /api/v1/groupsContent-Type: application/jsonRequired 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
Section titled “Look up”GET /api/v1/groups/{group_id}Required scope: groups:readReturns 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
Section titled “Update”PATCH /api/v1/groups/{group_id}Content-Type: application/jsonRequired scope: groups:write
{ "name": "Finance Administrators", "scopes": ["payments:read", "payments:approve", "audit:read", "users:read"]}slug is immutable. Other fields PATCH-merge.
Delete
Section titled “Delete”DELETE /api/v1/groups/{group_id}Required scope: groups:writeHard 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
Section titled “List members”GET /api/v1/groups/{group_id}/membersRequired 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
Section titled “Add a member”POST /api/v1/groups/{group_id}/membersContent-Type: application/jsonRequired 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/bulkContent-Type: application/json
{ "user_ids": ["usr_01HZX...", "usr_01HZY...", "usr_01HZZ..."]}Returns per-user results — succeeded, already-member, or error.
Remove a member
Section titled “Remove a member”DELETE /api/v1/groups/{group_id}/members/{user_id}Required scope: groups:writeReturns 204. The user's next access token will lack the group's scopes.
Bulk remove
Section titled “Bulk remove”POST /api/v1/groups/{group_id}/members/bulk-removeContent-Type: application/json
{ "user_ids": [...]}Patterns
Section titled “Patterns”"Promote this user"
Section titled “"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"
Section titled “"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
Section titled “Common errors”| Error | When |
|---|---|
group_not_found | The id is wrong or you can't see it |
slug_unavailable | Another group already uses this slug |
scope_unknown | A scope in the create / update isn't registered in the tenant |
user_not_found | The user_id in an add doesn't exist |
user_already_member | (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 |