> ## 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.

# Security overview

> How ModuleX secures your data, credentials, and access: one sign-in identity, API keys you can scope and revoke, credentials encrypted at rest, and an organization boundary that keeps every tenant's work separate.

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>;
};

ModuleX runs your workflows against the tools, models, and documents you connect. That means it holds things worth protecting: the keys to your connected services, your company knowledge, and the access rules for your team. This page is the plain-language tour of how ModuleX keeps each of those safe.

It covers three layers: how you and your code prove who you are, how connected credentials are encrypted, and how each organization's work stays separate from everyone else's. Each section links to a detailed page when you want the exact mechanics.

<CardGroup cols={3}>
  <Card title="Who can get in" icon="key" href="#how-access-works">
    People sign in once through a single identity provider, and code authenticates with API keys you can scope and revoke.
  </Card>

  <Card title="Your secrets, encrypted" icon="lock" href="#your-credentials-stay-encrypted">
    The credentials you connect are encrypted before they are stored, and never shown back to you in full.
  </Card>

  <Card title="Kept separate" icon="shield" href="#every-organization-stays-separate">
    Every organization is its own boundary. One organization's data, access, and bill never reach another.
  </Card>
</CardGroup>

## How access works

ModuleX has one identity for people and a separate, purpose-built credential for code. Both have to be present before any request runs.

<CardGroup cols={2}>
  <Card title="People sign in once" icon="user-check">
    Everyone who uses the ModuleX app signs in through a single identity provider. ModuleX does not store your password, and there is no separate ModuleX login to manage.
  </Card>

  <Card title="Code uses API keys" icon="terminal" href="/security/authentication">
    Scripts, the SDKs, and your own product authenticate with a ModuleX API key. Keys are created in the app, scoped to what they need, and can be revoked at any time.
  </Card>
</CardGroup>

These are two paths to the same checkpoint. The app uses the human sign-in; the API and the [SDKs](/sdks/overview) use an API key. For the full picture of when each is used and how ModuleX tells them apart, see [Auth model: JWT vs API key](/security/authentication).

### Authenticating from code

Every programmatic request carries two things: your API key as a bearer token, and the organization the request runs in. The API key always begins with `mx_live_`.

<CodeGroup>
  ```bash cURL theme={null}
  curl https://api.modulex.dev/workflows \
    -H "Authorization: Bearer mx_live_2J9vK4xM7pQ1sR8tU3wY6zB" \
    -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_2J9vK4xM7pQ1sR8tU3wY6zB",
          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_2J9vK4xM7pQ1sR8tU3wY6zB",
    organizationId: "8a1f3c2e-9b4d-4e7a-bc11-7d6e5f4a3b2c",
  });

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

<Note>
  The authentication header is `Authorization: Bearer` followed by your `mx_live_` key, plus the `X-Organization-ID` header. These two headers travel together on every org-scoped request. See [Authentication](/api-reference/authentication) for the complete reference.
</Note>

### Keys you control

API keys are built to be handed out carefully and taken back cleanly.

<CardGroup cols={3}>
  <Card title="Scoped to one organization" icon="crosshair">
    A key can be locked to a single organization. If a request uses that key for any other organization, ModuleX rejects it.
  </Card>

  <Card title="Never stored in the clear" icon="fingerprint">
    ModuleX keeps a one-way hash of each key, not the key itself. The full key is shown only once, when you create it.
  </Card>

  <Card title="Rate limited" icon="gauge">
    Each key and each user has a request limit, so a leaked or runaway key cannot overwhelm your account.
  </Card>
</CardGroup>

Because only a hash is stored, ModuleX cannot show you a key again after you create it. Save it somewhere safe, and if it leaks, revoke it and issue a new one.

## Your credentials stay encrypted

When you connect a service, such as a GitHub account, a Slack workspace, or a model provider, ModuleX stores a [credential](/concepts/credentials-oauth): an encrypted record that lets your workflows act on your behalf. Protecting those credentials is the heart of ModuleX's data security.

<Steps>
  <Step title="You connect a service">
    You authorize ModuleX through the service's own login, or you paste in an API key. For services that support it, ModuleX uses the OAuth2 authorization flow with PKCE, so it never has to handle your password.
  </Step>

  <Step title="ModuleX encrypts it before storing">
    The secret part of the credential is encrypted before it is written to the database. Each credential is encrypted with a key derived for that one credential in that one organization, so a credential cannot be unlocked outside the organization it belongs to.
  </Step>

  <Step title="It is decrypted only to run your work">
    The credential is decrypted in memory only at the moment a workflow or agent needs it, then used to call the connected service.
  </Step>

  <Step title="You only ever see it masked">
    When you view a credential in the app or through the API, ModuleX returns a masked value such as `start***end`, never the secret in full.
  </Step>
</Steps>

<MediaEmbed id="MX-MEDIA-4210" type="image" caption={"A simple lifecycle diagram showing how a connected credential is encrypted at rest and decrypted only at run time."} />

<Accordion title="What does encrypted at rest actually mean here?">
  Each stored credential is encrypted with a key that is unique to that credential and its organization, derived from a master encryption key that ModuleX keeps as a managed secret. Because the per-credential key depends on both the organization and the credential, ciphertext from one credential cannot be decrypted as another, even within the same organization. The exact derivation and key-management details are documented for engineers in [Data security & encryption](/security/data-encryption).
</Accordion>

<Accordion title="What about the API keys and platform secrets themselves?">
  API keys are stored as one-way hashes rather than recoverable values, so even ModuleX cannot read them back. The master secrets that protect credentials and hash API keys are required to be present and strong before ModuleX will start in a production environment, and they can be sourced from a managed secret store. The full configuration rules are in [Data security & encryption](/security/data-encryption).
</Accordion>

## Every organization stays separate

In ModuleX, an [organization](/concepts/organizations-roles) is the boundary around your work. Your workflows, credentials, knowledge bases, plan, and bill all live inside it, and stay separate from every other organization's.

<CardGroup cols={2}>
  <Card title="One boundary per organization" icon="box">
    Every resource you create belongs to exactly one organization. Data is not shared across organizations.
  </Card>

  <Card title="Every request names its organization" icon="route" href="/security/org-context">
    Org-scoped requests carry the `X-Organization-ID` header. ModuleX checks that you are a member of that organization before it runs anything.
  </Card>

  <Card title="Membership is verified each time" icon="user-check">
    ModuleX confirms your membership and role for the named organization on every request, not just at sign-in.
  </Card>

  <Card title="Keys can be locked down" icon="lock">
    An API key scoped to one organization simply cannot act on another, even if it is shared by mistake.
  </Card>
</CardGroup>

If a request leaves out the organization, or names one you do not belong to, ModuleX rejects it rather than guessing. The header handling, how an API key is bound to a single organization, and the exact error responses are covered in [Org context & X-Organization-ID](/security/org-context).

### Roles decide who can do what

Within an organization, your role decides what you are allowed to do. ModuleX keeps this deliberately simple.

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

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

<Note>
  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. If you see "member" referenced anywhere, treat it as legacy. See [Roles & permissions](/security/roles-permissions) for which actions require which role.
</Note>

## Where your data goes

Most of what ModuleX does runs against services you choose. Being clear about that is part of keeping your data secure.

<CardGroup cols={2}>
  <Card title="Bring your own keys" icon="key" href="/integrations/llm-providers/overview">
    When you connect your own model or tool provider, that usage runs on your account, at your provider, under your provider's terms. ModuleX adds no markup and stores the credential encrypted.
  </Card>

  <Card title="Managed usage" icon="sparkles" href="/billing/credits">
    When you use ModuleX-managed models or knowledge storage, ModuleX provisions the provider for you and meters the usage in credits.
  </Card>
</CardGroup>

Either way, the credential that authorizes the work is encrypted, scoped to your organization, and used only to run your workflows.

## Trust, compliance, and sub-processors

This page covers how the product is built to protect your data. For ModuleX's data-handling posture, retention, certifications, and the third parties that may process data on its behalf, see the Trust pages.

<CardGroup cols={3}>
  <Card title="Trust & compliance" icon="badge-check" href="/security/trust">
    How ModuleX handles, retains, and protects data.
  </Card>

  <Card title="Compliance" icon="clipboard-check" href="/security/compliance">
    ModuleX's compliance posture and certifications.
  </Card>

  <Card title="Sub-processors" icon="building" href="/security/sub-processors">
    Third parties that may process data on ModuleX's behalf.
  </Card>
</CardGroup>

<Note>
  Specific compliance certifications and the full sub-processor list are published on the Trust pages above. This overview does not assert any certification; confirm the current status there before relying on it.
</Note>

## Read the details

<CardGroup cols={2}>
  <Card title="Auth model: JWT vs API key" icon="key" href="/security/authentication">
    The two authentication paths, how ModuleX tells them apart, and the exact headers.
  </Card>

  <Card title="Data security & encryption" icon="lock" href="/security/data-encryption">
    How credentials are encrypted at rest, plus key management and production checks.
  </Card>

  <Card title="Org context & X-Organization-ID" icon="route" href="/security/org-context">
    How the organization header scopes every org-bound request.
  </Card>

  <Card title="Roles & permissions" icon="user-shield" href="/security/roles-permissions">
    The owner and admin roles and which actions each can take.
  </Card>
</CardGroup>
