workspaces module in @sublay/js is the client-side surface of the Workspaces bundle. It covers all 21 endpoints — workspace CRUD, membership management, the full invitation lifecycle, and the authority read — so a members-management UI needs no server-side code.
The actor is always derived from the bearer token. No actor userId is ever sent from the client, and the wrappers strip one if a caller smuggles it in. Acting on behalf of a different user is the node SDK service-key capability, and it is the only difference in reach between the two SDKs for this bundle — not a restriction on which operations a client may perform.
Requires the
workspaces bundle. See Bundles to install it.fetchWorkspace is the one read that also works without a token, returning the workspace when visibility permits.) Read the concepts first: capabilities vs permissions, rank, reach & ownership, and the identity-matched, no-token invite model.
Workspace lifecycle
createWorkspace
The bearer-token user becomes the owner. Root creation requires a verified email; child creation requirescreate-sub-workspace on the parent.
string
required
Display name (1–100 characters).
object
Opaque developer data.
string
Parent for child creation.
fetchWorkspace
string
required
string
memberCount adds the direct member count.fetchManyWorkspaces
Lists the bearer-token user’s direct-membership + owned workspaces (paginated). Self-service.number
number
string
updateWorkspace
Editsname/metadata. Requires the edit-workspace capability (or ownership). Does not flip the inherit flag.
string
required
string
object
updateWorkspaceInheritFlag
Flips whether authority held on an ancestor reaches into this node. Owner-only in both directions (own owner or an ancestor owner) — not a capability. Blocked with403 workspace/inherit-enforced when the project enforces its default.
string
required
boolean
required
true = open/door, false = sealed/wall.deleteWorkspace
Owner-only (own owner or an ancestor owner). Cascades the whole subtree transactionally and firesworkspace.deleted per deleted workspace — and no workspace.member.removed events for the memberships it tears down (see Webhooks).
string
required
transferWorkspaceOwnership
Owner-only (own owner or an ancestor owner).newOwnerId addresses the target — any verified user in the tenant, who need not already be a member.
string
required
string
required
The new owner (any verified user).
string
"demote" or "remove".number
On demote; defaults to
0.string[]
Membership
fetchWorkspaceMembers
The unified roster —WorkspaceRosterResponse, or WorkspaceRosterCountsResponse with countOnly. Never paginated. Requires roster visibility — any relation to the workspace.
string
required
string
ancestorOwners, reachHolders, descendants.boolean
fetchWorkspaceMemberStanding
One user’s resolved standing, addressed by user id — works for any relation, including users with no direct member row. Requires roster visibility — any relation to the workspace.string
required
string
required
The target whose standing to read (path param, not an actor).
WorkspaceMemberStanding. On its user field only id is guaranteed (WorkspaceStandingUser): the server returns the full user record when the user row still exists and falls back to { id } alone when it does not — a deleted user with a lingering membership row is a reachable case. Read the rest defensively (standing.user.username ?? "Deleted user").
reasons is an array of structured { type, viaWorkspaceId? } entries — the same shape a roster entry’s reasons carries — so you learn which ancestor grants an ancestor-owner / reach-holder standing.
updateWorkspaceMember
Two capability tiers: powerful fields (capabilities, permissions, rank) need edit-member-access plus the rank rule and no-escalation; cosmetic fields (title, metadata) need only edit-member-profile, and a member editing their own title needs nothing.
string
required
string
required
The member being edited (path param, not an actor).
string[]
string[]
number
string
object
removeWorkspaceMember
Removes a member from this node only. Requiresremove-member and is rank-bounded; sole-owner protected.
string
required
string
required
The member to remove (path param, not an actor).
removeWorkspaceMemberFromSubtree
Removes the target’s direct memberships on this workspace and every descendant you can reach. Requiresremove-member, rank-bounded per node. Blocks with 409 workspace/owns-descendants (and reports them) if the target owns any descendant workspace.
string
required
string
required
The user to offboard (path param, not an actor).
leaveWorkspace
Self-service — removes the bearer-token user’s own direct membership on this node only. No capability required. An owner cannot leave (409 workspace/sole-owner); transfer or delete first.
string
required
Invitations
createWorkspaceInvite
Requires theinvite capability (or ownership); the inviter is the bearer-token user. Address the invitee by exactly one of email, userId, or username — here userId/username address the invite target, not an actor. Email matching against existing accounts is case-insensitive.
string
required
string
One of
email/userId/username.string
Invite target user id.
string
Invite target username.
string[]
string[]
number
required
string
fetchWorkspaceInvites
The workspace’s outbox — the live pending invites it has issued (status='pending' AND expiresAt > now). Requires the invite capability (or ownership). Always returned in full; deliberately unpaginated.
string
required
revokeWorkspaceInvite
Marks a pending invitationrevoked. Requires the invite capability (or ownership). Note the verb-suffixed POST .../revoke route — this is not a DELETE.
string
required
string
required
resendWorkspaceInvite
Refreshes a pending invite — resets a 14-day expiry and resends the email — valid even on one already pastexpiresAt. Terminal invites → 409. Requires the invite capability (or ownership). Also a verb-suffixed POST .../resend. Returns the refreshed WorkspaceInvitation, carrying the new expiresAt. Like createWorkspaceInvite, it requires workspaces.inviteAcceptUrl on the project → otherwise 409 workspace/missing-invite-accept-url with expiresAt untouched.
string
required
string
required
fetchMyWorkspaceInvites
The caller’s inbox — the bearer-token user’s live pending invites across every workspace, matched byuserId from the token. Self-service, and surfacing is not verification-gated — a user sees pending invites right after signup (which nudges them to verify). The verified check applies when they act on one (accept or decline).
acceptWorkspaceInvite
Self-service — identity-matched to the bearer-token user, and a verified email is required. TheinviteId is non-secret. Idempotent when already a member/owner.
string
required
declineWorkspaceInvite
Self-service — identity-matched, and a verified email is required (the same gate as accept, so an unverified squatter on someone else’s address cannot burn their invite). Returns403 workspace/email-not-verified otherwise.
string
required
Authority-as-a-service
fetchWorkspaceAuthority
The bearer-token user’s resolved standing on a workspace —{ reasons, capabilities, permissions, rank }. Self-service, so nothing is fenced here. Drive your UI off this rather than guessing: a permission check is a one-line .includes() on the result.
reasons is an array of structured { type, viaWorkspaceId? } entries (viaWorkspaceId on ancestor-owner / reach-holder only). capabilities is the fully-resolved set and view is implied by every other capability — any user with standing carries it, while a stranger gets reasons: [] and an empty set.
string
required
permissions — there is deliberately no ?permission= check or can() helper.
