> ## Documentation Index
> Fetch the complete documentation index at: https://docs.modulex.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Organizations, roles & membership

> How tenancy works in ModuleX: the organization is your workspace and billing boundary, the X-Organization-ID header scopes every request, and the two live roles are owner and admin.

export const MediaEmbed = ({id, type = 'screenshot', caption = '', ext, ratio = '16 / 9'}) => {
  const isVideo = type === 'video' || type === 'app_video';
  const resolvedExt = ext || (isVideo ? 'mp4' : type === 'screenshot' ? 'webp' : 'svg');
  const src = 'https://media.modulex.dev/' + id + '.' + resolvedExt;
  const [status, setStatus] = useState('loading');
  const [isDev, setIsDev] = useState(false);
  const [inView, setInView] = useState(false);
  const boxRef = useRef(null);
  useEffect(() => {
    if (typeof window === 'undefined') return;
    const h = window.location.hostname;
    setIsDev(h === 'localhost' || h === '127.0.0.1' || h.endsWith('.mintlify.app'));
  }, []);
  useEffect(() => {
    if (inView) return;
    if (typeof IntersectionObserver === 'undefined') {
      setInView(true);
      return;
    }
    const el = boxRef.current;
    if (!el) return;
    const io = new IntersectionObserver(entries => {
      if (entries.some(e => e.isIntersecting)) {
        setInView(true);
        io.disconnect();
      }
    }, {
      rootMargin: '300px'
    });
    io.observe(el);
    return () => io.disconnect();
  }, [inView]);
  if (status === 'missing') {
    if (!isDev) return null;
    return <div style={{
      display: 'flex',
      flexDirection: 'column',
      justifyContent: 'center',
      gap: '0.4rem',
      padding: '1rem 1.25rem',
      margin: '1.25rem 0',
      width: '100%',
      aspectRatio: ratio,
      boxSizing: 'border-box',
      border: '1px dashed rgba(128,128,128,0.45)',
      borderRadius: '0.75rem',
      background: 'rgba(128,128,128,0.06)',
      color: 'currentColor',
      fontSize: '0.85rem',
      lineHeight: 1.45
    }}>
        <div style={{
      display: 'flex',
      alignItems: 'center',
      gap: '0.5rem',
      opacity: 0.75
    }}>
          <span aria-hidden="true">🎬</span>
          <code style={{
      fontSize: '0.75rem'
    }}>{id}</code>
          <span style={{
      fontSize: '0.65rem',
      textTransform: 'uppercase',
      letterSpacing: '0.04em',
      padding: '0.1rem 0.4rem',
      borderRadius: '0.4rem',
      background: 'rgba(128,128,128,0.18)'
    }}>
            {type}
          </span>
        </div>
        <div style={{
      opacity: 0.9
    }}>{caption || 'Media not uploaded yet.'}</div>
        <div style={{
      fontSize: '0.7rem',
      opacity: 0.5
    }}>
          Upload to R2 as <code>{id}.{resolvedExt}</code> — preview only, hidden in production.
        </div>
      </div>;
  }
  const mediaStyle = {
    display: status === 'loaded' ? 'block' : 'none',
    width: '100%',
    height: 'auto',
    borderRadius: '0.75rem'
  };
  const media = isVideo ? <video src={inView ? src : undefined} autoPlay loop muted playsInline preload="metadata" onLoadedData={() => setStatus('loaded')} onError={() => setStatus('missing')} style={mediaStyle} /> : <img src={inView ? src : undefined} alt={caption} onLoad={() => setStatus('loaded')} onError={() => setStatus('missing')} style={mediaStyle} />;
  return <figure style={{
    margin: '1.25rem 0'
  }}>
      <div ref={boxRef} style={status === 'loaded' ? {
    width: '100%'
  } : {
    width: '100%',
    aspectRatio: ratio,
    borderRadius: '0.75rem',
    background: 'rgba(128,128,128,0.06)'
  }}>
        {media}
      </div>
      {status === 'loaded' && caption ? <figcaption style={{
    marginTop: '0.5rem',
    textAlign: 'center',
    fontSize: '0.85rem',
    opacity: 0.7
  }}>
          {caption}
        </figcaption> : null}
    </figure>;
};

Everything you do in ModuleX happens inside an **organization**. It is your shared workspace and your billing boundary at the same time: your workflows, credentials, knowledge bases, plan, wallet, and credits all belong to one organization, and every run is metered against it.

This page explains the three ideas you need to work confidently across a team: what an organization is, how ModuleX knows which one you are acting in, and the two roles that decide who can do what. For the request-level detail, follow the links to [Org context & X-Organization-ID](/security/org-context) and [Roles & permissions](/security/roles-permissions).

## The three ideas on this page

<CardGroup cols={3}>
  <Card title="Organization" icon="building" href="#the-organization-your-workspace-and-billing-boundary">
    Your workspace and billing boundary. Every resource and every charge belongs to one organization.
  </Card>

  <Card title="Org context" icon="id-card" href="#the-org-context-which-organization-am-i-in">
    The `X-Organization-ID` header tells ModuleX which organization a request runs in.
  </Card>

  <Card title="Roles" icon="user-shield" href="#roles-owner-and-admin">
    Each member is an **owner** or an **admin**. There is no member role.
  </Card>
</CardGroup>

## The organization: your workspace and billing boundary

An organization is a tenant. It is the line ModuleX draws around your work so that your data, your access, and your bill all stay together and stay separate from everyone else's.

<CardGroup cols={2}>
  <Card title="One workspace for your resources" icon="folder-tree">
    Workflows, [credentials](/concepts/credentials-oauth), [knowledge bases](/concepts/knowledge-rag), and chats live inside an organization. Members of that organization share them.
  </Card>

  <Card title="One boundary for billing" icon="coins" href="/concepts/credits-billing">
    The plan, the credit allowance, and the prepaid wallet belong to the organization — not to an individual. Every run is metered against the organization it runs in.
  </Card>
</CardGroup>

You can belong to more than one organization — for example a personal workspace and a team workspace — and you can own up to **five** organizations. Each one is fully separate: switching between them swaps your entire set of resources, plan, and credits.

<Note>
  You never create an account by hand on the API side. ModuleX uses a single identity provider (Clerk) for sign-in, and your ModuleX user and a default personal organization are created automatically the first time you sign in. See [Just-in-time provisioning](#how-you-get-your-first-organization) below.
</Note>

<MediaEmbed id="MX-MEDIA-1150" type="image" caption={"A diagram showing one organization as a boundary that contains a user's resources and billing."} />

## The org context: which organization am I in?

Because you can belong to several organizations, every action has to say which one it applies to. ModuleX calls this the **org context**, and on the API it is carried by one header: **`X-Organization-ID`**.

In the [ModuleX app](/platform/overview) you set the org context with the organization switcher, and the app sends the header for you. When you call the API or use an [SDK](/sdks/overview), you set it yourself.

<MediaEmbed id="MX-MEDIA-1151" type="screenshot" caption={"The organization switcher in the ModuleX app."} />

### Org context in API and SDK calls

Every org-scoped request carries two headers: your API key as a bearer token, and the organization the request runs in. The example below lists the workflows in one organization.

<CodeGroup>
  ```bash cURL theme={null}
  curl https://api.modulex.dev/workflows \
    -H "Authorization: Bearer mx_live_2f9c4a1b8e7d6c5a4b3f2e1d0c9b8a76" \
    -H "X-Organization-ID: 8a1f3c2e-9b4d-4e7a-bc11-7d6e5f4a3b2c"
  ```

  ```python Python theme={null}
  import asyncio
  from modulex import Modulex


  async def main():
      async with Modulex(
          api_key="mx_live_2f9c4a1b8e7d6c5a4b3f2e1d0c9b8a76",
          organization_id="8a1f3c2e-9b4d-4e7a-bc11-7d6e5f4a3b2c",
      ) as client:
          workflows = await client.workflows.list()
          print(workflows)


  asyncio.run(main())
  ```

  ```javascript JavaScript theme={null}
  import { Modulex } from "modulex-js";

  const client = new Modulex({
    apiKey: "mx_live_2f9c4a1b8e7d6c5a4b3f2e1d0c9b8a76",
    organizationId: "8a1f3c2e-9b4d-4e7a-bc11-7d6e5f4a3b2c",
  });

  const workflows = await client.workflows.list();
  console.log(workflows);
  ```
</CodeGroup>

<Note>
  If you omit `X-Organization-ID` on an org-scoped endpoint, the request is rejected with a `400` and the message `X-Organization-ID header is required`. If you are a member of the organization but not allowed to perform the action, you get a `403`. See [Authentication](/api-reference/authentication) for how the two headers fit together and [Errors & status codes](/api-reference/errors) for the full response shapes.
</Note>

You can also set a default organization once on the client and override it per call. The exact header handling, the SDK precedence rules, and how an API key can be scoped to a single organization are covered in [Org context & X-Organization-ID](/security/org-context).

## Roles: owner and admin

Membership in an organization comes with a **role**. ModuleX has exactly two live roles.

<CardGroup cols={2}>
  <Card title="Owner" icon="crown">
    The role assigned to whoever creates the organization. Owners can do everything an admin can, plus owner-only actions such as removing members. An owner cannot leave their own organization.
  </Card>

  <Card title="Admin" icon="user-gear">
    Full working access to the organization: build and run workflows, use the AI Composer and the Assistant, manage credentials and knowledge, and invite teammates. New members join as admins.
  </Card>
</CardGroup>

<Warning>
  There is **no member role**. A `member` role existed in earlier versions of ModuleX and was retired on 2026-06-20. The live roles are **owner** and **admin** only, and the API rejects any attempt to invite or set a role other than `admin`. If you see "member" referenced anywhere, treat it as legacy.
</Warning>

The `owner` role is assigned only at the moment an organization is created; it is not something you set when inviting or updating a teammate. Every invited or updated member is an admin.

### What requires owner or admin

Because the two live roles both have broad working access, most day-to-day actions are available to either role. A few of ModuleX's most capable surfaces require an owner or admin explicitly, and a small set of governance actions are owner-only.

<Accordion title="Actions that require owner or admin">
  The following surfaces require the caller to be an **owner or admin** of the active organization:

  * The [AI Composer](/concepts/ai-composer) (turning plain English into a workflow).
  * The [Assistant](/concepts/assistant) (the agentic chat that calls your tools).
  * [Workflow schedules](/workflow-builder/execution/schedule) and related management.
  * Organization settings, the LLM catalog, and inviting teammates.

  A caller who is authenticated and a member of the organization but lacks the required role receives a `403`.
</Accordion>

<Accordion title="Actions that are owner-only">
  A few governance actions are reserved for the **owner**:

  * Removing another member from the organization.
  * Owners cannot leave their own organization (transfer ownership or remove the organization instead).
</Accordion>

<Note>
  Source-of-truth: this owner/admin model is enforced by the backend on every request. For the per-endpoint permission map — which routes require `organization_admin_required` versus `organization_owner_required` — see [Roles & permissions](/security/roles-permissions). Note that the in-app interface may optimistically show edit controls before the backend confirms your role; the server always enforces the real role on each call.
</Note>

## How you get your first organization

You do not sign up through a ModuleX form. The first time you sign in, ModuleX provisions everything you need automatically — this is **just-in-time (JIT) provisioning**.

<Steps>
  <Step title="You sign in">
    You authenticate through ModuleX's identity provider (Clerk). The backend verifies your token; there is no separate ModuleX password or login endpoint.
  </Step>

  <Step title="Your user and a personal organization are created">
    On your first valid sign-in, ModuleX creates your user record and a personal default organization, and makes you its **owner** — all in one step, so a second concurrent request never creates a duplicate.
  </Step>

  <Step title="Your workspace is seeded">
    The new organization is seeded with sensible defaults so you can start immediately, and you land in it as the active org context.
  </Step>
</Steps>

Your personal organization is named from the first available of your first name, your username, or the part of your email before the `@`. If none is available, it is named `My Workspace`.

<Note>
  JIT provisioning happens for sign-in (your user identity). It does **not** apply to API keys: a key cannot exist before your user does, and creating an API key requires signing in first. See [Quickstart](/get-started/quickstart) to create your first key.
</Note>

## Working as a team

When you are ready to collaborate, you add people to an organization by **inviting** them.

<Steps>
  <Step title="An owner or admin sends an invite">
    Invite a teammate by email. They join as an **admin**. Each invitation is valid for **7 days**.
  </Step>

  <Step title="The invitee accepts">
    Only the invited email address can accept the invitation. Once accepted, they become a member of the organization with the admin role.
  </Step>

  <Step title="They appear in your organization">
    The new admin shares the organization's workflows, credentials, knowledge, and billing. Owners can later remove a member; admins cannot.
  </Step>
</Steps>

<Warning>
  Inviting teammates is a paid capability. On the **Free** plan, sending an invite is declined with a `403` and the reason `TEAMMATES_NOT_ALLOWED`. Upgrade to **Pro** or higher to invite teammates. See [Plans & pricing](/billing/plans) for what each plan includes.
</Warning>

<MediaEmbed id="MX-MEDIA-1152" type="app_video" caption={"Inviting a teammate to an organization and the teammate accepting."} />

## How this connects to the rest of ModuleX

<CardGroup cols={2}>
  <Card title="Org context & X-Organization-ID" icon="id-card" href="/security/org-context">
    The request-level detail: how the org context header is read, how API-key org scope is enforced, and what happens when it is missing.
  </Card>

  <Card title="Roles & permissions" icon="user-shield" href="/security/roles-permissions">
    The per-endpoint permission map — exactly which actions require owner or admin.
  </Card>

  <Card title="Credits & the billing model" icon="coins" href="/concepts/credits-billing">
    How managed usage is metered in credits against the organization, and where the billing gate applies.
  </Card>

  <Card title="Private & org chat" icon="comments" href="/platform/chat/private-org-chat">
    How chats can be kept personal or shared across the organization.
  </Card>
</CardGroup>
