Manage groups
Create groups, add and remove members, attach policies to a group, and read a group's membership history.
On this page
A group is a set of members of your organization. Policies attached to a group, and permission sets assigned to it, apply to every member of the group. Use groups to manage access for teams instead of attaching policies to people one by one.
Your organization already has some groups: the four membership groups behind the member roles, and a default group. See Groups for what they are and which of them you can change.
Permissions you need
| Step | Endpoint | Action |
|---|---|---|
| Create, rename or delete a group | POST /v1/groups, PATCH and DELETE /v1/groups/<group-id> | iam:group:write |
| Add or remove members | POST /v1/groups/<group-id>/members, DELETE /v1/groups/<group-id>/members/<user-id> | iam:group:manage_members |
| Attach or detach policies | POST /v1/policies/<policy-id>/attachments, DELETE .../attachments/<attachment-id> | iam:policy:write |
| List groups, read a group, its members and its membership history | GET /v1/groups, GET /v1/groups/<group-id>, .../members, .../audit-logs | None: any member of the organization |
Owners hold every action. Admins hold these through IAM Admin Access. To add a member to a group you must also hold every action the group's policies allow (see below), and to attach a policy, every action that policy allows.
Create a group
curl -X POST https://api.hexelstudio.com/v1/groups \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "platform-team",
"display_name": "Platform team",
"description": "Engineers who run our deployments"
}'const res = await fetch("https://api.hexelstudio.com/v1/groups", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ACCESS_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "platform-team",
display_name: "Platform team",
description: "Engineers who run our deployments",
}),
});
if (!res.ok) {
throw new Error(`Create group failed: ${res.status} ${await res.text()}`);
}
const group = await res.json();
console.log(group.id);import os
import requests
resp = requests.post(
"https://api.hexelstudio.com/v1/groups",
headers={"Authorization": f"Bearer {os.environ['ACCESS_TOKEN']}"},
json={
"name": "platform-team",
"display_name": "Platform team",
"description": "Engineers who run our deployments",
},
timeout=10,
)
resp.raise_for_status()
group = resp.json()
print(group["id"])A short name, unique in your organization. default and the membership group names are taken.
The name shown in the console.
Optional description.
The response is 201 with the group. The group belongs to the organization of your token.
{
"id": "grp_7d8e9f0a...",
"organization_id": "org_9a8b7c...",
"name": "platform-team",
"display_name": "Platform team",
"description": "Engineers who run our deployments",
"group_type": "TEAM",
"created_at": "2026-10-20T10:00:00.000000Z",
"updated_at": "2026-10-20T10:00:00.000000Z"
}A group name already used in your organization fails with 500 and the database error.
Add members
POST /v1/groups/<group-id>/members adds one member, by user ID. Take the ID from the user_id field of the organization's members list.
curl -X POST https://api.hexelstudio.com/v1/groups/<group-id>/members \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"user_id": "<user-id>"}'The user ID of a member of your organization, or the ID of one of its service users (su_...).
The response is 201:
{
"id": "gm_3c4d5e6f...",
"group_id": "grp_7d8e9f0a...",
"user_id": "<user-id>",
"added_by_user_id": "<your-user-id>",
"added_at": "2026-10-20T10:05:00.000000Z"
}The new member gets every policy attached to the group, and every permission set assigned to it, at once on their existing tokens. Because of that, you can add a member only if you hold every action those policies allow.
| Status | Body | Cause |
|---|---|---|
400 | {"error": "user_id is required"} | user_id is missing. |
403 | {"error": "forbidden"} | You don't hold iam:group:manage_members. |
403 | {"error": "forbidden", "reason": "cannot grant actions the caller does not hold", "missing_actions": [...]} | The group grants actions you don't hold. |
403 | {"error": "membership groups are managed through organization membership"} | The group is a membership group. Change the member's role instead. |
404 | {"error": "group not found"} | No such group in your organization. |
422 | {"error": "not_org_member", "message": "..."} | user_id is not a member or service user of your organization. |
500 | {"error": "..."} | The user is already in the group. |
503 | {"error": "membership lookup failed"} | Membership couldn't be checked. Try again. |
List members
curl https://api.hexelstudio.com/v1/groups/<group-id>/members \
-H "Authorization: Bearer $ACCESS_TOKEN"The response is an array of the membership objects shown above.
Remove a member
curl -X DELETE https://api.hexelstudio.com/v1/groups/<group-id>/members/<user-id> \
-H "Authorization: Bearer $ACCESS_TOKEN"The response is 204, also when the user was not in the group. The member loses the group's policies at once. Removing a member needs only iam:group:manage_members: you don't need to hold the group's policies.
Attach policies to a group
Attach a policy with target type GROUP:
curl -X POST https://api.hexelstudio.com/v1/policies/<policy-id>/attachments \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"target_type": "GROUP", "target_id": "<group-id>"}'The policy applies organization-wide to every member of the group. To grant a group access in one workspace or environment only, assign it a permission set with a scope and "principal_type": "GROUP". Both are described in Write and attach policies, with detaching and the errors.
You can attach policies to a membership group too, to give every member of that role more access. See What a member gets by default.
Read and change a group
GET /v1/groupslists every group of your organization, by name, including the membership groups anddefault.GET /v1/groups/<group-id>returns one group. A group of another organization returns404with{"error": "group not found"}.PATCH /v1/groups/<group-id>changesname,display_nameordescription. The response is200with the group.
curl -X PATCH https://api.hexelstudio.com/v1/groups/<group-id> \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"display_name": "Platform engineering"}'Membership groups can't be changed or deleted: those calls return 403 with {"error": "membership groups are managed through organization membership"}.
Groups don't nest. A group has a parent_group_id field, but members of a group don't get the policies of its parent.
Delete a group
curl -X DELETE https://api.hexelstudio.com/v1/groups/<group-id> \
-H "Authorization: Bearer $ACCESS_TOKEN"The response is 204. Hexel Studio removes the group's members and the policies attached to it, so its members lose that access at once. Permission set assignments made to the group are not removed: delete them with DELETE /v1/permission-sets/<permission-set-id>/assignments/<assignment-id>. They grant nothing once the group is gone.
Group audit logs
GET /v1/groups/<group-id>/audit-logs returns the group's membership history, newest first.
curl https://api.hexelstudio.com/v1/groups/<group-id>/audit-logs \
-H "Authorization: Bearer $ACCESS_TOKEN"[
{
"id": "gal_8f9a0b1c...",
"group_id": "grp_7d8e9f0a...",
"action": "MEMBER_REMOVED",
"target_user_id": "<user-id>",
"created_at": "2026-10-21T08:00:00.000000Z"
},
{
"id": "gal_2a3b4c5d...",
"group_id": "grp_7d8e9f0a...",
"action": "MEMBER_ADDED",
"actor_user_id": "<your-user-id>",
"target_user_id": "<user-id>",
"created_at": "2026-10-20T10:05:00.000000Z"
}
]MEMBER_ADDED or MEMBER_REMOVED.
Who added the member. Not recorded for removals.
The member added or removed.
This log holds membership changes made with the groups API only. Creating, renaming and deleting groups, attaching policies to them, and changes of member roles are recorded in the audit logs.