Admin - Groups
Groups collect users within one tenant so you can share apps, workflows and agents with all of them at once. Use these endpoints to list, create, rename, re-staff and delete groups in the tenants your MSP manages.
Who Can Call These Endpoints
The API key must belong to a user on your MSP (admin.hatz.ai) account whose role includes the Manage Groups permission. Among the default MSP roles, Primary Admin, Admin, Helpdesk, Tenant Manager and Compliance Manager include it; Billing Manager and Sales User do not. A custom role includes it only if you added it.
Creating a group (POST) also needs the Create Groups permission. The default roles above that have Manage Groups also have Create Groups; a custom role with Manage Groups but not Create Groups gets a 400 on create.
| Caller | Result |
|---|---|
| MSP user with Manage Groups | Can manage groups in your MSP's own company and in every tenant your MSP manages |
| MSP user without Manage Groups | 403 with "Insufficient permissions to manage groups" |
| Tenant (company) user, including a tenant admin | 403 with "Insufficient permissions" |
| Missing or invalid API key | 401 |
Groups in tenants that belong to another MSP are never returned or changed.
IDs
- Groups use HashIDs such as
group_X7VAK72dp5nX3Cd6JXwdPg6OxD. - Members are user HashIDs (
user_...). Get them from Get Tenant Users (GET /v1/admin/tenants/{tenant_id}/users) on the Admin - Tenant page. tenant_idis the tenant ID returned by List Tenants (GET /v1/admin/tenants), for exampletenant_abc123.- Workshop items are identified by their UUID and an
item_typeofapp,workfloworagent.
List Groups
List groups, newest first. With tenant_id, only that tenant's groups are returned. Without it, the list covers every tenant you manage, plus your MSP's own company.
Endpoint
GET /v1/admin/groups
Query Parameters
tenant_id(optional): Tenant ID to filter by (e.g., "tenant_abc123")limit(optional): Number of groups to return (1-100, default: 50)offset(optional): Number of groups to skip (default: 0)cursor(optional): Thenext_cursorfrom the previous pagesearch_term(optional): Case-insensitive substring match on the group namename(optional): Exact, case-sensitive group name
Pagination
Pass the next_cursor from one response as cursor on the next request, keeping the same filters and limit. Stop when next_cursor is null.
The cursor is the last group's created_at. If several groups share the same created_at and a page ends among them, the next page skips the rest of them. When you need every group, page with limit and offset instead; results are ordered by created_at and then id, so offsets are stable while the groups don't change.
next_cursor is returned whenever a page is full, so the final request can return an empty groups array.
Use cursor or offset, not both. If you send both, offset skips groups after the cursor is applied.
Example Query
curl 'https://ai.hatz.ai/v1/admin/groups?tenant_id=tenant_abc123&limit=2' \
-H "X-API-Key: $HATZ_API_KEY"
Response
{
"groups": [
{
"id": "group_9OYJl94zb6glNS0wPZxQG8XYQl",
"name": "Finance",
"description": null,
"tenant_id": "tenant_abc123",
"owner": {
"id": "user_gE4JlrAxlgKXEfvA3jZexy6Pxq",
"email": "admin@yourmsp.com",
"first_name": "Alex",
"last_name": "Admin"
},
"members": [
{
"id": "user_766Epo4MVqxZeTJA8gL3Zmy31e",
"email": "jordan@example.com",
"first_name": "Jordan",
"last_name": "Lee"
}
],
"workshop_items": [],
"created_at": "2026-11-05T20:02:43.159868+00:00",
"updated_at": "2026-11-05T20:02:43.159868+00:00"
},
{
"id": "group_Xx9Xlk30byXXnSmrlZjBOmdY93",
"name": "Support",
"description": null,
"tenant_id": "tenant_abc123",
"owner": {
"id": "user_gE4JlrAxlgKXEfvA3jZexy6Pxq",
"email": "admin@yourmsp.com",
"first_name": "Alex",
"last_name": "Admin"
},
"members": [],
"workshop_items": [],
"created_at": "2026-11-05T20:02:42.936913+00:00",
"updated_at": "2026-11-05T20:02:42.936913+00:00"
}
],
"next_cursor": "2026-11-05T20:02:42.936913+00:00"
}
Next page:
curl 'https://ai.hatz.ai/v1/admin/groups?tenant_id=tenant_abc123&limit=2&cursor=2026-11-05T20%3A02%3A42.936913%2B00%3A00' \
-H "X-API-Key: $HATZ_API_KEY"
URL-encode the cursor: it contains : and +.
A tenant_id that is not one of your tenants returns an empty list.
Get Group
Get one group with its owner, members and workshop items.
Endpoint
GET /v1/admin/groups/{group_id}
Path Parameters
group_id(required): Group ID (e.g., "group_abc123")
Example Query
curl 'https://ai.hatz.ai/v1/admin/groups/group_X7VAK72dp5nX3Cd6JXwdPg6OxD' \
-H "X-API-Key: $HATZ_API_KEY"
Response
{
"id": "group_X7VAK72dp5nX3Cd6JXwdPg6OxD",
"name": "Sales",
"description": "Sales team",
"tenant_id": "tenant_abc123",
"owner": {
"id": "user_gE4JlrAxlgKXEfvA3jZexy6Pxq",
"email": "admin@yourmsp.com",
"first_name": "Alex",
"last_name": "Admin"
},
"members": [
{
"id": "user_n55abP89ZowO6iKAx7vqWjrqnZ",
"email": "sam@example.com",
"first_name": "Sam",
"last_name": "Rivera"
},
{
"id": "user_ExxypMkWV0PLDUAyLpdJlOEJ69",
"email": "taylor@example.com",
"first_name": "Taylor",
"last_name": "Kim"
}
],
"workshop_items": [
{
"id": "17a1718a-4be8-42db-bbc2-36f345e53ef2",
"item_type": "app"
},
{
"id": "5143da6d-c5de-4c2f-a0b6-3df4d0e542f0",
"item_type": "agent"
}
],
"created_at": "2026-11-05T20:02:42.715415+00:00",
"updated_at": "2026-11-05T20:02:42.715415+00:00"
}
Workflows are returned with item_type app. You can send them back as app or workflow.
Create Group
Create a group in a tenant, optionally with members and workshop items. The user who owns the API key becomes the group's owner.
Endpoint
POST /v1/admin/groups
Request Body
{
"name": "string", // Required: 3-72 characters; letters, numbers and spaces only
"description": "string", // Optional
"tenant_id": "string", // Optional: Tenant ID (e.g., "tenant_abc123"). Defaults to your MSP's own company
"members": ["string"], // Optional: User IDs (e.g., "user_abc123"), all from the same tenant
"workshop_items": [ // Optional: Apps, workflows or agents to share with the group
{
"id": "string", // Workshop item UUID
"item_type": "string" // "app", "workflow" or "agent"
}
]
}
Always pass tenant_id when you create a group for a customer. Without it, the group is created in your MSP's own company, not in a tenant.
Group names don't have to be unique: creating a second group with an existing name succeeds and returns a new id.
Example Query
curl 'https://ai.hatz.ai/v1/admin/groups' \
-H "X-API-Key: $HATZ_API_KEY" \
-H 'Content-Type: application/json' \
-d '{
"name": "Sales",
"description": "Sales team",
"tenant_id": "tenant_abc123",
"members": ["user_n55abP89ZowO6iKAx7vqWjrqnZ", "user_ExxypMkWV0PLDUAyLpdJlOEJ69"]
}'
Response
201 Created, with the new group in the same shape as Get Group.
Status codes
| Status | Meaning |
|---|---|
201 |
The group was created. |
400 |
The group could not be created: for example, the caller lacks Create Groups, a member is not in the tenant, a workshop item was not found or can't be shared with the tenant, or tenant_id is not a tenant you manage. error.message describes the problem. |
422 |
The body is invalid: a name outside 3-72 characters or with other characters, a tenant_id that isn't tenant_ followed by letters and digits, or a member ID not starting with user_. |
Update Group
Change a group's name, description, members or workshop items. Send only the fields you want to change; fields you leave out keep their current values.
membersreplaces the whole list. The group ends up with exactly the users you send, and anyone not in the list is removed. Send"members": []to remove everyone. To add or remove one person, read the group with Get Group, take theidof each object in itsmembers, change that list of IDs, and send all of it back asmembers. Updates are not locked or merged, so don't update the same group from two places at once.workshop_itemsworks the same way.If the tenant syncs groups from an identity provider, see Groups Managed by SCIM before changing members.
Endpoint
PATCH /v1/admin/groups/{group_id}
Path Parameters
group_id(required): Group ID (e.g., "group_abc123")
Request Body
{
"name": "string", // Optional: 3-72 characters; letters, numbers and spaces only
"description": "string", // Optional
"members": ["string"], // Optional: Replaces all members. User IDs from the group's tenant
"workshop_items": [ // Optional: Replaces all workshop items
{
"id": "string",
"item_type": "string" // "app", "workflow" or "agent"
}
]
}
Example Query
Rename a group without touching its members:
curl -X PATCH 'https://ai.hatz.ai/v1/admin/groups/group_X7VAK72dp5nX3Cd6JXwdPg6OxD' \
-H "X-API-Key: $HATZ_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"name": "Sales EMEA"}'
Set the group's members to exactly one user:
curl -X PATCH 'https://ai.hatz.ai/v1/admin/groups/group_X7VAK72dp5nX3Cd6JXwdPg6OxD' \
-H "X-API-Key: $HATZ_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"members": ["user_766Epo4MVqxZeTJA8gL3Zmy31e"]}'
Response
200 OK, with the updated group in the same shape as Get Group.
Status codes
| Status | Meaning |
|---|---|
200 |
The group was updated. An empty body ({}) changes nothing and returns the group. |
400 |
The group could not be updated: for example, a member is not in the group's tenant, or a workshop item was not found. error.message describes the problem. |
422 |
The body is invalid, for example a name with characters other than letters, numbers and spaces, or an unknown item_type. |
Delete Group
Delete a group. Its memberships and workshop item shares are removed with it; the users, apps, workflows and agents themselves are not affected.
Endpoint
DELETE /v1/admin/groups/{group_id}
Path Parameters
group_id(required): Group ID (e.g., "group_abc123")
Example Query
curl -X DELETE 'https://ai.hatz.ai/v1/admin/groups/group_X7VAK72dp5nX3Cd6JXwdPg6OxD' \
-H "X-API-Key: $HATZ_API_KEY"
Response
{
"message": "Group deleted successfully",
"group_id": "group_X7VAK72dp5nX3Cd6JXwdPg6OxD"
}
Errors
Errors use this shape:
{
"error": {
"type": "HTTPException",
"message": "Insufficient permissions to manage groups",
"details": {},
"path": "/v1/admin/groups"
}
}
| Status | Meaning |
|---|---|
400 |
A malformed group_id or tenant_id, or a create or update the platform refused. error.message describes the problem; its wording may change, so don't parse it. |
401 |
Missing or invalid API key. |
403 |
The API key's user is not an MSP user, or lacks the Manage Groups permission. |
422 |
Invalid query parameters or body. error.details.errors lists each field. |
A group that doesn't exist, has been deleted, or is in a tenant your MSP doesn't manage is never returned or changed. Don't rely on a specific status code for that case on GET, PATCH or DELETE of a single group.
Groups Managed by SCIM
If a tenant syncs users and groups from an identity provider (for example Microsoft Entra ID or Okta) through SCIM, the groups it creates appear here like any other group, with nothing marking them as synced. If you rename one or change its members through this API, the next sync from the identity provider can overwrite your change. Manage SCIM-synced groups in the identity provider instead.
Groups created by SCIM have no owner: owner.id is an empty string and the other owner fields are null.