Docs / API and agents

Organizations and memberships

One person can belong to several organizations with a separate role in each; switching, invitations, leaving and the single sign-on rule.

A person (a User) belongs to organizations through memberships. Each membership has its own role and permission overrides, so someone can be the Owner of their own customer business and a Buyer in another. User.orgId, role and permissions remain as the pointer to the active membership (the organization the person is working in); the session carries the active organization.

How a request is authorized#

The session guard reads the membership of the session's active organization on every request. No active membership means 401 for that session (it stops at once, whatever the cookie says); the role and permission overrides are the membership's, so a change applies without a new sign-in. Token claims only say who and where. Sessions of the same person in their other organizations are not affected. Staff sessions and support impersonation keep their own gates and are not membership based.

Routes#

Method and pathWhat
GET /account/membershipsthe caller's active memberships, pending invitations, canSwitch, and boundTo when single sign-on pins the account
POST /account/memberships/switch {organizationId}make another active membership the working one; re-issues the session. 404 when the caller is not an active member
POST /account/memberships/:id/acceptaccept an invitation; adds the organization next to the existing ones (the first one becomes active)
POST /account/memberships/:id/declinedecline an invitation
POST /account/memberships/leave {organizationId}leave an organization. 409 for the organization's recorded Owner (transfer first) and for memberships managed by an identity provider

Ownership#

Each organization has one Owner of record (Organization.ownerUserId). The Owner cannot be removed, left out by leaving, deactivated or have their role or permissions changed (409 "Transfer ownership first"); only an identity provider removing its own member overrides that, and then staff reassign. A pre-provisioned Owner who never signed in counts as gone (staff may reassign), and an SSO member whose identity provider maps a group to vendor-owner becomes the Owner in that case too (how roles come from the identity provider).

Method and pathWhoWhat
GET /account/memberships/ownershipany memberisOwner and, for the Owner, the active members ownership could go to
POST /account/memberships/transfer-ownership {toUserId, reason, confirm: true}the Ownerhands ownership to another active member (vendor Owners are ORG_ADMIN, customer Owners hold the Administrator set); the former Owner stays a member. 400 without a reason or the confirmation, 403 for a non-owner, 409 when the person is not an active member
POST /platform/organizations/:id/ownership {toUserId, reason, confirm: true}staffthe same, only when the Owner is gone (removed, deactivated or never set); 409 while they are an active member

| GET /platform/organizations/:id/members | staff with staff.ownership.reassign | active members with masked emails, role, SSO-managed, isOwner, eligible; audited as org.members_viewed (count only) |

The staff reassign endpoint needs the permission staff.ownership.reassign (high risk), and is the Change owner button on the SSO tab of the organization (/platform/organizations/[id]?tab=sso). An Owner can promote or demote any other member in the Team tab of Settings (/settings?tab=team) (the Owner themselves cannot be demoted by anyone, only transferred away); for SSO-managed members the identity provider's role overwrites a manual change at the next sign-in when it names a fitting role.

org.ownership_transferred is audited with the previous and new Owner ids, who did it (owner or staff) and the reason (free text, kept so the next administrator can see why).

Invitations to an existing person#

POST /admin/users with an email that already has an account no longer fails: it creates a pending membership (INVITED) and queues the email. Nothing moves until the invitee accepts from their own session. Responses never reveal the invitee's account id (the membership id is returned).

Single sign-on binds an account to one organization#

Kind of sign-inMemberships
Google (or the person's own email)one or many: create businesses, accept invitations, switch, leave
Company SSO through a vendor's identity provider, or staff through the staff organizationexactly that organization, controlled by the identity provider and WorkOS

An SSO-bound account cannot switch away, create a business, accept an invitation into another organization (409 sso_managed, with the reason), or leave on its own. Removal at the identity provider removes access (see the webhook doc). A Google user whose account already belongs to organizations of their own is refused when they sign in through a company SSO (they use a work account for the company), and an organization that is linked to SSO refuses invitations of existing Google users: they must sign in through the company SSO.

Audit#

org.member_joined, membership.invited, membership.role_changed and membership.removed are written in the same transaction as the change and carry ids, roles and the way it happened, never names or emails.