Members and teams
Members and teams
Section titled “Members and teams”If you’re an org admin landing here to invite a teammate, demote an outgoing engineer, or scope a project to a single squad, this is the page. It lives at /admin/ui/orgs/<your-slug>/members and is reached from the org-detail sidebar (“Members”) or the breadcrumb on any team-detail screen.
The page header tells you which tier you’re on (e.g. Tier: team) and your seat consumption against the tier cap (Seats: 4 / 10). If Seats is missing, you’re on a tier without a seat cap or the directory hasn’t initialised. Both numbers update on every member-status change.
Three stacked sections follow:
- Members — every directory row plus the synthetic admin-token bearer.
- Pending invites — outstanding email invitations and the form that creates them.
- Permissions matrix — the (role × capability) grid showing what each role can do at the current tier, with click-to-cycle overrides.
A separate per-team page lives at /admin/ui/orgs/<slug>/teams/<team-id> and is where you manage team membership and see which projects belong to a team.
Members table
Section titled “Members table”Columns: email, role, status, last sign-in, seat, teams, source, plus a per-row actions column.
- role —
owner,admin,member, orviewer. Owners and synthetic platform-admin rows render as a read-only badge; everyone else gets a dropdown that POSTs to the dashboard, which forwards to the JSONPATCH .../members/:idendpoint. The “Save” button stays disabled in two cases — the tooltip explains which:- On your own row: “You cannot change your own role here. Use Transfer ownership to step down.”
- On the last admin-tier member: “Last admin — promote another member first, then retry.”
- status —
active,suspended, orremoved. Removed rows show “removed — re-invite to restore” in the actions column instead of buttons; reactivation goes through a fresh invite cycle, not a click. - seat —
Yif the row counts toward the tier’s seat cap (active or suspended),Notherwise. The synthetic admin-token row is alwaysN. - teams — one badge per team membership, each linking to that team’s detail page. Org owners and admins inherit team-admin everywhere via the canDo inheritance rule, so their badge cell shows
—; only explicit memberships render here. - source —
invitefor normal directory rows,admin_tokenfor the synthetic platform bearer.
Per-row actions are Suspend / Reactivate, Force logout, and Remove. All three confirm before firing:
- Suspend: “Suspend this member? Their active sessions will be revoked and they will not be able to sign in until reactivated.”
- Force logout: “Sign this member out of every browser and device?” — disabled on your own row with tooltip “Use the account page to sign yourself out.”
- Remove: “Remove this member from the org? Their active sessions will be revoked and the seat will be freed.”
The platform-admin synthetic row shows “platform admin” in the actions column and is inert — there’s no directory entry to mutate.
Transfer ownership
Section titled “Transfer ownership”Visible only to the current owner, under a “Transfer ownership” disclosure below the members table. Pick a non-owner directory row from the dropdown (”— pick a member —”), click “Transfer ownership”, and confirm: “Transfer ownership of this tenant? You will be demoted to admin.” The recipient becomes the new owner; you become an admin. The transfer is atomic — no point in time has two owners — and it’s audited. If there are no eligible recipients yet, the panel renders “No other active members yet. Invite a teammate (admin or member) first, then come back here.”
Pending invites
Section titled “Pending invites”The form takes an Email and a Role (member or admin) and POSTs to the dashboard’s invite route, which calls Stytch to deliver the email and creates the pending row. On success you see the flash Invite sent to <email>; on failure the form re-renders with your input intact and the error inline.
If the org is not yet bound to a Stytch organization, the form is replaced by the hint: “This org is not bound to a Stytch organization yet. Bind it under Identity & SSO first, then return here to invite members.” The Identity & SSO link jumps back to the org-detail panel that handles binding.
The list below the form has columns email, role, expires, invited by, and a per-row Revoke button. Revoke confirms with “Revoke this pending invite? The recipient will no longer be able to accept it.” Successful revokes flash Invite revoked; failures flash Revoke failed: <error> and leave the row in place.
Permissions matrix
Section titled “Permissions matrix”The “Permissions matrix” card has roles as rows and capabilities as columns. Cells render one of:
- allow / deny — the default for the role at this tier.
- allow* / deny* — an override is in force; the asterisk tooltips read “override active”.
- — — the capability is tier-gated. Tooltip: “Capability not available on this tier”.
- always allow — the owner row for any capability the tier unlocks. Owners ignore overrides; the CHECK constraint forbids storing them.
Click any editable cell to cycle through: default → override-allow → override-deny → default. The page re-renders with a flash on success or “Permission update failed: <error>” on failure. The matrix-level help text at the top of the card states the rules verbatim, including the dashed-cell meaning.
The owner row is read-only — owners always have every capability the tier unlocks. Override clicks against tier-locked cells are no-ops.
Team detail (/teams/<team-id>)
Section titled “Team detail (/teams/<team-id>)”From any team badge you land on the per-team page. The header shows the team’s display name, slug (in <code>), and creation date. Three stacked cards follow:
- Projects (count) — which projects belong to this team, with a per-row “Edit policy” link to the project’s policy override editor. The attach/detach surface lives on the projects page, not here; the card includes the hint “Attach projects to this team from the projects page.”
- Members (count) — columns
email,org role,team role(dropdown:admin,member,viewer),status,actions. The remove button confirms with “Remove this member from the team?” and flashesMember removed from this teamon success. Role changes flashTeam role updated to <role>. - Add a member to this team — picks from org members not already on this team. The dropdown shows
email (org role); pick a team role; submit. If every org member is already on the team the form is replaced by “Every org member is already on this team.”
Org owners and admins implicitly act as team admins on every team, so team-scoped roles only matter for org members and viewers. That distinction is restated in the add-member intro: “Pick an org member and the role they should have on this team. Org owners and admins implicitly act as team admins on every team; team-scoped roles only matter for org members and viewers.”
Creating and renaming teams
Section titled “Creating and renaming teams”Team create and rename are admin-API operations today; there is no in-dashboard form. To create a team, POST /api/v1/admin/orgs/<slug>/teams with {"slug": "platform-sec", "name": "Platform Security"}. To rename, PATCH /api/v1/admin/orgs/<slug>/teams/<id> with a new name. Both require the projects:w capability. Errors come back as JSON: a duplicate slug returns 409 with "Team with that slug already exists in this org".
Last-admin guard
Section titled “Last-admin guard”The dashboard mirrors a server-authoritative rule: an org must always have at least one admin-tier (owner or admin) member. When a single member holds the only admin-tier seat, their role dropdown and Remove button are disabled with the tooltip “Last admin — promote another member first, then retry.” Defense in depth — the JSON members:w endpoint enforces the same rule, so a hand-crafted POST hits the same wall.
Related
Section titled “Related”- Tenants, projects, and keys — the tenant/project/team model the page edits.
- Sign-in and access control — what a member can actually do once they’re in.
- Session duration and admin controls — what Force logout revokes and how sessions are scoped.