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

# System status

> Check whether ModuleX is up: the public GET /system/health liveness probe on the REST backend, the GET /health probe on the realtime collaboration server, the API-key-gated health diagnostics, and where to watch live service status.

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 as two independently deployed services, and each exposes its own health endpoint. The REST backend (everything behind `https://api.modulex.dev` — workflows, runs, the [Assistant](/concepts/assistant), knowledge, billing) answers a public liveness probe at `GET /system/health`. The separate realtime collaboration server that powers [canvas co-editing](/realtime/overview) answers a public probe at `GET /health`. This page documents both, plus the two API-key-gated diagnostic endpoints, and tells you where to watch live status.

<CardGroup cols={2}>
  <Card title="REST backend liveness" icon="server" href="#rest-backend-liveness-get-system-health">
    `GET /system/health` — public, unauthenticated. Returns service name and version. Use it for load-balancer and uptime checks against `api.modulex.dev`.
  </Card>

  <Card title="Realtime server liveness" icon="wave-pulse" href="#realtime-server-liveness-get-health">
    `GET /health` on the collaboration server — public, unauthenticated. Returns uptime, active room count, and connection count. See [Realtime overview](/realtime/overview).
  </Card>
</CardGroup>

<Note>
  The two services are separate. A healthy REST backend does not imply a healthy realtime server, and vice versa. If realtime co-editing is failing while REST calls succeed, probe the realtime server's `/health` directly rather than `api.modulex.dev`.
</Note>

## Which probe to use

<CardGroup cols={2}>
  <Card title="Is the API up?" icon="circle-check">
    Call `GET /system/health` on `api.modulex.dev`. A `200` with `status: healthy` means the REST backend is serving. No auth required.
  </Card>

  <Card title="Is co-editing up?" icon="users">
    Call `GET /health` on the collaboration server. A `200` with `status: healthy` means the Socket.io server is serving, and the body also reports current rooms and connections.
  </Card>

  <Card title="Is the OAuth subsystem healthy?" icon="key">
    Call `GET /system/health/oauth` with an `X-Health-API-Key`. This runs internal integration-catalog and provider checks. Operator-only.
  </Card>

  <Card title="What are live load stats?" icon="gauge">
    Call `GET /admin/stats` or `GET /admin/quick-stats` on the realtime server with admin credentials. Operator-only; see [Realtime overview](/realtime/overview).
  </Card>
</CardGroup>

## REST backend liveness — `GET /system/health`

The primary liveness probe for the ModuleX REST API. It is public, takes no parameters, and returns a static health document. Use it for uptime monitors, container readiness/liveness probes, and load-balancer checks against `api.modulex.dev`.

<ParamField path="(no parameters)" type="none">
  This endpoint takes no path parameters, no query parameters, no request body, and no authentication headers. It is mounted at `/system/health` on the REST backend with no `/v1` segment (ModuleX does not version the REST path).
</ParamField>

<Note>
  Do not confuse `GET /system/health` (this public liveness probe, no auth) with `GET /system/health/oauth` (the API-key-gated diagnostics endpoint documented [below](#oauth-subsystem-diagnostics-get-system-health-oauth)). The path strings overlap but they are served by different routers with different auth.
</Note>

### Request

<CodeGroup>
  ```bash cURL theme={null}
  curl https://api.modulex.dev/system/health
  ```

  ```python Python theme={null}
  import httpx

  resp = httpx.get("https://api.modulex.dev/system/health")
  print(resp.status_code, resp.json())
  ```

  ```javascript JavaScript theme={null}
  const resp = await fetch("https://api.modulex.dev/system/health");
  console.log(resp.status, await resp.json());
  ```
</CodeGroup>

<Note>
  This is a raw HTTP probe, not an SDK operation. The official [JavaScript](/sdks/javascript) and [Python](/sdks/python) SDKs do not expose a typed `health` method, so the examples above use a plain HTTP client. No `Authorization` or `X-Organization-ID` header is needed.
</Note>

### Response

A `200 OK` with a fixed JSON document. The `version` field is the backend application version string.

```json 200 OK theme={null}
{
  "status": "healthy",
  "service": "ModuleX",
  "version": "0.1.2"
}
```

<ResponseField name="status" type="string" required>
  Always the literal `healthy` when the backend is serving requests. The endpoint returns this static value rather than running deep dependency checks, so treat a `200` as a liveness signal (the process is up and routing), not a guarantee that every downstream dependency is reachable.
</ResponseField>

<ResponseField name="service" type="string" required>
  Always the literal `ModuleX`. Lets a multi-service monitor confirm it probed the right backend.
</ResponseField>

<ResponseField name="version" type="string" required>
  The backend application version, for example `0.1.2`. This is the same version reported by the API's own metadata. It is a hardcoded string in the deployed build, so it changes only when a new backend version ships.
</ResponseField>

### Status codes and edge cases

<ResponseField name="200 OK" type="status">
  Returned whenever the backend process is up and routing. The body is the document above.
</ResponseField>

<ResponseField name="500 Internal Server Error" type="status">
  Not specific to this endpoint. If the backend hits an unhandled exception anywhere, it returns the platform-wide envelope `{"detail": "An unexpected internal server error occurred."}` with status `500`. A health probe that returns `500` (or fails to connect) indicates the backend is unhealthy.
</ResponseField>

<Accordion title="Edge cases and gotchas">
  * **No trailing-slash redirect.** The backend runs with redirect-on-trailing-slash disabled. Probe exactly `/system/health` — a request to `/system/health/` will not auto-redirect and may `404`.
  * **No `/v1` prefix.** ModuleX does not put a version segment in the REST path. The path is literally `/system/health`. See [Base URLs & versioning](/api-reference/environments).
  * **Static, not a deep check.** This probe does not test the datastore, the in-memory store, or model providers. It confirms the process is alive and serving HTTP. For subsystem-level diagnostics use the [OAuth health report](#oauth-subsystem-diagnostics-get-system-health-oauth).
  * **No rate-limit or billing gate.** This endpoint is outside the [billing gate](/billing/usage-gating) and is safe to poll from an uptime monitor.
</Accordion>

## Realtime server liveness — `GET /health`

The realtime collaboration server is a separate service from the REST backend. It powers [Socket.io canvas collaboration](/realtime/overview) — presence, cursors, node locks, and live co-editing. It exposes its own public health probe at `GET /health` (note: `/health`, **not** `/system/health` — the path differs from the backend).

<Warning>
  The exact production hostname of the realtime collaboration server is **TBD** — it is not standardized in the verified source material, which references hosts that conflict with the `api.modulex.dev` REST host. This page does not publish a production realtime hostname. Probe `/health` on your deployment's configured collaboration-server URL. See [Open questions](#open-questions). The path and response shape below are verified; only the host is unresolved.
</Warning>

<ParamField path="(no parameters)" type="none">
  This endpoint takes no path parameters, no query parameters, no request body, and no authentication headers. The realtime server's only public HTTP routes are `/health` and the admin diagnostics; every other path returns `404`.
</ParamField>

### Request

The host below is a placeholder — substitute your deployment's collaboration-server URL.

<CodeGroup>
  ```bash cURL theme={null}
  # Local development default port is 3001
  curl http://localhost:3001/health

  # Production: substitute your deployment's realtime host (TBD — see Open questions)
  curl https://YOUR_REALTIME_HOST/health
  ```

  ```python Python theme={null}
  import httpx

  # Substitute your deployment's collaboration-server URL.
  resp = httpx.get("http://localhost:3001/health")
  print(resp.status_code, resp.json())
  ```

  ```javascript JavaScript theme={null}
  // Substitute your deployment's collaboration-server URL.
  const resp = await fetch("http://localhost:3001/health");
  console.log(resp.status, await resp.json());
  ```
</CodeGroup>

### Response

A `200 OK` with a live snapshot of the server's load. Unlike the backend probe, this body changes on every call.

```json 200 OK theme={null}
{
  "status": "healthy",
  "uptime": 3725.41,
  "rooms": 4,
  "connections": 12,
  "timestamp": "2026-06-20T10:15:30.000Z"
}
```

<ResponseField name="status" type="string" required>
  Always the literal `healthy` when the realtime server is serving. As with the backend probe, this is a liveness signal — the only status string this endpoint emits is `healthy`; it has no failure path that returns a different value, so a degraded server shows up as a non-`200` response or a failed connection.
</ResponseField>

<ResponseField name="uptime" type="number" required>
  Process uptime in seconds (fractional), for example `3725.41`. Sourced from the Node process uptime. A small or reset value indicates the realtime server recently restarted.
</ResponseField>

<ResponseField name="rooms" type="number" required>
  The number of active collaboration rooms currently held in memory. Each room corresponds to a workflow canvas with at least one connected editor. See [Presence, locks & versioning](/realtime/presence-locks).
</ResponseField>

<ResponseField name="connections" type="number" required>
  The total number of connected Engine.IO clients across the server. This counts **all** clients on the underlying transport, including any connected to the admin namespace — not only canvas editors. It is therefore a different number from the `connections` figure reported by `GET /admin/quick-stats`, which counts only main-namespace user connections.
</ResponseField>

<ResponseField name="timestamp" type="string" required>
  An ISO 8601 timestamp marking when the server generated this response, for example `2026-06-20T10:15:30.000Z`.
</ResponseField>

### Status codes and edge cases

<ResponseField name="200 OK" type="status">
  Returned whenever the realtime server is up. There is no other success status and no documented error status for this endpoint — it has only the `200` path.
</ResponseField>

<Accordion title="Edge cases and gotchas">
  * **Different path from the backend.** The realtime probe is `/health`. The backend probe is `/system/health`. They are not interchangeable, and they live on different hosts.
  * **`connections` counts more than editors.** It reflects every Engine.IO client on the server, including admin-namespace connections. To count only collaborating users, use the admin stats endpoints below.
  * **No auth and no rate limit.** Like the backend probe, `/health` is public and ungated. It is safe to poll from an uptime monitor.
  * **Flat error shape elsewhere.** The realtime server has no shared error envelope. Its other (non-health) routes return a flat `{"error": "..."}` object with no code field — but `/health` itself only ever returns the `200` body above.
</Accordion>

## Operator-only diagnostics

Beyond the two public liveness probes, ModuleX exposes deeper diagnostics that require operator credentials. These are not for application code — they back internal monitoring — but they are documented here so you know what exists.

### OAuth subsystem diagnostics — `GET /system/health/oauth`

A backend diagnostics endpoint that runs internal consistency checks over the integration catalog and OAuth provider configuration. It is gated by a dedicated health API key, distinct from your `mx_live_*` key.

<ParamField header="X-Health-API-Key" type="string" required>
  The deployment's configured health-check key. The value is compared in constant time against the server's `HEALTH_CHECK_API_KEY`. This is **not** an `mx_live_*` API key and **not** a JWT — it is a separate operator secret. If the deployment has not configured a key, the endpoint returns `503` for everyone.
</ParamField>

<CodeGroup>
  ```bash cURL theme={null}
  curl https://api.modulex.dev/system/health/oauth \
    -H "X-Health-API-Key: YOUR_HEALTH_KEY"
  ```

  ```python Python theme={null}
  import httpx

  resp = httpx.get(
      "https://api.modulex.dev/system/health/oauth",
      headers={"X-Health-API-Key": "YOUR_HEALTH_KEY"},
  )
  print(resp.status_code, resp.json())
  ```

  ```javascript JavaScript theme={null}
  const resp = await fetch("https://api.modulex.dev/system/health/oauth", {
    headers: { "X-Health-API-Key": "YOUR_HEALTH_KEY" },
  });
  console.log(resp.status, await resp.json());
  ```
</CodeGroup>

The response is a structured health report. `overall` aggregates the individual checks: any check that fails makes `overall` `unhealthy`; any warning makes it `warning`; otherwise it is `healthy`.

```json 200 OK theme={null}
{
  "schema_version": 1,
  "overall": "healthy",
  "generated_at": "2026-06-20T10:00:00+00:00",
  "checks": [
    {
      "name": "package_catalog_parity",
      "status": "pass",
      "summary": "All 42 package tools present in catalog",
      "details": { "package_count": 42, "catalog_count": 43, "legacy_allowed": ["mcp_server"] },
      "suggestion": null
    }
  ]
}
```

<Expandable title="Response schema">
  <ResponseField name="schema_version" type="integer" required>
    The report schema version. Currently `1`.
  </ResponseField>

  <ResponseField name="overall" type="string" required>
    One of `healthy`, `warning`, or `unhealthy`, aggregated from the `checks` array.
  </ResponseField>

  <ResponseField name="generated_at" type="string" required>
    ISO 8601 timestamp (with offset) of when the report was produced.
  </ResponseField>

  <ResponseField name="checks" type="array" required>
    The individual diagnostic results. The checks run cover integration package/catalog parity, OAuth provider completeness, and obsolete settings attributes.

    <Expandable title="check object">
      <ResponseField name="name" type="string" required>
        The check identifier, for example `package_catalog_parity`.
      </ResponseField>

      <ResponseField name="status" type="string" required>
        One of `pass`, `warn`, or `fail`.
      </ResponseField>

      <ResponseField name="summary" type="string" required>
        A human-readable one-line result.
      </ResponseField>

      <ResponseField name="details" type="object" required>
        Check-specific structured data. Keys vary by check.
      </ResponseField>

      <ResponseField name="suggestion" type="string">
        An optional remediation hint, or `null`.
      </ResponseField>
    </Expandable>
  </ResponseField>
</Expandable>

<ResponseField name="200 OK" type="status">
  The report was produced. Inspect `overall` and each check's `status` rather than relying on the HTTP code alone — a `200` can still carry `overall: unhealthy`.
</ResponseField>

<ResponseField name="401 Unauthorized" type="status">
  The `X-Health-API-Key` was supplied but did not match. Body: `{"detail": "Invalid health check API key"}`.
</ResponseField>

<ResponseField name="503 Service Unavailable" type="status">
  The deployment has no health-check key configured, so the endpoint is disabled. Body: `{"detail": "HEALTH_CHECK_API_KEY not configured on this deployment"}`.
</ResponseField>

### Realtime load stats — `GET /admin/stats` and `GET /admin/quick-stats`

The realtime collaboration server exposes two admin diagnostics endpoints that report live connection, room, and per-organization load. They are operator-only and require both an admin API key and an allowlisted Clerk token — they are not part of the public API and not intended for application code.

<ParamField header="x-api-key" type="string" required>
  The realtime server's admin API key. May alternatively be passed as the `api_key` query parameter. Must match the server's configured `ADMIN_API_KEY`.
</ParamField>

<ParamField header="Authorization" type="string" required>
  A `Bearer <clerk_jwt>` token. The token's verified email must match the server's single allowlisted operator email, or the request is rejected with `403`.
</ParamField>

<CodeGroup>
  ```bash cURL theme={null}
  # Substitute your deployment's realtime host (TBD — see Open questions).
  curl https://YOUR_REALTIME_HOST/admin/quick-stats \
    -H "x-api-key: YOUR_ADMIN_API_KEY" \
    -H "Authorization: Bearer YOUR_CLERK_JWT"
  ```

  ```python Python theme={null}
  import httpx

  resp = httpx.get(
      "https://YOUR_REALTIME_HOST/admin/quick-stats",  # host TBD — see Open questions
      headers={
          "x-api-key": "YOUR_ADMIN_API_KEY",
          "Authorization": "Bearer YOUR_CLERK_JWT",
      },
  )
  print(resp.status_code, resp.json())
  ```

  ```javascript JavaScript theme={null}
  // Substitute your deployment's realtime host (TBD — see Open questions).
  const resp = await fetch("https://YOUR_REALTIME_HOST/admin/quick-stats", {
    headers: {
      "x-api-key": "YOUR_ADMIN_API_KEY",
      Authorization: "Bearer YOUR_CLERK_JWT",
    },
  });
  console.log(resp.status, await resp.json());
  ```
</CodeGroup>

`GET /admin/quick-stats` returns a lightweight, in-memory snapshot computed without scanning sockets:

```json 200 OK theme={null}
{
  "connections": 12,
  "activeConnections": 9,
  "idleConnections": 3,
  "organizations": 3,
  "rooms": 4,
  "uptime": "1h 2m 5s",
  "memory": 84,
  "timestamp": "2026-06-20T10:15:30.000Z"
}
```

`GET /admin/stats` returns a much larger snapshot built by scanning every connected socket (`server`, `connections`, `rooms`, `trackers`, `organizations[]`, `activeRooms[]`, `allConnections[]`, `timestamp`). It is more detailed but more expensive to compute.

<Warning>
  Quick-stats and full-stats are computed by different mechanisms and can disagree. Quick-stats reads in-memory counters and is approximate; full-stats recomputes from live sockets. Quick-stats also reports connection fields flat (`connections`, `activeConnections`, `idleConnections`) while full-stats nests them under `connections` as `total`, `active`, and `idle`. Treat quick-stats as an approximate, low-cost reading.
</Warning>

<Accordion title="Known-inaccurate fields in /admin/stats">
  Some fields in the full `GET /admin/stats` payload are placeholders in the current server build and do not carry real telemetry. Do not present them as accurate:

  * Per-connection `connectedAt` is stamped at collection time, not at real connect time, and `connectionDuration` is the literal string `N/A`.
  * Per-room `pendingPatches` is always `0`, `createdAt` is stamped at collection time, and `lastSavedAt` is always `null`.

  These are documented source limitations, not live metrics. For the canonical realtime event reference, see [Realtime overview](/realtime/overview).
</Accordion>

<ResponseField name="200 OK" type="status">
  Stats were collected and returned.
</ResponseField>

<ResponseField name="401 Unauthorized" type="status">
  Missing or invalid admin API key (`{"error": "Invalid or missing API key"}`), missing token (`{"error": "Authentication token required"}`), or invalid token (`{"error": "Invalid authentication token"}`).
</ResponseField>

<ResponseField name="403 Forbidden" type="status">
  The token was valid but its email is not the allowlisted operator (`{"error": "Unauthorized user"}`).
</ResponseField>

<ResponseField name="404 Not Found" type="status">
  Authenticated, but the path under `/admin/` is not `/admin/stats` or `/admin/quick-stats` (`{"error": "Admin endpoint not found"}`).
</ResponseField>

<ResponseField name="500 Internal Server Error" type="status">
  Stats collection threw (`{"error": "Failed to collect stats"}`).
</ResponseField>

<ResponseField name="503 Service Unavailable" type="status">
  The admin API key is not configured on the server, so admin routes are disabled (`{"error": "Admin API not configured"}`).
</ResponseField>

## Where to check live status

There is **no verified public status page for ModuleX** in the current source material. Until one is confirmed, do not point readers to a status-page URL.

<Note>
  **Public status page: TBD.** No public ModuleX status-page URL (for example a hosted incident/uptime page) is verifiable in the source material. When one is confirmed, this page will link it here. Until then, use the public health probes above for an at-a-glance liveness check.
</Note>

In the meantime, you can:

* **Probe the public health endpoints.** `GET /system/health` on `api.modulex.dev` for the REST backend, and `GET /health` on the realtime server for canvas collaboration. Both are unauthenticated and safe to poll.
* **Watch in-app system notices.** ModuleX surfaces operational announcements as system notifications inside the app — maintenance windows, incidents, and changelog entries appear in the notifications feed.
* **Track product changes.** Notable changes are recorded in the [Changelog](/reference/changelog).
* **Reach a human.** If something looks down, see [Getting help](/help/getting-help) for support channels.

<Tip>
  For container orchestration, point your liveness probe at `GET /system/health` for the backend and `GET /health` for the realtime server. Both return `200` with `status: healthy` when the service is serving and require no credentials, so neither needs a secret baked into the probe configuration.
</Tip>

<MediaEmbed id="MX-MEDIA-4430" type="screenshot" caption={"the in-app notifications feed showing a system notice (maintenance or incident)"} />

## Open questions

<Accordion title="Production realtime collaboration-server hostname (TBD)">
  The exact production hostname for the realtime collaboration server is not standardized in the verified source material — it references hosts that conflict with the `api.modulex.dev` REST host. This page does not publish a production realtime host; use your deployment's configured collaboration-server URL for the `/health`, `/admin/stats`, and `/admin/quick-stats` endpoints. The local development default is port `3001`.
</Accordion>

<Accordion title="Public status page (TBD)">
  No public ModuleX status-page URL is verifiable in the source material. This page will link it once confirmed. Until then, use the public health probes and in-app system notices described above.
</Accordion>
