manifest.py constructs one IntegrationManifest instance, and the ModuleX runtime imports it through the modulex.tools entry-point group to drive credential setup, validation, and tool discovery. This page is the exhaustive field reference for that contract.
The contract is defined in schema.py in the modulex-integrations package. Once you understand the manifest, read the @tool function contract for the executable side, and Build an integration for the end-to-end workflow.
Every model in the schema sets
extra="forbid". Any field name that is not part of the contract — a typo, or a legacy field the new schema dropped — fails at import time, not at runtime. There is no silent acceptance of unknown keys.How the contract is consumed
The runtime discovers integrations through themodulex.tools Python entry-point group. Each entry point names a modulex_integrations.tools.<name> package that exposes exactly two public names:
manifest— oneIntegrationManifestinstance.TOOLS— a tuple of LangChainStructuredToolobjects, one per action.
@tool tuple into the action shape it serves to the model and the app. Two consequences shape the entire schema:
auth_schemasis a discriminated union keyed onauth_type. A single integration can expose more than one credential method — GitHub ships OAuth2 and a personal access token side by side.- Action output schemas are intentionally absent from the manifest. The return shape of every action is derived from its
@toolfunction’s return-type annotation (defined inoutputs.py) viaModel.model_json_schema()at startup — not declared in the manifest. See Output models are not in the manifest.
🎬 MEDIA PLACEHOLDER · MX-MEDIA-4180 · [IMAGE]
[IMAGE_DESCRIPTION]: Diagram of how a manifest flows from package to runtime.
[IMAGE_DETAILS]: Show a single integration package box containing
manifest.py (produces manifest), tools.py (produces TOOLS), and outputs.py (typed return models). An arrow labeled modulex.tools entry-point group points to the ModuleX runtime, which reads manifest and TOOLS, derives each action’s output schema from the @tool return annotation, and emits the credential UI, validation, and tool discovery surfaces. Landscape, light theme, no UI chrome, annotate the extra="forbid" import-time validation step.IntegrationManifest
The top-level model your manifest.py produces. Every other model on this page is reachable from here.
The only accepted value is
"tool". It distinguishes a tool integration from the LLM and knowledge providers, which live inside the ModuleX backend rather than in this package.The integration’s identifier. Must match the pattern
^[a-z][a-z0-9_]*$ — lowercase, snake_case, starting with a letter. It must equal the entry-point key and the package directory name (for example github, google_ads).The human-readable label shown in the app, for example
"GitHub".A one-line description of what the integration does.
The per-integration manifest version. This is not the
modulex-integrations package version.The integration author.
The logo reference. The convention is
"modulex:<name>-themed", for example "modulex:github-themed".The provider’s homepage URL.
Free-form category strings used to group the integration in the catalog, for example
["Developer Tools & Infrastructure", "version-control", "productivity"].The list of callable actions the integration exposes. See
ActionDefinition.The credential methods the integration supports, as a discriminated union on
auth_type. See Auth schemas. A single integration may list more than one variant.ActionDefinition
One callable action exposed by the integration. The return shape is not declared here — it is derived from the matching @tool function’s return annotation in tools.py and outputs.py.
The action name. Must match the pattern
^[a-z][a-z0-9_]*$ and must equal a @tool function name in the package’s TOOLS tuple.A one-line description of what the action does.
The action’s parameters, keyed by parameter name. See
ParameterDef.ParameterDef
A single action parameter.
The parameter type. One of
"string", "integer", "number", "boolean", "array", or "object".A one-line description of the parameter.
The default value when the parameter is omitted.
Whether the parameter must be supplied. Defaults to
false.Note the contrast with EnvVar.required, which defaults to true.actions example
Auth schemas: the discriminated union
AuthSchema is a Pydantic discriminated union over six variant classes, keyed on the auth_type literal. List one or more variants on IntegrationManifest.auth_schemas. GitHub, for example, ships OAuth2AuthSchema plus BearerTokenAuthSchema; Exa ships ApiKeyAuthSchema plus ModulexKeyAuthSchema.
Shared base fields
Every variant inherits these fields from_AuthSchemaBase:
The credential method’s label in the app, for example
"OAuth2 Authentication" or "Personal Access Token".A one-line description of the credential method.
An ordered list of human setup steps shown in the credential UI.
The secrets and settings the operator must provide for this method. See
EnvVar.The HTTP call the runtime makes to validate a configured credential. Optional — some
modulex_key “public API” integrations (for example instacart, hackernews) have no credential to validate and ship no test endpoint; the runtime skips credential testing in that case. See TestEndpoint.The six variants
Each variant adds only itsauth_type discriminator. OAuth2AuthSchema additionally requires an oauth_config.
| Class | auth_type | Extra field | Used by (verified samples) |
|---|---|---|---|
OAuth2AuthSchema | "oauth2" | oauth_config: OAuthConfig | github, google_calendar, netlify |
BearerTokenAuthSchema | "bearer_token" | — | github (personal access token) |
ApiKeyAuthSchema | "api_key" | — | exa, convertapi, hunter, freshdesk |
ModulexKeyAuthSchema | "modulex_key" | — | exa (managed key), hackernews, instacart, firecrawl, jina_ai |
CustomAuthSchema | "custom" | — | postgresql, woocommerce, coinbase, mintlify |
InternalAuthSchema | "internal" | — | None in the catalog — reserved variant |
OAuthConfig
Required on OAuth2AuthSchema only. Carries the OAuth 2.0 authorize and token endpoints plus per-provider flags.
The provider’s authorization URL.
The provider’s token-exchange URL.
The OAuth scopes to request, for example
["repo", "user", "read:org", "workflow"].How client credentials are presented at the token exchange — in the request body, or as HTTP Basic auth.
An extra authorize-URL parameter for providers that require an explicit opt-in to refresh tokens. Google needs
"offline" to issue a refresh_token.An extra authorize-URL parameter. Setting
"consent" forces the provider to re-issue a refresh token on every reconnect rather than only on the user’s first authorization.Whether the provider supports PKCE (RFC 7636) on the authorization-code flow. Defaults to
true. Set it to false for providers that reject an unexpected code_verifier with invalid_grant — Netlify is the shipped example. The ModuleX runtime reads this flag from the manifest when it builds the authorization URL (use_pkce=oauth_config.get("use_pkce", True)), so a manifest opt-out is honored end to end.A source-research note flagged
use_pkce as possibly not yet honored by the runtime (“the runtime currently hardcodes PKCE on”). Verified against the current backend: credentials.py reads oauth_config.get("use_pkce", True) when generating the authorization URL, so the manifest flag is honored. This page reflects the current behavior.OAuth2AuthSchema example
EnvVar
A configurable secret or setting the operator must provide, listed under an auth schema’s setup_environment_variables.
The environment-variable-style key, for example
GITHUB_OAUTH2_CLIENT_ID.The label shown in the credential UI.
A one-line description of the value.
Whether the value must be supplied. Defaults to
true — the opposite of ParameterDef.required, which defaults to false.Whether the value is a secret. When
true, it is masked in the UI (use it for client secrets and tokens).Whether the value is a server-level secret. When
true, the managed ModuleX app resolves it from the server environment, while a bring-your-own-app operator supplies their own (for example a GitHub OAuth client ID and secret, or a Google Ads developer_token).Whether the runtime guarantees this value is present in the credential’s
auth_data at action-execution time, so a tools.py function can read it. The value is read by its normalized (prefix-stripped, lowercased) key.When false (the default), the EnvVar is used only for OAuth provider configuration and the credential test endpoint, never for action calls.A placeholder hint shown in the UI, for example
"ghp_xxxx...".A link to where the operator obtains the value.
only_for_custom × inject_into_auth_data
These two flags together determine where a value comes from and whether it reaches your tools.py function:
TestEndpoint
The HTTP call the runtime makes to validate a configured credential. URL, header, body, and query-parameter values may contain placeholders such as {access_token}, {token}, {api_key}, {bearer_token}, or any auth_data or EnvVar key, which the runtime substitutes with the resolved credential value before making the call.
The endpoint URL. May embed query placeholders, for example ConvertAPI’s
?Secret={api_key}.The HTTP method for the validation call.
Request headers, for example
{"Authorization": "Bearer {access_token}"}.The URL query string, used by integrations that pass their credential as a query parameter.
A JSON payload sent on non-GET methods, for example Exa’s
POST /search validation call.An optional declarative directive for HTTP Basic auth synthesis. When set, the runtime builds the
Authorization: Basic <base64(u:p)> header itself and ignores any Authorization entry in headers. See BasicAuthSpec.How the runtime decides the validation call succeeded. See
SuccessIndicators.A free-form hint about the cost of the validation call. Observed values include
"free" and "minimal"; the schema does not constrain the set.A one-line description of what the validation call checks.
SuccessIndicators
How to tell a credential test endpoint succeeded.
The HTTP status codes treated as success, for example
[200].Keys expected to be present in the response body, for example
["login", "id"].BasicAuthSpec
Declarative Basic auth for test endpoints where the Base64 cannot be precomputed because one or both halves are user secrets. The runtime resolves each placeholder against auth_data; if a placeholder name is not a key in auth_data, it is treated as a literal string (Mailgun, for example, uses username_placeholder="api").
The auth synthesis type. The only value is
"basic".The placeholder or literal used as the Basic auth username.
The placeholder or literal used as the Basic auth password.
TestEndpoint with BasicAuthSpec
Output models are not in the manifest
NeitherActionDefinition nor IntegrationManifest carries an output schema. The return shape of every action is derived at backend startup from its @tool function’s return-type annotation: the runtime resolves the annotation, and if the return type has a model_json_schema() method, it calls it to produce the model-facing output schema.
This is why your action’s typed Pydantic return model lives in outputs.py, and why the @serialize_pydantic_return wrapper must preserve the annotation while coercing the runtime value to a dict. Reading a manifest alone will never reveal an action’s output shape — see the @tool function contract for the full output-model and decorator rules.
Full manifest example
This abridged GitHub manifest shows the whole contract assembled: two auth schemas (OAuth2 and a personal access token) and a set of actions.🎬 MEDIA PLACEHOLDER · MX-MEDIA-4181 · [SCREENSHOT]
[SCREENSHOT_DESCRIPTION]: The credential setup UI rendered from a manifest’s auth schemas.
[SCREENSHOT_DETAILS]: Capture the in-app “Connect integration” panel for an integration that ships multiple auth schemas (GitHub is ideal — OAuth2 plus Personal Access Token). Show the method selector reflecting the two
auth_schemas, the EnvVar fields rendered with their display_name labels, a masked sensitive=True field, and the setup_instructions list. Light theme, desktop width, no real secret values visible.Validation rules to remember
Unknown fields fail at import time
Unknown fields fail at import time
Every model sets
extra="forbid". A misspelled field name or a leftover legacy field stops the package from importing — you find out immediately, not when a credential is created.name patterns are enforced
name patterns are enforced
Both
IntegrationManifest.name and ActionDefinition.name must match ^[a-z][a-z0-9_]*$. The integration name must also equal the entry-point key and the package directory name; each action name must equal a @tool function name in TOOLS.required defaults differ between ParameterDef and EnvVar
required defaults differ between ParameterDef and EnvVar
ParameterDef.required defaults to false; EnvVar.required defaults to true. Set them explicitly when the default is not what you want.test_endpoint is optional
test_endpoint is optional
A
modulex_key “public API” integration with no credential to validate can omit test_endpoint entirely; the runtime then skips credential testing.The manifest is not the callable surface
The manifest is not the callable surface
Manifest
parameters are catalog metadata. The @tool function’s args_schema is the authoritative list of arguments an action accepts, and output shapes come from the function’s return annotation, not the manifest.Next steps
@tool function contract
The decorator order, auth parameter patterns, and Pydantic output models that pair with this manifest.
Build an integration
The end-to-end workflow for authoring and registering your own integration.
Authentication & credentials
How the six auth schema variants map onto credential setup and OAuth2 (PKCE).
Glossary
Canonical definitions for every manifest term used on this page.