Nobody can grant what they do not hold
45 permissions, three built-in roles, member-only private products, and privilege escalation blocked by design.
The model, exactly
Every workspace member holds one role, drawn from 45 named permissions. Private products are limited to named members, and API keys are clamped to their creator's permissions.
Full definition
Every workspace member holds one role, and a role is a list drawn from 45 named permissions across 12 resource groups. Three roles are seeded when the workspace is created: Owner with all 45, Admin with 43, and Viewer with 12 read-only ones. Below the workspace, a product can be marked private, which limits it to an explicit list of members regardless of their role. API keys carry a separate scope map that is clamped to the permissions of whoever created them, and re-clamped automatically whenever that person's role changes. Permissions are workspace-scoped rather than per-environment: the unit of isolation below the workspace is the product, not the environment.
Roles and permissions
The full catalogue, custom roles, and what the built-in roles hold.
Members
Inviting, updating, removing, and what removal revokes.
Private product access
Marking a product private and granting it to named members.
API keys
Scopes, the project allowlist, rotation, and the step-up gate.
API authentication
How a scope requirement is declared per route and what a miss returns.
Workspaces
How membership, not the URL, is what grants access to anything.
Permissions across resource groups
45 / 12Namespaced as resource:action, covering workspace, members, keys, billing, roles, products, projects, environments, variables, versions, webhooks and analytics. The same catalogue ships in the CLI and is readable over the API.
Owner, Admin, Viewer
45 / 43 / 12Seeded in the same transaction that creates the workspace. Admin holds everything except deleting the workspace and managing billing. System roles are immutable: update and delete both return role.immutable.
Custom roles per workspace
25Names are 2 to 40 characters and unique case-insensitively. Two permissions can never be granted to a custom role at all, workspace:delete and billing:manage, and the validation refuses them for every caller including Owners.
You cannot grant above yourself
clampedCreating a role, editing a role, inviting a member or changing someone's role is refused when the result would exceed the permissions you personally hold, and the error names the exact offending permission strings.
Returned by a private product you lack
404Private-product access is re-checked server-side on every read and write beneath the product, across 21 controllers, and a denied request answers 404 rather than 403, so a restricted product is indistinguishable from one that does not exist.
To remove a member completely
1 transactionSoft-deletes the membership, hard-revokes every API key they hold in that workspace, revokes their CLI tokens, and drops their private-product grants. The key middleware independently re-checks membership on every request.
Team access control FAQ
Not through roles, and this is the most important limitation to understand. Permissions are workspace-scoped, so variables:read applies to every environment in the workspace at once. There is no per-environment ACL. What you can do is put the sensitive work in its own product, mark that product private, and grant it only to the people who should reach it, which isolates the entire project and environment subtree beneath it. For machines, a key can additionally be pinned to a project allowlist.
No, and it is important not to imply otherwise. There is one encryption key per workspace, derived from one shared passphrase. Private products and role checks are server-side authorization: they stop new requests, they do not make previously readable ciphertext unreadable. Anyone who ever held the passphrase can still decrypt anything they kept a copy of. Rotating the workspace passphrase is what actually retires that access.
No. Any role holding products:manage_private bypasses grant checks entirely, and both built-in Owner and Admin hold it. Private products protect against broad workspace-wide visibility, not against your own administrators. If you need a boundary that administrators cannot cross, that is a separate workspace.
A clamp on every path that could widen a permission set: creating a role, editing a role, inviting a member, changing a member's role, and creating an API key. Each is refused when the result would exceed what you personally hold, with the offending permissions named. Two permissions are additionally hard-blocked from custom roles for everyone, including Owners: deleting the workspace and managing billing.
They are hard-revoked in the same transaction as the removal, along with their CLI tokens and their private-product grants. That matters more than it sounds: a key outliving its owner is the classic way access survives an offboarding. The API-key middleware also re-checks workspace membership on every request, so there is a second line of defence.
43 endpoints, reached from the dashboard and from the CLI, in six groups: credentials (create, rotate, deactivate or delete an API key, create, update or delete a webhook endpoint, read or rotate its signing secret, approve a CLI device), access changes (invite, update or remove a member, create, update or delete a role, grant or revoke private-product access), destructive structure changes (delete a workspace, product, project or environment), variable writes at scale (apply a set, bulk update, bulk delete, roll back a version), workspace security and exposure settings (claim the key rotation lock, expose an environment, turn on virtual environments, claim or remove a custom domain, require two-factor of every member, turn verification codes off, open the billing portal), and account-level actions (delete your account, change your email, connect or disconnect a provider, enable or disable two-factor authentication). The authoritative list is in the step-up verification docs. Envless emails a six-character code, valid ten minutes, capped at five attempts, and one success elevates you for an hour; if you have two-factor authentication on you can answer with your authenticator or a recovery code instead of waiting for the email. It still does not gate reading variables, and it is not enforced on the public API surface at all.
Team members are plan-gated: Free and Dev are single-seat, Team allows up to 15 and Enterprise is unlimited. Both inviting and accepting count active members plus pending invites against that ceiling, so an invite that would exceed it is refused at send time rather than at acceptance.
Not today, and it is a gap worth knowing about. Member changes, role changes, key changes and CLI-token changes all emit workspace events that reach your webhook endpoint. Granting and revoking product access does not emit an event and has no webhook type. If you need that trail, record it on your side when you make the call.
