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 asmodulex-js; the Python package is published to PyPI as modulex-python and imported as modulex.
import and require. The Python SDK is built on httpx.AsyncClient and exposes a single async client class.
Create a client
You need a ModuleX API key (prefixmx_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.
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 noX-Authorizationheader anywhere in either SDK or the backend. (The backend also accepts an alternativeX-API-KEYheader, but neither SDK sends it.)X-Organization-ID: <org id>— added only when an organization id is resolved, scoping the request to one organization.
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.
Per-request options
Each JavaScript method accepts an optional trailingRequestOptions 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 asbase_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.
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 exampleclient.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.
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.statusandresp["status"]work.
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 aRetry-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.
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 layer — QuotaExceededError (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.