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

# Base URLs & versioning

> The ModuleX REST API base URL (https://api.modulex.dev), why there is no /v1 path segment, the trailing-slash rule, and how ModuleX versions and deprecates the API.

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

The ModuleX REST API is served from a single production base URL, with **no version segment in the path**. Every router mounts directly at its resource path, so a request URL is the base URL joined to the route — for example `https://api.modulex.dev/workflows`. This reference covers the base URL and how request URLs are composed, the no-`/v1` and trailing-slash rules, and how ModuleX versions and deprecates the API.

This page is the API-reference companion to [Base URLs, environments & versioning](/get-started/environments), which walks through pointing each SDK at a local or staging backend and the per-SDK environment-variable behavior. For the request lifecycle and content types, see the [API overview](/api-reference/overview); to authenticate requests, see [Authentication](/api-reference/authentication).

## Base URL

The API has one canonical production host. The SDKs default to it, so you do not set the base URL for normal use — you override it only to target a local backend or a self-hosted deployment.

| Environment          | Base URL                  | When you use it                                                        |
| -------------------- | ------------------------- | ---------------------------------------------------------------------- |
| Production (default) | `https://api.modulex.dev` | Every request, unless you override the base URL.                       |
| Local development    | `http://localhost:8000`   | A backend you run yourself; this is the documented local serving port. |

Both the [JavaScript SDK](/sdks/javascript) and the [Python SDK](/sdks/python) hardcode `https://api.modulex.dev` as the default base URL. The Python SDK additionally reads a `MODULEX_BASE_URL` environment variable as a fallback; the JavaScript SDK does not — see [Switching environments](/get-started/environments) for the full resolution order per SDK.

<Note>
  `https://api.modulex.dev` serves only REST endpoints and the run-event SSE streams. The ModuleX web app and the OAuth redirect target live at a separate host, `https://app.modulex.dev`, and realtime canvas collaboration runs over a separate Socket.io server. Do not point SDK calls at the app host. See [Realtime overview](/realtime/overview) and [Credentials & OAuth2](/concepts/credentials-oauth).
</Note>

### How a request URL is built

The SDKs build every request URL as `baseUrl + path` — the route path is appended verbatim, with no version segment inserted and no other rewriting. A trailing slash on the base URL is stripped before the path is joined, so `https://api.modulex.dev/` and `https://api.modulex.dev` behave identically.

```text Request URL composition theme={null}
base URL          https://api.modulex.dev
+ route path      /workflows/run
= request URL     https://api.modulex.dev/workflows/run
```

The same call against each surface — note that only the base URL changes:

<CodeGroup>
  ```bash cURL theme={null}
  # Production (default)
  curl https://api.modulex.dev/workflows \
    -H "Authorization: Bearer mx_live_8sJ2kQ4mZ1pX7vR9tB3nW6yL0aD5cF" \
    -H "X-Organization-ID: 9d1f0c2e-4b7a-4c1e-8f3a-2b6d5e0a1c34"

  # Local backend — same call, base URL swapped
  curl http://localhost:8000/workflows \
    -H "Authorization: Bearer mx_live_8sJ2kQ4mZ1pX7vR9tB3nW6yL0aD5cF" \
    -H "X-Organization-ID: 9d1f0c2e-4b7a-4c1e-8f3a-2b6d5e0a1c34"
  ```

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

  # Production (default) — base_url is optional
  client = Modulex(
      api_key="mx_live_8sJ2kQ4mZ1pX7vR9tB3nW6yL0aD5cF",
      organization_id="9d1f0c2e-4b7a-4c1e-8f3a-2b6d5e0a1c34",
  )

  # Local backend — override base_url
  local = Modulex(
      api_key="mx_live_8sJ2kQ4mZ1pX7vR9tB3nW6yL0aD5cF",
      organization_id="9d1f0c2e-4b7a-4c1e-8f3a-2b6d5e0a1c34",
      base_url="http://localhost:8000",
  )
  ```

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

  // Production (default) — baseUrl is optional
  const client = new Modulex({
    apiKey: "mx_live_8sJ2kQ4mZ1pX7vR9tB3nW6yL0aD5cF",
    organizationId: "9d1f0c2e-4b7a-4c1e-8f3a-2b6d5e0a1c34",
  });

  // Local backend — override baseUrl
  const local = new Modulex({
    apiKey: "mx_live_8sJ2kQ4mZ1pX7vR9tB3nW6yL0aD5cF",
    organizationId: "9d1f0c2e-4b7a-4c1e-8f3a-2b6d5e0a1c34",
    baseUrl: "http://localhost:8000",
  });
  ```
</CodeGroup>

## No `/v1` path segment

ModuleX does not put a version number in the URL path. There is no `/v1`, no `/api/v1`, and no `/api` prefix — each resource router mounts directly at its prefix.

| Resource        | Full URL                                                                  |
| --------------- | ------------------------------------------------------------------------- |
| Workflows       | `https://api.modulex.dev/workflows`                                       |
| Run a workflow  | `https://api.modulex.dev/workflows/run` (POST; `workflow_id` in the body) |
| Knowledge bases | `https://api.modulex.dev/knowledge-bases`                                 |
| Credentials     | `https://api.modulex.dev/credentials`                                     |
| Integrations    | `https://api.modulex.dev/integrations`                                    |
| Organizations   | `https://api.modulex.dev/organizations`                                   |
| API keys        | `https://api.modulex.dev/api-keys`                                        |

<Warning>
  Do not prefix routes with `/v1` or `/api/v1`. There is no such segment — a request to `https://api.modulex.dev/v1/workflows` does not resolve to the workflows router and will not return the list of workflows. Call `https://api.modulex.dev/workflows`.
</Warning>

## Trailing slashes

Automatic trailing-slash redirects are **turned off**. A route is matched exactly as declared, so request the path without a trailing slash.

| Request           | Result                                                                               |
| ----------------- | ------------------------------------------------------------------------------------ |
| `GET /workflows`  | Matches the list-workflows route.                                                    |
| `GET /workflows/` | Does **not** redirect to `/workflows`; it is treated as a different, unmatched path. |

Always call the canonical path with no trailing slash. The SDKs already do this for you, so this only matters when you build request URLs by hand (for example, in cURL or a custom HTTP client).

## Versioning

ModuleX versions the API and the SDKs separately. Neither uses a version segment in the URL path.

### API versioning

The API is **unversioned in the URL**. There is no `/v1` path segment, and there is **no API-version request header** to send or pin — you cannot request a specific API version from the client side.

The backend application advertises an internal version string of `0.1.2`, visible at the service root `GET /` and the public health endpoint `GET /system/health`. This is the build version of the service, not a contract version you negotiate against — it does not change how you call the API and you do not pass it on requests.

Because there is no version negotiation, ModuleX evolves the API in a backward-compatible way wherever possible: new fields are additive, and existing fields keep their meaning. Track changes that affect you through the [changelog](/reference/changelog), and use the [SDK ⇄ API parity matrix](/sdks/parity) to confirm which routes a given SDK release covers.

<Note>
  Responses are **snake\_case** JSON (for example `run_id`, `created_at`, `organization_ids`). The JavaScript SDK accepts camelCase request parameters and converts them to snake\_case on the wire, but does **not** convert responses — so JavaScript responses are snake\_case too. The Python SDK is snake\_case end to end. See the [SDK ⇄ API parity matrix](/sdks/parity) for the casing rules per SDK.
</Note>

### SDK versioning (semantic versioning)

The official SDKs follow semantic versioning and ship at version **1.0.0**.

| SDK                     | Package   | Version | Default base URL          | Reference                          |
| ----------------------- | --------- | ------- | ------------------------- | ---------------------------------- |
| JavaScript / TypeScript | `modulex` | `1.0.0` | `https://api.modulex.dev` | [JavaScript SDK](/sdks/javascript) |
| Python                  | `modulex` | `1.0.0` | `https://api.modulex.dev` | [Python SDK](/sdks/python)         |

Pin the SDK version in your dependency manifest so a major release does not change behavior under you. Breaking changes land in a new major version; consult the [changelog](/reference/changelog) before upgrading across a major boundary. Notable changes already shipped in the 1.0 line include the retirement of the organization `member` role (invites accept only `admin`; see [Roles & permissions](/security/roles-permissions)), realtime SSE frames discriminating on a `type` key inside the JSON rather than a named `event` line (see [SSE run streaming](/realtime/sse-streaming)), and the removal of client-side billing helpers from the JavaScript SDK (the `subscriptions` resource is [Python-SDK-only](/sdks/parity)).

## Deprecation

ModuleX does not version the API in the URL, so a removed or replaced capability is communicated through the response status and a pointer to its replacement, plus the [changelog](/reference/changelog). There is no `Deprecation` or `Sunset` response header to watch for.

### Removed endpoints return 410

When a capability is removed rather than changed, the route returns **`410 Gone`** with a `detail` message that names the replacement. The live example is the former "LLM mode" of the run endpoint: a single-node chat or knowledge Q\&A submitted to `POST /workflows/run` with only an `llm` config (no workflow, `workflow_id`, or `system_workflow`) has been removed in favor of the agentic [Assistant](/assistant/overview).

```http 410 response — LLM mode removed from /workflows/run theme={null}
HTTP/1.1 410 Gone
Content-Type: application/json

{
  "detail": "LLM mode on /workflows/run has been removed. Use the agentic assistant chat instead: POST /assistant/chat {message, llm?} and stream via the returned stream_url. Knowledge Q&A is now the assistant's search_knowledge tool."
}
```

A `410` is the FastAPI `HTTPException` shape — a string `detail`. It is **not** the billing `DenialEnvelope`, and the SDKs do not map `410` to a dedicated error class; it surfaces as the base `ModulexError`. For the full set of error shapes and which surface emits each, see [Errors & status codes](/api-reference/errors).

<Warning>
  Run a chat-style prompt or knowledge Q\&A through [`POST /assistant/chat`](/assistant/how-it-works), not through the run endpoint. Sending only an `llm` config to `POST /workflows/run` is a removed path and returns `410`. The run endpoint is for executing a saved workflow, an ad-hoc workflow schema, or a system workflow.
</Warning>

### Deprecated aliases

Some routes are kept as deprecated aliases for backward compatibility rather than removed. For example, the support-request router is mounted canonically at `/requests` and also at the deprecated alias `/support`, so `POST /support` is equivalent to `POST /requests`. Prefer the canonical path; aliases may be removed in a future major release and are noted in the [changelog](/reference/changelog).

## Interactive API documentation

The running backend serves machine- and human-readable references at the base URL. Use these to inspect the live schema for the environment you are calling.

| Path                                    | Surface                                                                                              |
| --------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| `https://api.modulex.dev/docs`          | Swagger UI (interactive).                                                                            |
| `https://api.modulex.dev/redoc`         | ReDoc reference.                                                                                     |
| `https://api.modulex.dev/openapi.json`  | The raw OpenAPI schema.                                                                              |
| `https://api.modulex.dev/system/health` | Public liveness check returning `{ "status": "healthy", "service": "ModuleX", "version": "0.1.2" }`. |

For the curated, language-tabbed reference and a live "Try it" playground inside these docs, browse the endpoint pages under [API overview](/api-reference/overview). To check live availability, see [System status & health](/reference/status).

<MediaEmbed id="MX-MEDIA-1200" type="screenshot" caption={"The Mintlify \"Try it\" API playground for a ModuleX endpoint, showing the base-URL host and the auth header fields populated."} />

## What does not change per environment

Switching environments changes only the base URL. Everything else about a request is identical across production and a local or self-hosted backend:

<Steps>
  <Step title="Authentication scheme">
    Every request sends `Authorization: Bearer mx_live_…`, and organization-scoped requests also send `X-Organization-ID`. Use a key that belongs to the environment you are calling. See [Authentication](/api-reference/authentication).
  </Step>

  <Step title="Route paths">
    The same paths with no `/v1` segment, called without a trailing slash.
  </Step>

  <Step title="Error envelopes">
    The same shapes, including the billing `DenialEnvelope` returned as `402` / `403` / `429` on the metered run, composer, assistant, and managed-knowledge surfaces. See [Errors & status codes](/api-reference/errors).
  </Step>

  <Step title="Rate limiting">
    The same per-key, per-user, and per-organization limits and `429` responses. See [Rate limiting](/api-reference/rate-limiting).
  </Step>
</Steps>

## Next steps

<CardGroup cols={2}>
  <Card title="Base URLs, environments & versioning" icon="globe" href="/get-started/environments">
    The getting-started walkthrough: point each SDK at local or staging, with per-SDK environment-variable behavior.
  </Card>

  <Card title="Authentication" icon="key" href="/api-reference/authentication">
    The `Authorization: Bearer` header and `X-Organization-ID` org context.
  </Card>

  <Card title="API overview" icon="book-open" href="/api-reference/overview">
    The request lifecycle, content types, and how each operation is shown three ways.
  </Card>

  <Card title="Errors & status codes" icon="triangle-alert" href="/api-reference/errors">
    The three error shapes, the billing `DenialEnvelope`, and the `410` removed-endpoint case.
  </Card>
</CardGroup>
