# Roles, policies and groups — tool reference

Generated from the OpenAPI document (`https://maison-labs.com/api/v1/openapi.json`). Field types are JSON Schema types; GET/DELETE inputs travel as path or query, other methods as a JSON body.

## `add_client_policy_statement` — Add a statement to a hotel policy

Set one rule (a feature and a level — a rule carries no client; where the policy applies is each role's Applies to) in a hotel-owned policy. A policy holds at most one rule per feature (and at most one every-feature rule): its level is exactly one of READ, WRITE, EXECUTE or DENY, and a higher level includes the lower ones (EXECUTE includes WRITE and READ). Adding a rule for a feature that already has one replaces the feature's current rule; the same level again changes nothing and answers the existing rule's id.

- REST: `POST https://maison-labs.com/api/v1/client/{clientId}/policy/{policyId}/statement`
- MCP tool / A2A skill: `add_client_policy_statement`
- Needs: policies WRITE (client)

Inputs:

| Field | Where | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `clientId` | path | string | yes | Client (hotel) id; format uuid |
| `policyId` | path | string | yes | Policy id; format uuid |
| `featureId` | body | string \| null | no | Feature id; null for ALL wildcard; default null |
| `permission` | body | enum: READ \| WRITE \| EXECUTE \| DENY | yes |  |

## `add_client_role_rule` — Add a rule to a hotel role

Set one rule (a feature and a level) in a hotel role's own policy; the role applies it at this hotel. A policy holds at most one rule per feature (and at most one every-feature rule): its level is exactly one of READ, WRITE, EXECUTE or DENY, and a higher level includes the lower ones (EXECUTE includes WRITE and READ). Adding a rule for a feature that already has one replaces the feature's current rule; the same level again changes nothing and answers the existing rule's id. Nobody grants more than they hold, and a Maison-only feature only Maison staff may grant (403 names the feature). Returns the rule id for `remove_client_role_rule`. Use `attach_client_role_policy` to apply a shared policy instead.

- REST: `POST https://maison-labs.com/api/v1/client/{clientId}/role/{roleId}/rule`
- MCP tool / A2A skill: `add_client_role_rule`
- Needs: maison-roles WRITE (client), policies WRITE (client)

Inputs:

| Field | Where | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `clientId` | path | string | yes | Client (hotel) id; format uuid |
| `roleId` | path | string | yes | Role id; format uuid |
| `featureId` | body | string | yes | Feature id (from list_client_features_catalogue); format uuid |
| `permission` | body | enum: READ \| WRITE \| EXECUTE \| DENY | yes |  |

## `add_role_policy` — Add a policy statement to a role

Set one rule (a feature and a level — a rule has no client; where it applies is the role's Applies to) in the role's own policy. A policy holds at most one rule per feature (and at most one every-feature rule): its level is exactly one of READ, WRITE, EXECUTE or DENY, and a higher level includes the lower ones (EXECUTE includes WRITE and READ). Adding a rule for a feature that already has one replaces the feature's current rule; the same level again changes nothing and answers the existing rule's id. Use `remove_role_policy` to remove it later, `attach_role_policy` to attach a shared policy.

- REST: `POST https://maison-labs.com/api/v1/role/{roleId}/policy`
- MCP tool / A2A skill: `add_role_policy`
- Needs: maison-roles WRITE (global), policies WRITE (global)

Inputs:

| Field | Where | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `roleId` | path | string | yes | Role id; format uuid |
| `featureId` | body | string | no | Feature id; omit for an ALL-features wildcard; format uuid |
| `permission` | body | enum: READ \| WRITE \| EXECUTE \| DENY | yes |  |

## `assign_role` — Assign a role to a user

Give a user this role at one client (`clientId`) or at all clients (null). Giving a role is a users action: the users grant at that client, or globally for all clients; a Maison-only role also needs the global roles grant. The role must be available there and not retired. Idempotent — revives a soft-deleted link when one already exists.

- REST: `POST https://maison-labs.com/api/v1/role/{roleId}/user`
- MCP tool / A2A skill: `assign_role`
- Needs: maison-users WRITE (client)

Inputs:

| Field | Where | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `roleId` | path | string | yes | Role id; format uuid |
| `userId` | body | string | yes | User id to assign the role to; format uuid |
| `clientId` | body | string \| null | no | Client scope for the assignment; null for a global grant; default null |

## `assign_role_to_group` — Assign a role to a group

Give a group this role at one client (`clientId`) or at all clients (null) so its members inherit it. Giving a role is a users action (the users grant at that client, or globally); a group available to one client receives roles only there. Idempotent.

- REST: `POST https://maison-labs.com/api/v1/role/{roleId}/group`
- MCP tool / A2A skill: `assign_role_to_group`
- Needs: maison-users WRITE (client)

Inputs:

| Field | Where | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `roleId` | path | string | yes | Role id; format uuid |
| `groupId` | body | string | yes | Group id to assign the role to; format uuid |
| `clientId` | body | string \| null | no | Client scope for the assignment; null for a global grant; default null |

## `attach_client_role_policy` — Attach a policy to a hotel role

Attach an existing policy to a hotel role; the role applies it at this hotel. The policy must be available here (to all clients or to this hotel). Needs the roles grant at the hotel.

- REST: `POST https://maison-labs.com/api/v1/client/{clientId}/role/{roleId}/policy`
- MCP tool / A2A skill: `attach_client_role_policy`
- Needs: maison-roles WRITE (client)

Inputs:

| Field | Where | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `clientId` | path | string | yes | Client (hotel) id; format uuid |
| `roleId` | path | string | yes | Role id; format uuid |
| `policyId` | body | string | yes | Policy id to attach; format uuid |

## `attach_role_policy` — Attach a policy to a role

Attach an existing policy to a role and say where the role applies it (`appliesTo`: null = all clients, or client ids). A policy is never applied to a client directly — the role applies it. The policy must be available where it applies (a client's policy only at that client); a role available to one client applies its policies there only (null). Idempotent for rows already applied; to replace where it applies use `set_role_policy_applies_to`. Needs the roles grant where the role is available.

- REST: `POST https://maison-labs.com/api/v1/role/{roleId}/policy/{policyId}/attach`
- MCP tool / A2A skill: `attach_role_policy`
- Needs: maison-roles WRITE (any)

Inputs:

| Field | Where | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `roleId` | path | string | yes | Role id; format uuid |
| `policyId` | path | string | yes | Policy id; format uuid |
| `appliesTo` | body | array of string \| null | yes | Where the role applies the policy: null = all clients, or the client ids. A role available to one client applies its policies there only (null). |

## `create_client_group` — Create a hotel group

Create a new access group owned by a hotel and optionally seed it with members and a role. Returns the new group id.

- REST: `POST https://maison-labs.com/api/v1/client/{clientId}/group`
- MCP tool / A2A skill: `create_client_group`
- Needs: groups WRITE (client)
- Composite of: `update_client_group_members`, `link_client_group_role`.

Inputs:

| Field | Where | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `clientId` | path | string | yes | Client (hotel) id; format uuid |
| `name` | body | string | yes |  |
| `description` | body | string | no |  |
| `memberIds` | body | array of string | no | User ids to add as initial members |
| `roleId` | body | string | no | Role to link to the group at this hotel; format uuid |

## `create_client_policy` — Create a hotel policy

Create a policy available to one hotel and optionally seed it with rules (each a feature and a level). Returns the new policy id. Roles apply it at this hotel (`attach_client_role_policy`).

- REST: `POST https://maison-labs.com/api/v1/client/{clientId}/policy`
- MCP tool / A2A skill: `create_client_policy`
- Needs: policies WRITE (client)
- Composite of: `add_client_policy_statement`.

Inputs:

| Field | Where | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `clientId` | path | string | yes | Client (hotel) id; format uuid |
| `name` | body | string | yes |  |
| `description` | body | string | no |  |
| `statements` | body | array of object | no | Seed rules: a feature and a level each — a rule carries no client |

## `create_client_role` — Create a hotel role

Create a new role owned by a hotel and optionally seed it with rules (a feature and a level each; seeding also needs the policies grant at the hotel, checked before the role exists). Returns the new role id. Add or remove rules later with `add_client_role_rule` / `remove_client_role_rule`.

- REST: `POST https://maison-labs.com/api/v1/client/{clientId}/role`
- MCP tool / A2A skill: `create_client_role`
- Needs: maison-roles WRITE (client)
- Composite of: `add_client_role_rule`.

Inputs:

| Field | Where | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `clientId` | path | string | yes | Client (hotel) id; format uuid |
| `name` | body | string | yes |  |
| `description` | body | string | no |  |
| `statements` | body | array of object | no |  |

## `create_group` — Create a group

Create a user group available to all clients (needs the global groups grant) — Maison only with `visibilityMaisonOnly` — and optionally seed it with members (`update_group_members`) and role assignments (`update_group_roles`, which also need the users grant where each role is given). Returns the new group id. Use `create_client_group` for a group available to one hotel.

- REST: `POST https://maison-labs.com/api/v1/group`
- MCP tool / A2A skill: `create_group`
- Needs: groups WRITE (global)
- Composite of: `update_group_members`, `update_group_roles`.

Inputs:

| Field | Where | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `name` | body | string | yes |  |
| `description` | body | string | no |  |
| `visibilityMaisonOnly` | body | boolean | no | The group is Maison only (seen only with the global groups read grant) |
| `memberIds` | body | array of string | no | User ids to add as initial members |
| `roles` | body | array of object | no |  |

## `create_policy` — Create a Maison policy

Create a policy available to all clients (`visibilityMaisonOnly` makes it Maison only), with no rules yet: fill them with `set_policy_rules`, then apply it with `attach_role_policy`. Needs the global policies grant. Returns the new policy id. Use `create_client_policy` for a policy of one hotel.

- REST: `POST https://maison-labs.com/api/v1/policy`
- MCP tool / A2A skill: `create_policy`
- Needs: policies WRITE (global)

Inputs:

| Field | Where | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `name` | body | string | yes |  |
| `description` | body | string | no |  |
| `visibilityMaisonOnly` | body | boolean | no | Only Maison staff see and use it |

## `create_role` — Create a role

Create a role available to one client (`clientId`) or to all clients (`clientId` null; `visibilityMaisonOnly` makes it Maison only), optionally seeded with rules on its own policy. Returns the new role id. A role available to a client needs the roles grant there; one available to all clients, or Maison only, the global grant. Seed rules need the global policies grant (checked before the role is written). Attach shared policies later with `attach_role_policy`.

- REST: `POST https://maison-labs.com/api/v1/role`
- MCP tool / A2A skill: `create_role`
- Needs: maison-roles WRITE (client)
- Composite of: `add_role_policy`.

Inputs:

| Field | Where | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `name` | body | string | yes |  |
| `description` | body | string | no |  |
| `clientId` | body | string \| null | no | Available to: a client id, or null for all clients; default null |
| `visibilityMaisonOnly` | body | boolean | no | Only with clientId null: the role is Maison only (global roles grant) |
| `policies` | body | array of object | no | Seed rules for the role's own policy |

## `delete_client_group` — Delete a hotel group

Delete (soft) a group the hotel owns, with its role links. 409 while it still has members, pending invitations included: remove them first (`update_client_group_members`). 404 when the group is not this hotel's.

- REST: `DELETE https://maison-labs.com/api/v1/client/{clientId}/group/{groupId}`
- MCP tool / A2A skill: `delete_client_group`
- Needs: groups WRITE (client)
- Destructive — confirm with the user first.

Inputs:

| Field | Where | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `clientId` | path | string | yes | Client (hotel) id; format uuid |
| `groupId` | path | string | yes | Group id; format uuid |

## `delete_client_policy` — Delete a hotel policy

Soft-delete a hotel-owned policy. 404 when not found.

- REST: `DELETE https://maison-labs.com/api/v1/client/{clientId}/policy/{policyId}`
- MCP tool / A2A skill: `delete_client_policy`
- Needs: policies WRITE (client)
- Destructive — confirm with the user first.

Inputs:

| Field | Where | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `clientId` | path | string | yes | Client (hotel) id; format uuid |
| `policyId` | path | string | yes | Policy id; format uuid |

## `delete_group` — Delete a group

Soft-delete a group and cascade to members and role links; needs the groups grant where the group is available. 404 when not found or already deleted.

- REST: `DELETE https://maison-labs.com/api/v1/group/{groupId}`
- MCP tool / A2A skill: `delete_group`
- Needs: groups WRITE (any)
- Destructive — confirm with the user first.

Inputs:

| Field | Where | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `groupId` | path | string | yes | Group id; format uuid |

## `delete_policy` — Delete a Maison policy

Delete (soft) a policy available to all clients. 409 while any role still applies it: detach it from its roles first (`detach_role_policy`; `get_policy` lists them). Needs the global policies grant. 404 when unknown or hidden.

- REST: `DELETE https://maison-labs.com/api/v1/policy/{policyId}`
- MCP tool / A2A skill: `delete_policy`
- Needs: policies WRITE (global)
- Destructive — confirm with the user first.

Inputs:

| Field | Where | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `policyId` | path | string | yes | Policy id; format uuid |

## `detach_client_role_policy` — Detach a policy from a hotel role

Stop a hotel role applying a policy. Needs the roles grant at the hotel; the policy itself is kept.

- REST: `DELETE https://maison-labs.com/api/v1/client/{clientId}/role/{roleId}/policy/{policyId}`
- MCP tool / A2A skill: `detach_client_role_policy`
- Needs: maison-roles WRITE (client)
- Destructive — confirm with the user first.

Inputs:

| Field | Where | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `clientId` | path | string | yes | Client (hotel) id; format uuid |
| `roleId` | path | string | yes | Role id; format uuid |
| `policyId` | path | string | yes | Policy id; format uuid |

## `detach_role_policy` — Detach a policy from a role

Stop a role applying a policy anywhere (every client it applied at). The policy itself is kept. 404 when the role does not apply it. Needs the roles grant where the role is available.

- REST: `POST https://maison-labs.com/api/v1/role/{roleId}/policy/{policyId}/detach`
- MCP tool / A2A skill: `detach_role_policy`
- Needs: maison-roles WRITE (any)
- Destructive — confirm with the user first.

Inputs:

| Field | Where | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `roleId` | path | string | yes | Role id; format uuid |
| `policyId` | path | string | yes | Policy id; format uuid |

## `disable_client_role` — Disable a hotel role

Soft-disable a hotel-owned role. Holders lose their grants at the next permission-cache refresh.

- REST: `POST https://maison-labs.com/api/v1/client/{clientId}/role/{roleId}/disable`
- MCP tool / A2A skill: `disable_client_role`
- Needs: maison-roles WRITE (client)
- Destructive — confirm with the user first.

Inputs:

| Field | Where | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `clientId` | path | string | yes | Client (hotel) id; format uuid |
| `roleId` | path | string | yes | Role id; format uuid |

## `disable_role` — Disable a role

Retire a role (sets disabled_date); needs the roles grant where the role is available. Holders lose their grants at the next permission-cache refresh. 404 when the role is unknown or already disabled.

- REST: `POST https://maison-labs.com/api/v1/role/{roleId}/disable`
- MCP tool / A2A skill: `disable_role`
- Needs: maison-roles WRITE (any)
- Destructive — confirm with the user first.

Inputs:

| Field | Where | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `roleId` | path | string | yes | Role id; format uuid |

## `get_client_group` — Get a hotel group

Fetch a hotel group's members, candidate users, role assignments, and effective access summary. 404 when not found.

- REST: `GET https://maison-labs.com/api/v1/client/{clientId}/group/{groupId}`
- MCP tool / A2A skill: `get_client_group`
- Needs: groups READ (client), maison-users READ (client)
- Read-only.

Inputs:

| Field | Where | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `clientId` | path | string | yes | Client (hotel) id; format uuid |
| `groupId` | path | string | yes | Group id; format uuid |

## `get_client_policy` — Get a hotel policy

Fetch a hotel policy's rules (feature + level) and its effective access summary. 404 when not found.

- REST: `GET https://maison-labs.com/api/v1/client/{clientId}/policy/{policyId}`
- MCP tool / A2A skill: `get_client_policy`
- Needs: policies READ (client)
- Read-only.

Inputs:

| Field | Where | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `clientId` | path | string | yes | Client (hotel) id; format uuid |
| `policyId` | path | string | yes | Policy id; format uuid |

## `get_client_role` — Get a hotel role

Fetch a hotel role's policies and its effective access summary. 404 when the role is not found or not assignable for this hotel.

- REST: `GET https://maison-labs.com/api/v1/client/{clientId}/role/{roleId}`
- MCP tool / A2A skill: `get_client_role`
- Needs: maison-roles READ (client)
- Read-only.

Inputs:

| Field | Where | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `clientId` | path | string | yes | Client (hotel) id; format uuid |
| `roleId` | path | string | yes | Role id; format uuid |

## `get_group` — Get a group

Full detail of a group you can see, including members and role assignments. 404 when unknown, available to a client you do not read, or Maison only without the global groups read grant.

- REST: `GET https://maison-labs.com/api/v1/group/{groupId}`
- MCP tool / A2A skill: `get_group`
- Needs: groups READ (any)
- Read-only.

Inputs:

| Field | Where | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `groupId` | path | string | yes | Group id; format uuid |

## `get_policy` — Get a Maison policy

Fetch one policy you can see with its rules (feature + level) and the roles that apply it. Read it before `set_policy_rules` so the rules you keep stay. 404 when not found, or available to a client you do not read.

- REST: `GET https://maison-labs.com/api/v1/policy/{policyId}`
- MCP tool / A2A skill: `get_policy`
- Needs: policies READ (any)
- Read-only.

Inputs:

| Field | Where | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `policyId` | path | string | yes | Policy id; format uuid |

## `get_role` — Get a role

Fetch a role's detail — its attached policies, each with `applies_to` (`all: true`, or the named `clients` where the role applies it) and rule count — its first page of direct users, and its first page of groups. 404 when the role is unknown, or available to a client you do not read.

- REST: `GET https://maison-labs.com/api/v1/role/{roleId}`
- MCP tool / A2A skill: `get_role`
- Needs: maison-roles READ (any)
- Read-only.

Inputs:

| Field | Where | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `roleId` | path | string | yes | Role id; format uuid |

## `link_client_group_role` — Link a group to a hotel role

Create a user_group_role_link so a hotel-owned group inherits a role at this hotel. Revives a soft-deleted link when one already exists.

- REST: `POST https://maison-labs.com/api/v1/client/{clientId}/usergroup`
- MCP tool / A2A skill: `link_client_group_role`
- Needs: groups WRITE (client), maison-users WRITE (client)

Inputs:

| Field | Where | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `clientId` | path | string | yes | Client (hotel) id; format uuid |
| `groupId` | body | string | yes | Group id to link; format uuid |
| `roleId` | body | string | yes | Role id to assign to the group; format uuid |

## `list_client_features_catalogue` — Hotel features catalogue

Features available for hotel-scoped roles and policies at a specific hotel, with their display labels.

- REST: `GET https://maison-labs.com/api/v1/client/{clientId}/featurescatalogue`
- MCP tool / A2A skill: `list_client_features_catalogue`
- Needs: policies READ (client)
- Read-only.

Inputs:

| Field | Where | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `clientId` | path | string | yes | Client (hotel) id; format uuid |

## `list_client_groups` — List hotel groups

The access groups available at a hotel: its own and those available to all clients — never another hotel's. Use `get_client_group` for membership details.

- REST: `GET https://maison-labs.com/api/v1/client/{clientId}/groups`
- MCP tool / A2A skill: `list_client_groups`
- Needs: groups READ (client)
- Read-only.

Inputs:

| Field | Where | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `clientId` | path | string | yes | Client (hotel) id; format uuid |

## `list_client_policies` — List hotel policies

The policies available at a hotel: its own and those available to all clients (never Maison-only ones, never another hotel's). Use `get_client_policy` for its rules.

- REST: `GET https://maison-labs.com/api/v1/client/{clientId}/policies`
- MCP tool / A2A skill: `list_client_policies`
- Needs: policies READ (client)
- Read-only.

Inputs:

| Field | Where | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `clientId` | path | string | yes | Client (hotel) id; format uuid |

## `list_client_roles` — List assignable roles at a hotel

Roles that can be assigned to users or groups at a specific hotel (excludes Maison-only and disabled roles). Use `create_client_role` to add a new hotel-owned role.

- REST: `GET https://maison-labs.com/api/v1/client/{clientId}/roles`
- MCP tool / A2A skill: `list_client_roles`
- Needs: maison-roles READ (client)
- Read-only.

Inputs:

| Field | Where | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `clientId` | path | string | yes | Client (hotel) id; format uuid |

## `list_features` — List features

Read-only catalogue of all Maison features: id, key (`name`), display `label`, `purpose` (what it does and where), description and `maison_only`. Use the ids in policy rules (`set_policy_rules`, `add_role_policy`, `create_role`); match a person's words against label, purpose and description, and skip deprecated features. A rule granting a `maison_only` feature (or attaching a policy that carries one) needs `allow-maison-only` WRITE held globally; a DENY rule does not.

- REST: `GET https://maison-labs.com/api/v1/features`
- MCP tool / A2A skill: `list_features`
- Needs: maison-roles READ (global)
- Read-only.

Inputs:

| Field | Where | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `limit` | query | integer | no | Page size (1–100); default 20 |
| `after` | query | string | no | Opaque cursor from the previous page's `nextCursor`; omit for the first page |
| `search` | query | string | no |  |

## `list_groups` — List groups

Page through the user groups you can see, with optional search and sort (name, member count or creation): groups available to all clients and those of each client you read — never another client's; Maison-only groups (`visibility_maison_only`) only with the global groups read grant. Use `get_group` for members and roles.

- REST: `GET https://maison-labs.com/api/v1/groups`
- MCP tool / A2A skill: `list_groups`
- Needs: groups READ (any)
- Read-only.

Inputs:

| Field | Where | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `limit` | query | integer | no | Page size (1–100); default 20 |
| `after` | query | string | no | Opaque cursor from the previous page's `nextCursor`; omit for the first page |
| `search` | query | string | no |  |
| `orderBy` | query | enum: name \| member_count \| created_date | no | default "created_date" |
| `sort` | query | enum: ASC \| DESC | no | default "DESC" |

## `list_policies` — List Maison policies

Page through the policies you can see (search by name or description), with rule and role counts: policies available to all clients, Maison-only ones (with the global policies grant) and those of each client you read — never another client's. Use `get_policy` for one policy's rules.

- REST: `GET https://maison-labs.com/api/v1/policies`
- MCP tool / A2A skill: `list_policies`
- Needs: policies READ (any)
- Read-only.

Inputs:

| Field | Where | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `search` | query | string | no |  |
| `limit` | query | integer | no | Page size (1–200); default 50 |
| `offset` | query | integer | no | Rows to skip; default 0 |

## `list_role_groups` — List a role's groups

Page through the groups given a role (their members inherit it), each with the client the link is at. `get_role` shows only the first page; use this for the rest.

- REST: `GET https://maison-labs.com/api/v1/role/{roleId}/groups`
- MCP tool / A2A skill: `list_role_groups`
- Needs: maison-roles READ (any)
- Read-only.

Inputs:

| Field | Where | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `roleId` | path | string | yes | Role id; format uuid |
| `limit` | query | integer | no | Page size (1–100); default 20 |
| `after` | query | string | no | Opaque cursor from the previous page's `nextCursor`; omit for the first page |

## `list_role_users` — List a role's users

Page through the users holding a role directly, each with the client the link is at (null = all clients). `get_role` shows only the first page; use this for the rest.

- REST: `GET https://maison-labs.com/api/v1/role/{roleId}/users`
- MCP tool / A2A skill: `list_role_users`
- Needs: maison-roles READ (any)
- Read-only.

Inputs:

| Field | Where | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `roleId` | path | string | yes | Role id; format uuid |
| `limit` | query | integer | no | Page size (1–100); default 20 |
| `after` | query | string | no | Opaque cursor from the previous page's `nextCursor`; omit for the first page |

## `list_roles` — List roles

Page through the roles you can see, with optional search, sort and filters (`scope`, `ownerClientId`, `includeRetired`): roles available to all clients, Maison-only roles (with the global roles grant) and the roles of each client you read — never another client's. Each row carries its client and whether you can manage it. Use `get_role` for one role's policies and members.

- REST: `GET https://maison-labs.com/api/v1/roles`
- MCP tool / A2A skill: `list_roles`
- Needs: maison-roles READ (any)
- Read-only.

Inputs:

| Field | Where | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `limit` | query | integer | no | Page size (1–200); default 20 |
| `after` | query | string | no | Opaque cursor from the previous page's `nextCursor`; omit for the first page |
| `offset` | query | integer | no | Rows to skip (after the cursor, when both are given) |
| `search` | query | string | no |  |
| `orderBy` | query | enum: name \| created_date | no | default "created_date" |
| `sort` | query | enum: ASC \| DESC | no | default "DESC" |
| `scope` | query | enum: global \| client | no | `global`: roles available to all clients; `client`: roles of one client |
| `ownerClientId` | query | string | no | Only the roles available to this client (its own roles); format uuid |
| `includeRetired` | query | boolean | no | Also list retired roles; default false |

## `remove_client_policy_statement` — Remove a statement from a hotel policy

Soft-delete one statement from a hotel-owned policy. 404 when not found.

- REST: `DELETE https://maison-labs.com/api/v1/client/{clientId}/policy/{policyId}/statement/{stmtId}`
- MCP tool / A2A skill: `remove_client_policy_statement`
- Needs: policies WRITE (client)
- Destructive — confirm with the user first.

Inputs:

| Field | Where | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `clientId` | path | string | yes | Client (hotel) id; format uuid |
| `policyId` | path | string | yes | Policy id; format uuid |
| `stmtId` | path | string | yes | Statement id to remove; format uuid |

## `remove_client_role_rule` — Remove a rule from a hotel role

Remove one rule (the id `add_client_role_rule` returned, or from `get_client_role`) from a hotel role's own policy. 404 when the role has no such rule.

- REST: `DELETE https://maison-labs.com/api/v1/client/{clientId}/role/{roleId}/rule/{ruleId}`
- MCP tool / A2A skill: `remove_client_role_rule`
- Needs: maison-roles WRITE (client), policies WRITE (client)
- Destructive — confirm with the user first.

Inputs:

| Field | Where | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `clientId` | path | string | yes | Client (hotel) id; format uuid |
| `roleId` | path | string | yes | Role id; format uuid |
| `ruleId` | path | string | yes | Rule id; format uuid |

## `remove_role_policy` — Remove a policy statement from a role

Soft-delete one rule from the role's own policy; needs the roles and policies grants where the role is available. The `policyId` here is the rule id returned by `add_role_policy`.

- REST: `DELETE https://maison-labs.com/api/v1/role/{roleId}/policy/{policyId}`
- MCP tool / A2A skill: `remove_role_policy`
- Needs: maison-roles WRITE (any), policies WRITE (any)
- Destructive — confirm with the user first.

Inputs:

| Field | Where | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `roleId` | path | string | yes | Role id; format uuid |
| `policyId` | path | string | yes | Rule id (returned by add_role_policy); format uuid |

## `restore_role` — Restore a retired role

Bring a retired role back (clears disabled_date) so it can be assigned again; its policies and links are as they were. Needs the roles grant where the role is available. 404 when the role is unknown or not retired. Use `disable_role` to retire one.

- REST: `POST https://maison-labs.com/api/v1/role/{roleId}/restore`
- MCP tool / A2A skill: `restore_role`
- Needs: maison-roles WRITE (any)

Inputs:

| Field | Where | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `roleId` | path | string | yes | Role id; format uuid |

## `set_client_policy_rules` — Set a hotel policy's rules

Save a hotel-owned policy's complete rule list in one call (the client Policies tab's rules table): each rule is a feature, or every feature, at READ, WRITE, EXECUTE or DENY. A policy holds at most one rule per feature (and at most one every-feature rule): its level is exactly one of READ, WRITE, EXECUTE or DENY, and a higher level includes the lower ones (EXECUTE includes WRITE and READ). READ = view it; WRITE = view and change it; EXECUTE = view, change and run or act with it (send, run a job, take an action); DENY = block it even when another policy or role grants it. How to work: (1) `get_client_policy` for the current rules and `list_client_features_catalogue` for the features this hotel can use (id, key, label, purpose). (2) Turn the person's words into rules: "front desk can view conversations and analytics but change nothing" is READ on Conversations and READ on Analytics; "can edit the knowledge base" is WRITE; "can send" or "can run" is EXECUTE; "must never see billing" is DENY. Give a feature exactly one rule (a repeat at another level is refused, nothing saved); DENY only to block. (3) Prefer the specific features over the every-feature rule (featureId null). (4) Send the FULL list: keep the current rules unless the person asked to drop them. (5) When the request is ambiguous, show the rules you would save as a short Feature / Level list and ask before calling. Every rule is checked against your own access at this hotel; one refused rule saves nothing. Policies shared by Maison are read-only here (403).

- REST: `PUT https://maison-labs.com/api/v1/client/{clientId}/policy/{policyId}/rules`
- MCP tool / A2A skill: `set_client_policy_rules`
- Needs: policies WRITE (client)

Inputs:

| Field | Where | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `clientId` | path | string | yes | Client (hotel) id; format uuid |
| `policyId` | path | string | yes | Policy id; format uuid |
| `rules` | body | array of object | yes | The COMPLETE rule list after the change, one rule per feature: a current rule you leave out is removed |

## `set_policy_rules` — Set a Maison policy's rules

Save a Maison policy's complete rule list in one call (the policy page's rules table): each rule is a feature, or every feature, at READ, WRITE, EXECUTE or DENY. A policy holds at most one rule per feature (and at most one every-feature rule): its level is exactly one of READ, WRITE, EXECUTE or DENY, and a higher level includes the lower ones (EXECUTE includes WRITE and READ). READ = view it; WRITE = view and change it; EXECUTE = view, change and run or act with it (send, run a job, take an action); DENY = block it even when another policy or role grants it. How to work: (1) `get_policy` for the current rules and `list_features` for the catalogue (key, label, purpose). (2) Turn the person's words into rules: "can view conversations and analytics but change nothing" is READ on Conversations and READ on Analytics; "can manage the knowledge base" is WRITE; "can send" or "can run" is EXECUTE; "must never see billing" is DENY. Give a feature exactly one rule (a repeat at another level is refused, nothing saved); DENY only to block. (3) Prefer the specific features over the every-feature rule (featureId null); use it only when the person means everything. (4) Send the FULL list: keep the current rules unless the person asked to drop them. (5) When the request is ambiguous (which features, which level), show the rules you would save as a short Feature / Level list and ask before calling. Every rule is checked against your own access; one refused rule saves nothing. Answers the saved rules and how many were added and removed.

- REST: `PUT https://maison-labs.com/api/v1/policy/{policyId}/rules`
- MCP tool / A2A skill: `set_policy_rules`
- Needs: policies WRITE (global)

Inputs:

| Field | Where | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `policyId` | path | string | yes | Policy id; format uuid |
| `rules` | body | array of object | yes | The COMPLETE rule list after the change, one rule per feature: a current rule you leave out is removed |

## `set_role_policy_applies_to` — Change where a role applies a policy

Replace where a role applies one of its policies (`appliesTo`: null = all clients, or client ids) in one call: clients outside the new set stop, missing ones start. Attaches the policy when the role does not apply it yet. Read the current `applies_to` with `get_role` first. Needs the roles grant where the role is available.

- REST: `PUT https://maison-labs.com/api/v1/role/{roleId}/policy/{policyId}/appliesto`
- MCP tool / A2A skill: `set_role_policy_applies_to`
- Needs: maison-roles WRITE (any)

Inputs:

| Field | Where | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `roleId` | path | string | yes | Role id; format uuid |
| `policyId` | path | string | yes | Policy id; format uuid |
| `appliesTo` | body | array of string \| null | yes | Where the role applies the policy: null = all clients, or the client ids. A role available to one client applies its policies there only (null). |

## `unassign_role` — Unassign a role from a user

Take a role away from a user: every scope by default, or only one with `linkScope` (`global` for the link at all clients, or a client id). Needs the users grant at each link's scope (nothing is written otherwise). Returns the number of links affected.

- REST: `DELETE https://maison-labs.com/api/v1/role/{roleId}/user/{userId}`
- MCP tool / A2A skill: `unassign_role`
- Needs: maison-users WRITE (any)
- Destructive — confirm with the user first.

Inputs:

| Field | Where | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `roleId` | path | string | yes | Role id; format uuid |
| `userId` | path | string | yes | User id to remove the role from; format uuid |
| `linkScope` | query | string \| string | no | `global` = only the link at all clients; a client id = only that client's link |

## `unlink_client_group` — Unlink a hotel group from its roles

Soft-delete all role links for a hotel-owned group at this hotel. Returns the number of links affected.

- REST: `DELETE https://maison-labs.com/api/v1/client/{clientId}/usergroup/{groupId}`
- MCP tool / A2A skill: `unlink_client_group`
- Needs: groups WRITE (client)
- Destructive — confirm with the user first.

Inputs:

| Field | Where | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `clientId` | path | string | yes | Client (hotel) id; format uuid |
| `groupId` | path | string | yes | Group id; format uuid |

## `update_client_group` — Update a hotel group

Rename or re-describe a group the hotel owns (`description` null clears it). 404 when the group is not this hotel's. Change its members with `update_client_group_members`.

- REST: `PUT https://maison-labs.com/api/v1/client/{clientId}/group/{groupId}`
- MCP tool / A2A skill: `update_client_group`
- Needs: groups WRITE (client)

Inputs:

| Field | Where | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `clientId` | path | string | yes | Client (hotel) id; format uuid |
| `groupId` | path | string | yes | Group id; format uuid |
| `name` | body | string | no |  |
| `description` | body | string \| null | no |  |

## `update_client_group_members` — Update hotel group members

Add or remove members in a hotel-owned access group.

- REST: `PUT https://maison-labs.com/api/v1/client/{clientId}/group/{groupId}/members`
- MCP tool / A2A skill: `update_client_group_members`
- Needs: groups WRITE (client)

Inputs:

| Field | Where | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `clientId` | path | string | yes | Client (hotel) id; format uuid |
| `groupId` | path | string | yes | Group id; format uuid |
| `add` | body | array of string | no | User ids to add |
| `remove` | body | array of string | no | User ids to remove |

## `update_client_policy` — Update a hotel policy

Patch a hotel-owned policy's name or description. 404 when not found.

- REST: `PUT https://maison-labs.com/api/v1/client/{clientId}/policy/{policyId}`
- MCP tool / A2A skill: `update_client_policy`
- Needs: policies WRITE (client)

Inputs:

| Field | Where | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `clientId` | path | string | yes | Client (hotel) id; format uuid |
| `policyId` | path | string | yes | Policy id; format uuid |
| `name` | body | string | no |  |
| `description` | body | string | no |  |

## `update_client_role` — Update a hotel role

Patch a hotel-owned role's name or description.

- REST: `PUT https://maison-labs.com/api/v1/client/{clientId}/role/{roleId}`
- MCP tool / A2A skill: `update_client_role`
- Needs: maison-roles WRITE (client)

Inputs:

| Field | Where | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `clientId` | path | string | yes | Client (hotel) id; format uuid |
| `roleId` | path | string | yes | Role id; format uuid |
| `name` | body | string | no |  |
| `description` | body | string | no |  |

## `update_group` — Update a group

Patch a group's name, description or Maison-only flag (`visibilityMaisonOnly`, which needs the global groups grant and a group available to all clients — 400 on a hotel's group); needs the groups grant where the group is available. 404 when unknown.

- REST: `PUT https://maison-labs.com/api/v1/group/{groupId}`
- MCP tool / A2A skill: `update_group`
- Needs: groups WRITE (any)

Inputs:

| Field | Where | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `groupId` | path | string | yes | Group id; format uuid |
| `name` | body | string | no |  |
| `description` | body | string | no |  |
| `visibilityMaisonOnly` | body | boolean | no | Set or clear Maison only (a group available to all clients; global groups grant) |

## `update_group_members` — Update group members

Add or remove members of a group; needs the groups grant where the group is available (and the global roles grant for a group carrying a Maison-only role). Both arrays are optional; supply only the changes needed.

- REST: `PUT https://maison-labs.com/api/v1/group/{groupId}/members`
- MCP tool / A2A skill: `update_group_members`
- Needs: groups WRITE (any)

Inputs:

| Field | Where | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `groupId` | path | string | yes | Group id; format uuid |
| `add` | body | array of string | no | User ids to add |
| `remove` | body | array of string | no | User ids to remove |

## `update_group_roles` — Update group roles

Add or remove role assignments of a group (each at a client, or at all clients with clientId null); needs the groups grant where the group is available, and the users grant where each role is given. A group available to one client gives roles only there. Both arrays are optional.

- REST: `PUT https://maison-labs.com/api/v1/group/{groupId}/roles`
- MCP tool / A2A skill: `update_group_roles`
- Needs: groups WRITE (any), maison-users WRITE (any)

Inputs:

| Field | Where | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `groupId` | path | string | yes | Group id; format uuid |
| `add` | body | array of object | no |  |
| `remove` | body | array of object | no |  |

## `update_policy` — Update a Maison policy

Rename or re-describe a policy available to all clients, or set / clear its Maison-only flag. Needs the global policies grant. 404 when the policy is unknown or hidden. Change its rules with `set_policy_rules`.

- REST: `PUT https://maison-labs.com/api/v1/policy/{policyId}`
- MCP tool / A2A skill: `update_policy`
- Needs: policies WRITE (global)

Inputs:

| Field | Where | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `policyId` | path | string | yes | Policy id; format uuid |
| `name` | body | string | no |  |
| `description` | body | string | no |  |
| `visibilityMaisonOnly` | body | boolean | no |  |

## `update_role` — Update a role

Patch a role's name, description or Maison-only flag (`visibilityMaisonOnly`, which needs the global roles grant). Needs the roles grant where the role is available (its client, or globally). 404 when the role is unknown.

- REST: `PUT https://maison-labs.com/api/v1/role/{roleId}`
- MCP tool / A2A skill: `update_role`
- Needs: maison-roles WRITE (any)

Inputs:

| Field | Where | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `roleId` | path | string | yes | Role id; format uuid |
| `name` | body | string | no |  |
| `description` | body | string | no |  |
| `visibilityMaisonOnly` | body | boolean | no | Set or clear Maison only (a role available to all clients; global roles grant) |
