Two role systems, and which one you want
Blog GuideRekey has two role systems. They look alike, they are spelled alike in your code, and choosing the wrong one does not throw: it returns a plausible value forever. This guide is about telling them apart, then getting the most out of each.
The two systems
| Application role | Organization role | |
|---|---|---|
| Answers | Is this person staff of your whole app? | What are they inside this organization? |
| Field | EndUser.role | OrganizationMembership.role |
| Scope | One value per end-user, app-wide | One value per (organization, end-user) |
| A person in two organizations | holds one value | holds two independent values |
| Enforced by Rekey | No. It is data your app interprets. | Yes, on the tier. |
| Assigned by | Operators, in the panel or over the API | Org owners and admins, from your app |
The mistake to avoid
if (user.role === "admin") after an organization switch reads the application role, and returns the same answer in every organization that user belongs to. For the organization-scoped answer, read activeOrganizationBaseRole from /me.Application roles, and when they fit
An application role is one value per end-user, for your whole Application. It is a label you stamp on a person, and Rekey never acts on it: no endpoint changes behaviour because someone is admin rather than user. It is stored, validated against a catalog so you cannot typo it, returned on /me, and that is the whole contract.
That makes it the right tool when the answer does not depend on which team the person is looking at:
- Your own staff.
supportsees a debug panel,moderatorcan hide content anywhere in the product. Being support is not something you are inside one customer's workspace and not another. - Programme membership.
beta,design-partner,early-access. A gate on the person, not on a team. - Account type, when it is genuinely per person. A marketplace where someone signs up as
buyerorseller, a clinic app where an account ispatientorclinician. - A product with no teams at all. If you never turn organizations on, this is your only role system, and it is enough.
It is the wrong tool the moment the answer is “it depends which team”. An agency contractor who edits for one client and only reviews for another cannot be described by a single value, and forcing it produces the bug where granting someone access to one workspace quietly grants it everywhere.
Only operators assign them
There is no SDK call that writes EndUser.role, and PATCH /users/me refuses a body naming it rather than ignoring it. That is deliberate: the field is frequently used as a permission input by the surrounding app, so a self-service write would be a privilege-escalation endpoint. Operators set it in the panel or through the tenant API.
Every sign-up receives the role marked default in the catalog, which is user until you change it.
Organization roles, and when you need them
An organization role is per (organization, end-user), so the same person holds an independent value in each team they belong to. It shipped with three names, OWNER, ADMIN and MEMBER, which are the right shape for a workspace and the wrong words for most products. You can now define your own per Application:
{ "name": "content-manager", "baseRole": "MEMBER", "description": "Drafts and edits content" }Every role carries a tier, one of OWNER, ADMIN or MEMBER. Rekey gates on the tier and never on the name. A content-manager on tier MEMBER can do exactly what MEMBER can, no more: the member-management ladder, the last-owner guard and organization-scoped billing writes all read the tier. What content-manager means beyond that is for your application to decide.
Three built-ins are seeded on every Application and cannot be renamed, re-tiered or deleted. That is what keeps memberships created before you had a catalog resolving to the authority they always had.
Which one, in one line
Configuring both
Both catalogs live on one page, Application → Users → Roles. They used to sit on the two pages they govern, which put the word “roles” in two places meaning different things. Side by side, the distinction is the first thing on the page.

Application roles take a name, an optional description, and a default flag. Organization roles take a name, a tier, and an optional description.

Names are lowercase letters, digits, hyphens and underscores. For organization roles, OWNER, ADMIN and MEMBER are reserved, so you cannot mint a lookalike that a reader would mistake for the real thing.

Organization roles need organizations switched on for the Application. With the feature off you still see the three built-ins, read-only, and a prompt pointing at the setting.

Who assigns what
Authoring a catalog and handing roles out are different acts with different credentials, and for organization roles the split is the point.
- Defining either catalog is operator-only, with your tenant session. Panel, REST, or MCP.
- Assigning an application role is also operator-only, for the escalation reason above.
- Assigning an organization role belongs to the organization. An OWNER or ADMIN does it from your app using their own end-user session, with no operator in the loop and no support ticket.
// The assignable names, for a role picker.
const roles = await rekey.organizations.listRoles(accessToken);
// [{ name: "OWNER", baseRole: "OWNER", isBuiltIn: true, ... },
// { name: "content-manager", baseRole: "MEMBER", isBuiltIn: false, ... }]
// An org OWNER or ADMIN assigns one, with their own session.
await rekey.organizations.setMemberRole(accessToken, orgId, targetUserId, {
role: "content-manager",
});
// Invitations take a name too. Omit it to use the Application's default role.
await rekey.organizations.invite(accessToken, orgId, {
email: "[email protected]",
});The ladder applies to tiers, so an ADMIN-tier member can hand out ADMIN-tier and MEMBER-tier roles but not OWNER-tier ones, whatever those roles are called. End-users can read the catalog but never write it, so no member can invent a name that outranks their own.
Gating your own features
GET /api/v1/auth/me returns both systems at once, which is the clearest way to see that they are different:
{
"role": "user", // application-wide
"activeOrganizationId": "org_...",
"activeOrganizationRole": "content-manager", // this organization
"activeOrganizationBaseRole": "MEMBER" // gate on this
}Read role for app-wide questions. Read activeOrganizationRole to branch on your own team vocabulary, and activeOrganizationBaseRole when the question is about authority. The two organization fields are null when the session has no active organization, and also when membership lapsed since the token was minted: a stale organization claim degrades to no organization, it never grants access.
Over MCP
The organization catalog is available to an agent through the operator MCP server: list_organization_roles to read, and create_organization_role, update_organization_role and delete_organization_role behind write scope. Ask on an Application that has organizations switched off and the read tool says so and names the tool that turns them on, rather than returning an empty list that reads like a missing feature.
There is deliberately no MCP tool for assigning an organization role to a member, because that is not an operator action.
What will refuse you, and why
Each guard below stops a catalog edit from leaving something in a state it cannot get itself out of.
- Built-in organization roles are permanent. OWNER, ADMIN and MEMBER cannot be renamed, re-tiered or deleted. Every membership that predates your catalog depends on them.
- Names are immutable. Rows store the name by value, so a rename would orphan every one holding it. Delete with
reassignTois the rename path, and it moves holders and drops the role in one transaction. - A role in use will not just vanish. Deleting one that end-users, memberships or pending invitations still reference is refused unless you name where those holders land. Invitations count, because one stores a name and is redeemed later.
- Owner-tier roles cannot be quietly demoted. Reassigning an owner-tier role to a lower tier, or re-tiering one off OWNER, is refused when any organization's only owner holds it. Neither path touches a membership row, so the usual last-owner guard never runs, and without these two checks an organization would be left with nobody able to manage members or authorize a charge.
Where next
The reference material lives in the docs: the roles section of the auth guide covers both systems and the endpoints, and the errors reference lists every refusal above with the code it returns. If you have not decided whether you need organizations at all, start there instead: a per-user product usually does not, and then application roles are the only system you need.
