I IntelliAuth Docs

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

http
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
json
{
  "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

http
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

http
GET /api/v1/groups/{group_id}
Required scope: groups:read

Returns the full group record plus a sample of members:

json
{
  "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

http
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

http
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

http
GET /api/v1/groups/{group_id}/members
Required scope: groups:read

Query parameters:
  cursor
  limit
json
{
  "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

http
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:

http
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

http
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

http
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:

ts
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:

  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.

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

See also