credential_id, an auth_type, an owning organization, and an optional display_name. This page covers the full lifecycle: create and connect, inspect and test, set a default, rotate, and revoke — both in the app and via the API.
For the auth-type variants themselves (API key, OAuth2 with PKCE, bearer, and the other schemas) and the OAuth2 connect flow in depth, see Authentication & credentials and Credentials & OAuth2. For request lifecycle and base URLs, see the API overview.
Credentials are organization-scoped. The organization is always taken from the
X-Organization-ID header (and the authenticated caller’s active org) — never from the request body. A credential created under one organization is invisible to every other organization. See Org context & X-Organization-ID.Who can manage credentials
Every credential endpoint requires an owner or admin role on the organization (the backend dependency isorganization_admin_required). The member role is retired and is not a current first-class role. See Roles & permissions.
The credential CRUD and OAuth endpoints have no per-call credit gate — they return the standard
{"detail": ...} error envelope, not the flat DenialEnvelope. The credit gate runs later, at execution time, only for ModuleX-managed (modulex_key) credentials. There is no 402 on these routes. For the gated surfaces and the DenialEnvelope shape, see Errors & status codes and Usage gating & limits.Authenticate every request
All examples use the same auth as the rest of the API: anAuthorization: Bearer token (a mx_live_* API key, or a Clerk JWT from the app) plus the X-Organization-ID header. The SDKs send both for you once configured. See Authentication.
Create and connect a credential
In the app
Via the API
POST /credentials creates a credential. The body is read raw and the credential type is auto-detected from auth_data / auth_type / integration_name — you do not send a type field. Returns 201 Created with a CredentialResponse.
string
required
The integration to attach the credential to (for example
slack, openai, github). Must be a live integration. For an external MCP server, prefer the dedicated MCP create path described in Authentication & credentials.object
The secret payload, encrypted at rest. The presence of specific keys selects the credential type:
string
Explicit auth type. Required for
modulex_key (ModuleX-managed pooled key — send no auth_data) and custom. For api_key, bearer_token, and oauth2, the type is inferred from auth_data and you may omit it. Accepted values: oauth2, api_key, bearer_token, modulex_key, custom. (The backend also tolerates a legacy bearer.)object
OAuth2 client configuration (
client_id, client_secret, token_url, and related fields) sent only when creating an oauth2 credential directly with an access_token. Most OAuth2 credentials are created by the connect flow on the Authentication & credentials page rather than this field.string
A human-friendly label, for example
Production Slack. Defaults to a generated name.object
Optional free-form metadata stored alongside the credential.
boolean
default:"false"
When
true, this credential becomes the default for its integration, unsetting any prior default.string
Optional ISO-8601 expiry timestamp (
SDK only — accepted by the SDK create methods).string
The unique identifier for the credential. Use it for every subsequent operation.
string
The integration this credential belongs to.
string
One of
tool, llm_provider, knowledge_provider.string
The credential’s label.
string
The detected auth type:
oauth2, api_key, bearer_token, modulex_key, or custom.boolean
Whether this credential is the default for its integration.
string
ISO-8601 creation timestamp.
string
ISO-8601 last-update timestamp.
string
ISO-8601 timestamp of last use, or
null if never used.string
ISO-8601 expiry, or
null if the credential does not expire.400 validation (CredentialValidationError / CredentialServiceError, including invalid auth_data), 401 / 403 auth, 429 rate limit, 500 on unexpected failure.
List and inspect credentials
List
GET /credentials returns credentials grouped by integration. Supplying integration_name switches to a flat list for that one integration.
string
Filter to a single integration and return a flat list instead of the grouped shape.
string
Filter by auth type (
oauth2, api_key, bearer_token, modulex_key, custom).integer
default:"100"
Page size. Range
1–500.integer
default:"0"
Number of items to skip. Minimum
0.logo, total_count, and the set of auth_types present:
Grouped response
Response fields are snake_case on the wire (for example
credentials_metadata, auth_type, display_name). The SDKs convert to their own conventions (integrationName in JS, snake_case in Python).Get one credential
GET /credentials/{credential_id} returns one credential with masked secrets. Pass include_masked=true to also receive a per-field map of masked values. The secret itself is never returned — only labels and masked fragments (for example xoxb***-end).
boolean
default:"false"
When
true, add a dict of per-field masked auth values to the response.organization_id, created_by, created_by_email, and auth_data_masked (a label for OAuth2/managed keys, or a masked secret for API-key and bearer credentials).
Errors: 404 not found, 403 access denied, 400 service error.
Test a credential
ModuleX can validate a credential against the integration’s declared test endpoint.POST /credentials/test-temporaryvalidates an unsaved credential before you store it (used by the test-before-save step in the app). Body:integration_name,auth_type(api_key/bearer_token/oauth2),auth_data.POST /credentials/{credential_id}/testvalidates a credential you have already saved. A credential past itsexpires_atreturnsis_valid: falsewithCredential has expired.
is_valid: true with a test_method of none or basic and a “no test endpoint” message.
test-temporary response
boolean
Whether the credential passed validation.
string
Human-readable result detail.
string
How validation ran:
api_call, basic, or none.string
ISO-8601 timestamp of the test.
test-temporary wraps any failure as 500 (Failed to test credential: ...). The saved-credential test returns 404 / 403 for missing or forbidden credentials.
Set a default credential
When an integration has more than one credential, ModuleX resolves which one to use in this precedence order: an explicitly requestedcredential_id, then the credential marked default, then the most recent valid user credential, then the most recent valid ModuleX-managed (modulex_key) credential. POST /credentials/{credential_id}/set-default marks a credential as the default and unsets any prior default for the same integration.
PUT /credentials/{credential_id} (body: display_name?, metadata? — secrets are not updatable here; to change a secret, rotate).
Rotate a credential
There is no dedicated rotate endpoint. Rotation in ModuleX is a deliberate three-step pattern: create the replacement, promote it to default, then revoke the old one. This keeps the integration usable throughout — the new credential is in place and default before the old secret is removed.Revoke a credential
Revoking deletes the credential permanently.DELETE /credentials/{credential_id} returns 204 No Content; in the app, open the credential’s detail panel and choose Delete.
404 not found, 403 access denied, 400 service error.
Per-organization scope and isolation
Every credential belongs to exactly one organization, and the active organization is fixed by theX-Organization-ID header on each request — it is never read from the body. The practical consequences:
- No cross-org access. A credential created in one organization cannot be listed, fetched, tested, or used from another. Switching organizations changes the credential set entirely.
- Encryption is org-scoped. Each credential’s secret is encrypted with a key derived from the organization ID and the credential ID, so ciphertext cannot be reused across credentials or organizations. See Data security & encryption.
- Owner/admin only. Because credentials are organization-wide, only owners and admins can create, change, or revoke them. See Roles & permissions.
- Resolution stays in-org. When a tool or LLM node runs, ModuleX resolves the credential within the same organization using the precedence in Set a default credential.
Auditing credential changes
GET /credentials/{credential_id}/audit returns the change history for a credential from the unified audit log. Logged operations include CREDENTIAL_CREATED, CREDENTIAL_UPDATED, CREDENTIAL_DELETED, CREDENTIAL_ROTATED, CREDENTIAL_REVOKED, CREDENTIAL_ACTIVATED, CREDENTIAL_DEACTIVATED, and MCP_DISCOVERY_REFRESHED.
integer
default:"100"
Page size. Range
1–500.integer
default:"0"
Number of items to skip. Minimum
0.A usage-statistics endpoint (
GET /credentials/{credential_id}/usage) and the audit response are documented in the API reference, but both have known field-shape issues today — treat their exact response fields as TBD until verified against a live response. See Known limitations.Errors
Credential endpoints return the standard{"detail": <string>} envelope (the dict form only for the rate-limit 429). There is no DenialEnvelope and no 402 on these routes.
For the full error model across all surfaces — including the three error-envelope shapes and the billing
DenialEnvelope on gated endpoints — see Errors & status codes.
Related pages
API overview
Request lifecycle, base URLs, and how every operation is shown three ways.
Authentication & credentials
The auth-type variants and the OAuth2 (PKCE) connect flow.
Credentials & OAuth2
How ModuleX stores and resolves credentials, conceptually.
Roles & permissions
Which actions require an owner or admin role.