Building with the SDK
Organizations
Teams, roles, and the session acting as one.
An organization is a group of your users. A session can act as an organization, and while it does, the tokens it holds say so.
The active organization
const { organizations, setActive } = useOrganizationList();
const { organization } = useOrganization();
await setActive('org_...'); // null returns to personal scope
Switching rotates the session’s tokens, because the organization is a claim inside them. An app holding the old access token would keep acting as the old organization until it expired, which is why the rotation is not optional.
Roles and permissions
A member holds a role, and a role grants permissions. Your backend receives both in the token:
{
"sub": "user_...",
"org_id": "org_...",
"org_role": "admin",
"org_permissions": ["org:members:read", "org:members:manage"]
}
Permissions are sent alongside the role rather than left for you to derive from it. The point of a permission model is that the mapping can change without every consumer reimplementing it.
Check them in the UI:
<Protect permission="org:members:manage">
<InviteForm />
</Protect>
And on your server, which is where it counts:
const auth = await getAuth(request, { environmentId });
if (!auth.has({ permission: 'org:members:manage' })) {
return new Response('Forbidden', { status: 403 });
}
Managing members
await zevauth.organizations.addMember({ email: 'colleague@example.com' });
await zevauth.organizations.setMemberRole({ userId, role: 'admin' });
await zevauth.organizations.removeMember({ userId });
Adding somebody requires them to have an account with you already.
Two rules apply underneath:
Anybody may remove themselves. An organization you cannot leave is a trap, so leaving needs no permission.
The last owner cannot be removed by anyone, including themselves. Otherwise the organization would be left with nobody able to administer it.
In the UI
<OrganizationSwitcher />
<OrganizationProfile />
<OrganizationProfile> decides what to show from the token’s claims, so a
control can briefly be visible to somebody who was just demoted. Pressing it
produces a refusal, not a change. Hiding the button is a courtesy; refusing the
request is the security.
Changing the organization
Renaming an organization or changing its picture needs the org:manage
permission — a different one from org:members:manage, because plenty of teams
want people who can invite colleagues without being able to rename the company
on every screen in the product.
await client.organizations.update({ name: 'Acme Corp' });
The picture works the same way as a user’s avatar: upload, then save.
const url = await client.organizations.uploadImage({ file });
await client.organizations.update({ imageUrl: url });
imageUrl: null removes it. Omitting the field leaves it alone.
The permission is checked against the caller’s live membership, not against
the org_permissions claim in their token. Claims are a snapshot from when the
token was minted, so somebody demoted a minute ago still carries the old ones
until their next refresh. That is fine for deciding what to draw and not enough
to decide what to allow.
Updated at, Friday, August 28, 2026