Your access to the Prospero API is limited by the scopes assigned to the authenticated user. Prospero's scopes are granular, giving you maximum control over the entities users can access and the actions they can perform.
Scopes
Prospero scopes are defined in a hierarchical structure, where more granular scopes take precedence over coarser ones. The scopes are described and best visualized as a nested object:
Code
When determining authorization for a given API endpoint, Prospero works bottom-up, from most granular scope to coarsest, to determine the user's permission level.
For example, if a user is attempting to create a new Project, Prospero will first check the user's projects scope (corresponding to entities.projects.default in the interface above). If this scope has a permission level greater than or equal to Permission.CREATE (Permission.CREATE or Permission.DELETE), they will be authorized to create a new Project. If the permission level is less than Permission.CREATE, the user is unauthorized and will be denied access. If the projects scope is absent, Prospero will check the user's entities scope (corresponding to entities.default), then finally, if the entities scope is absent, Prospero will check the user's default scope (corresponding to default).
Field-level Event permissions
Event access can be narrowed further, per field. The events.fields map holds a Permission for individual Event fields and each per-Project grant carries its own fields map (events.byProject[projectId].fields). Every field is optional. When resolving access to a specific Event field, Prospero checks that field's permission first, then falls back to the surrounding Event permission (the per-Project default for that Event's Project, otherwise events.default), then the coarser entities and top-level defaults. Because an unset field simply inherits the Event's permission, field-level entries are only needed where a field should diverge from its Event.
UserRoles
A UserRole is a reusable, Organization-scoped permission template. Instead of setting every scope on every user by hand, you define a role once (for example, Stage Manager) and assign users to it.
A user's effective permission for a given scope is resolved on the server by coalescing their own value with their UserRole's:
- If the user has an explicit value for the scope, that value wins.
- Otherwise, the value from their assigned
UserRoleis used. - If neither is set, the coarser-scope fallback described above applies.
In other words, a user's own per-scope value always overrides their UserRole, and any value they leave unset transparently inherits the UserRole's. Change a UserRole once and every inheriting user moves with it.
UserRoles carry the same scopes a user can — the top-level default, the settings.* permissions, each entities.*.default, the per-instance maps (events.byProject, bookings.byBookingModule), and the per-field Event permissions (events.fields, plus each events.byProject entry's fields). This lets a role narrow (or widen) access to specific projects, booking modules, or individual Event fields — for example, a role scoped to a single production, or one that may edit event titles but only read admin notes. The per-instance maps merge per key: a user's own grant for a project/module wins, otherwise the UserRole's applies; a key set on neither falls through to the events/bookings default. Event field permissions merge the same way, per field.
Effective permissions are always resolved server-side: the authorization returned on a User is already coalesced, so clients should never re-derive it from the role.
Manage roles through the /users/roles endpoints (gated by the caller's settings.membership permission). Every Organization is provisioned with two default roles: Super Admin (full access) and Admin (full control of domain entities, membership, and organization settings, except it cannot access billing or delete the Organization itself).
Each role carries a read-only kind — SUPER_ADMIN, ADMIN, or CUSTOM — identifying the seeded defaults. Roles created via the API are always CUSTOM. The default roles are protected: they cannot be deleted or renamed, and their authorization cannot be modified. Use kind to render their permissions read-only in clients.
Assign a role by setting userRoleId via PATCH /users/{userId}. Assignment requires a settings.membership permission of at least UPDATE in the target's Organization, and users can never change their own role. Both authoring and assigning a role are bound by a privilege ceiling: you cannot create or assign a role that grants more than you yourself hold for any scope, including per-Project and per-BookingModule grants.