Skip to main content
Every ModuleX integration declares itself through a single Pydantic contract. Your integration’s 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 the modulex.tools Python entry-point group. Each entry point names a modulex_integrations.tools.<name> package that exposes exactly two public names:
  • manifest — one IntegrationManifest instance.
  • TOOLS — a tuple of LangChain StructuredTool objects, one per action.
The backend reads both, then converts the manifest plus the @tool tuple into the action shape it serves to the model and the app. Two consequences shape the entire schema:
  1. auth_schemas is a discriminated union keyed on auth_type. A single integration can expose more than one credential method — GitHub ships OAuth2 and a personal access token side by side.
  2. Action output schemas are intentionally absent from the manifest. The return shape of every action is derived from its @tool function’s return-type annotation (defined in outputs.py) via Model.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.
integration_type
Literal["tool"]
default:"tool"
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.
name
string
required
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).
display_name
string
required
The human-readable label shown in the app, for example "GitHub".
description
string
required
A one-line description of what the integration does.
version
string
default:"1.0.0"
The per-integration manifest version. This is not the modulex-integrations package version.
author
string
default:"ModuleX"
The integration author.
The logo reference. The convention is "modulex:<name>-themed", for example "modulex:github-themed".
app_url
string | null
default:"null"
The provider’s homepage URL.
categories
string[]
default:"[]"
Free-form category strings used to group the integration in the catalog, for example ["Developer Tools & Infrastructure", "version-control", "productivity"].
actions
ActionDefinition[]
default:"[]"
The list of callable actions the integration exposes. See ActionDefinition.
auth_schemas
AuthSchema[]
default:"[]"
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.
There is no output_schema, docs_url, features, rate_limits, pricing, or metadata field on IntegrationManifest. The earlier <name>_integration.json format carried several informational fields the Pydantic schema deliberately drops; because extra="forbid" rejects them, they must be removed when you migrate a manifest and kept only in the integration’s README.

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.
name
string
required
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.
description
string
required
A one-line description of what the action does.
parameters
dict[str, ParameterDef]
default:"{}"
The action’s parameters, keyed by parameter name. See ParameterDef.

ParameterDef

A single action parameter.
type
ParameterType
required
The parameter type. One of "string", "integer", "number", "boolean", "array", or "object".
description
string
required
A one-line description of the parameter.
default
Any
default:"null"
The default value when the parameter is omitted.
required
boolean
default:"false"
Whether the parameter must be supplied. Defaults to false.Note the contrast with EnvVar.required, which defaults to true.
Here is a single action with two parameters:
actions example
ActionDefinition(
    name="list_repositories",
    description="List repositories for the authenticated user or organization",
    parameters={
        "visibility": ParameterDef(
            type="string",
            description="Filter by visibility: all, public, private",
            default="all",
        ),
        "per_page": ParameterDef(
            type="integer",
            description="Results per page",
            default=30,
        ),
    },
)
The manifest parameters are not the authoritative list of an action’s callable arguments — the @tool function’s args_schema is. Several shipped actions accept arguments (for example GitHub create_issue accepts assignees) that the function forwards to the upstream API but the manifest does not list. Treat manifest parameters as descriptive catalog metadata; the @tool function contract defines what the action actually accepts.

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:
display_name
string
required
The credential method’s label in the app, for example "OAuth2 Authentication" or "Personal Access Token".
description
string
required
A one-line description of the credential method.
setup_instructions
string[] | null
default:"null"
An ordered list of human setup steps shown in the credential UI.
setup_environment_variables
EnvVar[]
default:"[]"
The secrets and settings the operator must provide for this method. See EnvVar.
test_endpoint
TestEndpoint | null
default:"null"
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 its auth_type discriminator. OAuth2AuthSchema additionally requires an oauth_config.
Classauth_typeExtra fieldUsed by (verified samples)
OAuth2AuthSchema"oauth2"oauth_config: OAuthConfiggithub, 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
InternalAuthSchema is defined but unused. No shipped integration instantiates it. It remains a valid wire auth_type on the ModuleX side (the backend credential constraint accepts 'internal'), so treat it as a reserved or forward-looking variant rather than an option to author against today.Separately, the backend credential constraint accepts a seventh value, the legacy 'bearer', which has no corresponding schema variant. New integrations must emit only the six variants above; 'bearer' exists only as a legacy database value.

OAuthConfig

Required on OAuth2AuthSchema only. Carries the OAuth 2.0 authorize and token endpoints plus per-provider flags.
auth_url
string
required
The provider’s authorization URL.
token_url
string
required
The provider’s token-exchange URL.
scopes
string[]
default:"[]"
The OAuth scopes to request, for example ["repo", "user", "read:org", "workflow"].
token_auth_method
Literal["body", "basic"]
default:"body"
How client credentials are presented at the token exchange — in the request body, or as HTTP Basic auth.
access_type
string | null
default:"null"
An extra authorize-URL parameter for providers that require an explicit opt-in to refresh tokens. Google needs "offline" to issue a refresh_token.
prompt
string | null
default:"null"
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.
use_pkce
boolean
default:"true"
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.
Here is a complete OAuth2 auth schema:
OAuth2AuthSchema example
OAuth2AuthSchema(
    display_name="OAuth2 Authentication",
    description="Connect using GitHub OAuth (recommended for most use cases)",
    setup_environment_variables=[
        EnvVar(
            name="GITHUB_OAUTH2_CLIENT_ID",
            display_name="Client ID",
            description="GitHub OAuth App Client ID",
            required=True,
            sensitive=False,
            only_for_custom=True,
            sample_format="Iv1.xxxxxxxxxxxxxxxx",
            about_url="https://github.com/settings/applications/new",
        ),
        # ... CLIENT_SECRET with sensitive=True ...
    ],
    oauth_config=OAuthConfig(
        auth_url="https://github.com/login/oauth/authorize",
        token_url="https://github.com/login/oauth/access_token",
        scopes=["repo", "user", "read:org", "workflow"],
        token_auth_method="body",
    ),
    test_endpoint=TestEndpoint(
        url="https://api.github.com/user",
        method="GET",
        headers={
            "Authorization": "Bearer {access_token}",
            "Accept": "application/vnd.github+json",
            "X-GitHub-Api-Version": "2022-11-28",
        },
        success_indicators=SuccessIndicators(
            status_codes=[200],
            response_fields=["login", "id"],
        ),
        cost_level="free",
        description="Validates the OAuth token by fetching authenticated user info",
    ),
)
For how ModuleX stores and resolves the resulting credentials, see Credentials & OAuth2 and Authentication & credentials.

EnvVar

A configurable secret or setting the operator must provide, listed under an auth schema’s setup_environment_variables.
name
string
required
The environment-variable-style key, for example GITHUB_OAUTH2_CLIENT_ID.
display_name
string
required
The label shown in the credential UI.
description
string
required
A one-line description of the value.
required
boolean
default:"true"
Whether the value must be supplied. Defaults to true — the opposite of ParameterDef.required, which defaults to false.
sensitive
boolean
default:"false"
Whether the value is a secret. When true, it is masked in the UI (use it for client secrets and tokens).
only_for_custom
boolean
default:"false"
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).
inject_into_auth_data
boolean
default:"false"
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.
sample_format
string | null
default:"null"
A placeholder hint shown in the UI, for example "ghp_xxxx...".
about_url
string | null
default:"null"
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.
url
string
required
The endpoint URL. May embed query placeholders, for example ConvertAPI’s ?Secret={api_key}.
method
Literal["GET", "POST", "PUT", "DELETE", "PATCH"]
default:"GET"
The HTTP method for the validation call.
headers
dict[str, str]
default:"{}"
Request headers, for example {"Authorization": "Bearer {access_token}"}.
params
dict[str, str]
default:"{}"
The URL query string, used by integrations that pass their credential as a query parameter.
body
dict[str, Any] | null
default:"null"
A JSON payload sent on non-GET methods, for example Exa’s POST /search validation call.
auth
BasicAuthSpec | null
default:"null"
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.
success_indicators
SuccessIndicators
required
How the runtime decides the validation call succeeded. See SuccessIndicators.
cost_level
string
default:"free"
A free-form hint about the cost of the validation call. Observed values include "free" and "minimal"; the schema does not constrain the set.
description
string | null
default:"null"
A one-line description of what the validation call checks.

SuccessIndicators

How to tell a credential test endpoint succeeded.
status_codes
int[]
required
The HTTP status codes treated as success, for example [200].
response_fields
string[] | null
default:"null"
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").
type
Literal["basic"]
default:"basic"
The auth synthesis type. The only value is "basic".
username_placeholder
string
required
The placeholder or literal used as the Basic auth username.
password_placeholder
string
required
The placeholder or literal used as the Basic auth password.
Here is a Basic-auth test endpoint:
TestEndpoint with BasicAuthSpec
TestEndpoint(
    url="https://api.twilio.com/2010-04-01/Accounts/{TWILIO_ACCOUNT_SID}.json",
    method="GET",
    auth=BasicAuthSpec(
        username_placeholder="TWILIO_ACCOUNT_SID",
        password_placeholder="TWILIO_AUTH_TOKEN",
    ),
    success_indicators=SuccessIndicators(
        status_codes=[200],
        response_fields=["sid"],
    ),
    cost_level="free",
    description="Validates the Account SID and Auth Token by fetching the account.",
)

Output models are not in the manifest

Neither ActionDefinition 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.
from modulex_integrations.schema import (
    ActionDefinition,
    BearerTokenAuthSchema,
    EnvVar,
    IntegrationManifest,
    OAuth2AuthSchema,
    OAuthConfig,
    ParameterDef,
    SuccessIndicators,
    TestEndpoint,
)

manifest = IntegrationManifest(
    name="github",
    display_name="GitHub",
    description="GitHub repository and code management platform",
    version="1.0.0",
    author="ModuleX",
    logo="modulex:github-themed",
    app_url="https://github.com",
    categories=["Developer Tools & Infrastructure", "version-control", "productivity"],
    actions=[
        ActionDefinition(
            name="create_repository",
            description="Create a new repository",
            parameters={
                "name": ParameterDef(
                    type="string", description="Repository name", required=True
                ),
                "description": ParameterDef(
                    type="string", description="Repository description"
                ),
                "private": ParameterDef(
                    type="boolean",
                    description="Whether the repository is private",
                    default=False,
                ),
            },
        ),
        # ... more actions ...
    ],
    auth_schemas=[
        OAuth2AuthSchema(
            display_name="OAuth2 Authentication",
            description="Connect using GitHub OAuth (recommended for most use cases)",
            setup_environment_variables=[
                EnvVar(
                    name="GITHUB_OAUTH2_CLIENT_ID",
                    display_name="Client ID",
                    description="GitHub OAuth App Client ID",
                    required=True,
                    sensitive=False,
                    only_for_custom=True,
                    sample_format="Iv1.xxxxxxxxxxxxxxxx",
                    about_url="https://github.com/settings/applications/new",
                ),
                # ... CLIENT_SECRET with sensitive=True ...
            ],
            oauth_config=OAuthConfig(
                auth_url="https://github.com/login/oauth/authorize",
                token_url="https://github.com/login/oauth/access_token",
                scopes=["repo", "user", "read:org", "workflow"],
                token_auth_method="body",
            ),
            test_endpoint=TestEndpoint(
                url="https://api.github.com/user",
                method="GET",
                headers={
                    "Authorization": "Bearer {access_token}",
                    "Accept": "application/vnd.github+json",
                    "X-GitHub-Api-Version": "2022-11-28",
                },
                success_indicators=SuccessIndicators(
                    status_codes=[200], response_fields=["login", "id"]
                ),
                cost_level="free",
                description="Validates OAuth token by fetching authenticated user info",
            ),
        ),
        BearerTokenAuthSchema(
            display_name="Personal Access Token",
            description="Use your GitHub Personal Access Token (Classic or Fine-grained)",
            setup_instructions=[
                "Go to GitHub Settings then Developer settings then Personal access tokens",
                "Generate a token with the repo and user scopes",
                "Paste the token below",
            ],
            setup_environment_variables=[
                EnvVar(
                    name="GITHUB_PERSONAL_TOKEN",
                    display_name="Personal Access Token",
                    description="GitHub Personal Access Token",
                    required=True,
                    sensitive=True,
                ),
            ],
            test_endpoint=TestEndpoint(
                url="https://api.github.com/user",
                headers={"Authorization": "Bearer {bearer_token}"},
                success_indicators=SuccessIndicators(
                    status_codes=[200], response_fields=["login", "id"]
                ),
            ),
        ),
    ],
)
🎬 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

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.
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.
ParameterDef.required defaults to false; EnvVar.required defaults to true. Set them explicitly when the default is not what you want.
A modulex_key “public API” integration with no credential to validate can omit test_endpoint entirely; the runtime then skips credential testing.
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.