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

# API & SDKs — help

> Use ModuleX from your own code: the JavaScript and Python SDKs, how to authenticate, the base URL, streaming run output, and rate limits.

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 can do in the ModuleX app, you can also do from code. The REST API, the JavaScript SDK, and the Python SDK all talk to the same backend, so a workflow you build in the app runs the same way when you call it from a script.

This page answers the questions people ask most when they start using ModuleX from code. For the full developer reference, see the [API overview](/api-reference/overview) and the [SDKs overview](/sdks/overview).

## Quick answers

<CardGroup cols={2}>
  <Card title="Two official SDKs" icon="code" href="/sdks/overview">
    JavaScript and Python, both with streaming. A few features differ between them.
  </Card>

  <Card title="One way to authenticate" icon="key" href="/api-reference/authentication">
    Send your `mx_live_` API key as a Bearer token plus your organization ID.
  </Card>

  <Card title="One base URL" icon="link" href="/api-reference/environments">
    Every request goes to `https://api.modulex.dev` by default.
  </Card>

  <Card title="Live run streaming" icon="radio" href="/realtime/sse-streaming">
    Follow a run event by event with `listen()` or the listen endpoint.
  </Card>
</CardGroup>

## Common questions

<AccordionGroup>
  <Accordion title="Is there a JavaScript and a Python SDK?" icon="boxes-stacked">
    Yes — ModuleX publishes an official JavaScript SDK (`modulex-js`) and an official Python SDK (`modulex-python`), and both support live streaming. They cover the same operations with a handful of differences. The biggest one to know: managing **subscriptions** (plans, billing, checkout, the customer portal) is available in the **Python SDK only** — the JavaScript SDK has no subscriptions methods. For the full side-by-side comparison, see the [SDK parity matrix](/sdks/parity); to get started, see the [SDKs overview](/sdks/overview).
  </Accordion>

  <Accordion title="How do I authenticate?" icon="key">
    Send two things on every request: your API key as `Authorization: Bearer mx_live_…`, and your organization in the `X-Organization-ID` header. In the SDKs you pass your API key and organization ID when you create the client, and it adds both headers for you. Create and copy your key from Settings, API Keys (it is shown only once). Full details are on the [authentication](/api-reference/authentication) page.
  </Accordion>

  <Accordion title="What's the base URL?" icon="link">
    `https://api.modulex.dev`, with no version segment in the path — do not add `/v1` or `/api`. The SDKs use this base URL by default, so you normally do not set it yourself. See [base URLs & versioning](/api-reference/environments) for the details.
  </Accordion>

  <Accordion title="How do I stream a run's output?" icon="radio">
    Instead of waiting for a run to finish, you can follow it event by event. In the SDKs, call `listen()` and read the events as they arrive; over plain HTTP, open the run's listen endpoint, which streams server-sent events. This is how the app shows live progress. See [SSE run streaming](/realtime/sse-streaming) and [streaming & HITL](/sdks/streaming-hitl).
  </Accordion>

  <Accordion title="Is there a rate limit?" icon="gauge-high">
    Yes — a per-minute limit applies to your API key (and a higher combined limit across all your keys). If you go over it, ModuleX returns a `429` response with a `Retry-After` header telling you how many seconds to wait before retrying. The SDKs read that header and back off automatically. See [rate limiting](/api-reference/rate-limiting).
  </Accordion>

  <Accordion title="Can I set credentials with environment variables?" icon="terminal">
    In the Python SDK, yes: if you do not pass them in code, it reads `MODULEX_API_KEY`, `MODULEX_ORGANIZATION_ID`, and `MODULEX_BASE_URL` from the environment. The JavaScript SDK does **not** read environment variables on its own — you must pass your API key (and organization ID) to the client explicitly. See the [JavaScript SDK](/sdks/javascript) and [Python SDK](/sdks/python) pages.
  </Accordion>

  <Accordion title="Can I connect Claude Code, Cursor, or another AI client to ModuleX?" icon="plug">
    Yes. **ModuleX MCP** publishes your workflows, Files, and Knowledge as tools on a private Model Context Protocol endpoint that an MCP client connects to. An owner or admin creates a server and a key in Settings → ModuleX MCP, then you paste the endpoint URL and key into the client. This is separate from your `mx_live_` API key — MCP clients use a server-scoped `mx_mcp_` key. See [ModuleX MCP](/api-reference/mcp/overview) and [Connect an MCP client](/api-reference/mcp/connect-a-client).
  </Accordion>
</AccordionGroup>

## Authenticate and make a call

Every operation looks the same in all three: send your `mx_live_` key as a Bearer token plus your `X-Organization-ID`. Here is listing your workflows.

<CodeGroup>
  ```bash cURL theme={null}
  curl https://api.modulex.dev/workflows \
    -H "Authorization: Bearer mx_live_your_api_key" \
    -H "X-Organization-ID: your-org-id"
  ```

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

  async with Modulex(
      api_key="mx_live_your_api_key",
      organization_id="your-org-id",
  ) as client:
      page = await client.workflows.list()
  ```

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

  const client = new Modulex({
    apiKey: "mx_live_your_api_key",
    organizationId: "your-org-id",
  });

  const { workflows } = await client.workflows.list();
  ```
</CodeGroup>

<Note>
  The Python SDK is async — call its methods with `await` inside an `async` function. The JavaScript SDK uses promises, so `await` its methods too.
</Note>

## Stream a run live

To watch a run as it happens, call `listen()` with the run's id and read events until the run finishes.

<CodeGroup>
  ```bash cURL theme={null}
  curl -N https://api.modulex.dev/workflows/listen/your-run-id \
    -H "Authorization: Bearer mx_live_your_api_key" \
    -H "X-Organization-ID: your-org-id" \
    -H "Accept: text/event-stream"
  ```

  ```python Python theme={null}
  async for event in client.executions.listen("your-run-id"):
      print(event.event, event.data)
  ```

  ```javascript JavaScript theme={null}
  for await (const event of client.executions.listen("your-run-id")) {
    console.log(event.type, event.data);
  }
  ```
</CodeGroup>

## Handle rate limits

When you exceed the per-minute limit, ModuleX returns `429` with a `Retry-After` header (seconds to wait). The SDKs honor it and retry for you; if you call the API directly, wait that long before retrying.

<Steps>
  <Step title="Check the status code">
    A `429` means too many requests in the current window — not an error in your request itself.
  </Step>

  <Step title="Read Retry-After">
    The `Retry-After` response header tells you how many seconds to wait before trying again.
  </Step>

  <Step title="Wait, then retry">
    Pause for that many seconds and send the request again. The SDKs do this automatically.
  </Step>
</Steps>

For what other status codes mean (`401`, `402`, `403`, and more), see [errors & troubleshooting](/help/errors-troubleshooting) and the full [errors reference](/api-reference/errors).

<MediaEmbed id="MX-MEDIA-4580" type="screenshot" caption={"the API Keys screen where a user creates a `mx_live_` key"} />

## Where to go next

<CardGroup cols={2}>
  <Card title="Authentication" icon="key" href="/api-reference/authentication">
    The full authentication reference: Bearer keys, organization context, and headers.
  </Card>

  <Card title="SDKs overview" icon="code" href="/sdks/overview">
    Install and configure the JavaScript and Python SDKs.
  </Card>

  <Card title="Run a workflow" icon="play" href="/guides/run-a-workflow">
    An end-to-end walkthrough in cURL, Python, and JavaScript.
  </Card>

  <Card title="SDK parity matrix" icon="table" href="/sdks/parity">
    Exactly which operations each SDK supports, including the Python-only subscriptions.
  </Card>
</CardGroup>
