Requires the
workspaces bundle. See Bundles to add it.@sublay/core (there is no WorkspaceProvider and no Redux slice — the list uses the Wrapper pattern, matching house style).
Key concepts
- capabilities vs permissions — Sublay enforces the closed
capabilitiesvocabulary; it is blind to your opaquepermissions. - Ownership — one owner per workspace (the creator); ancestor owners are gods over descendants.
- Reach & the wall/door rule — strict per-node by default; opt-in downward reach via the child-owned
inheritsFromParentflag. - Invitations — identity-matched + verified-email, no token.
Project settings
Three project-level settings configure this bundle. They live underworkspaces in your project’s settings blob — set them in the dashboard under Settings → project settings, or via the update-project-settings endpoint. All three are absent by default, and absent means the strict behavior in the table below.
Project settings
The accept link carries nothing secret. Acceptance is identity-matched — the signed-in caller must be the invitation’s target and have a verified email — so there is no token to leak and no domain allowlist to maintain. The email is a plain nudge into your app. See the acceptance model.
What a client app can do
All 21 workspace endpoints are callable from a browser or mobile app with a normal signed-in user’s bearer token — including every management operation: editing a workspace, deleting it, transferring ownership, flipping the inherit flag, editing a member’s capabilities/rank, removing a member, offboarding across a subtree, and the full invite lifecycle (create, list, revoke, resend). Every one of these is exposed as a@sublay/core hook and as a @sublay/js function. A members-management panel needs no server-side code and no hand-rolled fetch.
With a client token, every endpoint in the bundle resolves the actor from that token and authorizes the action against that user’s own standing on the workspace. Most routes enforce this in guard middleware ahead of the controller; the self-scoped ones (create a workspace, list your own, leave, accept or decline an invite, read your own invites or your own authority) carry no guard middleware and authorize inside the controller instead. The one endpoint that does not require a token at all is the single-workspace read, GET /workspaces/:id — it takes an optional token and returns the workspace when visibility permits. Everything else requires a signed-in user, and whether the call then succeeds depends on their standing:
The authority read is the one read visibility does not gate.
GET /workspaces/:id/authority/me is mounted with authentication only — any signed-in user may call it for any workspace id. It returns 404 only when the workspace does not exist; a signed-in stranger gets 200 with reasons: [] and empty capabilities / permissions, which is the correct “you have no standing here” answer for a UI gate. The consequence is that the route confirms a workspace exists to any signed-in user.Its sibling read, one member’s standing (GET /workspaces/:id/members/:userId), is visibility-gated and deliberately 404s instead, so it never leaks existence. Do not assume the two behave alike.What actually needs a service key: acting on behalf of a different user. Several endpoints accept an actor
userId so a service/master key can perform an action as some named user. That parameter is node SDK only — @sublay/core and @sublay/js never expose it and strip it if it is smuggled in, because a client’s actor is always the token’s own user. This is the only difference in reach between the client SDKs and the node SDK for this bundle; it is not a restriction on which operations a client may perform.Note that a target-identifying userId is a different thing and is fully available on the client: the invitee userId on useCreateWorkspaceInvite, and the targetUserId path param on the member hooks.
