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.
| Outcome | HTTP status | When |
|---|---|---|
Missing X-Organization-ID | 400 | No org context on the request |
| Not a member of the org | 403 | Caller is not in the organization |
| Member but not owner/admin | 403 | Role check fails |
| Inactive user | 403 | The user account is not active |
| Rate limited | 429 | The org-class api rate limit (fail-open), with X-RateLimit-* and Retry-After headers |
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
- Open Settings → Credentials (or Browse to start from the integration catalog), then choose the integration you want to connect.
- Pick the auth type the integration supports —
oauth2,api_key,bearer_token,modulex_key, or acustomschema with fields from the integration’s manifest. - For API key, bearer, and custom types, fill in the secret fields and select Test to validate before saving (see Test a credential). For OAuth2 you are redirected to the provider’s consent screen, then back to ModuleX.
- Optionally set a display name and toggle Make default so this credential is used automatically for the integration.
- Save. The secret is encrypted at rest and only a masked form is ever shown again.
🎬 MEDIA PLACEHOLDER · MX-MEDIA-4030 · [APP_VIDEO]
[APP_VIDEO_DESCRIPTION]: Connecting an integration credential from Settings → Credentials, including the test-before-save step and the OAuth2 redirect.
[APP_VIDEO_DETAILS]: Record in the live app: open Settings → Credentials, pick an API-key integration (e.g. Tavily), enter a key, run Test (show the success toast), set a display name, toggle Make default, save. Then show an OAuth2 integration (e.g. GitHub) redirecting to consent and returning with a created credential. 30-45s, light mode, 16:9, highlight the Test and Make default controls.
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.
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.The secret payload, encrypted at rest. The presence of specific keys selects the credential type:
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.)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.A human-friendly label, for example
Production Slack. Defaults to a generated name.Optional free-form metadata stored alongside the credential.
When
true, this credential becomes the default for its integration, unsetting any prior default.Optional ISO-8601 expiry timestamp (
SDK only — accepted by the SDK create methods).The unique identifier for the credential. Use it for every subsequent operation.
The integration this credential belongs to.
One of
tool, llm_provider, knowledge_provider.The credential’s label.
The detected auth type:
oauth2, api_key, bearer_token, modulex_key, or custom.Whether this credential is the default for its integration.
ISO-8601 creation timestamp.
ISO-8601 last-update timestamp.
ISO-8601 timestamp of last use, or
null if never used.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.
Filter to a single integration and return a flat list instead of the grouped shape.
Filter by auth type (
oauth2, api_key, bearer_token, modulex_key, custom).Page size. Range
1–500.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).
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
Whether the credential passed validation.
Human-readable result detail.
How validation ran:
api_call, basic, or none.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.- Create a new credential for the same integration with the new secret (
POST /credentials). - Promote it with
make_default: trueon create, orPOST /credentials/{new_id}/set-defaultafterward, so resolution prefers it immediately. - Revoke the old credential with
DELETE /credentials/{old_id}once the new one is verified.
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.
🎬 MEDIA PLACEHOLDER · MX-MEDIA-4031 · [IMAGE]
[IMAGE_DESCRIPTION]: Diagram showing two organizations, each with its own isolated set of integration credentials, with the
X-Organization-ID header selecting the active org’s credential scope.
[IMAGE_DETAILS]: Two side-by-side org boxes (Org A, Org B), each containing distinct credential records for the same integrations (e.g. Slack, GitHub). An incoming API request labeled X-Organization-ID: Org A routes only to Org A’s credentials; a dashed line to Org B is crossed out. Note “encrypted per (org, credential)”. Clean, light mode, 16:9.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.
Page size. Range
1–500.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.
| Status | Meaning |
|---|---|
200 | Read, update, set-default, or test succeeded |
201 | Credential created |
204 | Credential deleted (revoked) |
400 | Missing X-Organization-ID, invalid auth_data / auth_type, or service validation error |
401 | Missing or invalid auth token |
403 | Not a member, or not owner/admin |
404 | Credential not found in this organization |
429 | Org-class API rate limit (fail-open); see X-RateLimit-* and Retry-After headers |
500 | Unexpected error (and the test-temporary failure wrapper) |
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.