Skip to main content
Every action inside an organization is checked against your role in that organization. ModuleX keeps the model deliberately small: there are two live roles, owner and admin, and the role you hold is verified on every request, not just at sign-in. This page is the exhaustive reference for that model: the live roles, the gate each kind of action requires, the exact errors you get when a check fails, and the edge cases that come from a role being retired mid-life. For the conceptual tour of tenancy and how the role attaches to your membership, see Organizations, roles & membership.
Roles are resolved per request from your membership in the organization named by the 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

Assigned to whoever creates the organization. An owner can do everything an admin can, plus a small set of owner-only actions such as removing a member. Owner is the only role that cannot be assigned later.

Admin

Full working access: create, edit, run, and deploy workflows, use the AI Composer and the Assistant, manage knowledge and credentials, schedule runs, and invite teammates. Everyone invited to an organization joins as an admin.
A few rules govern how those roles are set:
  • 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, so owner is 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.
There is a separate, account-level role on the underlying user record (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.
There is no member role you can assign. If you see member referenced in an old export, a legacy membership row, or a third-party type definition, treat it as deprecated. New invitations and role changes accept only admin (owner is creation-only), and the role checks below recognize only owner and admin.
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:
A membership created before 2026-06-20 can still hold the literal value 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.
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 (Socket.io) collaboration server still names 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:
1

Membership is verified

ModuleX reads the 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.
2

The role gate is applied

The route then requires either owner/admin or owner-only, depending on the action. If your resolved role is not high enough, the request is rejected with a 403 before any side effect occurs.
There are three role gates in the backend. Almost every product action uses the admin gate.
any member of the org
Requires only that you are an active member of the organization named in X-Organization-ID. Used for personal, non-privileged actions such as leaving an organization. Resolved by the organization_membership_required dependency.
owner or admin
Requires your role to be 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
Requires your role to be exactly 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.
The Composer, the Assistant, and schedules all require owner or admin. Some source comments describe the chat surfaces as “available to every member” — that comment is inaccurate. As shipped, those surfaces are admin-gated and a legacy member row is rejected with a 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 set owner through 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.
These guardrails mean ownership is effectively fixed at creation, and the role surface that an admin can actually move is narrow.

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.
Membership and invitation methods have partial SDK coverage. Listing your organizations is available in both SDKs; invitations and role changes may be REST-only on a given SDK version. Check the SDK ⇄ API parity matrix before relying on a specific method, and fall back to a direct REST call when a method is absent.

Inviting teammates

Invited teammates always join as admin. Two things gate an invitation:
1

Your role must be owner or admin

The invite, cancel, and resend operations are admin-gated. A legacy member row cannot invite.
2

Your plan must allow teammates

Inviting teammates is a paid-plan entitlement. On a Free plan, the invite is rejected with a 403 carrying the code TEAMMATES_NOT_ALLOWED, prompting an upgrade. The same entitlement is re-checked when the invitee accepts. See Plans & pricing.
Accepting or rejecting an invitation is done by the invited person and requires only that they are signed in; it is not gated on an organization role, because they are not yet a member. Owners cannot leave their own organization, so the last administrative tie to an organization is always an owner.

Errors

Role and membership failures come back as standard FastAPI error envelopes — a JSON object with a detail 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

The role-update route validates the target role against ^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.
Role changes are admin-gated, but an admin acting on another admin is rejected. This keeps admins from removing each other’s access; only the broader account/ownership context resolves such conflicts. You also cannot change your own role.
An API key can be locked to a single organization. If a request uses that key with a different 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.
The realtime collaboration server caches your organization membership and role for a short window (up to about five minutes). A role change made over REST is enforced immediately on REST calls, but a live Socket.io session may not reflect the new role until the cache expires or the connection is re-established. Reconnect to force an immediate re-check. See Presence, locks & versioning.
On the REST surface, your role is resolved fresh for each call from your current membership. Removing a member or changing a role takes effect on their next REST request, not on a session timer.

Organizations, roles & membership

The conceptual model for tenancy, membership, and how your role attaches to an organization.

Org context & X-Organization-ID

How the organization header is validated and how membership is checked on every request.

Auth model: JWT vs API key

The two authentication paths and how API-key organization scoping interacts with roles.

Permissions & limits (Assistant)

Who can use the Assistant and the usage limits that apply on top of the role gate.