Skip to main content
The ModuleX REST API is the programmatic surface behind every product feature: workflows, runs, the AI Composer, the Assistant, knowledge bases, credentials, and integrations. The same API powers the web app, the official SDKs, and your own code. This page explains the parts every request shares: the base URL, the routing scheme, the request lifecycle, content types, and how each operation is documented exactly once across cURL, Python, and JavaScript with a live playground.

Authenticate

Every request carries Authorization: Bearer mx_live_… and X-Organization-ID.

Pick an SDK

Official JavaScript and Python clients wrap every operation on this page.

Handle errors

Three error-envelope shapes, including the billing DenialEnvelope.

Stream results

Long-running runs stream over Server-Sent Events, not a polling loop.

Base URL

All API requests go to a single host. There is no separate path prefix for the API: routers mount directly at their resource path.
Base URL
https://api.modulex.dev
Both official SDKs default to this host, so you only set it when you point at a non-production environment:
  • JavaScript: DEFAULT_BASE_URL = 'https://api.modulex.dev', overridable with the baseUrl client option.
  • Python: DEFAULT_BASE_URL = "https://api.modulex.dev", overridable with the base_url argument or the MODULEX_BASE_URL environment variable.
A request URL is simply the base URL joined to the operation path. For example, running a workflow is POST https://api.modulex.dev/workflows/run.
Trailing-slash redirects are disabled (redirect_slashes=False). Call /workflows, not /workflows/ — a trailing slash is not auto-corrected and will not match the route. The SDKs build paths correctly for you.
For the full list of environments, hostnames, and the versioning policy, see Base URLs & versioning and Base URLs, environments & versioning.

No /v1 in the path

ModuleX does not put a version segment in the URL. There is no /v1, /api, or /api/v1 prefix anywhere in the routing scheme — paths are exactly their resource name.
POST https://api.modulex.dev/workflows/run
GET  https://api.modulex.dev/workflows
GET  https://api.modulex.dev/knowledge-bases
If you hand-build URLs (rather than using an SDK), drop any /v1 you may have copied from another API. A versioned path returns 404 Not Found because the route is not registered. Breaking changes are communicated through the changelog and SDK release notes rather than through a URL version. See Base URLs & versioning for how ModuleX evolves the API without a path version.

Request lifecycle

Every authenticated call follows the same path. Authentication and organization context are resolved as route dependencies — there is no separate request-id or auth middleware to account for.
1

You send a request

Set the method and resource path, attach the two required headers (Authorization: Bearer and X-Organization-ID), and send a JSON body for write operations.
2

Credentials are verified

A Bearer token that starts with mx_live_ is treated as an API key; any other Bearer token is treated as a Clerk session JWT (used by the app). A missing or invalid token returns 401.
3

Organization context is applied

For org-scoped operations, ModuleX reads X-Organization-ID and checks your membership and role. A missing header returns 400; a non-member or a role too low for the operation returns 403. See Roles & permissions.
4

Rate limits and the billing gate run

API-key traffic is rate limited per key and per user, and org traffic is rate limited per organization. Operations that consume managed usage also pass the usage gate before any work begins. Either check can stop the request with a 429, 403, or 402.
5

The operation runs and responds

The handler executes and returns a JSON body. For most operations that is the final result. For runs and agent turns, the response returns immediately with run metadata and the result streams separately over SSE.
🎬 MEDIA PLACEHOLDER · MX-MEDIA-1180 · [IMAGE] [IMAGE_DESCRIPTION]: Request lifecycle diagram for a ModuleX API call. [IMAGE_DETAILS]: A left-to-right flow with five stages matching the Steps above — Client request (showing the two required headers) → Auth (Bearer mx_live_* vs Clerk JWT branch) → Org context (X-Organization-ID, role check) → Rate limit + usage gate (showing the 429 / 403 / 402 exit) → Handler → JSON response, with a side branch from the handler labeled “SSE stream” for runs and agent turns. Annotate each guard with its failure status code. 16:9, light theme, ModuleX brand palette, no drop shadows.

Authentication recap

Authentication is the same on every operation, so it is documented in full on one page. Each request needs two headers:
HeaderValueRequired
AuthorizationBearer mx_live_… (your API key)Yes
X-Organization-IDThe organization UUID the call acts onYes for org-scoped operations
The backend also accepts the API key in an X-API-KEY header as an alternative to Authorization: Bearer, but the SDKs and all examples in these docs use Authorization: Bearer. The header is X-Organization-ID (capital ID); there is no X-Authorization header. For the full authentication model — how to create and scope keys, the difference between API keys and Clerk JWTs, and every auth-related status code — see Authentication and Auth model: JWT vs API key.

Content types and conventions

Content-Type
string
default:"application/json"
Send application/json for request bodies. Document upload to a knowledge base is the exception: those endpoints accept multipart/form-data.
Accept
string
default:"application/json"
Responses are JSON by default. Streaming endpoints return text/event-stream (Server-Sent Events) instead.
Field casing
string
Request and response bodies use snake_case on the wire (for example workflow_id, created_at, run_id). The Python SDK uses snake_case end to end. The JavaScript SDK accepts camelCase in method parameters and converts to snake_case before sending, but leaves responses in snake_case — so a JS response field is run_id, not runId.
Timestamps
string
Timestamps are ISO 8601 strings in UTC, for example 2026-06-21T12:00:00Z.
IDs
string
Resource identifiers are strings. Note that run_id carries distinct meanings across layers — see Workflows & runs for the three run-id identities.
List endpoints are paginated. The style (page, offset, or cursor) depends on the resource, and the SDKs provide auto-paginating helpers. See Pagination for the details.

One operation, three ways, plus a playground

Each operation is documented once. Instead of separate per-language pages, you get a single CodeGroup with three tabs — cURL, Python, JavaScript — that all perform the same call, plus an interactive playground for the underlying endpoint.

cURL

The raw HTTP request: exact method, path, headers, and JSON body.

Python

The async modulex-python client (v1.0.0).

JavaScript

The modulex-js client (v1.0.0).
The cURL tab is the ground truth: it shows the literal wire call, so it works in any language. The Python and JavaScript tabs show the official SDK method that wraps that same call. Where an operation has no SDK method (a parity gap), the docs say so and show cURL only — see the SDK ⇄ API parity matrix for the complete coverage map, including the subscriptions resource that exists only in the Python SDK.

Example: run a workflow

This is what the three-way pattern looks like. Running a deployed workflow is a single POST /workflows/run. The synchronous response returns immediately with run metadata while the result streams over SSE.
curl https://api.modulex.dev/workflows/run \
  -H "Authorization: Bearer mx_live_8s2Kd0pQ4rTv7Xw" \
  -H "X-Organization-ID: 5d9c2e7a-1f4b-4a6c-9e3d-0b8a1c2d3e4f" \
  -H "Content-Type: application/json" \
  -d '{
    "workflow_id": "wf_3b1c9a2e",
    "input": { "topic": "Q3 launch summary" },
    "stream": true
  }'
run_id
string
The per-execution identifier. Use it to stream events and to look up run status. A new run_id is minted on each run and on each resume of an interrupted run.
status
string
The status of the synchronous portion. For a streaming run this is "running"; terminal statuses (completed, failed, cancelled, interrupted) are observed over the SSE stream or the run-history endpoints, not here.
For the complete end-to-end walkthrough — authenticate, run, and consume the stream in all three languages — see Run a workflow (REST + SDK). To make your very first call, start with Make your first API call.

Live playground

Endpoint reference pages include a live “Try it” playground generated from the ModuleX OpenAPI specification. Enter your API key and organization ID once, fill in parameters, and send a real request from the browser to see the actual response. The playground exercises the same routes documented above — for example POST /workflows/run, GET /workflows, and POST /knowledge-bases/{knowledge_id}/search.
🎬 MEDIA PLACEHOLDER · MX-MEDIA-1181 · [SCREENSHOT] [SCREENSHOT_DESCRIPTION]: The interactive API playground for an endpoint, mid-request. [SCREENSHOT_DETAILS]: Capture a generated endpoint reference page (for example POST /workflows/run) showing the auth fields populated with Authorization: Bearer mx_live_… and X-Organization-ID, the request-body editor with the example body filled in, and a successful JSON response panel below the Send button. Crop to the playground panel. Light theme, ModuleX docs chrome, redact any real key value.

Errors

Operations return standard HTTP status codes. ModuleX has three error-envelope shapes, and you should branch on all of them. The shape depends on the surface that produced the error.
HTTPException — {detail: string}
object
The common FastAPI shape, used by plain CRUD and org-settings routes. The detail is a human-readable string, for example { "detail": "X-Organization-ID header is required" }.
Structured detail — {detail: object}
object
A dict-valued detail, used by the organization rate-limit deny (code: "rate_limited") and the wallet paid-gate 402.
DenialEnvelope — flat object
object
The flat billing/credit/rate/quota envelope returned by the usage gate, with no detail wrapper: { code, layer, key, current, limit, reason }.
The billing usage gate is live. Operations that consume managed usage — running workflows, the Composer, the Assistant, and managed knowledge — pass through the gate before any work begins. When you exceed a limit, the gate returns a flat DenialEnvelope whose layer field maps to the status code:
layerHTTP statusMeaning
rate429Rate limit exceeded (also carries X-RateLimit-* and Retry-After headers)
quota403A plan quota was exceeded
credit402Plan credit allowance exhausted, or an upgrade payment failed
wallet402Overage is disabled, or the prepaid wallet has insufficient balance
These billing envelopes appear only on the managed-usage surfaces above. Plain CRUD and org-settings routes return the {detail} HTTPException shape instead. For the complete status-code taxonomy, every error code, and which surface emits each shape, see Errors & status codes, Rate limiting, and Usage gating & limits.

Where to go next

Authentication

Create keys, scope them to an organization, and authenticate every call.

SDKs overview

Install the JavaScript or Python client and use every operation from code.

Run a workflow

A full REST and SDK walkthrough, from key to streamed result.

Errors & status codes

The three envelope shapes and the billing DenialEnvelope in detail.