Skip to main content
This guide takes you from a fresh account to your first authenticated request. You will create an organization, mint a mx_live_ API key, and call the API three ways — with cURL, the Python SDK, and the JavaScript SDK. Every ModuleX request authenticates with two pieces:
  • Authorization: Bearer mx_live_… — your API key, sent as a Bearer token.
  • X-Organization-ID: <org-id> — the organization the request acts in, required on every organization-scoped endpoint.
The auth header is Authorization: Bearer, not X-Authorization. ModuleX does not recognize an X-Authorization header. If you are migrating from older notes or third-party snippets that use it, switch to Authorization: Bearer. The backend also accepts the key in an X-API-KEY header as an alternative, but the official SDKs always send Authorization: Bearer.

Prerequisites

1

Sign in and pick an organization

Sign in to the ModuleX app. On first sign-in, a personal organization is provisioned for you automatically, so you always have at least one organization to act in. You can create more from the organization switcher. Every API call is scoped to exactly one organization — see Organizations, roles & membership.
2

Confirm your role

API keys are created by a signed-in user, and they inherit that user’s access. The live organization roles are owner and admin (the member role has been retired). You do not need to be an owner to create a personal API key, but the actions a key can perform are still gated by your role and by usage limits. See Roles & permissions.
3

Have a runtime ready

For the SDK examples you need Node.js 18+ (for the JavaScript SDK) or Python 3.9+ (for the async Python SDK). For the cURL examples you only need a terminal.

Step 1 — Create an API key

API keys are managed in the app, under your account’s API keys settings. Creating a key is the only time the full secret is shown — copy it immediately and store it somewhere safe. ModuleX stores only a salted hash and a short hint (for example mx_live_2J9vK4xM********), so a lost key cannot be recovered and must be replaced. When you create a key you can set the following fields.
string
required
A human-readable label for the key (1–255 characters), for example CI/CD or Production worker. Shown in the key list to help you identify it later.
string
default:"null"
Optional. Scopes the key to a single organization that you are a member of. When set, the key can only be used with a matching X-Organization-ID header — any other organization returns 403. When omitted (null), the key works across all organizations you belong to, and you choose the organization per request via the header.
string (ISO 8601)
default:"null"
Optional expiry timestamp. After this time the key is rejected with 401. Omit (null) for a key that never expires.
integer
default:60
Optional per-key request limit (1–1000). Defaults to 60 requests per minute. A separate per-user limit of 300 requests per minute applies across all of your keys combined.
The create response is the only place the full key appears.
string
The key’s unique identifier (UUID). Use it to fetch or revoke the key later.
string
The label you provided.
string
The full secret, for example mx_live_2J9vK4xM8nP3wQ7tR5sL1yB6cF0dG2h4vN8pX5mK9rT3wY7u. Returned once, only on creation. Every other endpoint returns the masked form instead.
string
The first 8 characters of the random part, used to recognize the key without exposing it.
string
The display form, for example mx_live_2J9vK4xM********.
string | null
The organization the key is scoped to, or null for an unscoped key.
integer
The per-key limit applied to this key.
string | null
The expiry timestamp, or null if the key never expires.
boolean
Whether the key is currently usable. Becomes false after revocation.
string
When the key was created (ISO 8601).
A mx_live_ key is mx_live_ followed by roughly 43 random Base62 characters. Treat it like a password: never commit it to source control or paste it into client-side code. Read it from an environment variable or a secrets manager instead.

Step 2 — Find your organization ID

Every organization-scoped request needs the X-Organization-ID header. You can read your organizations from GET /auth/me/organizations, which lists each organization’s id, slug, name, and your role in it. The id value is what goes in the header. GET /auth/me/organizations is not organization-scoped, so it does not itself require the X-Organization-ID header — it returns every organization you belong to.
A successful response looks like this:
Copy the id of the organization you want to work in. You will pass it as X-Organization-ID on every organization-scoped call.

Step 3 — Make your first authenticated call

With your key and organization ID in hand, call an organization-scoped endpoint. GET /organizations/llms returns the language-model catalog for your organization, including which models have credentials connected — a good first call that exercises both the Authorization and X-Organization-ID headers. In the SDKs you set the organization once on the client (it is sent as X-Organization-ID automatically) and override it per call when you need a different organization.
A 200 response confirms your credentials work. If you get an error instead, jump to Troubleshooting below.
GET /organizations/llms requires the admin or owner role. If your role is not admin or owner you will get a 403 — use GET /auth/me (which needs no role and no organization header) to confirm authentication on its own first.

Install the SDKs

The SDK examples above assume you have the relevant package installed.
Both SDKs are thin, typed clients over the same REST API — every method maps to one HTTP call. The base URL defaults to https://api.modulex.dev and has no version prefix: do not append /api or /v1. See Base URLs, environments & versioning.

Client configuration

The two SDKs take the same options under language-idiomatic names (camelCase in JavaScript, snake_case in Python).
Environment-variable fallback differs by SDK. The Python SDK reads MODULEX_API_KEY, MODULEX_ORGANIZATION_ID, and MODULEX_BASE_URL when the matching argument is omitted, so you can construct Modulex() with no arguments if those are set. The JavaScript SDK has no environment-variable fallback — you must pass apiKey explicitly; reading process.env.MODULEX_API_KEY yourself is just your own code, not SDK behavior. Full details: JavaScript SDK and Python SDK.
Reading the key from the environment looks like this:
Response fields stay snake_case on the wire (active_llm_total, created_at, organization_ids). The JavaScript SDK converts your request keys from camelCase to snake_case automatically, but it does not convert responses back — read response fields in snake_case in both SDKs.

How authentication is evaluated

Understanding the order the backend checks credentials helps you read errors correctly.
1

Credential resolution

The backend reads Authorization: Bearer <token> first. If the token starts with mx_live_ it is treated as an API key; otherwise it is treated as a Clerk JWT (the app’s user-login path). If no Bearer credential resolves, it falls back to the X-API-KEY header (which must also start with mx_live_). With neither present, the request is rejected with 401.
2

Key validation and rate limits

The key is looked up by its hash and must be active and unexpired. Each request consumes from the key’s per-minute limit (default 60) and your per-user limit (300 across all keys). Exceeding either returns 429 with X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, and Retry-After headers.
3

Organization scope

For organization-scoped endpoints, the backend reads X-Organization-ID. A missing header returns 400. If your key is scoped to a specific organization, the header must match that organization or you get 403. If you are not a member of the organization in the header, you get 403.
For the full authentication reference — including the Clerk JWT path used inside the app — see Authentication and the JWT vs API key auth model.

Troubleshooting

No valid credential was found, or the key is wrong, revoked, or expired. Confirm you are sending Authorization: Bearer mx_live_… (not X-Authorization), that the key is copied in full, and that it has not been revoked or passed its expires_at. Test the key in isolation with GET /auth/me, which needs no organization header.
You called an organization-scoped endpoint without the X-Organization-ID header. Add it with an organization ID from GET /auth/me/organizations. In the SDKs, set organizationId / organization_id on the client or pass it per call.
Three causes share this status: the organization in X-Organization-ID is not one you belong to; your key is scoped to a different organization than the header; or the endpoint requires the owner or admin role and yours is lower. Verify your membership and role with GET /auth/me/organizations.
You exceeded the per-key (default 60/min) or per-user (300/min) limit. Read the Retry-After header and back off; both SDKs retry 429 automatically up to maxRetries / max_retries, honoring Retry-After. See Rate limiting.
On run, Composer, Assistant, and managed-knowledge surfaces, a usage denial returns a flat envelope {code, layer, key, current, limit, reason} (not wrapped in detail). The layer maps to the status: credit/wallet402, quota403, rate429. This is distinct from the plain {"detail": "…"} shape on standard CRUD routes. See Errors & status codes and Usage gating & limits.

Next steps

Make your first API call

Run a workflow programmatically and stream its result in cURL, Python, and JavaScript.

Authentication reference

The complete header, token, and error reference for authenticating every request.

API overview

Base URLs, the request lifecycle, and how every operation is shown three ways.

SDKs overview

Install and configure the JavaScript and Python SDKs once, then use them everywhere.