X-Organization-ID header. The same role check runs whether the request comes from the app (a Clerk sign-in) or from code (an mx_live_ API key). See Org context & X-Organization-ID for how that header is validated.The live roles
An organization role is one of exactly two values.Owner
Admin
- The role lives on your organization membership record, with a column default of
admin. - Owner is assignable only at organization-creation time. The person who creates the organization becomes its owner; no API can promote another member to owner afterward. Role-changing requests are validated against the pattern
^admin$at the edge, soowneris rejected as an input value. - An organization can have more than one owner only through ownership transfer mechanics that are not exposed as a public role-update operation today. Treat owner as fixed at creation unless you remove and recreate the organization.
USER versus SUPER_ADMIN) used only by ModuleX platform staff. It is distinct from your organization role and is not something customers assign. This page covers organization roles only.The member role is retired
ModuleX previously had a third role,member, that granted read-only access. It was retired on 2026-06-20. The live roles are owner and admin only.
Because the role was retired in place rather than fully purged, a few legacy mentions of member can still surface. None of them let you grant the role through a supported path, but they are worth knowing about:
Old membership rows may still carry member
Old membership rows may still carry member
member in the database, and an unfiltered organization listing can still return such rows. The write gates below do not treat member as owner or admin, so a legacy member row has neither admin nor owner permissions on the gated surfaces.The membership-list filter still accepts member as a query value
The membership-list filter still accepts member as a query value
GET /auth/me/organizations accepts an optional role filter whose documented values are member, admin, and owner. Passing member filters for legacy rows; it does not create or imply a usable role. Prefer filtering by owner or admin.The realtime layer still lists member in its role union
The realtime layer still lists member in its role union
member in its internal role union and would grant such a connection read-only canvas access. This only matters for legacy rows; admins and owners get full read and write. See the caveat in Presence, locks & versioning.How a role is enforced
Every org-scoped route runs the same two-stage check before it does any work:Membership is verified
X-Organization-ID header, confirms you are an active member of that organization, and resolves your role in it. A missing header, an inactive account, or a non-membership stops the request here. See Org context & X-Organization-ID for the membership errors.The role gate is applied
403 before any side effect occurs.X-Organization-ID. Used for personal, non-privileged actions such as leaving an organization. Resolved by the organization_membership_required dependency.owner or admin. This is the gate on the working surfaces: workflows, the AI Composer, the Assistant, schedules, knowledge bases, and credentials. Resolved by the organization_admin_required dependency.owner. Reserved for a small set of organization-administration actions, such as removing another member. Resolved by the organization_owner_required dependency.What each role can do
The table below maps every privileged surface to the role it requires. “Admin” means owner or admin (owners always satisfy the admin gate); “Owner” means owner only.403.Admin can change roles, but not create owners
Promoting or demoting a teammate is an admin action with built-in guardrails. When you call the role-update operation:- The only accepted target role is
admin(the request body is validated against^admin$). You cannot setownerthrough this route. - You cannot change your own role.
- You cannot change the role of an owner.
- An admin cannot change the role of another admin; that is an owner-level conflict and is rejected.
Managing roles and membership
Role and membership management is exposed over REST and works the same from the app or from code. Authenticate every request with your API key as a bearer token plus the organization header, exactly as elsewhere in the API.Inviting teammates
Invited teammates always join as admin. Two things gate an invitation:Your role must be owner or admin
Your plan must allow teammates
403 carrying the code TEAMMATES_NOT_ALLOWED, prompting an upgrade. The same entitlement is re-checked when the invitee accepts. See Plans & pricing.Errors
Role and membership failures come back as standard FastAPI error envelopes — a JSON object with adetail field. (The flat billing DenialEnvelope documented on Usage gating & limits applies to usage/quota denials on the run, Composer, Assistant, and managed-knowledge surfaces; it is separate from the role errors here. The full taxonomy is on Errors & status codes.)
Edge cases & gotchas
Owner cannot be assigned after creation
Owner cannot be assigned after creation
^admin$, so owner is never an accepted value. The creator is the owner; there is no supported public operation to promote a second member to owner. Plan ownership at the point you create the organization.An admin cannot demote or change another admin
An admin cannot demote or change another admin
API-key org scope is enforced on top of your role
API-key org scope is enforced on top of your role
X-Organization-ID, it is rejected with 403 before the role check even runs. A correctly scoped key still grants only the role you hold in that organization — scoping a key does not elevate it. See Auth model: JWT vs API key.Realtime role changes can lag behind the database
Realtime role changes can lag behind the database
Membership is re-checked on every request, not cached at sign-in
Membership is re-checked on every request, not cached at sign-in