FerrisKeyDocs

Groups

Groups add structure inside an organization. They form a tree, and membership is recursive: a member of a group is effectively a member of every group above it, and inherits the roles mapped to each of them.

That is what makes “everyone in Engineering can read the build logs, and the Platform sub-team can also deploy” expressible without assigning roles user by user.

The group tree

A group belongs to one organization and points at a parent through parent_group_id. A null or missing parent means the group sits at the top level.

Acme Corp (organization)
└── Engineering              roles: build:read
    ├── Platform             roles: deploy
    └── Frontend

A user added to Platform is a member of Platform and of Engineering, and holds both deploy and build:read. A user added to Engineering holds only build:read.

Names are unique among siblings: two groups under the same parent, or two top-level groups, cannot share a name. Deleting a group deletes its sub-groups, with their memberships, role mappings and attributes.

Group fields

FieldTypeDescription
idUUID v7Identifier
organization_idUUIDThe organization the group belongs to
parent_group_idUUID or nullParent group. null for a top-level group
nameStringGroup name, required, at most 255 characters
descriptionStringFree-text description
created_atDateTime (UTC)Creation timestamp
updated_atDateTime (UTC)Last-modified timestamp

Updating a group can move it: sending a parent_group_id reparents it under that group. The new parent must belong to the same organization, and cannot be the group itself or one of its descendants (400). Leaving parent_group_id out, or sending null, keeps the current parent. The API has no way to move a group back to the top level.

Endpoints

All of them are scoped to a realm and an organization: the paths start with /realms/{realm_name}/organizations/{organization_id}.

Groups

MethodEndpointDescription
GET/realms/{realm_name}/organizations/{organization_id}/groupsList the organization’s groups
POST/realms/{realm_name}/organizations/{organization_id}/groupsCreate a group. Answers 201
GET/realms/{realm_name}/organizations/{organization_id}/groups/{group_id}Read one group
PUT/realms/{realm_name}/organizations/{organization_id}/groups/{group_id}Update or move a group
DELETE/realms/{realm_name}/organizations/{organization_id}/groups/{group_id}Delete a group and its sub-groups. Answers 204

The list is a flat, paginated page, not a tree. The contract is described in API listings. Each item carries the group’s own fields and a child_count, the number of direct sub-groups:

{
  "data": [
    {
      "id": "0199a4f2-3c1e-7b8a-9f00-1a2b3c4d5e6f",
      "organization_id": "01936b2e-1234-7000-abcd-000000000001",
      "parent_group_id": "0199a4f2-0000-7b8a-9f00-1a2b3c4d5e00",
      "name": "Platform",
      "description": "Runs the deploy pipeline",
      "created_at": "2026-07-10T09:00:00Z",
      "updated_at": "2026-07-10T09:00:00Z",
      "child_count": 0
    }
  ],
  "metadata": {
    "page": 1,
    "limit": 20,
    "total": 1,
    "total_pages": 1,
    "first_page": 1,
    "last_page": 1,
    "next_page": null,
    "prev_page": null
  }
}

To walk the tree, ask for the top level with is_root=true, then for the children of each group with parent_group_id. The other filters are:

ParameterMatches
searchCase-insensitive substring of the name or the description
name, descriptionCase-insensitive substring of that field
parent_group_idThe direct children of that group
is_roottrue keeps top-level groups, false keeps nested ones. is_root=true cannot be combined with parent_group_id
idsA comma-separated list of at most 100 group ids
created_from, created_toRFC 3339 date-times, inclusive and exclusive

order_by accepts name, created_at (the default) and updated_at.

Members

MethodEndpointDescription
GET/realms/{realm_name}/organizations/{organization_id}/groups/{group_id}/membersList the group’s members, paginated
POST/realms/{realm_name}/organizations/{organization_id}/groups/{group_id}/membersAdd a user to the group
DELETE/realms/{realm_name}/organizations/{organization_id}/groups/{group_id}/members/{user_id}Remove a user from the group

Adding a member takes { "user_id": "…" }, answers 201 with the membership, and refuses a user from another realm or a user already in the group. Removing a user who is not in the group answers 404.

The member list is enriched, so a client can render it without walking the user directory. Each entry carries id, group_id, user_id, username, email, firstname, lastname, enabled and created_at, the date the user joined. It filters on search (username or email), username, email, enabled, created_from and created_to, and sorts on username, email or created_at (the default).

Roles

MethodEndpointDescription
GET/realms/{realm_name}/organizations/{organization_id}/groups/{group_id}/rolesList the roles mapped to the group
POST/realms/{realm_name}/organizations/{organization_id}/groups/{group_id}/rolesMap a role to the group
DELETE/realms/{realm_name}/organizations/{organization_id}/groups/{group_id}/roles/{role_id}Unmap a role

Mapping takes { "role_id": "…" } and answers 204. The role must belong to the same realm. The list is not paginated: it returns the full role objects in a plain array. Every member of the group, and of every group below it, inherits the roles mapped here.

Attributes

MethodEndpointDescription
GET/realms/{realm_name}/organizations/{organization_id}/groups/{group_id}/attributesList the group’s attributes
PUT/realms/{realm_name}/organizations/{organization_id}/groups/{group_id}/attributes/{key}Upsert an attribute
DELETE/realms/{realm_name}/organizations/{organization_id}/groups/{group_id}/attributes/{key}Delete an attribute. Answers 204

Same key-value shape and same upsert behavior as organization attributes, except that the list is a plain array of attributes rather than an object with a data key.

Permissions

Groups use the permissions of the organization module. Reading groups, their members, roles and attributes needs view_organizations, manage_organizations or manage_realm. Creating, updating and deleting groups, and changing their members, roles and attributes, needs manage_organizations or manage_realm. manage_users and view_users are not enough. There are no group-specific permissions.