Roles and property access
Questi contenuti non sono ancora disponibili nella tua lingua.
Open Settings → Role permissions in a workspace to inspect the permission matrix. Built-in roles
are fixed. Select one and choose Duplicate to use its grants as the starting point for
a custom role, or create a role from scratch. The role name is unique in that workspace.
An unused custom role can be deleted; a role assigned to any member cannot. A custom role
cannot take a built-in role’s code or its English catalog name, however it is spelled:
“ADMIN”, “Revenue-manager” and “FRONT-DESK” are refused with team.invalid on /name.
Former translated names are ordinary custom-role names; existing roles retain their IDs and grants.
Open Settings → Users and roles to assign a role and choose All properties or Selected properties. Select at least one property and save. The member’s next request uses the new role and property set; a reload shows the saved choice. The invitation form also selects role and property access before issuing the link. All properties is the default; a selected invitation grants only those properties from its first accepted request. Organization owners and administrators inherit full access.
Permissions describe individual operations, such as reservations.cancel or
folios.refund and folios.approve_open_balance. Granting folios.post does not grant refunds, reversals or open-balance checkout approval. A member
limited to some properties can use a permission only at those properties. Team management,
workspace audit, Channex and property creation require unrestricted property access. Each
catalog entry declares its scope, property or workspace, and the table in
ADR 0036
lists what a selected-property member gets for each code. An empty selection grants nothing.
The guest directory includes guests created in an allowed property and guests with a
reservation there. A guest shared by two properties is visible in both.
For a new guest created by a member limited to several properties, the first selected
property is recorded as the origin until a reservation links the guest elsewhere.
API clients use the live session, GuestCraft-Workspace-Id, and the existing same-origin
write policy. GET /roles reads the catalog; GET/POST /roles/custom lists and creates
workspace roles; GET/PUT/DELETE /roles/custom/{role_id} addresses one role; and
GET/PUT /members/{member_id}/access addresses one member’s role and property access.
Team resources select the workspace with the canonical header; deprecated workspace_id
query/body aliases must match it. POST /roles/custom returns 201 with
Location and a strong ETag; item GETs return ETag; item PUTs and DELETE require
If-Match and reject stale values with 412. Delete returns 204. The SDK exposes
team.customRoles, createRole, getRole, updateRole, deleteRole, getAccess,
changeAccess, getSignInLock and unlockSignIn; React Query includes their generated options.
GET/DELETE /members/{member_id}/sign_in_lock reads and lifts a member’s lock of failed
sign-ins (team.manage, which includes a manager’s own lock from an earlier session; DELETE
takes the GET’s strong If-Match, answers 400 request.invalid without it, and is audited as
team.sign_in_unlocked). The lock itself is audited as team.sign_in_locked. Invitation creation accepts
nullable property_ids (omit or null for all). Input uses stable permission
codes and canonical property/role IDs. Invalid team bodies return team.invalid (422), while invalid path/header/query syntax
returns request.invalid (400); a
case-insensitive duplicate name returns team.duplicate_role; a referenced role returns
team.role_in_use; removing the last manager returns team.last_admin.
When adding an operation, choose a catalog permission, check it with can at the domain
boundary and pass the property ID where applicable. A new code must declare its scope; a
workspace code also declares restricted: "filtered" (its operations return only the member’s
properties’ rows) or "denied". A read that spans every property the caller may reach uses
canAcrossProperties with a property code and relies on the unit of work’s property scope. Add matching English labels and
descriptions, UI visibility and a test for built-in compatibility plus custom-role denial.
Property-linked tables need a restrictive RLS policy or a documented parent-policy path.
See ADR 0036.
Permission codes
Section titled “Permission codes”The catalog in core/tenancy declares each capability and its scope. Custom roles select them independently;
grant each screen’s read permissions as well as its writes:
| Area | Permission codes |
|---|---|
| Properties and room setup | properties.read, properties.configure, inventory.read, inventory.configure |
| Guests and reservations | guests.read, guests.manage, guests.merge, guests.consents, guests.reassign, reservations.read, reservations.create, reservations.modify, reservations.cancel, reservations.override_price, reservations.rerate |
| Planning protection | reservations.set_do_not_move (set or clear a reservation room’s Do not move flag) |
| Stays and housekeeping | stays.check_in, stays.check_out, stays.override_readiness, housekeeping.update, housekeeping.assign, housekeeping.configure |
| Rates | rates.read, rates.edit, rates.configure_plans |
| Night audit and cash drawer | night_audits.run, night_audits.configure, cash_shifts.read, cash_shifts.open, cash_shifts.close, cash_shifts.configure |
| Folio | folios.read, folios.post (charges), folios.post_payment, folios.refund, folios.correct (reversals), folios.rerate, folios.split, folios.transfer, folios.configure_extras, folios.credits_due, folios.approve_open_balance |
| Online payments | payments.configure (connect the workspace’s provider account; browser session only, unrestricted property access, administrators by default), payments.collect (payment links, saved-card charges, damage-hold capture and release; administrators, front desk and accounting), payments.refund (refunds through the provider; administrators and accounting) |
| Policies | policies.read, policies.configure, policies.prepayments, policies.simulate_cancellation, policies.override_cancellation, policies.no_show, policies.undo_no_show, policies.waive_no_show, policies.manage_guarantee, policies.waive_guarantee |
| Physical rooms | blocks.read, blocks.manage_ooo, blocks.manage_oos, maintenance.read, maintenance.manage |
| Channex and Messages | channex.view_health, channex.configure, channex.manage_account_mode, messages.read, messages.send, messages.configure |
| Reports, team and integrations | reports.view, team.manage, audit.view, api_keys.manage, webhooks.manage, events.read, oauth_applications.manage |
Planning moves, splits and resizing require reservations.modify; in-house changes additionally
require stays.check_in. Setting or clearing Do not move independently requires
reservations.set_do_not_move. A protected movement requires an explicit acknowledgement,
even when the actor can change the flag. Undo requires current reservation modification and, in house, check-in access. Restoring the
receipt’s frozen sale does not require a new price-override grant.
Cancellation still requires reservations.cancel; cancellation overrides additionally
require policies.override_cancellation. Recording a waived guarantee requires both
policies.manage_guarantee and policies.waive_guarantee. Check-out still requires
stays.check_out; an owed-balance approval additionally requires
folios.approve_open_balance. Guest resource edits require guests.manage. Consent evidence uses guests.consents (administrators, front desk and revenue managers by default). Merge and owning-property reassignment use guests.merge and guests.reassign (administrators by default); both require unrestricted property access. See guests. Erasure, access exports and legal holds use guests.erase, guests.export and guests.legal_hold (administrators by default), require unrestricted property access and a browser session, and are never granted to API keys; retention settings use workspaces.configure. See guest data rights.
Reading a reservation’s online payments, a payment link or a provider payment requires
folios.read; the payments-due list follows policies.prepayments, amounts included. A cash
settlement requires folios.post_payment to take cash and folios.refund to give it back. See
online payments.
events.read reads the events API and Settings → Events; events name objects of every property,
so it is refused to members (and API keys) limited to selected properties.
oauth_applications.manage lists and revokes the OAuth applications every member authorized in the
workspace (Settings → Authorized applications, administrators by default). It is browser-only and
refused to members limited to selected properties; no application or key can hold it. Authorizing
an application for oneself on the consent page needs no permission: the application never gets
more than the member holds. See OAuth applications.
Block type conversion needs both the original and destination type’s manage grant. Ending or removing a block requires its actual type’s grant. Maintenance changes do not grant housekeeping updates. PostgreSQL restricts property-bearing rows and folio children. Invitation authority and last-manager guards recheck effective unrestricted grants inside the serialized team transaction.
A dedicated financial operation returns its complete authorized result, including exact
amounts, when its own grant permits it. General reservation views still require
folios.read to expose money. For example, policies.prepayments and
folios.credits_due work independently of generic folio reads; payment/configuration
responses retain their documented money fields. ARI health answers Channex’s price limits only
with rates.read for the property; channex.view_health alone gives the rest of health. The front
desk’s deposits_due follows policies.prepayments too, amounts included.
The web desk requests only what the member may read. Without properties.read, the shell’s
property switcher and the Channels screen read no property list, and Channels names the mapped
properties by their IDs; without inventory.read, it shows no room type mapping. Without
rates.read, a reservation’s room does not name its rate plan and the booking form offers no
quote. Such a member can book only room types without an active rate plan, at a manual price:
the API requires a rate plan for a room type that has one.
Delegation and security review R1
Section titled “Delegation and security review R1”team.manage grants administration only within the actor’s current effective permissions.
It does not grant additional operational capabilities. A supervisor cannot create/update a
role beyond that ceiling, assign a broader custom or built-in role, invite someone with such
a role, or expand their own access. Organization owners/admins keep full rights. Invitation
acceptance rechecks the issuer and current target role under the same team lock.
Use GuestCraft-Workspace-Id on all team requests except acceptance and the public catalog.
Deprecated workspace aliases must match it. POST /roles/custom returns CustomRole and a
Location identifying the same representation. Role permission arrays have a canonical
order, so reordering a grant set preserves both its representation and strong validator.
List returns {data,next_cursor,grantable_permissions,grantable_builtin_roles}.
Follow cursor with limit (1–100). Member pages expose independent
next_cursor and invitations_next_cursor; pass the latter as invitation_cursor.
Role/access item operations and legacy member role/removal POSTs use strong If-Match.
Access PUT returns a fresh ETag. Inherited organization administration participates in
the access representation and its validator. Invalid team bodies return 422; missing custom roles return
team.role_not_found (404).
Audit entries expose safe permission/property deltas and previous/next role ids.
properties.read protects item and list reads. Room readiness belongs to inventory.read, and so do
reading housekeeping tasks, generated days and settings;
the ineffective housekeeping.read code has been removed. inventory.configure also permits
back-dated arrivals. A folio posting, correction, refund or re-rate answers the full folio only to
a caller that also holds folios.read for the property; any other caller receives a
FolioAcknowledgement with the folio’s identifiers only (ADR 0036, 2026-10-30 amendment).
PostgreSQL requires explicit app.property_ids='*' for unrestricted access. Missing/empty
context grants no property data. Selected contexts cannot modify access-control tables or
read workspace-wide audit/events, while still appending operational audit/event writes.