Skip to main content
ModuleX ships two official client SDKs — modulex-js for JavaScript and TypeScript, and modulex-python for Python. Both are thin, typed clients over the same ModuleX REST API and Server-Sent Events streams: they do not run workflows locally, and every method maps to one HTTP call. Pick the SDK for your runtime, install it once, configure authentication and your organization context, and you have typed access to every operation in the platform. This page covers installation, client configuration, the authentication scheme, and how SDK operations correspond to REST routes. For the side-by-side method-to-route table and the parity gaps between the two SDKs, see the SDK to API parity matrix.
Both SDKs are at version 1.0.0. The JavaScript SDK requires Node 18 or newer; the Python SDK requires Python 3.9 or newer and is async-only.

Install

Install the SDK for your language. The JavaScript package is published to npm as modulex-js; the Python package is published to PyPI as modulex-python and imported as modulex.
The JavaScript SDK has zero runtime dependencies and ships a dual ESM + CommonJS build, so it works with both import and require. The Python SDK is built on httpx.AsyncClient and exposes a single async client class.
Do not install Python extras such as modulex-python[all] or per-tool extras like modulex-python[github,slack]. These extras are currently empty: requesting them makes pip emit a warning and installs only the core package. Integrations are loaded server-side by ModuleX, not bundled into the SDK, so the core install is all you need. See installing and using integrations for how integrations load at runtime.

Create a client

You need a ModuleX API key (prefix mx_live_) and, for any organization-scoped operation, your organization id. Create an API key from the ModuleX dashboard, then pass it to the client constructor. See authentication for how to obtain and scope a key.
The Python client is an async context manager. Using async with closes the underlying HTTP client for you on exit; if you construct the client without async with, call await client.close() when you are done. The JavaScript client needs no explicit teardown.
The Python client throws synchronously if no API key is found: ValueError("api_key is required: pass api_key=... or set the MODULEX_API_KEY environment variable"). The JavaScript client throws a plain Error with the message ModuleX API key is required. Pass apiKey to the Modulex constructor. (the literal property name is apiKey).

Configuration options

Both SDKs accept the same set of settings; the option names follow each language’s idiom (camelCase in JavaScript, snake_case in Python), and the semantics and defaults are identical.
string
required
Your ModuleX API key with the mx_live_ prefix. Sent as Authorization: Bearer <key>. Required in both SDKs. In Python it may instead be supplied through the MODULEX_API_KEY environment variable; in JavaScript there is no environment-variable fallback and it must be passed to the constructor (see environment-variable fallback below).
string
default:"undefined / None"
The default organization context for every request, sent as the X-Organization-ID header. Optional; can be overridden per request. In Python it may also come from MODULEX_ORGANIZATION_ID. When no organization id is resolved, the SDK omits the header entirely.
string
default:"https://api.modulex.dev"
The REST API root. Do not append a version segment such as /v1 or an /api prefix — ModuleX routers mount at the root with their own per-resource prefixes. Trailing slashes are stripped. In Python it may also come from MODULEX_BASE_URL. See base URLs and versioning.
number
default:"30000 ms (JS) / 30.0 s (Python)"
Request timeout. JavaScript expresses it in milliseconds (default 30000); Python in seconds as a float (default 30.0). There is no environment-variable fallback for this option in either SDK.
number
default:"3"
Maximum automatic retries for transient failures. See retry behavior. No environment-variable fallback in either SDK.
function
default:"globalThis.fetch"
JavaScript only. A custom fetch implementation. There is no equivalent custom-transport option in the Python client.
object
default:"empty"
Python only. Extra headers merged into every request. These are spread before the auth and content-type headers, so they cannot override Authorization or Content-Type, but they can set or override others such as User-Agent.

Environment-variable fallback

The two SDKs differ here, and it is a common source of confusion. The JavaScript README reads process.env.MODULEX_API_KEY in its examples, but that is caller code passing the value to the constructor — the JavaScript SDK itself performs no environment lookup. Do not rely on automatic MODULEX_* pickup in JavaScript. The per-SDK detail lives in JavaScript SDK and Python SDK.

Authentication and organization context

Every request both SDKs make carries the same two headers, matching the REST authentication and auth model contract:
  • Authorization: Bearer mx_live_… — your API key as a Bearer token. This is the correct header. There is no X-Authorization header anywhere in either SDK or the backend. (The backend also accepts an alternative X-API-KEY header, but neither SDK sends it.)
  • X-Organization-ID: <org id> — added only when an organization id is resolved, scoping the request to one organization.
The organization id resolves in this order: a per-request override, then the client-level default, then (in Python only) the MODULEX_ORGANIZATION_ID environment variable. If none resolve, the header is omitted; org-scoped endpoints will then return a 400 with the message X-Organization-ID header is required. See org context for which endpoints require it.
Composer, Assistant, and Knowledge operations require the owner or admin organization role. The member role has been retired and is no longer a current first-class role; callers without owner/admin receive a 403. See roles and permissions.

Per-request options

Each JavaScript method accepts an optional trailing RequestOptions object; each Python method accepts equivalent keyword arguments. Both let you override the organization id, set extra query parameters, and control cancellation and timeout for a single call.
string
Override the organization context for this one call.
AbortSignal
JavaScript only. Cancel the in-flight request or stream. It is combined with the timeout signal, so an abort or a timeout both surface as a TimeoutError.
number
Override the client-level timeout for this call (milliseconds in JavaScript, seconds in Python).
object
JavaScript only. Extra query parameters; camelCase keys are converted to snake_case on the URL (for example pageSize becomes page_size).

How SDK operations map to REST

Each SDK method corresponds to exactly one REST route. The SDK builds the URL as base_url + path (no version prefix), sets the auth and organization headers, sends the request, and returns the parsed response. The example below shows the same operation — running a workflow — three ways: the raw REST call, then the Python and JavaScript SDK equivalents that wrap it.
The method executions.run maps to POST /workflows/run. To follow the run as it streams, pass the returned run_id to executions.listen, which maps to the SSE route GET /workflows/listen/{run_id} — see SSE run streaming and streaming and HITL in the SDKs. The end-to-end recipe is in run a workflow.
The legacy LLM-only mode of POST /workflows/run is deprecated and returns 410 Gone; for general agentic chat use assistant.chat (POST /assistant/chat) instead. See the Assistant and the parity matrix.

Naming and the resource grouping

Both SDKs expose 17 resource groups (for example client.workflows, client.executions, client.credentials). Method names follow each language’s convention: JavaScript uses camelCase (setDefault, getState) and Python uses snake_case (set_default, get_state) for the same route. A handful of methods diverge by more than casing — for example composer.focus in JavaScript is composer.set_focus in Python. The complete route-to-method table, including every name divergence, lives in the parity matrix.
The subscriptions resource is available in the Python SDK only; the JavaScript SDK has no subscriptions methods. Even in Python, only the read and link routes are wrapped (organization_plans, organization_billing, checkout_link, customer_portal); the wallet and plan-transition routes are not exposed by either SDK. For subscription and Stripe details see subscriptions and Stripe and the parity matrix.

Request and response casing

The SDKs differ in how they handle field casing on the wire, and this affects how you read responses.
  • JavaScript: request bodies and query keys you pass in camelCase are converted to snake_case before sending. Responses are not converted back — response fields stay snake_case (for example run_id, thread_id, created_at). This camelCase-in, snake_case-out asymmetry is intentional.
  • Python: requests and responses are snake_case end-to-end. Responses are Pydantic models that also support dict-style access, so both resp.status and resp["status"] work.
In the run example above, notice that even in JavaScript you read run.run_id (snake_case) from the response, while the request used workflowId (camelCase).

Retry and timeout behavior

Both SDKs retry transient failures automatically with exponential backoff and jitter, honoring a Retry-After header when present.
  • Retryable statuses: 429, 500, 502, 503.
  • Never retried: 400, 401, 403, 404, 409, 422 — these are thrown immediately.
  • Total attempts equal maxRetries + 1.
The SDKs differ on which methods are retried. JavaScript retries any verb on a retryable status (and network errors). Python retries only idempotent methods (GET/HEAD); POST, PUT, PATCH, and DELETE are never retried on an error status.
The Python SDK can send an Idempotency-Key header when you pass idempotency_key= to a mutating call, but the run endpoint assigns its own run_id, so it does not de-duplicate runs. See errors and retries.

Errors

Both SDKs map HTTP error statuses to typed exceptions that extend a base error class (ModulexError in both). Operations that pass through the billing admission gate — running workflows, Composer, Assistant, and managed knowledge — can return a flat DenialEnvelope with the shape {code, layer, key, current, limit, reason} as 402, 403, or 429. The two SDKs surface billing denials differently. Python maps 402 to PaymentRequiredError and raises billing subclasses keyed by the envelope layerQuotaExceededError (403), CreditExhaustedError (402), and WalletError (402). JavaScript has no payment-specific error class: a 402 falls through to the base ModulexError. The full taxonomy, including the three REST error-envelope shapes, is documented in errors and status codes, usage gating and limits, and SDK errors and retries.

Next steps

JavaScript SDK

Install, configure, and use the modulex-js client.

Python SDK

Install and configure the async modulex-python client.

Streaming & HITL

Consume SSE streams and answer human-in-the-loop prompts in both SDKs.

SDK to API parity

The full route-to-method map, with every gap and name divergence called out.