1
Workspace membership
The caller must be a member of the workspace named in the path (or, on routes without one, in the
X-Workspace-Id header or the credential’s own scope). A non-member gets 403, and a workspace that does not exist gets 404.2
Team membership
The caller must be a member of the team that owns the project. This is the gate that actually keeps one team’s secrets away from another’s — being in the workspace is not enough.
3
Project ownership
The project must genuinely belong to that team and workspace. A project id borrowed from another scope reads as
404, not as an authorisation error.Roles
Workspace membership carries one of four roles, ordered:The boundaries Sink enforces today are workspace membership, team membership, and owner-only operations. The Viewer / Member distinction is stored on the membership but does not yet restrict writes at the secret layer — anyone in the team can read and write that team’s secrets. Use team membership, not role, to scope who can change production values for now.
Owner-only operations
- Renaming or deleting a workspace
- Inviting someone to a workspace
DELETEon an environment’s entire secret set
Admin-or-owner operations
- Creating a team
- Listing every team in the workspace (others see only the teams they are in)
Inviting people
An owner sends an invite to an email address, optionally naming a team and a role:POST /invite/validate?token=…), accept it (POST /invite/accept, which creates the account and signs them in if they are new), or decline it (POST /invite/reject?token=…). Invites carry a status of pending, accepted, rejected or expired, and GET /invite/{workspace_id} lists them.
Sending an invite counts against the workspace’s seat limit, so a full workspace is refused with 402 before any mail goes out.
