Skip to main content
A credential is a stored, encrypted auth record that links your organization to an integration (a tool, an LLM provider, or a knowledge provider). Each credential has a 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 is organization_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: an Authorization: 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.
Errors: 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 1500.
integer
default:"0"
Number of items to skip. Minimum 0.
The grouped response keys each integration by name; each group lists its credentials with the per-credential fields above plus the integration 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.
The detail response adds 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-temporary validates 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}/test validates a credential you have already saved. A credential past its expires_at returns is_valid: false with Credential has expired.
If the integration declares no test endpoint, the test returns 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.
Errors: 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 requested credential_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.
You can also update a credential’s label or metadata in place with 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.
The manual OAuth2 token-refresh endpoint (POST /credentials/{credential_id}/oauth2/refresh) and the app’s refreshOAuth2 action are known to be broken and must not be relied on to rotate or refresh OAuth2 credentials. To refresh an expired OAuth2 connection, reconnect the integration through the OAuth connect flow (which creates a fresh credential), then revoke the stale one. ModuleX also refreshes OAuth2 tokens automatically at execution time when a token is within 5 minutes of expiry, so most refresh happens without any manual step. See Known limitations.

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.
Deletion is permanent — there is no soft-delete or undo. If the deleted credential was the default for its integration, any node or agent that relied on default resolution will fall through to the next valid credential, or fail with no credential found if none remains. Rotate (create the replacement first) instead of deleting in place for production integrations.
Errors: 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 the X-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 1500.
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.

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.