Skip to content

Groups API

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.

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 }
}
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.

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.

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 /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.

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 }
}
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.

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.

POST /api/v1/groups/{group_id}/members/bulk-remove
Content-Type: application/json
{
"user_ids": [...]
}

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.

If your HR system is the source of truth for group membership, sync nightly:

  1. Fetch external memberships.
  2. For each group, compute the diff against current IntelliAuth membership.
  3. Apply additions via bulk and removals via bulk-remove.

The platform handles concurrent membership changes correctly (each operation is independently atomic); you do not need locking on your end.

ErrorWhen
group_not_foundThe id is wrong or you can't see it
slug_unavailableAnother group already uses this slug
scope_unknownA scope in the create / update isn't registered in the tenant
user_not_foundThe 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