Skip to main content
Use this page to find the exact modulex-js and modulex-python method for any backend REST route — and to see, at a glance, where the two SDKs diverge in coverage, naming, or behavior. It is the single source of truth for “which method calls this endpoint” and “does this endpoint have a method at all.” Every row traces to a backend router and a per-SDK method. Where the SDKs disagree, both sides are shown. For the operation-level request and response detail, follow the route into the Endpoints reference; for the SDK client setup, see the JavaScript SDK and Python SDK pages.
This matrix covers method-to-route coverage and cross-SDK naming and behavior divergence. Per-route status codes and error envelopes live in Errors and status codes; SSE frame shapes live in SSE run streaming; credit gating lives in Usage gating and limits.

How to read this page

The SDKs are thin clients over the backend REST surface. A method calls base_url + path and returns the response. Three rules make the mapping predictable, and three structural facts make it diverge. The predictable rules:
  • Method names follow the route, with casing per language. JavaScript uses camelCase verbs (setDefault, getState); Python uses snake_case (set_default, get_state). The naming divergences section lists the handful of cases where the names differ by more than casing.
  • Auth is identical in both SDKs. Every request sends Authorization: Bearer mx_live_… plus X-Organization-ID when an organization is resolved. See Authentication. There is no X-Authorization header anywhere — neither SDK sends it and the backend does not read it.
  • One backend route can map to differently-grouped SDK methods. For example, GET /workflow-runs is workflowRuns.list in JavaScript but executions.list_runs in Python.
The structural divergences — covered in detail below — are: the JavaScript SDK returns raw, un-modeled responses while Python returns typed Pydantic models; the subscriptions resource exists only in Python; and environment-variable fallback exists only in Python.

The three structural divergences

These three facts are load-bearing for any side-by-side SDK code. Read them before the matrix.
The JavaScript SDK takes camelCase parameters and converts them to snake_case before sending (convertKeysToSnakeCase), but it does not convert responses back — it returns the parsed JSON verbatim, so response fields stay snake_case (run_id, created_at). A JS call is effectively response.json() as T: there is no runtime model, no validation, and no field renaming on the way out. This is the JS-only “raw response” behavior — camelCase in, snake_case out, asymmetric by design.The Python SDK is snake_case end to end. Requests are built as snake_case dicts verbatim, and responses are parsed into Pydantic models (configured extra="allow", with a dict-compatible shim). Field names are stable in both directions.
JavaScript
Python
There are two casing traps inside the JS request body itself: DSL config fields are already snake_case in TypeScript (integration_name, system_prompt), so the converter is a no-op on them, and UpdateChatParams.is_private is snake_case in the TS parameter surface — both break the otherwise-camelCase JS convention.
modulex-python exposes a full client.subscriptions resource (four methods). modulex-js has no subscriptions resource and no subscriptions methods — there is no subscriptions.ts in the JS SDK at all. This is a real parity gap, not a naming difference. See the subscriptions table and Subscriptions and Stripe.The JavaScript SDK surfaces billing only indirectly: organizations.invitePreview returns a prorated seat cost, and the dashboard analytics payloads carry subscription and current_month_credit_usage fields. If you need plan, billing, checkout, or customer-portal calls from JavaScript, call the REST routes directly with fetch.
The Python client reads MODULEX_API_KEY, MODULEX_BASE_URL, and MODULEX_ORGANIZATION_ID from the environment when the corresponding argument is omitted. The JavaScript client reads none of them — apiKey must be passed to the constructor or it throws, and baseUrl / organizationId are constructor-only.
JavaScript
Python
The JavaScript README shows process.env.MODULEX_API_KEY in example code, but that is caller code, not SDK behavior. timeout, max_retries, and default_headers have no environment fallback in either SDK.

Header divergences (the small ones)

The auth and org headers are identical across both SDKs. Only two header behaviors differ:
Idempotency-Key does not de-duplicate runs. Python plumbs an idempotency_key argument through to the Idempotency-Key header, but POST /workflows/run assigns its own run_id, so passing it does not prevent a duplicate run. JavaScript does not send the header. Do not rely on Idempotency-Key to make executions.run safe to retry; see Errors and retries.

Resource-group coverage

Both SDKs advertise 17 resource groups, but the sets differ by one: JavaScript lacks subscriptions. Two more groups are structured differently: Per-resource method counts:
Two count differences are tally artifacts, not coverage gaps. The credentials sets are equivalent — Python counts its sync SSE factory (bulk_modulex_keys_stream) separately, so it reads “15 + 1” against JS’s “17”. The organizations sets cover the same 11 routes; the JS note tallies 12. Treat both rows as full parity by route. The open question on the organizations tally is tracked in Open questions.

The route-to-method matrix

Legend: a check means the SDK has a method for that route; a cross means it does not. Paths are backend-relative — the SDK calls base_url + path. Where a method name differs by more than casing, the cell shows the exact name.

auth

POST /auth/organizations/leave requires X-Organization-ID.

workflows (CRUD and builder)

workflows.create, update, and delete require an owner or admin role. DELETE /workflows/{id} is a hard delete. listAll / list_all are auto-paginators over the same GET /workflows route, not separate endpoints.

executions (run, state, resume, cancel, listen)

These routes live under the /workflows router; on the SDK side they are executions.*. The legacy LLM-only mode of POST /workflows/run returns 410 Gone; use assistant.chat for agentic chat. getState / get_state return 404 (not 403) when you do not own the thread.
Three distinct “run id” identities. Do not assume one identifier across these calls. The per-execution run_id is what SSE, status, and history use; a new run_id is minted on every agent resume. The durable run_id keys the run record. The id field (returned by list/get) is a third identifier — and it, not the execution run_id, is what workflowRuns.get / get_run take. See Workflows and runs.

workflow-runs (durable history)

This is the clearest grouping divergence: JavaScript exposes a separate workflowRuns resource; Python folds run history into executions. {runPk} is the run record’s id (returned by list/get), not the execution run_id. iter_runs is a Python-only offset auto-paginator over GET /workflow-runs.

deployments

List items omit workflow_id and description. DeactivateDeploymentResponse.previous_live_deployment_id is optional in both SDKs (omitted on a no-op).

schedules

POST /schedules requires a live deployment. The admin tick route (include_in_schema=False) has no SDK method in either SDK. The exact response keys of retryRun / retry_run are an open question.

composer

POST /composer/chat returns 409 if a HITL prompt is pending, and 402 / 403 / 429 from the billing gate. resume mints a new run_id; its llm parameter is optional at the type level but required in production (the backend returns 400 without it). The focus / set_focus name difference is intentional.

assistant

POST /assistant/chat returns 409 on a pending HITL prompt or a run already in progress, plus 402 / 403 / 429 from the billing gate. Unlike composer, assistant.resume’s llm parameter is required at the type level in both SDKs. There is no assistant rename route on the backend (PATCH /assistant/chat/{id} / .../title does not exist) — this is an unbuilt route, not an SDK gap, so no method references it. See Assistant permissions and limits.

chats

chats.list groups by folder with no pagination. chats.messages uses offset/limit, and its body omits total / has_next despite the docstring. chats.delete is a soft delete with no permanent flag (unlike composer/assistant delete). There is no POST /chats route or method — chats are created implicitly by execution, so this is not a gap.

credentials

GET /credentials/oauth2/callback is a browser redirect target and has no SDK method by design. credentials.list returns a discriminated grouped/flat union in JavaScript but raw Any in Python (the backend types it Dict[str, Any]). The JS MCPServerCredentialResponse omits updated_at, integration_type, last_used_at, and expires_at versus the full CredentialResponse. bulkModulexKeys / bulk_modulex_keys_stream is a sync SSE factory on both sides.
The SDK credentials.refreshOAuth2 / refresh_oauth2 methods exist and map to a real backend route, but the app’s “refresh OAuth2” flow is a known limitation — the UI’s BFF route is missing. When an OAuth credential expires, reconnect it rather than relying on an in-app refresh.

integrations

GET /integrations/tools is admin-gated (organization_admin_required). Several detail methods carry a name difference (tool / tool_detail, and so on). See the Integrations overview.

knowledge-bases

Full 1:1 coverage (19 methods each). Note the two name differences beyond casing: documents / list_documents and searchMultiple / multi_search. Managed (modulexdb) search and ingest are billed in credits — see Managed knowledge.

organizations

The member role is retired. Invite and role-update methods accept only 'admin'; passing 'member' returns 422. The live organization roles are owner and admin only, and composer / assistant / knowledge routes require owner or admin (organization_admin_required). The Python assistant docstring’s “org member access” wording understates this — a former member is treated as having no access. See Roles and permissions.

api-keys

organization_id is sent in the request body here, not via the X-Organization-ID header. apiKeys.revoke is unusual: the backend DELETE returns a JSON body with 200 (not 204), so both SDKs parse a result — unlike credentials/knowledge delete, which return 204.

dashboard

All five dashboard routes return HTTP 200 with a success:false envelope on failure, bypassing the SDK error-mapping path. JavaScript callers must branch on the success field rather than rely on a thrown error; the Python SDK handles the nested-data envelope via its page unwrapper. See Errors and status codes.

notifications

notifications.create maps to /notifications/organization, not /notifications. GET /notifications returns a null organization_id without a valid org context. POST /notifications/system is commented out backend-side, so neither SDK has a method — not a gap.

system

GET /system/health is a JavaScript gap: client.system is documented as “health and utility” but exposes no health() method. Python has system.health. timezones/search requires q to be at least 2 characters (else 422).

subscriptions (Python-only)

This is the largest parity gap. The four subscriptions.* methods exist only in Python; the wallet and transition routes have no method in either SDK. To call any of these from JavaScript — or to call the wallet/transition routes from either SDK — issue the REST request directly:
See Subscriptions and Stripe and Wallet and top-ups for what these routes do.

Method-name divergences

For the same backend route, these names differ by more than casing. Everywhere else, the rule is JS camelCase ↔ Python snake_case of the same verb (setDefaultset_default, getStateget_state, retryRunretry_run).

Pagination and auto-paginators

Pagination style is per-resource, and the SDKs add convenience auto-paginators over the same routes — these are not separate endpoints. See Pagination. The JavaScript SDK auto-paginates workflows only. Python adds executions.iter_runs (offset over GET /workflow-runs) and workflows.list_all (page). Both auto-paginators are async iterators that call the underlying list route repeatedly.

Coverage gaps (explicit)

A complete accounting of where coverage is not 1:1.

Routes with no method in either SDK

These are intentionally unexposed (webhook receivers, internal intake, ops diagnostics, super-admin console, and the wallet/transition billing routes).

Routes present in one SDK only

Backend-route coverage

None. Every documented method in both SDKs maps to a real backend route. The only “method without a 1:1 typed route” cases are convenience auto-paginators (workflows.listAll / list_all, executions.iter_runs) that re-call an existing list route, and the bulk-keys SSE factory that maps to POST /credentials/bulk-modulex-keys/stream. If a future method ships without a route, it must be flagged here.

Behavior divergences for the same route

These are not coverage gaps, but they change how you write code against the same endpoint in each SDK. The 402 / 410 typing difference matters for retry logic — the Python SDK gives you billing-specific exception classes to branch on, while JavaScript surfaces both as the base error. See SDK errors and retries and Errors and status codes.

Open questions

These items are unresolved against source and are documented rather than guessed:

SDKs overview

Install once, use across every operation.

JavaScript SDK

Install, configure, and the raw-response behavior.

Python SDK

Async client, env fallback, and the subscriptions resource.

Errors and retries

Error classes, retry policy, and the idempotency no-op.