9 min read

Rekey 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 roleOrganization role
AnswersIs this person staff of your whole app?What are they inside this organization?
FieldEndUser.roleOrganizationMembership.role
ScopeOne value per end-user, app-wideOne value per (organization, end-user)
A person in two organizationsholds one valueholds two independent values
Enforced by RekeyNo. It is data your app interprets.Yes, on the tier.
Assigned byOperators, in the panel or over the APIOrg 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. support sees a debug panel, moderator can 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 buyer or seller, a clinic app where an account is patient or clinician.
  • 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

If the same person could reasonably deserve two different answers in two different teams, you want an organization role. Otherwise you want an application role.

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.

The Roles tab in the Rekey panel, showing a comparison table of application versus organization roles above the application-role catalog.
Users → Roles. The comparison is stated once, then each catalog follows.

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

The organization roles table listing built-in OWNER, ADMIN and MEMBER roles alongside custom client, content-manager and reviewer roles, each showing its tier.
A catalog for an agency CMS. The tier column is what Rekey enforces; the role column is your product's vocabulary.

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.

The new organization role dialog with a name field, a tier select showing MEMBER, ADMIN and OWNER, and a description field.
Pick the lowest tier that covers the job. Most custom roles belong on MEMBER.

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.

The organization roles section on an application with organizations disabled, showing the three built-in roles and a banner pointing to the Authentication tab.
Organizations off: built-ins visible, nothing to create yet, and the banner says where to turn it on.

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 reassignTo is 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.

Two role systems, and which one you want | Rekey