# ModuleX documentation — full text > Complete ModuleX documentation corpus for LLMs. 324 pages. # Authentication URL: https://docs.modulex.dev/api-reference/authentication Authenticate every ModuleX API request with an Authorization: Bearer mx_live_ API key plus the X-Organization-ID header. Full reference for headers, key format, org scope, and error responses. Every request to the ModuleX REST API is authenticated with a **ModuleX API key** sent in the `Authorization` header, and — for any organization-scoped endpoint — scoped to one organization with the `X-Organization-ID` header. The two SDKs send exactly these headers for you. The API does **not** read a header named `X-Authorization`. There is no such header anywhere in the backend or in either SDK. Use `Authorization: Bearer` (preferred) or the `X-API-KEY` fallback, both described below. ## The two headers you send | Header | Required | Value | Applies to | | --- | --- | --- | --- | | `Authorization` | Yes | `Bearer mx_live_…` | Every endpoint | | `X-Organization-ID` | On org-scoped endpoints | The organization's id (a UUID) | Workflows, runs, knowledge bases, credentials, integrations, composer, assistant, schedules, org settings | A minimal authenticated request looks like this: ```bash Authenticated request curl https://api.modulex.dev/workflows \ -H "Authorization: Bearer mx_live_2J9vK4xM8nP3wQ7tR5sL1yB6cF0dG2h4vN8pX5mK9rT3wY7u" \ -H "X-Organization-ID: a1b2c3d4-e29b-41d4-a716-446655440000" ``` The base URL is `https://api.modulex.dev` and there is no `/v1` path segment — routers are mounted at the root. See [Base URLs & versioning](/api-reference/environments) for environments and the versioning policy, and the [API overview](/api-reference/overview) for the full request lifecycle. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-1190 · [SCREENSHOT]** **[SCREENSHOT_DESCRIPTION]:** The API keys settings screen in the ModuleX app, showing the "create key" dialog and the one-time full-key reveal. **[SCREENSHOT_DETAILS]:** Capture from the live app at Settings → API keys. Show the create-key form (name, optional organization scope, optional expiry, rate limit) and, in a second frame, the success state that reveals the full `mx_live_…` key once with a copy button. Mask the random portion of the key. Light theme, 16:9, annotate the "copy now — shown once" warning. ## API keys A ModuleX API key is a per-user secret used by SDKs and any programmatic caller. It is distinct from the Clerk session token (JWT) that the web app uses — see [Auth model: JWT vs API key](/security/authentication) for how the two paths differ. ### Key format | Property | Value | | --- | --- | | Prefix | `mx_live_` | | Random part | ~43 Base62 characters (from 32 random bytes) | | Example | `mx_live_2J9vK4xM8nP3wQ7tR5sL1yB6cF0dG2h4vN8pX5mK9rT3wY7u` | | Storage on the server | Hashed (`SHA-256` over a server pepper + key); the raw key is never stored | | Shown to you | **Once**, at creation time only | The backend distinguishes credentials by prefix: a token starting with `mx_live_` is treated as an API key; anything else presented as a bearer token is treated as a Clerk JWT. The full key is returned **only** in the response to creating it. List and detail endpoints return a masked value (`mx_live_2J9vK4xM********`). Store the key in a secret manager when you create it — there is no way to recover it later. If you lose it, revoke the key and create a new one. ### Create a key Create keys in the app (Settings → API keys) or via the API. Creating a key requires a logged-in app session (a Clerk JWT) — you cannot bootstrap the first key with another API key. A label for the key. 1–255 characters. Scope the key to a single organization. The caller must be an active member of that organization. When omitted (the default), the key works across all of your organizations and you choose the organization per request with `X-Organization-ID`. An ISO 8601 timestamp. When omitted (the default), the key never expires. Per-key request budget, 1–1000. ```bash cURL curl -X POST https://api.modulex.dev/api-keys \ -H "Authorization: Bearer $CLERK_JWT" \ -H "Content-Type: application/json" \ -d '{ "name": "CI/CD", "rate_limit_per_minute": 120 }' ``` ```python Python import asyncio from modulex import Modulex # An API key is created from a logged-in app session, not via the SDK auth path. # The SDK below is shown for consistency with the other operations on this page; # in practice you create the first key in the app, then use it as MODULEX_API_KEY. async def main(): async with Modulex(api_key="mx_live_2J9vK4xM8nP3wQ7tR5sL1yB6cF0dG2h4vN8pX5mK9rT3wY7u") as client: key = await client.api_keys.create(name="CI/CD", rate_limit_per_minute=120) print(key.key) # full key, shown once asyncio.run(main()) ``` ```javascript JavaScript import { Modulex } from 'modulex-js'; const client = new Modulex({ apiKey: 'mx_live_2J9vK4xM8nP3wQ7tR5sL1yB6cF0dG2h4vN8pX5mK9rT3wY7u', }); const key = await client.apiKeys.create({ name: 'CI/CD', rateLimitPerMinute: 120 }); console.log(key.key); // full key, shown once ``` The create response (HTTP `201`) is the **only** response that includes the full `key`: UUID of the key record. The label you set. The full `mx_live_…` key. Returned only here. The first 8 characters of the random part, for display. `mx_live_{hint}********` — what list/detail endpoints return. The org the key is scoped to, or `null` for all orgs. Expiry timestamp, or `null` for never. Whether the key is past its expiry. Whether the key is active (not revoked). The per-key budget. When the key was last used, or `null`. Creation timestamp. When the key was revoked, or `null`. ### Key limits | Limit | Value | What happens when hit | | --- | --- | --- | | Keys per user | 10 | `400` with code `MAX_KEYS_EXCEEDED` | | Default rate limit per key | 60 requests/min | `429` (see [error responses](#error-responses)) | | Rate limit across all your keys | 300 requests/min | `429` | ### Revoke a key Revoking is permanent and immediate; the record is kept for audit. A revoked key returns `401` on its next use. ```bash Revoke a key curl -X DELETE https://api.modulex.dev/api-keys/550e8400-e29b-41d4-a716-446655440000 \ -H "Authorization: Bearer $CLERK_JWT" ``` ## Sending the key You can present the key two ways. Both are accepted by the backend. This is what both SDKs send, and what you should use. ```http Authorization: Bearer mx_live_2J9vK4xM8nP3wQ7tR5sL1yB6cF0dG2h4vN8pX5mK9rT3wY7u ``` The backend also accepts the key in a dedicated header. Neither SDK uses this header — it exists for direct callers that prefer it. The value must still start with `mx_live_`. ```http X-API-KEY: mx_live_2J9vK4xM8nP3wQ7tR5sL1yB6cF0dG2h4vN8pX5mK9rT3wY7u ``` The backend resolves credentials in this order: If present and the token starts with `mx_live_`, it is used as an API key. Otherwise it is verified as a Clerk JWT. Consulted only if no bearer credential resolved, and only if the value starts with `mx_live_`. A `401` is returned: `Authentication required. Provide Authorization: Bearer token or X-API-KEY header.` ## Organization scope ModuleX is multi-tenant. Identity (your key) and organization (the tenant you act in) are **two separate axes**: the key says who you are; `X-Organization-ID` says which organization the request runs against. Most resources — workflows, runs, knowledge bases, credentials, integrations, the composer, the assistant, and schedules — are organization-scoped and require the header. Identity-only endpoints such as `GET /auth/me` and `GET /auth/invitations/my` do not. ```bash Org-scoped request curl https://api.modulex.dev/organizations/llms \ -H "Authorization: Bearer mx_live_2J9vK4xM8nP3wQ7tR5sL1yB6cF0dG2h4vN8pX5mK9rT3wY7u" \ -H "X-Organization-ID: a1b2c3d4-e29b-41d4-a716-446655440000" ``` On an org-scoped endpoint, the backend checks the header in this order: Missing `X-Organization-ID` on an org-scoped route returns `400`: `X-Organization-ID header is required`. If the key is **scoped** to a specific organization (you set `organization_id` when creating it), the header must name that same organization, or you get `403`: `API key is scoped to a different organization`. You must be a member of the named organization, or you get `403`: `User is not a member of organization {id}`. Some surfaces require a privileged role (see below). Find the organizations you belong to — and their ids — with `GET /auth/me/organizations`. The header value is the organization's id. For the full org-context model see [Org context & X-Organization-ID](/security/org-context) and the concept page on [organizations, roles & membership](/concepts/organizations-roles). A few request **bodies** also carry an `organization_id` field (for example, when creating an org-scoped API key). That field sets which organization **owns** the new resource. It is independent of the `X-Organization-ID` header, which sets which organization you are **acting in**. Do not conflate the two. ### Roles The live organization roles are **`owner`** and **`admin`**. The `member` role is retired and is not a current role — document and design against `owner`/`admin` only. A handful of surfaces require `owner` or `admin` rather than plain membership: the composer, the assistant, and the organization-settings and member-management endpoints. A membership-only call to one of these returns `403`. See [Roles & permissions](/security/roles-permissions) for the per-endpoint matrix. ## Authenticating with the SDKs Both official SDKs send `Authorization: Bearer ` and add `X-Organization-ID` whenever an organization is resolved. The default base URL is `https://api.modulex.dev`; pass `baseUrl` / `base_url` to target another environment. ```bash cURL # Verify the key — identity endpoint, no org header needed curl https://api.modulex.dev/auth/me \ -H "Authorization: Bearer mx_live_2J9vK4xM8nP3wQ7tR5sL1yB6cF0dG2h4vN8pX5mK9rT3wY7u" ``` ```python Python import asyncio from modulex import Modulex async def main(): async with Modulex( api_key="mx_live_2J9vK4xM8nP3wQ7tR5sL1yB6cF0dG2h4vN8pX5mK9rT3wY7u", organization_id="a1b2c3d4-e29b-41d4-a716-446655440000", ) as client: me = await client.auth.me() print(me) asyncio.run(main()) ``` ```javascript JavaScript import { Modulex } from 'modulex-js'; const client = new Modulex({ apiKey: 'mx_live_2J9vK4xM8nP3wQ7tR5sL1yB6cF0dG2h4vN8pX5mK9rT3wY7u', organizationId: 'a1b2c3d4-e29b-41d4-a716-446655440000', }); const me = await client.auth.me(); console.log(me); ``` You can override the organization per call without changing the client default: ```python Python # Per-call org override llms = await client.organizations.llms(organization_id="other-org-uuid") ``` ```javascript JavaScript // Per-call org override const llms = await client.organizations.llms({ organizationId: 'other-org-uuid' }); ``` **Environment-variable fallback differs between the SDKs.** The Python SDK reads `MODULEX_API_KEY`, `MODULEX_ORGANIZATION_ID`, and `MODULEX_BASE_URL` when the matching argument is omitted. The JavaScript SDK has **no** such fallback — you must pass `apiKey` (and `organizationId`) to the constructor explicitly, or it throws. Any `process.env.*` usage in JS examples is your own caller code, not SDK behavior. See the [JavaScript SDK](/sdks/javascript) and [Python SDK](/sdks/python) pages for installation and full configuration, and [SDKs overview](/sdks/overview) for how each operation maps across cURL, Python, and JavaScript. ## Error responses Authentication and organization-scope failures use the standard FastAPI envelope, `{"detail": ""}`. | Status | When | Example `detail` | | --- | --- | --- | | `401` | No credential, or invalid/expired key | `Invalid API key` | | `401` | Neither header present | `Authentication required. Provide Authorization: Bearer token or X-API-KEY header.` | | `400` | Missing `X-Organization-ID` on an org-scoped route | `X-Organization-ID header is required` | | `403` | Inactive user | `Inactive user` | | `403` | Org-scoped key used against a different org | `API key is scoped to a different organization` | | `403` | Not a member of the named organization | `User is not a member of organization {id}` | | `403` | Role too low for the surface (owner/admin required) | `Insufficient permissions` | ```json 401 example { "detail": "Invalid API key" } ``` ### Rate-limit responses When you exceed a key's per-minute budget or the across-keys per-user budget, the per-key limiter returns `429` with a string `detail` and rate-limit headers: ```json 429 (per-key / per-user limiter) { "detail": "API key rate limit exceeded" } ``` | Header | Meaning | | --- | --- | | `X-RateLimit-Limit` | The limit that applied | | `X-RateLimit-Remaining` | Requests left in the window | | `X-RateLimit-Reset` | Unix epoch (seconds) when the window resets | | `Retry-After` | Seconds to wait before retrying (default 60) | The organization-level `api`-class limiter can also return `429`, but with a **dict-valued** `detail`: ```json 429 (org api-class limiter) { "detail": { "code": "rate_limited", "layer": "rate", "key": "api", "current": 300, "limit": 300, "reason": "rate_limit_exceeded" } } ``` A third `429` shape — the flat billing-gate `DenialEnvelope` — appears only on run and managed-usage surfaces (workflow runs, composer, assistant, managed knowledge), never on a CRUD or settings route. The same surfaces can also return `402` and `403` billing denials. See [Usage gating & limits](/billing/usage-gating) for the gate, [Rate limiting](/api-reference/rate-limiting) for all three `429` shapes, and [Errors & status codes](/api-reference/errors) for the complete envelope reference. ## Security notes - Treat the key like a password. Send it only over HTTPS; never embed it in client-side code, a public repository, or a URL query string. - Use `X-API-KEY` only if you cannot set `Authorization` — `Authorization: Bearer` is the supported path. - Scope keys to one organization and set a tight `rate_limit_per_minute` for automation, so a leaked key has limited blast radius. - Rotate by creating a new key, switching traffic, then revoking the old one. Revocation is immediate. - The Clerk JWT used by the web app and the `mx_live_` API key used by code are different credentials with different lifecycles. See [Auth model: JWT vs API key](/security/authentication) for which path to use where. ## Next steps Get a key and make your first authenticated call. How organization scope is resolved and enforced. Every error envelope and what produces it. Canonical terms: API key, Clerk JWT, organization, role. --- # Base URLs & versioning URL: https://docs.modulex.dev/api-reference/environments The ModuleX REST API base URL (https://api.modulex.dev), why there is no /v1 path segment, the trailing-slash rule, and how ModuleX versions and deprecates the API. The ModuleX REST API is served from a single production base URL, with **no version segment in the path**. Every router mounts directly at its resource path, so a request URL is the base URL joined to the route — for example `https://api.modulex.dev/workflows`. This reference covers the base URL and how request URLs are composed, the no-`/v1` and trailing-slash rules, and how ModuleX versions and deprecates the API. This page is the API-reference companion to [Base URLs, environments & versioning](/get-started/environments), which walks through pointing each SDK at a local or staging backend and the per-SDK environment-variable behavior. For the request lifecycle and content types, see the [API overview](/api-reference/overview); to authenticate requests, see [Authentication](/api-reference/authentication). ## Base URL The API has one canonical production host. The SDKs default to it, so you do not set the base URL for normal use — you override it only to target a local backend or a self-hosted deployment. | Environment | Base URL | When you use it | | --- | --- | --- | | Production (default) | `https://api.modulex.dev` | Every request, unless you override the base URL. | | Local development | `http://localhost:8000` | A backend you run yourself; this is the documented local serving port. | Both the [JavaScript SDK](/sdks/javascript) and the [Python SDK](/sdks/python) hardcode `https://api.modulex.dev` as the default base URL. The Python SDK additionally reads a `MODULEX_BASE_URL` environment variable as a fallback; the JavaScript SDK does not — see [Switching environments](/get-started/environments) for the full resolution order per SDK. `https://api.modulex.dev` serves only REST endpoints and the run-event SSE streams. The ModuleX web app and the OAuth redirect target live at a separate host, `https://app.modulex.dev`, and realtime canvas collaboration runs over a separate Socket.io server. Do not point SDK calls at the app host. See [Realtime overview](/realtime/overview) and [Credentials & OAuth2](/concepts/credentials-oauth). ### How a request URL is built The SDKs build every request URL as `baseUrl + path` — the route path is appended verbatim, with no version segment inserted and no other rewriting. A trailing slash on the base URL is stripped before the path is joined, so `https://api.modulex.dev/` and `https://api.modulex.dev` behave identically. ```text Request URL composition base URL https://api.modulex.dev + route path /workflows/9d1f0c2e-4b7a-4c1e-8f3a-2b6d5e0a1c34/run = request URL https://api.modulex.dev/workflows/9d1f0c2e-4b7a-4c1e-8f3a-2b6d5e0a1c34/run ``` The same call against each surface — note that only the base URL changes: ```bash cURL # Production (default) curl https://api.modulex.dev/workflows \ -H "Authorization: Bearer mx_live_8sJ2kQ4mZ1pX7vR9tB3nW6yL0aD5cF" \ -H "X-Organization-ID: 9d1f0c2e-4b7a-4c1e-8f3a-2b6d5e0a1c34" # Local backend — same call, base URL swapped curl http://localhost:8000/workflows \ -H "Authorization: Bearer mx_live_8sJ2kQ4mZ1pX7vR9tB3nW6yL0aD5cF" \ -H "X-Organization-ID: 9d1f0c2e-4b7a-4c1e-8f3a-2b6d5e0a1c34" ``` ```python Python from modulex import Modulex # Production (default) — base_url is optional client = Modulex( api_key="mx_live_8sJ2kQ4mZ1pX7vR9tB3nW6yL0aD5cF", organization_id="9d1f0c2e-4b7a-4c1e-8f3a-2b6d5e0a1c34", ) # Local backend — override base_url local = Modulex( api_key="mx_live_8sJ2kQ4mZ1pX7vR9tB3nW6yL0aD5cF", organization_id="9d1f0c2e-4b7a-4c1e-8f3a-2b6d5e0a1c34", base_url="http://localhost:8000", ) ``` ```javascript JavaScript import { Modulex } from "modulex"; // Production (default) — baseUrl is optional const client = new Modulex({ apiKey: "mx_live_8sJ2kQ4mZ1pX7vR9tB3nW6yL0aD5cF", organizationId: "9d1f0c2e-4b7a-4c1e-8f3a-2b6d5e0a1c34", }); // Local backend — override baseUrl const local = new Modulex({ apiKey: "mx_live_8sJ2kQ4mZ1pX7vR9tB3nW6yL0aD5cF", organizationId: "9d1f0c2e-4b7a-4c1e-8f3a-2b6d5e0a1c34", baseUrl: "http://localhost:8000", }); ``` ## No `/v1` path segment ModuleX does not put a version number in the URL path. There is no `/v1`, no `/api/v1`, and no `/api` prefix — each resource router mounts directly at its prefix. | Resource | Full URL | | --- | --- | | Workflows | `https://api.modulex.dev/workflows` | | Run a workflow | `https://api.modulex.dev/workflows/{workflow_id}/run` | | Knowledge bases | `https://api.modulex.dev/knowledge-bases` | | Credentials | `https://api.modulex.dev/credentials` | | Integrations | `https://api.modulex.dev/integrations` | | Organizations | `https://api.modulex.dev/organizations` | | API keys | `https://api.modulex.dev/api-keys` | Do not prefix routes with `/v1` or `/api/v1`. There is no such segment — a request to `https://api.modulex.dev/v1/workflows` does not resolve to the workflows router and will not return the list of workflows. Call `https://api.modulex.dev/workflows`. ## Trailing slashes Automatic trailing-slash redirects are **turned off**. A route is matched exactly as declared, so request the path without a trailing slash. | Request | Result | | --- | --- | | `GET /workflows` | Matches the list-workflows route. | | `GET /workflows/` | Does **not** redirect to `/workflows`; it is treated as a different, unmatched path. | Always call the canonical path with no trailing slash. The SDKs already do this for you, so this only matters when you build request URLs by hand (for example, in cURL or a custom HTTP client). ## Versioning ModuleX versions the API and the SDKs separately. Neither uses a version segment in the URL path. ### API versioning The API is **unversioned in the URL**. There is no `/v1` path segment, and there is **no API-version request header** to send or pin — you cannot request a specific API version from the client side. The backend application advertises an internal version string of `0.1.2`, visible at the service root `GET /` and the public health endpoint `GET /system/health`. This is the build version of the service, not a contract version you negotiate against — it does not change how you call the API and you do not pass it on requests. Because there is no version negotiation, ModuleX evolves the API in a backward-compatible way wherever possible: new fields are additive, and existing fields keep their meaning. Track changes that affect you through the [changelog](/reference/changelog), and use the [SDK ⇄ API parity matrix](/sdks/parity) to confirm which routes a given SDK release covers. Responses are **snake_case** JSON (for example `run_id`, `created_at`, `organization_ids`). The JavaScript SDK accepts camelCase request parameters and converts them to snake_case on the wire, but does **not** convert responses — so JavaScript responses are snake_case too. The Python SDK is snake_case end to end. See the [SDK ⇄ API parity matrix](/sdks/parity) for the casing rules per SDK. ### SDK versioning (semantic versioning) The official SDKs follow semantic versioning and ship at version **1.0.0**. | SDK | Package | Version | Default base URL | Reference | | --- | --- | --- | --- | --- | | JavaScript / TypeScript | `modulex` | `1.0.0` | `https://api.modulex.dev` | [JavaScript SDK](/sdks/javascript) | | Python | `modulex` | `1.0.0` | `https://api.modulex.dev` | [Python SDK](/sdks/python) | Pin the SDK version in your dependency manifest so a major release does not change behavior under you. Breaking changes land in a new major version; consult the [changelog](/reference/changelog) before upgrading across a major boundary. Notable changes already shipped in the 1.0 line include the retirement of the organization `member` role (invites accept only `admin`; see [Roles & permissions](/security/roles-permissions)), realtime SSE frames discriminating on a `type` key inside the JSON rather than a named `event` line (see [SSE run streaming](/realtime/sse-streaming)), and the removal of client-side billing helpers from the JavaScript SDK (the `subscriptions` resource is [Python-SDK-only](/sdks/parity)). ## Deprecation ModuleX does not version the API in the URL, so a removed or replaced capability is communicated through the response status and a pointer to its replacement, plus the [changelog](/reference/changelog). There is no `Deprecation` or `Sunset` response header to watch for. ### Removed endpoints return 410 When a capability is removed rather than changed, the route returns **`410 Gone`** with a `detail` message that names the replacement. The live example is the former "LLM mode" of the run endpoint: a single-node chat or knowledge Q&A submitted to `POST /workflows/{workflow_id}/run` with only an `llm` config (no workflow, `workflow_id`, or `system_workflow`) has been removed in favor of the agentic [Assistant](/assistant/overview). ```http 410 response — LLM mode removed from /workflows/run HTTP/1.1 410 Gone Content-Type: application/json { "detail": "LLM mode on /workflows/run has been removed. Use the agentic assistant chat instead: POST /assistant/chat {message, llm?} and stream via the returned stream_url. Knowledge Q&A is now the assistant's search_knowledge tool." } ``` A `410` is the FastAPI `HTTPException` shape — a string `detail`. It is **not** the billing `DenialEnvelope`, and the SDKs do not map `410` to a dedicated error class; it surfaces as the base `ModulexError`. For the full set of error shapes and which surface emits each, see [Errors & status codes](/api-reference/errors). Run a chat-style prompt or knowledge Q&A through [`POST /assistant/chat`](/assistant/how-it-works), not through the run endpoint. Sending only an `llm` config to `POST /workflows/{workflow_id}/run` is a removed path and returns `410`. The run endpoint is for executing a saved workflow, an ad-hoc workflow schema, or a system workflow. ### Deprecated aliases Some routes are kept as deprecated aliases for backward compatibility rather than removed. For example, the support-request router is mounted canonically at `/requests` and also at the deprecated alias `/support`, so `POST /support` is equivalent to `POST /requests`. Prefer the canonical path; aliases may be removed in a future major release and are noted in the [changelog](/reference/changelog). ## Interactive API documentation The running backend serves machine- and human-readable references at the base URL. Use these to inspect the live schema for the environment you are calling. | Path | Surface | | --- | --- | | `https://api.modulex.dev/docs` | Swagger UI (interactive). | | `https://api.modulex.dev/redoc` | ReDoc reference. | | `https://api.modulex.dev/openapi.json` | The raw OpenAPI schema. | | `https://api.modulex.dev/system/health` | Public liveness check returning `{ "status": "healthy", "service": "ModuleX", "version": "0.1.2" }`. | For the curated, language-tabbed reference and a live "Try it" playground inside these docs, browse the endpoint pages under [API overview](/api-reference/overview). To check live availability, see [System status & health](/reference/status). **🎬 MEDIA PLACEHOLDER · MX-MEDIA-1200 · [SCREENSHOT]** **[SCREENSHOT_DESCRIPTION]:** The Mintlify "Try it" API playground for a ModuleX endpoint, showing the base-URL host and the auth header fields populated. **[SCREENSHOT_DETAILS]:** Capture the interactive endpoint page for `GET /workflows` in these docs (the OpenAPI playground). Show the request panel with the base URL `https://api.modulex.dev`, the `Authorization: Bearer mx_live_…` header field, and the `X-Organization-ID` header field filled with placeholder values. Light theme, 16:9, no real credentials visible. Place under the "Interactive API documentation" heading. ## What does not change per environment Switching environments changes only the base URL. Everything else about a request is identical across production and a local or self-hosted backend: Every request sends `Authorization: Bearer mx_live_…`, and organization-scoped requests also send `X-Organization-ID`. Use a key that belongs to the environment you are calling. See [Authentication](/api-reference/authentication). The same paths with no `/v1` segment, called without a trailing slash. The same shapes, including the billing `DenialEnvelope` returned as `402` / `403` / `429` on the metered run, composer, assistant, and managed-knowledge surfaces. See [Errors & status codes](/api-reference/errors). The same per-key, per-user, and per-organization limits and `429` responses. See [Rate limiting](/api-reference/rate-limiting). ## Next steps The getting-started walkthrough: point each SDK at local or staging, with per-SDK environment-variable behavior. The `Authorization: Bearer` header and `X-Organization-ID` org context. The request lifecycle, content types, and how each operation is shown three ways. The three error shapes, the billing `DenialEnvelope`, and the `410` removed-endpoint case. --- # Errors & status codes URL: https://docs.modulex.dev/api-reference/errors Every ModuleX API error: the four error-envelope shapes, the full HTTP status taxonomy, which surface emits the DenialEnvelope (402/403/429), and how the SDKs map each status to a typed error class. ModuleX does not wrap every error in one schema. The API returns **four coexisting error-envelope shapes**, and which one you get depends on the surface you call and the failure that occurred. This page documents all four, the full HTTP status taxonomy, and how the [JavaScript SDK](/sdks/javascript) and [Python SDK](/sdks/python) normalize each status into a typed error class so you can branch on it in code. The most important distinction: a **402 / 403 / 429** can mean two very different things. On the **run, Composer, Assistant, and managed-knowledge** surfaces, it is a structured [billing-gate](/billing/usage-gating) denial — the flat `DenialEnvelope` (shape D). On plain CRUD and org-settings routes, the same status codes carry only the standard `{"detail": …}` shape. The two are not interchangeable, and a client that handles billing denials must branch on both. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-1220 · [IMAGE]** **[IMAGE_DESCRIPTION]:** A decision diagram that maps an HTTP error response to one of the four ModuleX envelope shapes. **[IMAGE_DETAILS]:** Start node "Error response (status >= 400)". First branch on status: 422 leads to "Shape C — `{detail: [{loc, msg, type}]}` validation array". For 402/403/429 a branch asks "Surface = run / Composer / Assistant / managed-knowledge?" — yes leads to "Shape D — flat DenialEnvelope `{code, layer, key, current, limit, reason}`", no continues. A second branch asks "detail is a string or an object?" leading to "Shape A — `{detail: ''}`" and "Shape B — `{detail: {…struct…}}`". Annotate that Shape D has NO `detail` wrapper, and that 402/429 each have multiple live shapes. Light theme, 16:9, clean flowchart, no UI chrome. ## The four envelope shapes There is no global error wrapper and no shared error-schema module. Each shape below is emitted by a different code path. Read them top to bottom — A is by far the most common. ```json Shape A — {"detail": ""} { "detail": "X-Organization-ID header is required" } ``` ```json Shape B — {"detail": {…struct…}} { "detail": { "code": "rate_limited", "layer": "rate", "key": "api", "current": 300, "limit": 300, "reason": "rate_limit_exceeded" } } ``` ```json Shape C — 422 validation array { "detail": [ { "loc": ["query", "q"], "msg": "String should have at least 2 characters", "type": "string_too_short" } ] } ``` ```json Shape D — flat DenialEnvelope { "code": "credit_plan_exhausted", "layer": "credit", "key": "sync_exec", "current": null, "limit": 5000.0, "reason": "credit_plan_exhausted" } ``` ### Shape A — `{"detail": ""}` The standard FastAPI / Starlette `HTTPException` envelope, used by essentially every router. The `detail` value is a plain human-readable string. Missing headers, missing body fields, not-found resources, role and scope rejections, and most server errors all arrive in this shape. This is also one of the three live **429** paths: the per-key and per-user rate limiter returns a string `detail` (`"API key rate limit exceeded"` or `"User rate limit exceeded (across all API keys)"`) alongside the rate-limit headers. See [Rate limiting](/api-reference/rate-limiting). ### Shape B — `{"detail": {…struct…}}` A few sites pass a **dictionary** as `detail`, which the framework serializes verbatim under the `detail` key. There are two live producers: - **Org-level rate-limit deny (429).** The org `api`-class limiter returns `{"detail": {"code": "rate_limited", "layer": "rate", "key": "api", "current": …, "limit": …, "reason": "rate_limit_exceeded"}}`. Note the `code` is `"rate_limited"` here — not `"rate_limit_exceeded"` as in shape D — and the whole struct is wrapped in `detail`. - **Wallet paid-gate deny (402).** The `/subscriptions/wallet/extra-usage` and `/subscriptions/wallet/topup` routes return `{"detail": {"reason": "paid_subscription_required"}}`. This is a plain `HTTPException`, **not** a `DenialEnvelope`. **A dict `detail` is not a `DenialEnvelope`.** Shape B nests its fields inside `detail`, and its `code` for the org limiter is `"rate_limited"`. The flat `DenialEnvelope` (shape D) has **no** `detail` wrapper and uses `code: "rate_limit_exceeded"`. Same status code, different wire shape — see [the multiple 402 and 429 shapes](#status-codes-with-more-than-one-live-shape). ### Shape C — 422 validation array There is no custom 422 handler, so request-validation failures use the framework default: `{"detail": [ {loc, msg, type}, … ]}`. Each array item identifies the field, a message, and an error type. This is triggered by query/body/path constraints such as a minimum string length. The exact field set of the 422 array is the framework's default and was not confirmed by a backend override. Treat `loc`, `msg`, and `type` as the fields you can rely on, and read the array as a list of per-field problems rather than a fixed schema. ### Shape D — flat `DenialEnvelope` The structured billing / usage / rate / quota denial. It is **flat** — its fields sit at the top level of the body, **not** under `detail`. This is the envelope the [usage gate](/billing/usage-gating) raises, and it is **live** on the run, Composer, Assistant, and managed-knowledge surfaces. The machine-stable denial code. Branch on this in your client. One of `rate_limit_exceeded`, `quota_exceeded`, `credit_plan_exhausted`, `wallet_overage_disabled`, `wallet_insufficient`, or `upgrade_payment_failed`. Which gate layer denied the request: `rate`, `quota`, `credit`, or `wallet`. The layer determines the HTTP status — `rate` → 429, `quota` → 403, `credit` → 402, `wallet` → 402. The limit or counter key that was hit, such as `sync_exec` or `max_knowledge_bases`. `null` when not applicable. The observed usage or count at the moment of denial. `null` when the gate does not report a running total for this denial. The limit that was reached. `null` when no numeric limit applies (for example, a suspended subscription). A short reason token, for example `overage_disabled`, `insufficient_balance`, or `credit_plan_exhausted`. Frequently mirrors `code`, but may differ. #### `DenialEnvelope` codes Each gate layer maps to a fixed `code`, HTTP status, and meaning: | `code` | `layer` | HTTP | What it means | | --- | --- | :-: | --- | | `rate_limit_exceeded` | `rate` | 429 | Per-period run/usage rate limit reached on a gated surface. Carries `Retry-After` plus `X-RateLimit-*` headers (each omitted if not set). | | `quota_exceeded` | `quota` | 403 | A countable entitlement (for example, the maximum number of knowledge bases) is at its limit. | | `credit_plan_exhausted` | `credit` | 402 | The plan's credit allowance is used up and no wallet overage is available (or the subscription is suspended). | | `wallet_overage_disabled` | `wallet` | 402 | The allowance is exhausted and overage spending is turned off for the organization. | | `wallet_insufficient` | `wallet` | 402 | Overage is on, but the prepaid wallet does not have enough balance. | | `upgrade_payment_failed` | `credit` | 402 | A plan-upgrade proration charge failed. | For where these come from and how credits are metered, see [Usage gating & limits](/billing/usage-gating) and [Credits & metering](/billing/credits). ## Which surface emits which shape This is the single most important fact about ModuleX errors. **Shape D (`DenialEnvelope`) is live on run and managed-usage surfaces, and absent everywhere else.** A 402 / 403 / 429 from a CRUD or org-settings route is **never** a `DenialEnvelope` — it carries the `{"detail": …}` shapes (A or B). A 402 / 403 / 429 from a gated surface **can** be a `DenialEnvelope`. The usage gate runs on these surfaces, so they can return a flat `DenialEnvelope` (shape D) for 402 / 403 / 429 — in addition to the standard `{"detail": …}` shapes for other failures: - `POST /workflows/{workflow_id}/run` and `POST /workflows/run` — workflow execution. See [Run via API](/workflow-builder/execution/api-endpoint). - The [AI Composer](/concepts/ai-composer) chat and resume endpoints. - The [Assistant](/concepts/assistant) chat and resume endpoints. - Managed-knowledge retrieval and ingest on [managed knowledge](/platform/knowledge/managed) bases. On these surfaces, branch on the flat top-level `code` / `layer` fields. Plain CRUD and org-settings routes never call the usage gate. They only return the `{"detail": …}` shapes (A, or B for the wallet/org-limiter cases). Examples: - `GET` / `POST` / `PATCH` / `DELETE /workflows` and `/workflows/{workflow_id}`. - `GET` / `POST /knowledge-bases`, `/credentials`, `/api-keys`, `/schedules`. - `GET /organizations`, `GET /integrations`. A 402 / 403 / 429 here is a string `detail` (or, for the wallet routes and the org rate limiter, a dict `detail`) — not a `DenialEnvelope`. ## HTTP status taxonomy Every status ModuleX returns, what it means in this product, and the envelope shape(s) you should expect. | Status | Meaning in ModuleX | Envelope shape(s) | | :-: | --- | --- | | **200** | Success. No global success wrapper — each route returns its own `response_model` or dict, in snake_case. | n/a | | **201** | Resource created (for example `POST /workflows`). | n/a | | **400** | Bad request: missing `X-Organization-ID`, a missing required body field, an out-of-range value, or "no active deployment". | A | | **401** | No or invalid authentication: missing token, invalid Clerk JWT, or invalid API key. Carries `WWW-Authenticate: Bearer`. See [Authentication](/api-reference/authentication). | A | | **402** | Payment required. **Wallet routes:** shape B `{"detail": {"reason": "paid_subscription_required"}}`. **Gated surfaces:** shape D `credit` / `wallet` denial. | B (wallet routes) · D (gated surfaces) | | **403** | Forbidden: inactive user, wrong org role (`owner` / `admin` required — the `member` role is retired), or an API-key org-scope mismatch. **Gated surfaces:** shape D `quota` denial. | A (CRUD / org) · D (gated `quota`) | | **404** | Not found — and not-owned-by-your-org is **identical** to not-found by design, so existence never leaks across organizations. | A | | **409** | Conflict. Both SDKs expose a `ConflictError`, but no backend producer is pinned. Handle it defensively. | A (assumed) | | **410** | Gone: the removed "LLM mode" on `POST /workflows/run`, and stale or already-consumed [HITL](/realtime/hitl) resume requests. | A | | **422** | Request validation failure (the framework default array). Note: a malformed `workflow_schema` surfaces as **500**, not 422, because run/workflow bodies are typed as open dictionaries. | C (normal) · A (the 500 case) | | **429** | Rate limited. Three live shapes — see below. | A · B · D (gated surfaces) | | **500** | Unhandled exception → catch-all `{"detail": "An unexpected internal server error occurred."}`; or a route's own wrapped 500 with a descriptive `detail`. | A | | **502** | External-service error, for example `"Stripe error: …"` from checkout, portal, or top-up. | A | | **503** | Service unavailable or fail-closed: an admin/health key not configured, or a transient Stripe-webhook retry. | A | **The catch-all 500 hides the original exception.** Any unhandled exception is returned as a fixed-string 500. Some routers, however, catch their own exceptions and re-raise a 500 `HTTPException` with a descriptive `detail` before the catch-all sees it — so a 500 may or may not include a useful message. Do not parse 500 `detail` strings programmatically. ### Status codes with more than one live shape Two status codes carry more than one wire shape at the same time. A client that handles them must branch on **all** of the shapes below. A 402 can be either of these, depending on the route: ```json Wallet paid-gate (Shape B) { "detail": { "reason": "paid_subscription_required" } } ``` ```json Gated-surface credit/wallet denial (Shape D) { "code": "credit_plan_exhausted", "layer": "credit", "key": "sync_exec", "current": null, "limit": 5000.0, "reason": "credit_plan_exhausted" } ``` Shape B comes only from the two wallet routes; shape D comes from the usage gate on run / Composer / Assistant / managed-knowledge. A 429 can be any of these three. They disagree on `code` and on wrapping: ```json Per-key / per-user limiter (Shape A) { "detail": "API key rate limit exceeded" } ``` ```json Org api-class limiter (Shape B) { "detail": { "code": "rate_limited", "layer": "rate", "key": "api", "current": 300, "limit": 300, "reason": "rate_limit_exceeded" } } ``` ```json Gated-surface rate denial (Shape D) { "code": "rate_limit_exceeded", "layer": "rate", "key": "sync_exec", "current": null, "limit": null, "reason": "rate_limit_exceeded" } ``` All three may carry `Retry-After` and `X-RateLimit-*` headers. Note the `code` difference: `"rate_limited"` for the org limiter versus `"rate_limit_exceeded"` for the gate. See [Rate limiting](/api-reference/rate-limiting) for the header contract. ## Rate-limit headers The 429 responses may carry these headers. Each is **omitted** when its value is not set, so you may see only some of them on a given response. How long to wait before retrying. Defaults to `60` on gated-surface rate denials. Both SDKs honor this for 429 backoff; the Python SDK also tolerates an HTTP-date form. The ceiling for the current window. Remaining requests in the current window. When the current window resets. ## How the SDKs map errors Both SDKs are at v1.0.0 and parse every envelope shape into one class tree, so you catch a typed error instead of inspecting raw JSON. Every error exposes `status`, the response `body`, and the response `headers`; the structured `code`, `layer`, and `reason` fields are surfaced where the envelope provides them. The two SDKs differ in one important way: **Python adds a `BillingError` family** (with `PaymentRequiredError`, `QuotaExceededError`, `CreditExhaustedError`, and `WalletError`) that the JavaScript SDK does not have. In JavaScript, a 402 and a `rate`-layer envelope fall through to the base `ModulexError` — with `code` / `layer` / `reason` still populated. ### Status → SDK class | Status | JavaScript class | Python class | | :-: | --- | --- | | 400 | `BadRequestError` | `BadRequestError` | | 401 | `AuthenticationError` | `AuthenticationError` | | 402 | `ModulexError` (base — no 402 class) | `PaymentRequiredError` / `CreditExhaustedError` / `WalletError` | | 403 | `PermissionError` | `PermissionError` / `QuotaExceededError` (gated `quota`) | | 404 | `NotFoundError` | `NotFoundError` | | 409 | `ConflictError` | `ConflictError` | | 410 | `ModulexError` (base — no 410 class) | `ModulexError` (base — no 410 class) | | 422 | `ValidationError` | `ValidationError` | | 429 | `RateLimitError` | `RateLimitError` (header path) **or** base `BillingError` (envelope `layer="rate"`) | | 500 | `InternalError` | `InternalError` | | 502 | `ExternalServiceError` | `ExternalServiceError` | | 503 | `ServiceUnavailableError` | `ServiceUnavailableError` | | other ≥ 400 | base `ModulexError` | base `ModulexError` | | stream error | `StreamError` | `StreamError` | | timeout / abort | `TimeoutError` | `TimeoutError` | **In Python, one 429 status can raise two different classes.** A header-only 429 raises `RateLimitError`; a 429 whose body is a `layer="rate"` envelope raises base `BillingError`. JavaScript surfaces both as `RateLimitError` because it discriminates on status, not on envelope shape. In Python, catch **both** `RateLimitError` and `BillingError` to cover every 429. The Python SDK shadows the builtin `PermissionError` and `TimeoutError`. ### Catch and discriminate The example below makes an authenticated request and handles a billing denial, a rate limit, and validation errors. Authentication uses `Authorization: Bearer mx_live_…` plus `X-Organization-ID` — see [Authentication](/api-reference/authentication). ```bash cURL # A 402 DenialEnvelope from the run surface looks like this on the wire. # Branch on the top-level `layer` / `code` (no `detail` wrapper). curl -i -X POST https://api.modulex.dev/workflows/run \ -H "Authorization: Bearer mx_live_8Kd2pQ7nR4xWvL0eYbT1" \ -H "X-Organization-ID: org_9a2f7c41" \ -H "Content-Type: application/json" \ -d '{"workflow_id": "wf_3f8a21c0", "input": {"topic": "quarterly report"}}' # HTTP/1.1 402 Payment Required # {"code":"credit_plan_exhausted","layer":"credit","key":"sync_exec", # "current":null,"limit":5000.0,"reason":"credit_plan_exhausted"} ``` ```python Python from modulex import ( Modulex, BillingError, CreditExhaustedError, WalletError, QuotaExceededError, RateLimitError, ValidationError, ModulexError, ) async with Modulex( api_key="mx_live_8Kd2pQ7nR4xWvL0eYbT1", organization_id="org_9a2f7c41", ) as client: try: run = await client.executions.run( workflow_id="wf_3f8a21c0", input={"topic": "quarterly report"}, ) except CreditExhaustedError as err: # 402, layer="credit": plan allowance is used up. print(err.code, err.layer, err.limit) except WalletError as err: # 402, layer="wallet": overage off or balance insufficient. print(err.code, err.reason) except QuotaExceededError as err: # 403, layer="quota": a countable entitlement is at its limit. print(err.code, err.key, err.current, err.limit) except (RateLimitError, BillingError) as err: # 429 can raise either class — catch both. Back off on Retry-After. retry_after = getattr(err, "retry_after", None) print("rate limited; retry after", retry_after) except ValidationError as err: # 422: err.body["detail"] is the loc/msg/type array. print(err.body) except ModulexError as err: # Base catch-all for 402 falling through, 410, 409, 5xx, etc. print(err.status_code, err.code, err.reason) ``` ```javascript JavaScript import { Modulex, RateLimitError, ValidationError, PermissionError, ModulexError, } from 'modulex-js'; const client = new Modulex({ apiKey: 'mx_live_8Kd2pQ7nR4xWvL0eYbT1', organizationId: 'org_9a2f7c41', }); try { const run = await client.executions.run({ workflowId: 'wf_3f8a21c0', input: { topic: 'quarterly report' }, }); } catch (err) { if (err instanceof RateLimitError) { // 429 — back off using the parsed Retry-After header. console.log('rate limited; retry after', err.retryAfter); } else if (err instanceof PermissionError) { // 403 — role/scope rejection (Shape A). console.log(err.message); } else if (err instanceof ValidationError) { // 422 — err.body.detail is the loc/msg/type array. console.log(err.body); } else if (err instanceof ModulexError) { // 402 and 410 fall through to the base class here. // code / layer / reason are still populated for DenialEnvelope bodies. console.log(err.status, err.code, err.layer, err.reason); } else { throw err; } } ``` **JavaScript has no 402 or billing class.** Catch the base `ModulexError` for 402 and read `err.code` / `err.layer` / `err.reason` — they are populated from the `DenialEnvelope`. The same applies to 410. See [Errors & retries](/sdks/errors-retries) for the full SDK error class list and the retry policy. ### Retry classification Both SDKs treat the same statuses as retryable and honor `Retry-After` for 429 backoff. | Statuses | Treatment | | --- | --- | | `429`, `500`, `502`, `503` | **Retryable** (with backoff; 429 honors `Retry-After`). | | `400`, `401`, `403`, `404`, `409`, `422` | **Not retryable** — fix the request. | The two SDKs differ on scope: the **Python SDK retries `GET` and `HEAD` only**, while the **JavaScript SDK retries all methods**. SSE connection errors are thrown once, with no automatic reconnect, in both SDKs. For the retry-budget details and idempotency behavior, see [Errors & retries](/sdks/errors-retries). ## Related pages The per-key, per-user, and per-org rate limits behind the 429 responses. The billing admission gate that raises the 402 / 403 / 429 `DenialEnvelope`. The auth headers behind 401 and 403 — `Authorization: Bearer` plus `X-Organization-ID`. The full error class tree, retry policy, and idempotency behavior in both SDKs. --- # API overview & request lifecycle URL: https://docs.modulex.dev/api-reference/overview How the ModuleX REST API works: the api.modulex.dev base URL, the unversioned (no /v1) routing scheme, the authenticate, run, stream request lifecycle, content types, and the one operation, three ways model with a live playground. The ModuleX REST API is the programmatic surface behind every product feature: workflows, runs, the [AI Composer](/concepts/ai-composer), the [Assistant](/concepts/assistant), [knowledge bases](/concepts/knowledge-rag), [credentials](/concepts/credentials-oauth), and [integrations](/integrations/overview). The same API powers the web app, the official [SDKs](/sdks/overview), and your own code. This page explains the parts every request shares: the base URL, the routing scheme, the request lifecycle, content types, and how each operation is documented exactly once across cURL, Python, and JavaScript with a live playground. Every request carries `Authorization: Bearer mx_live_…` and `X-Organization-ID`. Official JavaScript and Python clients wrap every operation on this page. Three error-envelope shapes, including the billing `DenialEnvelope`. Long-running runs stream over Server-Sent Events, not a polling loop. ## Base URL All API requests go to a single host. There is no separate path prefix for the API: routers mount directly at their resource path. ```text Base URL https://api.modulex.dev ``` Both official SDKs default to this host, so you only set it when you point at a non-production environment: - JavaScript: `DEFAULT_BASE_URL = 'https://api.modulex.dev'`, overridable with the `baseUrl` client option. - Python: `DEFAULT_BASE_URL = "https://api.modulex.dev"`, overridable with the `base_url` argument or the `MODULEX_BASE_URL` environment variable. A request URL is simply the base URL joined to the operation path. For example, running a workflow is `POST https://api.modulex.dev/workflows/run`. Trailing-slash redirects are disabled (`redirect_slashes=False`). Call `/workflows`, not `/workflows/` — a trailing slash is not auto-corrected and will not match the route. The SDKs build paths correctly for you. For the full list of environments, hostnames, and the versioning policy, see [Base URLs & versioning](/api-reference/environments) and [Base URLs, environments & versioning](/get-started/environments). ## No `/v1` in the path ModuleX does **not** put a version segment in the URL. There is no `/v1`, `/api`, or `/api/v1` prefix anywhere in the routing scheme — paths are exactly their resource name. ```text Correct POST https://api.modulex.dev/workflows/run GET https://api.modulex.dev/workflows GET https://api.modulex.dev/knowledge-bases ``` ```text Incorrect — these routes do not exist POST https://api.modulex.dev/v1/workflows/run GET https://api.modulex.dev/api/v1/workflows ``` If you hand-build URLs (rather than using an SDK), drop any `/v1` you may have copied from another API. A versioned path returns `404 Not Found` because the route is not registered. Breaking changes are communicated through the [changelog](/reference/changelog) and SDK release notes rather than through a URL version. See [Base URLs & versioning](/api-reference/environments) for how ModuleX evolves the API without a path version. ## Request lifecycle Every authenticated call follows the same path. Authentication and organization context are resolved as route dependencies — there is no separate request-id or auth middleware to account for. Set the method and resource path, attach the two required headers ([`Authorization: Bearer`](/api-reference/authentication) and [`X-Organization-ID`](/security/org-context)), and send a JSON body for write operations. A `Bearer` token that starts with `mx_live_` is treated as an [API key](/security/authentication); any other Bearer token is treated as a Clerk session JWT (used by the app). A missing or invalid token returns `401`. For org-scoped operations, ModuleX reads `X-Organization-ID` and checks your membership and role. A missing header returns `400`; a non-member or a role too low for the operation returns `403`. See [Roles & permissions](/security/roles-permissions). API-key traffic is rate limited per key and per user, and org traffic is rate limited per organization. Operations that consume managed usage also pass the [usage gate](/billing/usage-gating) before any work begins. Either check can stop the request with a `429`, `403`, or `402`. The handler executes and returns a JSON body. For most operations that is the final result. For runs and agent turns, the response returns immediately with run metadata and the result streams separately over [SSE](/realtime/sse-streaming). **🎬 MEDIA PLACEHOLDER · MX-MEDIA-1180 · [IMAGE]** **[IMAGE_DESCRIPTION]:** Request lifecycle diagram for a ModuleX API call. **[IMAGE_DETAILS]:** A left-to-right flow with five stages matching the Steps above — Client request (showing the two required headers) → Auth (Bearer mx_live_* vs Clerk JWT branch) → Org context (X-Organization-ID, role check) → Rate limit + usage gate (showing the 429 / 403 / 402 exit) → Handler → JSON response, with a side branch from the handler labeled "SSE stream" for runs and agent turns. Annotate each guard with its failure status code. 16:9, light theme, ModuleX brand palette, no drop shadows. ### Authentication recap Authentication is the same on every operation, so it is documented in full on one page. Each request needs two headers: | Header | Value | Required | | --- | --- | --- | | `Authorization` | `Bearer mx_live_…` (your API key) | Yes | | `X-Organization-ID` | The organization UUID the call acts on | Yes for org-scoped operations | The backend also accepts the API key in an `X-API-KEY` header as an alternative to `Authorization: Bearer`, but the SDKs and all examples in these docs use `Authorization: Bearer`. The header is `X-Organization-ID` (capital `ID`); there is no `X-Authorization` header. For the full authentication model — how to create and scope keys, the difference between API keys and Clerk JWTs, and every auth-related status code — see [Authentication](/api-reference/authentication) and [Auth model: JWT vs API key](/security/authentication). ## Content types and conventions Send `application/json` for request bodies. Document upload to a knowledge base is the exception: those endpoints accept `multipart/form-data`. Responses are JSON by default. Streaming endpoints return `text/event-stream` ([Server-Sent Events](/realtime/sse-streaming)) instead. Request and response bodies use `snake_case` on the wire (for example `workflow_id`, `created_at`, `run_id`). The Python SDK uses `snake_case` end to end. The JavaScript SDK accepts `camelCase` in method parameters and converts to `snake_case` before sending, but **leaves responses in `snake_case`** — so a JS response field is `run_id`, not `runId`. Timestamps are ISO 8601 strings in UTC, for example `2026-06-21T12:00:00Z`. Resource identifiers are strings. Note that `run_id` carries distinct meanings across layers — see [Workflows & runs](/concepts/workflows-and-runs) for the three run-id identities. List endpoints are paginated. The style (page, offset, or cursor) depends on the resource, and the SDKs provide auto-paginating helpers. See [Pagination](/api-reference/pagination) for the details. ## One operation, three ways, plus a playground Each operation is documented **once**. Instead of separate per-language pages, you get a single `CodeGroup` with three tabs — cURL, Python, JavaScript — that all perform the same call, plus an interactive playground for the underlying endpoint. The raw HTTP request: exact method, path, headers, and JSON body. The async `modulex-python` client (v1.0.0). The `modulex-js` client (v1.0.0). The cURL tab is the ground truth: it shows the literal wire call, so it works in any language. The Python and JavaScript tabs show the official SDK method that wraps that same call. Where an operation has no SDK method (a parity gap), the docs say so and show cURL only — see the [SDK ⇄ API parity matrix](/sdks/parity) for the complete coverage map, including the `subscriptions` resource that exists only in the Python SDK. ### Example: run a workflow This is what the three-way pattern looks like. Running a deployed workflow is a single `POST /workflows/run`. The synchronous response returns immediately with run metadata while the result streams over [SSE](/realtime/sse-streaming). ```bash cURL curl https://api.modulex.dev/workflows/run \ -H "Authorization: Bearer mx_live_8s2Kd0pQ4rTv7Xw" \ -H "X-Organization-ID: 5d9c2e7a-1f4b-4a6c-9e3d-0b8a1c2d3e4f" \ -H "Content-Type: application/json" \ -d '{ "workflow_id": "wf_3b1c9a2e", "input": { "topic": "Q3 launch summary" }, "stream": true }' ``` ```python Python import asyncio from modulex import Modulex async def main(): async with Modulex( api_key="mx_live_8s2Kd0pQ4rTv7Xw", organization_id="5d9c2e7a-1f4b-4a6c-9e3d-0b8a1c2d3e4f", ) as client: run = await client.executions.run( workflow_id="wf_3b1c9a2e", input={"topic": "Q3 launch summary"}, stream=True, ) print(run.run_id, run.status) asyncio.run(main()) ``` ```javascript JavaScript import { Modulex } from 'modulex-js'; const client = new Modulex({ apiKey: 'mx_live_8s2Kd0pQ4rTv7Xw', organizationId: '5d9c2e7a-1f4b-4a6c-9e3d-0b8a1c2d3e4f', }); const run = await client.executions.run({ workflowId: 'wf_3b1c9a2e', input: { topic: 'Q3 launch summary' }, stream: true, }); // Responses stay snake_case even in the JS SDK. console.log(run.run_id, run.status); ``` The per-execution identifier. Use it to stream events and to look up run status. A new `run_id` is minted on each run and on each resume of an interrupted run. The status of the synchronous portion. For a streaming run this is `"running"`; terminal statuses (`completed`, `failed`, `cancelled`, `interrupted`) are observed over the [SSE stream](/realtime/sse-streaming) or the run-history endpoints, not here. The run body accepts exactly one workflow source: Run a previously saved workflow. Requires an active [deployment](/workflow-builder/execution/deploy); otherwise the call returns `400`. Mutually exclusive with `workflow`. An inline workflow definition to run without saving. Mutually exclusive with `workflow_id`. Pair with `attribution_workflow_id` to attribute the ad-hoc run to a saved workflow's run history. State input values passed to the workflow's entry node. Runtime overrides for this execution. Recognized keys include `thread_id`, `recursion_limit`, and `batch_interval_ms`. Whether to open an SSE stream for real-time events. If `true`, the run is not persisted and no thread is created. If `true`, the run and its messages are visible only to the creator. The legacy direct-LLM mode of this endpoint was removed. A request that only supplies LLM config now returns `410 Gone` — use [the Assistant](/assistant/overview) (`POST /assistant/chat`) instead. For the complete end-to-end walkthrough — authenticate, run, and consume the stream in all three languages — see [Run a workflow (REST + SDK)](/guides/run-a-workflow). To make your very first call, start with [Make your first API call](/get-started/first-api-call). ### Live playground Endpoint reference pages include a live "Try it" playground generated from the ModuleX OpenAPI specification. Enter your API key and organization ID once, fill in parameters, and send a real request from the browser to see the actual response. The playground exercises the same routes documented above — for example `POST /workflows/run`, `GET /workflows`, and `POST /knowledge-bases/{knowledge_id}/search`. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-1181 · [SCREENSHOT]** **[SCREENSHOT_DESCRIPTION]:** The interactive API playground for an endpoint, mid-request. **[SCREENSHOT_DETAILS]:** Capture a generated endpoint reference page (for example `POST /workflows/run`) showing the auth fields populated with `Authorization: Bearer mx_live_…` and `X-Organization-ID`, the request-body editor with the example body filled in, and a successful JSON response panel below the Send button. Crop to the playground panel. Light theme, ModuleX docs chrome, redact any real key value. ## Errors Operations return standard HTTP status codes. ModuleX has **three** error-envelope shapes, and you should branch on all of them. The shape depends on the surface that produced the error. The common FastAPI shape, used by plain CRUD and org-settings routes. The `detail` is a human-readable string, for example `{ "detail": "X-Organization-ID header is required" }`. A dict-valued `detail`, used by the organization rate-limit deny (`code: "rate_limited"`) and the wallet paid-gate `402`. The flat billing/credit/rate/quota envelope returned by the usage gate, with no `detail` wrapper: `{ code, layer, key, current, limit, reason }`. The billing usage gate is **live**. Operations that consume managed usage — running workflows, the [Composer](/concepts/ai-composer), the [Assistant](/concepts/assistant), and [managed knowledge](/platform/knowledge/managed) — pass through the gate before any work begins. When you exceed a limit, the gate returns a flat `DenialEnvelope` whose `layer` field maps to the status code: | `layer` | HTTP status | Meaning | | --- | --- | --- | | `rate` | `429` | Rate limit exceeded (also carries `X-RateLimit-*` and `Retry-After` headers) | | `quota` | `403` | A plan quota was exceeded | | `credit` | `402` | Plan credit allowance exhausted, or an upgrade payment failed | | `wallet` | `402` | Overage is disabled, or the prepaid wallet has insufficient balance | These billing envelopes appear only on the managed-usage surfaces above. Plain CRUD and org-settings routes return the `{detail}` `HTTPException` shape instead. For the complete status-code taxonomy, every error `code`, and which surface emits each shape, see [Errors & status codes](/api-reference/errors), [Rate limiting](/api-reference/rate-limiting), and [Usage gating & limits](/billing/usage-gating). ## Where to go next Create keys, scope them to an organization, and authenticate every call. Install the JavaScript or Python client and use every operation from code. A full REST and SDK walkthrough, from key to streamed result. The three envelope shapes and the billing `DenialEnvelope` in detail. --- # Pagination URL: https://docs.modulex.dev/api-reference/pagination How ModuleX list endpoints paginate — offset/page, offset/limit, and next_cursor styles — plus the SDK auto-paginators (listAll, list_all, iter_runs) and manual cursor loops, with cURL, Python, and JavaScript. ModuleX list endpoints return one page at a time. There is **no single pagination style** across the API — different resources use different schemes, and the response field you read to fetch the next page depends on which scheme the endpoint uses. This page documents all three styles, the exact query parameters and response fields for each list endpoint, and the SDK helpers that page automatically so you do not have to loop by hand. Every list call is authenticated the same way as any other request: `Authorization: Bearer mx_live_…` plus `X-Organization-ID`. See [Authentication](/api-reference/authentication) for the full model. All list endpoints in this reference are read-only `GET` requests, which means the SDKs will safely retry them on transient failures (`429`/`500`/`502`/`503`) — see [SDK errors & retries](/sdks/errors-retries). ## The three pagination styles | Style | Query params | Response page key | "More?" signal | Used by | | --- | --- | --- | --- | --- | | **Offset / page** | `page`, `page_size` | `workflows` | `total`, `total_pages` (only when `page_size` is sent) | `GET /workflows` | | **Offset / limit** | `limit`, `offset` | `runs` | `has_more` (no total count) | `GET /workflow-runs` | | **Cursor** | `limit`, `cursor` | `items` | `next_cursor` (`null` at the end) | `GET /composer/chats`, `GET /assistant/chats` | Responses are **snake_case on the wire** for both SDKs. The JavaScript SDK converts your *request* parameters from camelCase to snake_case (so you pass `pageSize` and it sends `page_size`), but it does **not** convert responses back — you read `response.total_pages`, `response.has_more`, and `response.next_cursor` exactly as the API returns them. The Python SDK is snake_case in both directions. There is no generic, top-level `{ items, total, … }` envelope shared by every endpoint. Each list nests its rows under a **resource-specific key** (`workflows`, `runs`, or `items`) and exposes only the "more?" signal that fits its style. The JavaScript SDK ships a `PaginatedList` interface whose fields are all optional precisely because it is a superset across these styles — concrete list responses use their own narrower types, not `PaginatedList` directly. ### Choosing a style as a caller - **Offset / page** lets you jump to an arbitrary page and shows a total count and page count — good for "page 3 of 12" UIs, at the cost of an extra `COUNT(*)` on the server. - **Offset / limit** with `has_more` avoids the count query and is cheaper for deep history; you cannot show a total, only "there is another page." - **Cursor** is the most stable under concurrent writes: rows are keyed by their `updated_at` timestamp, so inserting a new row while you page does not shift or duplicate items the way a moving `offset` can. Cursor lists are user-scoped and always newest-first. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-1210 · [IMAGE]** **[IMAGE_DESCRIPTION]:** Side-by-side comparison diagram of the three ModuleX pagination styles (offset/page, offset/limit with has_more, cursor with next_cursor). **[IMAGE_DETAILS]:** Three labeled columns. Column 1 "Offset / page": show `page=1&page_size=50` → response `{workflows, total, total_pages}` and a "jump to page N" callout. Column 2 "Offset / limit": show `limit=50&offset=0` → `{runs, has_more}` advancing by `offset += limit` until `has_more=false`. Column 3 "Cursor": show `limit=20` → `{items, next_cursor}` feeding `cursor=next_cursor` until `next_cursor=null`, with a note that new rows do not shift the window. Landscape 16:9, light theme, neutral palette, no app chrome. ## Offset / page — `GET /workflows` `GET /workflows` lists the workflows in your organization. It is the only list that exposes a **total count and page count**, and it does so **only when you supply `page_size`**. With no pagination parameters, it returns *all* matching workflows in a single response plus `total` — there is no implicit default page size. This is also the only endpoint with an SDK **auto-paginator** in both clients (JavaScript `workflows.listAll`, Python `workflows.list_all`). ### Request parameters 1-based page number. Must be `>= 1`. Optional. When omitted but `page_size` is sent, the server defaults to page `1`. Items per page, `1`–`100`. Optional. **Pagination metadata (`page`, `page_size`, `total_pages`) is included in the response only when this parameter is present.** Omit both `page` and `page_size` to receive every matching workflow at once. In the JavaScript SDK this parameter is `pageSize` (camelCase) and is sent as `page_size`. Filter by lifecycle status (for example `active`, `draft`, `published`). Optional. Filter by workflow category. Optional. Filter by sharing visibility — one of `private`, `organization`, `public`, or `system`. Optional. New workflows default to `organization`, and `private` is no longer creator-only (it behaves like `organization`). Free-text search over workflow name/description. Optional. ### Response ```json { "workflows": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Weekly AI digest", "description": "Summarize the week's AI news", "version": "1.0", "status": "active", "visibility": "organization", "category": "research", "tags": ["news", "summary"], "creator_id": "9c4f2e1a-7b3d-4c8e-9f0a-1b2c3d4e5f6a", "created_at": "2026-06-01T09:00:00+00:00", "updated_at": "2026-06-19T14:32:10+00:00" } ], "total": 134, "page": 1, "page_size": 50, "total_pages": 3 } ``` The page of workflow summaries. Rows are nested under `workflows`, not a generic `items` key. The total number of workflows matching the filters across all pages. **Always present**, regardless of whether pagination parameters were sent. The current 1-based page. **Present only when `page_size` was supplied** in the request. The page size echoed back. **Present only when `page_size` was supplied.** `ceil(total / page_size)`. **Present only when `page_size` was supplied.** This is the field auto-paginators read to know when to stop. ### Paging by hand Increment `page` until you reach `total_pages`. ```bash cURL curl "https://api.modulex.dev/workflows?status=active&page=1&page_size=50" \ -H "Authorization: Bearer mx_live_xxx" \ -H "X-Organization-ID: 11111111-1111-1111-1111-111111111111" ``` ```python Python import asyncio from modulex import Modulex async def main(): async with Modulex( api_key="mx_live_xxx", organization_id="11111111-1111-1111-1111-111111111111", ) as client: page = 1 while True: result = await client.workflows.list( status="active", page=page, page_size=50 ) for wf in result.workflows: print(wf.id, wf.name) if page >= result.total_pages: break page += 1 asyncio.run(main()) ``` ```javascript JavaScript import { Modulex } from "modulex-js"; const client = new Modulex({ apiKey: "mx_live_xxx", organizationId: "11111111-1111-1111-1111-111111111111", }); let page = 1; while (true) { const result = await client.workflows.list({ status: "active", page, pageSize: 50, }); for (const wf of result.workflows) { console.log(wf.id, wf.name); } const totalPages = result.total_pages ?? Math.ceil(result.total / 50); if (page >= totalPages) break; page += 1; } ``` ### Auto-paginating with the SDKs Both SDKs expose a generator that walks every page for you, fixing `page_size` at `100` internally and yielding one workflow at a time. The JavaScript method is `workflows.listAll`; the Python method is `workflows.list_all`. Pass the same filters as `list`, **minus** `page`/`page_size` (the helper controls those). ```python Python async with Modulex( api_key="mx_live_xxx", organization_id="11111111-1111-1111-1111-111111111111", ) as client: # list_all returns an AsyncPage[WorkflowResponse]; iterate it directly. async for wf in client.workflows.list_all(status="active", search="digest"): print(wf.id, wf.name) # typed WorkflowResponse ``` ```javascript JavaScript // listAll is the ONLY auto-paginator in the JS SDK — workflows only. for await (const wf of client.workflows.listAll({ status: "active", search: "digest" })) { console.log(wf.id, wf.name); // WorkflowSummary } ``` There is no cURL equivalent for `listAll` / `list_all`. Auto-pagination is an SDK convenience that loops the underlying `GET /workflows` calls; with raw HTTP you page manually as shown above. ## Offset / limit — `GET /workflow-runs` `GET /workflow-runs` returns durable run history for the organization, newest first. It is **preview-optimized** (it omits the heavy input/output snapshot columns) and deliberately **does not return a total count** — paging uses a `has_more` boolean instead, so the server never runs a `COUNT(*)` over potentially huge history. Advance by adding `limit` to `offset` until `has_more` is `false`. The `id` field on each run row is the run-history **primary key**, which is what you pass to `GET /workflow-runs/{run_pk}`. It is **not** the same as the per-execution `run_id` used by `GET /workflows/listen/{run_id}` and `POST /workflows/cancel/{run_id}`. Passing a `run_id` to the by-PK endpoint returns `404`. ModuleX uses "run id" in three distinct senses — see [Workflows & runs](/concepts/workflows-and-runs). ### Request parameters Page size, `1`–`100`. Optional, defaults to `50`. Number of rows to skip from the start of the (newest-first) result set. Must be `>= 0`. Optional, defaults to `0`. Filter to a single workflow's run history (UUID). Optional. In the JavaScript SDK this is `workflowId`. Filter by durable run status (for example `succeeded`, `failed`, `running`). Optional. Filter by how the run was triggered (for example `api`, `schedule`, `chat`). Optional. In the JavaScript SDK this is `triggerType`. ### Response ```json { "runs": [ { "id": "7f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f", "run_id": "6a7b8c9d-1e2f-3a4b-5c6d-7e8f9a0b1c2d", "workflow_id": "550e8400-e29b-41d4-a716-446655440000", "trigger_type": "api", "is_ad_hoc": false, "status": "succeeded", "started_at": "2026-06-19T14:30:00+00:00", "completed_at": "2026-06-19T14:30:42+00:00", "duration_seconds": 42.0, "error_message": null, "created_at": "2026-06-19T14:30:00+00:00", "has_output": true } ], "has_more": true, "limit": 50, "offset": 0 } ``` The page of run-history rows, newest first. Rows are nested under `runs`. `true` when another page exists at `offset + limit`. The server determines this without a count query (it fetches one row beyond the page). Stop when this is `false`. The page size echoed back. The offset echoed back. ### Paging by hand ```bash cURL curl "https://api.modulex.dev/workflow-runs?status=succeeded&limit=50&offset=0" \ -H "Authorization: Bearer mx_live_xxx" \ -H "X-Organization-ID: 11111111-1111-1111-1111-111111111111" ``` ```python Python async with Modulex( api_key="mx_live_xxx", organization_id="11111111-1111-1111-1111-111111111111", ) as client: offset = 0 limit = 50 while True: result = await client.executions.list_runs( status="succeeded", limit=limit, offset=offset ) for run in result.runs: print(run.run_id, run.status) if not result.has_more: break offset += limit ``` ```javascript JavaScript let offset = 0; const limit = 50; while (true) { const result = await client.workflowRuns.list({ status: "succeeded", limit, offset, }); for (const run of result.runs) { console.log(run.run_id, run.status); } if (!result.has_more) break; offset += limit; } ``` Run history is grouped differently in the two SDKs. The Python SDK folds it into `client.executions` (`list_runs` for one page, `iter_runs` to auto-paginate). The JavaScript SDK exposes a separate `client.workflowRuns` resource (`list` / `get`) and has **no** auto-paginator for runs — loop manually. Both call the same `GET /workflow-runs` routes. ### Auto-paginating runs (Python only) The Python SDK provides `executions.iter_runs`, which walks every page using `has_more` and yields typed `WorkflowRunListItem` records. Its internal page size defaults to `50`. ```python Python async with Modulex( api_key="mx_live_xxx", organization_id="11111111-1111-1111-1111-111111111111", ) as client: async for run in client.executions.iter_runs(status="succeeded"): print(run.run_id, run.status) # typed WorkflowRunListItem ``` There is **no JavaScript equivalent of `iter_runs`**. The JS SDK's only auto-paginator is `workflows.listAll`. For run history in JavaScript, loop `client.workflowRuns.list({ offset })` on `has_more` as shown above. This gap is tracked in the [SDK parity matrix](/sdks/parity). ## Cursor — `GET /composer/chats` and `GET /assistant/chats` The [AI Composer](/concepts/ai-composer) and [Assistant](/assistant/overview) chat lists are **user-scoped** (they return the calling user's own chats) and use cursor pagination keyed on each row's `updated_at` timestamp, newest first. You pass the previous page's last `updated_at` back as the `cursor` to fetch the next page; the server returns `next_cursor`, which is `null` once there are no more pages. Both endpoints have an identical contract. Because the cursor is an `updated_at` timestamp rather than a moving offset, inserting or updating chats while you page does not skip or duplicate rows the way `offset` can. ### Request parameters Page size, `1`–`100`. Optional, defaults to `20`. An ISO 8601 timestamp — the `updated_at` of the **last item on the previous page**. Omit it to fetch the first (newest) page. Optional. `cursor` must be a valid ISO 8601 timestamp. A malformed value returns `400` with `{"detail": "cursor must be an ISO timestamp"}`. Always source the cursor from a prior response's `next_cursor` rather than constructing it yourself. ### Response ```json { "items": [ { "id": "c1a2b3c4-d5e6-7f80-9a1b-2c3d4e5f6a7b", "title": "Build a lead-routing workflow", "created_at": "2026-06-18T11:00:00+00:00", "updated_at": "2026-06-19T16:45:12+00:00", "is_running": false } ], "next_cursor": "2026-06-19T16:45:12+00:00" } ``` The page of chat summaries, newest first. Rows are nested under `items` (this is the one list family that uses the generic `items` key). The Composer list adds a `workflow_id` field on each row; the Assistant list does not. The cursor for the next page — the `updated_at` of the last item — **or `null` when there are no more pages**. The server returns a non-null `next_cursor` only when the page came back full (exactly `limit` rows), so a short final page yields `null`. ### Paging by hand Loop, passing `next_cursor` back as `cursor`, until `next_cursor` is `null`. Neither SDK ships an auto-paginator for these lists, so you loop manually in every language. ```bash cURL # First page curl "https://api.modulex.dev/assistant/chats?limit=20" \ -H "Authorization: Bearer mx_live_xxx" \ -H "X-Organization-ID: 11111111-1111-1111-1111-111111111111" # Next page — pass the previous response's next_cursor (URL-encoded) curl "https://api.modulex.dev/assistant/chats?limit=20&cursor=2026-06-19T16%3A45%3A12%2B00%3A00" \ -H "Authorization: Bearer mx_live_xxx" \ -H "X-Organization-ID: 11111111-1111-1111-1111-111111111111" ``` ```python Python async with Modulex( api_key="mx_live_xxx", organization_id="11111111-1111-1111-1111-111111111111", ) as client: cursor = None while True: page = await client.assistant.list(limit=50, cursor=cursor) for chat in page.items: print(chat.title) cursor = page.next_cursor if cursor is None: break ``` ```javascript JavaScript let cursor; do { const page = await client.assistant.list({ limit: 50, cursor }); for (const chat of page.items) { console.log(chat.title); } cursor = page.next_cursor ?? undefined; } while (cursor); ``` Swap `assistant` for `composer` to page Composer chats — `client.composer.list(...)` takes the same `limit` and `cursor` and returns the same `{ items, next_cursor }` shape (plus a `workflow_id` per row). ## SDK auto-paginator reference Only three SDK methods auto-paginate. Everything else is a single page you loop yourself. | Method | SDK | Endpoint | Style | Yields | Notes | | --- | --- | --- | --- | --- | --- | | `workflows.listAll(params?)` | JavaScript | `GET /workflows` | page | `WorkflowSummary` | The **only** JS auto-paginator. Returns an `AsyncGenerator`; fixes `pageSize=100`. | | `workflows.list_all(...)` | Python | `GET /workflows` | page | `WorkflowResponse` | Returns an `AsyncPage[T]`. Fixes `page_size=100`. | | `executions.iter_runs(...)` | Python | `GET /workflow-runs` | offset | `WorkflowRunListItem` | Returns an `AsyncPage[T]`; default page size `50`. No JS equivalent. | In Python, `list_all` and `iter_runs` return an `AsyncPage[T]` — a lazy async-iterable that validates each row into the typed Pydantic model as you iterate. You consume it with `async for`; it fetches pages on demand, not all at once. In JavaScript, `listAll` is a plain `AsyncGenerator` you consume with `for await`. The cursor lists (`composer.list`, `assistant.list`) and the JavaScript runs list (`workflowRuns.list`) return a **single page**; you loop them by hand using the snippets above. There is no plan to wrap the cursor lists in an auto-paginator at present. The full route-to-method mapping, including these pagination gaps, is in the [SDK ⇄ API parity matrix](/sdks/parity). ## Edge cases and gotchas It does — but **only when you send `page_size`**. With no pagination parameters, `GET /workflows` returns every matching workflow plus `total`, and omits `page`, `page_size`, and `total_pages` entirely. If your code reads `response.total_pages` unconditionally, send `page_size` or fall back to `Math.ceil(total / pageSize)` (which is exactly what the JS `listAll` helper does). You can't get an exact count from this endpoint; it intentionally trades the count query for cheaper deep paging. Use `has_more` to drive a "Load more" affordance instead of "page N of M". If you need an authoritative total, that signal is not exposed here. `cursor` must be a valid ISO 8601 timestamp. The response is `{"detail": "cursor must be an ISO timestamp"}`. Always reuse a prior response's `next_cursor` verbatim; do not hand-build or truncate it. When sending it over raw HTTP, URL-encode it (the `+` and `:` characters in a timestamp must be percent-encoded). This is inherent to `offset`-based paging (`GET /workflow-runs`) when rows are inserted during iteration — a newer run pushes everything down by one, so a fixed `offset` can re-show or skip a row. Cursor pagination (`GET /composer/chats`, `GET /assistant/chats`) avoids this because the cursor pins to an `updated_at` timestamp rather than a positional offset. If stable iteration over runs matters, filter to a closed set (for example a single `workflow_id` plus a terminal `status`) so new runs don't enter the window. No. The flat `DenialEnvelope` (`402`/`403`/`429`) is returned only by the billing-gated surfaces — run, composer, assistant, and managed knowledge. The list endpoints documented here are plain reads; on error they return the standard FastAPI `{"detail": ""}` shape, or a header-based `429` if you exceed the request rate limit. See [Errors & status codes](/api-reference/errors) and [Rate limiting](/api-reference/rate-limiting). The JS SDK converts your **request** parameters from camelCase to snake_case (`pageSize` → `page_size`) but leaves **responses** in their original snake_case. So you pass `pageSize`/`workflowId`/`triggerType` in, and read `total`, `total_pages`, `has_more`, and `next_cursor` out. The Python SDK is snake_case in both directions. ## Errors on list endpoints List endpoints use the standard FastAPI error envelope `{"detail": ""}`. | Status | When it happens | | --- | --- | | `400` | Missing `X-Organization-ID`; or a malformed `cursor` on a cursor list (`{"detail": "cursor must be an ISO timestamp"}`). | | `401` | Missing or invalid token (`WWW-Authenticate: Bearer` is returned). | | `403` | The caller lacks the required role. The run-history, Composer, and Assistant lists require an organization **owner or admin** (the `member` role is retired — see [Roles & permissions](/security/roles-permissions)). | | `422` | A query parameter failed validation (for example `page_size` outside `1`–`100`), returned as the FastAPI validation array. | | `429` | You exceeded the per-key or per-user request rate limit. The response carries `Retry-After` and `X-RateLimit-*` headers — see [Rate limiting](/api-reference/rate-limiting). | | `500` | An unexpected server error. | The SDKs map these to typed errors: `401` → `AuthenticationError`, `403` → `PermissionError`, `404` → `NotFoundError`, `422` → `ValidationError`, and `429` → `RateLimitError` (which exposes `retryAfter`/`retry_after`, `limit`, `remaining`, and `reset` from the headers). The read-only list calls are auto-retried on `429`/`500`/`502`/`503` with backoff that honors `Retry-After`. See [SDK errors & retries](/sdks/errors-retries) for the complete error class hierarchy and retry policy. ## Next steps The JavaScript and Python clients, unified by operation, with install and configuration. Every REST route mapped to its JS and Python methods, with pagination and other gaps called out. The three error-envelope shapes and which surface emits each. Per-key and per-user limits, the `429` response, and the rate-limit headers. --- # Rate limiting URL: https://docs.modulex.dev/api-reference/rate-limiting How ModuleX rate-limits the REST API: per-key and per-user limits, the org and run-surface limiters, the 429 response shapes (string detail, dict detail, and the flat DenialEnvelope), the X-RateLimit-* / Retry-After headers, and how to back off in the SDKs. ModuleX rate-limits the REST API at several independent layers. Most callers only meet the **per-key** and **per-user** request limiters, which return a 429 with a plain string `detail` and a standard set of rate-limit headers. Workflow runs and other managed-usage surfaces add a **run-rate** limiter that returns the flat [`DenialEnvelope`](/api-reference/errors), and organization-scoped routes add a third **org `api`-class** limiter. This page documents every limiter, every 429 wire shape, the headers each carries, and how to back off. This page covers the HTTP API only. The realtime Socket.io collaboration server enforces a separate transport-level flood limit that is **not** an HTTP 429 — see [its own section below](#socket-io-transport-rate-limit-not-a-429) and [/realtime/socket-events](/realtime/socket-events). **🎬 MEDIA PLACEHOLDER · MX-MEDIA-1230 · [IMAGE]** **[IMAGE_DESCRIPTION]:** Diagram of the rate-limit layers an API request passes through. **[IMAGE_DETAILS]:** A left-to-right flow: incoming request → (1) per-key limiter `ratelimit:apikey:{id}` → (2) per-user limiter `ratelimit:user:{id}` → (3) org `api`-class limiter (org-scoped routes only) → (4) run-rate limiter `sync_exec`/`async_exec` (run/managed-usage surfaces only). Each box notes the 429 shape it emits (Shape A string `detail`, Shape B dict `detail`, Shape D flat `DenialEnvelope`). Annotate that any layer can short-circuit with 429. 16:9, light theme, no app chrome. ## The rate-limit layers at a glance | Layer | Scope | Where it runs | Default / limit | 429 wire shape | | --- | --- | --- | --- | --- | | Per-key | One API key | Every API-key-authenticated request | `60` req/min (key default) | Shape A — `{"detail": "API key rate limit exceeded"}` + headers | | Per-user | All keys for one user | Every API-key-authenticated request | `300` req/min | Shape A — `{"detail": "User rate limit exceeded (across all API keys)"}` + headers | | Org `api`-class | One organization | Org-scoped routes (membership-gated) | Plan-dependent (`api` class) | Shape B — `{"detail": {"code": "rate_limited", ...}}` + headers | | Run-rate (`sync_exec` / `async_exec`) | One organization | Run / managed-usage surfaces only | Plan-dependent (run classes) | Shape D — flat [`DenialEnvelope`](/api-reference/errors) + headers | All limiters use a Redis-backed **sliding 60-second window**, and all **fail open**: if Redis is unavailable or the check raises, the request is allowed rather than rejected. The per-key and per-user limiters are checked together — a request is admitted only if **both** have remaining capacity, and the layer that ran out determines the `detail` string. A single 429 status can carry **three different bodies** depending on which limiter fired. The per-key/per-user limiter returns a string `detail`; the org limiter returns a dict `detail` with `code: "rate_limited"`; the run-rate limiter returns a flat `DenialEnvelope` with `code: "rate_limit_exceeded"`. Branch on the response body, not just the status code. The full taxonomy lives on [/api-reference/errors](/api-reference/errors). ## Per-key and per-user limits Every request authenticated with an `mx_live_…` API key passes through a two-level request limiter before it reaches a route handler. Both levels share the same sliding 60-second window. - **Per-key** — each API key has its own `rate_limit_per_minute`. The default is **60 requests per minute**. The Redis counter key is `ratelimit:apikey:{api_key_id}`. - **Per-user** — across **all** of a user's API keys, the aggregate limit is **300 requests per minute**. The Redis counter key is `ratelimit:user:{user_id}`. A request is admitted only when both counters are below their limit. When the per-key limit is hit first, the `detail` is `"API key rate limit exceeded"`; when the per-user aggregate is hit first, it is `"User rate limit exceeded (across all API keys)"`. Both responses are HTTP 429 with the same header set. The per-key request ceiling, stored on each API key. New keys are created with `60`; an administrator can raise or lower it per key. The per-user aggregate of `300` req/min applies on top and is not configurable per request. This limiter authenticates and counts the request **before** any route runs, so it applies uniformly to reads, writes, and run triggers made with an API key. ### 429 response — per-key / per-user (Shape A) ```json 429 Too Many Requests — Shape A { "detail": "API key rate limit exceeded" } ``` The response carries these headers: The ceiling for the limiter that produced the response (the per-key limit, e.g. `60`). Requests left in the current 60-second window. `0` when the limit is hit. Unix epoch time in seconds when the window resets and capacity is restored. Seconds to wait before retrying. Always present on a 429; defaults to `60` when a more precise value is not available. ## Org `api`-class limit Org-scoped routes (those that require organization membership via `X-Organization-ID`) add an **organization `api`-class** limiter on top of the per-key and per-user limiters. Its ceiling comes from your plan's `api` rate-limit entitlement, so it is plan-dependent rather than a fixed default. See [/billing/usage-gating](/billing/usage-gating) and [/billing/plans](/billing/plans) for the per-plan values. This limiter **stacks** with the others — an org-scoped API-key request is counted against the org `api`-class counter, the per-user counter (300/min), and the per-key counter. It **fails open** on any error, so a Redis outage never blocks org traffic. Administrative routes and scheduled runs do not pass through it. ### 429 response — org `api`-class (Shape B) The org limiter returns the structured envelope **wrapped under `detail`** (a dict-valued `detail`), with `code: "rate_limited"`: ```json 429 Too Many Requests — Shape B { "detail": { "code": "rate_limited", "layer": "rate", "key": "api", "current": 100, "limit": 100, "reason": "rate_limit_exceeded" } } ``` Always `"rate_limited"` for this limiter. Note this differs from the run-rate limiter's `"rate_limit_exceeded"` (see below) — same status, different code. Always `"rate"`. Always `"api"` — the rate class that denied the request. Observed request count in the window at denial time. The org `api`-class ceiling from your plan. Always `"rate_limit_exceeded"`. This response carries the same `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers documented above. ## Run-rate limits (run / managed-usage surfaces) Workflow runs and the other managed-usage surfaces — the AI Composer, the [Assistant](/assistant/permissions-and-limits), and managed knowledge retrieval and ingestion — pass through an additional **run-rate** limiter as part of the billing admission gate. This limiter enforces two plan-scoped classes: - **`sync_exec`** — synchronous (blocking) run executions per minute. - **`async_exec`** — asynchronous run executions per minute. Both ceilings come from your plan's rate-limit entitlements, so the exact numbers are plan-dependent. A `null` entitlement means **unlimited** (the limiter is skipped), and a `0` entitlement means **blocked**. See [/billing/usage-gating](/billing/usage-gating) for the per-plan run-rate values and [/billing/plans](/billing/plans) for the plan matrix. When the run-rate limiter denies a request, it first releases any credit reservation the gate had taken (so a blocked run is charged nothing), then raises a 429 carrying the flat [`DenialEnvelope`](/api-reference/errors). This is the form you must handle on these surfaces alongside the header-based 429. The run-rate limiter is part of the **live** billing admission gate. It applies on the run / Composer / Assistant / managed-knowledge surfaces and is **absent** on plain CRUD and org-settings routes — those return only the `{"detail": …}` shapes. The same gate also produces 402 (credit/wallet) and 403 (quota) denials; those are covered on [/api-reference/errors](/api-reference/errors) and [/billing/usage-gating](/billing/usage-gating). ### 429 response — run-rate (Shape D, `DenialEnvelope`) The run-rate denial is a **flat** object with no `detail` wrapper and `code: "rate_limit_exceeded"`: ```json 429 Too Many Requests — Shape D { "code": "rate_limit_exceeded", "layer": "rate", "key": "sync_exec", "current": 150, "limit": 150, "reason": "rate_limit_exceeded" } ``` Machine-stable denial code. For the run-rate limiter this is always `"rate_limit_exceeded"`. Branch on this in client code. The gate layer that denied the request. `"rate"` for run-rate denials. The `layer` also determines the HTTP status: `rate` → 429, `quota` → 403, `credit` → 402, `wallet` → 402. The rate class that was exhausted — `"sync_exec"` or `"async_exec"`. Observed run count in the window at denial time. May be `null` when not applicable. The plan ceiling for the class that was hit. May be `null` when not applicable. Short reason token; `"rate_limit_exceeded"` for run-rate denials. A `rate`-layer `DenialEnvelope` also carries the rate-limit headers, each **omitted** when its underlying value is unknown rather than sent as an empty string. `Retry-After` is always present and defaults to `60`. Seconds to wait before retrying. Defaults to `60`. The class ceiling. Omitted when unknown. Runs left in the window. Omitted when unknown. Unix epoch (seconds) when the window resets. Omitted when unknown. ## Reading the headers Every 429 response — whichever limiter fired — carries `Retry-After`, and where the value is known, `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset`. The example below triggers a workflow run, then inspects the headers on a 429. Replace `mx_live_…` with your API key and the IDs with your own. ```bash cURL # Trigger a run; on 429, the headers tell you how long to wait. curl -i -X POST "https://api.modulex.dev/workflows/wf_8f3c2a1b/run" \ -H "Authorization: Bearer mx_live_8b1c4e9f2a7d6035c1e84f29" \ -H "X-Organization-ID: org_3d9a1f7c52e84b06" \ -H "Content-Type: application/json" \ -d '{"input": {"topic": "quarterly report"}}' # HTTP/1.1 429 Too Many Requests # Retry-After: 60 # X-RateLimit-Limit: 150 # X-RateLimit-Remaining: 0 # X-RateLimit-Reset: 1718924400 ``` ```python Python import asyncio from modulex import Modulex, RateLimitError, BillingError async def main(): async with Modulex( api_key="mx_live_8b1c4e9f2a7d6035c1e84f29", organization_id="org_3d9a1f7c52e84b06", ) as client: try: run = await client.executions.run( workflow_id="wf_8f3c2a1b", input={"topic": "quarterly report"}, ) except RateLimitError as err: # Header-based 429 (per-key / per-user / org limiter). print("retry after", err.retry_after, "seconds") print("limit", err.limit, "remaining", err.remaining, "reset", err.reset) except BillingError as err: # Run-rate DenialEnvelope 429 surfaces here when layer == "rate". if err.layer == "rate": print("run-rate limited:", err.code, "retry after", err.retry_after) asyncio.run(main()) ``` ```javascript JavaScript import { Modulex, RateLimitError } from 'modulex-js'; const client = new Modulex({ apiKey: 'mx_live_8b1c4e9f2a7d6035c1e84f29', organizationId: 'org_3d9a1f7c52e84b06', }); try { const run = await client.executions.run({ workflowId: 'wf_8f3c2a1b', input: { topic: 'quarterly report' }, }); } catch (err) { if (err instanceof RateLimitError) { // Both header-based and DenialEnvelope 429s map to RateLimitError in JS. console.log('retry after', err.retryAfter, 'seconds'); console.log('limit', err.limit, 'remaining', err.remaining, 'reset', err.reset); } } ``` The two SDKs surface 429s differently. The [JavaScript SDK](/sdks/javascript) maps **every** 429 to `RateLimitError` regardless of body shape. The [Python SDK](/sdks/python) maps the header-based 429 (Shapes A and B) to `RateLimitError`, but a flat `rate`-layer `DenialEnvelope` (Shape D) raises the **base** `BillingError` with `layer == "rate"`. Catch **both** in Python to handle every 429. See [/sdks/errors-retries](/sdks/errors-retries). ## Backing off and retrying Both SDKs treat `429`, `500`, `502`, and `503` as **retryable** and honor `Retry-After` when deciding how long to wait between attempts; `400`, `401`, `403`, `404`, `409`, and `422` are not retried. The Python client retries `GET` and `HEAD` requests only, while the JavaScript client retries all methods. SSE stream connection errors are surfaced once and are not auto-reconnected. When you back off by hand, prefer `Retry-After` over a fixed sleep, and add jitter so concurrent clients do not retry in lockstep: ```python Python import asyncio import random from modulex import Modulex, RateLimitError async def run_with_backoff(client, workflow_id, *, max_attempts=5): for attempt in range(max_attempts): try: return await client.executions.run( workflow_id=workflow_id, input={"topic": "report"} ) except RateLimitError as err: if attempt == max_attempts - 1: raise wait = (err.retry_after or 60) + random.uniform(0, 1) await asyncio.sleep(wait) ``` ```javascript JavaScript import { RateLimitError } from 'modulex-js'; async function runWithBackoff(client, workflowId, maxAttempts = 5) { for (let attempt = 0; attempt < maxAttempts; attempt++) { try { return await client.executions.run({ workflowId, input: { topic: 'report' } }); } catch (err) { if (!(err instanceof RateLimitError) || attempt === maxAttempts - 1) throw err; const wait = ((err.retryAfter ?? 60) + Math.random()) * 1000; await new Promise((resolve) => setTimeout(resolve, wait)); } } } ``` The per-user aggregate of 300 requests per minute counts **every** key a user owns, so adding more API keys for one user does not raise the ceiling. For high-volume runs, watch the run-rate (`sync_exec` / `async_exec`) classes on [/billing/usage-gating](/billing/usage-gating) and consider a plan with higher run ceilings on [/billing/plans](/billing/plans). ## Socket.io transport rate limit (not a 429) The realtime collaboration server enforces its own **per-user flood limit** on socket messages. This is a transport-level guard and is deliberately distinct from the HTTP rate limits above — it is **not** an HTTP 429 and it does **not** carry a `DenialEnvelope`. When a connected user sends gated events too quickly, the server emits an `error` event: ```json Socket.io error event { "code": "rate_limited", "message": "Too many requests. Please slow down.", "retryAfterMs": 1234 } ``` The `retryAfterMs` hint is included only on a genuine limit hit; a store error blocks the operation without it (the limiter fails closed here). The limiter is keyed per user across all gated events. For the full Socket.io event reference, see [/realtime/socket-events](/realtime/socket-events). Do not confuse the Socket.io `code: "rate_limited"` (transport, with `message` and `retryAfterMs`) with the HTTP org-limiter `code: "rate_limited"` (a 429 with a dict `detail`) or the run-rate `code: "rate_limit_exceeded"` (a 429 with a flat `DenialEnvelope`). They are three separate mechanisms on two different transports. ## Related pages The three HTTP error-envelope shapes and which surface emits each, including the full status taxonomy. The billing admission gate and the per-plan run-rate ceilings that drive Shape D 429s. Authenticate every request with `Authorization: Bearer mx_live_…` and `X-Organization-ID`. SDK error classes, the retry policy, and how each SDK surfaces a 429. --- # Assistant examples: four tasks worked end to end URL: https://docs.modulex.dev/assistant/examples Four worked ModuleX Assistant tasks you can copy: research and summarize, inbox triage, draft and send, and a multi-tool task. See the plain-language prompt, the steps the Assistant takes, where it pauses for approval, and the result. These are four common tasks people give the [Assistant](/assistant/overview), each shown end to end: the plain-language prompt you type, the steps the Assistant works through, the point where it pauses for your approval, and what you get back. Copy a prompt, adapt it to your own services, and go. Every example uses tools you have already connected to ModuleX. If a task needs a service you have not connected yet, the Assistant asks you to add it right inside the chat — see [authentication and credentials](/integrations/authentication). For the bigger picture of what the Assistant is, start with the [Assistant overview](/assistant/overview). For reusable patterns that combine the Assistant with workflows and knowledge, see [recipes](/power-using/recipes). **🎬 MEDIA PLACEHOLDER · MX-MEDIA-3290 · [APP_VIDEO]** **[APP_VIDEO_DESCRIPTION]:** One Assistant example carried out end to end in the chat surface, from prompt to result. **[APP_VIDEO_DETAILS]:** Screen-record the ModuleX app at the chat surface. Run the "draft and send" example below: the user types a request, the Assistant streams its reasoning, calls a read tool to gather context, drafts a message, pauses on an approval card before sending, the user approves, and the final confirmation appears. 40-50s, 16:9, light theme, no narration. Highlight the tool-call chips, the draft preview, and the approval card. This is the canonical Assistant example walkthrough. ## How to read these examples Each example follows the same shape, so once you have read one you can skim the rest. The exact plain-language message you send to start the [turn](/assistant/how-it-works). One message per turn — there is no form to fill in. The steps the Assistant works through: it reads, calls tools one at a time, and may pause to ask you something. You watch it all [stream live](/assistant/streaming). What you get back at the end of the turn — an answer, a summary, or a confirmed action. The Assistant calls one tool at a time and pauses before anything sensitive — sending a message, creating a record, deleting data. Nothing risky runs until you approve it on the [human-in-the-loop](/assistant/human-in-the-loop) card. The tools named below (GitHub, Slack, Gmail, Linear, and so on) are examples; the Assistant uses whatever you have connected from the [integration catalog](/integrations/catalog). ## Example 1 — Research and summarize Pull together information that lives across your connected services, or in your own documents, and get a tidy summary back. This is a read-only task, so the Assistant does not need to pause for approval — it gathers and reports. Type a request that names what to find and how you want it summarized. ```text Prompt Find the five newest open issues in the acme/api repository and summarize them as a short bulleted list, grouped by label. ``` The Assistant works the task one tool at a time, streaming its progress: 1. It checks which services are connected and confirms GitHub is available. 2. It calls a read action to fetch the newest open issues from `acme/api`. 3. It reads the results, groups them by label, and writes the summary. Because nothing is created or sent, the Assistant runs straight through without an approval pause. A short, grouped summary in the chat — for example, a `bug` group and a `feature` group, each with a one-line description per issue and a link. You can ask a follow-up in the same chat, such as "Now draft a reply to the top bug," which becomes Example 3. You can point the same kind of task at your [knowledge bases](/concepts/knowledge-rag) instead of, or alongside, a live service. Ask something like "Summarize what our onboarding guide says about SSO," and the Assistant searches your connected knowledge and answers from your own documents. Searching managed knowledge carries a small credit cost — see [credits and metering](/billing/credits). **🎬 MEDIA PLACEHOLDER · MX-MEDIA-3291 · [SCREENSHOT]** **[SCREENSHOT_DESCRIPTION]:** A research-and-summarize turn in the chat: tool-call chips for the read action, then a grouped bulleted summary. **[SCREENSHOT_DETAILS]:** Capture the ModuleX app chat surface after a read-only research turn completes. Show the user prompt, one or two tool-call chips (for example a GitHub read action), and the Assistant's grouped bulleted summary below. 16:9, light theme, no real customer data. ## Example 2 — Inbox and triage Sort through incoming items — emails, support tickets, or issues — and turn a noisy inbox into a ranked, labeled list you can act on. Reading and ranking are safe, so the Assistant does that on its own; if you ask it to apply labels or change anything, it pauses for approval first. Describe what to triage and the order you want. ```text Prompt Review my unread support tickets from the last 24 hours, rank them by urgency, and tell me which three I should answer first and why. ``` 1. The Assistant confirms your support tool is connected. 2. It calls a read action to list the unread tickets from the last day. 3. It reads each ticket, judges urgency from the content, and ranks them. 4. It reports the top three with a one-line reason each. If your prompt also asked it to *act* — "and add the `urgent` label to the top three" — the Assistant would stop on an approval card before changing any ticket, because applying a label is a write action. A ranked shortlist with reasons, ready for you to act on. If you approved label changes, you also get a confirmation of exactly what was changed. Listing and reading items never changes your data, so the Assistant does those steps without interrupting you. The moment a step would *write* — apply a label, archive a thread, reassign a ticket — it pauses and shows you precisely what it is about to do. You can approve, choose a different option, or decline. See [human-in-the-loop](/assistant/human-in-the-loop) for the question and approval kinds. ## Example 3 — Draft and send Have the Assistant gather context, write something for you, and send it — with an approval step in the middle so you always see the final draft before it goes out. This is the canonical "pause before a sensitive action" pattern. Say what to write, to whom, and where to send it. ```text Prompt Draft a friendly reply to the newest message in the #support Slack channel letting them know we are looking into it, then send it. ``` 1. The Assistant confirms Slack is connected. 2. It calls a read action to fetch the newest message in `#support` for context. 3. It writes a draft reply and shows it to you. 4. It pauses on an approval card — the send does not happen yet. Review the draft on the approval card. Approve to send it as written, decline to stop, or reply in the chat with a change such as "make it shorter" and the Assistant revises and asks again. Only the person who started the turn can answer its question. Once you approve, the Assistant calls the send action and confirms the message went out — with a link to it where the service provides one. The Assistant always pauses before a sensitive action. A draft is never sent, a record is never created, and nothing is deleted until you approve it on the card. If you decline, the turn ends without acting. Read more about how the pause works in [human-in-the-loop](/assistant/human-in-the-loop). **🎬 MEDIA PLACEHOLDER · MX-MEDIA-3292 · [SCREENSHOT]** **[SCREENSHOT_DESCRIPTION]:** The approval card mid-turn, showing the drafted message and approve/decline controls before the send. **[SCREENSHOT_DETAILS]:** Capture the ModuleX app chat surface at the moment the Assistant pauses to ask for approval before sending a message. Show the streamed draft preview and the approval card with its approve and decline actions. 16:9, light theme, no real recipient data. ## Example 4 — Multi-tool task Chain several services together in one request. The Assistant plans the steps, calls each tool in turn, carries results from one step into the next, and pauses before the one step that writes. Describe the whole task in one message — the Assistant figures out the order. ```text Prompt Find my three newest GitHub issues labeled "bug", create a Linear ticket for each one, and post a summary of what you created to the #engineering Slack channel. ``` The Assistant breaks the request into ordered steps and works them one tool at a time: 1. It reads the three newest `bug` issues from GitHub. 2. It pauses on an approval card before creating Linear tickets, since creating records is a write action. 3. After you approve, it creates one Linear ticket per issue, carrying each issue's title and link into the new ticket. 4. It pauses again before posting to Slack, then — once approved — posts a summary of the tickets it created. Three new Linear tickets and one Slack summary post, plus a final message in the chat recapping everything it did and linking to each item it created. If the task needs a service you have not connected — say Linear in the example above — the Assistant pauses and asks you to add the credential, including starting an OAuth connection, without leaving the chat. Once you connect it, the Assistant picks the task back up automatically. See [using tools](/assistant/using-tools) and [authentication and credentials](/integrations/authentication). The Assistant does a task once, right now. If you find yourself running the same multi-tool task often — or you want to schedule it, trigger it from an API, or add branching and retries — rebuild it as a reusable [workflow](/concepts/workflows-and-runs) with the [AI Composer](/concepts/ai-composer) or the [workflow builder](/workflow-builder/overview). Many people prove a task with the Assistant first, then promote it to a workflow. ## Good to know before you start A few things shape every example above. Each turn is a single text message — there is no multi-part form and no file upload. Send your whole request in one message; the Assistant plans the steps from there. A chat holds only one open question at a time. While the Assistant is waiting for your approval, it will not start another turn on that chat — finish the current step first. As built today, you need the **owner** or **admin** role in the [organization](/concepts/organizations-roles) to use the Assistant. The retired `member` role is not a current role. See [permissions and limits](/assistant/permissions-and-limits). A turn is metered like any managed run, with model token usage recorded separately. If your allowance is exhausted, the turn is declined before anything runs. See [credits and the billing model](/concepts/credits-billing). ## Try an example from your own code You can drive any of these tasks programmatically. A turn starts with one authenticated request that creates or continues a chat and returns a `stream_url`; you open that stream to watch the Assistant work, exactly as you would in the app. Every request uses `Authorization: Bearer mx_live_…` plus your `X-Organization-ID` header — see [authentication](/api-reference/authentication). ```bash cURL curl -X POST https://api.modulex.dev/assistant/chat \ -H "Authorization: Bearer mx_live_your_api_key" \ -H "X-Organization-ID: org_your_organization_id" \ -H "Content-Type: application/json" \ -d '{ "message": "Find the five newest open issues in acme/api and summarize them, grouped by label.", "llm": { "integration_name": "openai", "provider_id": "openai", "model_id": "gpt-4o" } }' ``` ```python Python from modulex import AsyncModulex client = AsyncModulex( api_key="mx_live_your_api_key", organization_id="org_your_organization_id", ) resp = await client.assistant.chat( "Find the five newest open issues in acme/api and summarize them, grouped by label.", llm={ "integration_name": "openai", "provider_id": "openai", "model_id": "gpt-4o", }, ) print(resp.stream_url) # open this to stream the turn ``` ```javascript JavaScript import { Modulex } from "modulex-js"; const client = new Modulex({ apiKey: "mx_live_your_api_key", organizationId: "org_your_organization_id", }); const resp = await client.assistant.chat({ message: "Find the five newest open issues in acme/api and summarize them, grouped by label.", llm: { integration_name: "openai", provider_id: "openai", model_id: "gpt-4o", }, }); console.log(resp.stream_url); // open this to stream the turn ``` A successful response returns the chat and run identifiers plus a `stream_url`. Open it over Server-Sent Events to receive the Assistant's actions and output as they happen — including any [human-in-the-loop](/assistant/human-in-the-loop) approval pause, which you answer to continue: ```json Response { "status": "running", "chat_id": "f2b1c0de-1111-2222-3333-444455556666", "run_id": "a1b2c3d4-aaaa-bbbb-cccc-dddddddddddd", "thread_id": "f2b1c0de-1111-2222-3333-444455556666", "stream_url": "/assistant/chat/f2b1c0de-1111-2222-3333-444455556666/listen/a1b2c3d4-aaaa-bbbb-cccc-dddddddddddd" } ``` For the full set of Assistant operations, the event stream, and the resume contract, see [how the Assistant works](/assistant/how-it-works), [streaming responses](/assistant/streaming), and [human-in-the-loop](/assistant/human-in-the-loop). ## Where to go next What the Assistant is, what it can do, and how to start a session. Reusable patterns that combine the Assistant with workflows and knowledge to solve real problems. How the Assistant discovers and calls the integration tools these examples rely on. The pause-and-approve step that protects every sensitive action above. Connect a service so the Assistant can act on it, then call its tools. Turn a task you run often into a reusable, schedulable workflow. --- # The Assistant's agentic loop: reason, act, observe URL: https://docs.modulex.dev/assistant/how-it-works A technical walkthrough of how the ModuleX Assistant runs a turn: the reason to act to observe loop, the shared ComposerChat table with kind=assistant, turns and runs, and how a turn terminates — with parameters, events, errors, and credit impact. This page is the technical reference for what happens inside a single Assistant turn. It covers the agentic reason to act to observe loop, the storage and execution model the Assistant shares with the [AI Composer](/concepts/ai-composer), the relationship between turns and runs, every way a turn can terminate, the limits that bound it, and the exact credit impact. If you are looking for the product tour first, start with the [Assistant overview](/assistant/overview). The Assistant runs the `assistant` profile of the same agent core that powers the Composer. The two are separated only by `kind` and `profile`: the Composer edits a [workflow](/concepts/workflows-and-runs) graph, while the Assistant has no workflow tools at all and acts directly on your connected [integration tools](/assistant/using-tools) and [knowledge bases](/concepts/knowledge-rag). ## The reason, act, observe loop A turn is an agentic loop. The Assistant alternates between calling the language model (reason) and calling exactly one tool (act), then feeds the tool result back into the model (observe), and repeats until the model produces a final answer with no further tool call. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-3230 · [IMAGE]** **[IMAGE_DESCRIPTION]:** A diagram of the Assistant's reason to act to observe loop for a single turn. **[IMAGE_DETAILS]:** Authored diagram (not a screenshot). Show a cycle of three nodes: "Reason (LLM step)", "Act (one tool call)", "Observe (tool result fed back)" connected by arrows in a loop. Add an entry arrow labeled "user message starts the turn" and two exit arrows: one labeled "no tool call → final answer (done)" and one labeled "HITL tool → pause for input (interrupted)". Mark the loop ceiling "recursion_limit = 100". Annotate the act node "serial: one tool per step". 16:9, light theme, ModuleX brand colors. This is the canonical agentic-loop diagram referenced from the Assistant overview and the workflows-and-runs concept page. Each pass of the loop produces streamed events you can observe live over [SSE](/assistant/streaming): The Assistant calls the language model with the conversation so far and the available tools. The model streams its text as `response_chunk` events. It either ends the turn with a final answer, or it decides to call a tool. If the model asks for a tool, the Assistant emits a `tool_call` event and runs that single action. Tool calls are serialized: `SerialToolCallsMiddleware` forces one tool call per model step, so two tools never fire in the same step. The Assistant can call a tool that discovers integrations, runs an integration action, searches knowledge, or asks you a [human-in-the-loop](/assistant/human-in-the-loop) question. The Assistant emits a `tool_result` event carrying the tool's output (or an error payload on failure) and feeds it back to the model. The loop returns to the reason step. The loop continues until the model returns a turn with no tool call. The Assistant then emits a `done` event with the final response and usage totals, and the turn ends. The same loop powers the Composer, but the Assistant's tool set is restricted to discovery (`get_available_integrations`, `get_integration_details`, `get_organization_credentials`), execution (`execute_integration_tool`), knowledge (`search_knowledge`), and the HITL tools (`ask_user_choice`, `ask_user_multi_choice`, `ask_user_yes_no`, `ask_user_free_text`, `request_credential`). Workflow inspect, build, edit, and run tools are deliberately excluded — a resumed Assistant run can never regain them because the profile is re-derived from the chat's `kind`. ## Storage and execution model Assistant chats and Composer chats live in the **same** `composer_chats` table, distinguished only by the `kind` discriminator (`assistant` vs `composer`). Every service call is scoped by `kind`, so the Assistant and the Composer cannot read or operate on each other's chats. The Assistant intentionally reuses the Composer's agent factory, executor, HITL interrupt/resume machinery, OAuth auto-resume, and SSE streaming — staying in the shared table is required so that OAuth auto-resume keeps working. The shared chat row backing both surfaces. The chat identifier. This value **is** the LangGraph checkpoint `thread_id`, so `thread_id == chat_id` for the life of the conversation. `assistant` for Assistant chats, `composer` for Composer chats. The discriminator that scopes every service call. Defaults to the first 50 characters of the opening message. The `run_id` of the in-flight or interrupted run, set synchronously at run start and cleared on completion, failure, or cancel. It is the race-free binding for run-to-chat ownership. Always `null` for Assistant chats — they are never workflow-bound. Because of this, the executor's `workflow_change` and `workflow_sync` events never fire for the Assistant. What differs from the Composer at runtime: the Assistant has no subagents (the Composer uses an integration-resolver and a credential-resolver), runs slimmer middleware (it keeps `SerialToolCallsMiddleware` and `SummarizationMiddleware` plus provider prompt caching, and skips the todo-list, filesystem, and subagent middleware), and uses an Assistant system prompt with no workflow or focus language. The `save`, `revert`, and `focus` endpoints that the Composer exposes are omitted entirely. ## Turns and runs These two words are not interchangeable; the distinction matters for billing, streaming, and identity. One user message answered by one agent loop. A turn starts with `POST /assistant/chat` and ends when the loop reaches a final answer, pauses for input, is cancelled, or errors. **Billing charges exactly one run credit per turn.** A single execution of the loop, identified by a `run_id`. A turn maps to one run — but when a turn pauses for [human-in-the-loop](/assistant/human-in-the-loop) input and you resume it, a **new** `run_id` is minted for the continuation. So one logical turn can span more than one `run_id`. ### The three run-id identities `run_id` is used at several layers, and you must not assume one identity. The same caution applies to workflow runs — see [workflows and runs](/concepts/workflows-and-runs) for the full treatment. The identifier used for SSE streaming, status, and history Redis keys. **A new `run_id` is minted on every resume**, so it is not stable across a multi-step conversation and is not a conversation-level key. The LangGraph checkpoint thread. `thread_id == chat_id` and is stable across the whole conversation. Every run in a chat re-enters the same thread. The durable run identifier persisted on the run row, a unique string column. It is the stable durable identifier the docs reference for a run. ### How a turn is admitted, charged, and tracked When `POST /assistant/chat` is called, the Assistant runs a synchronous, reject-before-write sequence so a denied turn never writes any rows: If the chat already has a pending HITL question, the request returns **409** (answer it first). If a run is already in flight on the chat, the request returns **409** (wait for it or cancel it). A chat holds only one open question and one running turn at a time. The Assistant calls the run-admission gate with the turn key `{chat.id}:{run_id}`, usage type `run`, and rate class `sync_exec`. Assistant turns count under `sync_exec` unconditionally. On denial it raises a billing error (**402 / 403 / 429**) before any rows are written. See [credit impact](#credit-impact). On success the Assistant appends your message, sets `running_id` to the new `run_id`, commits, and charges exactly one run credit for the turn. Any error on this path releases the admission reservation so it does not leak. The loop is scheduled as a background task with `profile="assistant"` and `workflow_id=None`. The response returns immediately with a `stream_url` you open to watch the turn. The `POST /assistant/chat` response gives you everything you need to start streaming: ```json Response { "status": "running", "chat_id": "f2b1c0de-1111-2222-3333-444455556666", "run_id": "a1b2c3d4-aaaa-bbbb-cccc-dddddddddddd", "thread_id": "f2b1c0de-1111-2222-3333-444455556666", "stream_url": "/assistant/chat/f2b1c0de-1111-2222-3333-444455556666/listen/a1b2c3d4-aaaa-bbbb-cccc-dddddddddddd" } ``` ## Termination and limits A turn's loop ends in exactly one of four ways. Three are terminal for the run; one (HITL pause) keeps the chat alive awaiting your response. The model returns a turn with no tool call. The Assistant emits a `done` event carrying the final `response`, the list of `tool_calls`, and a `usage` object (`input_tokens`, `output_tokens`, `total_tokens`, `llm_calls`). For the Assistant, `has_workflow_changes` is always `false` and `workflow_tool` is always `null` (the composer-shaped keys are present but empty). The run status becomes `completed` and `running_id` is cleared. The model calls a HITL tool (`ask_user_*` or `request_credential`), which fires a LangGraph `interrupt(...)`. The Assistant writes an interrupt-audit row (`outcome="pending"`), publishes a `user_input_request` event, sets a Redis pending sentinel (TTL **7 days**), flips run status to `interrupted`, appends a history-only `interrupted` marker, and cold-exits the loop awaiting resume. The chat stays open — this is not a terminal run for the conversation. You answer with `POST /assistant/chat/{chat_id}/resume`, which mints a **new** `run_id` and re-enters the same thread. See [human-in-the-loop](/assistant/human-in-the-loop) and the [HITL resume reference](/realtime/hitl). `POST /assistant/chat/{chat_id}/cancel` sets a Redis cancel flag, publishes a `cancelled` event, and sets run status to `cancelled`. If the run was paused on a HITL interrupt, cancel also clears the pending sentinel and flips the audit row to `cancelled` so the question is not re-presented. Then `running_id` is cleared. Calling cancel with no active run returns **400** (`No active execution to cancel`). On failure the Assistant emits an `error` event with a sanitized message, followed by a `done` event with `error: true`, and sets run status to `failed`. Language-model token usage accrued before the failure is still recorded (token billing is independent of success). ### Loop limits The loop is bounded so it cannot run forever, and it emits guidance long before it hits the ceiling. The hard ceiling on agent loop iterations for a single run, enforced by LangGraph. A run that exceeds it stops with an error rather than looping indefinitely. There is no per-turn wall-clock timeout exposed in the run config; the recursion limit is the effective bound. After this many tool failures in a row, the Assistant publishes a `guidance` event (`{message, consecutive_failures}`) so the model — and you — know something is going wrong. The counter resets on the next successful tool call. This nudges the loop, it does not terminate it. Every multiple of this many language-model calls in a single run, the Assistant publishes a wind-down `guidance` event (`{message, total_llm_calls}`) encouraging the model to converge. This is advisory, not a hard stop. A `guidance` event is informational. It signals that the loop is struggling (repeated failures) or running long (many LLM calls), but it does not end the turn. Only `done`, `cancelled`, `error`, and the HITL `interrupted` pause change the run's lifecycle. ## Events emitted during a turn The turn streams as Server-Sent Events. Each frame is a single `data: ` line with **no** `event:` line — discriminate on the JSON `type` field. The payload for most events is nested under a `data` key, for example `{"type":"response_chunk","data":{"chunk":"..."}}`. See [streaming responses](/assistant/streaming) for how to consume the stream in the app and the SDKs, and the [SSE run streaming reference](/realtime/sse-streaming) for the transport details. | `type` | Payload | Emitted when | | --- | --- | --- | | `metadata` | `{run_id, thread_id, workflow_id, workflow_type, timestamp}` | At run start. | | `response_chunk` | `{chunk}` | On each model text delta (the reason step). | | `tool_call` | `{tool, input, tool_call_id, timestamp}` | When a tool is invoked (the act step). | | `tool_result` | `{tool, tool_call_id, output, timestamp}` | When a tool returns (the observe step); on failure `output` is `{error, success: false}`. | | `guidance` | `{message, consecutive_failures}` or `{message, total_llm_calls}` | On the failure threshold or every LLM-call milestone. | | `user_input_request` | `{kind, request_id, message, ...}` | When a HITL tool fires an interrupt. | | `interrupted` | `{request_id, kind}` | History-only terminal marker for the paused run. | | `run_resumed` | `{new_run_id}` | On OAuth auto-resume, published on the old run's channel. | | `done` | `{response, has_workflow_changes, tool_calls, workflow_tool, usage}` | On success — terminal. | | `error` | `{message}` | On failure, followed by a `done` with `error: true`. | | `cancelled` | `{reason}` | After `POST /assistant/chat/{chat_id}/cancel`. | | `heartbeat` | `{type: "heartbeat"}` | Keepalive every 15s of inter-event silence. | For Assistant runs, the `metadata` frame reports `workflow_type` as `composer` and `workflow_id` as `null`. This is a known label quirk — the executor tags every run `workflow_type: composer` regardless of profile. The reliable per-surface discriminator is the request's `surface` metadata tag (`assistant`), not `workflow_type`. The `workflow_change` and `workflow_sync` events never fire for the Assistant. ## Credit impact The Assistant meters usage like any other managed run. Two distinct charges apply per turn: The flat run charge is **1 credit** per turn, applied through the admission gate at the start of the turn. Resuming a paused turn re-enters through `/resume` (not `/chat`), so a resume does **not** add a second run charge for the same turn. Language-model token usage is recorded separately in the executor, based on input and output token counts, regardless of whether the turn succeeds, errors, or is cancelled. This is independent of the flat run credit. If the loop calls integration tools or searches managed [knowledge](/platform/knowledge/managed), those carry their own credit costs documented in [credits and metering](/billing/credits). Bring-your-own-key model usage is not credited (it is recorded for analytics only). ### When a turn is denied The Assistant is one of the surfaces where the billing admission gate is live. If your organization's allowance and wallet cannot cover the turn, the gate denies it **before any rows are written** and the request fails with a flat `DenialEnvelope` — note this has no `detail` wrapper, unlike standard FastAPI errors: ```json DenialEnvelope (402) { "code": "credit_plan_exhausted", "layer": "credit", "key": null, "current": null, "limit": null, "reason": "credit_plan_exhausted" } ``` The `layer` field maps to the HTTP status: `credit` and `wallet` to **402**, `quota` to **403**, and `rate` to **429** (with `Retry-After` and `X-RateLimit-*` headers). For the full envelope taxonomy and how it differs from the `{detail}` shape, see [errors and status codes](/api-reference/errors); for the gating rules and per-plan limits, see [usage gating and limits](/billing/usage-gating) and [permissions and limits](/assistant/permissions-and-limits). ## A worked example: one turn with a tool call This walks one full turn end to end: start the turn, stream the loop, watch a tool call and its result, and receive the final answer. Every request uses `Authorization: Bearer mx_live_…` plus `X-Organization-ID` — see [authentication](/api-reference/authentication). As built today the Assistant requires the **owner** or **admin** role in the organization; the retired `member` role is not a current role. Send one text message. The `message` must be a JSON string (v1 is text-only — an array or object returns **400**). Omit `chat_id` to start a new chat, or pass it to continue one. If you omit `llm`, your organization's saved default is used. ```bash cURL curl -X POST https://api.modulex.dev/assistant/chat \ -H "Authorization: Bearer mx_live_your_api_key" \ -H "X-Organization-ID: org_your_organization_id" \ -H "Content-Type: application/json" \ -d '{ "message": "List my GitHub repositories", "llm": { "integration_name": "openai", "provider_id": "openai", "model_id": "gpt-4o" } }' ``` ```python Python from modulex import AsyncModulex client = AsyncModulex( api_key="mx_live_your_api_key", organization_id="org_your_organization_id", ) resp = await client.assistant.chat( "List my GitHub repositories", llm={ "integration_name": "openai", "provider_id": "openai", "model_id": "gpt-4o", }, ) print(resp.chat_id, resp.run_id) ``` ```javascript JavaScript import { Modulex } from "modulex-js"; const client = new Modulex({ apiKey: "mx_live_your_api_key", organizationId: "org_your_organization_id", }); const resp = await client.assistant.chat({ message: "List my GitHub repositories", llm: { integration_name: "openai", provider_id: "openai", model_id: "gpt-4o", }, }); console.log(resp.chat_id, resp.run_id); ``` Open the returned `stream_url` to receive the turn's events as they happen. Both SDKs expose `assistant.listen(chatId, runId)` as an async iterator that yields the data payload of each frame. ```bash cURL curl -N https://api.modulex.dev/assistant/chat/{CHAT_ID}/listen/{RUN_ID} \ -H "Authorization: Bearer mx_live_your_api_key" \ -H "X-Organization-ID: org_your_organization_id" ``` ```python Python async with client.assistant.listen(resp.chat_id, resp.run_id) as stream: async for event in stream: if event.event == "response_chunk": print(event.data["chunk"], end="") elif event.event == "tool_call": print(f"\n[tool] {event.data['tool']}") elif event.event == "done": print("\n[final]", event.data["response"]) ``` ```javascript JavaScript for await (const event of client.assistant.listen(resp.chat_id, resp.run_id)) { if (event.type === "response_chunk") { process.stdout.write(event.data.chunk); } else if (event.type === "tool_call") { console.log(`\n[tool] ${event.data.tool}`); } else if (event.type === "done") { console.log("\n[final]", event.data.response); } } ``` A clean single-tool turn looks like this on the wire (reason, act, observe, then a final answer): ```text Frame trace data: {"type":"metadata","data":{"run_id":"a1b2c3d4-...","thread_id":"f2b1c0de-...","workflow_id":null,"workflow_type":"composer","timestamp":"2026-06-20T10:00:01+00:00"}} data: {"type":"response_chunk","data":{"chunk":"Let me check your GitHub."}} data: {"type":"tool_call","data":{"tool":"execute_integration_tool","input":{"integration":"github","action":"list_repos"},"tool_call_id":"run-xyz","timestamp":"2026-06-20T10:00:02+00:00"}} data: {"type":"tool_result","data":{"tool":"execute_integration_tool","tool_call_id":"run-xyz","output":{"success":true,"repos":["acme/api","acme/web"]},"timestamp":"2026-06-20T10:00:03+00:00"}} data: {"type":"response_chunk","data":{"chunk":"You have 2 repositories: acme/api and acme/web."}} data: {"type":"done","data":{"response":"You have 2 repositories: acme/api and acme/web.","has_workflow_changes":false,"tool_calls":[{"tool":"execute_integration_tool","input":{"integration":"github","action":"list_repos"},"output":{"success":true,"repos":["acme/api","acme/web"]}}],"workflow_tool":null,"usage":{"input_tokens":1200,"output_tokens":340,"total_tokens":1540,"llm_calls":3}}} ``` If the model instead emits a `user_input_request` (for example, to connect a missing credential), the run pauses and the stream stays open on heartbeats. Answer with `assistant.resume(...)` — which mints a new `run_id` you then open a fresh stream on. The full request and response kinds are in [human-in-the-loop](/assistant/human-in-the-loop). The `message` field must be a JSON string; an array or object returns **400**. Continuing a chat that belongs to another organization returns **404** (ownership is hidden as not-found, not 403). A second turn while one is pending or running returns **409**. ## Where to go next How the Assistant discovers and calls integration tools in the act step, and how it requests credentials. Consume the turn's events live over SSE in the app and the SDKs. The pause-and-resume contract: question kinds, response kinds, and how resume mints a new run. Who can use the Assistant and the billing and usage limits that bound a turn. The run-id identities and the difference between a turn and a run, in the wider ModuleX model. Where the Assistant sits relative to the Composer and the workflow engine. --- # Human-in-the-loop: pause, ask, and resume the Assistant URL: https://docs.modulex.dev/assistant/human-in-the-loop How the ModuleX Assistant pauses mid-task to ask a structured question or request approval, the five request kinds and seven response kinds, and exactly how to resume a paused run over REST and the SDKs. Human-in-the-loop (HITL) is how the Assistant pauses a turn to ask you something — a choice, a piece of text, a yes/no decision, an approval, or a missing credential — and then continues once you answer. While the Assistant is paused it holds exactly one open question, the run is suspended server-side, and no further action runs until you respond. This page covers every request and response kind, the approval gate, the resume contract, and what happens to a paused run. The Assistant inherits this machinery verbatim from the [AI Composer](/concepts/ai-composer); the wire contract is identical to the [workflow interrupt node](/workflow-builder/nodes/interrupt) HITL question shape and to the SDK [streaming and HITL](/sdks/streaming-hitl) flow. The full pause/resume reference, including the workflow-run variant, lives at [human-in-the-loop (HITL) resume](/realtime/hitl). HITL is **request-driven**: the Assistant only pauses when its agent calls a HITL tool (`ask_user_*` or `request_credential`). You do not configure interrupt points the way you do in the [interrupt node](/workflow-builder/nodes/interrupt) — the agent decides when an answer or approval is needed. ## The pause/resume lifecycle A turn that needs your input moves through a fixed lifecycle. The Assistant pauses, you answer, and a **new run** continues the same conversation. You send a message to `POST /assistant/chat`. The turn begins streaming over the run's SSE stream. See [streaming responses](/assistant/streaming). The agent calls a HITL tool. The run emits a `user_input_request` SSE frame carrying the question, writes a pending audit row, sets a Redis pending sentinel, flips the run status to `interrupted`, and suspends. The stream stays open — there is no terminal frame. You call `POST /assistant/chat/{chat_id}/resume` with the question's `request_id`, a typed `response`, and your `llm` config. A three-layer guard validates the answer. Resume mints a **new** `run_id`, re-enters the same `thread_id`, and returns a fresh `stream_url`. You open that new stream to watch the turn finish. The continued run streams the rest of its work and ends with a `done` frame (or pauses again on another question). ```text Frame trace (one HITL turn) data: {"type":"metadata","data":{"run_id":"a1b2c3d4-...","thread_id":"f2b1c0de-...","workflow_id":null,"workflow_type":"composer","timestamp":"2026-06-21T10:00:01+00:00"}} data: {"type":"response_chunk","data":{"chunk":"Let me confirm before I send."}} data: {"type":"user_input_request","data":{"kind":"yes_no","request_id":"yesno-req-42","message":"Send the welcome email now?","required":true,"allow_free_text":false,"yes_label":"Send it","no_label":"Not yet"}} : stream stays open, run paused server-side — NO done/error frame : client answers: POST /assistant/chat/{chat_id}/resume : resume returns a NEW run_id; re-listen on /assistant/chat/{chat_id}/listen/{new_run_id} data: {"type":"done","data":{"response":"Sent.","has_workflow_changes":false,"tool_calls":[],"workflow_tool":null,"usage":{"input_tokens":1200,"output_tokens":120,"total_tokens":1320,"llm_calls":1}}} ``` A live HITL pause does **not** close the SSE stream and emits **no** terminal frame. The `interrupted` marker is written to history only — it is never published on the live channel. Do not wait for a terminal event after a `user_input_request`; treat the question itself as the signal to stop reading and answer. A later reconnect replays the history (including the `interrupted` marker) so the replay ends cleanly. See [SSE run streaming](/realtime/sse-streaming). ## Request kinds A HITL question is a `UserInputRequest`, discriminated on its `kind` field. There are exactly **five** request kinds. The Assistant emits one inside the `user_input_request` SSE frame. The question payload is **nested one level deeper** than every other run event. The SSE frame is `{"type":"user_input_request","data":}`, so the question lives at `data.data`, not `data`. A naive read of `frame.data.kind` returns nothing — the kind is at `frame.data.data.kind`. The Python SDK ships `user_input_request_from_event(event.data)` to extract it for you. ### Common base fields Every request kind shares these base fields. The unique id binding this question to its answer. You must echo it back on resume. Ids are prefixed by tool (for example `choice-`, `yesno-`, `text-`, `multi-`, `cred-`). The id is `UNIQUE` on the audit row. The question text, rendered as Markdown. Whether an answer is required. When `true`, skipping is not offered. Whether the user may add free text alongside a structured answer (used by `single_choice`). Optional structured context the agent attaches to the question. May be `null`. Optional advisory timeout for the UI. It is a hint only — the authoritative expiry is the pending sentinel's 7-day TTL (see [paused runs](#paused-runs)). May be `null`. ### The five kinds Pick one option from a list. 1–10 options. Each `ChoiceOption` is `{value, label, description?, icon?, badge?}`. Answer with a `single_choice` response carrying `selected_value` (and optionally `free_text` when `allow_free_text` is `true`). Pick one or more options. 1–20 options. Minimum number of selections required. Maximum number of selections allowed. May be `null` (no maximum). Answer with a `multi_choice` response carrying `selected_values`. A binary decision — commonly used as an approval prompt. Label for the affirmative answer. Label for the negative answer. Answer with a `yes_no` response carrying `answer` (a boolean). A free-text reply. Optional input placeholder. Whether to render a multi-line input. Minimum character length. Maximum character length. May be `null`. Answer with a `free_text` response carrying `text`. The Assistant needs a service connected before it can act. See [using tools](/assistant/using-tools) and [authentication and credentials](/integrations/authentication). The integration's internal name (for example `github`). The human-readable name (for example `GitHub`). Optional logo URL. One or more ways to authenticate. Each `CredentialAuthOption` is `{auth_type, display_name, fields?, oauth_initiate_endpoint?, setup_instructions?, test_supported}`, where `auth_type` is one of `oauth2`, `api_key`, `bearer_token`, `modulex_key`, or `custom`. Optional name of the step waiting on the credential. Answer with a `credential_added` response (or, if connecting failed, `credential_failed`). For OAuth2, completing the flow auto-resumes the chat — see [OAuth auto-resume](#oauth-auto-resume). ## Response kinds You answer by sending a `UserInputResponse`, discriminated on its `kind` field. There are **seven** response kinds. Note the asymmetry: one request kind (`credential_request`) maps to two response kinds (`credential_added` / `credential_failed`), and `skipped` is a response-only kind with no matching request kind.
Response `kind` Fields Answers request kind
`single_choice` `selected_value?` (string), `free_text?` (string) `single_choice`
`multi_choice` `selected_values` (string array) `multi_choice`
`yes_no` `answer` (boolean) `yes_no`
`free_text` `text` (string) `free_text`
`credential_added` `credential_id`, `integration_name`, `auth_type` `credential_request`
`credential_failed` `integration_name`, `auth_type`, `error_code`, `error_message`, `retryable` (default `true`), `provider_details?` `credential_request`
`skipped` `reason?` (string) any (response-only)
One of `oauth_denied`, `oauth_provider_error`, `invalid_credentials`, `network_error`, `popup_closed`, `timeout`, `unknown`. Sending this kind hands the agent a structured failure so it can recover rather than commit to a broken credential. When you answer with `credential_added`, the resume endpoint runs a server-side **preflight**: it tests the freshly added credential before the agent commits to it. If the test fails, the resume value is swapped to a `credential_failed` response with `error_code: invalid_credentials` and `retryable: true`, so the agent gets a clean failure instead of a broken credential. This is invisible to your code but can change the effective answer. ## Approval gates An **approval gate** is the Assistant stopping for your confirmation before a sensitive or destructive action — for example, before sending an email or deleting a record. Mechanically, an approval is a HITL question (most often a `yes_no` request) that the agent raises ahead of the risky tool call; the action runs only after you answer affirmatively. Stopping for approval before sensitive actions is a **paid-plan capability** ("manage approvals"), enforced by the Assistant's tool-execution gating policy — distinct from the [billing admission gate](/billing/usage-gating) that meters each turn. Read-only actions generally run without a gate; write or destructive actions are gated. The set of gated actions is governed by ModuleX, not configured per chat. Approval is not a separate request kind. It is a regular `yes_no` (or `single_choice`) question with approval-shaped labels. Handle it like any other HITL question: stop reading the stream, present the choice, and resume with the user's decision. Only the user who triggered the question may answer it — including approvals. A different user gets a `403`. The retired `member` role cannot use the Assistant at all. Which plans include managed approvals, and the usage limits that apply to Assistant turns. ## Responding and resuming You resume a paused chat by answering its open question. Resume mints a **new** `run_id`; the original stream does not carry the continued events — you must open the new `stream_url`. ### `POST /assistant/chat/{chat_id}/resume` Authenticate every request with `Authorization: Bearer mx_live_…` plus `X-Organization-ID`, and the owner or admin role in that organization — see [authentication](/api-reference/authentication). The caller must be the same user who triggered the question. #### Request body The paused question's `request_id`, taken from the `user_input_request` frame. It must match the current pending sentinel exactly. The typed answer, discriminated on `kind` (one of the seven [response kinds](#response-kinds)). A response that fails discriminated-union validation returns `422`. The LLM selection used to rebuild the chat model on resume. Although the underlying field is schema-optional, the Assistant endpoint **rejects a missing `llm` with `400`** — it is required in practice, because the graph persists state but not the in-process model. Keys: `integration_name`, `provider_id`, `model_id`, and optional `credential_id`. #### The three-layer resume guard Every resume passes three checks before the run continues: The pending sentinel must exist and `request_id` must match it. A missing sentinel or mismatch returns `410` — the question was already answered, cancelled, or expired. The caller's user id must equal the question's originator. A different user returns `403`. The pending sentinel is cleared with a `WATCH`/`MULTI`/`EXEC` compare-and-delete. If two answers race, the loser returns `410`. #### Response `resuming`. The chat that was resumed. A **new** `run_id`, minted for the continued turn. Re-listen on this id. The conversation thread id. Equal to `chat_id` and unchanged across the resume. The SSE URL for the new run: `/assistant/chat/{chat_id}/listen/{new_run_id}`. Open it to watch the turn finish. ```bash cURL curl -X POST https://api.modulex.dev/assistant/chat/f2b1c0de-1111-2222-3333-444455556666/resume \ -H "Authorization: Bearer mx_live_your_api_key" \ -H "X-Organization-ID: org_your_organization_id" \ -H "Content-Type: application/json" \ -d '{ "request_id": "yesno-req-42", "response": { "kind": "yes_no", "answer": true }, "llm": { "integration_name": "openai", "provider_id": "openai", "model_id": "gpt-4o" } }' ``` ```python Python from modulex import Modulex from modulex.types.realtime import ( user_input_request_from_event, YesNoResponse, ComposerLLMConfig, ) client = Modulex( api_key="mx_live_your_api_key", organization_id="org_your_organization_id", ) llm = ComposerLLMConfig(integration_name="openai", provider_id="openai", model_id="gpt-4o") # Start a turn, then watch for a question. start = await client.assistant.chat("Send the welcome email to new signups", llm=llm) chat_id, run_id = start.chat_id, start.run_id while True: paused = None async with client.assistant.listen(chat_id, run_id) as stream: async for event in stream: if event.event == "user_input_request": paused = user_input_request_from_event(event.data) # nested data.data break # stop reading; go answer it if event.is_terminal: break if paused is None: break # the turn finished # Answer the question. Resume mints a NEW run_id. resumed = await client.assistant.resume( chat_id, request_id=paused.request_id, response=YesNoResponse(answer=True), llm=llm, # required on assistant resume (no default) ) run_id = resumed.run_id # re-listen on the new run ``` ```javascript JavaScript import { Modulex } from "modulex"; const client = new Modulex({ apiKey: "mx_live_your_api_key", organizationId: "org_your_organization_id", }); const start = await client.assistant.chat({ message: "Send the welcome email to new signups", llm: { integration_name: "openai", provider_id: "openai", model_id: "gpt-4o" }, }); let chatId = start.chat_id; let runId = start.run_id; for (;;) { let paused = null; for await (const evt of client.assistant.listen(chatId, runId)) { if (evt.type === "user_input_request") { paused = evt.data; // UserInputRequest; discriminate on paused.kind break; // stop reading; go answer it } if (evt.type === "done" || evt.type === "error") break; } if (!paused) break; // the turn finished // Answer the question. Resume mints a NEW run_id. const resumed = await client.assistant.resume(chatId, { requestId: paused.request_id, response: { kind: "yes_no", answer: true }, llm: { integration_name: "openai", provider_id: "openai", model_id: "gpt-4o" }, }); runId = resumed.run_id; // re-listen on the new run } ``` `assistant.resume` requires `llm` (no default) in the Python and JavaScript SDKs, matching the endpoint's `400`-on-missing behavior. This differs from `composer.resume`, where `llm` defaults to `None`. See [streaming and HITL](/sdks/streaming-hitl). ### OAuth auto-resume When a `credential_request` offers an `oauth2` option and the user completes the OAuth flow, the chat resumes **without** a manual `resume` call. The OAuth callback re-runs the same three-layer guard, publishes a `run_resumed` event on the **old** run's channel (so a live client swaps to the new stream), and continues the turn on a new `run_id` — funneling through the same resume path. This is why Assistant chats are stored alongside Composer chats: the shared storage keeps OAuth auto-resume working. ```text run_resumed on the OLD run's channel data: {"type":"run_resumed","data":{"new_run_id":"b2c3d4e5-..."}} ``` When you see `run_resumed`, stop reading the current stream and open `/assistant/chat/{chat_id}/listen/{new_run_id}`. ### Resume errors `llm` is missing from the resume body. Envelope: `{"detail": "..."}`. The caller is not the user who triggered the question. Envelope: `{"detail": "..."}`. See [roles and permissions](/security/roles-permissions). The chat does not exist or is not in your organization. Ownership failures return `404` (not `403`) so existence is never leaked. Envelope: `{"detail": "..."}`. The question was already answered, cancelled, or expired, or your resume lost the atomic compare-and-delete race. Envelope: `{"detail": "..."}`. There is no dedicated `410` exception class in the SDKs — it surfaces as the base error type. The `response` failed discriminated-union validation (for example a missing required field for its `kind`). Standard FastAPI validation envelope. A billing or rate-limit denial on the continued turn. Flat `DenialEnvelope` shape — see [credit impact](#credit-impact). Distinct from the `403` ownership error above, which uses the `{"detail": "..."}` shape. For the full pause/resume reference shared with the workflow-run variant — including how the [interrupt node](/workflow-builder/nodes/interrupt) resumes via the workflow thread (reusing the same `run_id`) rather than minting a new one — see [human-in-the-loop (HITL) resume](/realtime/hitl). The two flows are not interchangeable. ## Paused runs A paused Assistant run is a turn suspended on an open HITL question. Understanding its server-side state explains the concurrency rules and the expiry. A chat may hold only **one** open question at a time. While a question is pending — or while a turn is still running — `POST /assistant/chat` returns `409`. The Assistant forces one tool call per model step, so two questions cannot fire in the same super-step. Answer or cancel the current question before starting a new turn. Each open question sets a Redis pending sentinel keyed to the chat, with a **7-day** TTL. This is the authoritative window in which the question can be answered; the question's `timeout_hint_seconds` is only a UI hint. If the sentinel expires, a later resume returns `410`. A paused run reports status `interrupted` in the Redis run-status doc (`{running, completed, failed, interrupted, cancelled}`, 1-hour TTL). `chat.running_id` stays set so the chat→run binding is preserved. The status document is what `GET /assistant/chat/{chat_id}/status` reads to report `awaiting_input` and the `pending_request_id`. `GET /assistant/chat/{chat_id}` re-surfaces a pending question on refresh: when `running_id` is set, the pending sentinel exists, and the matching audit row's `outcome` is still `pending`, the response includes `pending_user_input_request` with the full question payload. Otherwise it is `null`. This lets a client that disconnected pick the question back up without replaying the stream. `GET /assistant/chat/{chat_id}/status` returns `awaiting_input`, `pending_request_id`, `is_running`, `running_id`, and the raw `run_status` doc. `is_running` is `true` only when a run is active **and not** awaiting input — a paused run reports `is_running: false, awaiting_input: true`. ### Cancelling a paused run `POST /assistant/chat/{chat_id}/cancel` cancels the active execution. If the run is paused on a HITL question, cancel also clears the pending sentinel and flips the audit row to `cancelled`, so `GET /assistant/chat` stops re-presenting the question. With no active run, cancel returns `400` (`"No active execution to cancel"`). ```text Status of a paused, then cancelled, run GET /assistant/chat/{id}/status → {"is_running": false, "awaiting_input": true, "pending_request_id": "yesno-req-42", ...} POST /assistant/chat/{id}/cancel → {"status": "cancelled", "chat_id": "...", "run_id": "..."} GET /assistant/chat/{id}/status → {"is_running": false, "awaiting_input": false, "pending_request_id": null, ...} ``` **🎬 MEDIA PLACEHOLDER · MX-MEDIA-3250 · [APP_VIDEO]** **[APP_VIDEO_DESCRIPTION]:** The Assistant pausing mid-task on an approval card, the user answering, and the same conversation resuming on a new run. **[APP_VIDEO_DETAILS]:** Screen-record the ModuleX app chat surface. Show one Assistant turn that calls a read tool, then pauses on a `yes_no` approval card ("Send the welcome email now?") with the two labelled buttons; the user clicks the affirmative label; capture the stream reconnecting and the turn finishing with a final answer. 25-35s, 16:9, light theme, no narration. Highlight the approval card, the button click, and the live resume. ## Credit impact HITL does not change how a turn is metered — it shifts where the work happens. A turn is charged **exactly one run credit** when it starts at `POST /assistant/chat`. A resume re-enters the conversation through `POST .../resume`, not `/chat`, so answering a question does **not** charge another run credit for the same turn. Language-model token usage is recorded on top of the run credit, on token counts, regardless of whether the turn succeeded, paused, or was cancelled. A paused turn that you resume keeps accumulating tokens on the continued run. Both `POST /assistant/chat` and the resumed turn pass the synchronous [billing admission gate](/billing/usage-gating) before any work runs. On denial, the request is rejected before any rows are written and returns the flat `DenialEnvelope`: ```json DenialEnvelope (402 / 403 / 429) { "code": "credit_plan_exhausted", "layer": "credit", "key": null, "current": null, "limit": null, "reason": "credit_plan_exhausted" } ``` The `layer` maps to the status code: `credit` and `wallet` return `402`, `quota` returns `403`, and `rate` returns `429` (with `Retry-After` and `X-RateLimit-*` headers). This is distinct from the standard `{"detail": "..."}` envelope used by validation and ownership errors — see [errors and status codes](/api-reference/errors) and [rate limiting](/api-reference/rate-limiting). The flat `DenialEnvelope` is emitted **only** on the metered surfaces — run, composer, **assistant**, and managed knowledge. Plain CRUD and org-settings routes return the `{"detail": "..."}` `HTTPException` shape instead. Your error handling on Assistant resume must account for both shapes. ## Reference: every HITL error
Status When Envelope
`400` `resume` body missing `llm`; or `cancel` with no active run `{"detail": "..."}`
`403` Caller is not the question's originator; or not owner/admin `{"detail": "..."}`
`404` Chat not found or not in your organization (no existence leak) `{"detail": "..."}`
`409` `POST /assistant/chat` while a question is pending or a run is in progress `{"detail": "..."}`
`410` `resume`: question already answered, cancelled, expired, or lost the atomic claim `{"detail": "..."}`
`422` `resume`: `response` fails discriminated-union validation FastAPI validation error
`402` / `403` / `429` Billing or rate-limit denial on the turn or resume `DenialEnvelope` `{code, layer, ...}`
## Where to go next The full pause/resume reference, including the workflow-run variant and the shared question/answer wire contract. The workflow node that pauses a run to ask a human a structured question — the builder counterpart to Assistant HITL. How to consume the Assistant's SSE stream, including the `user_input_request` and `run_resumed` frames. How the Assistant discovers tools and requests credentials, the source of `credential_request` questions. Who can use the Assistant and approve actions, and the usage limits that apply. The JavaScript and Python `resume` methods, the nested-payload helper, and the new-run_id contract. --- # Choose the Assistant model & behavior URL: https://docs.modulex.dev/assistant/models-and-settings Pick a managed or BYOK model for the ModuleX Assistant, understand per-organization model availability, and configure how a chat behaves. The model is the engine behind every Assistant reply. ModuleX lets you run the Assistant on a **managed model** (provisioned by ModuleX and billed in credits) or on a **BYOK** model (your own provider account, billed by the provider with no ModuleX markup). This page shows you how to choose, what each choice costs, what is available to your organization, and how a chat's settings behave. If you are new to the Assistant, start with the [Assistant overview](/assistant/overview). For how the Assistant uses tools and pauses for approval, see [Using tools](/assistant/using-tools) and [Human-in-the-loop](/assistant/human-in-the-loop). The Assistant shares its model picker and provider catalog with the rest of ModuleX. The same managed-vs-BYOK choice applies in the [chat surface](/platform/chat/model-selection) and the [workflow Composer](/workflow-builder/composer). Connect and manage providers once on the [LLM providers](/integrations/llm-providers/overview) page. ## The two ways to run the Assistant ModuleX provisions the model for you. Nothing to connect — it works the moment you open a chat. Usage is metered in **credits**. The managed provider's name on the wire is `modulexai`. Connect your own OpenAI, Anthropic, Google Gemini, or xAI account. The model provider bills you directly with **no ModuleX markup**, and the usage is **not** charged in credits. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-3270 · [SCREENSHOT]** **[SCREENSHOT_DESCRIPTION]:** the Assistant chat model picker open, showing the managed option and the connected BYOK providers **[SCREENSHOT_DETAILS]:** Capture the live ModuleX app Assistant chat with the model dropdown expanded. Show a managed `modulexai` model at the top, then connected providers (e.g. OpenAI, Anthropic) grouped below, with one model selected. Light theme, 16:9, no real org name or email visible. ### How they compare | | Managed (`modulexai`) | BYOK | | --- | --- | --- | | Setup | None — available by default | Connect a provider credential first | | Who bills you | ModuleX, in **credits** | The provider, directly | | ModuleX markup | Priced in credits (`100 credits = $1.00`) | **None** | | Counts toward your credit limit | Yes | No — tracked for analytics only | | Providers | ModuleX-provisioned models | OpenAI, Anthropic, Google Gemini, xAI | Use managed models to get started with zero setup and predictable in-app billing. Switch to BYOK when you already have provider accounts, want to use a specific model, or want provider billing to stay outside your ModuleX credit budget. See [Credits & metering](/billing/credits) for what a credit is. ## Choose a model in the app Go to the chat surface and open a new Assistant chat or an existing one. See the [Chat overview](/platform/chat/overview) for the full surface. The model selector sits in the chat. It lists the managed model first, then each provider your organization has connected. Select a managed model for credit-based billing, or a model under a connected BYOK provider to bill through that provider. Your choice applies to the next turn you send. The Assistant runs on the selected model. Every turn records which model actually served the request, so usage and any credit charges are attributed to the served model. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-3271 · [APP_VIDEO]** **[APP_VIDEO_DESCRIPTION]:** switching the Assistant model mid-conversation and sending a new turn **[APP_VIDEO_DETAILS]:** Screen recording in the live ModuleX app. Show an existing Assistant chat, open the model picker, switch from a managed model to a BYOK model, send a follow-up message, and show the new turn streaming. ~20s, light theme, 16:9, highlight the picker with a subtle cursor focus. ### What happens if you do not pick a model If you do not choose a model, the Assistant uses your **organization's default model**. ModuleX resolves the default like this: If an owner or admin has saved a default model in organization settings, that model is used. If no default is saved, ModuleX falls back to the first available managed (`modulexai`) model. This keeps brand-new and legacy organizations working without anyone configuring a model first. A saved default is stored as `settings.composer_llm` on the organization and is shared by the Assistant and the workflow Composer. Setting or clearing it requires the **owner** or **admin** role (see [Permissions & limits](/assistant/permissions-and-limits)). ## Per-organization model availability The models you can pick are scoped to your organization. Two things determine what shows up in the picker. The managed `modulexai` provider is provisioned for every organization, so at least one managed model is always selectable. This is what powers the fallback default. A BYOK provider's models appear only after someone in your organization connects a credential for that provider. No credential means no models from that provider in the picker. To make a BYOK provider available to your whole organization, connect its credential once on the [LLM providers](/integrations/llm-providers/overview) page. Connected credentials are organization-scoped, so every member who can use the Assistant sees the same provider list. For how credentials are stored and resolved, see [Credentials & OAuth2](/concepts/credentials-oauth) and [Managing credentials](/integrations/managing-credentials). Only organization **owners** and **admins** can use the Assistant today. The `member` role has been retired for Assistant, Composer, and schedules. If a teammate cannot open the Assistant, check their role — see [Roles & permissions](/security/roles-permissions) and [Organizations, roles & membership](/concepts/organizations-roles). ### When a model is retired or unavailable Managed model catalogs change over time. ModuleX handles this for you so a chat does not break: If a managed model is marked deprecated or in maintenance, ModuleX routes your request to its replacement model automatically. Usage and any credit charge are recorded against the model that actually served the turn, not the one you requested. If a requested managed model id can't be resolved, ModuleX softens to the managed provider's default serving model rather than failing the turn. BYOK providers pass your model choice straight through to the provider — ModuleX does not rewrite it. If the provider no longer serves that model, the provider returns the error so you see it directly. Pick a current model from your provider, or reconnect the provider on the [LLM providers](/integrations/llm-providers/overview) page. ## How the model is sent on a turn When the app sends a turn to the Assistant, the model choice travels as an `llm` object. You will see the same object if you drive the Assistant from the [API](/assistant/streaming) or an SDK. It has these fields: | Field | Required | What it is | | --- | --- | --- | | `integration_name` | Yes | The provider, e.g. `modulexai` for managed, or `openai` / `anthropic` / `gemini` / `xai` for BYOK. | | `provider_id` | Yes | The provider's id for the model. | | `model_id` | Yes | The model to run, e.g. `gpt-4o`. | | `credential_id` | No | A specific connected credential to use for this turn. | If the `llm` object is omitted entirely, ModuleX applies your organization's default as described above. If it is present but missing any required field, the request is rejected with a `400` validation error. Validation errors use the `{detail}` envelope — see [Errors & status codes](/api-reference/errors). ```bash cURL curl -X POST https://api.modulex.dev/assistant/chat \ -H "Authorization: Bearer mx_live_a1b2c3d4e5f6a7b8c9d0e1f2" \ -H "X-Organization-ID: org_8e7d6c5b4a39281706f5e4d3" \ -H "Content-Type: application/json" \ -d '{ "message": "Summarize the latest commit on my main branch", "llm": { "integration_name": "openai", "provider_id": "openai", "model_id": "gpt-4o" } }' ``` ```python Python from modulex import Modulex client = Modulex( api_key="mx_live_a1b2c3d4e5f6a7b8c9d0e1f2", organization_id="org_8e7d6c5b4a39281706f5e4d3", ) response = client.assistant.chat( message="Summarize the latest commit on my main branch", llm={ "integration_name": "openai", "provider_id": "openai", "model_id": "gpt-4o", }, ) print(response["stream_url"]) ``` ```typescript JavaScript import { Modulex } from "modulex-js"; const client = new Modulex({ apiKey: "mx_live_a1b2c3d4e5f6a7b8c9d0e1f2", organizationId: "org_8e7d6c5b4a39281706f5e4d3", }); const response = await client.assistant.chat({ message: "Summarize the latest commit on my main branch", llm: { integration_name: "openai", provider_id: "openai", model_id: "gpt-4o", }, }); console.log(response.stream_url); ``` Every Assistant request authenticates with `Authorization: Bearer mx_live_…` plus the `X-Organization-ID` header. The provider keys for a BYOK model are your connected credentials — they are never put in the request body. See [Authentication](/api-reference/authentication). To run the Assistant on a managed model from code, set `integration_name` to `modulexai` and use a managed `provider_id` / `model_id` from the [ModuleX-managed models](/integrations/llm-providers/modulexai) catalog. Omit `llm` to let ModuleX apply the organization default. ## Behavior settings Beyond the model, a few settings shape how the Assistant works in a chat. The Assistant can pause to ask you something or to confirm an action before it runs. A chat holds only **one pending question at a time** — answer or cancel it before sending a new message. If you try to send while a question is open, ModuleX asks you to answer it first. See [Human-in-the-loop](/assistant/human-in-the-loop). For actions that change or delete data in a connected service, the Assistant shows a "Run this?" approval card and waits for you to approve or cancel. Read-only lookups run without an approval step. Learn more in [Using tools](/assistant/using-tools). You can stop an in-progress turn at any time. Stopping cancels the current run; you can then send a new message. The model and provider settings you picked stay in place. The Assistant streams its thinking, tool steps, and text as it works, so you see progress live rather than waiting for a final answer. See [Streaming responses](/assistant/streaming). The Assistant v1 is **text-only** — each turn is a single text message. Picking a model does not enable image or audio input in the Assistant chat. (Voice input is a separate chat feature — see [Voice messages](/platform/chat/voice-message).) ## What your model choice costs How a turn is billed depends on which kind of model you picked. Run on credits. ModuleX checks your plan and credit balance **before** the turn starts, then records token usage as the turn completes. If your plan credits and wallet are exhausted, the turn is refused before any work happens. Billed by your provider, not in credits. ModuleX still tracks the usage for your analytics, but it does not count against your credit limit. Because every Assistant turn passes through the billing admission gate, a managed turn can be refused with a `402`, `403`, or `429` when credits, plan quota, or rate limits are reached. These denials use the flat `{code, layer, key, current, limit, reason}` envelope — see [Usage gating & limits](/billing/usage-gating) and [Errors & status codes](/api-reference/errors). For who can use the Assistant and the limits that apply, see [Permissions & limits](/assistant/permissions-and-limits). Switching to a BYOK model does not remove the per-turn rate limit or the requirement to be an owner or admin — it only changes who pays for the model tokens. ## Related pages Connect and manage managed and BYOK model providers. The same model picker, across the chat surface. The default managed provider, billed in credits. Who can use the Assistant and the limits that apply. Watch the Assistant work in real time. What a credit is and what consumes them. --- # Meet the Assistant: agentic chat that gets work done URL: https://docs.modulex.dev/assistant/overview The ModuleX Assistant is a workflow-independent agentic chat. Describe a task in plain language and it searches your connected tools, calls them step by step, and pauses for your approval — no workflow to build first. The Assistant is an agentic chat that gets work done with the tools your team has already connected. You describe a task in plain language, and the Assistant decides which tools to call, runs them one step at a time, and pauses to ask you whenever it needs a decision or an approval. There is no workflow to build first — you just start a conversation. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-3220 · [APP_VIDEO]** **[APP_VIDEO_DESCRIPTION]:** The Assistant completing a multi-tool task end to end inside the chat surface. **[APP_VIDEO_DETAILS]:** Screen-record the ModuleX app at the chat surface. Show one turn where the user types a request such as "Find my three newest GitHub issues and post a summary to the #standup Slack channel," then capture the Assistant streaming its reasoning text, calling a discovery step, calling a GitHub action, pausing on an approval card before the Slack post, the user approving, and the final answer. 30-45s, 16:9, light theme, no narration. Highlight the tool-call chips and the approval card. ## What the Assistant is Think of the Assistant as a colleague who already knows how to use every service you have connected to ModuleX. You ask in plain language; it works through the task and reports back. The Assistant searches your connected integrations and calls their actions directly — for example, listing your repositories or sending a message — using your organization's stored credentials. Each request becomes a turn. The Assistant streams its thinking, calls one tool at a time, reads the result, and keeps going until the task is done. For sensitive actions it stops and shows an approval card. Nothing risky runs until you say yes. The Assistant runs on the same agent engine as the [AI Composer](/concepts/ai-composer), but with one important difference: it has **no access to your workflows at all**. It cannot list, open, build, edit, or run a workflow. Its job is to act directly with your connected tools and your [knowledge bases](/concepts/knowledge-rag) — not to assemble a workflow graph. To learn what happens under the hood, see [how the Assistant works](/assistant/how-it-works). ### What it can do Run a single action on a connected service, like "list my GitHub repos" or "create a Linear issue." The Assistant discovers which tools exist and runs them for you. If a task needs a service you have not connected yet, the Assistant asks you to add the credential — including starting an OAuth connection — right inside the chat. Answer questions from your connected knowledge bases with retrieval, so replies are grounded in your own documents. Ask you a question, offer choices, or request approval mid-task, then continue once you answer. The Assistant is text-only today: each turn is a single text message. It does not build or run workflows, and it does not accept file uploads or `messages[]` history in a single turn. ## Assistant vs Composer vs Builder ModuleX gives you three ways to get things done. They are easy to tell apart once you know what each one produces. **Acts for you, now.** A chat that calls your tools step by step to complete a task. Produces results and answers — not a saved workflow. **Builds a workflow for you.** A chat copilot inside the builder that turns a plain-English description into an editable workflow graph you can save and reuse. **You build it by hand.** The visual canvas where you drag, connect, and configure nodes yourself for full control.
  Assistant AI Composer Workflow builder
**What you get** Work done in the chat — answers and completed actions An editable, reusable workflow graph An editable, reusable workflow graph
**How you drive it** Plain-language chat Plain-language chat Drag, connect, and configure nodes by hand
**Touches your workflows?** No — it has no workflow tools Yes — it creates and edits the graph Yes — you are editing the graph
**Calls your tools live?** Yes, directly during the chat While building, not as a saved run When you run the workflow
**Reusable later?** The chat is saved; the result is one-off Yes — the workflow can be run again Yes — the workflow can be run again
**Best for** One-off tasks and quick questions Going from idea to a workflow fast Precise, complex automations
The Assistant and the AI Composer share the same chat engine and the same approval and pause-for-input behavior, but they are separate surfaces. The Composer edits a workflow graph; the Assistant has no workflow tools whatsoever. The [workflow builder](/workflow-builder/overview) is the canvas both of them — and you — can edit. ## When to use the Assistant - You want something done once, right now — a lookup, a summary, a quick post. - The task spans a few connected services and you would rather describe it than wire it. - You want an answer grounded in your own knowledge. - You are exploring what your connected tools can do. - You will repeat the task and want to save it as a [workflow](/concepts/workflows-and-runs). - You need to schedule it, trigger it from an API, or run it from chat. - You need branching, loops, retries, or precise control over each step. - You want to share a reusable automation with your team. Many people start with the Assistant to prove a task is possible, then rebuild the repeatable version with the [AI Composer](/concepts/ai-composer) or the [workflow builder](/workflow-builder/overview). ## How to start a session The Assistant lives at the chat surface of the ModuleX app. Starting a session takes one message. Sign in to the ModuleX app and open the chat. The Assistant greets you with a few starter prompts you can use as a launching point. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-3221 · [SCREENSHOT]** **[SCREENSHOT_DESCRIPTION]:** The Assistant's empty-state landing with the greeting, the centered message box, and starter prompt chips. **[SCREENSHOT_DETAILS]:** Capture the ModuleX app chat landing for a new session. Show the "Welcome" greeting, the centered composer input, and four starter-prompt chips. 16:9, light theme, no personal data visible. Type what you want done — for example, "Summarize my newest support tickets" — and send. A starter chip fills the box for you but does not send on its own, so you can edit it first. The Assistant streams its reply live: you see its thinking, each tool it calls, and the result of each call as it goes. You can stop a turn at any time. Learn more in [streaming responses](/assistant/streaming). If the Assistant needs a choice, a piece of information, or approval for a sensitive action, it pauses and shows you a question or an approval card. Answer it and the Assistant continues. See [human-in-the-loop](/assistant/human-in-the-loop). If the task needs a service you have not connected, the Assistant asks you to add the credential — including starting an OAuth connection — without leaving the chat. See [authentication and credentials](/integrations/authentication). Your conversations are saved, so you can reopen a chat later, pick up where you left off, and browse your history. The Assistant is scoped to one of your [organizations](/concepts/organizations-roles). As built today, you must have the **owner** or **admin** role in that organization to start or continue an Assistant chat; other members receive a permission error. The retired `member` role is not a current role. See [roles and permissions](/security/roles-permissions) and [permissions and limits](/assistant/permissions-and-limits) for the full picture. A chat can hold only one open question at a time. While the Assistant is waiting for your answer, or while a turn is still running, it will not start another turn on the same chat — you finish the current step first. This keeps each conversation predictable. Yes. Each turn is metered like any other managed run, and language-model token usage is recorded separately. If your plan's allowance is exhausted, the turn is declined before anything runs, and you will see a clear billing message. See [credits and the billing model](/concepts/credits-billing) and [usage gating and limits](/billing/usage-gating). ## Start the Assistant from your own code You can also drive the Assistant programmatically. A turn starts with one authenticated request that creates or continues a chat and returns a stream URL; you then open that stream to watch the turn unfold. Every request uses `Authorization: Bearer mx_live_…` plus your `X-Organization-ID` header — see [authentication](/api-reference/authentication). ```bash cURL curl -X POST https://api.modulex.dev/assistant/chat \ -H "Authorization: Bearer mx_live_your_api_key" \ -H "X-Organization-ID: org_your_organization_id" \ -H "Content-Type: application/json" \ -d '{ "message": "List my GitHub repositories", "llm": { "integration_name": "openai", "provider_id": "openai", "model_id": "gpt-4o" } }' ``` ```python Python import httpx resp = httpx.post( "https://api.modulex.dev/assistant/chat", headers={ "Authorization": "Bearer mx_live_your_api_key", "X-Organization-ID": "org_your_organization_id", }, json={ "message": "List my GitHub repositories", "llm": { "integration_name": "openai", "provider_id": "openai", "model_id": "gpt-4o", }, }, ) data = resp.json() print(data["stream_url"]) # open this to stream the turn ``` ```javascript JavaScript const resp = await fetch("https://api.modulex.dev/assistant/chat", { method: "POST", headers: { Authorization: "Bearer mx_live_your_api_key", "X-Organization-ID": "org_your_organization_id", "Content-Type": "application/json", }, body: JSON.stringify({ message: "List my GitHub repositories", llm: { integration_name: "openai", provider_id: "openai", model_id: "gpt-4o", }, }), }); const data = await resp.json(); console.log(data.stream_url); // open this to stream the turn ``` A successful response returns the chat and run identifiers plus a `stream_url`, which you open over Server-Sent Events to receive the Assistant's actions and output as they happen: ```json Response { "status": "running", "chat_id": "f2b1c0de-1111-2222-3333-444455556666", "run_id": "a1b2c3d4-aaaa-bbbb-cccc-dddddddddddd", "thread_id": "f2b1c0de-1111-2222-3333-444455556666", "stream_url": "/assistant/chat/f2b1c0de-1111-2222-3333-444455556666/listen/a1b2c3d4-aaaa-bbbb-cccc-dddddddddddd" } ``` There is a separate, older chat surface in the app whose "send message" call has **no working backend route** and is documented as a [known limitation](/reference/known-limitations). It is not the Assistant. To talk to the Assistant programmatically, always use the `/assistant/chat` request shown above, which is the supported path. For the full set of Assistant operations, see [how the Assistant works](/assistant/how-it-works) and [streaming responses](/assistant/streaming). ## Where to go next The agentic loop: how the Assistant reasons, picks a tool, acts, observes the result, and decides when it is done. Where the Assistant fits in the ModuleX mental model, and how it relates to the Composer and the engine. How the Assistant discovers and calls integration tools, and how it requests credentials. A tour of the chat surface where the Assistant lives, and how it sits alongside knowledge and workflow runs. How the Assistant pauses for a question or an approval, and how you respond. Worked examples of common Assistant tasks, end to end. --- # Assistant permissions, rate limits & credit cost URL: https://docs.modulex.dev/assistant/permissions-and-limits Exactly who can use the ModuleX Assistant (owner or admin only — the member role is retired), the credit cost per turn, the per-plan rate limits, and the 402/403/429 denial envelopes the Assistant returns when a limit is hit. This page is the technical reference for the access rules and the limits that bound the [Assistant](/concepts/assistant). It covers who is allowed to call the Assistant, the credit cost charged for each turn, the per-plan rate limits, and every denial response you can receive. For what happens inside a turn, see [the Assistant's agentic loop](/assistant/how-it-works); for streaming the result, see [streaming responses](/assistant/streaming). Two distinct controls govern every Assistant request, and they are checked in order on every turn: A role check. The caller must be an `owner` or `admin` in the target organization. A request from any other role is rejected before the turn starts. A usage check. The turn must pass the credit and rate gate for the organization's plan. A turn that exceeds a limit is denied before any work is billed. ## Who can use the Assistant Every Assistant endpoint depends on `organization_admin_required`. To call the Assistant you must satisfy all three of the following: Send a valid bearer token in the `Authorization` header — either a Clerk session JWT (the app) or an `mx_live_*` API key (programmatic). API keys may also be sent in the `X-API-KEY` header. See [authentication](/api-reference/authentication). Send the `X-Organization-ID` header to scope the request to one organization. Without it the request cannot be resolved to an org and is rejected. See [org context](/security/org-context). Your role in the named organization must be `owner` or `admin`. Any other role returns **403**. ### Roles that can and cannot call the Assistant The live ModuleX organization role model has exactly two roles. The `member` role is **retired** (removed as a first-class role on 2026-06-20) and cannot be used to access the Assistant. | Role | Can use the Assistant | Notes | | --- | :-: | --- | | `owner` | Yes | Assignable only at organization creation. | | `admin` | Yes | The default role for invited users; the role new invitations grant. | | `member` | No | **Retired.** Not a current role. Old rows or legacy filters may still surface the string, but the Assistant rejects it with **403**. | The `member` role is retired. The Assistant, the [AI Composer](/concepts/ai-composer), and managed [knowledge](/concepts/knowledge-rag) operations all require `owner` or `admin`. An older comment in the source described the Assistant as available to "every org member" — that comment is inaccurate. As built today, a caller without the `owner` or `admin` role cannot use the Assistant. For the full role model see [roles and permissions](/security/roles-permissions) and [organizations, roles and membership](/concepts/organizations-roles). ### Permission denial response A caller who is authenticated but lacks the `owner` or `admin` role receives a standard FastAPI error envelope — the `{detail}` shape, not the billing `DenialEnvelope`: ```json 403 — role not sufficient { "detail": "Admin or owner role required in organization " } ``` A **403** here means the role check failed and uses the `{detail}` envelope. A **403** from the usage gate (a quota denial) is a different shape — the flat `DenialEnvelope`. The two are distinguished by their body: a role failure has a top-level `detail` string; a quota denial has top-level `code` and `layer` fields. See [errors and status codes](/api-reference/errors). ### Per-endpoint authorization Every Assistant endpoint enforces the same role gate plus a resource-ownership check. Continuing, listening to, resuming, or deleting a chat that belongs to a different organization returns **404** (ownership is hidden as not-found rather than revealed as 403). Start or continue a turn. Adds concurrency guards: a chat with a pending human-in-the-loop question returns **409**, and a chat with a run already in flight returns **409**. `GET /assistant/chat/{chat_id}` — fetch a chat and its messages. **404** if the chat is missing or owned by another organization. `GET /assistant/chat/{chat_id}/listen/{run_id}` — bind the `run_id` to this chat and stream events. A mismatched or cross-tenant `run_id` returns **404** ("Run not found"). `POST /assistant/chat/{chat_id}/resume` — answer a pending question. Additionally, only the user who triggered the question may answer it — anyone else gets **403**. `GET /assistant/chats` — list your own Assistant chats. Filtered to the caller (`creator_id`), so it returns only chats you created, not every chat in the org. `GET /assistant/chat/{chat_id}/status` — read run status. **404** if missing. `POST /assistant/chat/{chat_id}/cancel` — cancel a running execution. **400** if there is no active execution. `DELETE /assistant/chat/{chat_id}` — soft-delete (or hard-delete with `permanent=true`). **404** if missing. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-3280 · [SCREENSHOT]** **[SCREENSHOT_DESCRIPTION]:** The organization members settings page showing the owner/admin role assignment that grants Assistant access. **[SCREENSHOT_DETAILS]:** Capture the organization members/roles screen in the ModuleX app. Show at least one `owner` and one `admin` row with their role labels visible, and the role selector that only offers `admin` (no `member` option). Light theme, 16:9, crop to the members table. Annotate the role column. This illustrates that only owner/admin roles exist and can use the Assistant. ## Limits overview Once the role check passes, every turn runs through the billing admission gate **before any database row is written or any model call is made**. This gate is live on the Assistant surface (alongside the [AI Composer](/concepts/ai-composer), [workflow runs](/workflow-builder/execution/api-endpoint), and managed [knowledge](/platform/knowledge/managed)). It enforces three things in one synchronous step: The organization must have credit allowance or wallet funds for the turn's run charge. The turn must fit within the plan's `sync_exec` runs-per-minute limit. The organization must not be suspended and must be within plan quotas. A turn that fails any check is denied with a `402`, `403`, or `429` carrying the flat `DenialEnvelope`. Because the gate runs before any write, a denied turn creates no chat message, no run, and no charge. ## Credit cost per turn The Assistant charges **exactly one run credit per turn**. A turn is one user message and the agent run it triggers — regardless of how many model calls or tool calls the agent makes inside that turn. The flat per-turn charge, written once as a `run` usage record. `RUN_CREDIT = 1`. One credit equals `$0.01` (100 credits = `$1.00`). The charge is the same for every turn no matter how long the agentic loop runs. Charged after the admission gate admits the turn, keyed by the turn key `{chat.id}:{run_id}`. The charge is idempotent on that key, so it cannot be double-counted. Answering a [human-in-the-loop](/assistant/human-in-the-loop) question re-enters the same turn through `POST /assistant/chat/{chat_id}/resume`, which mints a new `run_id` but does **not** levy a second run credit — the resume path is marked as already-charged. Two other charges can accrue during a turn, billed separately from the flat run credit. These are the cost of the work the agent actually does: When the turn uses a ModuleX-managed model, language-model tokens are metered on input and output token counts and recorded in the executor's `finally` block — billed regardless of whether the turn succeeds, fails, or is cancelled. Bring-your-own-key model usage is **not** credited; it is analytics-only and billed by your upstream provider. See [credits and metering](/billing/credits) and [model selection](/assistant/models-and-settings). Each managed [integration tool](/assistant/using-tools) the Assistant executes carries a flat tool base charge (`TOOL_BASE = 1` credit, anchored to `$0.01`, multiplied by any pool multiplier). Each managed [knowledge](/concepts/knowledge-rag) search carries a retrieval charge (`RETRIEVAL_BASE = 1` credit). These accrue on top of the per-turn run credit. The single run credit is the Assistant's flat per-turn fee. Token, tool, and retrieval charges are the variable cost of the actions inside the turn and are metered independently. For the full credit lifecycle (reserve, charge, settle) and the credit-to-dollar conversion, see [credits and metering](/billing/credits). ## Rate limits Every Assistant turn counts against the organization plan's `sync_exec` rate class — the synchronous-execution runs-per-minute limit. Assistant turns count under `sync_exec` unconditionally. When the rate is exceeded the gate releases the credit reservation and returns **429**. ### Per-plan limits The values below come from the runtime-authoritative plan configuration (`subscription_plans.json`). The `sync_exec` column is the one that bounds the Assistant. Where a value is `null`, that limit is unlimited (the check is skipped). | Plan | `sync_exec` (Assistant turns / min) | `api` (requests / min) | Monthly credit allowance | Knowledge bases | | --- | :-: | :-: | :-: | :-: | | Free | 10 | 10 | 300 (one-time) | 10 | | Pro | 150 | 100 | 5,000 / month | 3 | | Max | 500 | 500 | 20,000 / month | 50 | | Enterprise | unlimited | unlimited | custom | unlimited | The `sync_exec` limit governs Assistant turns (and other synchronous executions). The separate `api` limit governs raw request throughput per key or user. A turn can be denied by either: too many turns per minute returns a `sync_exec` rate denial, while too many HTTP requests per minute returns an API rate denial. For the canonical, all-surface rate-limit reference see [rate limiting](/api-reference/rate-limiting) and the plan matrix in [usage gating and limits](/billing/usage-gating). The credit allowances and rate limits above follow the runtime plan configuration, which is the authoritative source for limits. A stale schema docstring in the codebase lists different numbers (for example Pro 10,000 / Max 50,000 credits); those are out of date. Use the values in the table. The marketing site and the runtime config agree on these credit and rate numbers; they disagree only on the **annual price** of paid plans — see [plans and pricing](/billing/plans) for both figures. ## Denial responses When a limit is hit, the turn is rejected with the flat `DenialEnvelope` — a different shape from the role-failure `{detail}` envelope above. The envelope's `layer` field determines the HTTP status. The billing/usage denial body. Flat — no `detail` wrapper. The machine-readable denial code, for example `credit_plan_exhausted`, `rate_limit_exceeded`, or `quota_exceeded`. The gate layer that denied the turn: `credit`, `wallet`, `quota`, or `rate`. This maps to the HTTP status. The bucket or rate-class key the denial applies to (for example `sync_exec` for a rate denial, or the organization id for a credit denial). May be `null`. The current measured value at denial time (for example the current request count for a rate limit). May be `null`. The limit that was exceeded (for example the plan's `sync_exec` value, or the monthly credit allowance). May be `null`. A short reason string, usually equal to or paired with `code`. The status code depends on which layer denied the turn: | Layer | HTTP status | Code | When it happens | | --- | :-: | --- | --- | | `rate` | **429** | `rate_limit_exceeded` | The plan's `sync_exec` runs-per-minute limit was exceeded. Includes `Retry-After`, `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset` headers. | | `quota` | **403** | `quota_exceeded` | A plan quota was exceeded, or the organization is suspended after dunning. | | `credit` | **402** | `credit_plan_exhausted` | The monthly plan credit allowance is used up and no wallet overage applies. | | `wallet` | **402** | `wallet_overage_disabled`, `wallet_insufficient`, `upgrade_payment_failed` | Plan credits are exhausted and the wallet cannot cover the turn (overage off, balance too low, or a failed upgrade payment). | ```json 402 — credit allowance exhausted { "code": "credit_plan_exhausted", "layer": "credit", "key": "", "current": null, "limit": 5000.0, "reason": "credit_plan_exhausted" } ``` ```json 429 — sync_exec rate exceeded (with Retry-After and X-RateLimit-* headers) { "code": "rate_limit_exceeded", "layer": "rate", "key": "sync_exec", "current": 150, "limit": 150, "reason": "rate_limit_exceeded" } ``` The Assistant endpoints can return **both** envelope shapes. A validation or authorization failure (a missing field, a wrong role, a not-found chat) returns `{detail: ...}`. A credit, wallet, quota, or rate denial returns the flat `DenialEnvelope`. A client must branch on the response body to tell a role-based **403** apart from a quota-based **403**. The full taxonomy of all three error-envelope shapes lives on [errors and status codes](/api-reference/errors). ## Other limits on a turn Beyond credit and rate gating, a few structural limits bound an Assistant turn: A chat may hold only one pending [human-in-the-loop](/assistant/human-in-the-loop) question at a time. Starting a new turn while a question is pending returns **409** ("This chat has a pending question; answer it first"). A chat may have only one run in flight. Starting a new turn while a run is in progress returns **409** ("A run is already in progress on this chat; wait for it to finish or cancel it"). Cancel the in-flight run with `POST /assistant/chat/{chat_id}/cancel`. The `message` field must be a JSON string. An array or object returns **400** ("Message must be a string"). Multimodal input is not supported on this surface. A pending HITL question is held for 7 days. After that the pending sentinel expires; see [human-in-the-loop](/assistant/human-in-the-loop) for resume and cancel semantics. A run's SSE history is retained for 1 hour, so you can reconnect and replay events within that window. After it expires only live events on an active run are available. See [streaming responses](/assistant/streaming). ## Worked example — handling a denied turn This example starts an Assistant turn and shows how to detect and react to each denial class. Every request authenticates with `Authorization: Bearer mx_live_…` plus `X-Organization-ID` (see [authentication](/api-reference/authentication)). The caller must hold the `owner` or `admin` role. ```bash cURL # Start a turn. Any of these statuses can come back: # 200 success | 403 {detail} role failure | 402/403/429 DenialEnvelope | 409 busy curl -i -X POST https://api.modulex.dev/assistant/chat \ -H "Authorization: Bearer mx_live_xxxxxxxxxxxxxxxxxxxx" \ -H "X-Organization-ID: org_3f9a12c4d5e6" \ -H "Content-Type: application/json" \ -d '{ "message": "Summarize my three most recent GitHub issues", "llm": {"integration_name":"openai","provider_id":"openai","model_id":"gpt-4o"} }' # On 429 the response carries Retry-After and X-RateLimit-* headers: # HTTP/1.1 429 Too Many Requests # Retry-After: 60 # X-RateLimit-Limit: 150 # X-RateLimit-Remaining: 0 # {"code":"rate_limit_exceeded","layer":"rate","key":"sync_exec",...} ``` ```python Python import httpx BASE = "https://api.modulex.dev" HEADERS = { "Authorization": "Bearer mx_live_xxxxxxxxxxxxxxxxxxxx", "X-Organization-ID": "org_3f9a12c4d5e6", "Content-Type": "application/json", } resp = httpx.post( f"{BASE}/assistant/chat", headers=HEADERS, json={ "message": "Summarize my three most recent GitHub issues", "llm": {"integration_name": "openai", "provider_id": "openai", "model_id": "gpt-4o"}, }, ) if resp.status_code == 200: run = resp.json() print("turn started:", run["run_id"], "stream:", run["stream_url"]) elif resp.status_code in (402, 403, 429): body = resp.json() if "layer" in body: # flat DenialEnvelope -> a usage/billing limit layer = body["layer"] if layer == "rate": wait = int(resp.headers.get("Retry-After", "60")) print(f"rate limited on {body['key']}; retry after {wait}s") elif layer in ("credit", "wallet"): print(f"out of credit ({body['code']}); top up the wallet or upgrade") elif layer == "quota": print(f"quota or suspension ({body['code']})") else: # {"detail": ...} -> role / validation failure print("not permitted:", body["detail"]) # e.g. owner/admin required elif resp.status_code == 409: print("chat is busy: a run or question is already pending") ``` ```javascript JavaScript const BASE = "https://api.modulex.dev"; const headers = { Authorization: "Bearer mx_live_xxxxxxxxxxxxxxxxxxxx", "X-Organization-ID": "org_3f9a12c4d5e6", "Content-Type": "application/json", }; const resp = await fetch(`${BASE}/assistant/chat`, { method: "POST", headers, body: JSON.stringify({ message: "Summarize my three most recent GitHub issues", llm: { integration_name: "openai", provider_id: "openai", model_id: "gpt-4o" }, }), }); const body = await resp.json(); if (resp.status === 200) { console.log("turn started:", body.run_id, "stream:", body.stream_url); } else if ([402, 403, 429].includes(resp.status)) { if ("layer" in body) { // flat DenialEnvelope -> a usage/billing limit if (body.layer === "rate") { const wait = Number(resp.headers.get("Retry-After") ?? "60"); console.log(`rate limited on ${body.key}; retry after ${wait}s`); } else if (body.layer === "credit" || body.layer === "wallet") { console.log(`out of credit (${body.code}); top up the wallet or upgrade`); } else if (body.layer === "quota") { console.log(`quota or suspension (${body.code})`); } } else { // {"detail": ...} -> role / validation failure console.log("not permitted:", body.detail); // e.g. owner/admin required } } else if (resp.status === 409) { console.log("chat is busy: a run or question is already pending"); } ``` The branch on `"layer" in body` is the reliable way to tell a usage denial (flat `DenialEnvelope`) from a role or validation failure (`{detail}`). Retry a **429** after the `Retry-After` interval; do not retry a **402** or a role **403** — those require a credit top-up, a plan upgrade, or a role change rather than a retry. ## Related pages The full organization role model and which actions require owner or admin. The billing admission gate and its 402/403/429 denial responses across all surfaces. What a credit is, the conversion to dollars, and exactly what consumes credits. What happens inside a single turn, from start to termination. --- # Stream the Assistant over SSE URL: https://docs.modulex.dev/assistant/streaming Stream the Assistant's actions and output live with Server-Sent Events: the action-frame wire format, every event type, listen() in the JavaScript and Python SDKs, how to cancel a turn, and how app streaming differs from the SDKs. Every Assistant turn streams. When you start a turn, the response does not arrive in one block — the Assistant pushes a series of small JSON frames over a Server-Sent Events (SSE) connection as it thinks, calls tools, reads results, pauses for your input, and finishes. This page is the technical reference for that stream: the frame format on the wire, every event `type` the Assistant emits, how to consume the stream with `listen()` in the SDKs, how to cancel a turn, and how streaming in the app differs from streaming from your own code. The Assistant stream is one specific case of ModuleX run streaming. For the transport-level reference that also covers workflow runs, see [SSE run streaming](/realtime/sse-streaming). For the SDK helpers that pair streaming with answering pause-for-input questions, see [streaming and HITL](/sdks/streaming-hitl). ## How an Assistant turn streams A turn is a request-then-listen pattern. You never receive events on the same request that starts the turn — starting a turn and listening to it are two separate calls. Send `POST /assistant/chat` with your message. The response is a small JSON body (not a stream) that contains the `chat_id`, a freshly minted `run_id`, the `thread_id`, and a `stream_url`. See [how the Assistant works](/assistant/how-it-works) for the full request shape. Open `GET /assistant/chat/{chat_id}/listen/{run_id}` (this is exactly the `stream_url` from step 1). The connection stays open and the server pushes frames as the turn progresses. Read frames and switch on each frame's `type`. Keep reading until you receive a terminal frame (`done`, `error`, or `cancelled`), or until the turn pauses for [human-in-the-loop](/assistant/human-in-the-loop) input. If the turn paused with a `user_input_request`, answer it with `POST /assistant/chat/{chat_id}/resume`. Resume mints a **new** `run_id` and a new `stream_url`, so you open a fresh stream on the new id. Otherwise the turn is done. The stream is server-to-client only. There is no way to send data back to the Assistant over the open SSE connection — answering a paused turn, cancelling, and starting the next turn are all separate REST calls. Resume and cancel are covered below and in [human-in-the-loop](/assistant/human-in-the-loop). ## The stream endpoint Opens the live event stream for one Assistant run. Returns a `text/event-stream` response with the headers `Cache-Control: no-cache`, `Connection: keep-alive`, and `X-Accel-Buffering: no`. The Assistant chat to stream. Must be a chat you own in the current organization, with `kind` set to `assistant`. The run to stream, as returned by `POST /assistant/chat` or `POST /assistant/chat/{chat_id}/resume`. A new `run_id` is minted on every turn and on every resume, so the id is per-turn, not per-conversation. The stable per-conversation id is the `thread_id`, which equals the `chat_id`. ### Authentication and authorization Every call uses the standard ModuleX headers — see [authentication](/api-reference/authentication). - `Authorization: Bearer mx_live_…` — your API key (a Clerk JWT is also accepted; the backend additionally accepts the key in `X-API-KEY`). - `X-Organization-ID: org_…` — the organization context. Missing this header returns `400`. The Assistant endpoints require the **owner** or **admin** role in that organization, enforced by `organization_admin_required`. The retired `member` role cannot use the Assistant. See [roles and permissions](/security/roles-permissions) and [permissions and limits](/assistant/permissions-and-limits). Before the server subscribes you to the stream, it runs an ownership check that binds the `run_id` to your `chat_id` and organization. A `run_id` that is not in your chat — including one that belongs to another tenant — returns **`404`** with `Run not found`, never `403`. There is no existence leak: "does not exist" and "not yours" return the same `404`. A non-2xx status on connect is delivered as an error **before the first frame**, not as a frame in the stream. In the SDKs this surfaces as a thrown typed exception the moment you start iterating (for example `NotFoundError` for `404`). See [error frames vs connection errors](#error-frames-vs-connection-errors). ## Action frames: the wire format The Assistant stream is a **data-only** SSE stream. Each event is written as a single `data:` line whose value is a JSON object, terminated by a blank line: ```text data: {"type":"response_chunk","data":{"chunk":"Let me check that for you."}} ``` There is **no** SSE `event:` line. The discriminator you switch on is the `type` field **inside** the JSON, not the SSE event name. This matters if you write your own parser: do not look for a named SSE event; parse the `data:` payload and read `type`. The chat-list sidebar stream (`GET /chats/stream`) uses the opposite convention — real SSE `event:` lines such as `event: chat_list_updated`. That is a different stream for a different purpose and is not the Assistant turn stream. Do not reuse a parser written for one on the other. The full taxonomy is in [SSE run streaming](/realtime/sse-streaming). ### Flat vs wrapped payloads Within a single Assistant stream, payloads are inconsistent about nesting. Most Assistant frames are **wrapped**: the real payload sits under a `data` key alongside `type`. The keepalive `heartbeat` frame is **flat** — it carries only `type` and nothing else. ```text data: {"type":"tool_call","data":{"tool":"get_available_integrations","input":{},"tool_call_id":"tc_1","timestamp":"2026-06-20T10:00:02Z"}} data: {"type":"heartbeat"} ``` One frame nests **one level deeper** than the rest: `user_input_request`. Its frame is `{"type":"user_input_request","data":{…the question…}}`, so the actual question object is at `data.data`, not `data`. A naive read of `frame.data.kind` returns nothing — read `frame.data.data.kind`. The Python SDK ships `user_input_request_from_event()` specifically to hide this; see [the HITL frame](#the-hitl-frame-user_input_request). ### Responses are snake_case Frame payloads are the backend's raw JSON, which is snake_case (`run_id`, `tool_call_id`, `request_id`). The SDKs do **not** convert streamed response fields to camelCase, so the typed event models you consume use snake_case field names. (The JavaScript SDK does convert request bodies camelCase to snake_case on the way out, but never response payloads.) ## Event types These are the frame types the Assistant emits, as actually published by the executor. Switch on `type`. Every payload field below lives under the frame's `data` key unless noted. First frame of the run. Carries `run_id`, `thread_id`, `workflow_id` (always `null` for the Assistant), `workflow_type`, and `timestamp`. `workflow_type` is **always `"composer"`** on an Assistant run — even though no workflow is involved. It is not a reliable surface discriminator. Do not branch on it to detect "this is an Assistant run." The reliable surface tag is the request metadata `surface` field (`assistant`). A streamed slice of the Assistant's natural-language reply. The payload is `{ chunk }`, where `chunk` is a text fragment. Concatenate `chunk` values in order to build the running reply text. The Assistant has decided to call a tool. Payload: `tool` (the tool name), `input` (the arguments object), `tool_call_id` (a stable id for this call), and `timestamp`. The Assistant calls one tool at a time within a step. See [using tools](/assistant/using-tools). The result of a `tool_call`. Payload: `tool`, `tool_call_id` (matches the originating `tool_call`), `output`, and `timestamp`. On a tool error the `output` carries an error shape such as `{error, success: false}`. Pair a result to its call by `tool_call_id`. An internal progress signal the agent emits after repeated tool failures or at language-model-call milestones. Payload is `{message, consecutive_failures}` or `{message, total_llm_calls}`. Informational; safe to ignore in most clients. The turn paused to ask you something — a choice, a yes/no, free text, or a credential connection. The question object is at `data.data` (one level deeper than every other frame). After this frame the stream goes **quiet** with no terminal frame; the run is paused server-side awaiting your answer. See [the HITL frame](#the-hitl-frame-user_input_request) and [human-in-the-loop](/assistant/human-in-the-loop). Published on the **old** run's channel when an OAuth credential connection auto-resumes the turn without an explicit resume call. Payload is `{new_run_id}`. When you see it, switch your stream to the new `run_id`. See [OAuth auto-resume](#oauth-auto-resume). The turn finished successfully. Payload carries `response` (the final reply text), `tool_calls` (the calls made this turn), `usage` (`input_tokens`, `output_tokens`, `total_tokens`, `llm_calls`), and the composer-shaped keys `has_workflow_changes` (always `false` for the Assistant) and `workflow_tool` (always `null`). This frame ends the stream. The turn failed. Payload carries a `message`. This frame ends the stream. A backend exception mid-turn is emitted as a final `error` frame rather than dropping the connection. See [error frames vs connection errors](#error-frames-vs-connection-errors). The turn was cancelled, typically by `POST /assistant/chat/{chat_id}/cancel`. Payload carries a `reason`. This frame ends the stream. See [cancel a turn](#cancel-a-turn). A **history-only** marker written when a turn pauses for input. It is appended to the run's history but **never published live**, so an actively-connected client does not receive it — the live stream simply goes quiet after `user_input_request`. On a later reconnect the replay uses `interrupted` to stop cleanly. Treat it as terminal only when replaying history. A `{"type":"heartbeat"}` keepalive injected roughly every 15 seconds of idle time to keep the connection and any intermediary proxies alive. It carries no payload. Ignore it. The Python SDK filters heartbeats out by default; the JavaScript SDK yields them and you skip them yourself. See [SDK heartbeat handling](#heartbeats-and-keepalives). The Assistant has **no workflow**, so the composer-only frames `workflow_change` and `workflow_sync` **never fire** on an Assistant stream. If you are writing one consumer for both the Assistant and the [Composer](/concepts/ai-composer), you can ignore those two types on the Assistant path. ### Terminal frames and when the stream ends A stream ends after one of these terminal frames: `done`, `error`, or `cancelled`. The `interrupted` marker is also terminal, but only on a history **replay** — it is never sent live. There is one case where the stream does **not** end with a terminal frame: a live pause for input. After a `user_input_request`, the connection stays open and idle (with periodic heartbeats) but no `done`/`error`/`cancelled` arrives, because the run is parked waiting for your answer. Your consumer should stop reading on `user_input_request`, answer it, and open a fresh stream on the new `run_id` that resume returns. ### Heartbeats, history, and reconnection Each published frame is also appended to a per-run history list with a **1-hour TTL**. When you (re)connect to a run that already has history, the server first replays the stored frames in order, then tails live. If the run already reached a terminal state, the replay yields up to and including the terminal frame and then closes — there is no live producer to wait for. This makes reconnecting within the hour replay-safe. The SDKs do not auto-reconnect; reconnection (and de-duplicating replayed `tool_call`/`tool_result` frames by `tool_call_id`) is the consumer's job. ## The HITL frame: `user_input_request` When the Assistant needs a decision, it pauses and emits a `user_input_request`. The question is at `data.data` and is discriminated on its `kind`. The wire contract is shared with the Composer and is verified field-for-field across both SDKs. ```text data: {"type":"user_input_request","data":{"kind":"yes_no","request_id":"req_42","message":"Send the welcome email now?","required":true,"allow_free_text":false,"yes_label":"Send it","no_label":"Not yet"}} ``` All kinds share the base fields `request_id`, `message` (markdown), `required` (default `true`), `allow_free_text` (default `false`), `context` (optional), and `timeout_hint_seconds` (optional). - `single_choice` — `options` (1 to 10 `ChoiceOption` entries). - `multi_choice` — `options` (1 to 20), `min_selections`, `max_selections` (optional). - `yes_no` — `yes_label` (default `Yes`), `no_label` (default `No`). - `free_text` — `placeholder` (optional), `multiline`, `min_length`, `max_length` (optional). - `credential_request` — `integration_name`, `integration_display_name`, `integration_logo` (optional), `auth_options` (a list of `CredentialAuthOption` whose `auth_type` is one of `oauth2`, `api_key`, `bearer_token`, `modulex_key`, `custom`), and `pending_node_name` (optional). You answer with `POST /assistant/chat/{chat_id}/resume`, sending the `request_id`, a `response` discriminated on its own `kind`, and the `llm` config. Resume mints a new `run_id` and `stream_url`. Only the user who triggered the question may answer it. The complete request/response contract, error codes, and worked examples are in [human-in-the-loop](/assistant/human-in-the-loop) and [streaming and HITL](/sdks/streaming-hitl). ### OAuth auto-resume When a `credential_request` opens an OAuth connection and you complete it, the OAuth callback resumes the turn for you — you do not call resume manually. The callback publishes a `run_resumed` frame **on the old run's channel** carrying `{new_run_id}`. Your client should react by switching its stream to `new_run_id`. This is why Assistant chats share the Composer's storage and resume machinery. ## `listen()` in the SDKs Both official SDKs wrap the stream behind a single `listen()` method that returns an async iterator of parsed frames. The auth headers, the data-only parsing, the snake_case payloads, and the connect-time error handling are all handled for you. SDK streams are **not** subject to the client's retry policy or request timeout. `maxRetries` and `timeout` apply to ordinary requests only; `listen()` opens the connection directly and relies on heartbeats to keep it alive. Cancellation is explicit — see [cancel a turn](#cancel-a-turn). The example below starts a turn, opens the stream, and prints text and tool activity until a terminal frame. Authenticate with `Authorization: Bearer mx_live_…` plus `X-Organization-ID` — see [authentication](/api-reference/authentication). ```bash cURL # 1. Start the turn — returns chat_id, run_id, and stream_url. curl -sN -X POST https://api.modulex.dev/assistant/chat \ -H "Authorization: Bearer mx_live_your_api_key" \ -H "X-Organization-ID: org_your_organization_id" \ -H "Content-Type: application/json" \ -d '{ "message": "List my GitHub repositories", "llm": { "integration_name": "openai", "provider_id": "openai", "model_id": "gpt-4o" } }' # => {"status":"running","chat_id":"...","run_id":"...","thread_id":"...","stream_url":"/assistant/chat/.../listen/..."} # 2. Open the stream (substitute the stream_url from step 1). # Each event arrives as a single `data:` line; the type is inside the JSON. curl -sN https://api.modulex.dev/assistant/chat/{CHAT_ID}/listen/{RUN_ID} \ -H "Authorization: Bearer mx_live_your_api_key" \ -H "X-Organization-ID: org_your_organization_id" \ -H "Accept: text/event-stream" ``` ```python Python import asyncio from modulex import Modulex client = Modulex(api_key="mx_live_your_api_key", organization_id="org_your_organization_id") async def main(): # 1. Start the turn. started = await client.assistant.chat( "List my GitHub repositories", llm={"integration_name": "openai", "provider_id": "openai", "model_id": "gpt-4o"}, ) # 2. Open the stream. Heartbeats are filtered out by default. async with client.assistant.listen(started.chat_id, started.run_id) as stream: async for event in stream: if event.event == "response_chunk": print(event.data["data"]["chunk"], end="", flush=True) elif event.event == "tool_call": print(f"\n[calling {event.data['data']['tool']}]") elif event.event == "user_input_request": print("\n[paused — needs your input; answer with assistant.resume]") break if event.is_terminal: # done, error, cancelled, interrupted break asyncio.run(main()) ``` ```javascript JavaScript import { Modulex } from "modulex-js"; const client = new Modulex({ apiKey: "mx_live_your_api_key", organizationId: "org_your_organization_id", }); // 1. Start the turn. const started = await client.assistant.chat({ message: "List my GitHub repositories", llm: { integration_name: "openai", provider_id: "openai", model_id: "gpt-4o" }, }); // 2. Open the stream. Frames are yielded already discriminated on `type`. for await (const evt of client.assistant.listen(started.chat_id, started.run_id)) { switch (evt.type) { case "response_chunk": process.stdout.write(evt.data.chunk ?? ""); break; case "tool_call": console.log(`\n[calling ${evt.data.tool}]`); break; case "user_input_request": console.log("\n[paused — answer with assistant.resume]"); break; // stop reading; go answer it case "heartbeat": break; // ignore keepalives case "done": case "error": case "cancelled": break; } } ``` The SDK surfaces differ in shape. The Python SDK yields a normalized `SSEEvent` whose logical name is on `event.event` and whose raw payload is on `event.data`; it exposes `event.is_terminal`. The JavaScript SDK casts each frame to a typed union and yields the payload object directly, discriminated on `evt.type`. In both, a `user_input_request` payload is nested — Python at `event.data["data"]`, JavaScript at `evt.data`. ### Heartbeats and keepalives The two SDKs treat the `heartbeat` keepalive differently: - **Python** filters `heartbeat` (and `keepalive`) frames out — they are never yielded unless you pass `include_heartbeats=True`. Its read timeout is disabled on streams, so a long, idle pause-for-input does not time out. - **JavaScript** skips SSE comment lines automatically, but a `{"type":"heartbeat"}` data frame is yielded as a normal event — your consumer must ignore it (as the example does). ### Error frames vs connection errors There are two distinct error surfaces, and you handle them in different places: 1. **Connection errors** happen on connect, before any frame. A non-2xx status throws a typed exception synchronously as you begin iterating — `401` AuthenticationError, `403` PermissionError, `404` NotFoundError, `429` RateLimitError. Wrap the start of iteration in a try/catch. 2. **Mid-stream errors** arrive as a normal `error` frame inside the stream. The parser does not throw on them — your switch must handle `type === "error"` and treat it as terminal. For the full SDK error model — classes, retries, and the `410` gap on resume — see [errors and retries](/sdks/errors-retries) and [streaming and HITL](/sdks/streaming-hitl). ## Cancel a turn There is no in-band cancel over the stream. To stop a running turn, make a separate REST call to `POST /assistant/chat/{chat_id}/cancel`. The server sets a cancel flag, publishes a `cancelled` frame to the stream, sets the run status to `cancelled`, and — if the turn was paused on a HITL question — clears the pending question so it is not re-presented. If there is no running turn, the call returns `400` with `No active execution to cancel`. In the SDKs you also stop **reading** locally. Closing the iterator (via an `AbortController` in JavaScript, or by exiting the `async with` / breaking the loop in Python) stops consuming frames; calling `cancel()` stops the turn on the server. Do both: stop reading to free the client, and call cancel to stop the work and its metering. ```bash cURL curl -X POST https://api.modulex.dev/assistant/chat/{CHAT_ID}/cancel \ -H "Authorization: Bearer mx_live_your_api_key" \ -H "X-Organization-ID: org_your_organization_id" # => {"status":"cancelled","chat_id":"...","run_id":"..."} ``` ```python Python # Stop consuming locally (exit the `async with`), then cancel the turn server-side. await client.assistant.cancel(chat_id) # => AssistantCancelResponse(status="cancelled", chat_id=..., run_id=...) ``` ```javascript JavaScript // Abort the local stream... const controller = new AbortController(); const stream = client.assistant.listen(chatId, runId, { signal: controller.signal }); setTimeout(() => controller.abort(), 5_000); // stop reading after 5s // ...and cancel the turn on the server. await client.assistant.cancel(chatId); // => { status: "cancelled", chat_id: "...", run_id: "..." } ``` Aborting the local stream in JavaScript stops iteration cleanly and releases the underlying reader, but it does **not** stop the turn on the server by itself — the work keeps running and keeps metering until you call `cancel()`. Always pair a local abort with a server-side `cancel()` when you want the turn to actually stop. ## App streaming vs SDK streaming Both the ModuleX app and the SDKs consume the same backend stream and the same frame types, but the transport mechanics differ. | | In the app | From the SDKs | | --- | --- | --- | | Transport | Browser `EventSource` through a Next.js proxy route | `fetch`-based reader (JavaScript) / `httpx` SSE (Python) | | Auth | The proxy injects `Authorization` + `X-Organization-ID`; the browser passes the org id as an `organization_id` **query param** because `EventSource` cannot set headers | You send `Authorization: Bearer mx_live_…` + `X-Organization-ID` **headers** directly | | Reconnect | Retries the same `run_id` with `1s, 2s, 4s` backoff, up to 3 attempts, de-duplicating replayed frames by `tool_call_id` | No auto-reconnect — you reconnect and de-duplicate yourself | | Heartbeats | Treated as no-op keepalives | Python filters them; JavaScript yields them for you to ignore | | Rendering | Frames drive live UI: streaming text, tool-call chips, an approval card on a paused turn | You receive raw typed frames and do what you want with them | **🎬 MEDIA PLACEHOLDER · MX-MEDIA-3260 · [APP_VIDEO]** **[APP_VIDEO_DESCRIPTION]:** A single Assistant turn streaming live in the app, with the matching wire frames shown alongside. **[APP_VIDEO_DETAILS]:** Screen-record the ModuleX app chat surface during one Assistant turn that calls a tool. Show streaming reply text appearing token by token, a tool-call chip transitioning from running to completed, and the final answer. Optionally split-screen the corresponding `response_chunk`, `tool_call`, `tool_result`, and `done` frames. 25-40s, 16:9, light theme, no narration, no personal data. The app's chat experience is covered in [chat overview](/platform/chat/overview); the proxy and reconnection behavior described here is an implementation detail you do not configure. ## A full worked example: a tool turn from start to finish The trace below is a complete Assistant turn that calls a discovery tool, calls an integration action, and finishes. Note the data-only frames, the wrapped payloads, and the heartbeat keepalive. ```text data: {"type":"metadata","data":{"run_id":"a1b2c3d4-aaaa-bbbb-cccc-dddddddddddd","thread_id":"f2b1c0de-1111-2222-3333-444455556666","workflow_id":null,"workflow_type":"composer","timestamp":"2026-06-20T10:00:01Z"}} data: {"type":"response_chunk","data":{"chunk":"Let me look that up."}} data: {"type":"tool_call","data":{"tool":"get_available_integrations","input":{},"tool_call_id":"tc_1","timestamp":"2026-06-20T10:00:02Z"}} data: {"type":"tool_result","data":{"tool":"get_available_integrations","tool_call_id":"tc_1","output":{"success":true,"integrations":["github"]},"timestamp":"2026-06-20T10:00:03Z"}} data: {"type":"heartbeat"} data: {"type":"tool_call","data":{"tool":"execute_integration_tool","input":{"integration":"github","action":"list_repositories"},"tool_call_id":"tc_2","timestamp":"2026-06-20T10:00:04Z"}} data: {"type":"tool_result","data":{"tool":"execute_integration_tool","tool_call_id":"tc_2","output":{"status":"ok","repos":["acme/api","acme/web"]},"timestamp":"2026-06-20T10:00:05Z"}} data: {"type":"response_chunk","data":{"chunk":"Here are your repos: acme/api, acme/web."}} data: {"type":"done","data":{"response":"Here are your repos: acme/api, acme/web.","has_workflow_changes":false,"tool_calls":[{"tool":"execute_integration_tool","tool_call_id":"tc_2"}],"workflow_tool":null,"usage":{"input_tokens":1200,"output_tokens":340,"total_tokens":1540,"llm_calls":2}}} ``` If the same turn needed a credential you had not connected, the `execute_integration_tool` step would instead produce a `user_input_request` of kind `credential_request`; the stream would go quiet, and you would connect the credential (which auto-resumes via `run_resumed`) or answer with resume. See [using tools](/assistant/using-tools) and [human-in-the-loop](/assistant/human-in-the-loop). ## Credit impact Streaming a turn does not add cost on top of the turn itself. The turn is charged once when it starts, and language-model token usage is recorded separately as the turn runs — these are the same charges described in [permissions and limits](/assistant/permissions-and-limits) and [credits and metering](/billing/credits). Opening the `listen` stream, receiving frames, reconnecting within the history window, and cancelling do not incur additional run charges. A turn that is cancelled mid-flight still records the language-model tokens it consumed before cancellation. ### When the stream returns a billing denial A turn is admitted by the billing gate **before** it starts, on `POST /assistant/chat` — not on the `listen` call. If your organization's allowance is exhausted (or a rate or quota limit is hit), that POST is rejected before any run is created, so you never get a `stream_url` to open. The denial uses the flat `DenialEnvelope`: ```json { "code": "credit_plan_exhausted", "layer": "credit", "key": null, "current": null, "limit": null, "reason": "credit_plan_exhausted" } ``` The `layer` maps to the HTTP status: `rate` is `429` (with `Retry-After` and `X-RateLimit-*` headers), `quota` is `403`, and `credit` or `wallet` is `402`. This is distinct from the ordinary `{"detail": "…"}` shape used by validation and ownership errors — the Assistant endpoints can emit either. For the complete error catalog and all three envelope shapes, see [errors and status codes](/api-reference/errors) and [usage gating and limits](/billing/usage-gating). ## Next steps Answer a paused turn: the question and response kinds, the resume call, and the new-run_id contract. How the Assistant discovers, calls, and reports on integration tools — the `tool_call` and `tool_result` frames in context. The transport-level reference for ModuleX run streaming, including workflow runs and the full event taxonomy. Consume streams and answer pause-for-input prompts in the JavaScript and Python SDKs. Who can use the Assistant and the billing and usage limits that apply to each turn. The agentic loop behind the frames: how the Assistant reasons, acts, and decides when it is done. --- # How the Assistant uses tools URL: https://docs.modulex.dev/assistant/using-tools How the ModuleX Assistant discovers integrations, calls tool actions with your organization's credentials, requests missing credentials over human-in-the-loop, and what each tool costs in credits — BYOK vs managed. The Assistant is the workflow-independent agentic chat: it searches your connected integrations, picks an action, and runs it with your organization's credentials — no workflow graph required. This page is the technical reference for the **tool side** of that loop: the tools the Assistant has, the parameters and outputs of each, the approval behavior, how it asks for a missing credential over human-in-the-loop, and the credit impact of every call. For the surrounding agentic loop (reason → act → observe → finish), see [How the Assistant works](/assistant/how-it-works). For pause-and-resume mechanics shared by every interrupt the Assistant raises, see [Human-in-the-loop](/assistant/human-in-the-loop). To connect the integrations the Assistant calls, start at [Integrations overview](/integrations/overview). The Assistant runs the `assistant` profile of the shared agent core. It has **no workflow tools at all** — it cannot list, build, run, or edit a [workflow](/concepts/workflows-and-runs). Every tool below is scoped to discovering integrations, calling integration actions, retrieving [knowledge](/concepts/knowledge-rag), and asking you questions. ## The Assistant's toolset The Assistant is handed a fixed set of tools at run start. There are three discovery tools, one execution tool, one knowledge tool, and the human-in-the-loop tools (one of which is `request_credential`). | Tool | Purpose | Pauses the run? | | --- | --- | :-: | | `get_available_integrations` | Browse the compact catalog of tools, LLM providers, and knowledge providers. | No | | `get_integration_details` | Fetch the full action descriptions and parameter schemas for one integration. | No | | `get_organization_credentials` | List which credentials the organization already has (no secrets). | No | | `execute_integration_tool` | Run **one** integration action with the organization's credentials. | Only for destructive actions | | `search_knowledge` | Retrieve from a connected [knowledge base](/concepts/knowledge-rag). | No | | `request_credential` | Ask you to connect an integration that has no credential yet. | Yes (HITL) | | `ask_user_choice` / `ask_user_yes_no` / `ask_user_free_text` / `ask_user_multi_choice` | Ask you a structured question. | Yes (HITL) | The discovery tools and execution tool together form a deliberate two-step pattern: the Assistant first narrows the catalog, then pulls the exact parameter schema for the action it chose, then executes. The sections below document each in turn. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-3240 · [IMAGE]** **[IMAGE_DESCRIPTION]:** A flow diagram of the Assistant's discover → detail → execute tool sequence with the credential branch. **[IMAGE_DETAILS]:** Left-to-right flow. Step 1 box `get_available_integrations` (compact catalog). Step 2 box `get_integration_details` (full parameter schema). Step 3 box `execute_integration_tool`. From step 3, a branch: "credential exists?" — yes goes to "run the action", no goes to a `request_credential` box that loops back to step 3 after the user connects. Annotate that destructive actions pause for a yes/no approval before "run the action". Light theme, 16:9, clean flowchart, no UI chrome. ## Tool discovery ### Browse the catalog — `get_available_integrations` The first discovery step returns a **compact** index of everything the organization could connect, so the full portfolio fits in the agent's context without bloating it. For tools you get the integration name, a short description, categories, and **action names only** — no per-action descriptions or parameter schemas at this step. One of `tools`, `llm_providers`, `knowledge_providers`, or `all`. Filters which catalog segment is returned. Optional case-insensitive substring filter. It matches name, display name, description, category, and action name. A narrow query (for example `query="slack"`) keeps the response small **without** risking that a relevant integration is dropped — filtering is exact-substring, not lossy truncation. The response groups results by segment. Tool entries carry action names only; you must call `get_integration_details` for the parameter schema before you can configure a call. Catalog integration id, for example `github`. Human-readable name, for example `GitHub`. Short description (truncated to 100 characters). Catalog categories. Action **names** only — descriptions and parameters come from `get_integration_details`. Each entry adds a `models` list of `{id, display_name, max_output_tokens}`. See [LLM providers](/integrations/llm-providers/overview). Each entry carries `name`, `display_name`, `description`, and `categories`. See [Knowledge providers](/integrations/knowledge-providers/overview). ### Read an integration's actions — `get_integration_details` The second discovery step. Call it after choosing an integration to get the parameter schemas you need to actually configure an action. The integration name, for example `tavily`, `firecrawl`, or `github`. Optional. Restrict the response to a single action to keep it small. An unknown action returns `{error: "Action '' not found in "}`. For a tool integration, each action returns its `name`, a `description` (truncated to 300 characters), and a `parameters` array. Each parameter carries `name`, `type`, `required`, `description`, and — when defined in the manifest — `enum` and `default`: ```json Example: get_integration_details("github", "create_issue") { "name": "github", "integration_type": "tool", "actions": [ { "name": "create_issue", "description": "Create a new issue in a repository.", "parameters": [ { "name": "owner", "type": "string", "required": true, "description": "Repository owner" }, { "name": "repo", "type": "string", "required": true, "description": "Repository name" }, { "name": "title", "type": "string", "required": true, "description": "Issue title" }, { "name": "body", "type": "string", "required": false, "description": "Issue body (markdown)" }, { "name": "labels", "type": "array", "required": false, "description": "Label names" } ] } ] } ``` An unknown integration returns `{error: "Integration '' not found"}`. The parameter `type` is one of `string`, `integer`, `number`, `boolean`, `array`, or `object` — the same types the [manifest schema](/integrations/building/manifest-schema) defines. ### See existing credentials — `get_organization_credentials` The Assistant uses this to decide whether it already has a way to authenticate before it calls an action. It returns only **safe** fields — never a token, key, or secret. Optional. Return only credentials for this integration (for example `slack`). Pass it when the Assistant already knows which integration it needs so the response stays small. The credential's id (UUID). The integration this credential is for. The credential's display name. One of `oauth2`, `api_key`, `bearer_token`, `modulex_key`, `custom`. See [Authentication & credentials](/integrations/authentication). Whether this is the default credential for its integration. Whether the credential is currently valid. ## Calling a tool ### `execute_integration_tool` This is the only tool that performs a real action. The Assistant calls it with the integration id and action name it discovered, plus the action parameters. It runs exactly **one** action per call. Catalog integration id, for example `github`. The action/service name, for example `list_repositories`. The action parameters, matching the schema from `get_integration_details`. **Never** include credentials or tokens — the runtime injects them automatically from the organization's credential. Credential fields such as `api_key`, `token`, `access_token`, `bearer_token`, `auth_type`, and `auth_data` are stripped from the schema the Assistant sees, so it cannot supply them even if it tries. Internally, the call resolves a credential, decrypts it, refreshes an OAuth token if it is within five minutes of expiry, maps the parameters, invokes the underlying action, and returns the raw action output. Credential selection follows a fixed precedence: an explicit credential, then the integration's default credential, then the most recent valid credential — preferring **your own** credentials over managed ones. See [Credentials & OAuth2](/concepts/credentials-oauth) for the full resolution rules. #### Return shape `execute_integration_tool` always returns a dict with a `status` field. It does **not** raise to the agent — every failure is converted to a structured status the Assistant can react to mid-stream. One of `ok`, `cancelled`, `needs_credential`, `credit_limit`, or `error`. Echoes the requested integration. Echoes the requested action. Present when `status` is `ok`. The raw action output (a dict, list, or string), truncated to 8000 characters before it re-enters the agent loop. The credential and execution envelope around the action are stripped — the Assistant sees only the bare action result. Present for `needs_credential` and `credit_limit` — a human-readable explanation the Assistant can act on (for example, "call request_credential, then retry"). Present for `cancelled` (the user declined an approval) and `credit_limit` (the stable token `credit_exhausted`). Present when `status` is `error` — the failure text, truncated to 500 characters. ```json status: ok { "status": "ok", "integration_name": "github", "tool_name": "list_repositories", "result": { "repositories": [{ "name": "hello-world", "private": false }] } } ``` The action ran and returned a result. A destructive action was gated for approval and you declined. No credential exists for the integration in this organization. The Assistant should call `request_credential`, then retry. A managed (`modulex_key`) credential hit the plan's monthly credit ceiling with no wallet overage available. `reason` is `credit_exhausted`. The action raised, the action name was not found, or any other unexpected failure. ### The approval gate — reads run, destructive actions pause The Assistant applies a **safety** gate (not an authorization gate — your org membership already authorizes the call) before it executes. The policy differs from the [AI Composer](/concepts/ai-composer) in the builder, and the difference matters: On the **Assistant surface**, only **catastrophic** actions pause for approval. Actions whose names contain `delete_`, `_delete`, `drop_`, `truncate`, `purge`, `destroy`, `wipe`, or raw query execution (`execute_query`, `execute_statement`, `execute_raw_query`, `raw_query`) always require your explicit yes/no. **Everything else — including ordinary creates and sends, like posting a Slack message or creating a Linear issue — runs immediately.** The Assistant is a "just do what I asked" surface; asking before every write is impractical. The Composer in the builder is stricter: it default-denies every write or unknown action against a frozen read-action allowlist. When a destructive action is gated, the Assistant raises a yes/no [human-in-the-loop](/assistant/human-in-the-loop) interrupt with a redacted preview of the parameters (secret-looking keys are masked). If you answer no, the call returns `status: cancelled` and the action never runs. ## Credential requests over human-in-the-loop When `execute_integration_tool` returns `needs_credential`, the Assistant calls `request_credential` to ask you to connect the integration. This raises a credential [interrupt](/assistant/human-in-the-loop): the run pauses, the app renders a connect popup, and the Assistant resumes once you finish. ### `request_credential` Canonical integration slug, for example `slack` or `google_drive`. Used to resolve OAuth and credential storage. Human-readable name shown in the popup header, for example `Slack`. One entry per auth type you can choose. At least one is required. Each matches the `CredentialAuthOption` schema below. Optional URL to the integration's logo for the popup. Optional. Reserved for the workflow context and not used by the Assistant (Assistant chats are never workflow-bound). When `false` (the default for the Assistant), the popup shows an inline "Skip for now" button so you can defer connecting. Set `true` only when the task truly cannot proceed without the credential. Optional rendering hints for the app. Each entry in `auth_options` is a `CredentialAuthOption`: One of `oauth2`, `api_key`, `bearer_token`, `modulex_key`, `custom`. The button label inside the popup. Form-field definitions for form-based auth types such as `api_key`. The full URL the app opens to start the OAuth flow. **Required when `auth_type` is `oauth2`** — an OAuth option without it is rejected before the interrupt fires. Optional human-readable setup steps shown beside the form or OAuth button. When `true`, the new credential is round-trip tested before the Assistant resumes, so an invalid credential surfaces as a structured failure instead of sending the Assistant down a broken path. ### How you respond, and how the run resumes You answer a credential request in one of three ways, and the Assistant resumes with that result baked into the tool response: You complete the OAuth popup or fill the form. The credential is persisted and the run resumes with `kind: "credential_added"` carrying the new `credential_id`, `integration_name`, and `auth_type`. The Assistant retries the original action. The run resumes with `kind: "credential_failed"` carrying an `error_code` — one of `oauth_denied`, `oauth_provider_error`, `invalid_credentials`, `network_error`, `popup_closed`, `timeout`, or `unknown` — plus a `retryable` flag. The Assistant can try a different auth option or report the failure. When the request was not `required`, the inline "Skip for now" button resumes the run with `kind: "skipped"`. The Assistant proceeds without the credential. **OAuth auto-resume.** When you complete an OAuth flow that the Assistant opened, the OAuth callback resumes the chat automatically — you do **not** make a separate resume call. The callback re-runs the same ownership checks and continues the run, swapping your live stream to the new run id. See [Human-in-the-loop](/assistant/human-in-the-loop) for the resume contract and stream handover. **Reconnect, do not rely on token refresh.** The manual OAuth token-refresh path (`POST /credentials/{credential_id}/oauth2/refresh`, and the equivalent UI `refreshOAuth2` action) is currently **broken** and must not be treated as a working flow. ModuleX still refreshes OAuth tokens **automatically** during tool execution when a token is within five minutes of expiry. If a credential has fully expired or its refresh token is no longer valid, **reconnect the integration** — run the connect flow again to mint a fresh credential — rather than calling the manual refresh. This is tracked in [Known limitations](/reference/known-limitations). ## BYOK vs managed credentials Every integration call resolves to one of two kinds of credential, and the kind decides **who you pay and whether the call counts against your credits**. You connect your own provider account (OAuth, API key, or bearer token). Usage is billed **directly by the provider** with no ModuleX markup. BYOK tool calls are **not** credit-limited — they are tracked for analytics only, never gated on credits. ModuleX provides a pooled, system-managed key (`auth_type: modulex_key`) so you can call a tool without bringing your own account. Managed usage is **billed in credits** and is subject to your plan's monthly [credit](/billing/credits) ceiling. On the wire the managed provider is `modulexai` for tools and LLMs, and `modulexdb` for [managed knowledge](/platform/knowledge/managed). When both exist, resolution prefers your **own** credential over the managed key. So if you connect your own GitHub account, the Assistant uses it (no credits), and only falls back to a managed key when you have not connected your own. ### Credit impact of a tool call A managed tool call is metered with a per-action soft cost: | Quantity | Value | Notes | | --- | --- | --- | | Tool base cost | **1 credit** | Anchored to $0.01 (100 credits = $1). | | Effective cost | `1 × multiplier` | The integration's credit multiplier, default `1.0`. | | BYOK tool cost | **0 credits** | BYOK is tracked, never charged or gated. | The credit check runs only for managed (`modulex_key`) credentials: if the plan's monthly budget is exhausted and no wallet overage is available, the call returns `status: credit_limit` with `reason: credit_exhausted`. Plans with no monthly ceiling (for example Enterprise) are never blocked here. See [Credits & metering](/billing/credits) and [Usage gating & limits](/billing/usage-gating) for the full model. The Assistant **turn itself** is also metered separately from the tool. Each user message charges one run credit per turn and is admitted through the billing gate before any work starts. That gate, and the LLM token usage it records, are documented in [Permissions & limits](/assistant/permissions-and-limits) and [Usage gating & limits](/billing/usage-gating). ## Errors and denials The Assistant's tools surface failures in two distinct places — and they look different. **Tool-level results.** `execute_integration_tool` never raises to the agent; it returns a `status`. Treat `needs_credential`, `credit_limit`, `cancelled`, and `error` as normal control flow the Assistant handles itself. **Turn-level denials.** The Assistant endpoint that starts a turn is on the billing gate. A denial there is **not** the `{detail}` shape — it is the flat `DenialEnvelope`: ```json DenialEnvelope (HTTP 402 / 403 / 429) { "code": "credit_plan_exhausted", "layer": "credit", "key": null, "current": null, "limit": null, "reason": "credit_plan_exhausted" } ``` The `layer` maps to a status: `rate` → **429** (with `Retry-After` and `X-RateLimit-*` headers), `quota` → **403**, and `credit` or `wallet` → **402**. This is one of the [four error envelopes](/api-reference/errors) ModuleX returns, and it is emitted on the run, Composer, Assistant, and managed-knowledge surfaces only — plain CRUD routes use the `{detail}` shape. See [Errors & status codes](/api-reference/errors) and [Rate limiting](/api-reference/rate-limiting). ## A worked example: list, then create This shows the full sequence the Assistant runs to satisfy "create a GitHub issue titled 'Bug' in octocat/hello-world", calling the API directly. Authenticate every request with `Authorization: Bearer mx_live_…` and `X-Organization-ID` — see [Authentication](/api-reference/authentication). Owner or admin role is required for the Assistant; the retired `member` role cannot use it (see [Roles & permissions](/security/roles-permissions)). Post the user message. The response returns the chat id, the run id, and the SSE `stream_url` to open for live events. The operation is shown once below as cURL, Python, and JavaScript. ```bash cURL curl -X POST https://api.modulex.dev/assistant/chat \ -H "Authorization: Bearer mx_live_8f3a2b1c9d0e4f5a6b7c8d9e0f1a2b3c" \ -H "X-Organization-ID: a1b2c3d4-e29b-41d4-a716-446655440000" \ -H "Content-Type: application/json" \ -d '{ "message": "Create a GitHub issue titled '\''Bug'\'' in octocat/hello-world", "llm": { "integration_name": "openai", "provider_id": "openai", "model_id": "gpt-4o" } }' ``` ```python Python import httpx resp = httpx.post( "https://api.modulex.dev/assistant/chat", headers={ "Authorization": "Bearer mx_live_8f3a2b1c9d0e4f5a6b7c8d9e0f1a2b3c", "X-Organization-ID": "a1b2c3d4-e29b-41d4-a716-446655440000", }, json={ "message": "Create a GitHub issue titled 'Bug' in octocat/hello-world", "llm": {"integration_name": "openai", "provider_id": "openai", "model_id": "gpt-4o"}, }, ) start = resp.json() # {"status": "running", "chat_id": ..., "run_id": ..., "stream_url": ...} ``` ```javascript JavaScript const resp = await fetch("https://api.modulex.dev/assistant/chat", { method: "POST", headers: { Authorization: "Bearer mx_live_8f3a2b1c9d0e4f5a6b7c8d9e0f1a2b3c", "X-Organization-ID": "a1b2c3d4-e29b-41d4-a716-446655440000", "Content-Type": "application/json", }, body: JSON.stringify({ message: "Create a GitHub issue titled 'Bug' in octocat/hello-world", llm: { integration_name: "openai", provider_id: "openai", model_id: "gpt-4o" }, }), }); const start = await resp.json(); // { status: "running", chat_id, run_id, stream_url } ``` Over the stream you see the Assistant call `get_available_integrations` (with `query="github"`) and then `get_integration_details("github", "create_issue")` to read the parameter schema. These are read-only and never pause. The Assistant calls `execute_integration_tool` with the action and parameters. Because `create_issue` is not destructive, it runs immediately on the Assistant surface — no approval prompt. The tool result arrives in the stream: ```json tool_result { "status": "ok", "integration_name": "github", "tool_name": "create_issue", "result": { "success": true, "issue": { "number": 42, "title": "Bug", "state": "open" } } } ``` Had no GitHub credential existed, the result would instead be `{"status": "needs_credential", ...}`, and the Assistant would call `request_credential` and pause — see the resume steps under [Human-in-the-loop](/assistant/human-in-the-loop). **🎬 MEDIA PLACEHOLDER · MX-MEDIA-3241 · [APP_VIDEO]** **[APP_VIDEO_DESCRIPTION]:** A short screen recording of the Assistant connecting an integration mid-task via a credential request. **[APP_VIDEO_DETAILS]:** Capture in the live ModuleX app, Assistant chat. User asks the Assistant to do something that needs an unconnected integration (e.g. "send a message in Slack"). Show the Assistant pausing and rendering the credential connect popup with the OAuth button and the inline "Skip for now" button. The user clicks connect, completes OAuth in the popup, and the Assistant auto-resumes and completes the action — all without the user typing again. 20-30s, dark theme, highlight the popup and the auto-resume. No audio needed. ## Related The pause-and-resume contract behind credential requests and approvals. Connect the services the Assistant calls and manage their credentials. The agentic loop that decides when to discover, call a tool, and finish. Who can use the Assistant and the billing and usage limits that apply. --- # Credits & metering URL: https://docs.modulex.dev/billing/credits Exactly how ModuleX meters managed usage in credits: the 1000-credit-per-dollar unit, the per-operation cost table (run, retrieval, ingest, tool, LLM tokens), the reserve to charge to settle lifecycle, and why BYOK usage is never credited. A **credit** is the unit ModuleX uses to meter **managed** usage — anything that runs through ModuleX-provisioned model, tool, or knowledge infrastructure. This page is the exact reference for what a credit is worth, what every operation costs, how a charge moves through the **reserve → charge → settle** lifecycle, and why **bring-your-own-key (BYOK)** usage is never credited. Credits only cover the metering side of billing. For the admission gate that turns an empty allowance into a `402` / `403` / `429`, see [Usage gating & limits](/billing/usage-gating); for the prepaid balance that funds spend beyond your plan, see [Wallet & top-ups](/billing/wallet); for the per-plan allowances, see [Plans & pricing](/billing/plans). ## The credit unit The whole system is anchored to one constant: **100 credits = $1.00**, so **1 credit = $0.01**. This is the same value the app surfaces in its credit tooltips and the same value the wallet uses to convert dollars to credits. Credits per US dollar. `100` credits = `$1.00`. Dollars per credit, derived as `1 / SCALE`. Used to convert a wallet top-up amount into credits and to value credit usage in dollars. Each organization receives a monthly **credit allowance** from its [plan](/billing/plans). Because the unit is fixed at $0.01, each plan's monthly allowance has a clear dollar value: Pro's 5,000 credits is $50.00 of managed usage, and Max's 20,000 credits is $200.00. The Free plan grants 300 credits as a one-time trial allowance rather than a monthly grant. **Two numbers carry the FREE allowance, and they are not the same layer.** The organization-level FREE allowance resolves to **1,500** credits, while the per-user free credit pool stored in the database defaults its `cap` column to **500**. These are different layers (an org grant versus a per-user pool cap), not a single contradicted value. If you are reconciling a FREE account's balance, check which layer you are reading. ## What consumes credits Only **managed** usage is metered. An operation is managed when it runs through ModuleX's own provider pool — that is, when its integration name is `modulexai` (managed [LLM and tool](/integrations/llm-providers/modulexai) calls) / `modulexdb` ([managed knowledge](/integrations/knowledge-providers/modulexdb)), or when the API key source is the platform environment. Everything else — your own provider keys — is BYOK and is **not** credited (see [BYOK is never credited](#byok-is-never-credited)). There are two cost families: **flat per-operation costs**, and **token-metered LLM costs**. ### Flat per-operation costs These operations cost a fixed number of credits regardless of size. | Operation | `usage_type` | Cost (credits) | When it is charged | | --- | --- | :-: | --- | | Workflow / agent run | `run` | **1** | Once per logical run or agent turn. A [resumed](/realtime/hitl) turn is not charged again. | | Knowledge retrieval | `retrieval` | **1** | Per managed-knowledge search or retrieve call. | | Document ingest | `file_ingest` | **1** | Per ingested document. Idempotent on the document ID, so a retried ingest of the same document is not double-charged. | | Integration tool call | (tool) | **1 × multiplier** | Per managed [tool](/workflow-builder/nodes/tool) action. The base of `1` credit is anchored to $0.01; the per-pool `multiplier` defaults to `1.0`. | Credits charged once per logical run or agent turn, written as one `usage_type="run"` row. Credits per managed-knowledge search or retrieval call (`usage_type="retrieval"`). Credits per ingested document (`usage_type="file_ingest"`), idempotent on the document ID. Base credits for a managed integration tool call, equal to `SCALE / 100` (anchored to $0.01). The final tool cost is `TOOL_BASE × multiplier`, where `multiplier` comes from the managed key pool and defaults to `1.0`. A managed run that also retrieves knowledge and calls a tool accrues **separate** charges: one `run` credit, one `retrieval` credit per search, and the tool cost — plus any LLM token cost (below). Costs are additive across operation types within a single run. ### Token-metered LLM cost Managed LLM and embedding calls are metered on **tokens**, not a flat fee. The cost is computed from the model's per-million-token input and output rates, plus a fixed system margin, then converted to credits: ```text Credit cost formula cost = ( prompt_tokens × input_rate / 1_000_000 + completion_tokens × output_rate / 1_000_000 ) × MARGIN × SCALE ``` The system margin applied to every token-metered cost — a flat +5%. The result is quantized to a fixed-precision decimal (`NUMERIC(20,8)`) before it is recorded. A few rules to be aware of: - **Embeddings are input-only.** Embedding calls force `completion_tokens = 0`, so you pay only for the input tokens. - **Rates come from the model pricing table.** Per-token rates are resolved by the model's identifier; if a specific model is not priced, ModuleX falls back to a `__default__` rate, and if that is also absent the rate is treated as **0** — never a silent `1.0`. An unpriced model therefore costs nothing rather than overcharging. - **The margin is fixed.** `MARGIN = 1.05` is applied uniformly; there is no per-model margin override in the cost path. - **BYOK LLM calls are not metered here.** If the call uses your own provider key, no credit cost is computed — the provider bills you directly. **Why an unpriced model can read as free.** Token metering deliberately fails to `0` rather than to a default multiplier, so a newly added model with no pricing row will not be overcharged. This is the intended conservative behavior; do not rely on it as a pricing strategy. ## The reserve → charge → settle lifecycle Every managed operation passes through a three-stage credit lifecycle. The gate runs **before** any database write so a denied operation never creates partial state, and Postgres — not Redis — is the source of truth for what you have actually spent. Before the operation does any work, the admission gate resolves your plan's entitlement bundle and **hard-reserves** the credits the operation will need. The reservation is an atomic Redis operation guarded by a Postgres-sum fallback if Redis is unavailable. If the reservation fails — no allowance, no wallet overage, a suspended subscription, or a rate limit — the gate raises a denial and **nothing is written**. This is where a `402` / `403` / `429` [`DenialEnvelope`](/api-reference/errors) comes from. When the operation completes, ModuleX writes **one** usage row recording the `usage_type` and `credit_cost`, then settles the Redis reservation from *reserved* to *used*. The charge is **idempotent** on an idempotency key, so the same run or turn is never charged twice — including across a resume. If the operation errors or is cancelled before it is charged, the gate **releases** the never-costed reservation so the held budget self-heals immediately rather than waiting for the reservation's time-to-live to expire. **A resumed turn is not re-charged.** When a paused run or agent turn resumes, the charge step is a no-op for the already-charged turn, and admin-scope usage is never charged at all. The idempotency key — derived from the chat and run identifiers for an agent turn, or the document and token signature for an ingest — guarantees one charge per logical unit of work. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-1260 · [IMAGE]** **[IMAGE_DESCRIPTION]:** A horizontal lifecycle diagram of a single managed operation moving through reserve, charge, and release. **[IMAGE_DETAILS]:** Three stages left to right. Stage 1 "Reserve" — before any DB write, the admission gate resolves the entitlement bundle and hard-reserves credits in Redis (with a Postgres-sum fallback); a side branch labeled "denied" exits to a red "402 / 403 / 429 DenialEnvelope — nothing written" box. Stage 2 "Charge" — on success, write one usage row (usage_type, credit_cost), idempotent on the idempotency key, and settle the Redis reservation reserved to used. Stage 3 "Release" — on early error or cancel, release the un-costed reservation so held budget self-heals. Annotate "Postgres is the source of truth; Redis is advisory." Light theme, 16:9, clean flowchart, no UI chrome. ### What a usage row records Each charge produces one organization credit-usage row. The fields you will see when you inspect usage are: The kind of charge: `run`, `retrieval`, `file_ingest`, or an LLM/embedding token charge. Tool calls are recorded through the credential-usage path. The credits charged for this operation. `1` for `run`, `retrieval`, and `file_ingest`; the computed value for token-metered calls; `TOOL_BASE × multiplier` for tools. The dedup key enforced by a partial-unique constraint. A duplicate key is swallowed rather than charged again, which is how resumes and retried ingests avoid double charges. The monthly billing-bucket ordinal that partitions paid usage per period. Computed as `year × 12 + (month − 1)`, with the boundary on your subscription's anchor day. FREE usage uses `0`. ## How charges draw down your balance Charges are drawn from your allowances in a fixed order: plan allowance first, then the wallet. Each managed charge first draws from your plan's monthly credit allowance for the current `month_offset` bucket. While the allowance covers the charge, the reservation resolves against the plan and nothing touches the wallet. When the plan allowance for the bucket is exhausted, a **paid** organization with overage enabled spends down its prepaid [wallet](/billing/wallet) at the same $0.01 per credit. Overage is off by default and can only be enabled on a paid plan. If overage is disabled or the wallet balance is insufficient, the gate denies the operation with a `wallet` -layer `402`. **Free organizations have no overage path.** The wallet overage cascade is paid-plan only. On the Free plan, once the one-time allowance and the per-user free pool are spent — or once the account ages past its free-tier window — managed operations are denied with a `credit` -layer `402`. There is no wallet to fall back to. ## Denials raised by the gate When the reserve step fails, the gate raises a flat [`DenialEnvelope`](/api-reference/errors) — `{code, layer, key, current, limit, reason}` — with an HTTP status derived from the layer. These are the credit-relevant denials you will see while metering managed usage: | `code` | `layer` | HTTP | Meaning | | --- | --- | :-: | --- | | `credit_plan_exhausted` | `credit` | 402 | Plan allowance is used up and no wallet overage is available (or the subscription is suspended). | | `wallet_overage_disabled` | `wallet` | 402 | Allowance exhausted and overage is turned off for the organization. | | `wallet_insufficient` | `wallet` | 402 | Overage is on, but the prepaid wallet balance is too low. | | `quota_exceeded` | `quota` | 403 | A countable entitlement (for example, the number of knowledge bases) is at its limit. | | `rate_limit_exceeded` | `rate` | 429 | A per-period run/usage rate limit was reached. Carries `Retry-After` and `X-RateLimit-*` headers. | The flat `DenialEnvelope` is **only** raised on the gated surfaces — workflow runs, [Composer](/concepts/ai-composer), [Assistant](/concepts/assistant), and managed-knowledge operations. The wallet management routes return a *different* `402` shape (`{"detail": {"reason": "paid_subscription_required"}}`), and plain CRUD routes never emit a `DenialEnvelope` at all. See [Errors & status codes](/api-reference/errors) for all four envelope shapes and [Usage gating & limits](/billing/usage-gating) for the gate itself. Example `402` body for an exhausted plan allowance: ```json 402 — credit_plan_exhausted { "code": "credit_plan_exhausted", "layer": "credit", "key": "sync_exec", "current": null, "limit": 5000.0, "reason": "credit_plan_exhausted" } ``` ## BYOK is never credited If you connect your own model, tool, or vector-store provider with your own API key, that usage is **bring-your-own-key (BYOK)**. ModuleX classifies it as analytics-only: it is logged for usage visibility but **never** charged in credits, and the upstream provider bills you directly. Runs through ModuleX's provider pool (`modulexai` / `modulexdb`, or a platform key). Metered in credits using the costs above. Subject to the [usage gate](/billing/usage-gating). Runs through your own provider key. Recorded for analytics only, **not** charged in credits. Billed by your provider, with no ModuleX markup. **BYOK turns off credit metering, not the rest of the gate.** Because no credit cost is computed for a BYOK call, it does not draw down your allowance or wallet — but countable entitlements (such as knowledge-base quotas) and rate limits still apply, and those denials are still raised as a `quota` (403) or `rate` (429) `DenialEnvelope`. ## Inspecting credit usage There is no dedicated REST endpoint or SDK method for *reserving* or *charging* credits — that happens server-side inside the gate. You observe credit usage through the dashboard analytics surface, which both SDKs expose. The example below reads the analytics overview, which includes the current month's credit usage. Authentication uses `Authorization: Bearer mx_live_…` plus the [`X-Organization-ID`](/api-reference/authentication) header. ```bash cURL curl https://api.modulex.dev/dashboard/analytics/overview \ -H "Authorization: Bearer mx_live_8Kd2pQ7nR4xWvL0eYbT1" \ -H "X-Organization-ID: org_9a2f7c41" ``` ```python Python from modulex import Modulex async with Modulex( api_key="mx_live_8Kd2pQ7nR4xWvL0eYbT1", organization_id="org_9a2f7c41", ) as client: overview = await client.dashboard.analytics_overview() print(overview) ``` ```javascript JavaScript import { Modulex } from 'modulex-js'; const client = new Modulex({ apiKey: 'mx_live_8Kd2pQ7nR4xWvL0eYbT1', organizationId: 'org_9a2f7c41', }); const overview = await client.dashboard.analyticsOverview(); console.log(overview); ``` **Dashboard analytics return HTTP 200 even on failure.** All dashboard routes return `200` with a `success: false` envelope when something goes wrong, instead of an error status, so the SDK error path does not fire. Branch on the `success` field in the response rather than relying on a thrown error. See the [SDK ⇄ API parity matrix](/sdks/parity) for this behavior. **Wallet top-ups and subscription changes are managed elsewhere.** Funding the wallet, toggling overage, and changing plans are part of the subscription surface, and the `subscriptions` resource exists **only in the Python SDK** — the JavaScript SDK has no subscriptions methods, and the wallet/top-up routes have no SDK method in either SDK. See [Wallet & top-ups](/billing/wallet) and [Subscriptions & Stripe](/billing/subscription-lifecycle). ## Related pages The admission gate that reserves credits and raises the 402 / 403 / 429 denial. The prepaid balance that funds managed usage beyond your plan allowance. The per-plan monthly credit allowances, rate limits, and quotas. The flat DenialEnvelope and the other error-envelope shapes. Where retrieval and ingest credits are spent. Canonical definitions for credit, wallet, overage, and the gate terms. --- # Billing & credits overview URL: https://docs.modulex.dev/billing/overview How ModuleX bills in one picture: your plan gives a monthly credit allowance, credits meter managed usage, a prepaid wallet covers overage, and a usage gate enforces it all. Managed usage costs credits; bring-your-own-key usage does not. ModuleX billing fits into four ideas. Your **plan** gives your [organization](/concepts/organizations-roles) a monthly **credit** allowance. Credits meter the usage ModuleX runs for you. When the allowance runs out, a prepaid **wallet** can cover the difference. And a **usage gate** checks all of this before each piece of managed work begins. This page gives you the whole picture at a glance. Each part links to a page with the full detail when you need it. ## The four parts of billing Free, Pro, Max, and Enterprise. Your plan sets your monthly credit allowance and your limits. The unit ModuleX bills in. Credits meter managed runs, tool calls, model usage, and knowledge retrieval. A prepaid balance that covers usage beyond your plan allowance, with manual top-ups and auto top-up. The check that runs before each piece of managed work, so you are never billed for work that could not be admitted. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-1240 · [IMAGE]** **[IMAGE_DESCRIPTION]:** A single diagram showing how a plan, credits, the wallet, and the usage gate fit together for one organization. **[IMAGE_DETAILS]:** One illustration (not a screenshot), 16:9, light theme, ModuleX brand palette. Draw a left-to-right flow. Start with a box labeled "Plan" feeding a "Monthly credit allowance" meter. Below the meter, a separate box labeled "Wallet (prepaid balance)" with an arrow that joins in only when the allowance is empty. In the center, a gate icon labeled "Usage gate — checks before each managed action". On the right, three small chips labeled "Run", "Tool call", "Knowledge retrieval" that pass through the gate and decrement the meter. Keep wording inside shapes minimal; the goal is to show plan → allowance → (then) wallet, with the gate in front of everything. ## How it works in one paragraph Managed usage is priced in **credits**, where **100 credits cost $1.00** (one credit is $0.01). Each [plan](/billing/plans) gives your organization a monthly credit allowance. As you use ModuleX, that allowance is drawn down. If you are on a paid plan and you turn on overage, work that goes beyond the allowance is paid for from your prepaid [wallet](/billing/wallet). Before any managed run, tool call, model call, or knowledge retrieval starts, the [usage gate](/billing/usage-gating) confirms you have budget — so you are never charged for work that was not admitted. ## Credits at a glance A **credit** is the single unit ModuleX bills in for managed usage. It maps to money at a fixed rate, so a credit balance is easy to reason about. Credits convert to dollars at a fixed rate of $0.01 per credit, the same rate for your plan allowance and your wallet. A single workflow run or one Assistant turn costs one credit, separate from any model or tool usage inside it. Model usage is metered by tokens, tool calls have a base cost, and each knowledge retrieval or document ingest costs a credit. For exactly what consumes credits and how token usage is converted, see [Credits & metering](/billing/credits). ## Plans and your allowance Your plan decides your monthly credit allowance and your limits. The credit allowances below are the values ModuleX runs against. | Plan | Monthly credit allowance | Best for | | --- | --- | --- | | **Free** | 300 credits (one-time, to get started) | Trying ModuleX on your own | | **Pro** | 5,000 credits per month | Individuals and small teams | | **Max** | 20,000 credits per month | Heavy or team-wide usage | | **Enterprise** | Custom | Organizations that need scale, SSO, and self-hosting | At the $0.01-per-credit rate, Pro's 5,000 credits is worth $50 of managed usage and Max's 20,000 credits is worth $200. The Free allowance is a one-time grant to get you started, not a recurring monthly reset. ModuleX's marketing site and its backend pricing configuration currently list **two different annual prices** for Pro and Max, and which one is authoritative is being confirmed. Always check the price shown at checkout before you subscribe to an annual plan. The credit allowances above are not affected by this — they are the same everywhere. See [Plans & pricing](/billing/plans) for the full comparison and both annual figures. ## Managed vs bring-your-own-key (BYOK) ModuleX gives you two ways to use models, tools, and knowledge. The difference is who runs the work and who bills for it. ModuleX provisions the model, tool, or vector store for you. This usage is **metered in credits** against your plan allowance and wallet. It is the default when you do not connect your own provider. You connect your own provider account. That usage is billed directly by the provider with **no ModuleX credits charged** — ModuleX records it for analytics only. A simple way to remember it: **if ModuleX runs it, it costs credits; if your own key runs it, it does not.** You can mix the two — for example, use a ModuleX-managed model in one workflow and your own [OpenAI](/integrations/llm-providers/openai) or [Anthropic](/integrations/llm-providers/anthropic) key in another. Connecting your own providers is covered in [LLM providers](/integrations/llm-providers/overview) and [Knowledge providers](/integrations/knowledge-providers/overview). ## The wallet: covering work beyond your allowance When your monthly allowance is used up, a paid organization can keep working by spending down a prepaid **wallet**. On a paid plan, an owner or admin enables overage for the organization. Until it is on, work stops once the allowance is empty rather than spending the wallet. Add a prepaid balance. A $10 top-up adds 1,000 credits, at the same $0.01-per-credit rate as your plan. Auto top-up can refill the wallet automatically when the balance gets low, so usage is not interrupted. Overage and the wallet are paid-plan features. The wallet only comes into play **after** your plan allowance is exhausted — your monthly credits are always spent first. The exact top-up limits, the auto top-up defaults, and how a balance is reconciled are in [Wallet & top-ups](/billing/wallet). ## The usage gate: checked before work begins Every piece of managed work passes through a **usage gate** before it starts. The gate confirms your organization can pay for the work — checking your plan allowance, your wallet, and your limits — and only then does the work run. This is why you are never billed for work that could not be admitted. The gate applies to ModuleX's managed-usage surfaces: Each managed workflow run is checked and metered before it executes. Turning plain English into a workflow runs through the gate per turn. Each Assistant turn is admitted and metered before the agent acts. Retrieval and document ingest on ModuleX-managed knowledge are metered. When the gate declines work, the request comes back with a clear status: a **402** if your credits are exhausted or your wallet cannot cover it, a **403** if you have hit a plan quota, or a **429** if you have hit a rate limit. Everyday actions that do not run managed work — such as browsing your workflows or editing organization settings — are **not** gated and never return these billing responses. For the full response shapes and how to handle them, see [Usage gating & limits](/billing/usage-gating) and [Errors & status codes](/api-reference/errors). ## Where billing lives Billing belongs to the **organization**, not to an individual. The plan, the credit allowance, and the wallet are shared by everyone in the organization, and every run is metered against the organization it runs in. Managing the plan and the wallet is reserved for owners and admins — see [Organizations, roles & membership](/concepts/organizations-roles). ## Keep going Compare Free, Pro, Max, and Enterprise — allowances, limits, and pricing. Exactly what a credit is and what consumes credits. The gate and its 402, 403, and 429 responses, in detail. The prepaid wallet, manual top-ups, and auto top-up. The concept behind it all — how managed usage is metered and where the gate applies. Free trials, grace periods, and what happens if a payment fails. --- # Plans & pricing URL: https://docs.modulex.dev/billing/plans Compare the ModuleX Free, Pro, Max, and Enterprise plans — monthly credits, rate limits, storage, team features, and what each tier costs per month and per year. {/* AUDIENCE: non-technical — card-driven, skimmable. Source: M02 (modulex-www/pricing.md), X06 (billing-and-credits.md), X09 (glossary). */} {/* CONSTRAINT §2.5: present BOTH conflicting annual prices ($240/$960 marketed vs $300/$1,200 Stripe SKU); pick NEITHER silently. Credits canonical from subscription_plans.json (Pro 5000 / Max 20000). */} ModuleX has four plans: **Free**, **Pro**, **Max**, and **Enterprise**. Each plan gives your organization a monthly allowance of [credits](/billing/credits) plus a set of limits and features. You pay only for managed usage — usage that runs on your own provider keys (BYOK) is billed by that provider, not by ModuleX. This page covers what each plan includes and what it costs. For how credits are spent and what counts as managed usage, see [Credits & metering](/billing/credits). **🎬 MEDIA PLACEHOLDER · MX-MEDIA-1250 · [IMAGE]** **[IMAGE_DESCRIPTION]:** Plan comparison table rendered as a four-column pricing graphic (Free, Pro, Max, Enterprise). **[IMAGE_DETAILS]:** Show monthly credits (1,500 / 25,000 / 100,000 / Custom), monthly price ($0 / $25 / $100 / Custom), and a "most popular" tag on Pro. Light and dark variants, 16:9, no annual price shown in the graphic itself (annual pricing is unresolved — see the callout in this page). Match the column order Free → Pro → Max → Enterprise. ## Plans at a glance **$0 / month** · **300 credits** (one-time trial grant) Explore ModuleX with trial credits. Solo workspace, community support, no card required. **$25 / month** · **5,000 credits / month** For individuals building AI workflows. Invite teammates, higher rate limits, more storage. **$100 / month** · **20,000 credits / month** For teams scaling automation. Everything in Pro with the highest standard limits and storage. **Custom** · **Custom credits** For organizations at scale. SSO, self-hosting, custom limits, and dedicated support. Credit allowances are the same figure everywhere ModuleX states them, and they map directly to dollars: **100 credits = $1.00**, so 1 credit = $0.01. At that rate, Pro's 5,000 credits is worth $50 of managed usage and Max's 20,000 credits is worth $200. The Free plan's 300 credits is a **one-time** grant for trying ModuleX, not a recurring monthly allowance. ## Plan comparison The credits and limits below come from the ModuleX plan configuration (the live, runtime source). Where the marketing site and the configuration disagree, this page shows both — see [Annual pricing](#annual-pricing-two-figures-to-check-at-checkout) below. | What you get | Free | Pro | Max | Enterprise | | --- | --- | --- | --- | --- | | **Monthly price** | $0 | $25 | $100 | Custom | | **Annual price** | — | $240/yr or $300/yr — [confirm at checkout](#annual-pricing-two-figures-to-check-at-checkout) | $960/yr or $1,200/yr — [confirm at checkout](#annual-pricing-two-figures-to-check-at-checkout) | Custom | | **Credits** | 300 (one-time trial) | 5,000 / month | 20,000 / month | Custom | | **Workflow runs** | 10 runs / min | 150 runs / min | 500 runs / min | Custom | | **API requests** | 10 / min | 100 / min | 500 / min | Custom | | **Storage** | 1 GiB | 50 GiB | 500 GiB | Custom | | **Max file size** | 10 MiB | 50 MiB | 100 MiB | Custom | | **Knowledge bases** | 10 | 3 | 50 | Unlimited | | **Integrations & tools** | 600+ tools | 600+ tools | 600+ tools | 600+ tools | | **AI Composer** | Included | Included | Included | Included | | **Bring your own keys (BYOK)** | Included | Included | Included | Included | | **Team collaboration** | — | Included | Included | Included | | **Single sign-on (SSO / SAML)** | — | — | — | Included | | **Self-hosted deployment** | — | — | — | Included | | **Onboarding assistance** | — | — | — | Included | | **Support** | Community | Community + Discord | Email + Discord | Dedicated | A few rows above are worth a closer read: - **Knowledge bases on Pro (3) is lower than Free (10).** This reflects the current plan configuration; the limit is the count of separate [knowledge bases](/billing/credits), not the documents or storage inside them (Pro gets far more storage). If you expect to keep many separate knowledge bases, factor this in when choosing a plan. - **"Workflow runs" is the synchronous run rate** (runs started per minute). Pro and Max also list higher asynchronous run rates in marketing material, but the asynchronous rate is not currently enforced — plan around the synchronous figures above. - **Storage values are binary** (GiB / MiB), so "50 GB" on the marketing site means 50 GiB of storage. ## Annual pricing: two figures to check at checkout **The monthly price is the same everywhere, but two different annual prices are in circulation. Confirm the exact yearly amount in Stripe checkout before you commit to an annual plan.** The monthly prices are consistent across ModuleX: Pro is **$25/month** and Max is **$100/month**. Annual pricing is where the two sources of truth currently disagree, and the authoritative annual figure is not yet settled, so both are shown here without picking a winner. The pricing site advertises annual billing at a **20% discount**: - **Pro:** $240 / year ($20 / month) - **Max:** $960 / year ($80 / month) The annual subscription products in billing are **12 × the monthly price** (no discount): - **Pro:** $300 / year - **Max:** $1,200 / year **What this means for you:** the amount you are actually charged is the amount shown on the Stripe checkout page when you select annual billing. **Read that total before confirming.** If it does not match the discounted figure you expected, choose monthly billing or [contact the ModuleX team](/enterprise/contact-sales) before completing the purchase. ## Free trial Pro and Max include a **7-day free trial**. You can start a trial once per organization, and only if neither your organization nor you (as the owner) has subscribed or trialed before. No credit card is required to use the Free plan itself. Create an organization and use your 300 trial credits to explore [workflows](/concepts/workflows-and-runs), the [AI Composer](/concepts/ai-composer), and the [Assistant](/assistant/overview). When you are ready for more, start a 7-day trial of Pro or Max to unlock higher credit allowances, team collaboration, and bigger rate limits. Keep the plan to continue past the trial. If you do not add a payment method before the trial ends, the trial subscription is cancelled automatically. For trial eligibility, grace periods, and what happens when a payment fails, see [Trials & dunning](/billing/trials-dunning). ## Choosing a plan Start on **Free** — it needs no card and includes 300 trial credits. Move to **Pro** when you want to invite teammates and need a recurring monthly credit allowance. Choose **Max** when a team needs the highest standard rate limits and storage. Choose **Enterprise** when you need SSO, self-hosting, or custom limits. On a paid plan, you can turn on overage and spend down a prepaid [wallet](/billing/wallet) once your monthly allowance is used up. Without a wallet balance (or on the Free plan once trial credits are gone), managed usage is blocked until your allowance refreshes or you top up. See [Usage gating & limits](/billing/usage-gating) for how these limits are enforced. No. Usage that runs on your own provider keys (BYOK) is billed by that provider directly — ModuleX adds no markup and charges no credits for it. Credits apply only to ModuleX-managed models, tools, and retrieval. See [Credits & metering](/billing/credits). Yes. You can upgrade or downgrade your plan, and switch between monthly and annual billing. Plan changes are managed through your organization's billing settings; downgrades and cancellations can be scheduled and undone before they take effect. See [Subscriptions & Stripe](/billing/subscription-lifecycle). Enterprise adds single sign-on (SSO / SAML), self-hosted deployment, custom rate limits and credit allowances, advanced analytics, hands-on onboarding, and dedicated support. Pricing is custom. See [Enterprise](/security/enterprise) or [contact sales](/enterprise/contact-sales). ## Next steps Learn what a credit is and exactly what consumes credits. See how plan limits are enforced and the responses you get when you hit one. SSO, self-hosting, seats, and dedicated support for larger teams. --- # Subscriptions & Stripe URL: https://docs.modulex.dev/billing/subscription-lifecycle The ModuleX subscription lifecycle: Stripe checkout and the customer portal, plan transitions, the signature-verified Stripe webhook, lifecycle states, and the Python-SDK-only subscriptions resource. Full request/response schemas and error codes. A ModuleX subscription is a Stripe-backed plan attached to your [organization](/concepts/organizations-roles). It sets the monthly [credit](/billing/credits) allowance and the [limits](/billing/usage-gating) your organization runs against. This page covers the full lifecycle: how a subscription is created through Stripe Checkout, how plan changes and cancellations are scheduled, how ModuleX stays in sync through a signature-verified Stripe webhook, the states a subscription moves through, and the SDK surface. The `subscriptions` resource exists in the **Python SDK only**. The JavaScript SDK has no `subscriptions` resource and no subscription methods. Wherever a checkout or portal link is needed from code, use the Python SDK or call the REST endpoints directly. See [Subscriptions are Python-SDK-only](#subscriptions-are-pythonsdkonly) below and the [SDK parity matrix](/sdks/parity). ## Who can manage subscriptions Every `/subscriptions/*` endpoint is **organization-scoped** and reserved for owners and admins: - Most endpoints require **admin or owner** (the `organization_admin_required` dependency). - `POST /subscriptions/wallet/topup` requires **owner** specifically. The [`member` role is retired](/security/roles-permissions): only owners and admins exist. Every request must carry the organization context header. See [Org context & X-Organization-ID](/security/org-context) and [Roles & permissions](/security/roles-permissions). A bearer token: either a `mx_live_…` API key or a Clerk session token. Example: `Authorization: Bearer mx_live_…`. See [Authentication](/api-reference/authentication). The UUID of the organization whose subscription you are managing. Missing this header on an org-scoped route returns `400`/`422`. These endpoints have **no global `/v1` prefix** — each router carries its own. The subscription endpoints live under `/subscriptions`, and the Stripe webhook lives under `/stripe`. Trailing-slash redirects are off, so call paths exactly as written. See [Base URLs & versioning](/api-reference/environments). ## Lifecycle states A subscription's `status` mirrors Stripe verbatim. ModuleX treats the following set as **active** (entitlements apply): `trialing`, `active`, `past_due`, `unpaid`. Anything outside that set falls the organization back to the Free plan. | State | What it means | Entitlements | | --- | --- | --- | | `trialing` | Inside the 7-day trial (Pro/Max only, once per organization). | Plan entitlements apply. | | `active` | Paid and current. | Plan entitlements apply. | | `past_due` | A renewal payment failed; inside the dunning grace window. | Plan entitlements apply until the grace window closes, then operations are suspended. | | `unpaid` | Payment unresolved after dunning. | In the active set, but `operations_suspended` is set — the [usage gate](/billing/usage-gating) denies managed work with a `402`. | | `canceled` / other | Not in the active set. | Organization falls back to Free. | **`operations_suspended`** is the flag that gates a paying-but-unpaid organization. It is set when `status` is `unpaid`, or when `status` is `past_due` and the failed-payment grace window (anchored on the first failure, plus 3 days) has passed. A suspended organization's managed runs return a `credit_exhausted` `402` from the gate and do **not** fall back to the Free tier. Restoring payment clears the flag — see [Trials & dunning](/billing/trials-dunning). ```mermaid stateDiagram-v2 [*] --> trialing: checkout (eligible) [*] --> active: checkout (no trial) trialing --> active: trial ends, payment ok trialing --> canceled: trial ends, no payment method active --> past_due: renewal payment fails past_due --> active: payment recovered past_due --> unpaid: grace window passes unpaid --> active: payment recovered active --> canceled: cancel at period end canceled --> [*] ``` **🎬 MEDIA PLACEHOLDER · MX-MEDIA-1290 · [IMAGE]** **[IMAGE_DESCRIPTION]:** A clean state diagram of the ModuleX subscription lifecycle from checkout to cancellation, showing trial, active, past_due, unpaid, and canceled. **[IMAGE_DETAILS]:** One illustration (not a screenshot), 16:9, light theme, ModuleX brand palette. Render the lifecycle as labeled state nodes with directed arrows: a start point branching into "trialing" and "active"; "trialing" → "active" (trial ends, payment ok) and → "canceled" (no payment method); "active" → "past_due" (renewal fails); "past_due" → "active" (recovered) and → "unpaid" (grace passes, ~3 days); "unpaid" → "active" (recovered); "active" → "canceled" (cancel at period end). Mark the set `{trialing, active, past_due, unpaid}` as "active set — entitlements apply" with a subtle band, and annotate that a suspended (unpaid / past-grace past_due) org is gated. Keep node labels to the state names; put transition reasons on the arrows. ## Creating a subscription — Stripe Checkout A new subscription starts with a **Stripe Checkout session** in subscription mode. ModuleX returns a hosted checkout URL; you redirect the owner or admin to it, and the subscription is created by Stripe and mirrored back through the [webhook](#staying-in-sync-the-stripe-webhook). ### `POST /subscriptions/checkout-link` Creates a Stripe Checkout session (`mode=subscription`) for a plan and billing interval. Requires admin or owner. The plan, interval, and legacy plan id are sent as **query parameters**, not a JSON body. The Python SDK's `checkout_link()` does the same — it passes `params`, not `json`. The billing interval: `month` or `year`. The plan to subscribe to: `pro` or `max`. Preferred over `plan_id`. One of `plan_slug` or `plan_id` is required. A legacy database plan id. Deprecated — use `plan_slug`. The hosted Stripe Checkout URL. Redirect the user here to complete payment. On completion, Stripe redirects to `{FRONTEND_URL}/settings/billing?checkout=success`; on abandonment, to `?checkout=canceled`. ```bash cURL curl -X POST 'https://api.modulex.dev/subscriptions/checkout-link?interval=month&plan_slug=pro' \ -H 'Authorization: Bearer mx_live_…' \ -H 'X-Organization-ID: 7f1c9a2e-3b4d-4e6a-9c12-8a5b6d2f0e11' ``` ```python Python import asyncio from modulex import Modulex async def main(): async with Modulex( api_key="mx_live_…", organization_id="7f1c9a2e-3b4d-4e6a-9c12-8a5b6d2f0e11", ) as mx: # plan_slug and interval are sent as query parameters. link = await mx.subscriptions.checkout_link("pro", "month") print(link.url) # redirect the owner/admin here asyncio.run(main()) ``` ```javascript JavaScript // The JavaScript SDK has NO subscriptions resource. // Call the REST endpoint directly to create a checkout link. const res = await fetch( "https://api.modulex.dev/subscriptions/checkout-link?interval=month&plan_slug=pro", { method: "POST", headers: { Authorization: "Bearer mx_live_…", "X-Organization-ID": "7f1c9a2e-3b4d-4e6a-9c12-8a5b6d2f0e11", }, }, ); const { url } = await res.json(); // redirect the owner/admin to `url` ``` The Stripe **price** is resolved live by lookup key (for example `pro_monthly`, `max_annual`), never a hard-coded id. The checkout **quantity** is the organization's active member count (the owner is seat 1). The session stamps `subscription_data.metadata = { organization_id, plan_slug }` so the webhook can attribute the resulting subscription. | Status | Condition | | --- | --- | | `400` | Neither `plan_slug` nor `plan_id` supplied. | | `400` | The plan is Enterprise — `"Enterprise plans are sales-led; contact sales."` See [Contact sales](/enterprise/contact-sales). | | `400` | The plan is inactive — `"This plan is no longer available for purchase"`. | | `400` | No Stripe price for the requested interval. | | `400` | The organization has no Stripe customer configured. | | `404` | `"Subscription plan not found"`. | | `500` | The Stripe API key is not configured. | | `502` | A Stripe API error occurred (`"Stripe error: …"`). | These are plain `{"detail": "…"}` (`HTTPException`) shapes, not the billing `DenialEnvelope`. See [Errors & status codes](/api-reference/errors). ### Trials A **7-day trial** is added to the checkout automatically when both conditions hold: - The organization is trial-eligible: it has never trialed **and** the owner has never subscribed before. ModuleX tracks this with `organizations.has_ever_trialed` and `users.has_ever_subscribed`. - The synced Stripe price has `trial_period_days > 0`. Trials apply to **Pro and Max only**. If a trial ends with no payment method on file, the subscription is canceled (`trial_settings.end_behavior.missing_payment_method = "cancel"`). Full eligibility and grace behavior is on [Trials & dunning](/billing/trials-dunning). ## Changing plans — transitions Once a subscription exists, an admin or owner changes plan or interval through a **transition**. ModuleX computes the direction (upgrade, downgrade, or no-op) and applies it either immediately or at the period end. ### `POST /subscriptions/transition` Self-serve plan change. Requires admin or owner. Seats are preserved across the change (the quantity is carried over). The target plan: `pro` or `max`. Enterprise transitions are rejected — Enterprise is sales-led. The target billing interval: `month` or `year`. The response depends on the direction: One of `upgrade`, `downgrade`, or `noop`. `immediate` for upgrades, `period_end` for downgrades. Absent on a no-op. ISO 8601 timestamp when a `period_end` change takes effect (downgrades only). The plan the downgrade will move to (downgrades only). The interval the downgrade will move to (downgrades only). The Stripe `SubscriptionSchedule` id backing a scheduled downgrade (downgrades only). Present on a no-op: `"Already on this plan + interval."` The direction rule is the single source of truth for up- versus down-grade: | From → To | Direction | When it applies | | --- | --- | --- | | Lower tier → higher tier | **Upgrade** | Immediately. | | Higher tier → lower tier | **Downgrade** | At the period end. | | Same tier, monthly → annual | **Upgrade** | Immediately. | | Same tier, annual → monthly | **Downgrade** | At the period end. | | Same tier, same interval | **No-op** | Returns the no-op message. | ```bash cURL curl -X POST 'https://api.modulex.dev/subscriptions/transition' \ -H 'Authorization: Bearer mx_live_…' \ -H 'X-Organization-ID: 7f1c9a2e-3b4d-4e6a-9c12-8a5b6d2f0e11' \ -H 'Content-Type: application/json' \ -d '{"plan_slug": "max", "interval": "month"}' ``` ```python Python # There is no SDK method for transitions in either SDK — call REST directly. import asyncio, httpx async def main(): async with httpx.AsyncClient() as http: res = await http.post( "https://api.modulex.dev/subscriptions/transition", headers={ "Authorization": "Bearer mx_live_…", "X-Organization-ID": "7f1c9a2e-3b4d-4e6a-9c12-8a5b6d2f0e11", }, json={"plan_slug": "max", "interval": "month"}, ) print(res.json()) # e.g. {"action": "upgrade", "effective": "immediate"} asyncio.run(main()) ``` ```javascript JavaScript // No subscriptions resource in the JS SDK — call REST directly. const res = await fetch("https://api.modulex.dev/subscriptions/transition", { method: "POST", headers: { Authorization: "Bearer mx_live_…", "X-Organization-ID": "7f1c9a2e-3b4d-4e6a-9c12-8a5b6d2f0e11", "Content-Type": "application/json", }, body: JSON.stringify({ plan_slug: "max", interval: "month" }), }); console.log(await res.json()); ``` **Upgrades** modify the Stripe subscription immediately with `proration_behavior="always_invoice"` and `payment_behavior="error_if_incomplete"`, under a per-organization advisory lock. If the card is declined, the change is rejected with a `402` `upgrade_payment_failed` `DenialEnvelope` (see below). After an upgrade, the entitlement cache is busted so the new plan takes effect at once. **Downgrades** (and annual → monthly) are applied as a two-phase Stripe `SubscriptionSchedule`. The organization keeps its current, higher entitlements until the period-end webhook fires — the entitlement cache is **not** busted on a downgrade. The pending change is mirrored in `pending_plan_slug`, `pending_interval`, and `pending_effective_at`. | Status | Condition | | --- | --- | | `400` | `plan_slug` is missing — `"plan_slug is required"`. | | `400` | Target is Enterprise — `"Enterprise plans are sales-led; contact sales."` | | `400` | No active subscription — `"No active subscription to transition; start a checkout first."` | | `400` | `"Target plan price not available"`. | | `402` | Upgrade payment failed (card declined) — flat `DenialEnvelope` with `code: "upgrade_payment_failed"`. | | `404` | `"Plan not found"`. | | `500` | The Stripe API key is not configured. | | `502` | `"Plan transition failed at the billing provider"`. | ### `POST /subscriptions/transition/cancel` Undoes a **pending downgrade** or a **pending cancellation** — whether it was scheduled from the app or from the Stripe customer portal. Requires admin or owner. It is **idempotent**: when nothing is pending it returns a no-op without error. ModuleX detects a pending state from `pending_plan_slug` (a scheduled downgrade) or from `cancel_at_period_end` / `cancel_at` / `stripe_schedule_id` (a scheduled cancel), releases the Stripe schedule and/or resumes the subscription, and clears the pending mirror columns. Because the current tier is never lost in this operation, the entitlement cache is not busted. `true` when a pending change was undone; `false` on a no-op. Present on a no-op explaining that nothing was pending. ```bash cURL curl -X POST 'https://api.modulex.dev/subscriptions/transition/cancel' \ -H 'Authorization: Bearer mx_live_…' \ -H 'X-Organization-ID: 7f1c9a2e-3b4d-4e6a-9c12-8a5b6d2f0e11' ``` ## The customer portal The **Stripe Billing Portal** is where owners and admins update payment methods, view invoices, and cancel a subscription. ModuleX returns a one-time portal URL. ### `POST /subscriptions/customer-portal` Creates a Stripe Billing Portal session. Requires admin or owner. Returns to `{FRONTEND_URL}/?org_settings=settings/billing` when the user is done. The hosted Stripe customer portal URL. Redirect the user here. ```bash cURL curl -X POST 'https://api.modulex.dev/subscriptions/customer-portal' \ -H 'Authorization: Bearer mx_live_…' \ -H 'X-Organization-ID: 7f1c9a2e-3b4d-4e6a-9c12-8a5b6d2f0e11' ``` ```python Python import asyncio from modulex import Modulex async def main(): async with Modulex( api_key="mx_live_…", organization_id="7f1c9a2e-3b4d-4e6a-9c12-8a5b6d2f0e11", ) as mx: portal = await mx.subscriptions.customer_portal() print(portal.url) # redirect the owner/admin here asyncio.run(main()) ``` ```javascript JavaScript // No subscriptions resource in the JS SDK — call REST directly. const res = await fetch( "https://api.modulex.dev/subscriptions/customer-portal", { method: "POST", headers: { Authorization: "Bearer mx_live_…", "X-Organization-ID": "7f1c9a2e-3b4d-4e6a-9c12-8a5b6d2f0e11", }, }, ); const { url } = await res.json(); ``` A cancellation made in the Stripe portal arrives back in ModuleX as a scheduled period-end cancel. You can undo it with [`POST /subscriptions/transition/cancel`](#post-subscriptionstransitioncancel) while it is still pending. | Status | Condition | | --- | --- | | `400` | The organization has no Stripe customer configured. | | `404` | `"Organization not found"`. | | `500` | The Stripe API key is not configured. | | `502` | A Stripe API error occurred (`"Stripe error: …"`). | ## Reading subscription state Two read endpoints expose the current plan and the catalog. Both are the read methods the Python SDK wraps. ### `GET /subscriptions/organization-billing` Returns the organization's current subscription, its plan, and any scheduled change or cancellation. Requires admin or owner. `false` when the organization has no active subscription (on Free). Then `subscription` and `plan` are `null`. The active subscription, or `null`. One of the [lifecycle states](#lifecycle-states): `trialing`, `active`, `past_due`, `unpaid`, etc. ISO 8601 timestamp. ISO 8601 timestamp — the renewal date. `month` or `year`. The subscription price in dollars. Seat count (active members). A pending period-end plan/interval change, or `null`: `{ plan_slug, interval, effective_at }`. A pending cancellation, or `null`: `{ cancel_at_period_end, effective_at }`. ISO 8601 timestamp. The plan backing the subscription, or `null`. For example `Pro`. For example `pro`. Price entries. In this billing object each entry uses the field name **`price`** (see the field-name note below): `{ interval, price, currency }`. Feature labels for the plan. ```bash cURL curl 'https://api.modulex.dev/subscriptions/organization-billing' \ -H 'Authorization: Bearer mx_live_…' \ -H 'X-Organization-ID: 7f1c9a2e-3b4d-4e6a-9c12-8a5b6d2f0e11' ``` ```python Python import asyncio from modulex import Modulex async def main(): async with Modulex( api_key="mx_live_…", organization_id="7f1c9a2e-3b4d-4e6a-9c12-8a5b6d2f0e11", ) as mx: billing = await mx.subscriptions.organization_billing() if billing.has_subscription: sub = billing.subscription print(sub.status, sub.billing_interval, sub.current_period_end) asyncio.run(main()) ``` ```javascript JavaScript // No subscriptions resource in the JS SDK — call REST directly. const res = await fetch( "https://api.modulex.dev/subscriptions/organization-billing", { headers: { Authorization: "Bearer mx_live_…", "X-Organization-ID": "7f1c9a2e-3b4d-4e6a-9c12-8a5b6d2f0e11", }, }, ); const billing = await res.json(); ``` ```json { "has_subscription": true, "subscription": { "id": "sub_1QabcDEfghIJklmn", "status": "active", "current_period_start": "2026-06-01T00:00:00+00:00", "current_period_end": "2026-07-01T00:00:00+00:00", "billing_interval": "month", "current_price": 25.0, "quantity": 3, "scheduled_change": null, "cancel_at_period_end": false, "scheduled_cancel": null, "created_at": "2026-05-01T09:12:44+00:00" }, "plan": { "id": "00000000-0000-0000-0000-0000000000pr", "name": "Pro", "plan_slug": "pro", "sort_order": 0, "is_enterprise": false, "prices": [{ "interval": "month", "price": 25.0, "currency": "usd" }], "features": ["Live team collaboration", "Email support"] } } ``` ### `GET /subscriptions/organization-plans` Lists the available plans with a per-organization `relation` (whether each plan is the current one, an upgrade, or a downgrade), whether it is selectable, and any discounted price. Requires admin or owner. The Admin plan is never exposed, and no Stripe ids are leaked. The plan catalog. `false` for Enterprise. Price entries. In the catalog each entry uses the field name **`amount`** (not `price`): `{ interval, amount, currency }`. Per-interval discounted prices when a coupon applies: `{ month, year }`. An optional display badge. Feature labels. `current`, `upgrade`, or `downgrade` relative to this organization. The number of plans returned. ```bash cURL curl 'https://api.modulex.dev/subscriptions/organization-plans' \ -H 'Authorization: Bearer mx_live_…' \ -H 'X-Organization-ID: 7f1c9a2e-3b4d-4e6a-9c12-8a5b6d2f0e11' ``` ```python Python import asyncio from modulex import Modulex async def main(): async with Modulex( api_key="mx_live_…", organization_id="7f1c9a2e-3b4d-4e6a-9c12-8a5b6d2f0e11", ) as mx: catalog = await mx.subscriptions.organization_plans() for plan in catalog.plans: # catalog prices use the `amount` field prices = [(p.interval, p.amount) for p in plan.prices] print(plan.plan_slug, plan.relation, prices) asyncio.run(main()) ``` ```javascript JavaScript // No subscriptions resource in the JS SDK — call REST directly. const res = await fetch( "https://api.modulex.dev/subscriptions/organization-plans", { headers: { Authorization: "Bearer mx_live_…", "X-Organization-ID": "7f1c9a2e-3b4d-4e6a-9c12-8a5b6d2f0e11", }, }, ); const { plans, total } = await res.json(); ``` The price field name **differs by endpoint**. The catalog (`organization-plans`) uses **`amount`**, while the billing object (`organization-billing`) uses **`price`**. The Python SDK models reflect this: `PlanPrice.amount` for the catalog versus `BillingPlanPrice.price` for the billing plan. Read the correct field for the endpoint you called. ## Staying in sync — the Stripe webhook ModuleX does not poll Stripe for state. Stripe pushes events to a single webhook, and ModuleX mirrors the relevant ones into its own subscription, wallet, and catalog tables. The webhook is the source of every status change you observe through the read endpoints. ### `POST /stripe/webhook` The webhook receiver. It is **not** authenticated with a bearer token — it verifies a **Stripe signature** instead. It is idempotent, order-aware, and retryable. The Stripe signature header. ModuleX verifies it against `STRIPE_WEBHOOK_SECRET` using the **raw request body**. Verification or parse failure returns `400 "Invalid webhook signature"` with no error detail leaked. A missing `STRIPE_WEBHOOK_SECRET` returns `500`. This endpoint is a Stripe-to-server receiver, not a client call. Neither SDK exposes it, and you should never call it yourself — you configure its URL in your Stripe dashboard. See the [SDK parity matrix](/sdks/parity) for the full list of routes with no SDK method. #### Handled events Each delivered event is normalized once and dispatched by type. A redelivered event that was already processed short-circuits to `{ "received": true, "duplicate": true }`. | Event type | Effect | | --- | --- | | `customer.subscription.created` / `.updated` / `.deleted` | Watermarked mirror upsert of the subscription. | | `invoice.payment_failed` / `invoice.payment_action_required` | Dunning: anchor the grace window and schedule a suspend job. | | `invoice.paid` / `invoice.payment_succeeded` | Restore: clear the dunning anchor and any suspension. No fresh credit grant. | | `subscription_schedule.*` | Re-mirror the linked subscription and persist a portal-scheduled cancel. | | `payment_intent.succeeded` | Apply a [wallet](/billing/wallet) top-up credit (idempotent on the payment-intent id). | | `charge.refunded` | Apply a wallet refund (idempotent on the charge id). Never touches subscription status. | | `product.*` / `price.*` / `coupon.*` / `promotion_code.*` | Re-sync the catalog mirror (best-effort). | | `checkout.session.completed` / `customer.subscription.trial_will_end` | Logged and acknowledged; the business effect is handled elsewhere. | | Unknown | Acknowledged and ignored: `{ "received": true, "ignored": true, "type": … }`. | Stripe emits `customer.subscription.deleted` for a cancellation — there is **no** `customer.subscription.canceled` event. If you wire up your own listeners against Stripe, handle `.deleted`. A `customer.subscription.deleted` event mirrors the subscription's terminal status; the organization then falls to Free because the deleted status is outside the active set. A restored payment after dunning clears suspension but does **not** re-grant credits or reset the monthly bucket — this is deliberate, to prevent farming a fresh allowance by lapsing and recovering. See [Trials & dunning](/billing/trials-dunning) and [Credits & metering](/billing/credits). #### Retries and dead-lettering The webhook answers Stripe so that genuinely transient failures are retried and permanent ones are not: An organization-not-found race (the customer-create event arrives before the org commit), or a database or Stripe error mid-processing, returns **503** so Stripe retries — up to `WEBHOOK_TRANSIENT_RETRY_CAP` (default 5) attempts. Once the retry cap is reached, the event is dead-lettered, marked processed, and acknowledged with **200** so Stripe stops retrying: `{ "received": true, "dead_lettered": true, … }`. A malformed payload (for example a missing customer) raises a permanent error: the event is dead-lettered, marked processed, and acknowledged with **200**. No retry. The event is recorded for deduplication and acknowledged with **200**: `{ "received": true, "type": … }`. **Ordering.** Subscription mirror upserts carry a watermark on the event's `created` timestamp. A stale (older) event that arrives after a newer one is rejected without corrupting the newer state, so out-of-order delivery from Stripe cannot roll your subscription back. ## Subscriptions are Python-SDK-only The `subscriptions` resource is part of the **Python SDK only**. The **JavaScript SDK has no `subscriptions` resource** and exposes no subscription methods — confirmed against the SDK parity matrix. From JavaScript, call the REST endpoints directly (as shown in the JavaScript tabs above) or surface billing through the app's customer portal. The Python SDK wraps **four** of the subscription endpoints: | Python method | REST endpoint | Notes | | --- | --- | --- | | `client.subscriptions.organization_plans()` | `GET /subscriptions/organization-plans` | Catalog with per-org `relation`. Prices use `amount`. | | `client.subscriptions.organization_billing()` | `GET /subscriptions/organization-billing` | Current subscription + plan. Plan prices use `price`. | | `client.subscriptions.checkout_link(plan_slug, interval, *, plan_id=None)` | `POST /subscriptions/checkout-link` | Params sent as **query**, not body. | | `client.subscriptions.customer_portal()` | `POST /subscriptions/customer-portal` | Returns a portal URL. | The remaining `/subscriptions/*` endpoints have **no SDK method in either SDK** and must be called over REST: - `GET /subscriptions/wallet` - `PATCH /subscriptions/wallet/extra-usage` - `POST /subscriptions/wallet/topup` - `POST /subscriptions/transition` - `POST /subscriptions/transition/cancel` The wallet endpoints are documented on [Wallet & top-ups](/billing/wallet); the transition endpoints are above on this page. Every method accepts a per-call `organization_id` that overrides the client default. All four methods are `async` and require an owner or admin token. The full method-to-route mapping, including the cross-SDK gaps, is on the [SDK parity matrix](/sdks/parity); installation and configuration are on the [Python SDK](/sdks/python) page. ## Error shapes you may see Subscription endpoints can return **two different `402` shapes**, and you must branch on both: ```json DenialEnvelope (gate/upgrade) { "code": "upgrade_payment_failed", "layer": "credit", "key": "7f1c9a2e-3b4d-4e6a-9c12-8a5b6d2f0e11", "current": null, "limit": 5000.0, "reason": "upgrade_payment_failed" } ``` ```json HTTPException (paid-plan required) { "detail": { "reason": "paid_subscription_required" } } ``` - The **flat `DenialEnvelope`** (`{ code, layer, key, current, limit, reason }`) comes from the billing gate — for example an upgrade whose card was declined returns `code: "upgrade_payment_failed"`. This is the same envelope the [usage gate](/billing/usage-gating) raises for `402` / `403` / `429`. - The **`HTTPException` shape** (`{"detail": {…}}`) comes from the wallet paid-plan checks (`PATCH /subscriptions/wallet/extra-usage`, `POST /subscriptions/wallet/topup`), which return `{"detail": {"reason": "paid_subscription_required"}}` when an organization without a paid subscription tries to use a paid-only feature. The other subscription errors (`400`, `404`, `500`, `502`) use the plain `{"detail": "…"}` string shape. All three envelope shapes are catalogued on [Errors & status codes](/api-reference/errors). In the Python SDK, a `402` raises `PaymentRequiredError` and, when the body carries a recognized billing layer, a more specific subclass (`CreditExhaustedError`, `WalletError`, or `QuotaExceededError`). A `403` raises `PermissionError` and a `429` raises `RateLimitError`. See [Errors & retries](/sdks/errors-retries). ## Configuration reference These environment variables govern the Stripe integration on the server. They are listed here so you understand the behavior; you set them on the deployment, not through the API. | Variable | Default | Controls | | --- | --- | --- | | `STRIPE_API_KEY` | `""` | Stripe secret key. Missing → `500` on any Stripe call. | | `STRIPE_WEBHOOK_SECRET` | `""` | Webhook signature secret. Missing → `500` on the webhook. | | `FRONTEND_URL` | `http://localhost:3000` | Base for the checkout success/cancel and portal return URLs. | | `WEBHOOK_TRANSIENT_RETRY_CAP` | `5` | Transient retries before an event is dead-lettered. | The wallet top-up bounds (`WALLET_TOPUP_MIN_USD = 10`, `WALLET_TOPUP_MAX_USD = 1000`), the credit conversion rate ($0.01 per credit), and auto top-up defaults live with the [wallet](/billing/wallet) and [credits](/billing/credits) configuration rather than the subscription surface. ## Related pages Free, Pro, Max, and Enterprise — allowances, limits, and both annual prices. The prepaid wallet, the top-up checkout, and auto top-up. Trial eligibility, grace periods, suspension, and recovery. The admission gate and its 402, 403, and 429 responses. Install and configure the async client that exposes the subscriptions resource. Which routes each SDK covers — and where the gaps are. --- # Trials, grace periods & account suspension URL: https://docs.modulex.dev/billing/trials-dunning How ModuleX trials work: the 7-day Pro and Max trial and who is eligible, the 30-day window for free accounts, and the 3-day grace period before a past-due account is suspended. This page covers the three moments where time and payment change what your account can do: starting a trial, using a free account, and what happens when a payment fails. None of it removes your work; it changes whether new managed usage can run until you act. A 7-day Pro or Max trial, once per organization, for eligible accounts. Free accounts get a one-time credit allowance with a 30-day window. A 3-day grace period before a past-due account is suspended. ## The 7-day Pro and Max trial You can start a **7-day trial** of a paid plan to use [Pro or Max](/billing/plans) allowances before you pay. The trial is attached to your organization and runs for seven days from the moment you start it. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-1300 · [SCREENSHOT]** **[SCREENSHOT_DESCRIPTION]:** The billing settings screen showing the trial offer on the Pro and Max plan cards at checkout. **[SCREENSHOT_DETAILS]:** Capture `/settings/billing` for an eligible organization, light mode, 16:9. Show the Pro and Max plan cards with the "7-day trial" indication visible, and the monthly/annual toggle. Redact the real organization name. Highlight the trial badge on the plan card. ### Which plans can be trialed The trial is available on the **Pro** and **Max** plans only. The Free plan has no trial, and **Enterprise** plans are sales-led, so they are not started through a self-serve trial. Eligible for the 7-day trial. Eligible for the 7-day trial. No trial. See [free accounts](#free-accounts-and-the-30-day-window). Sales-led. [Talk to sales](/security/enterprise) instead. ### Who is eligible Eligibility is checked when you reach checkout. Two conditions both have to be true: A trial is offered **once per organization**. If your organization has used a trial before, it will not be offered again. The organization owner must never have held a paid subscription. If the owner has subscribed to a paid plan before — in this or another organization — the trial is not offered. If either condition is not met, you can still subscribe to Pro or Max; you simply start paying immediately instead of starting with a trial. Even when your organization and owner are both eligible, the trial only appears if the selected plan's price is configured for it. If you expected a trial and do not see one on an otherwise eligible account, contact support before subscribing so you are not charged earlier than you intended. ### What happens when the trial ends A trial requires a payment method to convert into a paid subscription. If no payment method is on file when the trial ends, the subscription is **cancelled** rather than charged. The trial converts to a paid Pro or Max subscription and billing begins. The subscription is cancelled at the end of the trial — you are not charged. To add a payment method or change your plan before the trial ends, open the customer portal from your billing settings. See [Subscriptions & Stripe](/billing/subscription-lifecycle) for how the subscription lifecycle and customer portal work. ## Free accounts and the 30-day window A free account does not run on a monthly allowance the way a paid plan does. Instead, a free account receives a **one-time** credit allowance, and that allowance is usable for **30 days** from when the account was created. Free accounts get a fixed, one-time grant of [credits](/billing/credits) — not a monthly top-up. After 30 days from account creation, the free credit allowance is no longer available for new managed usage. After the 30-day window, your free credits no longer cover new managed usage such as runs, agent turns, or managed knowledge retrieval. Your workflows, knowledge bases, and saved work stay in place — what stops is the ability to spend free credits on new managed activity. To keep running managed usage past the free window, [upgrade to Pro or Max](/billing/plans). If you are eligible, you can start with the [7-day trial](#the-7-day-pro-and-max-trial) above. Free credits are a one-time grant tied to the account, not a balance that refills. They are separate from the prepaid [wallet](/billing/wallet), which is used by paid plans for overage. ## Failed payments, grace, and suspension If a payment fails on a paid subscription, ModuleX does not switch your account off immediately. There is a short **grace period** first, and only then is the account suspended. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-1301 · [IMAGE]** **[IMAGE_DESCRIPTION]:** A horizontal timeline showing the dunning flow from a failed payment through the grace period to suspension, and the restore path. **[IMAGE_DETAILS]:** Three stages left to right — "Payment fails (day 0): grace period starts" → "Grace period: 3 days, account keeps working" → "Day 3: account suspended if still unpaid". Add a return arrow labelled "Payment succeeds → suspension lifted, no fresh credits". Light mode, brand colors, 16:9. No screenshots; this is an illustrative diagram. ### The dunning timeline On the **first** failed payment in the billing cycle, a grace period begins. A repeated retry of the same failed payment does not restart the clock — the grace period is anchored to that first failure. For **3 days** after the first failure, your account keeps working normally while the payment is retried. Use this time to fix the payment method in the customer portal. If the payment is still unpaid after the 3-day grace period, the organization's operations are **suspended**. While suspended, new managed usage is blocked. ### What "suspended" means in practice A suspended organization cannot start new managed usage. Attempts to run a workflow, take an [Assistant](/assistant/overview) turn, use the [AI Composer](/concepts/ai-composer), or run managed knowledge retrieval are turned away by the [usage gate](/billing/usage-gating) — even if the plan's credit allowance was not used up. While an organization is suspended, calls to the managed-usage surfaces return a credit-exhausted denial (HTTP `402`). The denial uses the standard billing envelope documented in [Usage gating & limits](/billing/usage-gating) and [Errors & status codes](/api-reference/errors). Plain read or settings requests are not affected by the billing gate. The denial returned on a suspended account looks like the credit-exhausted denial below. See [Usage gating & limits](/billing/usage-gating) for the full set of billing denial shapes. ```json Credit-exhausted denial (HTTP 402) { "code": "credit_plan_exhausted", "layer": "credit", "key": "8f1c2d34-5e6f-4a7b-9c8d-0e1f2a3b4c5d", "current": null, "limit": 5000.0, "reason": "credit_plan_exhausted" } ``` ### Getting back to normal When the outstanding payment succeeds, the suspension is **lifted automatically** and your account resumes. Paying off a failed invoice **does not reset your usage or grant fresh credits**. Your account resumes the same billing period it was in, with the usage you had already recorded. This is deliberate — paying late lifts the suspension, it does not restart your allowance. To pay an outstanding invoice or update your card, open the customer portal from billing settings. The lifecycle, the customer portal, and how subscription changes are processed are covered in [Subscriptions & Stripe](/billing/subscription-lifecycle). ## Where this leaves your data Trials ending, free windows closing, and suspensions all affect **new managed usage** — not the things you have already built. Starting new managed runs, Assistant and Composer turns, and managed knowledge retrieval, once a trial ends without payment, a free window closes, or an account is suspended. Your workflows, knowledge bases, credentials, and history remain in place. Reading and managing them is not blocked by the billing gate. ## Frequently asked The trial is tied to both your organization and the owner. Because the owner must never have subscribed before, starting a new organization under the same owner does not re-open trial eligibility once that owner has held a paid plan. Only if a payment method is on file. With a payment method, the trial converts to a paid subscription and billing begins. Without one, the subscription is cancelled at the end of the trial and you are not charged. No. Free credits are a one-time grant, not a refilling balance. To keep running managed usage past the 30-day window, [upgrade to Pro or Max](/billing/plans). Three days. The grace period is anchored to your first failed payment in the cycle, and an automatic retry of that same payment does not extend it. Paying lifts the suspension but does not reset your usage or grant new credits. You resume the same billing period you were in. If you need more capacity, top up your [wallet](/billing/wallet) or move to a higher [plan](/billing/plans). ## Related Compare Free, Pro, Max, and Enterprise allowances and limits. What a credit is and exactly what consumes credits. How the billing gate admits or denies managed usage. The subscription lifecycle, the customer portal, and Stripe. --- # Usage gating & limits URL: https://docs.modulex.dev/billing/usage-gating How the ModuleX usage gate admits or denies managed work before it runs: the four denial layers (credit, quota, rate, wallet), the flat DenialEnvelope returned as 402/403/429, the run/Composer/Assistant/managed-knowledge surfaces it covers, and why plain CRUD is never gated. Every piece of **managed work** in ModuleX passes through an admission **usage gate** before it runs. The gate resolves your [organization's](/concepts/organizations-roles) plan entitlements, reserves the [credit](/billing/credits) the work will cost, and checks your rate and quota limits — all *before* any database row is written or any model is called. If the gate declines, the request fails with a structured denial and **no work is performed and nothing is charged**. This page is the reference for that gate: what it checks, the exact response it returns when it declines, which surfaces it covers, and the surfaces it deliberately leaves alone. **The gate fails closed.** When the gate cannot confirm you have budget — including when its backing cache is unavailable — it denies the request rather than letting it through unmetered. You are never billed for work the gate did not admit. ## What the gate is The gate is a synchronous **reject-before-write** check that runs at the start of every managed run or turn. It does three things in order, then either admits the work or raises a denial: The gate loads your organization's active subscription period — the plan's monthly credit allowance, rate limits, quotas, and wallet flags. A **suspended** organization (an unpaid or past-grace subscription) is denied here and does not fall back to the free tier. See [Subscriptions & Stripe](/billing/subscription-lifecycle) for how suspension happens. The gate hard-reserves the credit the work will cost against your current billing period (for paid plans) or your per-user free pool (on Free). The reservation is atomic, so two concurrent runs cannot both spend the last credit. If the plan allowance is exhausted, the reservation cascades to your prepaid [wallet](/billing/wallet) — but only if overage is enabled and the balance covers it. For runs, the gate also consumes one unit of your plan's per-period run-rate limit. If that limit is hit, the gate releases the credit reservation it just made and denies with a rate error. Only after all three pass does the work begin. When it finishes, the reserved credit is charged and the reservation is settled; if the work errors out early, the reservation is released so the held budget self-heals. The full lifecycle — reserve, charge, settle, release — is covered in [Credits & metering](/billing/credits). **🎬 MEDIA PLACEHOLDER · MX-MEDIA-1270 · [IMAGE]** **[IMAGE_DESCRIPTION]:** A flowchart of one managed request passing through the usage gate, showing the three checks and the four possible denials. **[IMAGE_DETAILS]:** One illustration (not a screenshot), 16:9, light theme, ModuleX brand palette. Left-to-right flow. Start node "Managed request (run / Composer turn / Assistant turn / managed-knowledge op)". First decision diamond "Subscription suspended?" → yes branches to a red "402 credit_plan_exhausted". Next "Plan allowance left?" → no goes to a sub-decision "Overage enabled? Wallet balance?" producing two red outcomes "402 wallet_overage_disabled" and "402 wallet_insufficient"; the credit-exhausted-with-no-wallet path produces "402 credit_plan_exhausted". Next "Quota OK? (e.g. knowledge bases)" → no goes to "403 quota_exceeded". Next "Run-rate limit OK?" → no goes to "429 rate_limit_exceeded". The all-yes path reaches a green "Admitted → work runs → credit charged". Annotate the whole flow with "Nothing is written or charged on any denial path." Keep node text short. ## The denial response: `DenialEnvelope` When the gate declines, it returns a single, flat JSON shape called the **`DenialEnvelope`** — the same shape for every denial layer. It is **not** wrapped in a `detail` key (unlike the standard `{"detail": "…"}` errors the rest of the API returns). Front-end and SDK code branches on the top-level `code` and `layer` fields. ```json Example: credits exhausted (402) { "code": "credit_plan_exhausted", "layer": "credit", "key": "org_2a1f9c4e7b8d", "current": null, "limit": 5000.0, "reason": "credit_plan_exhausted" } ``` ### Fields The machine-stable denial code. Branch on this. One of `credit_plan_exhausted`, `wallet_overage_disabled`, `wallet_insufficient`, `upgrade_payment_failed`, `quota_exceeded`, or `rate_limit_exceeded`. Which gate layer denied the request — one of `credit`, `wallet`, `quota`, or `rate`. The layer fixes the HTTP status: `credit` and `wallet` return **402**, `quota` returns **403**, and `rate` returns **429**. The specific limit or counter key involved — for example the organization id for a credit denial, `sync_exec` for a run-rate denial, or a quota key such as `max_knowledge_bases`. May be `null`. The observed usage or count at the moment of denial. May be `null` when not applicable (for example, the credit layer often reports `null` here). The limit that was hit. May be `null` when the limit is not applicable or not exposed. A short reason token, often identical to `code` (for example `overage_disabled`, `insufficient_balance`). The `DenialEnvelope` is **flat** — its fields are at the top level of the response body, with no `detail` wrapper. A 402/403/429 that arrives wrapped as `{"detail": …}` is **not** a `DenialEnvelope`; it comes from a non-gated route (see [Plain CRUD is never gated](#plain-crud-is-never-gated)). For the complete catalog of every error-envelope shape ModuleX returns and how each SDK maps them to a typed error class, see [Errors & status codes](/api-reference/errors). ## The four denial layers Each denial belongs to one of four layers. The layer determines the HTTP status, and each layer has one or more codes. | `code` | `layer` | HTTP | When it happens | | --- | --- | :-: | --- | | `credit_plan_exhausted` | `credit` | 402 | The plan's monthly credit allowance is used up and no wallet overage is available — or the subscription is suspended. | | `wallet_overage_disabled` | `wallet` | 402 | The allowance is exhausted and overage spending is turned off for the organization. | | `wallet_insufficient` | `wallet` | 402 | Overage is on, but the prepaid [wallet](/billing/wallet) balance is too low to cover the work. | | `upgrade_payment_failed` | `credit` | 402 | An immediate plan-upgrade proration charge failed; the plan change was rolled back and you stay on the old plan. | | `quota_exceeded` | `quota` | 403 | A countable entitlement is at its limit (for example, the maximum number of [knowledge bases](/platform/knowledge/managed) on your plan). | | `rate_limit_exceeded` | `rate` | 429 | The per-period run-rate limit for your plan was reached on a gated surface. | Returned when the plan allowance is gone and there is no wallet to fall back on, or when the subscription is suspended for non-payment. Resolve it by topping up the [wallet](/billing/wallet) (with overage enabled), upgrading your [plan](/billing/plans), or waiting for the monthly allowance to reset. ```json 402 credit_plan_exhausted { "code": "credit_plan_exhausted", "layer": "credit", "key": "org_2a1f9c4e7b8d", "current": null, "limit": 5000.0, "reason": "credit_plan_exhausted" } ``` Returned when the plan allowance is exhausted and the cascade to the prepaid wallet cannot complete: either overage is switched off (`wallet_overage_disabled`) or the balance is too low (`wallet_insufficient`). Enable overage and top up in [Wallet & top-ups](/billing/wallet). ```json 402 wallet_overage_disabled { "code": "wallet_overage_disabled", "layer": "wallet", "key": "org_2a1f9c4e7b8d", "current": null, "limit": null, "reason": "overage_disabled" } ``` Returned when a countable plan entitlement is at its ceiling. The `key` names the entitlement. Free up the resource or upgrade your [plan](/billing/plans). ```json 403 quota_exceeded { "code": "quota_exceeded", "layer": "quota", "key": "max_knowledge_bases", "current": 10, "limit": 10, "reason": "quota_exceeded" } ``` Returned when the per-period run-rate limit is reached. A rate denial also carries rate-limit headers (see below). Back off and retry after the indicated delay. ```json 429 rate_limit_exceeded { "code": "rate_limit_exceeded", "layer": "rate", "key": "sync_exec", "current": 150, "limit": 150, "reason": "rate_limit_exceeded" } ``` ### Rate-limit headers on a 429 A `rate`-layer denial is accompanied by the standard rate-limit headers. Each numeric header is **omitted** when its value is not known, so a client may see only some of them. `Retry-After` is always present. How long to wait before retrying. Defaults to `60` when no more specific value is available. The run-rate limit for your plan's run-rate class. Omitted if not set. Remaining units in the current window (typically `0` at the moment of denial). Omitted if not set. When the window resets. Omitted if not set. The gate's run-rate `rate_limit_exceeded` (a flat `DenialEnvelope`) is distinct from the **API-key/user request rate limit**, which also returns 429 but with a `{"detail": "…"}` string body and the same headers. Both are real and both can occur. For the full picture of every 429 path, see [Rate limiting](/api-reference/rate-limiting). ## Which surfaces are gated The usage gate runs on ModuleX's **managed-usage surfaces** — the places where ModuleX does paid work on your behalf. On these surfaces a 402/403/429 can be a flat `DenialEnvelope`. `POST /workflows/{workflow_id}/run` and `POST /workflows/run`. Each run reserves one run credit and consumes the run-rate class before it executes. The [Composer](/workflow-builder/composer) chat and resume endpoints. Each turn is admitted and metered before the agent edits your workflow. The [Assistant](/assistant/overview) chat and resume endpoints. Each turn is admitted before the agent reasons or calls a tool. Retrieval and document ingest on ModuleX-managed (`modulexdb`) knowledge bases. BYOK vector stores are not metered and not gated. ### Per-surface behavior A managed run reserves one run credit (`RUN_CREDIT = 1`) and consumes the **`sync_exec`** run-rate class. If the rate class is exhausted, the credit reservation is released first, then the run is denied with `rate_limit_exceeded` (429). A credit or wallet shortfall denies with the matching 402 before the workflow starts. The run endpoint requires an owner or admin caller. See [Run via API](/workflow-builder/execution/api-endpoint). Each Composer message is one turn. The gate admits the turn (reserving one credit) and consumes the run-rate class before the agent runs; resuming a paused turn re-enters through the resume endpoint and is not double-charged. Composer requires an owner or admin caller. See [AI Composer in the builder](/workflow-builder/composer). Each Assistant message is one turn, admitted and rate-checked exactly like a Composer turn, even though the Assistant has no workflow tools. The Assistant requires an owner or admin caller. Permissions and limits are detailed in [Permissions & limits](/assistant/permissions-and-limits). On a ModuleX-managed knowledge base, each retrieval reserves one credit (`RETRIEVAL_BASE = 1`) and each document ingest reserves one credit (`FILE_INGEST_BASE = 1`) before the work begins, both against the `api` rate class. A bring-your-own vector store (Qdrant, Pinecone, MongoDB Atlas, Weaviate) is not managed usage, so it is neither metered nor gated. See [Managed knowledge](/platform/knowledge/managed). ### Plain CRUD is never gated Everyday actions that do not run managed work are **not** gated and never return a `DenialEnvelope`. Listing, reading, creating, updating, and deleting resources, and changing organization settings, all bypass the gate entirely. A 402/403/429 from one of these routes is a standard `{"detail": …}` error, never the flat envelope. Routes such as `GET`/`POST`/`PATCH`/`DELETE /workflows` and `/workflows/{workflow_id}`, `GET`/`POST /knowledge-bases`, `/credentials`, `/api-keys`, `/schedules`, and `GET /organizations` or `GET /integrations` do not call the usage gate. If you get a 403 on one of these, it is an authorization or quota error in `{"detail": …}` form, not a billing denial. Do not parse a flat `DenialEnvelope` from a CRUD response. ## Handling a denial in code Run a workflow and handle a billing denial across cURL, Python, and JavaScript. Authenticate with your API key as a bearer token plus the organization header, exactly as on every ModuleX request — see [Authentication](/api-reference/authentication). ```bash cURL # A denied run returns the flat DenialEnvelope (no "detail" wrapper). curl -sS -X POST 'https://api.modulex.dev/workflows/run' \ -H 'Authorization: Bearer mx_live_8Kf2pQ7nR4tV9wXz' \ -H 'X-Organization-ID: org_2a1f9c4e7b8d' \ -H 'Content-Type: application/json' \ -d '{ "workflow_id": "wf_3d8b1a2c", "input": { "topic": "quarterly report" }, "stream": false }' # Example 402 response body when the plan allowance is exhausted: # { # "code": "credit_plan_exhausted", # "layer": "credit", # "key": "org_2a1f9c4e7b8d", # "current": null, # "limit": 5000.0, # "reason": "credit_plan_exhausted" # } ``` ```python Python from modulex import Modulex from modulex import ( CreditExhaustedError, WalletError, QuotaExceededError, RateLimitError, ) async with Modulex( api_key="mx_live_8Kf2pQ7nR4tV9wXz", organization_id="org_2a1f9c4e7b8d", ) as client: try: run = await client.executions.run( workflow_id="wf_3d8b1a2c", input={"topic": "quarterly report"}, stream=False, ) print(run.run_id, run.status) except CreditExhaustedError as exc: # 402, layer="credit" print("Out of credits:", exc.code, exc.limit) except WalletError as exc: # 402, layer="wallet" print("Wallet problem:", exc.code, exc.reason) except QuotaExceededError as exc: # 403, layer="quota" print("Quota hit:", exc.key, exc.current, "/", exc.limit) except RateLimitError as exc: # 429, header path print("Rate limited, retry after", exc.retry_after, "s") ``` ```javascript JavaScript import { Modulex, ModulexError, RateLimitError } from 'modulex-js'; const client = new Modulex({ apiKey: 'mx_live_8Kf2pQ7nR4tV9wXz', organizationId: 'org_2a1f9c4e7b8d', }); try { const run = await client.executions.run({ workflowId: 'wf_3d8b1a2c', input: { topic: 'quarterly report' }, stream: false, }); console.log(run.run_id, run.status); } catch (err) { if (err instanceof RateLimitError) { console.log('Rate limited, retry after', err.retryAfter, 's'); } else if (err instanceof ModulexError && err.layer) { // Billing denials (402/403) carry .code, .layer, and .reason. console.log('Denied:', err.layer, err.code, err.reason); } else { throw err; } } ``` The two SDKs model denials differently. The **Python** SDK has a `BillingError` family — `CreditExhaustedError`, `WalletError`, `QuotaExceededError`, and a base for `rate`-layer envelopes — so you can catch each layer by type. The **JavaScript** SDK has no dedicated billing subclasses: a 402 or a `rate`-layer envelope surfaces as the base `ModulexError` with `.code`, `.layer`, and `.reason` populated, while a header-path 429 is a `RateLimitError`. The full status-to-class mapping for both SDKs is in [Errors & status codes](/api-reference/errors) and [Errors & retries](/sdks/errors-retries). ## What is not billed or gated The gate only meters **managed** usage — work ModuleX runs through its own provisioned models, tools, and vector stores. The following are not gated and never cost credits: - **Bring-your-own-key (BYOK) usage.** When you connect your own [LLM provider](/integrations/llm-providers/overview) or [knowledge provider](/integrations/knowledge-providers/overview), that usage is billed directly by the provider and recorded for analytics only — no credit is charged and the gate does not deny it. - **Plain CRUD and org-settings routes**, as covered above. - **Reading runs, history, and dashboards.** For exactly what a credit is and how managed model, tool, and retrieval usage is converted into credits, see [Credits & metering](/billing/credits). ## Related pages Every error-envelope shape, the full HTTP status taxonomy, and how the SDKs map each status to a typed error class. What a credit is, the reserve-charge-settle lifecycle, and exactly what consumes credits. Per-key and per-user rate limits and all three live 429 paths. Enable overage, top up the prepaid wallet, and set up auto top-up. Allowances, quotas, and rate limits for Free, Pro, Max, and Enterprise. The concept behind metering and where the gate applies. --- # Wallet & top-ups URL: https://docs.modulex.dev/billing/wallet The ModuleX prepaid wallet: balance, manual top-ups ($10–$1000), auto top-up thresholds, and the paid-only overage toggle — with request and response schemas, every error, and edge cases. The **wallet** is your organization's prepaid balance. It covers managed usage that goes beyond your monthly plan [credit](/billing/credits) allowance, once **overage** is turned on. Your plan allowance is always spent first; the wallet only comes into play after it runs out. This page is the technical reference for the wallet: how the balance is stored, how to read it, how manual top-ups and auto top-up work, and how the paid-only overage toggle behaves. For where the wallet fits in the larger billing model, start with [Billing & credits overview](/billing/overview). The wallet and overage are **paid-plan features**. On the [Free plan](/billing/plans) there is no wallet spend — work stops when the allowance is exhausted. See [Usage gating & limits](/billing/usage-gating) for what happens at the gate. ## How the wallet works The wallet holds a balance in **US dollars** and converts to credits at the same fixed rate as your plan allowance: **100 credits = $1.00**, so **one credit is $0.01**. A $10 top-up therefore adds 1,000 credits of spending power. An owner or admin enables overage for the organization. Until overage is on, managed work stops once your plan allowance is empty — the wallet is not touched. Add a prepaid balance with a manual top-up ($10–$1000) through Stripe Checkout, or let auto top-up keep it funded for you. Once your plan allowance is exhausted, admitted managed work is paid for from the wallet. Spending is reconciled against the wallet automatically as runs settle. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-1280 · [SCREENSHOT]** **[SCREENSHOT_DESCRIPTION]:** The wallet section of the billing settings page in the ModuleX app, showing the balance, the overage toggle, and the top-up control. **[SCREENSHOT_DETAILS]:** Capture from the live app at Settings → Billing, signed in as an owner or admin on a paid plan. Show the current wallet balance in dollars, the "Extra usage / overage" toggle in the on state, the auto top-up threshold and amount fields, and the "Top up" button. Light theme, 16:9, crop to the wallet card only. Use a non-zero balance (for example, $12.50) so the value is visible. ### Where the balance lives The wallet is one row per organization. Its balance can go **negative** because overage is reconciled after work settles, so a single burst of usage can briefly push the balance below zero. The wallet balance in dollars, stored as a high-precision decimal. May be **negative** after overage reconciliation. A missing wallet reads as `0` — never as unlimited. Whether overage spending is turned on for the organization. When `false`, the wallet is never spent, even if it holds a balance. The balance at which auto top-up fires, in dollars. Default **$5.00**. `null` if not configured for the organization. How much auto top-up adds per charge, in dollars. Default **$20.00**. `null` if not configured. `true` when auto top-up has been turned off — including automatically, after too many failed charges (see [Auto top-up](#auto-top-up)). Managing the wallet belongs to the organization, not to an individual. Reading and changing it is reserved for **owners and admins**; opening a top-up is **owner-only**. See [Roles & permissions](/security/roles-permissions) and [Organizations, roles & membership](/concepts/organizations-roles). ## Authentication and the wallet endpoints The wallet is managed through the `/subscriptions/wallet/*` endpoints. These are **organization-management** endpoints used by the app's billing settings, so they authenticate with your bearer token and require an organization context header: - `Authorization: Bearer ` — your bearer token. - `X-Organization-ID: ` — the organization whose wallet you are managing. **Required**; a missing or empty value is rejected. The wallet endpoints are **not** part of the public SDK surface. Neither the [JavaScript SDK](/sdks/javascript) nor the [Python SDK](/sdks/python) exposes a wallet method — see the [SDK ⇄ API parity matrix](/sdks/parity). The examples below show cURL only because there is no SDK equivalent to call. Day to day, the wallet is managed in the app's billing settings rather than from code. All examples use the base URL `https://api.modulex.dev`. See [Base URLs & versioning](/api-reference/environments) for environments. ## Read the wallet Returns the balance, the overage toggle, and the auto top-up configuration. Lazily creates the wallet row the first time it is read, so a brand-new organization gets a zero-balance wallet with overage off. **`GET /subscriptions/wallet`** — owner or admin. ```bash cURL curl https://api.modulex.dev/subscriptions/wallet \ -H "Authorization: Bearer mx_live_8f3a2c1d9b7e4f60a1c2d3e4f5061728" \ -H "X-Organization-ID: 7f1c9a3e-2b6d-4c8a-9e10-3f4a5b6c7d8e" ``` The wallet state. Wallet balance in USD. May be negative. Whether overage is on. Auto top-up trigger balance in USD. Auto top-up charge in USD. Whether auto top-up is off. ```json Example 200 response { "balance": 12.5, "extra_usage_enabled": true, "auto_topup_threshold": 5.0, "auto_topup_amount": 20.0, "auto_topup_disabled": false } ``` ## The overage toggle Overage is the switch that decides whether the wallet is ever spent. When it is **off**, your organization can only spend its plan allowance; once that runs out, managed work is declined at the gate. When it is **on**, work that exceeds the allowance is paid for from the wallet. **`PATCH /subscriptions/wallet/extra-usage`** — owner or admin. **Paid-only.** Whether to turn overage on (`true`) or off (`false`). The value is coerced to a boolean. Legacy alias for `extra_usage_enabled`, accepted for backward compatibility. Prefer `extra_usage_enabled`, which matches the field name in the `GET` response. ```bash cURL curl -X PATCH https://api.modulex.dev/subscriptions/wallet/extra-usage \ -H "Authorization: Bearer mx_live_8f3a2c1d9b7e4f60a1c2d3e4f5061728" \ -H "X-Organization-ID: 7f1c9a3e-2b6d-4c8a-9e10-3f4a5b6c7d8e" \ -H "Content-Type: application/json" \ -d '{"extra_usage_enabled": true}' ``` The new overage state. ```json Example 200 response { "extra_usage_enabled": true } ``` ### Paid-only: the 402 you get on Free Turning overage **on** requires an active paid subscription. If your organization is on the Free plan or has no active subscription, the request is declined: ```json 402 Payment Required { "detail": { "reason": "paid_subscription_required" } } ``` This `402` is a plain `HTTPException` shape — `{"detail": {"reason": "paid_subscription_required"}}` — **not** the flat `DenialEnvelope` shape that the usage gate returns. The two billing `402` contracts are different, and a client that handles billing errors must branch on both. The gate's wallet denials (`wallet_overage_disabled`, `wallet_insufficient`) use the `DenialEnvelope` shape covered in [Usage gating & limits](/billing/usage-gating) and [Errors & status codes](/api-reference/errors). Turning overage **off** is always allowed, on any plan. ### Turning overage on sets a baseline When you switch overage from off to on, ModuleX takes a snapshot of how many credits the current billing period has already used and records it as the **overage baseline**. From that point forward, the wallet only pays for overage incurred *after* you turned it on — it never retroactively bills for plan-allowance overflow that built up while overage was off. The reconciler bills wallet overage against a floor of `max(plan allowance, overage baseline)`, so flipping the toggle on does not produce a surprise charge for past usage. The baseline is re-snapshotted on every off-to-on transition, scoped to the current subscription and billing period. ## Manual top-ups A manual top-up opens a one-time Stripe Checkout to add a prepaid balance to the wallet. The amount is yours to choose within a fixed range, and the credit is applied to the wallet only after the payment succeeds. **`POST /subscriptions/wallet/topup`** — **owner-only.** **Paid-only.** The dollar amount to add to the wallet. Must be **at least $10 and at most $1,000**, finite, and have **no sub-cent precision** (no fraction smaller than a whole cent). The credited amount equals the amount paid; at $0.01 per credit, `amount_usd` × 100 credits are added — for example, $10 adds 1,000 credits. ```bash cURL curl -X POST https://api.modulex.dev/subscriptions/wallet/topup \ -H "Authorization: Bearer mx_live_8f3a2c1d9b7e4f60a1c2d3e4f5061728" \ -H "X-Organization-ID: 7f1c9a3e-2b6d-4c8a-9e10-3f4a5b6c7d8e" \ -H "Content-Type: application/json" \ -d '{"amount_usd": 50}' ``` The Checkout link to open. Opening it does not yet credit the wallet — see the note below. The Stripe Checkout URL to redirect the payer to. The validated dollar amount that will be charged. The number of credits the wallet will receive once the payment succeeds. ```json Example 200 response { "url": "https://checkout.stripe.com/c/pay/cs_live_a1B2c3D4e5F6g7H8i9J0", "amount_usd": 50.0, "credits": 5000 } ``` The top-up call returns a Checkout **link** — it does **not** credit the wallet. The balance is increased exactly once, after Stripe confirms the payment, through the `payment_intent.succeeded` webhook. That step is idempotent on the payment, so a duplicate or replayed event never double-credits. For the webhook contract, see [Subscriptions & Stripe](/billing/subscription-lifecycle). ### Top-up amounts at a glance | `amount_usd` | Credits added | Notes | | --- | --- | --- | | `10` | 1,000 | Minimum top-up | | `50` | 5,000 | | | `100` | 10,000 | | | `1000` | 100,000 | Maximum top-up | ### Top-up errors The amount is validated on the server before any Stripe or database work, so an invalid request never opens a Checkout. Returned for an invalid amount or a misconfigured organization. `amount_usd` is missing or not numeric. `amount_usd` is not finite (for example, infinity or NaN). The amount is below $10 or above $1,000. The amount has a fraction smaller than one cent. The organization has no Stripe customer record yet. The organization is on Free or has no active paid subscription: `{"detail": {"reason": "paid_subscription_required"}}`. The organization could not be found: `{"detail": "Organization not found"}`. Stripe is not configured on the server (no API key): `{"detail": "Stripe API key is not configured"}`. Stripe rejected the Checkout creation: `{"detail": "Stripe error: …"}`. These `400` / `402` / `404` / `500` / `502` responses use the plain `{"detail": …}` shape (or `{"detail": {"reason": …}}` for the paid gate), because `/subscriptions/wallet/topup` is an organization-management route rather than a metered-run surface. It does **not** return the gate's `DenialEnvelope`. See [Errors & status codes](/api-reference/errors) for the full envelope taxonomy. ## Auto top-up Auto top-up keeps the wallet funded without manual action. When enabled, ModuleX charges the configured amount whenever the balance falls to the threshold, so overage usage is not interrupted. | Setting | Default | Per-organization override | Meaning | | --- | --- | --- | --- | | Threshold (`auto_topup_threshold`) | **$5.00** | Yes | The balance at which a top-up fires. The trigger is proactive — it fires before the balance reaches zero, so a run is not paused waiting for funds. | | Amount (`auto_topup_amount`) | **$20.00** | Yes | How much each automatic charge adds to the wallet. | | Failure cutoff | **3 consecutive failures** | No | After three consecutive declined charges, auto top-up is turned off and `auto_topup_disabled` is set to `true`. | When an auto top-up charge succeeds, the credit lands through the same `payment_intent.succeeded` path as a manual top-up, and the consecutive-failure counter and the `auto_topup_disabled` flag are reset. Auto top-up settings appear in the wallet response (`auto_topup_threshold`, `auto_topup_amount`, `auto_topup_disabled`), and the defaults above are fixed in ModuleX's billing configuration. The exact API for changing the threshold and amount, and for re-enabling auto top-up after it has been disabled, is managed in the app's billing settings; a dedicated public endpoint for editing these values is not part of the documented wallet API at this time. ## Refunds A refund issued through Stripe is mirrored back to the wallet as a negative ledger entry, decreasing the balance by the refunded amount. The mirror is idempotent on the Stripe charge, so a replayed refund event is applied only once. A refund **never** changes your subscription status or plan entitlements — it only adjusts the wallet balance. ## Edge cases and behavior to know Overage is reconciled after managed work settles, so a burst of usage can push the wallet below zero before the next top-up. A negative balance is expected and self-corrects on your next top-up or auto top-up. Reading a wallet that has never existed returns `0`, never an unlimited balance. If `extra_usage_enabled` is `false`, the wallet is never spent — even if it holds a positive balance. Once your plan allowance is gone, managed work is declined at the gate until you turn overage back on. The balance is preserved. The wallet only covers usage **beyond** your monthly plan allowance. Within the allowance, no wallet spend occurs. See [Credits & metering](/billing/credits) for what consumes the allowance. Creating a top-up returns a Checkout URL; the wallet is credited only after the payment succeeds and the `payment_intent.succeeded` webhook is processed. If a payer abandons Checkout, no credit is applied. When overage is off or the wallet cannot cover a piece of work, the **usage gate** declines it with a flat `DenialEnvelope` `402` (`wallet_overage_disabled` or `wallet_insufficient`) — not the `{"detail": {"reason": "paid_subscription_required"}}` shape returned by the wallet management endpoints. See [Usage gating & limits](/billing/usage-gating). ## Related pages What a credit is and exactly what consumes credits and the wallet. The gate that decides whether the wallet is spent, and its 402/403/429 responses. Which plans include overage and the wallet, and what each allowance is. The Stripe webhook contract that applies top-ups and refunds to the wallet. Every billing error shape, including both 402 contracts. How plans, credits, the wallet, and the gate fit together. --- # AI Composer: text to workflow URL: https://docs.modulex.dev/concepts/ai-composer How the AI Composer turns a plain-English request into an editable workflow graph on the canvas, pauses to ask you questions, and lets you save or revert its changes. The **AI Composer** is the text-to-workflow agent in ModuleX. You describe what you want in plain English, and it builds an editable [workflow](/concepts/workflows-and-runs) on the [canvas](/workflow-builder/canvas) for you — adding nodes, wiring them together, and setting up inputs as it goes. You stay in control the whole time. The Composer pauses to ask you questions when it needs a decision or a credential, streams its work live, and never makes its changes permanent until you approve them. Type a request like "summarize new support emails and post them to Slack" and watch the workflow take shape on the canvas. Composer edits are pending until you save them. Don't like the result? Revert in one click and the workflow returns to where it was. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-1110 · [APP_VIDEO]** **[APP_VIDEO_DESCRIPTION]:** The Composer generating a workflow from a single prompt, end to end. **[APP_VIDEO_DETAILS]:** Screen-record the workflow builder with the Composer panel open. Type a prompt such as "When a new lead is added, summarize it and post to the #sales Slack channel", then show nodes appearing on the canvas as the agent streams its work, a save/revert action bar at the end, and a final saved state. 30–45s, 16:9, dark theme, no audio. Highlight the streamed build steps and the pending-changes bar. ## What the Composer does The Composer is an AI agent that edits a workflow's graph for you. Instead of dragging nodes onto the canvas yourself, you tell it the outcome you want and it assembles the steps. As it works, it can: It adds any of the nine [node types](/workflow-builder/nodes/overview) — for example an [LLM node](/workflow-builder/nodes/llm), a [tool node](/workflow-builder/nodes/tool), or a [knowledge node](/workflow-builder/nodes/knowledge) — and wires them in the right order. It defines the inputs your workflow accepts so the steps have the data they need to run. It looks at the [integrations](/integrations/overview) your organization has connected and picks the right tools for the job. It can do a test run of the workflow while building so it can catch problems early. The Composer edits the workflow live, but those edits are held as **pending changes** until you decide what to do with them (see [Save and revert](#save-and-revert)). This page explains the concept. To use the Composer hands-on inside the canvas, see AI Composer in the builder. ## Composer vs Assistant ModuleX has two AI agents, and they are easy to mix up. They share the same underlying chat engine, but they do different jobs. | | **AI Composer** | **[Assistant](/concepts/assistant)** | | --- | --- | --- | | What it produces | An editable workflow on the canvas | Answers and actions, in a chat | | Works on a workflow? | Yes — it builds and edits one | No — it has no workflow tools | | Where you find it | The [workflow builder](/workflow-builder/overview) | [Chat](/platform/chat/overview) | | Calls your integration tools? | While building and test-running | Directly, to get a task done | | Save / revert changes | Yes (pending changes on the canvas) | Not applicable — no canvas to change | Use the **Composer** when you want to *build something reusable* you can run again and again. Use the **[Assistant](/concepts/assistant)** when you want to *get a one-off task done now* in a conversation. Both agents pause the same way when they need your input, which is covered next. ## When the Composer asks you a question The Composer doesn't guess on important decisions. When it needs a choice, a piece of text, or access to a connected service, it pauses and asks. This is called **human-in-the-loop** (HITL). While it is waiting, the conversation stays open and nothing changes until you respond. You can also skip most questions if you'd rather the Composer decide. The Composer offers a short list of options and you pick one (or several). For example, choosing which model provider to use for a step. A simple confirmation before it does something — for instance, before running a sensitive action while test-building. A short written answer, such as the exact wording for a message or the name of a channel. If a step needs a service you haven't connected yet, the Composer asks you to connect it. For services that use OAuth, this opens the provider's sign-in flow, and the Composer **continues on its own** as soon as you finish — you don't need to re-send your request. Only one question can be open per conversation at a time, and only the person who was asked can answer it. If you start a new request while a question is still open, the Composer asks you to answer the open one first. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-1111 · [SCREENSHOT]** **[SCREENSHOT_DESCRIPTION]:** A Composer human-in-the-loop question rendered inline in the chat. **[SCREENSHOT_DETAILS]:** Capture the Composer chat panel showing a single-choice question (for example "Which Slack channel should I post to?") with a few selectable options and a visible "Skip for now" button. Dark theme, builder context visible behind the panel. Crop tightly to the question card. For the developer-facing detail on how pause-and-resume works over the API, see [Human-in-the-loop resume](/realtime/hitl). The same idea also exists as a workflow step you can place yourself — the [Interrupt node](/workflow-builder/nodes/interrupt). ## Save and revert Composer changes are never silently committed. After the Composer finishes a turn, its edits sit on the canvas as **pending changes**, and an action bar gives you two choices. Send your request. The Composer streams its work and applies edits to the canvas as it goes. When the turn finishes, the workflow shows the Composer's edits with a pending-changes action bar. Nothing is locked in yet. **Save** keeps the changes and updates the workflow's saved snapshot. **Revert** restores the workflow to its last saved snapshot and discards the edits. Confirms the Composer's edits and refreshes the saved snapshot of your workflow. This becomes the new baseline for the next revert. Restores the workflow to the snapshot from before this turn and clears the pending changes — a clean undo of the Composer's latest edits. You can keep iterating without committing: send another request, see the new result, and only save once you're happy. Each save sets the baseline that a later revert will return to. ## Building a turn programmatically Most people use the Composer through the [builder](/workflow-builder/composer). If you're working from code, a Composer turn starts with a single call that creates or continues a chat and kicks off the agent. The response gives you a stream URL you can follow to watch the build live. Every request needs your API key and your organization context — see [Authentication](/api-reference/authentication). ```bash cURL curl -X POST https://api.modulex.dev/composer/chat \ -H "Authorization: Bearer mx_live_8Kqp2wZ7nR4tVx1aB6cD3eF" \ -H "X-Organization-ID: 7f3a2b10-9c4d-4e8a-b1f6-2d5c8e0a9b41" \ -H "Content-Type: application/json" \ -d '{ "workflow_id": "a1b2c3d4-5e6f-4708-9a1b-2c3d4e5f6a7b", "message": "Add an LLM node that summarizes the input text", "llm": { "integration_name": "anthropic", "provider_id": "anthropic", "model_id": "claude-haiku-3.5" } }' ``` ```python Python from modulex import Modulex client = Modulex( api_key="mx_live_8Kqp2wZ7nR4tVx1aB6cD3eF", organization_id="7f3a2b10-9c4d-4e8a-b1f6-2d5c8e0a9b41", ) turn = await client.composer.chat( "Add an LLM node that summarizes the input text", workflow_id="a1b2c3d4-5e6f-4708-9a1b-2c3d4e5f6a7b", llm={ "integration_name": "anthropic", "provider_id": "anthropic", "model_id": "claude-haiku-3.5", }, ) print(turn.stream_url) ``` ```javascript JavaScript import { Modulex } from "modulex-js"; const client = new Modulex({ apiKey: "mx_live_8Kqp2wZ7nR4tVx1aB6cD3eF", organizationId: "7f3a2b10-9c4d-4e8a-b1f6-2d5c8e0a9b41", }); const turn = await client.composer.chat({ workflowId: "a1b2c3d4-5e6f-4708-9a1b-2c3d4e5f6a7b", message: "Add an LLM node that summarizes the input text", llm: { integration_name: "anthropic", provider_id: "anthropic", model_id: "claude-haiku-3.5", }, }); console.log(turn.stream_url); ``` A successful response confirms the turn is running and tells you where to stream it: ```json Response { "status": "running", "composer_chat_id": "f2c1a890-3b47-4d62-8e15-6a9c0d2e4f73", "workflow_id": "a1b2c3d4-5e6f-4708-9a1b-2c3d4e5f6a7b", "run_id": "8e7d6c5b-4a39-4218-9f0e-1d2c3b4a5e6f", "thread_id": "f2c1a890-3b47-4d62-8e15-6a9c0d2e4f73", "stream_url": "/composer/chat/f2c1a890-3b47-4d62-8e15-6a9c0d2e4f73/listen/8e7d6c5b-4a39-4218-9f0e-1d2c3b4a5e6f" } ``` From there you follow the `stream_url` to watch the agent's steps, answer any human-in-the-loop questions it raises, and then confirm with save or revert. The Composer runs on managed AI usage, so it consumes [credits](/concepts/credits-billing) — one credit per turn, plus the cost of the AI it uses. If your organization is out of credits or over a limit, the call is refused before any work begins with a billing response (a `402`, `403`, or `429`). See [Usage gating & limits](/billing/usage-gating) and [Errors & status codes](/api-reference/errors) for the exact shapes. The Composer is available to organization **owners and admins**. The previous **member** role has been retired, so any account that can open the Composer can also use it. See [Organizations, roles & membership](/concepts/organizations-roles). ## Known limitations A few related behaviors are not fully wired today. We list them here so you don't build a flow that depends on them. Deleting nodes **one at a time** is fully supported and syncs to everyone editing the canvas with you. A multi-node bulk-delete in a single action is **not** handled by the realtime collaboration server, so it may not reach your co-editors reliably. Delete nodes individually when you're working with others. See [Realtime co-editing & external sync](/workflow-builder/realtime-coediting) and [Known limitations](/reference/known-limitations). When the Composer asks for a credential and you connect a service via OAuth, the connect-and-continue flow works. However, automatically **refreshing** an already-expired OAuth credential from the app is **not available** today — reconnect the integration instead of relying on an in-app refresh. See [Managing credentials](/integrations/managing-credentials) and [Known limitations](/reference/known-limitations). ## Next steps Open the Composer panel on the canvas and build hands-on. A step-by-step walkthrough from prompt to running workflow. The chat agent that gets one-off tasks done without a workflow. The developer-facing detail on pausing and resuming a turn. --- # Assistant: agentic chat with tools and approvals URL: https://docs.modulex.dev/concepts/assistant The Assistant is ModuleX's workflow-independent agentic chat. It searches your connected tools, calls them step by step, pauses for approval on risky actions, and shares the same chat store as the AI Composer. The Assistant is an agentic chat that gets work done with your connected tools. You describe a task in plain language, and the Assistant decides which tools to call, runs them one step at a time, and pauses to ask you when it needs a decision or an approval. There is no workflow to build first. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-1120 · [APP_VIDEO]** **[APP_VIDEO_DESCRIPTION]:** The Assistant completing a multi-step task end to end in the chat surface. **[APP_VIDEO_DETAILS]:** Screen-record the ModuleX app chat. Capture a single turn where the user types a request like "Summarize my three newest GitHub issues and post the summary to the #standup Slack channel," then show the Assistant streaming text, calling a discovery tool, calling a GitHub tool, pausing on an approval card for the Slack post, the user approving, and the final answer. 30–45s, 16:9, light theme, no audio narration. Highlight the tool-call chips and the approval card. ## What the Assistant is Think of the Assistant as a colleague who already knows how to use every service you have connected. You ask, it acts. The Assistant can search your connected integrations and call their actions directly — for example, list your repositories or send a message — using your organization's stored credentials. Each turn is an agentic loop: the Assistant reasons, picks a tool, runs it, reads the result, and repeats until the task is done or it needs you. Before a destructive action, or when it needs a credential or a decision, the Assistant stops and asks. Nothing risky happens without your say-so. The Assistant is **workflow-independent**: it has no tools to inspect, build, run, or edit a workflow. It exists to answer questions and complete tasks directly. If you want to design a reusable workflow graph instead, that is what the [AI Composer](/concepts/ai-composer) is for. For the full product walkthrough — capabilities, models, examples, and limits — see the [Assistant overview](/assistant/overview). ## What the Assistant can do Plain language-model Q&A, with no tools required when none are needed. Retrieve answers from your connected knowledge bases. See [Knowledge & RAG](/concepts/knowledge-rag). Discover what you have connected and run a tool action directly. See [Integrations overview](/integrations/overview). When a task needs a service you have not connected yet, the Assistant asks you to add the credential — including an OAuth connect flow. See [Credentials & OAuth2](/concepts/credentials-oauth). ## How a turn works Every message you send is one **turn** — one request that the Assistant works on until it finishes or stops to ask you something. You type a task. The Assistant starts a run and begins streaming its work back to you in real time. It writes out its thinking, then calls a tool — for example, discovering your integrations or executing a specific action. You see each tool call and its result as it happens. If the next step is risky, needs a decision, or needs a credential, the Assistant pauses and shows you a question. The turn waits for your answer. See [Approval gates and questions](#approval-gates-and-questions). When the task is complete, the Assistant returns its final answer and the run ends. The Assistant's actions stream live over Server-Sent Events. The same stream powers the in-app chat and the SDKs — see [SSE run streaming](/realtime/sse-streaming) and [Streaming responses](/assistant/streaming). ## Approval gates and questions The Assistant is built to act, not to ask permission for everything. It runs ordinary, low-risk actions on its own — but it draws a hard line at anything that could change or remove your data, and at anything it cannot do without input from you. Any action that looks destructive — deletes, drops, truncates, purges, raw database statements, and similar — always pauses for a yes/no approval before it runs. The Assistant shows you an approval card describing the action; only an explicit **yes** lets it proceed. Secret-looking values in the preview are redacted. For non-destructive actions, the Assistant just runs them as part of getting your task done. This keeps everyday work fast. (This is the key difference from the Composer, which gates most write actions while it is editing a workflow.) When the path forward is ambiguous, the Assistant can ask you a structured question: pick one option, pick several, answer yes/no, or type free text. Your answer steers the rest of the turn. If a task needs a service you have not connected, the Assistant asks you to add the credential. For OAuth services, completing the connect flow resumes the turn automatically — you do not have to re-send your message. A chat can hold **one pending question at a time**. While the Assistant is waiting on your answer, sending a new message to that chat is rejected until you respond, so a turn can never fork. For the full pause-and-resume model, see [Human-in-the-loop](/assistant/human-in-the-loop) and the [HITL resume reference](/realtime/hitl). ## Assistant and Composer share one chat store The Assistant and the [AI Composer](/concepts/ai-composer) are two surfaces over the **same** chat machinery. Both store their conversations in the shared `ComposerChat` store and reuse the same agent engine, streaming, approval/resume flow, and billing. A single field tells them apart: | Field | Assistant | AI Composer | | --- | --- | --- | | `kind` | `assistant` | `composer` | | Bound to a workflow | No — never workflow-bound | Yes — edits a specific workflow graph | | Workflow tools | None | Builds and edits the graph | | What it is for | Getting tasks done with tools | Designing and editing a workflow | Every operation is scoped by `kind`, so an Assistant chat and a Composer chat can never act on each other's conversations. Because both live in the shared store, OAuth connect-and-resume works identically across both surfaces. ## When to use the Assistant vs the Composer For one-off or ad-hoc tasks you want done now: ask a question, pull data from a tool, search your knowledge, or chain a few tool calls together. Nothing is saved as a reusable graph. When you want a **repeatable** automation. The Composer turns a plain-English description into an editable [workflow graph](/concepts/workflow-engine) you can run again, schedule, and call from the API. A simple rule: if you would run it more than once, build it with the Composer. If you just need it handled, ask the Assistant. ## Who can use it, and what it costs The Assistant requires an organization **owner** or **admin** role. The legacy `member` role is retired and cannot use the Assistant. See [Organizations, roles & membership](/concepts/organizations-roles) and [Roles & permissions](/security/roles-permissions). Each Assistant turn is metered usage and runs through the billing gate, so it consumes [credits](/concepts/credits-billing). Resuming a paused turn after you answer a question is part of the same turn — you are not charged twice. ### When usage is blocked Because Assistant turns are managed usage, they pass through the live billing admission gate **before** any work starts. If your organization is out of credits, over a limit, or rate-limited, the turn is rejected up front with a structured denial: | Status | Meaning | | --- | --- | | `402` | Out of credits, or wallet/overage cannot cover the turn. | | `403` | A plan quota for this usage is exhausted. | | `429` | Too many requests too quickly — slow down and retry. | These denials use ModuleX's flat denial envelope (`code`, `layer`, `key`, `current`, `limit`, `reason`), which is different from ordinary validation errors. For the exact shapes and how to handle them, see [Usage gating & limits](/billing/usage-gating), [Permissions & limits](/assistant/permissions-and-limits), and [Errors & status codes](/api-reference/errors). ## Where to go next The full product tour of the Assistant. Where you talk to the Assistant in the app. Build a reusable workflow from a prompt instead. --- # Credentials & OAuth2 (PKCE) URL: https://docs.modulex.dev/concepts/credentials-oauth How ModuleX stores integration credentials, runs the OAuth2 authorization-code flow with PKCE, encrypts secrets at rest, and resolves which credential a tool or model uses at run time — with the full credentials API and SDK reference. A **credential** is a stored, encrypted authentication record that links one [organization](/concepts/organizations-roles) to one integration — a tool, an LLM provider, or a knowledge provider. Credentials never live in your workflow definitions; they are resolved at run time, decrypted in memory, and used to authenticate the outbound call ModuleX makes on your behalf. This page is the reference for how credentials are modelled, how the OAuth2 authorization-code flow works (with PKCE on by default), how secrets are encrypted at rest, and how ModuleX picks which credential to use when a [tool](/workflow-builder/nodes/tool) or [model](/integrations/llm-providers/overview) runs. For the connect-it walkthrough see [Authentication & credentials](/integrations/authentication); for day-to-day management see [Managing credentials](/integrations/managing-credentials). Every credentials endpoint requires **owner or admin** role on the organization, and every request must carry both `Authorization: Bearer mx_live_…` and the `X-Organization-ID` header. The organization is taken from that header, never from the request body. See [Authentication](/api-reference/authentication) and [Roles & permissions](/security/roles-permissions). ## The credential model Each credential record belongs to exactly one organization and one integration, and it stores its secrets in encrypted columns. The non-sensitive fields below are what every API and SDK response returns; the secret material (`auth_data`, `oauth_config`) is never returned in clear text. The credential's UUID. This is the handle you pass to every per-credential operation. The integration this credential authenticates (for example `github`, `openai`, `slack`). The integration category: `tool`, `llm_provider`, or `knowledge_provider`. A human-readable label. Defaults to the integration's display name when you do not set one. The authentication mechanism. One of `oauth2`, `api_key`, `bearer_token`, `modulex_key`, `custom`, or `internal`. (The backend also still accepts the legacy value `bearer`. The `internal` type is system-managed and is **excluded from list output**.) Whether this is the default credential for its integration in the organization. Only one credential per integration can be the default. ISO-8601 timestamp of creation. ISO-8601 timestamp of the last update. ISO-8601 timestamp of the last time the credential was resolved for a run, or `null` if never used. ISO-8601 expiry timestamp. For OAuth2 credentials this is the access-token expiry derived from the provider's `expires_in`. Arbitrary metadata. For MCP server credentials this holds the discovered tool catalog. Note the field is `credentials_metadata` (snake_case) on the wire. **Wire casing.** The credentials API does not camelCase its responses. Fields such as `credentials_metadata`, `auth_type`, `display_name`, and `created_by_email` are snake_case on the wire. The SDKs accept camelCase **parameters** (for example `integrationName`, `makeDefault`) and translate them for you, but they return the snake_case response shapes unchanged. ### The six authentication types `auth_type` records how a credential authenticates. When you create a credential, ModuleX auto-detects the type from the body you send (see [Create a credential](#create-a-credential)). | `auth_type` | What it stores | Set by | | --- | --- | --- | | `oauth2` | An OAuth2 access token (+ optional refresh token and `oauth_config`) | The OAuth2 flow, or `auth_data.access_token` + a top-level `oauth_config` | | `api_key` | A single API key | `auth_data.api_key` present in the body | | `bearer_token` | A bearer token | `auth_data.token` or `auth_data.bearer_token` present | | `modulex_key` | A pointer to a ModuleX-managed pooled key (billed in credits) | `auth_type: "modulex_key"` in the body | | `custom` | Free-form auth data validated by the integration | `auth_type: "custom"` in the body | | `internal` | System-managed secrets (for example managed knowledge) | The platform; never created by you and never listed | **`modulex_key` is metered; your own keys are not.** A `modulex_key` credential routes through ModuleX-provisioned provider keys and consumes [credits](/concepts/credits-billing) on every use, gated by your plan. Credentials you bring yourself (BYOK) — `api_key`, `bearer_token`, `oauth2`, `custom` — are billed by the provider directly and carry **no** ModuleX credit gate. See [Credits & metering](/billing/credits). **🎬 MEDIA PLACEHOLDER · MX-MEDIA-1140 · [IMAGE]** **[IMAGE_DESCRIPTION]:** A diagram of the credential model: one organization, many credentials, each linked to one integration and one auth type. **[IMAGE_DETAILS]:** Center-left, an "Organization" node. Arrows fan out to several credential cards, each labelled with an integration (GitHub, OpenAI, Slack) and an auth-type pill (`oauth2`, `api_key`, `modulex_key`). Show one card marked "default" for its integration. Annotate that secrets are stored encrypted and never returned. Light theme, 16:9, minimal text so it reads at a glance. ## OAuth2 authorization-code flow (with PKCE) For integrations that support OAuth2, ModuleX runs the standard authorization-code flow with **PKCE (Proof Key for Code Exchange, S256) on by default**. The flow has two API steps you drive — **initiate** and the **callback** — and the provider's browser redirect connects them. A one-time `state` token (held in Redis with a 5-minute TTL) ties the two halves together. Call `POST /credentials/oauth2/initiate` with the integration name and a `redirect_uri`. ModuleX generates the PKCE `code_verifier`/`code_challenge`, stores the flow state in Redis (5-minute TTL), and returns an `authorization_url` plus a `state` token. Redirect the user's browser to the returned `authorization_url`. The user authenticates with the provider and grants the requested scopes. The provider redirects the browser to `GET /credentials/oauth2/callback` with a `code` and the `state`. This endpoint is **anonymous** — it cannot carry your app token, so trust comes from the one-time `state` it reads (and deletes) from Redis. The callback exchanges the authorization code for tokens, encrypts them, creates an `oauth2` credential, and **302-redirects the browser** to your frontend landing page with the result appended as query parameters — never as JSON. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-1141 · [IMAGE]** **[IMAGE_DESCRIPTION]:** A sequence diagram of the OAuth2 PKCE flow across the four actors. **[IMAGE_DETAILS]:** Four lanes — User's browser, Your app, ModuleX backend, Provider. Show: app → ModuleX `initiate` (returns authorization_url + state); browser → Provider (authorize); Provider → ModuleX `callback` (code + state); ModuleX → Provider (exchange code, PKCE verifier); ModuleX stores encrypted credential; ModuleX → browser 302 to frontend (status=success&credential_id). Mark the Redis-held `state` with a 5-minute TTL. Light theme, 16:9. ### Initiate the OAuth2 flow Start an OAuth2 authorization-code flow and get back the URL to send the user to. The integration to authorize. It must declare an `oauth2` auth schema, or the call returns `400` "does not support OAuth2". The callback URL the provider redirects back to. Use the ModuleX callback, `https://api.modulex.dev/credentials/oauth2/callback`. When `true`, the flow uses ModuleX's managed OAuth application for the integration. When `false`, you must supply `custom_oauth_config`. Your own OAuth app configuration. Required when `use_modulex_oauth` is `false`; must include `client_id` and `client_secret`. The `auth_url`/`token_url` are taken from the integration's schema. Space-separated scopes to request. Defaults to the provider's or schema's configured scopes. Label for the credential that will be created on success. Whether to set the resulting credential as the default for its integration. Per-user setup environment variables to fold into the persisted `auth_data`, keyed by raw env-var name. Only keys the integration is allowed to read are retained. AI Composer chat to atomically resume once the callback completes. Must be supplied together with `composer_request_id` and `composer_llm_config` — partial linkage returns `400`. The Composer interrupt request id, validated on the callback. All-or-none with `composer_chat_id` and `composer_llm_config`. LLM configuration used to rebuild the chat model on Composer resume. All-or-none with the two fields above. **Response** — `200 OK`: The provider URL to redirect the user to. The opaque one-time CSRF/state token correlating this flow. Valid for 5 minutes. ```bash cURL curl -X POST https://api.modulex.dev/credentials/oauth2/initiate \ -H "Authorization: Bearer mx_live_xxx" \ -H "X-Organization-ID: 11111111-1111-1111-1111-111111111111" \ -H "Content-Type: application/json" \ -d '{ "integration_name": "github", "redirect_uri": "https://api.modulex.dev/credentials/oauth2/callback", "use_modulex_oauth": true, "make_default": true }' # → {"authorization_url":"https://github.com/login/oauth/authorize?...","state":"f3a1...token"} ``` ```python Python from modulex import Modulex async with Modulex( api_key="mx_live_xxx", organization_id="11111111-1111-1111-1111-111111111111", ) as client: flow = await client.credentials.initiate_oauth2( "github", redirect_uri="https://api.modulex.dev/credentials/oauth2/callback", use_modulex_oauth=True, make_default=True, ) # Redirect the user's browser to flow.authorization_url print(flow.authorization_url, flow.state) ``` ```javascript JavaScript import { Modulex } from "modulex-js"; const client = new Modulex({ apiKey: "mx_live_xxx", organizationId: "11111111-1111-1111-1111-111111111111", }); const flow = await client.credentials.initiateOAuth2({ integrationName: "github", redirectUri: "https://api.modulex.dev/credentials/oauth2/callback", useModulexOauth: true, makeDefault: true, }); // Redirect the user's browser to flow.authorization_url console.log(flow.authorization_url, flow.state); ``` | Status | Cause | | --- | --- | | `400` | Integration has no `oauth2` schema; ModuleX OAuth app not configured; missing `custom_oauth_config` when `use_modulex_oauth` is `false`; partial Composer linkage | | `404` | Integration not found | | `503` | OAuth state storage (Redis) unavailable | | `500` | Unexpected error during initiation | ### The callback (browser redirect target) `GET /credentials/oauth2/callback` is where the provider sends the user's browser. You do not call it directly and there is no SDK method for it — it is the redirect target you pass as `redirect_uri`. - It is **anonymous**: no `Authorization` header is required, because the browser arriving from the provider has no app token. Trust comes from the one-time `state` value, which is read and deleted from Redis on first use. - It **always responds with a `302` redirect** to your frontend landing page (default `https://app.modulex.dev/oauth/callback`), never with JSON — including on failure. - The result is appended as query parameters: - Success: `?status=success&integration=&credential_id=` - Failure: `?status=error&integration=&error_code=&message=` | `error_code` | Meaning | | --- | --- | | `oauth_denied` | The user denied access at the provider | | `oauth_provider_error` | The provider returned an error other than denial | | `missing_params` | The callback arrived without a `code` or `state` | | `invalid_state` | The `state` was unknown, expired (>5 min), or already used | | `token_exchange_failed` | Exchanging the authorization code for tokens failed | | `credential_creation_failed` | The credential could not be saved | | `invalid_credentials` | A Composer-linked credential failed its test before auto-resume | | `internal_error` | An unexpected error occurred | **Token exchange handles non-standard providers.** The code-for-token exchange sends client credentials either in the request body (`client_secret_post`, the default) or as HTTP Basic auth, depending on the integration's `token_auth_method`. It also handles providers that return a `200` with a `{"ok": false}` body (Slack-style failures) and redacts token values from logs. ### Refreshing OAuth2 tokens ModuleX keeps OAuth2 access tokens fresh **automatically** at run time. When a credential is resolved for a [tool](/workflow-builder/nodes/tool) or model call and its access token expires within the next 5 minutes, ModuleX refreshes it inline using the stored refresh token and re-encrypts the result before the call proceeds. You do not need to schedule or trigger this. **Do not rely on a manual or programmatic token refresh.** A manual refresh endpoint and the matching `refreshOAuth2` / `refresh_oauth2` SDK methods exist in the surface, but the path is **known-broken and is not a supported flow**: - In the ModuleX app, the credential UI's refresh action targets a frontend route that does not exist and **404s before reaching the backend**. - The backend handler itself has defects that prevent it from completing reliably. If a credential's authorization has fully lapsed (for example the refresh token was revoked or expired) and automatic refresh cannot recover it, **reconnect the integration** by running the OAuth2 flow again ([`POST /credentials/oauth2/initiate`](#initiate-the-oauth2-flow)). Re-authorizing replaces the stale tokens with a fresh credential. This is the only supported way to restore a lapsed OAuth2 connection. See [Known limitations](/reference/known-limitations). **Automatic refresh has one parity gap.** The inline run-time refresh always sends client credentials in the request body. A provider that requires HTTP Basic auth for refresh (for example Notion's `token_auth_method: "basic"`) would receive an `invalid_client` error on automatic refresh. For those providers, reconnect via the OAuth2 flow rather than depending on background refresh. ## Encryption at rest Credential secrets are stored in encrypted text columns and decrypted only in memory at the moment of use. ModuleX uses two distinct schemes, both built on a master `ENCRYPTION_KEY` (required in production, minimum 256 bits). See [Data security & encryption](/security/data-encryption) for the platform-wide picture. `auth_data` and `oauth_config` are encrypted with a key derived per credential via **HKDF-SHA256**, then sealed with **Fernet** (AES-128-CBC + HMAC-SHA256). ModuleX OAuth app `client_id`/`client_secret` are encrypted with **PBKDF2-HMAC-SHA256** (100,000 iterations, a random 16-byte salt per value), then Fernet-sealed. - **Key scoping.** The per-credential key is derived from `(organization_id, credential_id)`, so ciphertext cannot be moved between credentials or organizations — a copied blob simply fails to decrypt. - **No secret read-back.** Reading a credential returns masked placeholders only: an `oauth2` credential shows the literal `"OAuth2"`; an `api_key` shows a masked key like `sk-proj12…xyz`; a bearer token shows a masked token. The clear-text secret is never returned by any endpoint. - **Key-version scaffold.** Every encrypted table carries an `encryption_key_version` column (a key-rotation scaffold for SOC 2 CC6.1). It currently defaults to and stays at `1`. The canonical model class for a stored credential is `OrganizationIntegrationCredential` (the table is `organization_integration_credentials`). It is also exported under the back-compatibility aliases `OrganizationToolCredential` and `OrganizationCredential`. ## Credential resolution at run time When a tool or model executes, ModuleX must decide **which** credential to use and then prepare it for the outbound call. This happens in two stages. ### Stage 1 — pick a credential If the run specifies a `credential_id`, ModuleX uses it directly. The credential must match both the organization and the integration; otherwise resolution fails with "no credential found". Otherwise, ModuleX looks for the credential marked `is_default` for that integration and type. If there is no default, ModuleX falls back to any valid credential, preferring your **own** credentials over `modulex_key` ones, then the most recently created. If nothing valid exists, resolution fails. **Precedence, in one line:** explicit `credential_id` → the `is_default` credential → most-recent valid **user** credential → most-recent valid `modulex_key`. ### Stage 2 — prepare for execution The chosen credential is then prepared depending on its type: ModuleX decrypts the `auth_data`. For `oauth2` credentials it also checks the access-token expiry and **refreshes inline** if the token expires within 5 minutes (see [Refreshing OAuth2 tokens](#refreshing-oauth2-tokens)). There is **no credit check** on this path — your own keys are billed by the provider, not by ModuleX. The encrypted blob holds a **UUID pointer, not the real API key**. ModuleX decrypts the pointer, verifies it against the organization's managed-key records, fetches the real key from the system key pool, and runs a **credit-limit check** before use. If the credit limit is exceeded — or there is no active subscription — preparation fails and the call does not proceed. Tool usage on this path is metered after execution. **Where the billing gate lives.** The credentials CRUD and OAuth endpoints on this page do **not** themselves run a per-call credit gate — they return the standard `{"detail": …}` error shape. The credit gate fires during resolution of `modulex_key` credentials, which happens on the run / composer / assistant / managed-knowledge surfaces. Those surfaces return the flat `DenialEnvelope` (`402`/`403`/`429`) when usage is denied. See [Usage gating & limits](/billing/usage-gating) and [Errors & status codes](/api-reference/errors). ## The credentials API All paths below are literally `/credentials/...` (the router is mounted with no version prefix). Every route requires owner/admin and the auth headers shown above. Each operation maps to a JavaScript and Python SDK method; see the [SDK ⇄ API parity matrix](/sdks/parity). | Method & path | JavaScript | Python | Purpose | | --- | --- | --- | --- | | `GET /credentials` | `credentials.list` | `credentials.list` | List, grouped by integration (or flat when filtered) | | `GET /credentials/{id}` | `credentials.get` | `credentials.get` | Get one credential (masked) | | `POST /credentials` | `credentials.create` | `credentials.create` | Create a credential (type auto-detected) | | `PUT /credentials/{id}` | `credentials.update` | `credentials.update` | Update display name / metadata | | `DELETE /credentials/{id}` | `credentials.delete` | `credentials.delete` | Delete a credential (`204`) | | `POST /credentials/{id}/set-default` | `credentials.setDefault` | `credentials.set_default` | Make default for its integration | | `POST /credentials/test-temporary` | `credentials.testTemporary` | `credentials.test_temporary` | Validate auth data **before** saving | | `POST /credentials/{id}/test` | `credentials.test` | `credentials.test` | Test a saved credential | | `GET /credentials/{id}/usage` | `credentials.usage` | `credentials.usage` | Usage statistics | | `GET /credentials/{id}/audit` | `credentials.audit` | `credentials.audit` | Audit log entries | | `POST /credentials/oauth2/initiate` | `credentials.initiateOAuth2` | `credentials.initiate_oauth2` | Start an OAuth2 flow | | `GET /credentials/oauth2/callback` | — | — | Browser redirect target (no SDK method) | | `POST /credentials/{id}/oauth2/refresh` | `credentials.refreshOAuth2` | `credentials.refresh_oauth2` | Manual refresh — **broken, do not use** | | `POST /credentials/mcp-server` | `credentials.mcpServer` | `credentials.create_mcp_server` | Create an MCP server credential | | `POST /credentials/{id}/refresh-discovery` | `credentials.refreshDiscovery` | `credentials.refresh_mcp_discovery` | Re-discover MCP tools | | `GET /credentials/{id}/mcp-tools` | `credentials.mcpTools` | `credentials.mcp_tools` | List discovered MCP tools | | `POST /credentials/bulk-modulex-keys/stream` | `credentials.bulkModulexKeys` | `credentials.bulk_modulex_keys_stream` | SSE: bulk-create `modulex_key` credentials | ### List credentials `GET /credentials` returns credentials grouped by integration. Supplying `integration_name` switches the response to a flat list for that one integration. Return a flat list for this integration instead of the grouped shape. Filter by auth type (for example `oauth2`, `api_key`). Page size, `1`–`500`. Number of records to skip. Must be `≥ 0`. ```bash cURL curl "https://api.modulex.dev/credentials?auth_type=oauth2&limit=50" \ -H "Authorization: Bearer mx_live_xxx" \ -H "X-Organization-ID: 11111111-1111-1111-1111-111111111111" ``` ```python Python from modulex import Modulex async with Modulex( api_key="mx_live_xxx", organization_id="11111111-1111-1111-1111-111111111111", ) as client: grouped = await client.credentials.list(auth_type="oauth2", limit=50) github = await client.credentials.list(integration_name="github") # flat shape ``` ```javascript JavaScript import { Modulex } from "modulex-js"; const client = new Modulex({ apiKey: "mx_live_xxx", organizationId: "11111111-1111-1111-1111-111111111111", }); const grouped = await client.credentials.list({ authType: "oauth2", limit: 50 }); const github = await client.credentials.list({ integrationName: "github" }); // flat shape ``` ```json { "integrations": { "github": { "integration_name": "github", "integration_type": "tool", "total_count": 1, "auth_types": ["oauth2"], "credentials": [ { "credential_id": "b3f1c0de-1111-2222-3333-444455556666", "integration_name": "github", "integration_type": "tool", "display_name": "Production GitHub", "auth_type": "oauth2", "is_default": true, "created_at": "2026-01-19T12:00:00", "updated_at": "2026-01-19T12:00:00", "last_used_at": null, "expires_at": "2026-02-19T12:00:00", "credentials_metadata": null } ] } }, "total_credentials": 1, "total_integrations": 1, "filters": { "auth_type": "oauth2" } } ``` With `integration_name` set, the response is flat instead: ```json { "credentials": [ /* CredentialResponse[] */ ], "total_count": 1, "integration_name": "github", "filters": { "auth_type": null } } ``` ### Create a credential `POST /credentials` (returns `201`). The credential **type is auto-detected from the body** — you do not pass a route-level type. Match the trigger column from the [authentication types table](#the-six-authentication-types). The integration to create the credential for (for example `openai`). The secret material. Its shape selects the type: `{"api_key": "…"}` → `api_key`; `{"token": "…"}` or `{"bearer_token": "…"}` → `bearer_token`; `{"access_token": "…"}` + a top-level `oauth_config` → `oauth2`. Set explicitly to `modulex_key` or `custom` to select those types. For other types the value is inferred from `auth_data`. OAuth2 configuration (`token_url`, `client_id`, `client_secret`, …). Required to create an `oauth2` credential directly. Stored encrypted. Human-readable label. Set this credential as the default for its integration. ISO-8601 datetime after which the credential is treated as expired. ```bash cURL curl -X POST https://api.modulex.dev/credentials \ -H "Authorization: Bearer mx_live_xxx" \ -H "X-Organization-ID: 11111111-1111-1111-1111-111111111111" \ -H "Content-Type: application/json" \ -d '{ "integration_name": "openai", "display_name": "Production OpenAI key", "make_default": true, "auth_data": { "api_key": "sk-proj-abc123" } }' ``` ```python Python from modulex import Modulex async with Modulex( api_key="mx_live_xxx", organization_id="11111111-1111-1111-1111-111111111111", ) as client: credential = await client.credentials.create( "openai", display_name="Production OpenAI key", make_default=True, auth_data={"api_key": "sk-proj-abc123"}, ) ``` ```javascript JavaScript import { Modulex } from "modulex-js"; const client = new Modulex({ apiKey: "mx_live_xxx", organizationId: "11111111-1111-1111-1111-111111111111", }); const credential = await client.credentials.create({ integrationName: "openai", displayName: "Production OpenAI key", makeDefault: true, authData: { api_key: "sk-proj-abc123" }, }); ``` For OAuth2, prefer the [initiate flow](#initiate-the-oauth2-flow) over creating an `oauth2` credential directly — it runs PKCE and the code exchange for you. ### Test a credential Two operations validate credentials against the integration's configured test endpoint: - `POST /credentials/test-temporary` validates auth data **before** you save it. It takes `integration_name`, `auth_type`, and `auth_data`. - `POST /credentials/{id}/test` validates a **saved** credential. An expired credential returns `is_valid: false` with "Credential has expired". A test response reports `is_valid`, a human-readable `message`, `tested_at`, and a `test_method` of `api_call`, `basic`, or `none`. Integrations without a configured test endpoint report `test_method: "none"` and `is_valid: true` with a "no test endpoint" message rather than failing. ```bash cURL curl -X POST https://api.modulex.dev/credentials/test-temporary \ -H "Authorization: Bearer mx_live_xxx" \ -H "X-Organization-ID: 11111111-1111-1111-1111-111111111111" \ -H "Content-Type: application/json" \ -d '{ "integration_name": "tavily", "auth_type": "api_key", "auth_data": { "api_key": "tvly-abc123" } }' ``` ```python Python result = await client.credentials.test_temporary( "tavily", "api_key", {"api_key": "tvly-abc123"}, ) saved = await client.credentials.test("b3f1c0de-1111-2222-3333-444455556666") ``` ```javascript JavaScript const result = await client.credentials.testTemporary({ integrationName: "tavily", authType: "api_key", authData: { api_key: "tvly-abc123" }, }); const saved = await client.credentials.test("b3f1c0de-1111-2222-3333-444455556666"); ``` ### Set default, update, and delete - `POST /credentials/{id}/set-default` makes a credential the default for its integration and unsets the previous default. - `PUT /credentials/{id}` updates only `display_name` and `metadata`. Secrets are immutable through this route — to change a secret, reconnect (OAuth2) or create a new credential and delete the old one. There is no rotate endpoint. - `DELETE /credentials/{id}` permanently deletes a credential and returns `204 No Content`. ```bash cURL # Make default curl -X POST https://api.modulex.dev/credentials/b3f1c0de-1111-2222-3333-444455556666/set-default \ -H "Authorization: Bearer mx_live_xxx" \ -H "X-Organization-ID: 11111111-1111-1111-1111-111111111111" # Delete (204) curl -X DELETE https://api.modulex.dev/credentials/b3f1c0de-1111-2222-3333-444455556666 \ -H "Authorization: Bearer mx_live_xxx" \ -H "X-Organization-ID: 11111111-1111-1111-1111-111111111111" ``` ```python Python await client.credentials.set_default("b3f1c0de-1111-2222-3333-444455556666") await client.credentials.update( "b3f1c0de-1111-2222-3333-444455556666", display_name="GitHub (CI)", ) await client.credentials.delete("b3f1c0de-1111-2222-3333-444455556666") ``` ```javascript JavaScript await client.credentials.setDefault("b3f1c0de-1111-2222-3333-444455556666"); await client.credentials.update("b3f1c0de-1111-2222-3333-444455556666", { displayName: "GitHub (CI)", }); await client.credentials.delete("b3f1c0de-1111-2222-3333-444455556666"); ``` ## MCP server credentials A credential can also point at an external [Model Context Protocol (MCP) server](/integrations/building/custom-mcp). Creating one connects to the server (over `streamable_http` by default), discovers its tools, and stores the catalog in `credentials_metadata`. MCP server credentials are persisted with `integration_name: "mcp_server"` and `auth_type: "custom"`. The MCP server's URL. Headers to send when connecting, for example `{"Authorization": "Bearer mcp-token"}`. Label for the credential. Set as default. ```bash cURL curl -X POST https://api.modulex.dev/credentials/mcp-server \ -H "Authorization: Bearer mx_live_xxx" \ -H "X-Organization-ID: 11111111-1111-1111-1111-111111111111" \ -H "Content-Type: application/json" \ -d '{ "server_url": "https://mcp.example.com/sse", "display_name": "Internal MCP", "headers": { "Authorization": "Bearer mcp-token" } }' ``` ```python Python mcp = await client.credentials.create_mcp_server( "https://mcp.example.com/sse", display_name="Internal MCP", headers={"Authorization": "Bearer mcp-token"}, ) tools = await client.credentials.mcp_tools(mcp.credential_id) refreshed = await client.credentials.refresh_mcp_discovery(mcp.credential_id) ``` ```javascript JavaScript const mcp = await client.credentials.mcpServer({ serverUrl: "https://mcp.example.com/sse", displayName: "Internal MCP", headers: { Authorization: "Bearer mcp-token" }, }); const tools = await client.credentials.mcpTools(mcp.credential_id); const refreshed = await client.credentials.refreshDiscovery(mcp.credential_id); ``` - `GET /credentials/{id}/mcp-tools` returns the discovered tool list and a total count. - `POST /credentials/{id}/refresh-discovery` re-discovers tools and reports what was added or removed. It is valid only for `mcp_server` credentials; calling it on any other credential returns `400`. ## Errors Credentials endpoints return the standard `{"detail": ""}` HTTPException envelope. There is **no `402`** on these routes — credit denial happens during run-time resolution, not on these CRUD calls (see [Where the billing gate lives](#stage-2--prepare-for-execution)). The status codes you will see across this subsystem: | Status | When | | --- | --- | | `201` | Credential created | | `204` | Credential deleted | | `302` | OAuth2 callback redirect (always, even on error) | | `400` | Validation error, unsupported auth, malformed body | | `401` / `403` | Missing/invalid auth, not a member, or not owner/admin | | `404` | Credential or integration not found | | `429` | Organization rate limit (with `X-RateLimit-*` and `Retry-After` headers; fail-open) | | `500` | Unexpected server error | | `503` | OAuth state storage (Redis) unavailable on initiate | See [Errors & status codes](/api-reference/errors) for the full error model across surfaces and [SDK errors & retries](/sdks/errors-retries) for how the SDKs surface them. ## Where to go next The connect-it walkthrough and the six integration auth schema variants. Create, scope, and rotate credentials in the app and via the API. How ModuleX encrypts credentials and manages keys platform-wide. Documented gaps, including the broken manual OAuth2 refresh path. --- # Credits & the billing model URL: https://docs.modulex.dev/concepts/credits-billing How ModuleX meters managed usage in credits, what spends them, the difference between managed and bring-your-own-key usage, and where the billing gate can pause a run. ModuleX charges for **managed usage** in **credits**. A credit is a small, fixed unit of value: **100 credits = $1.00**, so one credit is worth $0.01. Your plan includes a monthly credit allowance, and managed work — running a workflow, chatting with the [Assistant](/concepts/assistant), retrieving from managed knowledge, calling a tool — draws down that allowance as you go. This page is the mental model: where credits come from, what spends them, what is free, and what happens when the allowance runs out. For the exact metering rules and the wallet mechanics, follow the links to [Credits & metering](/billing/credits) and the [Billing & credits overview](/billing/overview). **🎬 MEDIA PLACEHOLDER · MX-MEDIA-1160 · [IMAGE]** **[IMAGE_DESCRIPTION]:** A single flow diagram showing where credits come from and where they go. **[IMAGE_DETAILS]:** Left to right: a "Plan allowance" box (monthly credits) feeding into a "Usage" column listing managed actions (Run / Assistant turn / Tool call / Managed knowledge retrieval & ingest). Below the plan box, a smaller "Wallet (prepaid)" box labeled "used after the allowance runs out, if overage is on." An arrow from Usage passes through a gate icon labeled "Usage gate (checks before each action)" before reaching a "Result" node. Off to the side, a separate, greyed-out lane labeled "BYOK — billed by your provider, not in credits." Light theme, 16:9, clean line icons, no UI chrome. Annotate the credit unit "100 credits = $1.00". ## Credits in one minute The unit ModuleX uses to meter managed usage. **100 credits = $1.00** (1 credit = $0.01). Allowances and the wallet are both counted in credits. Your [plan](/billing/plans) grants a monthly credit allowance. When that runs out, a paid organization with overage turned on can keep going using a prepaid [wallet](/billing/wallet). Managed work: a workflow [run](/concepts/workflows-and-runs), an Assistant turn, a tool call, and managed-knowledge retrieval and ingest. See [what costs credits](#what-costs-credits). [Bring-your-own-key (BYOK)](#managed-versus-byok-usage) usage is not charged in credits — your own provider bills you directly. **The credit value is fixed.** 100 credits always equal $1.00. Each paid plan includes a monthly credit allowance — for example, the $25/month plan includes 5,000 credits ($50 of managed usage), and the $100/month plan includes 20,000 credits ($200). See [Plans & pricing](/billing/plans) for current figures. ## What costs credits Credits are spent on **managed** actions — work ModuleX runs for you on ModuleX-provisioned providers. The table below is the plain-language summary; [Credits & metering](/billing/credits) has the exact per-action numbers. | Managed action | What it costs | | --- | --- | | Run a workflow, or take one Assistant turn | A flat base charge per run or turn, plus the cost of any model usage inside it. A turn is one message you send and the response it produces. | | Use a language model on managed credentials | Metered by tokens — input and output — at the model's rate, with a small system margin applied. | | Call an integration [tool](/workflow-builder/nodes/tool) | A flat base charge per tool call. | | Retrieve from managed ([modulexdb](/integrations/knowledge-providers/modulexdb)) knowledge | A flat base charge per search, plus the cost of embedding your query. | | Ingest a document into managed knowledge | A flat base charge per document, plus the cost of embedding its text. Retrying an unchanged document does not charge again. | **Resuming does not double-charge.** Each turn is charged exactly once. When the Assistant or a workflow pauses for [human-in-the-loop](/realtime/hitl) approval and then continues, resuming the same turn is not charged a second time. ### What does not cost credits Model and tool calls made on **your own** provider accounts are tracked for analytics only — never billed in credits. See [managed versus BYOK](#managed-versus-byok-usage). Retrieval and ingest on a knowledge base backed by **your own** vector store (Qdrant, Pinecone, MongoDB Atlas, or Weaviate) are not charged by ModuleX. Browsing workflows, editing the canvas, viewing runs, and managing settings are not metered in credits. Designing a workflow does not spend credits. Credits apply when managed work actually executes. ## Managed versus BYOK usage Every model, tool, and knowledge action runs in one of two modes. The mode decides whether ModuleX meters it in credits. ModuleX provides the model, tool, or vector store on your behalf — the managed providers are **modulexai** (models and tools) and **modulexdb** (knowledge). **Managed usage is billed in credits.** You connect your own provider accounts and ModuleX uses your keys. **BYOK usage is not charged in credits** — your provider bills you directly, with no ModuleX markup. **The rule in one line.** Managed (modulexai / modulexdb) usage spends credits. BYOK usage is uncosted by ModuleX and tracked for analytics only. For how to connect your own providers, see [LLM providers](/integrations/llm-providers/overview) and [external knowledge providers](/platform/knowledge/external-providers). ## When the allowance runs out Credits are drawn first from your plan's monthly allowance. What happens next depends on your plan and settings. Managed usage draws down your monthly credit allowance until it reaches zero. If you are on a paid plan and have **overage** turned on, further managed usage is funded by your prepaid [wallet](/billing/wallet). You can top the wallet up manually, or set auto top-up to refill it before it runs dry. Without an allowance or a funded wallet, the next managed action is declined rather than billed silently. See [the usage gate](#the-usage-gate-when-a-run-is-paused). For the full mechanics — how the wallet works, overage, top-ups, and auto top-up — see [Wallet & top-ups](/billing/wallet) and [Credits & metering](/billing/credits). ## The usage gate: when a run is paused ModuleX checks your credit and limit status **before** it starts a piece of managed work. This check is the **usage gate**, and it is live today. If you do not have the budget or are over a limit, the gate declines the action up front — so you are never charged for work that could not run. The gate applies on the **managed-usage surfaces**: starting a workflow [run](/concepts/workflows-and-runs), using the [AI Composer](/concepts/ai-composer), taking an [Assistant](/concepts/assistant) turn, and managed-knowledge retrieval and ingest. Plain read-and-manage actions — listing workflows, editing settings, viewing runs — do **not** go through this gate. **Where the gate does and does not apply.** A billing denial can appear on the managed-run surfaces above. It does **not** appear on ordinary list/create/update/delete and organization-settings requests, which use a different, plain error shape. See [Errors & status codes](/api-reference/errors) for the exact shapes. When the gate declines an action, ModuleX returns a billing denial. There are three kinds, by what was exceeded: Your plan allowance is used up and the wallet cannot cover the action (no overage, or an empty or insufficient balance). Top up the [wallet](/billing/wallet) or [upgrade your plan](/billing/plans). You have hit a plan limit on a resource, such as the number of knowledge bases. Upgrade or free up the resource. You are sending managed requests faster than your plan's per-minute rate. Slow down and retry; the response tells you how long to wait. Each billing denial is a structured response that tells you exactly what was hit — the kind of limit, the value, and the cap — so you can react automatically. The full response shape and how to handle it in code live on [Usage gating & limits](/billing/usage-gating) and [Errors & status codes](/api-reference/errors). **🎬 MEDIA PLACEHOLDER · MX-MEDIA-1161 · [SCREENSHOT]** **[SCREENSHOT_DESCRIPTION]:** The in-app message a user sees when the usage gate declines a managed action. **[SCREENSHOT_DETAILS]:** Capture the "out of credits" state in the ModuleX app — for example, attempting an Assistant turn or a workflow run with the plan allowance exhausted and no wallet overage. Show the inline notice that explains the action was paused (not silently charged) with a clear path to top up or upgrade. Crop to the notice and surrounding context, light theme, no personal data. If the empty-credit state cannot be reproduced, mark this as a synthetic mock built from the real component. ## How it fits together The whole billing picture: plans, credits, the wallet, and the gate. Free, Pro, Max, and Enterprise — allowances, limits, and pricing. The exact rules for what a credit is and what each action costs. The admission gate and its 402 / 403 / 429 responses, in detail. --- # Knowledge & RAG URL: https://docs.modulex.dev/concepts/knowledge-rag How ModuleX retrieves your company knowledge: knowledge bases, managed (modulexdb) versus bring-your-own-key storage, and the ingest and retrieval flow that powers RAG. ModuleX uses **retrieval-augmented generation (RAG)** to ground answers in your own content. You upload documents to a **knowledge base**, ModuleX prepares them for search, and your chats, the [Assistant](/concepts/assistant), and your [workflows](/concepts/workflows-and-runs) can then retrieve the most relevant passages on demand — so responses cite your material instead of guessing. This page explains the ideas you need: what a knowledge base is, the difference between managed and bring-your-own storage, and the two halves of RAG — getting content in (ingest) and getting answers out (retrieval). **🎬 MEDIA PLACEHOLDER · MX-MEDIA-1130 · [IMAGE]** **[IMAGE_DESCRIPTION]:** A single diagram showing the two halves of RAG side by side — ingest on the left, retrieval on the right. **[IMAGE_DETAILS]:** Left "Ingest" lane: a document icon → Parse → Chunk → Embed → stored as vectors in a knowledge base. Right "Retrieve" lane: a user question → Embed query → vector search over the same knowledge base → top matching chunks returned to a chat / Assistant / workflow. Show the knowledge base in the center connecting both lanes. Horizontal layout, light theme, 16:9, clean line icons, no UI chrome. Label the center store "Knowledge base (managed = modulexdb, or your own vector store)". ## What a knowledge base is A **knowledge base** is a named collection of your documents that ModuleX has prepared for search. Think of it as one searchable library: a set of product manuals, a policy handbook, or a folder of support articles. Each knowledge base carries its own settings for how documents are split and how they are turned into searchable vectors, so you can tune one library for long reference PDFs and another for short snippets — without affecting the rest. The files you upload. Each one moves through the [ingest](#getting-content-in-ingest) pipeline and ends up searchable. Each document is split into smaller passages. Retrieval returns chunks, not whole files, so answers stay focused. Every chunk is converted to a numeric vector that captures its meaning, which is what makes search by meaning possible. You can manage knowledge bases in the app or over the API. For the hands-on walkthrough, see [Knowledge overview](/platform/knowledge/overview) and the [Build a RAG knowledge base](/guides/build-a-knowledge-base) guide. Throughout these docs the feature is called **Knowledge** (singular). A single library is a **knowledge base**, often shortened to **KB**. ## Managed versus bring-your-own storage When you create a knowledge base, you choose where the vectors live. This choice decides both who hosts the storage and how usage is billed. ModuleX hosts the vector storage for you with the built-in **modulexdb** provider. Nothing to set up. **Ingest and retrieval are metered in [credits](/billing/credits).** Point a knowledge base at a vector store you already run. ModuleX reads and writes to it, and **usage on your own store is not charged in credits** — your provider bills you directly. ### Managed knowledge: modulexdb Managed knowledge bases use ModuleX's hosted vector store, **modulexdb**. They are the fastest way to start because there is no external account to connect. Because ModuleX runs the storage and the embedding calls on your behalf, **each retrieval and each document ingest spends credits** (see [How credits apply](#how-credits-apply)). ### Bring your own key (BYOK) If you already operate a vector database, you can connect it and keep your data in your own infrastructure. ModuleX supports these external knowledge providers: Use a Qdrant cluster as your vector store. Use a Pinecone index as your vector store. Use MongoDB Atlas Vector Search. Use a Weaviate instance as your vector store. **Billing in one line.** Managed (modulexdb) knowledge is billed in credits. BYOK knowledge is uncosted by ModuleX — it is tracked for analytics only, and any charges come from your own provider. See [Credits & metering](/billing/credits). ## Getting content in: ingest **Ingest** is the one-time preparation each document goes through after you upload it. ModuleX runs it in the background so you can keep working while large files process. You add a file to a knowledge base. Supported formats include PDF, Word (`.docx` / `.doc`), plain text, Markdown, HTML, CSV, JSON, Excel (`.xlsx`), and PowerPoint (`.pptx`). ModuleX extracts the readable text from the file, whatever its format. The text is split into smaller passages using the knowledge base's chunking settings, so each piece is a focused, retrievable unit. Each chunk is converted into an embedding — a vector that represents its meaning — and stored alongside the text in the knowledge base. ### Watching a document process Every document reports its progress, so you always know whether it is ready to search. A document moves through these states: - **Pending** — uploaded and waiting in the queue. - **Processing** — being parsed, chunked, and embedded. - **Completed** — fully ingested and ready to retrieve. - **Failed** — something went wrong during ingest. You can fix the source file and retry the document; retrying a document does not re-charge an unchanged ingest. For managing and monitoring documents in the app, see [Managing documents](/platform/knowledge/documents). ## Getting answers out: retrieval **Retrieval** happens every time something asks a question. ModuleX turns the question into an embedding, compares it against the stored chunks, and returns the closest matches. Those passages are then handed to the model so its answer is grounded in your content. ModuleX offers a few retrieval styles for different needs: Finds chunks by meaning, even when the wording differs from the question. This is the default style. Blends meaning-based search with exact keyword matching, which helps with names, codes, and acronyms. Searches several knowledge bases at once and merges the best results. Returns a ready-to-use block of text, assembled from the top chunks within a token budget, for dropping straight into a prompt. ### Where retrieval shows up You rarely call retrieval by hand. It runs wherever an answer should come from your own material: Ask a question in chat and get an answer drawn from your documents. The Assistant retrieves from your knowledge as one of the tools it can use. Add a step to a workflow that retrieves from a knowledge base. ### Retrieving over the API If you build on ModuleX directly, you can run a search against a knowledge base yourself. Authenticate every request with your API key and organization, exactly as on every other endpoint (see [Authentication](/api-reference/authentication)). ```bash cURL curl -X POST https://api.modulex.dev/knowledge-bases/7c1e9b2a-4f3d-4a21-9c77-2b1e0f9a3c44/search \ -H "Authorization: Bearer mx_live_8Kd2pQ7rExample" \ -H "X-Organization-ID: org_3xampleOrg42" \ -H "Content-Type: application/json" \ -d '{ "query": "What is our refund policy?", "top_k": 5, "min_score": 0.2 }' ``` ```python Python import asyncio from modulex import Modulex async def main(): async with Modulex( api_key="mx_live_8Kd2pQ7rExample", organization_id="org_3xampleOrg42", ) as mx: results = await mx.knowledge.search( "7c1e9b2a-4f3d-4a21-9c77-2b1e0f9a3c44", "What is our refund policy?", top_k=5, ) for match in results.matches: print(match.score, match.content) asyncio.run(main()) ``` ```javascript JavaScript import { Modulex } from "modulex"; const client = new Modulex({ apiKey: "mx_live_8Kd2pQ7rExample", organizationId: "org_3xampleOrg42", }); const results = await client.knowledge.search( "7c1e9b2a-4f3d-4a21-9c77-2b1e0f9a3c44", { query: "What is our refund policy?", topK: 5, minScore: 0.2 }, ); for (const match of results.matches) { console.log(match.score, match.content); } ``` A successful search returns the matching chunks ranked by relevance: ```json Response { "query": "What is our refund policy?", "knowledge_base_id": "7c1e9b2a-4f3d-4a21-9c77-2b1e0f9a3c44", "top_k": 5, "total_matches": 3, "matches": [ { "chunk_id": "c1a2b3c4-...", "document_id": "d9e8f7a6-...", "document_filename": "refund-policy.pdf", "chunk_index": 4, "score": 0.83, "content": "Refunds are available within 30 days of purchase...", "metadata": {} } ] } ``` On a **managed (modulexdb)** knowledge base, a search that would exceed your plan allowance can return a billing denial instead of results — a `402`, `403`, or `429` response. See [Usage gating & limits](/billing/usage-gating) and [Errors & status codes](/api-reference/errors) for the response shape and how to handle it. ## How credits apply Credits are only spent on **managed (modulexdb)** knowledge. BYOK knowledge bases are not charged by ModuleX. One credit is a small, fixed unit of managed usage (100 credits = $1.00). | Action on a managed knowledge base | What it costs | | --- | --- | | Ingest a document | A flat base charge per document, plus the cost of embedding its text. Retrying an unchanged document does not re-charge. | | Run a retrieval (any search style) | A flat base charge per search, plus the cost of embedding the query. | On a **BYOK** knowledge base, neither ingest nor retrieval spends credits. For the full pricing model — how credits are metered, the wallet, and overage — see [Credits & metering](/billing/credits) and [Billing & credits overview](/billing/overview). ## Plan limits Your plan sets how many knowledge bases you can keep, how large each uploaded file can be, and how many documents each knowledge base holds. The values below come from the current subscription plan configuration. | Limit | Free | Pro | Max | Enterprise | | --- | --- | --- | --- | --- | | Knowledge bases | 10 | 3 | 50 | Unlimited | | Max file size per upload | 10 MB | 50 MB | 100 MB | Unlimited | | Documents per knowledge base | 100 | 1,000 | 1,000 | Unlimited | The **Pro** knowledge-base count (3) is lower than **Free** (10) in the current plan configuration. This is a known inconsistency in the source data, shown here as-is — confirm the limit that applies to your organization on the [Plans & pricing](/billing/plans) page before relying on it. The per-upload file size is set by your **plan**, not a single fixed cap. ## Where to go next Create and manage knowledge bases in the ModuleX app. A start-to-finish guide: create a knowledge base, ingest documents, and query it. How ModuleX-hosted vector storage and retrieval work. Connect Qdrant, Pinecone, MongoDB Atlas, or Weaviate. --- # Organizations, roles & membership URL: https://docs.modulex.dev/concepts/organizations-roles How tenancy works in ModuleX: the organization is your workspace and billing boundary, the X-Organization-ID header scopes every request, and the two live roles are owner and admin. Everything you do in ModuleX happens inside an **organization**. It is your shared workspace and your billing boundary at the same time: your workflows, credentials, knowledge bases, plan, wallet, and credits all belong to one organization, and every run is metered against it. This page explains the three ideas you need to work confidently across a team: what an organization is, how ModuleX knows which one you are acting in, and the two roles that decide who can do what. For the request-level detail, follow the links to [Org context & X-Organization-ID](/security/org-context) and [Roles & permissions](/security/roles-permissions). ## The three ideas on this page Your workspace and billing boundary. Every resource and every charge belongs to one organization. The `X-Organization-ID` header tells ModuleX which organization a request runs in. Each member is an **owner** or an **admin**. There is no member role. ## The organization: your workspace and billing boundary An organization is a tenant. It is the line ModuleX draws around your work so that your data, your access, and your bill all stay together and stay separate from everyone else's. Workflows, [credentials](/concepts/credentials-oauth), [knowledge bases](/concepts/knowledge-rag), and chats live inside an organization. Members of that organization share them. The plan, the credit allowance, and the prepaid wallet belong to the organization — not to an individual. Every run is metered against the organization it runs in. You can belong to more than one organization — for example a personal workspace and a team workspace — and you can own up to **five** organizations. Each one is fully separate: switching between them swaps your entire set of resources, plan, and credits. You never create an account by hand on the API side. ModuleX uses a single identity provider (Clerk) for sign-in, and your ModuleX user and a default personal organization are created automatically the first time you sign in. See [Just-in-time provisioning](#how-you-get-your-first-organization) below. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-1150 · [IMAGE]** **[IMAGE_DESCRIPTION]:** A diagram showing one organization as a boundary that contains a user's resources and billing. **[IMAGE_DETAILS]:** One illustration (not a screenshot), 16:9, light theme, ModuleX brand palette. Draw a single rounded rectangle labeled "Organization (workspace + billing boundary)". Inside it, two stacked groups: a "Resources" group with small labeled chips (Workflows, Credentials, Knowledge bases, Chats) and a "Billing" group with chips (Plan, Credit allowance, Wallet). Above the boundary show two person icons labeled "owner" and "admin" pointing into it. To the right, show a second, separate, greyed-out organization boundary to convey isolation between organizations. Minimal text inside shapes. ## The org context: which organization am I in? Because you can belong to several organizations, every action has to say which one it applies to. ModuleX calls this the **org context**, and on the API it is carried by one header: **`X-Organization-ID`**. In the [ModuleX app](/platform/overview) you set the org context with the organization switcher, and the app sends the header for you. When you call the API or use an [SDK](/sdks/overview), you set it yourself. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-1151 · [SCREENSHOT]** **[SCREENSHOT_DESCRIPTION]:** The organization switcher in the ModuleX app. **[SCREENSHOT_DETAILS]:** Capture from the live ModuleX app. Show the organization switcher control (the menu where a user picks the active organization), expanded to reveal the list of organizations the user belongs to, with the currently active one highlighted. Light theme. Crop tightly to the switcher and its surrounding header; redact any real organization names. Annotate with a single callout arrow pointing at the active organization labeled "This sets your org context". ### Org context in API and SDK calls Every org-scoped request carries two headers: your API key as a bearer token, and the organization the request runs in. The example below lists the workflows in one organization. ```bash cURL curl https://api.modulex.dev/workflows \ -H "Authorization: Bearer mx_live_2f9c4a1b8e7d6c5a4b3f2e1d0c9b8a76" \ -H "X-Organization-ID: 8a1f3c2e-9b4d-4e7a-bc11-7d6e5f4a3b2c" ``` ```python Python import asyncio from modulex import Modulex async def main(): async with Modulex( api_key="mx_live_2f9c4a1b8e7d6c5a4b3f2e1d0c9b8a76", organization_id="8a1f3c2e-9b4d-4e7a-bc11-7d6e5f4a3b2c", ) as client: workflows = await client.workflows.list() print(workflows) asyncio.run(main()) ``` ```javascript JavaScript import { Modulex } from "modulex-js"; const client = new Modulex({ apiKey: "mx_live_2f9c4a1b8e7d6c5a4b3f2e1d0c9b8a76", organizationId: "8a1f3c2e-9b4d-4e7a-bc11-7d6e5f4a3b2c", }); const workflows = await client.workflows.list(); console.log(workflows); ``` If you omit `X-Organization-ID` on an org-scoped endpoint, the request is rejected with a `400` and the message `X-Organization-ID header is required`. If you are a member of the organization but not allowed to perform the action, you get a `403`. See [Authentication](/api-reference/authentication) for how the two headers fit together and [Errors & status codes](/api-reference/errors) for the full response shapes. You can also set a default organization once on the client and override it per call. The exact header handling, the SDK precedence rules, and how an API key can be scoped to a single organization are covered in [Org context & X-Organization-ID](/security/org-context). ## Roles: owner and admin Membership in an organization comes with a **role**. ModuleX has exactly two live roles. The role assigned to whoever creates the organization. Owners can do everything an admin can, plus owner-only actions such as removing members. An owner cannot leave their own organization. Full working access to the organization: build and run workflows, use the AI Composer and the Assistant, manage credentials and knowledge, and invite teammates. New members join as admins. There is **no member role**. A `member` role existed in earlier versions of ModuleX and was retired on 2026-06-20. The live roles are **owner** and **admin** only, and the API rejects any attempt to invite or set a role other than `admin`. If you see "member" referenced anywhere, treat it as legacy. The `owner` role is assigned only at the moment an organization is created; it is not something you set when inviting or updating a teammate. Every invited or updated member is an admin. ### What requires owner or admin Because the two live roles both have broad working access, most day-to-day actions are available to either role. A few of ModuleX's most capable surfaces require an owner or admin explicitly, and a small set of governance actions are owner-only. The following surfaces require the caller to be an **owner or admin** of the active organization: - The [AI Composer](/concepts/ai-composer) (turning plain English into a workflow). - The [Assistant](/concepts/assistant) (the agentic chat that calls your tools). - [Workflow schedules](/workflow-builder/execution/schedule) and related management. - Organization settings, the LLM catalog, and inviting teammates. A caller who is authenticated and a member of the organization but lacks the required role receives a `403`. A few governance actions are reserved for the **owner**: - Removing another member from the organization. - Owners cannot leave their own organization (transfer ownership or remove the organization instead). Source-of-truth: this owner/admin model is enforced by the backend on every request. For the per-endpoint permission map — which routes require `organization_admin_required` versus `organization_owner_required` — see [Roles & permissions](/security/roles-permissions). Note that the in-app interface may optimistically show edit controls before the backend confirms your role; the server always enforces the real role on each call. ## How you get your first organization You do not sign up through a ModuleX form. The first time you sign in, ModuleX provisions everything you need automatically — this is **just-in-time (JIT) provisioning**. You authenticate through ModuleX's identity provider (Clerk). The backend verifies your token; there is no separate ModuleX password or login endpoint. On your first valid sign-in, ModuleX creates your user record and a personal default organization, and makes you its **owner** — all in one step, so a second concurrent request never creates a duplicate. The new organization is seeded with sensible defaults so you can start immediately, and you land in it as the active org context. Your personal organization is named from the first available of your first name, your username, or the part of your email before the `@`. If none is available, it is named `My Workspace`. JIT provisioning happens for sign-in (your user identity). It does **not** apply to API keys: a key cannot exist before your user does, and creating an API key requires signing in first. See [Quickstart](/get-started/quickstart) to create your first key. ## Working as a team When you are ready to collaborate, you add people to an organization by **inviting** them. Invite a teammate by email. They join as an **admin**. Each invitation is valid for **7 days**. Only the invited email address can accept the invitation. Once accepted, they become a member of the organization with the admin role. The new admin shares the organization's workflows, credentials, knowledge, and billing. Owners can later remove a member; admins cannot. Inviting teammates is a paid capability. On the **Free** plan, sending an invite is declined with a `403` and the reason `TEAMMATES_NOT_ALLOWED`. Upgrade to **Pro** or higher to invite teammates. See [Plans & pricing](/billing/plans) for what each plan includes. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-1152 · [APP_VIDEO]** **[APP_VIDEO_DESCRIPTION]:** Inviting a teammate to an organization and the teammate accepting. **[APP_VIDEO_DETAILS]:** Screen recording from the live ModuleX app, ~30–45s, light theme, 16:9. Show an owner or admin opening the team/members area, sending an invite by email, and the resulting pending invitation. Then cut to the invited person's view accepting the invitation and landing in the shared organization as an admin. Redact real email addresses and organization names. Annotate the moment the new member appears in the members list with a callout "Joins as admin". ## How this connects to the rest of ModuleX The request-level detail: how the org context header is read, how API-key org scope is enforced, and what happens when it is missing. The per-endpoint permission map — exactly which actions require owner or admin. How managed usage is metered in credits against the organization, and where the billing gate applies. How chats can be kept personal or shared across the organization. --- # How ModuleX works URL: https://docs.modulex.dev/concepts/overview The end-to-end mental model behind ModuleX: you author a workflow, you run it, and you observe the run live as each step streams its progress. ModuleX runs on one loop you will see everywhere in these docs: **you author a workflow, you run it, and you observe the run as it happens.** Once that loop clicks, every other feature is a deeper version of one of those three steps. This page is the mental model. It does not teach you every setting — it shows you the shape of the whole system so the rest of the docs have somewhere to hang. For the term-by-term map, see [Core concepts at a glance](/get-started/core-concepts). **🎬 MEDIA PLACEHOLDER · MX-MEDIA-1080 · [IMAGE]** **[IMAGE_DESCRIPTION]:** A single end-to-end flow diagram showing the author → run → observe loop. **[IMAGE_DETAILS]:** One illustration (not a screenshot). Left-to-right flow in three labeled stages. Stage 1 "Author" shows two inputs converging on a workflow graph: a chat bubble labeled "AI Composer (plain English)" and a hand-drawn canvas labeled "Visual builder". Stage 2 "Run" shows the graph executing node by node, with a small ticket labeled "run". Stage 3 "Observe" shows a live event stream (stacked event chips: metadata, node update, done) flowing back to a person. Add a thin badge beneath Stage 2 reading "credit check before start" and a side note "can pause for your input". 16:9, light theme, ModuleX brand palette, minimal text inside shapes. ## The loop in one line Describe a process to the AI Composer, or build it on the visual canvas. Both produce the same editable **workflow** graph. Start a **run** from chat, the API, or an SDK. ModuleX checks your credit and capacity, then executes the graph step by step. Watch the run stream its progress event by event, pause it for your input when needed, and inspect the final result. There is a shortcut, too: the [Assistant](/concepts/assistant) collapses all three steps into one chat. You give it a goal, it picks and runs the tools itself, and you watch the same live stream — no workflow to author first. More on that below. ## Step 1 — Author a workflow A **workflow** is an editable graph that connects tools, data, and agents into a repeatable process. You build it one of two ways, and both ways edit the same graph. Tell the **AI Composer** what you want in plain English. It turns your description into a complete, editable workflow graph, then keeps editing the canvas as you chat with it. Open the **workflow builder** and place nodes by hand. You connect steps, set their options, and pass data between them with references. A workflow is assembled from a fixed set of **nine node types** — each step does one thing and writes its result back into the run so later steps can use it. You connect steps with edges, and you move data between them with a **reference** written `{{node_id.field}}`. You do not need the details now; the [workflow engine](/concepts/workflow-engine) and the [node types overview](/workflow-builder/nodes/overview) cover them in depth. Authoring is multi-user. Several people can edit the same canvas at once, with live cursors, presence, and node locks so edits never collide. See the [realtime and collaboration model](/concepts/realtime-model). ## Step 2 — Run it A **run** is one execution of a workflow. You can start a run from three places, and the result is the same execution either way. Trigger a workflow inside a chat and watch it run there. Run a workflow programmatically over REST and stream the result. Run from your own code with the official JavaScript or Python client. ### What happens when you press run Before any work starts, ModuleX runs a quick admission check: do you have the credit and the capacity to run right now? If yes, the run proceeds. If not, the request is declined cleanly with a billing error — nothing partial executes. This check applies to runs, AI Composer turns, Assistant turns, and managed-knowledge calls. ModuleX accepts the request and hands back a **run id** along with a stream you can listen on. The actual execution happens in the background, so the response comes back immediately. The engine walks the graph from its entry point. Every node does its work — calling a model, retrieving from a knowledge base, calling an integration tool, branching, and so on — and writes its result into the run state for the next step to read. The run ends in one of a small set of outcomes — it completes, it fails, or it is cancelled. Some runs pause partway to ask you a question first (see [Observe](#step-3-observe-the-run)). The admission check is real and live on the run, AI Composer, Assistant, and managed-knowledge surfaces. When it declines a request you get a `402`, `403`, or `429` response carrying a structured reason ([usage gating & limits](/billing/usage-gating)). Plain create, read, update, delete, and organization-settings calls are not gated this way — they return a simpler error shape. See [Errors & status codes](/api-reference/errors) for both. ### What a run costs ModuleX meters **managed** usage in [credits](/billing/credits). Each run is a flat charge, plus the managed model and managed-retrieval usage it consumes along the way. If you bring your own model keys, that usage is billed by your provider and is not metered as credits. The [credits and billing model](/concepts/credits-billing) explains exactly what consumes credits. ## Step 3 — Observe the run You do not wait blindly for a result. While a run executes, it **streams** its progress to you event by event over Server-Sent Events (SSE) — so you can watch each step happen live, in the app or from your own code. The run emits a sequence of events as it goes: a metadata event when it starts, a node-update event each time a step finishes, and a terminal event when it ends. You read them in order. A run can stop to ask you a structured question — a choice, a yes or no, free text, or a request to connect a credential. You answer, and the run continues. This is called human-in-the-loop. Each event arrives as a small JSON object with a `type` field that tells you what it is. A run typically streams a `metadata` event, then a `node_update` event per step, and finishes with a `done` event. If a step needs your input, you get an `interrupt` (workflows) or a `user_input_request` (the AI Composer and Assistant), the stream stays open, and you respond to continue. A `heartbeat` event arrives periodically during quiet stretches — for example while a run is paused waiting on you — so the connection stays open. You can ignore heartbeats; they carry no run data. For the full event taxonomy, see the [realtime overview](/realtime/overview). When a run pauses for your input, it does not lose its place — its state is checkpointed. You answer the question and the run resumes from exactly where it stopped. Answering a pause never charges you a second time; a run is charged once. You can also cancel a running or paused run. Cancellation is graceful: the current step finishes, then the run stops. For the precise pause-and-resume contract, see [human-in-the-loop resume](/realtime/hitl). Run progress (SSE) and canvas collaboration (which keeps several editors in sync) are two **separate** realtime systems that travel over different channels. The [realtime and collaboration model](/concepts/realtime-model) keeps them straight — and notes which sync paths are live versus retired. ## Run a workflow from code The same loop works programmatically. You authenticate, start a run, then open the stream and read events until a terminal one. Every request carries two headers: your API key as a bearer token, and the organization the request runs in. ```bash cURL # 1. Start the run curl -X POST https://api.modulex.dev/workflows/run \ -H "Authorization: Bearer mx_live_2f9c4a1b8e7d6c5a4b3f2e1d0c9b8a76" \ -H "X-Organization-ID: 8a1f3c2e-9b4d-4e7a-bc11-7d6e5f4a3b2c" \ -H "Content-Type: application/json" \ -d '{"workflow_id": "wf_3d8b9a1c-2e4f-4a6b-8c0d-1e2f3a4b5c6d", "input": {"topic": "weekly sales summary"}}' # → {"status":"running","run_id":"6a7b...","thread_id":"550e...","chat_id":"..."} # 2. Observe the run (SSE stream of events until a terminal one) curl -N https://api.modulex.dev/workflows/listen/6a7b8c9d-0e1f-2a3b-4c5d-6e7f8a9b0c1d \ -H "Authorization: Bearer mx_live_2f9c4a1b8e7d6c5a4b3f2e1d0c9b8a76" \ -H "X-Organization-ID: 8a1f3c2e-9b4d-4e7a-bc11-7d6e5f4a3b2c" ``` ```python Python import asyncio from modulex import Modulex async def main(): async with Modulex( api_key="mx_live_2f9c4a1b8e7d6c5a4b3f2e1d0c9b8a76", organization_id="8a1f3c2e-9b4d-4e7a-bc11-7d6e5f4a3b2c", ) as client: # 1. Start the run run = await client.executions.run( workflow_id="wf_3d8b9a1c-2e4f-4a6b-8c0d-1e2f3a4b5c6d", input={"topic": "weekly sales summary"}, ) # 2. Observe the run, event by event async with client.executions.listen(run.run_id) as stream: async for event in stream: print(event.event) if event.is_terminal: break asyncio.run(main()) ``` ```javascript JavaScript import { Modulex } from "modulex-js"; const client = new Modulex({ apiKey: "mx_live_2f9c4a1b8e7d6c5a4b3f2e1d0c9b8a76", organizationId: "8a1f3c2e-9b4d-4e7a-bc11-7d6e5f4a3b2c", }); // 1. Start the run const run = await client.executions.run({ workflowId: "wf_3d8b9a1c-2e4f-4a6b-8c0d-1e2f3a4b5c6d", input: { topic: "weekly sales summary" }, }); // 2. Observe the run, event by event for await (const event of client.executions.listen(run.runId)) { console.log(event.type); if (event.type === "done" || event.type === "error" || event.type === "cancelled") { break; } } ``` Wire fields stay snake_case (`run_id`, `thread_id`). The SDKs accept camelCase in your code and convert it for you on the request. For the full run-from-code walkthrough, see [Run a workflow (REST + SDK)](/guides/run-a-workflow); for authentication, see [Authentication](/api-reference/authentication). ## The shortcut: the Assistant Not every task is worth building a workflow for. The [Assistant](/concepts/assistant) is an agentic chat that authors, runs, and observes in a single conversation: you state a goal, it decides which connected tools to call, it calls them step by step, and you watch the same kind of live stream. It pauses for your approval before sensitive actions, just like a run can. Use the **AI Composer** when you want a reusable workflow you can run again and again. It produces a graph you own and can edit. Use the **Assistant** for one-off or open-ended tasks. It acts directly with your tools — no workflow to set up first. ## Where everything lives The whole loop happens inside an **organization** — the shared workspace and billing boundary. Your workflows, credentials, knowledge bases, plan, and credits all belong to one organization, and every run is metered against it. Each member has a **role**; the live roles are **owner** and **admin**, and actions such as using the AI Composer, the Assistant, and schedules require one of them. How tenancy works: the organization context, membership, and what owners and admins can do. How managed usage is metered in credits, and where the admission check applies. ## Keep going A closer look at what a workflow is, what a run is, and the run identities you will meet. How runs stream over SSE and how the canvas stays in sync across collaborators. Create an organization, get an API key, and make your first authenticated call. --- # Realtime & collaboration model URL: https://docs.modulex.dev/concepts/realtime-model ModuleX has two separate realtime systems: runs stream their progress to you over SSE, and the workflow canvas syncs between teammates over Socket.io. This page explains what each one is for and how they fit together. ModuleX feels live in two different ways. When you run a workflow, you watch each step report its progress in real time. When you build a workflow with a teammate, you see their cursor move and their edits appear on the same canvas as yours. These are two separate systems, and keeping them apart is the whole mental model. This page gives you that model in plain terms: what each realtime system does, when each one is in play, and what to expect when several people work together. For the wire-level detail, follow the links into [Realtime overview & event taxonomy](/realtime/overview). ## Two realtime planes Think of ModuleX as having two independent "live" channels that never overlap. One pushes a run's progress *to* you. The other keeps everyone's *canvas* in agreement. A one-way feed of a run's progress: the workflow, the AI Composer, or the Assistant reports each step to you as it happens. A two-way sync of the workflow canvas: cursors, presence, locks, and edits flow between everyone in the same workflow. "Realtime" in ModuleX always means one of these two systems. They share no events and no connection. A run streaming to you over SSE is unrelated to your teammate editing the canvas over Socket.io — even when both are happening in the same workflow at the same time. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-1170 · [IMAGE]** **[IMAGE_DESCRIPTION]:** A diagram contrasting the two realtime planes side by side. **[IMAGE_DETAILS]:** One illustration (not a screenshot), 16:9, light theme, ModuleX brand palette. Split the canvas into two labeled panels. Left panel "Run streaming (SSE)": a single server icon with a one-way arrow pointing right to one person icon, annotated "progress events, server → you". Show small example chips along the arrow reading "started", "step finished", "done". Right panel "Canvas collaboration (Socket.io)": two person icons connected by double-headed arrows to a shared canvas card in the middle, annotated "cursors, edits, locks — everyone ↔ everyone". A thin vertical divider between panels with the caption "Two separate systems". Keep text minimal inside shapes. ## Run streaming: watching a run as it happens When you start a run, ModuleX opens a live feed of that run's progress over **Server-Sent Events (SSE)**. It is a one-way stream: the server sends you events as the run moves forward, and you watch. You see this feed in three places, and they all work the same way: Each node reports when it starts, when it finishes, and what it produced — until the run reaches `done`. The Composer streams its thinking and the changes it makes to your canvas as it builds. The Assistant streams its response and the tool calls it makes, step by step. A run's stream carries a small set of progress events. You do not need to memorize them, but it helps to recognize the rhythm: The first event describes the run: which workflow, which version, and the run's identifier. As the run progresses, you get an event when a node (or an Assistant tool call) starts, and another when it produces output. If a step fails, you see the failure and any automatic retry. The stream ends with a final event — `done` when the run completes, or `error` if it could not. A run you cancel ends with `cancelled` instead. The stream is one-way. To act on a run — to cancel it, or to answer a question it asks — you make a separate request; you do not "reply" on the stream. See [SSE run streaming](/realtime/sse-streaming) for the full event list and frame format, and [Human-in-the-loop (HITL) resume](/realtime/hitl) for how a paused run is answered. ### When a run pauses to ask you something Some runs stop and wait for a person. A workflow can include an [interrupt step](/workflow-builder/nodes/interrupt), and the [AI Composer](/concepts/ai-composer) and [Assistant](/concepts/assistant) can pause to ask for a choice, a confirmation, or a credential before continuing. This is **human-in-the-loop (HITL)**. When this happens, the stream goes quiet — it does not end. The run is waiting. You answer the question (in the app, or with a separate request via an [SDK](/sdks/streaming-hitl)), and the run continues. Because answering restarts the run's progress, you reconnect to a fresh stream to watch the rest. A paused run holds its place for a while, then expires if no one answers. The exact pause-and-resume mechanics — what kinds of questions a run can ask, and how each is answered — are covered in [Human-in-the-loop (HITL) resume](/realtime/hitl). ## Canvas collaboration: building together live The second realtime system is for the [workflow builder](/workflow-builder/realtime-coediting). When two or more people open the same workflow, ModuleX connects them over **Socket.io** so the canvas stays in sync. Unlike run streaming, this is a two-way conversation: every person both sends their changes and receives everyone else's. You see who else is in the workflow, each with their own color, and their live cursor as they move around the canvas. When someone is editing a node, it is locked to them so two people cannot overwrite the same node at once. Adding, moving, connecting, or deleting nodes is broadcast to everyone in the workflow as it happens. Each accepted edit advances a version so the server can keep everyone consistent and resolve conflicts. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-1171 · [APP_VIDEO]** **[APP_VIDEO_DESCRIPTION]:** Two people co-editing the same workflow canvas in real time. **[APP_VIDEO_DETAILS]:** Screen recording from the live ModuleX app, ~30–45s, light theme, 16:9, shown as a split or picture-in-picture of two users. Show both users opening the same workflow; each sees the other's labeled, colored cursor moving. One user adds and connects a node and it appears instantly for the other. One user clicks into a node to edit it; show the other user seeing that node locked (an indicator that it is being edited by a teammate). Redact real names and organization names. Annotate the lock indicator with a callout "Locked while a teammate edits". ### Who can edit Collaboration follows the same roles as the rest of ModuleX. Anyone in the [organization](/concepts/organizations-roles) can open a workflow and watch, but editing the canvas requires an **owner or admin**. There is **no member role** in ModuleX. A `member` role existed in earlier versions and was retired on 2026-06-20. The live roles are **owner** and **admin** only, and editing the canvas — adding, moving, connecting, or locking nodes — is restricted to those roles. See [Roles & permissions](/security/roles-permissions) for the full map. ### Conflicts are handled for you When several people edit quickly, two changes can arrive based on slightly different versions of the canvas. ModuleX detects this and asks the affected client to rebase onto the latest version, rather than letting one person silently overwrite another. You do not manage this by hand; the builder keeps everyone converged. The version, lock, and conflict details are in [Presence, locks & versioning](/realtime/presence-locks). ## External sync: when a workflow changes outside the canvas Not every change to a workflow comes from a person dragging nodes. The [AI Composer](/concepts/ai-composer) edits the workflow on your behalf, and a workflow can also be changed through the API. ModuleX needs everyone with the canvas open to see those changes too. This is **external sync**. When a workflow is edited from outside the live canvas, that change is delivered to everyone currently viewing the workflow over the same Socket.io collaboration system, using the **`workflow:external-sync`** event. The canvas updates in place, just as it would for a teammate's edit. External sync runs over the live **Socket.io `workflow:external-sync`** event — not over a background channel. An older design that bridged external changes through a flat Redis channel named `workflow:updated` is **not the live path**: the backend does not publish to that channel, so it never fires. If you read about a `workflow:updated` Redis bridge anywhere, treat it as legacy; the path that actually keeps the canvas in sync is `workflow:external-sync`. The event reference is [Socket.io collaboration events](/realtime/socket-events), and the broader gaps are tracked in [Known limitations](/reference/known-limitations). **🎬 MEDIA PLACEHOLDER · MX-MEDIA-1172 · [APP_VIDEO]** **[APP_VIDEO_DESCRIPTION]:** The AI Composer editing a workflow while a teammate watches the canvas update. **[APP_VIDEO_DETAILS]:** Screen recording from the live ModuleX app, ~20–30s, light theme, 16:9. Show user A asking the AI Composer to make a change (for example, "add a step that sends a Slack message"). As the Composer applies the change, show user B's canvas — open to the same workflow — updating in place to show the new node without user B doing anything. Redact real names and organization names. Annotate user B's canvas with a callout "Updated by external sync". ## Putting it together The two systems often run at once, and that is fine — they do not interfere. You and a teammate are both in the same workflow. Your teammate adds a node; you see it appear over **canvas collaboration**. Meanwhile you run the workflow; you watch its steps complete over **run streaming**. Two live systems, two purposes, no conflict. You ask the [AI Composer](/concepts/ai-composer) to restructure the workflow. The Composer streams its progress to you over **run streaming**, and the edits it makes land on everyone else's canvas over **external sync** on the collaboration system. A run reaches a [human-in-the-loop](/realtime/hitl) step. Its **run stream** goes quiet while it waits. You answer, and a fresh stream picks up where it left off. The canvas, and anyone editing it, is unaffected. ## Where to go next The technical map of both planes — every SSE event and every Socket.io event in one place. Invite a teammate and co-edit a workflow live, step by step. What co-editing looks like in the app: presence, cursors, and live edits. How canvas edits and external changes sync, in the builder's own terms. --- # Workflow engine & nodes URL: https://docs.modulex.dev/concepts/workflow-engine How the ModuleX workflow engine compiles a JSON workflow into a runnable graph: the 9 node types, edges, the __start__/__end__ virtual nodes, run state and reducers, and the {{nodeId.path}} reference model. {/* AUDIENCE: technical. Feeds: B04 (workflow-engine), X04 (data-model), X09 (glossary). */} The workflow engine is the runtime that turns a `WorkflowDefinition` — the JSON contract you build in the [workflow builder](/workflow-builder/overview) or generate with the [AI Composer](/concepts/ai-composer) — into an executable graph and runs it. This page documents the engine's contract: how a workflow compiles, the 9 node types and what each writes to state, how edges and the virtual `__start__`/`__end__` endpoints work, how run state and reducers behave, and how the `{{nodeId.path}}` reference syntax resolves data between steps. This is the conceptual model behind every run. For the per-node configuration reference, see the individual [node type pages](/workflow-builder/nodes/overview). For the wire-level run lifecycle and events, see [SSE run streaming](/realtime/sse-streaming). **🎬 MEDIA PLACEHOLDER · MX-MEDIA-1100 · [IMAGE]** **[IMAGE_DESCRIPTION]:** Diagram of how a JSON workflow definition compiles into a runnable graph. **[IMAGE_DETAILS]:** Show the pipeline left to right: a `WorkflowDefinition` JSON block (metadata, config, state_schema, nodes[], edges[]) → a compile step that builds dynamic run state (one field per node id) and wires nodes/edges → the compiled graph with `__start__` and `__end__` rendered as dashed virtual endpoints feeding into and out of real nodes → a "run" arrow streaming node events out over SSE. Annotate that the checkpointer persists state to Postgres. 16:9, light and dark variants, no decorative gradients. ## The mental model A workflow is a directed graph of **nodes** connected by **edges**, executed over a shared **run state**. The engine evaluates the graph one node at a time (or in parallel where a loop fans out), and every node writes its result back into state. Later nodes read earlier results through the `{{nodeId.path}}` reference syntax. Four pieces make up the contract: The 9 step types — `llm`, `tool`, `agent`, `function`, `conditional`, `interrupt`, `transformer`, `guardrails`, `knowledge`. Each node writes its result to run state under its own `id`. Directed connections between nodes. The virtual `__start__` and `__end__` endpoints mark where execution enters and leaves the graph. A dynamic dictionary that flows through the run. Its fields come from your `state_schema`, plus one auto-added field per node id, plus any loop fields. The `{{nodeId.path}}` syntax that pulls a value out of state — typed when the whole field is a single reference, templated when embedded in a string. Under the hood, the engine compiles your `WorkflowDefinition` into a LangGraph `StateGraph`, runs it in a background task, persists state to Postgres through LangGraph's `AsyncPostgresSaver` checkpointer, and streams node and run events over [SSE](/realtime/sse-streaming). You do not interact with LangGraph directly — you describe the graph in JSON and the engine handles compilation, persistence, and streaming. ## The `WorkflowDefinition` A workflow is a single JSON object. These are its top-level fields. Workflow name, description, version, and tags. The engine denormalizes some of these for fast filtering, but the workflow definition is the source of truth. Execution configuration. Several fields are accepted on the wire but **forced to constant values** by the engine regardless of what you send (see [Execution config](#execution-config) below). Declares the named fields that flow through the run, their types, and their reducers. See [Run state](#run-state). The list of `NodeDefinition` objects. **The virtual `__start__` and `__end__` endpoints must not appear here** — they are wired automatically and a validator rejects a definition that lists them as nodes. The list of `EdgeDefinition` objects connecting nodes. Edges may also express conditional branches and loops. The node where execution begins. Defaults to the virtual `__start__` endpoint. The canvas `{x, y}` of the `__start__` endpoint. Visual only — it has no effect on execution. The definition is parsed with extra fields ignored, so removed or unknown top-level keys (for example, a legacy `end_points` field) are silently dropped rather than causing an error. Build against the fields documented here. ### Execution config `config` accepts the fields below, but the engine **overrides** most of them with fixed values. You can send them, but only `recursion_limit` is honored from your input. | Field | Wire default | Effective value | Notes | | --- | --- | --- | --- | | `recursion_limit` | `500` | respected | The maximum number of node transitions in one run. The default of 500 supports loops up to roughly 100 iterations (each node transition counts as one step). | | `enable_checkpointing` | `true` | always `true` | State is always persisted; you cannot disable it. | | `checkpointer_type` | `"postgres"` | always `"postgres"` | The checkpointer is always Postgres-backed. See [Checkpointer and threads](#checkpointer-and-threads). | | `stream_mode` | `"updates"` | always `"updates"` | The engine streams per-node updates as they complete. | | `enable_subgraph_streaming` | `true` | always `true` | Subgraph events stream through to the client. | A run can override `recursion_limit` at execution time through its `ExecutionConfig` (`thread_id?`, `recursion_limit?`, `checkpoint_id?`); otherwise the workflow's configured limit applies. ## Nodes A workflow has exactly **9 node types**. Every node, whatever its type, follows the same output convention: **it writes its result to run state under its own `id`**. A node with `id: "node_llm_1"` produces `state["node_llm_1"]`, and later nodes reference it as `{{node_llm_1}}`. Several configs expose a legacy `output_key` field. It is **deprecated** — node output always lands under the node `id`, not a custom key. Do not rely on `output_key`, `input_keys`, or `prompt_template`; they are kept only for backward compatibility. Each node type maps to one configuration object on the `NodeDefinition`. The full per-node parameter reference lives on the dedicated node pages; the table below is the index. | Type | Config slot | What it does | Reference | | --- | --- | --- | --- | | `llm` | `llm_config` | Calls a language model with system/user prompts, optional structured output. | [LLM node](/workflow-builder/nodes/llm) | | `tool` | `tool_config` | Calls a single integration tool (action). | [Tool node](/workflow-builder/nodes/tool) | | `agent` | `agent_config` | Runs an autonomous tool-calling loop up to `max_iterations`. | [Agent node](/workflow-builder/nodes/agent) | | `function` | `function_config` | Runs a built-in registry function (HTTP, webhook, schema validation). | [Function node](/workflow-builder/nodes/function) | | `conditional` | `conditional_config` | Branches on an expression or LLM decision, or controls a loop. | [Conditional node](/workflow-builder/nodes/conditional) | | `interrupt` | `interrupt_config` | Pauses the run to ask a human a structured question. | [Interrupt node](/workflow-builder/nodes/interrupt) | | `transformer` | `transformer_config` | Reshapes and combines data between steps. | [Transformer node](/workflow-builder/nodes/transformer) | | `guardrails` | `guardrails_config` | Validates content with JSON/regex/PII checks. | [Guardrails node](/workflow-builder/nodes/guardrails) | | `knowledge` | `knowledge_config` | Retrieves from a knowledge base. | [Knowledge node](/workflow-builder/nodes/knowledge) | An unknown `type` fails compilation with an "Unsupported node type" error. See the [node types overview](/workflow-builder/nodes/overview) for the complete list and how each writes to state. ### The `NodeDefinition` Every node, regardless of type, shares this shape. The node's unique identifier within the workflow. This is the key its output is written under in run state, and the name later nodes reference with `{{id}}`. One of the 9 node types. A human-readable label shown in the builder. An optional description of what the node does. Whether the node participates in the run. A disabled node is dropped at compile time and its edges are rewired to the next enabled target, so you can disable a step without rewiring the graph by hand. A `null` value is treated as `true`. The node's horizontal canvas position. Visual only. The node's vertical canvas position. Visual only. Optional per-node retry policy. Most node types are retry-wrapped; the `interrupt` node is **not** retry-wrapped, because retrying a human pause is meaningless. Exactly one type-specific configuration slot, matching the node `type` (for example, `llm_config` for an `llm` node). The builder may send a wrapped `{"config": {...}}` form; the engine normalizes it to the type-specific key automatically. ### What each node type writes to state Because every node writes under its own `id`, knowing each type's output shape tells you exactly what later nodes can reference. Resolves the system and user prompt templates, calls the model, and returns the response text under the node id. If you configure a `structured_output_schema`, the engine asks the model for structured JSON and returns the parsed object instead, falling back to JSON extraction from the raw response if structured output fails. LLM usage is metered in [credits](/billing/credits) after each call. See the [LLM node](/workflow-builder/nodes/llm). Resolves the `input_mapping` (which uses `{{...}}` references), loads the tool for the configured integration and credential, invokes it, and returns the unwrapped result under the node id. A `{success, action, result: {...}}` wrapper is flattened to the root, with `result` kept for backward-compatible `.result.field` paths. The tool node honors both `parameter_defaults` and `parameter_overrides`. See the [Tool node](/workflow-builder/nodes/tool). Binds the configured tools to the model and runs a tool-calling loop up to `max_iterations` (default 10): each turn invokes the model, executes any tool calls, and appends the results. The agent applies `parameter_defaults` only — `parameter_overrides` are **ignored** for agents. It raises if the resolved input is empty, and meters LLM usage every iteration. See the [Agent node](/workflow-builder/nodes/agent). Looks up a built-in function by `function_name` and executes it. On success it writes `result.data` under the node id; on failure it writes `{error, success: false, ...}` so downstream nodes can branch on the error. There are exactly four built-ins: `http_request`, `send_webhook`, `validate_schema`, and `validate_workflow_schema`. See the [Function node](/workflow-builder/nodes/function). Has a `condition_type` of `"expression"`, `"llm"`, or `"loop"`. An expression node evaluates ordered branches and stores the matched target; the engine then adds conditional edges that route on it. An LLM node asks the model to pick a route. A loop node acts as the loop controller. See the [Conditional node](/workflow-builder/nodes/conditional). Builds a question payload (`message`, `data`, optional `resume_schema` and `examples`) and pauses the run. The active client receives an `interrupt` event; the run status becomes `interrupted`. On [resume](/realtime/hitl), the human's value is stored under the node id. This node is **not** retry-wrapped. See the [Interrupt node](/workflow-builder/nodes/interrupt). Resolves a `source` reference and applies an ordered list of operations (string, object, array, type, date, and math operations). The transformed value is written under the node id. See the [Transformer node](/workflow-builder/nodes/transformer). Runs JSON-schema, regex, and PII checks against a `source` reference, with an `on_failure` action of `block`, `warn`, `transform`, or `route`. The hallucination check is a placeholder and returns a "coming soon" marker. The result records which validations ran, whether the content was valid, and any findings. See the [Guardrails node](/workflow-builder/nodes/guardrails). Resolves a `query` reference and retrieves from the configured [knowledge base](/concepts/knowledge-rag). Managed (`modulexdb`) retrieval reserves and records [credits](/billing/credits); bring-your-own-key vector stores are uncosted. The output is formatted as `chunks`, `context`, or `both`. See the [Knowledge node](/workflow-builder/nodes/knowledge). ## Edges and the virtual endpoints Edges are directed connections between nodes. Two endpoint names are **virtual**: `__start__` and `__end__`. They mark where execution enters and leaves the graph but are not real nodes — they have no configuration and **must not appear in the `nodes` array**. ```json title="Edges with the virtual endpoints" { "edges": [ { "source": "__start__", "target": "node_llm_1" }, { "source": "node_llm_1", "target": "node_http_2" }, { "source": "node_http_2", "target": "__end__" } ] } ``` A few rules govern how edges compile: An edge from `__start__` connects the engine's entry point to your first node. The workflow's `entry_point` defaults to `__start__`. An edge to `__end__` terminates a branch. Any enabled node that has no outgoing edge is automatically connected to the exit. **An explicit edge to `__end__` is therefore optional** — leave it out and the node still terminates correctly. A node with `enabled: false` is dropped at compile time and its edges are rewired to the next enabled target, recursively. A node with more than one incoming edge is wrapped so it runs once after its predecessors converge, rather than once per incoming edge. Listing `__start__` or `__end__` in the `nodes` array is rejected by the workflow validator as a virtual-node error. They belong only in `edges` and in `entry_point`. ## Run state State is a single dictionary that flows through the run. Its fields come from three sources, combined at compile time: 1. **Your declared fields** — the `state_schema.fields` you define. 2. **One field per node id** — the engine auto-adds a field (typed `Any`) for every node so its output streams correctly. This is why `{{node_id}}` always resolves to that node's output. 3. **Loop fields** — auto-added bookkeeping fields for any loop (see [Loops](#loops)). ### Declaring a state field Each entry in `state_schema.fields` is a `StateField`. One of `string`, `integer`, `float`, `boolean`, `object`, `array`, or `messages` (mapped to Python `str`, `int`, `float`, `bool`, `dict`, `list`, and `list` respectively). An optional description of the field. How concurrent or repeated writes to this field merge. One of `none`, `add`, or `update` (see [Reducers](#reducers)). Whether the field must be present. The value applied at run start if no input overrides it. At run start, the engine applies declared `default`s first, then type-appropriate empty values for anything without a default, then your run `input` on top — so input always wins. ### Reducers A reducer decides what happens when a field is written more than once (for example, by parallel branches of a loop). Plain replace — the new value overwrites the old. This is the default. A smart-append reducer. For `array` fields it appends, auto-wrapping a scalar into a single-element list (`[] + "x"` becomes `["x"]`); for other types it uses addition. Use this to accumulate results across loop iterations. A type-safe dictionary merge. New keys are added and existing keys are overwritten, while the rest of the object is preserved. The reducer matters most inside loops and parallel branches, where the same field is written multiple times. For a straight-line workflow, the default `none` (replace) is usually what you want. See [Variables & references](/workflow-builder/variables-and-references) for worked examples. ## References — `{{nodeId.path}}` References are how data moves between nodes. The syntax is `{{nodeId.path}}`, where `nodeId` is a node's `id` and `path` is an optional dot-and-bracket path into that node's output. ```text title="Reference forms" {{node_abc123}} whole output of node_abc123 {{node_search.results[0].title}} nested field via dot + bracket path {{node_http_2.body.status}} field of a structured result ``` How a reference resolves depends on where it appears: When a field is **exactly** one `{{...}}` reference, the engine returns the **native typed value** — a number stays a number, an object stays an object, an array stays an array. Use this to pass structured data between nodes without stringifying it. ```json title="Typed pass-through" { "input_mapping": { "results": "{{node_search.results}}" } } ``` When a `{{...}}` reference is **embedded in a larger string**, the engine substitutes it as text. Object and array values are JSON-encoded into the string. Unresolved references are left intact rather than blanked out. ```json title="String interpolation" { "user_prompt": "Summarize this article: {{node_fetch.body}}" } ``` A **list item** of the form `{{...nodeId.path}}` (note the leading `...`) is spliced into the surrounding list when it resolves to a list; a scalar or object is appended, and `None` contributes nothing. The `...` prefix only has spread meaning in a list-item position. ```json title="Spreading a list into a list" { "items": ["{{...node_a.rows}}", "{{...node_b.rows}}", "literal"] } ``` Resolution rules to keep in mind: - **Missing paths resolve to nothing.** An out-of-range index, a missing key, or a malformed bracket path resolves to `null` silently rather than raising — design downstream nodes to tolerate `null`. - **`input_mapping` skips empty values.** Keys whose resolved value is `null` or an empty string are dropped from the mapping, so they never reach the tool or function. - **Mixed and nested structures recurse.** A reference inside a nested object or list is resolved per-item, preserving the surrounding structure. See [Variables & references](/workflow-builder/variables-and-references) for the full reference cookbook. ## Loops A loop repeats a section of the graph. Loops are expressed either as an edge whose condition type is `loop`, or as a `conditional` node whose `condition_type` is `"loop"` (the node acts as the loop controller). A loop has three modes: A fixed number of iterations, from `iterations` or a `iterations_ref` reference. One iteration per item in a `collection` (a `{{...}}` reference). Set `parallel: true` to fan the iterations out concurrently; otherwise they run in sequence. Repeats while a condition expression is true, bounded by `max_iterations` (default 100) as a safety stop. Every loop auto-creates bookkeeping fields in run state, keyed by its `loop_id`: | Field | Type | Present for | Meaning | | --- | --- | --- | --- | | `{loop_id}_iteration` | integer | all loops | The current iteration count. | | `{loop_id}_initialized` | boolean | all loops | Whether the loop has started. | | `{loop_id}_completed` | boolean | all loops | Whether the loop has finished. | | `{loop_id}_item` | any | foreach | The current item. | | `{loop_id}_index` | integer | foreach | The current item's index. | | `{loop_id}_collection_size` | integer | foreach | The total number of items. | | `{loop_id}_results` | array | when `accumulate` is on | Accumulated per-iteration results (uses the smart-append reducer). | A loop's `exit_target` defaults to `__end__` — an empty or missing exit target is normalized to the virtual end. Set `body_target` to the first node of the loop body, and optionally `body_end` (it defaults to `body_target`) to mark where one iteration ends. `recursion_limit` (default 500) caps total node transitions across the whole run, including every loop iteration. A long-running `foreach` over a large collection can exhaust it — raise the limit on `config` or at run time, or set `parallel: true` to fan out instead of iterating. ## Checkpointer and threads Every run's state is persisted by LangGraph's `AsyncPostgresSaver` checkpointer, backed by the same Postgres database as the rest of ModuleX. Checkpointing is always on — the `enable_checkpointing` and `checkpointer_type` config fields are forced, so you cannot turn it off or switch to an in-memory saver. The checkpointer is what makes [human-in-the-loop](/realtime/hitl) possible: when an `interrupt` node pauses a run, its full state is checkpointed, and a resume call restores that state and continues the **same** run from where it stopped. A **thread** is the unit of checkpoint continuity, identified by a `thread_id` on the run config. When you run a workflow from a chat, the chat's id is reused as the thread id, so the run shares the conversation's checkpoint thread; an ephemeral run without a thread gets a generated id. ## Run lifecycle and events A run executes in a background task: it persists a durable run row, streams a `metadata` event, applies state defaults, then steps the graph node by node, publishing a `node_update` event as each node completes. An `interrupt` pauses the run; `done`, `error`, or `cancelled` terminate it. A run has a default timeout of one hour. You observe a run by listening to its [SSE stream](/realtime/sse-streaming). The wire is a sequence of `data: \n\n` frames with **no `event:` line** — the event discriminator is the `type` key inside each JSON payload, so a client parses each frame and switches on `.type`. ```text title="SSE frames for a successful two-node run" data: {"type": "metadata", "data": {"run_id": "run_7f3a", "thread_id": "chat_42", "workflow_name": "My Flow", "workflow_version": 3, "workflow_type": "workflow"}} data: {"type": "node_update", "node": "node_llm_1", "output": {"node_llm_1": "Hello world"}} data: {"type": "node_update", "node": "node_http_2", "output": {"node_http_2": {"status_code": 200, "body": {"ok": true}}}} data: {"type": "done", "data": {"message": "Workflow completed successfully"}} ``` Note that the live wire is **flat** — `node_update` carries `node` and `output` keys, not the `node_id`/`status` fields of the internal typed model. Build clients against the wire shape shown here. A `{"type": "heartbeat"}` keepalive is injected every 15 seconds so the connection never idles closed, and on reconnect the stream replays the run's buffered history before tailing live. The full event taxonomy, interrupt and resume frames, and reconnection semantics are documented on [SSE run streaming](/realtime/sse-streaming); for workflow concepts and the distinct run-id identities, see [Workflows & runs](/concepts/workflows-and-runs). ### Failures and credits There is no fixed per-node credit charge. Charging happens inline: LLM and agent calls meter token usage, and managed knowledge retrieval reserves and records [credits](/billing/credits) per call. A mid-run hard stop — for example, exhausted credits — surfaces as a node error carrying a machine-readable reason code, so a client can match it deterministically. The managed-knowledge billing gate is best-effort inside a run: a billing hiccup is recorded but does not crash a workflow that is already running. For node-level retry behavior, how failures surface in the stream, and debugging failed runs, see [Error handling & retries](/workflow-builder/error-handling-retries). For where and how managed usage is gated, see [Usage gating & limits](/billing/usage-gating). ## Next steps Every node type with its full parameter reference. The complete `{{nodeId.path}}` reference and run-state cookbook. What a run is, its status model, and the three run-id identities. The full run-event taxonomy and how to consume it. --- # Workflows & runs explained URL: https://docs.modulex.dev/concepts/workflows-and-runs Understand the difference between a workflow and a run in ModuleX, how a run moves through its lifecycle and status, and the three distinct identifiers that all get called a run id. A **workflow** is the thing you build once. A **run** is what happens each time you press go. Keeping those two ideas separate is the key to understanding everything else on this page — including the three different identifiers that are all, confusingly, called a "run id". The reusable design: the nodes, the connections between them, and the settings you saved. It sits still until you run it. One execution of that design with specific inputs. Each run has its own progress, its own result, and its own identifier. ## Workflow vs. run Think of a workflow like a recipe and a run like the meal you cook from it. One recipe, many meals — each meal made with different ingredients and turning out slightly differently. You edit it in the [workflow builder](/workflow-builder/overview) or with the [AI Composer](/concepts/ai-composer), give it a name, and [deploy](/workflow-builder/execution/deploy) it. It stays put until you change it. Starting a run takes a snapshot, feeds in your inputs, and executes the [nodes](/workflow-builder/nodes/overview) step by step. When it finishes, the run becomes a record in your history. Every time you run from the canvas, [from chat](/workflow-builder/execution/run-on-chat), [from the API](/workflow-builder/execution/api-endpoint), or [on a schedule](/workflow-builder/execution/schedule), you create a new, independent run. If you delete a workflow, its past runs stay in your history so you keep the audit trail. The run record keeps everything except its link back to the (now deleted) workflow. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-1090 · [IMAGE]** **[IMAGE_DESCRIPTION]:** A simple "one workflow, many runs" diagram. **[IMAGE_DETAILS]:** Left side: a single labelled workflow card ("Research summary"). An arrow fans out to the right into three run cards, each with a different input ("AI trends", "Q2 sales", "competitor news") and a different status pill (succeeded / running / failed). Light theme, clean and friendly, 16:9, minimal text so it reads at a glance. ## What happens during a run Once you start a run, ModuleX walks through the workflow's nodes in order, passing data from each step to the next. You can watch this happen live, and some workflows pause partway through to ask you something before they continue. You provide the inputs and press run. ModuleX checks your plan's [credits and limits](/concepts/credits-billing) first, then begins executing in the background and immediately hands you a **run id** so you can follow along. Each node runs and writes its result into the run's shared state. Progress streams to you live over [Server-Sent Events](/realtime/sse-streaming), so you see each step as it completes rather than waiting for the whole thing. If the workflow includes an [interrupt node](/workflow-builder/nodes/interrupt), the run pauses and asks a question — for example, to approve a draft before it is sent. The run waits for your answer; nothing idles out. You respond, and the run continues from exactly where it stopped. It keeps the same run id and is never charged twice for the same execution. The run ends in one of a few final states (done, failed, or cancelled) and is saved to your run history, where you can reopen it later to see its inputs and results. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-1091 · [APP_VIDEO]** **[APP_VIDEO_DESCRIPTION]:** A 30–45s screen recording of a run streaming live in the app, including an interrupt pause and resume. **[APP_VIDEO_DETAILS]:** Record in the workflow builder. Start a run, show nodes lighting up one by one as the live stream arrives, pause on an interrupt prompt ("Approve this plan?"), click approve, then show the run completing with a success state. Highlight the run-id chip in the runs panel. Light theme, 16:9, no audio narration needed (captions optional). ## The run lifecycle A run moves through a predictable set of moves: start it, watch it, optionally inspect or steer it, and let it finish. You rarely think about these as separate actions in the app — but they map directly to what happens behind the scenes and to the [API](/workflow-builder/execution/api-endpoint). Kick off a run from a deployed workflow, an ad-hoc canvas, or a schedule. ModuleX returns right away with a run id while the work continues in the background. Subscribe to the run's live event stream to follow progress in real time. Reconnecting mid-run replays what you missed, so you never lose the thread. See [SSE run streaming](/realtime/sse-streaming). When a run pauses at an interrupt, send your answer to continue it. The run picks up from its saved checkpoint — same run, same id. Stop a run that is running or paused. Cancellation is graceful: the current step finishes, then the run stops cleanly between steps. ## The status model Every run carries a status that tells you where it is. The live stream you watch and the saved history record use slightly different words for the same idea, which is worth knowing so nothing surprises you. | Status | What it means | | --- | --- | | `pending` | The run is queued and about to start. | | `running` | The run is actively executing its nodes. | | `interrupted` | The run is paused at an interrupt node, waiting for your answer. | | `succeeded` | The run completed successfully. | | `failed` | A step errored and the run stopped. | | `cancelled` | You (or a teammate) stopped the run. | | `skipped` | The run did not execute (for example, a scheduled run that was skipped). | **One word, two places.** While a run streams live, a successful finish arrives as a `done` event. In your saved run history, that same run is recorded with the status `succeeded`. They describe the same outcome — `done` is the live signal, `succeeded` is the stored label. ## The three identifiers all called a "run id" This is the single most important thing to get right about runs. ModuleX uses the phrase "run id" for **three different identifiers** that serve different jobs. They are not interchangeable, and using one where another is expected leads to a "not found" result. Reach for the right id for the job: use the **execution run id** to watch, resume, or cancel a live run; use the **run record id** to open a finished run in your history; and the **scheduled-run id** only when you are looking at a schedule's own run log. The live handle for one execution. It is what you watch, resume, and cancel against while a run is in flight. The permanent key for a finished run in your history. You use it to look a completed run back up later. The key for one entry in a schedule's own run log. It is separate from both ids above. This is the identifier ModuleX hands back the instant you start a run. It is what every live action keys off: - **Watch** the run's live stream (`/workflows/listen/{run_id}`). - **Cancel** a run that is still going (`/workflows/cancel/{run_id}`). - It is also used to replay a run's recent event history if you reconnect. For a workflow run, **resuming after an interrupt keeps the same execution run id** — one logical run, charged once. For the conversational [AI Composer](/concepts/ai-composer) and [Assistant](/concepts/assistant), each turn (and each resume) gets a **new** execution run id, even though the conversation itself keeps one stable thread. So an execution run id is not a stable handle across a whole conversation. Every real run is also written to your durable run history as its own record, and that record has its own identifier — separate from the execution run id above. You use the **run record id** to open a single finished run and read back its inputs and results (`/workflow-runs/{run_pk}`). When you list your run history, each row shows *both* ids: the execution run id (the live handle the run used) and the record id (the history key). Pass the record id — not the execution run id — when you fetch one run's detail. When a workflow runs [on a schedule](/workflow-builder/execution/schedule), the schedule keeps its **own** log of each time it fired. Every entry in that log has its own identifier, separate again from the two ids above. A single scheduled-run entry links the schedule, the workflow it ran, and the execution run id of the actual run it triggered — so you can trace a schedule firing all the way back to the live run it produced. When you open one entry in a schedule's run history, you address it by its scheduled-run id, not by the execution run id it points to. ### Side by side | Identifier | What it points to | When you use it | | --- | --- | --- | | **Execution run id** | One live execution | Watch, resume, or cancel a run in flight | | **Run record id** | One row in your run history | Open a finished run to read its inputs and result | | **Scheduled-run id** | One entry in a schedule's run log | Inspect a single scheduled firing | **🎬 MEDIA PLACEHOLDER · MX-MEDIA-1092 · [IMAGE]** **[IMAGE_DESCRIPTION]:** A diagram showing the three "run id" identities and how they relate. **[IMAGE_DETAILS]:** Three labelled boxes — "Execution run id (live)", "Run record id (history)", "Scheduled-run id (schedule log)". Draw an arrow from the scheduled-run entry to the execution run id ("triggers"), and an arrow from the execution to the run record ("saved as"). Annotate that resume keeps the same execution run id for workflows but mints a new one for chat agents. Light theme, friendly, 16:9; keep labels short. ## See it in practice You do not need to think about these ids in the app — the interface hands you the right one for whatever you are doing. They become visible when you run a workflow programmatically. The example below starts a run (which returns the **execution run id**), then lists your run history (where each row carries both the **execution run id** and the **run record id**). ```bash cURL # 1. Start a run — the response includes the execution run id ("run_id"). curl -X POST https://api.modulex.dev/workflows/run \ -H "Authorization: Bearer mx_live_xxx" \ -H "X-Organization-ID: 11111111-1111-1111-1111-111111111111" \ -H "Content-Type: application/json" \ -d '{"workflow_id":"550e8400-e29b-41d4-a716-446655440000","input":{"query":"AI trends"}}' # 2. List run history — each row carries both the execution run id ("run_id") # and the run record id ("id"). curl https://api.modulex.dev/workflow-runs \ -H "Authorization: Bearer mx_live_xxx" \ -H "X-Organization-ID: 11111111-1111-1111-1111-111111111111" ``` ```python Python from modulex import Modulex async with Modulex( api_key="mx_live_xxx", organization_id="11111111-1111-1111-1111-111111111111", ) as client: # 1. Start a run — `run.run_id` is the execution run id. run = await client.executions.run( workflow_id="550e8400-e29b-41d4-a716-446655440000", input={"query": "AI trends"}, ) # 2. List run history — each row has `run_id` (execution) and `id` (record). history = await client.executions.list_runs() ``` ```javascript JavaScript import { Modulex } from "modulex-js"; const client = new Modulex({ apiKey: "mx_live_xxx", organizationId: "11111111-1111-1111-1111-111111111111", }); // 1. Start a run — `run.run_id` is the execution run id. const run = await client.executions.run({ workflowId: "550e8400-e29b-41d4-a716-446655440000", input: { query: "AI trends" }, }); // 2. List run history — each row has `run_id` (execution) and `id` (record). const history = await client.workflowRuns.list(); ``` Every request uses `Authorization: Bearer mx_live_…` together with the `X-Organization-ID` header. See [authentication](/api-reference/authentication) for how to get a key and find your organization id. ## Where to go next How nodes, edges, and run state actually fit together under the hood. The live event stream you watch a run with, frame by frame. A hands-on, end-to-end walkthrough across REST and both SDKs. Run a workflow automatically on a recurring schedule. --- # ModuleX Enterprise URL: https://docs.modulex.dev/enterprise/branding ModuleX Enterprise is the top tier of ModuleX — the same AI workflow orchestration platform, with self-hosting, SSO and SAML, custom limits, and dedicated support. Here is how the Enterprise edition is positioned and named, and how it differs from Free, Pro, and Max. ModuleX Enterprise is the top tier of ModuleX. It is not a different product or a separate codebase — it is the same AI workflow orchestration platform you can use today, packaged for organizations that need to run it at scale, on their own terms, with self-hosting, single sign-on, custom limits, and dedicated support. This page explains how the Enterprise edition is positioned, what makes it different from the other tiers, and how ModuleX is named and branded. For the business case and value, see [ModuleX for enterprise](/enterprise/overview). For the security and trust details behind it, see [Enterprise](/security/enterprise). **🎬 MEDIA PLACEHOLDER · MX-MEDIA-4310 · [IMAGE]** **[IMAGE_DESCRIPTION]:** A simple positioning graphic showing the four ModuleX tiers — Free, Pro, Max, Enterprise — as one continuous product, with Enterprise highlighted as the top tier rather than a separate product. **[IMAGE_DETAILS]:** Authored illustration, not an app capture. Show one horizontal track labeled "ModuleX" with four stops (Free, Pro, Max, Enterprise) left to right; mark Enterprise as the endpoint and tag it with "self-hosting · SSO/SAML · custom limits · dedicated support." Use the ModuleX brand wordmark. 16:9, light theme, no numeric prices. ## What "ModuleX Enterprise" means ModuleX is an AI workflow orchestration platform. You build AI workflows with natural language or a visual canvas, connect the tools you already use, and run those workflows from chat, the API, or the SDKs. "Enterprise" is the top of the four tiers — **Free, Pro, Max, and Enterprise**. Calling it the "Enterprise edition" is shorthand for the same platform configured for larger teams: the capabilities that smaller teams do not need are switched on, and the numbers that smaller teams hit as limits become custom. Workflows, the AI Composer, the Assistant, knowledge and RAG, integrations, and the SDKs are the same across every tier. Enterprise adds capabilities and removes ceilings — it does not change what ModuleX is. The Enterprise tier is positioned "for organizations at scale" — teams that need to invite many people, run large-scale operations, and operate ModuleX under their own governance. Free, Pro, and Max are self-serve. Enterprise is the one tier you arrange with the ModuleX team, because the limits, deployment, and support are tailored to you. There is no separate "Enterprise" download, login, or product to learn. If you already know how to build and run a workflow in ModuleX, you already know ModuleX Enterprise. The difference is in deployment, scale, sign-on, and support — not in how the product works day to day. ## How Enterprise differs from the other tiers Every tier shares the same core: workflows, the [AI Composer](/concepts/ai-composer), the [Assistant](/concepts/assistant), [knowledge and RAG](/concepts/knowledge-rag), the full integration catalog, and the JavaScript and Python SDKs. What changes at the Enterprise tier is who can run it where, how much it can do, and how you are supported. Enterprise is the only tier that can be self-hosted. Free, Pro, and Max run on ModuleX's managed cloud; Enterprise can run inside your own environment. See [deployment and SSO](/enterprise/deployment-sso). Single sign-on with SAML is an Enterprise-only capability, so your team signs in through your identity provider. Lower tiers do not include SSO or SAML. Credits, run rates, API rate limits, and storage are fixed allowances on Free, Pro, and Max. On Enterprise they are custom — sized to your usage rather than capped at a tier number. Lower tiers are supported through community and email channels. Enterprise adds dedicated support, including a dedicated Slack channel and hands-on onboarding. ### What the Enterprise tier includes These are the capabilities ModuleX lists for the Enterprise tier: - **Invite teammates** — bring your whole team into your [organization](/concepts/organizations-roles). - **Large-scale operations** — run at volumes the fixed tiers cap. - **Custom rate limits** — run-rate, API, and credit limits sized to you, not to a tier. - **Advanced analytics** — deeper visibility into usage and activity. - **Onboarding assistance** — hands-on help getting your team set up. - **Dedicated Slack channel** — a direct line to the ModuleX team. Beyond the plan card, the Enterprise capability set named for sales conversations is: **SSO and SAML**, **self-hosted deployment**, **custom rate limits and credits**, **advanced analytics and audit logs**, **dedicated Slack support**, and **hands-on onboarding**. ModuleX lists audit logs across all tiers in its plan comparison, but a per-tier entitlement that gates audit logs could not be confirmed in the product configuration, so treat audit-log behavior as **TBD** until verified. For everything ModuleX could not yet confirm, see [known limitations](/reference/known-limitations). ### Tier-by-tier at a glance The pricing details — including ModuleX's two conflicting annual figures, which are presented side by side and not yet resolved — live on [plans and pricing](/billing/plans). This page covers only what changes at the Enterprise tier.
Capability Free, Pro, Max Enterprise
**Self-hosted deployment** Not available Available
**SSO and SAML** Not available Available
**Credits, run rates, limits, storage** Fixed per tier Custom
**Pricing** Published per tier Custom — arranged with sales
**Support** Community and email Dedicated, with a Slack channel
**Onboarding** Self-serve Hands-on onboarding
**How you get it** Sign up in the app Contact sales
## Brand and naming ModuleX is the name of the product and the brand. Using the name correctly matters most in two places: in your own documents and contracts, and when you refer to the company behind the product. **ModuleX** is the product and brand name, written as one word with a capital M and a capital X. Use "ModuleX Enterprise" for the top tier — never a standalone "Enterprise" product, because Enterprise is a tier of ModuleX, not a separate product. **ModulexAI, LLC** is the legal entity that operates the Service, a Delaware limited liability company. You will see "ModulexAI" as the legal name in metadata and in the copyright line. In legal and procurement documents, the company is ModulexAI, LLC; the product is ModuleX. - **Product / brand:** `ModuleX` (one word, capital M and X). - **Top tier:** `ModuleX Enterprise` — the Enterprise tier of ModuleX. - **Legal entity:** `ModulexAI, LLC` (Delaware LLC), shown as the legal name `ModulexAI` in metadata and as `© ModulexAI` in the copyright line. - **The Service:** the umbrella legal term for the ModuleX websites, applications, APIs, and related services. - **Marketing site:** `https://modulex.dev`. - **Documentation:** `https://docs.modulex.dev`. ModuleX's brand voice is plain, specific, and confident. The product avoids buzzwords such as "streamline," "seamless," "empower," and "innovative." When you describe ModuleX Enterprise in your own materials, the clearest framing is the truest one: it is ModuleX, at enterprise scale. ## How to get ModuleX Enterprise Free, Pro, and Max are self-serve — you choose them in the app. Enterprise is arranged with the ModuleX team, because the limits, deployment model, and support are tailored to your organization. If you are new, build and run something first. The fastest path is the [quickstart](/get-started/quickstart) or [running your first workflow](/get-started/first-workflow). The platform you try is the platform Enterprise scales. Decide which Enterprise capabilities matter for you — self-hosting, SSO and SAML, custom limits, dedicated support — using the differences above and [ModuleX for enterprise](/enterprise/overview). Reach out through [contact sales](/enterprise/contact-sales) to size your limits, deployment, and support. Enterprise is the one tier that is arranged with a person rather than purchased self-serve. ## Where to go next Why teams and enterprises choose ModuleX: collaboration, governance, and scale. SSO, self-hosting, seats, and support for larger teams. Free, Pro, Max, and Enterprise — allowances, limits, and how the tiers compare. Single sign-on and self-hosted or managed deployment options for Enterprise. Talk to the ModuleX team about Enterprise. --- # Contact sales URL: https://docs.modulex.dev/enterprise/contact-sales Talk to the ModuleX team about an Enterprise plan — when to reach out, what to prepare, and how to start the conversation. Enterprise is the only ModuleX plan that is **sales-led** — there is no self-serve checkout. You agree on scope, limits, and pricing with the ModuleX team, and your organization is configured to match. This page covers when to reach out, what to have ready, and how to start the conversation. Enterprise is one of four ModuleX plans: **Free**, **Pro**, **Max**, and **Enterprise**. To see what Enterprise adds and why teams choose ModuleX, read [ModuleX for enterprise](/enterprise/overview). For the full side-by-side comparison of allowances and limits, see [Plans & pricing](/billing/plans). ## When to contact sales Reach out when a fixed paid plan no longer fits how your organization works. The most common reasons: Your team should sign in through your own identity provider instead of managing individual ModuleX logins. You need to run ModuleX in your own environment for data-residency or infrastructure-control reasons. You need custom rate limits or a credit allowance sized to your real usage, rather than the Pro or Max caps. You're adding many teammates across departments and want per-seat licensing with no fixed cap. You want a dedicated Slack channel with the ModuleX team rather than community or email support. You'd like guided setup to get your organization running quickly. If you only need a couple of these and your usage fits within a fixed tier, compare [Plans & pricing](/billing/plans) first — you may not need Enterprise yet. If you're not sure, reach out anyway; the team can help you decide. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-4330 · [IMAGE]** **[IMAGE_DESCRIPTION]:** A simple "is Enterprise right for you?" decision graphic. **[IMAGE_DETAILS]:** A clean, friendly flow with a few yes/no branches — "Need SSO or self-hosting?", "Outgrown fixed limits?", "Rolling out to a large team?" — leading to either "Stay on Pro or Max" or "Talk to sales about Enterprise." Light and dark variants. Landscape, suitable for full content width. No screenshots of the app required. ## What to prepare A short conversation goes faster when you arrive with a few details. None of these are required to reach out, but having them ready helps the team scope the right plan on the first call. Roughly how many people will use ModuleX. The team typically works in these brackets: 1–10, 11–50, 51–200, and 200+. Which Enterprise features matter to you — for example single sign-on, self-hosted deployment, custom limits, dedicated support, or onboarding. See [ModuleX for enterprise](/enterprise/overview) for the full list. A rough sense of how much you'll run — number of workflows, how often they run, and whether you'll use ModuleX-managed models or [bring your own keys](/billing/credits). This helps size your credit allowance and rate limits. Whether you need self-hosting, specific data-residency, or have a security review process. For background, see the [Security overview](/security/overview) and [Deployment & SSO](/enterprise/deployment-sso). When you want to be up and running, and any procurement or review steps on your side. You don't have to figure out exact numbers before reaching out. The team will help you translate your needs into seats, limits, and a credit allowance during the conversation. ## How to reach the team Start an Enterprise conversation in whichever way is easiest for you. Write to **contact@modulex.dev** with your team size and what you're trying to do. It's the fastest way to start. Fill in the Enterprise contact form on the ModuleX website and the team will follow up. You can also reach Enterprise sales from inside the product: the Enterprise plan has no self-serve checkout, so choosing it directs you to contact the team rather than to a payment screen. Have a question that isn't about Enterprise pricing — billing, an account issue, or a technical problem? Start at [Getting help](/help/getting-help) instead, which points you to the right channel. ## What happens next Send your details by email or through the contact form. You agree on seats, custom rate limits and credits, SSO, deployment, and the support channel that fit your organization. The ModuleX team sets up your Enterprise plan and helps you onboard your team. ## Common questions No. Free, Pro, and Max are all self-serve — you can sign up and start without talking to anyone. Sales is only for Enterprise, where scope, limits, and pricing are agreed with the team. See [Plans & pricing](/billing/plans). Yes. The Free plan includes trial credits and requires no credit card, so you can build with the AI Composer and test real Assistant runs first. Then reach out when you know you need Enterprise capabilities. Enterprise pricing is custom — it's based on your seats, usage, and the capabilities you need, rather than a fixed monthly price. The team will give you a quote once your plan is scoped. Enterprise replaces the fixed-tier caps with negotiated values — for example your credit allowance and rate limits are configured per organization rather than fixed at the Pro or Max tier. For how these work, see [Credits & metering](/billing/credits) and [Usage gating & limits](/billing/usage-gating). Anyone can start the conversation, but it helps if the person knows your team size, expected usage, and any security or deployment requirements. In ModuleX, organization roles are **owner** and **admin** — billing and plan changes require one of these. See [Roles & permissions](/security/roles-permissions). ## Related pages Why teams and enterprises choose ModuleX. SSO, self-hosting, seats, custom limits, and support in detail. Compare Free, Pro, Max, and Enterprise side by side. --- # Deployment & SSO URL: https://docs.modulex.dev/enterprise/deployment-sso Single sign-on through Clerk and self-hosted vs. ModuleX-managed deployment for Enterprise: the entitlement flags that unlock them, just-in-time user provisioning, the production secrets a self-host requires, and the startup security gate. This page covers the two Enterprise-only capabilities in this group: **single sign-on (SSO)** for your team, and **self-hosted deployment** of the ModuleX backend. Both are gated by per-plan entitlement flags that are set only on the **Enterprise** plan; everything below describes how each capability works against the verified backend behavior, what it requires, and which specifics are not pinned in source and are marked TBD. Authentication mechanics referenced here are documented in full on [the authentication model page](/security/authentication); credential and secret encryption is documented on [the data security & encryption page](/security/data-encryption). **Both capabilities are Enterprise entitlements.** In the plan configuration, `feature.sso` and `feature.self_host` are `true` only on the **Enterprise** plan and `false` on Free, Pro, and Max. They are surfaced as account feature flags; they are not toggles you set yourself. Talk to the team to enable them — see [contact sales](/enterprise/contact-sales). ## How SSO works in ModuleX ModuleX has **one identity provider: Clerk.** The backend only ever *verifies* Clerk-issued JWTs — there are no first-party `/auth/login`, `/register`, `/logout`, or `/refresh` routes. The auth provider is read from `modulex.toml` as `[auth].provider`, which defaults to `"clerk"` and is the only configuration key that file consumes: ```toml modulex.toml [auth] provider = "clerk" ``` Because every human sign-in flows through Clerk, **enterprise SSO is delivered as a Clerk enterprise connection** (for example SAML or OIDC against your identity provider). When an SSO user signs in, Clerk mints a JWT, and the ModuleX backend verifies that JWT exactly as it would any other Clerk token. No separate SSO code path exists inside the ModuleX backend. **SAML/OIDC connection details are configured in the Clerk dashboard, not in this repository — TBD.** The ModuleX source contains no SAML, OIDC, or SCIM implementation; the `feature.sso` flag is an entitlement signal, not enforcement code. The exact list of supported identity providers, the SAML/OIDC connection setup steps, the metadata/ACS URLs, and whether SCIM directory sync is offered are not pinned in source. Treat those specifics as **TBD** and confirm them with the ModuleX team before relying on them. ### What the backend verifies on every request Whether a user arrives via SSO or ordinary Clerk sign-in, the token they carry is verified the same way. The verifier requires both `CLERK_JWKS_URL` and `CLERK_ISSUER` to be set; if either is missing, verification returns no claims and the request is rejected. JWKS endpoint used to verify the JWT signature. JWKS responses are cached in-process with a 300-second TTL. Missing value disables JWT verification entirely. Expected `iss` claim. The token is decoded with `issuer=CLERK_ISSUER`. **Audience is not validated.** The backend decodes Clerk JWTs with `verify_aud: False`, so the token's audience (`aud`/`azp`) is not bound. The realtime server applies the same policy. Do not describe ModuleX as enforcing audience binding. The claims read downstream are `sub` (the Clerk user id), `email`, `azp`, and `sid`. When the configured provider is `clerk`, the effective organization role is sourced from the JWT's `metadata.role`. See [the authentication model page](/security/authentication) for the full header-by-header breakdown, including the `Authorization: Bearer` forms and the Socket.io handshake. ### Just-in-time user provisioning ModuleX provisions accounts **just in time (JIT)** on the first valid JWT for an unknown user. This is what makes SSO usable without manual seat setup: a user who signs in through your identity provider for the first time is created automatically. A Clerk JWT for a user with no existing ModuleX record reaches the backend. A `User` is created with the user-level role `USER`. (User-level roles are `USER` and `SUPER_ADMIN`; these are distinct from organization roles.) A personal default organization is created, and the new user is added as its `owner`. The flow is idempotent and concurrent-safe, guarded by a row lock so a webhook-plus-JIT race cannot double-provision. **There is no JIT path for API keys.** A `mx_live_*` API key cannot create a user — key creation is itself JWT-gated (`POST /api-keys` requires a signed-in user). So SSO/JIT provisioning applies to human sign-ins only; programmatic callers must already correspond to a provisioned user. See [the SDKs overview](/sdks/overview) for the programmatic path. ### Organization roles after SSO sign-in Once provisioned, a user acts inside an organization under an organization role. The live roles are **`owner`** and **`admin`** only. **The `member` role is retired.** It was retired on the backend and may still appear on old organization rows, in legacy invitation constraints, and in the realtime read-gate, but the REST edge rejects it everywhere (invite and role-update schemas enforce `admin` only). Document and plan around `owner`/`admin`. Note also that the agentic surfaces — Composer, Assistant, and schedules — require **owner/admin**, despite an older in-code comment suggesting any member can use the Assistant. Full detail is on [the roles & permissions page](/security/roles-permissions). `owner` is assignable only at organization creation. Invitations and role updates are constrained to `admin` at the API edge. Organization scope itself is carried by the `X-Organization-ID` header on REST/SDK calls and by the Socket.io handshake `auth.organizationId` field on the realtime surface — see [org context & X-Organization-ID](/security/org-context). **🎬 MEDIA PLACEHOLDER · MX-MEDIA-4320 · [IMAGE]** **[IMAGE_DESCRIPTION]:** SSO sign-in and just-in-time provisioning sequence diagram. **[IMAGE_DETAILS]:** Left-to-right flow: user → identity provider (IdP) → Clerk (mints JWT) → ModuleX backend (verifies JWT via JWKS) → first request triggers JIT provisioning (create User role `USER`, create default org, add owner membership). Show the `CLERK_JWKS_URL` / `CLERK_ISSUER` verification step and a side note that audience is not validated. Light theme, 16:9, no real names. ## Deployment options ModuleX runs in two shapes. The default is **ModuleX-managed** (cloud); **self-hosted** is an Enterprise-only entitlement. The default. ModuleX operates the backend, realtime server, database, and secret storage. Available on every plan. Authentication, secrets, and the production security gate are managed for you. Enterprise only (`feature.self_host = true`). You operate the ModuleX backend in your own environment and supply the required configuration and secrets described below. Talk to the team via [contact sales](/enterprise/contact-sales). **Self-hosting is gated by the `self_host` entitlement.** In the plan configuration, `feature.self_host` is `true` only on Enterprise. There is no public self-service installer documented in source. The packaging, container images, and orchestration topology for a customer self-host are **TBD** — confirm the supported deployment artifacts with the ModuleX team. ### Self-host requirements A self-hosted backend sources its configuration through a single settings layer, resolving each secret **environment variable first, then Azure Key Vault, then default**. The following are the configuration values a production self-host depends on. Types and defaults are as defined in the backend configuration. Canonical environment name; drives all production-only enforcement. Aliases are normalized: `prod` becomes `production`, `stage` becomes `staging`. Set to `production` for a real deployment. The values `production` and `staging` are the two that trigger the hard security gate below. Symmetric key used to encrypt integration credentials. **Required in `production`/`staging`** (minimum length 32 = 256-bit). In development it defaults to an empty string and the app logs an insecure-key warning. Generate with `python3 -c "import secrets; print(secrets.token_urlsafe(32))"`. If this key changes, all previously encrypted credential data becomes unrecoverable. Treat it as permanent for the lifetime of the data. Pepper mixed into API-key hashing (`SHA-256(API_KEY_PEPPER + key)`). **Required in `production`/`staging`** (minimum length 32). Generate with `python3 -c "import secrets; print(secrets.token_hex(32))"`. `API_KEY_PEPPER` is production-required but is **absent from the shipped `.env.example`**. A self-host that copies the sample file will not know to set it, and the app will fail the startup security gate. Set it explicitly. PostgreSQL DSN. **Required in `production`/`staging`** (empty value is an error in those environments, a warning otherwise). Use the async driver form, for example `postgresql+asyncpg://user:pass@db:5432/modulex`. Redis connection string. Used for caching, rate limiting, and realtime coordination. JWKS endpoint for verifying Clerk JWTs. Required for any human sign-in (including SSO). Missing value disables JWT verification. Expected JWT `iss` claim; must match the issuer your Clerk instance mints. Gate for the platform `/admin/*` cross-organization monitoring routes. Comma-separated to allow zero-downtime rotation. An empty list makes the admin gate **fail closed** (no access). Generate with `openssl rand -hex 32`. When set, secrets are sourced from Azure Key Vault after the environment variable lookup. Authentication uses the Container App system-assigned managed identity with the "Key Vault Secrets User" role. When unset (local/dev), only environment variables are read. Server base URL used to build OAuth callback URLs. Frontend origin. CORS allowed hosts; split on commas and stripped. Frontend base the backend redirects to after an OAuth token exchange. Frontend landing path after OAuth; reads `status`, `integration`, `credential_id`, `error_code`, and `message` query parameters. Per-key requests per minute. Total per-user requests per minute. Maximum number of API keys per user. API key for the read-only diagnostics endpoint. When empty, the endpoint is disabled and returns `503`. Selects a capacity preset (`small`, `medium`, `large`, `enterprise`, `azure_e8ds_v5`, `azure_e8ds_v5_peak`). An unknown name falls back to per-variable overrides such as `MAX_CONCURRENT_EXECUTIONS` (default `50`), `REQUEST_TIMEOUT` (default `30.0`), and `MAX_QUEUE_SIZE` (default `200`). **Casing on the wire.** Environment variables are `UPPER_SNAKE_CASE`. When sourced from Azure Key Vault, secret names are `UPPER-KEBAB-CASE` (underscores become hyphens) — for example `ENCRYPTION_KEY` is stored as `ENCRYPTION-KEY` and `API_KEY_PEPPER` as `API-KEY-PEPPER`. The conversion is automatic. **Key Vault rotation needs a restart.** The Key Vault secret resolver is cached for the process lifetime, so rotating a secret in the vault is not picked up until the process restarts. Environment-sourced values are likewise read once at startup. ### The production startup security gate On startup, the backend validates the security-critical secrets. The behavior depends on `ENVIRONMENT`: | Condition | `production` / `staging` | `development` | | --- | --- | --- | | `ENCRYPTION_KEY` missing | Startup fails | Warning, continues with empty key | | `ENCRYPTION_KEY` shorter than 32 chars | Startup fails | Warning, continues | | `API_KEY_PEPPER` missing | Startup fails | Warning, continues with empty pepper | | `API_KEY_PEPPER` shorter than 32 chars | Startup fails | Warning, continues | | `DATABASE_URL` empty | Startup fails | Warning, continues | In `production`/`staging`, any validation error prints `❌ SECURITY VALIDATION FAILED` and the process exits with code `1`. In development the process prints an insecure-settings warning and continues. On success it logs that security settings were validated. ### Minimal production environment ```bash .env (self-host) # Identity / encryption (REQUIRED in production, >=32 chars each) ENCRYPTION_KEY="$(python3 -c 'import secrets; print(secrets.token_urlsafe(32))')" API_KEY_PEPPER="$(python3 -c 'import secrets; print(secrets.token_hex(32))')" # NOT in .env.example ENVIRONMENT=production # or "prod" -> normalized to production DATABASE_URL=postgresql+asyncpg://user:pass@db:5432/modulex # required in prod REDIS_URL=redis://cache:6379 # Clerk auth (required for human sign-in, including SSO) CLERK_JWKS_URL=https://clerk.example.com/.well-known/jwks.json CLERK_ISSUER=https://clerk.example.com # Platform admin gate (fails CLOSED if empty); rotate via comma-separated list ADMIN_DASHBOARD_API_KEYS="$(openssl rand -hex 32)" ``` ```bash .env (Key Vault variant) # Same as above, but source the security secrets from Azure Key Vault. # Store them in the vault as ENCRYPTION-KEY and API-KEY-PEPPER (kebab-case). ENVIRONMENT=production DATABASE_URL=postgresql+asyncpg://user:pass@db:5432/modulex REDIS_URL=redis://cache:6379 CLERK_JWKS_URL=https://clerk.example.com/.well-known/jwks.json CLERK_ISSUER=https://clerk.example.com ADMIN_DASHBOARD_API_KEYS="$(openssl rand -hex 32)" # Enables Key Vault sourcing; ENCRYPTION_KEY / API_KEY_PEPPER resolved from the vault. AZURE_KEY_VAULT_URL=https://my-vault.vault.azure.net/ ``` **Operational details that are not pinned in source — TBD.** The process/worker runtime (host, port, worker count), container images and orchestration topology, backup/restore procedures, and any Celery or telemetry configuration are not documented in the configuration source. The backend exposes no `PORT`, `WORKERS`, `HOST`, or `SENTRY_*` environment variables, which suggests those are supplied by the process manager or compose stack rather than by application config. Confirm the supported runtime with the ModuleX team. ## Provisioning and seats User provisioning is JIT, as described above — new SSO users are created on first sign-in with an owner default organization. **Directory-driven provisioning and de-provisioning (SCIM) is not implemented in source and is TBD.** Seat limits come from plan entitlements, not from a deployment setting: | Plan | Seat quota | Self-host | SSO | | --- | --- | --- | --- | | Free | 5 | No | No | | Pro | Per-seat licensed | No | No | | Max | Per-seat licensed | No | No | | Enterprise | Custom / per-seat licensed | Yes | Yes | A `seats` quota of `null` means the plan is per-seat licensed (billed, not capped at a fixed number). Enterprise sets `feature.self_host` and `feature.sso` to `true`; all other plans set both to `false`. For the full plan comparison and the credit and rate-limit allowances, see [plans & pricing](/billing/plans). **Enterprise annual pricing is unresolved — present both figures.** The marketing site advertises Pro annual at `$240/yr` and Max annual at `$960/yr` (a 20% discount), while the backend Stripe configuration prices the annual SKUs at `$300/yr` and `$1,200/yr` (full monthly × 12, no discount). The authoritative annual price is an open question; do not pick a winner here. Always confirm the actual charge at checkout, and see [plans & pricing](/billing/plans) for the canonical figures. ## Requirements at a glance - An Enterprise plan with `feature.sso = true`. - A Clerk enterprise connection configured against your identity provider (SAML/OIDC). Connection setup lives in the Clerk dashboard and is **TBD** in this documentation. - `CLERK_JWKS_URL` and `CLERK_ISSUER` configured on the backend (managed for you on cloud). - Users are provisioned JIT on first sign-in as organization `owner` of a default org; live roles are `owner`/`admin`. - An Enterprise plan with `feature.self_host = true`. - `ENVIRONMENT=production` (or `staging`). - `ENCRYPTION_KEY` and `API_KEY_PEPPER`, each at least 32 characters — both production-required, and `API_KEY_PEPPER` is missing from the sample env file. - A PostgreSQL `DATABASE_URL` (async driver) and a `REDIS_URL`. - `CLERK_JWKS_URL` and `CLERK_ISSUER` for sign-in. - `ADMIN_DASHBOARD_API_KEYS` if you use the platform admin dashboard (the gate fails closed when empty). - Optionally, `AZURE_KEY_VAULT_URL` to source secrets from Azure Key Vault. - The startup security gate exits the process with code `1` if any required secret is missing or too short in `production`/`staging`. - Container images, orchestration, and runtime topology are **TBD** — confirm with the ModuleX team. ## Related Clerk JWT verification, `mx_live_*` API keys, and the header forms every caller uses. How credentials and secrets are encrypted, and the production security checks. The live `owner`/`admin` role model and what each can do. Enable SSO and self-hosting on an Enterprise plan. --- # ModuleX for enterprise URL: https://docs.modulex.dev/enterprise/overview ModuleX scales from one person to a whole organization: realtime team collaboration, owner/admin governance, org-scoped credits and billing, and BYOK or managed usage. Built on the same platform your team already knows. You can start ModuleX as one person with one idea. As your team grows, the same platform grows with you: the workflows you built solo become shared, the credentials your team connects become governed, and your usage becomes something you can see, scope, and control across the whole organization. This page is the value picture for teams and enterprises. For the security architecture behind it, see [security overview](/security/overview). **🎬 MEDIA PLACEHOLDER · MX-MEDIA-4300 · [IMAGE]** **[IMAGE_DESCRIPTION]:** A single value-overview graphic showing the three enterprise pillars — Collaboration, Governance, Scale — as one connected story. **[IMAGE_DETAILS]:** Authored illustration (not an app capture). Three labelled panels left to right: "Collaborate" (multiple cursors on one workflow canvas), "Govern" (an organization with owner/admin roles and a credential vault), "Scale" (a usage/credits meter and higher rate limits). Keep it calm and diagrammatic, sentence-case labels, light theme, 16:9. No fabricated logos or metrics. ## From one person to a whole organization ModuleX is an AI workflow orchestration platform. The way you build and run work does not change as you scale — what changes is who you do it with, and how much control you have over it. Your team co-edits the same [workflow](/concepts/workflows-and-runs) on a live canvas, runs from chat, API, or SDK, and shares one [knowledge base](/concepts/knowledge-rag) — instead of passing files and prompts around. Every workflow, run, credential, and knowledge base belongs to an [organization](/concepts/organizations-roles). Owner and admin roles decide who can build, connect tools, and run agentic surfaces. Higher plans raise your run rates, API limits, and credit allowance. On the Enterprise plan, rate limits and credits are custom, and deployment can move to your own infrastructure. The rest of this page walks through those three pillars — collaboration, governance, and scale — and points to the deeper reference for each. When you are ready to talk to the team, head to [contact sales](/enterprise/contact-sales). ## Collaboration: your team works on one canvas Most automation tools assume one builder. ModuleX assumes a team. The [workflow builder](/workflow-builder/overview) is a realtime, multi-user canvas, so several people can shape the same workflow at the same time and see each other's changes as they happen. Multiple people edit one canvas together. You see who else is present, where their cursor is, and which nodes are being worked on. See [presence, locks and versioning](/realtime/presence-locks). Workflows, knowledge bases, credentials, and chats live in the organization, not on one person's laptop. Anyone with access works from the same source of truth. The [AI Composer](/workflow-builder/composer) turns a plain-English description into an editable workflow graph your team can refine — a fast shared starting point, not a black box. Keep conversations personal, or make them organization-visible so teammates can pick up, review, and continue the work. Live team collaboration is available on the Pro, Max, and Enterprise plans; the Free plan is a solo workspace. See [plans and pricing](/billing/plans) for what each plan includes. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-4301 · [APP_VIDEO]** **[APP_VIDEO_DESCRIPTION]:** Two teammates co-editing the same workflow canvas in realtime. **[APP_VIDEO_DETAILS]:** Screen-record the ModuleX app builder with two cursors visible. Show one person adding a node while the other repositions an edge, with presence avatars and a node lock appearing. 20-30s, 16:9, light theme, no narration, no real customer data. Highlight the two cursors and the presence indicators. For how edits stay in sync across collaborators and external changes, see [realtime co-editing and external sync](/workflow-builder/realtime-coediting). ## Governance: clear ownership over access and data As more people build, you need to know who can do what. In ModuleX, the [organization](/concepts/organizations-roles) is the unit of tenancy and billing: every workflow, run, credential, knowledge base, and wallet is scoped to one organization, and access is decided by org roles. ### Roles that map to responsibility ModuleX uses two active organization roles. The agentic surfaces — the [AI Composer](/concepts/ai-composer), the [Assistant](/assistant/overview), knowledge management, and schedules — require the **owner** or **admin** role. Full control of the organization, including billing and membership. The owner can do everything an admin can, plus manage the plan. Can build and edit workflows, connect tools, manage knowledge, and run the agentic surfaces. Admins handle day-to-day building and operations. The older `member` role has been retired and is not a current first-class role. Where the app still references it, treat it as a known gap — see [roles and permissions](/security/roles-permissions) and [known limitations](/reference/known-limitations). Plan your access model around **owner** and **admin** only. ### Credentials stay in the organization The keys your team connects — the credentials behind every [integration](/integrations/overview) — are stored against the organization, encrypted at rest, and resolved at run time rather than handed back out. Your team connects a service once; everyone who is authorized can use it without anyone copying secrets around. Connect a tool once for the organization. Credentials are stored encrypted and never re-exposed after they are saved. Programmatic access selects an organization with the `X-Organization-ID` header, so each call is scoped to exactly one tenant's data. For how secrets are encrypted and how access is enforced, see [data security and encryption](/security/data-encryption) and the full [security overview](/security/overview). ### Visibility over usage The public pricing surface lists usage and analytics, and audit-log visibility, across the plans, with advanced analytics on Max and Enterprise. The exact scope of audit logging is set on the platform rather than pinned in this research base, so treat the detailed audit-log coverage as **TBD** until it is confirmed on [trust and compliance](/security/trust). What is firm: usage is metered per organization in [credits](/concepts/credits-billing), and that record is the basis for the analytics each plan exposes. ## Scale: grow run rates, usage, and deployment The same workflows run whether you are one person or a thousand — but how much you can run, and where it runs, depends on your plan. The limits and credit allowances below come from the canonical backend plan configuration; see [plans and pricing](/billing/plans) and [credits and metering](/billing/credits) for the full picture.
  Free Pro Max Enterprise
**Credits** 300 (one-time trial) 5,000 / mo 20,000 / mo Custom
**API rate limit** 10 / min 100 / min 500 / min Custom
**Workflow execution** 10 runs / min 150 runs / min 500 runs / min Custom
**Live team collaboration** — Yes Yes Yes
**Self-hosted deployment** — — — Yes
**SSO / SAML** — — — Yes
**Onboarding** — — — Hands-on
**Support** Community Discord Email + Discord Dedicated
The asynchronous workflow-execution rate that higher plans advertise is configured but not yet enforced, so today's effective limit is the per-minute run rate shown above. Credit and rate-limit numbers are pinned to the backend plan configuration; the public pricing page is the place to confirm current figures at checkout. See [plans and pricing](/billing/plans). ### Pay your way: BYOK or managed credits Cost control is part of scaling. ModuleX gives you two billing models, and you can mix them. Connect your own model and tool provider accounts. The provider bills you directly with no ModuleX markup, so your AI spend stays on your existing accounts. Use ModuleX-provisioned providers and pay in [credits](/concepts/credits-billing) — no setup, with usage metered per organization. Managed usage is what your plan's allowance covers. BYOK is presented as available across all plans. Because BYOK usage is billed by the provider, it is not metered in ModuleX credits — it shows up in analytics only. See [credits and metering](/billing/credits). ### Deploy where you need it On the Enterprise plan, ModuleX can run on your own infrastructure, with single sign-on for your identity provider. Sign in through your identity provider. SSO and SAML are Enterprise-plan capabilities. See [deployment and SSO](/enterprise/deployment-sso). Run ModuleX in your own environment for full control over where your data and runs live, with custom rate limits and credits. ## What the Enterprise plan adds The Enterprise plan is for organizations operating at scale. Its capabilities, as listed on the public Enterprise offering, are: Sign in through your identity provider. Run ModuleX in your own environment. Limits and allowances sized to your workload instead of fixed plan tiers. Deeper usage visibility for the organization. Detailed audit-log scope is TBD pending [trust and compliance](/security/trust). A dedicated support channel for your team. Guided onboarding to get your organization running. Enterprise pricing is custom. The capabilities above mirror the public Enterprise offering; the Free, Pro, and Max plans are self-serve. For a side-by-side of every plan, see [plans and pricing](/billing/plans), and for the SSO and self-hosting reference, see [the Enterprise security page](/security/enterprise). ## Trust and compliance Security is covered in depth in its own [security overview](/security/overview): the [authentication model](/security/authentication), [organization context](/security/org-context), [roles and permissions](/security/roles-permissions), and [data security and encryption](/security/data-encryption). This research base does not pin any formal compliance certification (for example SOC 2, ISO 27001, or specific regulatory programs), and ModuleX's published terms state the service is not designed for use under regulated regimes such as HIPAA, GLBA, or PCI-DSS. Do not assume a certification that is not confirmed. The authoritative, current statement of compliance posture and sub-processors lives on [trust and compliance](/security/trust), [compliance](/security/compliance), and [sub-processors](/security/sub-processors) — treat any certification claim as **TBD** until it appears there. ## Built on org-scoped access Everything above rests on one idea: each request acts inside exactly one organization. When your team works programmatically, every call carries the API key plus the organization it acts on — the same governance boundary you see in the app. Authentication uses `Authorization: Bearer mx_live_…` together with the `X-Organization-ID` header; see [authentication](/api-reference/authentication). The example below lists the workflows in one organization. Swap the organization id to act inside a different tenant — the access boundary travels with the header. ```bash cURL curl https://api.modulex.dev/workflows \ -H "Authorization: Bearer mx_live_your_api_key" \ -H "X-Organization-ID: org_your_organization_id" ``` ```python Python from modulex import Modulex client = Modulex(api_key="mx_live_your_api_key") workflows = client.workflows.list( organization_id="org_your_organization_id", ) for workflow in workflows: print(workflow.id, workflow.name) ``` ```javascript JavaScript import { Modulex } from "modulex-js"; const client = new Modulex({ apiKey: "mx_live_your_api_key" }); const workflows = await client.workflows.list({ organizationId: "org_your_organization_id", }); for (const workflow of workflows) { console.log(workflow.id, workflow.name); } ``` For the full developer surface, see the [API overview](/api-reference/overview) and the [SDKs overview](/sdks/overview). ## Common questions An organization is the tenant and billing unit. Workflows, runs, credentials, knowledge bases, and the credit wallet all belong to an organization, and access is decided by org roles. You can belong to more than one organization and switch between them. See [organizations, roles and membership](/concepts/organizations-roles). The agentic surfaces — the AI Composer, the Assistant, knowledge management, and schedules — require the **owner** or **admin** role. The `member` role has been retired. See [roles and permissions](/security/roles-permissions). Yes. With BYOK you connect your own provider accounts and the provider bills you directly with no ModuleX markup. You can also use ModuleX-managed credits, and mix the two. See [credits and metering](/billing/credits). Self-hosted deployment is an Enterprise-plan capability, alongside SSO and SAML and custom limits. See [deployment and SSO](/enterprise/deployment-sso) and [the Enterprise security page](/security/enterprise). Compliance posture and sub-processors are documented on [trust and compliance](/security/trust), [compliance](/security/compliance), and [sub-processors](/security/sub-processors). Any specific certification should be confirmed there rather than assumed; this research base does not pin one. Enterprise pricing is custom. Tell us about your team and we will follow up. See [contact sales](/enterprise/contact-sales). ## Talk to the team If your organization needs custom limits, self-hosting, SSO, or hands-on onboarding, the Enterprise plan is built for you. Tell us about your team — size, the tools you use, and what you want to automate — and we will help you find the right plan. Enterprise pricing is custom. Compare Free, Pro, Max, and Enterprise side by side, including allowances and limits. SSO, self-hosting, seats, and support, for larger teams. How ModuleX secures your data, credentials, and access. Single sign-on and self-hosted deployment options for enterprise. --- # Architecture overview URL: https://docs.modulex.dev/get-started/architecture How ModuleX fits together: one FastAPI backend, a Next.js app, a Socket.io realtime server, two SDKs, and an installable tool catalog, bound by one Postgres, one Redis, and Clerk. {/* AUDIENCE: technical. Feeds: X01 (architecture), B01 (REST overview), B19 (inter-repo contracts). */} ModuleX is a system of seven repositories built around a single backend. One FastAPI service owns the database, the credit ledger, the tool runtime, and every REST and SSE surface. A Next.js app is the only first-party human surface; it talks to the backend over REST and SSE and to a separate Socket.io server for realtime canvas collaboration. Two SDKs wrap the same REST surface, and the integration tool catalog ships as an installable Python package the backend loads at startup. This page maps the components, the wires between them, the boot order, the auth context that threads through every hop, and the parts of the system you should not build against because they are inert today. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-1060 · [IMAGE]** **[IMAGE_DESCRIPTION]:** ModuleX component-and-data-flow diagram covering all seven repositories. **[IMAGE_DETAILS]:** Recreate the text diagram in section "Component and data-flow map" as a clean architecture illustration. Show the browser, `modulex-ui` (Next.js, :3000, BFF proxy that adds `Authorization: Bearer` + `X-Organization-ID`), `modulex-ws` (Socket.io, :3001), and `modulex` (FastAPI, :8000) as a central column; below it the three shared backplanes (Postgres incl. the Procrastinate schema, Redis, Stripe) and the installed `modulex-integrations` package reached over the `modulex.tools` entry-point group; off to the side Clerk (external IdP) at the top and the two SDKs (`modulex-js`, `modulex-python`) calling the backend over REST. Label the realtime split: SSE for run events, Socket.io for canvas collaboration. 16:9, light and dark variants, no decorative gradients. ## The seven repositories Seven repositories make up ModuleX, but only five of them run as processes — two are libraries and one is a static marketing site. | Repository | Stack | Role | Listens on | | --- | --- | --- | --- | | `modulex` | Python 3.12, FastAPI (Pydantic v2) | The backend: all REST and SSE, auth, billing, the tool runtime, the workflow, Composer, and Assistant engines, and the background worker | `:8000` (dev canonical) | | `modulex-ui` | Next.js 15 / React 19 (App Router) | The first-party web app and a thin backend-for-frontend (BFF) proxy layer | `:3000` | | `modulex-ws` | Node 20 / TypeScript, Socket.io | The realtime multi-user canvas collaboration server | `:3001` | | `modulex-integrations` | Python 3.12 package | The installable tool catalog (175 integrations) loaded by the backend | — (library) | | `modulex-js` | TypeScript SDK | The JavaScript/TypeScript client wrapping the REST surface | — (library) | | `modulex-python` | Python SDK | The async Python client wrapping the REST surface | — (library) | | `modulex-www` | Static marketing site | The public marketing and pricing site | — (static) | The backend serves on `http://localhost:8000` in development; that is the canonical base URL for every example in these docs. (A `python -m app.main` shortcut binds `8001` to avoid local conflicts, but the standard `uvicorn` command and all tooling use `8000`.) The SDKs default to the production REST host `https://api.modulex.dev`. The integration count is **175 live integrations** — the figure to use anywhere a tool count appears. A vendored `dist/manifests.json` snapshot in `modulex-integrations` lags at 136 entries; do not quote it. See the [integration catalog](/integrations/catalog) for the authoritative list. ## The three shared backplanes Every component above is wired together by exactly three external services. The authoritative datastore. The backend is the only authoritative writer; `modulex-ws` does its own reads and writes against the same database. The Procrastinate job queue lives in a dedicated `procrastinate` schema in the same Postgres. Used by the backend for run-event pub/sub, history replay, rate-limit counters, caches, and credit reservation; used by `modulex-ws` as the Socket.io adapter and for presence and lock keys. The external identity provider. It issues the user JWTs that the backend, the app, and the realtime server all verify against Clerk's JWKS. The Postgres and Redis instances are shared, but the ownership boundaries are strict: - The backend **never** reads or writes the `presence:*` or `lock:workflow:*` Redis keys. Those belong exclusively to `modulex-ws`. - The only Redis surface the two backends are designed to share is pub/sub channels — and even that bridge is inert today (see [Components you should not build against](#components-you-should-not-build-against)). ## Component and data-flow map ```text ┌──────────────────────────┐ │ Clerk (external IdP) │ │ JWT issue + JWKS │ └───┬──────────────┬────────┘ sign-in / JWT │ │ JWKS verify │ │ ┌─────────────────────────▼─┐ ┌──▼──────────────────────┐ browser │ modulex-ui (Next.js :3000) │ │ modulex-ws (Socket.io │ ─────────►│ • App Router pages │ │ :3001) │ │ • app/api/* BFF proxies │ │ • canvas collaboration │ │ (add Bearer + X-Org-ID) │ │ • presence / locks │ └───┬───────────────┬────────┘ └────┬───────────┬────────┘ REST + SSE │ │ Socket.io │ Socket.io │ shared (proxied) │ └───────────────────────┘ │ Postgres + Redis │ │ (own reads) ▼ │ ┌───────────────────────────────────────────────────────────────┴──────────┐ │ modulex (FastAPI :8000) │ │ • 21 routers (REST) • SSE: workflow:run:{run_id}:events (Redis) │ │ • auth/org dependencies • workflow / Composer / Assistant engines │ │ • tool runtime ───────────────────────► loads @tool from modulex-integr. │ │ • billing / credit ledger • Procrastinate worker (separate process) │ └───┬───────────────┬───────────────────┬──────────────────────┬────────────┘ │ Postgres │ Redis │ entry-point group │ Stripe ▼ ▼ ▼ "modulex.tools" ▼ (external) ┌────────┐ ┌──────────┐ ┌──────────────────────┐ │Postgres│ │ Redis │ │ modulex-integrations │ │(+ proc-│ │(pub/sub, │ │ (installed package, │ │ rastin-│ │ cache, │ │ 175 tools) │ │ ate) │ │ rate-lim)│ └──────────────────────┘ └────────┘ └──────────┘ SDKs: modulex-js / modulex-python ──REST──► modulex (:8000) Docs: modulex-www (static marketing site, no runtime wire) ``` ## The five deployables There are five things that actually run. The other repositories are libraries (`modulex-integrations`, `modulex-js`, `modulex-python`) or a static site (`modulex-www`). | Unit | From repository | Process | | --- | --- | --- | | API server | `modulex` | `uvicorn app.main:app` (`:8000`) | | Background worker | `modulex` | `python -m app.workers.run_worker --queue default --queue ingest` | | App server | `modulex-ui` | Next.js (`:3000`) | | Realtime server | `modulex-ws` | `node dist/index.js` (`:3001`) | | (libraries / static) | `modulex-integrations`, `modulex-js`, `modulex-python`, `modulex-www` | not a process | The background worker is a separate process from the API server and does **not** run the API's startup lifespan. Tasks that need Redis (such as organization-usage refresh and the credit-reservation sweeper) connect to Redis themselves rather than relying on a shared client wired during API startup. ### Boot order The two long-lived servers each have a deterministic startup sequence. The root log level comes from `LOG_LEVEL` (default `INFO`). In `production` or `staging`, a failed security check aborts startup. In development it logs a warning and continues. Applies migrations and syncs the integration catalog from the installed `modulex-integrations` package into the database. Falls back to an in-memory implementation if Redis is unreachable. Wires the run-event delivery machinery (multi-instance aware). The LangGraph state checkpointer is always the Postgres saver. All 21 REST routers mount at their resource prefixes. There is no `/v1` segment. Opens three ioredis clients (default, subscriber, publisher). Runs `SELECT 1` against the shared database. Exposes `/health` and `/admin/*`. The adapter lets multiple realtime instances broadcast to one another. The handshake gate verifies the Clerk JWT and resolves org membership before any event handler runs. Binds node, edge, lock, cursor, presence, and patch handlers, then listens on `:3001`. ## The REST surface The backend exposes 21 routers, mounted directly at their resource prefixes (`/workflows`, `/composer`, `/assistant`, `/knowledge-bases`, `/credentials`, and so on). There is **no API version segment** — no `/v1` or `/api/v1`. Trailing-slash redirects are disabled, so a path is matched exactly as written. Every response body is **snake_case** JSON (Pydantic v2 default); request headers are PascalCase-hyphen (`X-Organization-ID`, `X-API-KEY`). The full request lifecycle, content types, and base URLs are covered in the [API overview](/api-reference/overview); the environments and versioning policy live in [Base URLs, environments & versioning](/get-started/environments). ### Standard error envelopes The backend emits **three** error-envelope shapes, and you must branch on all of them. Each is owned by a different surface: | Shape | Example | Where it comes from | | --- | --- | --- | | `{"detail": ""}` | `{"detail": "X-Organization-ID header is required"}` | The common FastAPI `HTTPException` on CRUD and org-settings routes | | `{"detail": {…}}` | `{"detail": {"code": "rate_limited", "layer": "rate", "key": "api", "current": 300, "limit": 300}}` | The org-level rate-limit deny | | Flat `{"code", "layer", "key", "current", "limit", "reason"}` | `{"code": "credit_plan_exhausted", "layer": "credit", "key": "run", "current": 5000, "limit": 5000, "reason": "plan_credits_exhausted"}` | The billing/credit admission gate (`DenialEnvelope`) on run surfaces | The complete shape catalog, status mapping, and which surface emits each lives in [Errors & status codes](/api-reference/errors). The billing/credit admission gate is **live** on the run, [Composer](/concepts/ai-composer), [Assistant](/assistant/overview), and managed-knowledge surfaces. Calls to those surfaces can return a flat `DenialEnvelope` as **402** (credit or wallet), **403** (quota), or **429** (rate) — alongside the existing rate-limit-header 429. Plain CRUD and org-settings routes do **not** return this envelope; they return the `{"detail": …}` `HTTPException` shape. See [Usage gating & limits](/billing/usage-gating). ## Auth and org context across every hop The same two pieces of context — a **Clerk JWT** (or an `mx_live_` API key) and an **organization id** — thread through every wire, but the transport differs per hop. | Hop | Identity token | Org context | | --- | --- | --- | | Browser → app | Clerk session | `selected_organization_id` cookie | | App BFF → backend (REST) | `Authorization: Bearer ` | `X-Organization-ID` header | | Backend dependency | Bearer JWT **or** `mx_live_*` API key | `X-Organization-ID` header (or `?organization_id` query) | | Browser → realtime server | `auth.token` (Clerk JWT) in the Socket.io handshake | `auth.organizationId` in the handshake | | SDK → backend | `mx_live_*` API key (or Clerk JWT) | `X-Organization-ID` header | The auth header is `Authorization: Bearer mx_live_…`, **not** `X-Authorization`. Both SDKs send `Authorization: Bearer` plus `X-Organization-ID`; the backend also accepts the key in an `X-API-KEY` header as an alternative. There is no `X-Authorization` header anywhere in the system. See [Authentication](/api-reference/authentication). The org context header is `X-Organization-ID` (exact casing) on every REST and SSE hop. The realtime server resolves org membership independently, through its own Redis-cached SQL lookup. The full model — Clerk JWT versus API key, roles, and the org header — is in [Auth model: JWT vs API key](/security/authentication) and [Org context & X-Organization-ID](/security/org-context). Live org roles are **`owner`** and **`admin`** only — the `member` role is retired. Composer, Assistant, and managed-knowledge actions require owner or admin. See [Roles & permissions](/security/roles-permissions). ## Two realtime planes ModuleX has two realtime systems that do not share a transport. Owned by the backend, delivered over **SSE** on top of Redis pub/sub. Carries workflow, Composer, and Assistant run progress: `metadata`, `node_update`, `tool_call`, `interrupt`, `done`, and more. Run endpoints emit flat `data: {"type": …}` frames; the discriminator is the `type` key inside the JSON, with no `event:` line. Owned by `modulex-ws`, delivered over **Socket.io**. Carries presence, cursors, node locks, node and edge edits, and external sync. The handshake takes the Clerk JWT and the org id; events are `node:add`, `patch`, `lock`, and the like. On reconnect, the SSE path is replay-safe: the listener first re-yields the run's Redis history list (one-hour TTL) in order, then tails live. Run-event delivery is Redis-pub/sub-native, so any backend replica subscribed to `workflow:run:{run_id}:events` receives events published by any other replica. The realtime server scales horizontally through the Socket.io Redis adapter. The end-to-end model is in [Realtime overview & event taxonomy](/realtime/overview) and the [realtime & collaboration model](/concepts/realtime-model). Not all SSE in the system uses the same frame convention. Run and agent streams use flat typed-JSON `data.type` frames; the sidebar chat-list stream (`GET /chats/stream`) uses **named** SSE events (`event: chat_list_updated`). A consumer that crosses both must expect both conventions. ## The tool runtime seam The most architecturally significant internal seam is how the backend turns a catalog name such as `github.create_issue` into an executed, billed action against the installed [integrations](/integrations/overview) package. At startup, the backend queries Python entry points in the `modulex.tools` group from the installed `modulex-integrations` package and caches the result for the process lifetime. Restart the worker to pick up a freshly installed package version. The startup sequence syncs the catalog into the database. The runtime then reads auth schemas from the **database** catalog, not from live package manifests — so a new manifest field is inert until the catalog is re-synced against the bumped package. The backend imports each tool's `@tool`-decorated function and wraps it so that credential fields are stripped before the model ever sees them. The executor resolves and decrypts the credential (refreshing OAuth2 tokens that are close to expiry), injects auth into the call, and invokes the tool. Tools run only inside workflow runs and Composer or Assistant turns — there is no REST endpoint for direct tool execution. Managed (`modulexai`) usage is metered in [credits](/billing/credits); bring-your-own-key (BYOK) credentials are tracked but not credit-limited. Most integrations come from the `modulex-integrations` package, but not all: legacy JSON-defined integrations such as `mcp_server` (which loads tools dynamically from a [custom MCP server](/integrations/building/custom-mcp) credential) are also loaded, and the package wins on a name collision. The full tool contract is in [Build an integration](/integrations/building/overview). ## SDK client configuration Both SDKs wrap the same REST surface and send the same headers. The constructor shapes differ in one structural way: the Python client reads environment-variable fallbacks; the JavaScript client does not. An `mx_live_…` API key. In Python this falls back to the `MODULEX_API_KEY` environment variable; in JavaScript it must be passed to the constructor or the client throws. The organization id sent as `X-Organization-ID`. In Python it falls back to `MODULEX_ORGANIZATION_ID`; in JavaScript it is constructor-only. The REST host. In Python it falls back to `MODULEX_BASE_URL`; in JavaScript it is constructor-only. Per-request timeout. No environment fallback in either SDK. Retry budget for retryable requests. No environment fallback in either SDK. Configure a client and trigger one workflow run. The run accepts either `workflowId` (a deployed workflow) or an inline `workflow` definition, plus `input`, `config`, and `stream`. ```bash cURL curl https://api.modulex.dev/workflows/run \ -H "Authorization: Bearer mx_live_2f8c9d1a4b6e7f0c3d5a8b2e" \ -H "X-Organization-ID: 7c1e9a4b-2d6f-4a83-9b15-0e2c8f6a1d34" \ -H "Content-Type: application/json" \ -d '{ "workflow_id": "wf_3a9c1e7b-5d24-4f81-bc06-2e8a9d4f1b73", "input": { "topic": "Q3 release notes" }, "stream": true }' ``` ```python Python import asyncio from modulex import Modulex async def main(): # api_key, organization_id, and base_url fall back to # MODULEX_API_KEY / MODULEX_ORGANIZATION_ID / MODULEX_BASE_URL. client = Modulex( api_key="mx_live_2f8c9d1a4b6e7f0c3d5a8b2e", organization_id="7c1e9a4b-2d6f-4a83-9b15-0e2c8f6a1d34", ) async with client: run = await client.executions.run( workflow_id="wf_3a9c1e7b-5d24-4f81-bc06-2e8a9d4f1b73", input={"topic": "Q3 release notes"}, stream=True, ) print(run) asyncio.run(main()) ``` ```javascript JavaScript import { Modulex } from "modulex-js"; // apiKey and organizationId are constructor-only (no env fallback in JS). const client = new Modulex({ apiKey: "mx_live_2f8c9d1a4b6e7f0c3d5a8b2e", organizationId: "7c1e9a4b-2d6f-4a83-9b15-0e2c8f6a1d34", }); const run = await client.executions.run({ workflowId: "wf_3a9c1e7b-5d24-4f81-bc06-2e8a9d4f1b73", input: { topic: "Q3 release notes" }, stream: true, }); console.log(run); ``` One wire-casing detail to keep in mind: the JavaScript SDK takes camelCase parameters and converts them to snake_case before sending, but it does **not** convert responses back — response fields stay snake_case (`run_id`, `created_at`). The Python SDK is snake_case in both directions. The full route-to-method mapping, naming differences, and parity gaps are in the [SDK ⇄ API parity matrix](/sdks/parity). ## Components you should not build against A few wires exist in the codebase but are inert or unbuilt today. Build against the live path, not these. The realtime server subscribes to literal Redis channels `workflow:updated` and `workflow:deleted`, but the backend publishes workflow-change events on per-workflow channels (`workflow:{id}:changes`). No backend publisher targets the flat channels, so those subscriptions never fire. Live external canvas sync flows over the **Socket.io `workflow:external-sync`** event, not the Redis bridge. See [Socket.io collaboration events](/realtime/socket-events) and [Realtime co-editing & external sync](/workflow-builder/realtime-coediting). Several app calls have no target on the path they use and are documented as known limitations, not working flows: OAuth2 refresh from the app (the backend route exists but the app's proxy route is missing — reconnect the integration instead), the chat `sendMessage` call (no backend route), the templates surface (no router shipped), and the plural `nodes:delete` realtime emit (only the singular `node:delete` handler is registered). See [Known limitations](/reference/known-limitations). The `subscriptions` resource exists only in the Python SDK; the JavaScript SDK has no equivalent. See [Subscriptions & Stripe](/billing/subscription-lifecycle) and the [parity matrix](/sdks/parity). ## How repositories stay in contract The repositories deploy independently, so ModuleX coordinates breaking changes through a file-based "brief" protocol: the backend holds inbound brief directories (one per sibling repository), each a markdown file with an acknowledgment block. Two conventions hold across them: - Breaking changes merge in the order **backend → realtime server → app**, with each side holding its branch until the upstream field lands. - Any new integration manifest field (a new env var or OAuth setting) is inert until the catalog sync re-reads the bumped package into the database — the database catalog, not live manifests, is the runtime source. ## Where to go next The end-to-end mental model, from a prompt or canvas to a running, observable workflow. The two realtime planes and their event taxonomies in full. The request lifecycle, content types, and how every operation is shown three ways. Hosts, environments, and the no-`/v1` versioning policy. --- # Core concepts at a glance URL: https://docs.modulex.dev/get-started/core-concepts A skimmable map of the ideas behind ModuleX: workflows and runs, nodes, the AI Composer, the Assistant, knowledge and RAG, credits, and organizations. This page is a map, not a manual. It defines the words you will meet everywhere else in the docs and points you to the page that covers each one in depth. Read it top to bottom once, then come back whenever a term is unfamiliar. If you only remember one sentence: **you build a workflow, you run it, and ModuleX meters managed usage in credits — all inside an organization.** **🎬 MEDIA PLACEHOLDER · MX-MEDIA-1020 · [IMAGE]** **[IMAGE_DESCRIPTION]:** A single concept map that shows how the core ideas connect. **[IMAGE_DETAILS]:** One illustration (not a screenshot). Center node "Organization" containing a "Workflow" made of "Nodes"; arrows out to "Run" (one execution), "AI Composer" (builds the workflow), "Assistant" (chat that uses tools), "Knowledge" (RAG), and "Credits" (meters managed usage). Label the connectors with plain verbs (builds, runs, retrieves, meters). 16:9, light theme, ModuleX brand palette, minimal text inside shapes. ## The building blocks Start here. These four terms describe what you make and what happens when you press run. A **workflow** is an editable graph that connects tools, data, and agents into a repeatable process. You build it on the visual canvas or generate it from a prompt with the AI Composer. A **run** is one execution of a workflow. Each run streams its progress live and ends in a final state you can inspect. You can run from chat, the API, or an SDK. A **node** is one step in a workflow. Every node writes its result back into the run so later steps can use it. ModuleX has nine node types. A **reference** — written `{{node_id.field}}` — pulls a value from an earlier node into a later one. References are how data flows between steps. ### The nine node types A workflow is assembled from a fixed set of nine **node types**. You do not need to memorize them now — each links to its own reference page. Call a language model with a prompt and optional structured output. Run an autonomous step that can call tools and loop until it is done. Retrieve relevant context from a knowledge base inside a run. Call one action from a connected integration. Run a built-in function such as an HTTP request or a webhook. Branch on an expression or an LLM decision, or loop over data. Pause the run to ask a person a question, then resume with their answer. Reshape, map, and combine data between steps. Validate content with schema, regex, or PII checks before it moves on. ## The two AI helpers ModuleX has two assistants, and they are easy to mix up. The short version: **the AI Composer builds workflows; the Assistant gets work done without one.** The text-to-workflow agent. Describe what you want in plain English and the **AI Composer** turns it into a complete, editable workflow graph — then keeps editing the canvas as you chat with it. A workflow-independent agentic chat. The **Assistant** searches your connected tools, decides the next step, calls tools, drafts outputs, and pauses for your approval before sensitive actions — no workflow required. Use the **AI Composer** when you want a reusable process you can run again and again — for example, "every time a form is submitted, summarize it and post to Slack." The Composer produces a workflow you own and can edit on the canvas. Use the **Assistant** for one-off or open-ended tasks where you do not want to build anything first — for example, "find the three most recent issues in this repo and draft a reply." The Assistant acts directly using your connected tools. Both can stop and ask you for input or approval mid-task. That pause-and-resume behavior is called **human-in-the-loop**. ## Knowledge and RAG **Knowledge** is your own content — documents, files, and data — made searchable so a workflow or the Assistant can pull in relevant context automatically. The retrieval technique behind this is **RAG** (retrieval-augmented generation). A **knowledge base** is the unit RAG searches over. You add documents to it; ModuleX splits them into chunks and indexes them for retrieval. Use the ModuleX-managed store (`modulexdb`), or bring your own vector store such as Qdrant, Pinecone, MongoDB Atlas, or Weaviate. Managed retrieval and ingest are billed in credits; bring-your-own stores are not. ## Connecting the outside world A **workflow** or the **Assistant** is only as useful as what it can reach. That is what integrations are for. An **integration** is a connector to an external service — Slack, GitHub, Google, and more. ModuleX ships **175** integrations. A **tool** is one callable action an integration exposes (for example, "create an issue"). A single integration usually exposes many tools. A **credential** is the stored, encrypted connection that lets ModuleX act on your behalf in an external service — for example an OAuth2 connection or an API key. **BYOK** (bring your own key) lets you connect your own model or service accounts. Usage you bring is billed by that provider, not metered as ModuleX credits. ## How usage is paid for Managed usage runs on **credits**. Bring-your-own-key usage does not consume credits — it is billed directly by your provider. A **credit** is the unit that meters ModuleX-managed usage. **100 credits equal $1.00** (one credit is $0.01). Each run, managed model call, and managed retrieval consumes credits. Your **plan** sets your monthly credit allowance and limits. The plans are **Free**, **Pro**, **Max**, and **Enterprise**. The prepaid **wallet** covers usage beyond your plan's allowance once you turn on extra usage. Top it up manually or automatically. Before a run, Composer turn, Assistant turn, or managed knowledge call starts, ModuleX checks that you have credit and capacity. If you do not, the request is declined with a clear billing error instead of running and failing. The usage gate is live on the run, AI Composer, Assistant, and managed-knowledge surfaces. When it declines a request you get a `402`, `403`, or `429` response carrying a structured reason. Plain create-read-update-delete and organization-settings calls are not gated this way. See [Usage gating & limits](/billing/usage-gating) and [Errors & status codes](/api-reference/errors) for the exact shapes. ## Where everything lives: organizations Everything in ModuleX belongs to an **organization**. It is the tenant and billing boundary — your workflows, credentials, knowledge bases, plan, wallet, and credits are all scoped to one organization. An **organization** is the shared workspace and billing unit. People you invite become members of it, and every run is metered against its plan and credits. Each member has a **role**. The live roles are **owner** and **admin**. Actions such as using the AI Composer, the Assistant, and schedules require owner or admin. Earlier ModuleX had a separate "member" role. It has been **retired** — the current roles are **owner** and **admin** only. If you see "member" referenced in older material, treat it as out of date. ## Working together in real time ModuleX is multi-user. Two things stream live, and they travel over different channels — worth knowing because the rest of the docs keep them separate. While a run executes, its progress streams to you event by event, so you can watch each node complete in real time. Several people can edit the same workflow canvas at once, with live cursors, presence, and locks so edits do not collide. ## Keep going See these concepts as one end-to-end flow, from a prompt to a running, observable workflow. Create an organization, get an API key, and make your first authenticated call. The full canonical term list, including the exact spellings the docs standardize on. --- # Base URLs, environments & versioning URL: https://docs.modulex.dev/get-started/environments The ModuleX API base URL (https://api.modulex.dev), how to point the SDKs at local or staging environments, why there is no /v1 path segment, and how the API and SDKs are versioned. The ModuleX REST API is served from a single production base URL. There is **no `/v1` (or `/api/v1`) path segment** — routers mount directly at their resource path, so a request URL is just the base URL joined to the route (for example `https://api.modulex.dev/workflows`). This page covers the base URL, how to point each surface at a different environment, the trailing-slash rule, and how the API and the SDKs are versioned. For how to authenticate those requests, see [Authentication](/api-reference/authentication). For the request lifecycle and content types, see the [API overview](/api-reference/overview). ## Base URL | Environment | Base URL | Notes | | --- | --- | --- | | Production (default) | `https://api.modulex.dev` | The base URL every SDK uses unless you override it. | | Local development | `http://localhost:8000` | The backend's documented local serving port. | Both the [JavaScript SDK](/sdks/javascript) and the [Python SDK](/sdks/python) default to `https://api.modulex.dev`, so you do not set the base URL for normal use. You only override it to target a local backend or a self-hosted deployment. The production OAuth redirect target — where a browser is sent back after connecting a credential — is the ModuleX **app** at `https://app.modulex.dev`, not the API host. The API host `https://api.modulex.dev` only serves REST endpoints and the realtime streams. See [Credentials & OAuth2](/concepts/credentials-oauth). ### How a request URL is built The SDKs build every request URL as `baseUrl + path`, with no version segment inserted. A trailing slash on the base URL is stripped before the path is joined, so `https://api.modulex.dev/` and `https://api.modulex.dev` behave identically. ```text Request URL composition base URL https://api.modulex.dev + route path /workflows/9d1f0c2e-4b7a-4c1e-8f3a-2b6d5e0a1c34/run = request URL https://api.modulex.dev/workflows/9d1f0c2e-4b7a-4c1e-8f3a-2b6d5e0a1c34/run ``` ## No `/v1` path segment ModuleX does not put a version number in the URL path. Every resource router mounts directly at its prefix: | Resource | Full URL | | --- | --- | | Workflows | `https://api.modulex.dev/workflows` | | Run a workflow | `https://api.modulex.dev/workflows/{workflow_id}/run` | | Knowledge bases | `https://api.modulex.dev/knowledge-bases` | | Credentials | `https://api.modulex.dev/credentials` | | Integrations | `https://api.modulex.dev/integrations` | | Organizations | `https://api.modulex.dev/organizations` | | API keys | `https://api.modulex.dev/api-keys` | Do not prefix routes with `/v1` or `/api/v1`. There is no such segment — a request to `https://api.modulex.dev/v1/workflows` does not resolve to the workflows router. Use `https://api.modulex.dev/workflows`. ## Trailing slashes The API has **automatic trailing-slash redirects turned off**. A route is matched exactly as declared, so request the path without a trailing slash: | Request | Result | | --- | --- | | `GET /workflows` | Matches the list-workflows route. | | `GET /workflows/` | Does **not** redirect to `/workflows`; it is treated as a different, unmatched path. | Always call the canonical path with no trailing slash. The SDKs already do this for you. ## Switching environments You change the environment by changing the base URL. Authentication is unchanged across environments — every request still sends `Authorization: Bearer mx_live_…` and, for organization-scoped endpoints, `X-Organization-ID`. Use a key that belongs to the environment you are calling. ```bash cURL # Production (default) curl https://api.modulex.dev/workflows \ -H "Authorization: Bearer mx_live_8sJ2kQ4mZ1pX7vR9tB3nW6yL0aD5cF" \ -H "X-Organization-ID: 9d1f0c2e-4b7a-4c1e-8f3a-2b6d5e0a1c34" # Local backend — same call, base URL swapped curl http://localhost:8000/workflows \ -H "Authorization: Bearer mx_live_8sJ2kQ4mZ1pX7vR9tB3nW6yL0aD5cF" \ -H "X-Organization-ID: 9d1f0c2e-4b7a-4c1e-8f3a-2b6d5e0a1c34" ``` ```python Python from modulex import Modulex # Production (default) — no base_url needed client = Modulex( api_key="mx_live_8sJ2kQ4mZ1pX7vR9tB3nW6yL0aD5cF", organization_id="9d1f0c2e-4b7a-4c1e-8f3a-2b6d5e0a1c34", ) # Local backend — override base_url local = Modulex( api_key="mx_live_8sJ2kQ4mZ1pX7vR9tB3nW6yL0aD5cF", organization_id="9d1f0c2e-4b7a-4c1e-8f3a-2b6d5e0a1c34", base_url="http://localhost:8000", ) ``` ```javascript JavaScript import { Modulex } from "modulex"; // Production (default) — no baseUrl needed const client = new Modulex({ apiKey: "mx_live_8sJ2kQ4mZ1pX7vR9tB3nW6yL0aD5cF", organizationId: "9d1f0c2e-4b7a-4c1e-8f3a-2b6d5e0a1c34", }); // Local backend — override baseUrl const local = new Modulex({ apiKey: "mx_live_8sJ2kQ4mZ1pX7vR9tB3nW6yL0aD5cF", organizationId: "9d1f0c2e-4b7a-4c1e-8f3a-2b6d5e0a1c34", baseUrl: "http://localhost:8000", }); ``` ### Configuring the base URL per SDK The two SDKs differ in whether the base URL can come from an environment variable. This matters when you deploy the same code to different environments. **Python SDK.** Set it explicitly on the constructor, or set the `MODULEX_BASE_URL` environment variable, or let it fall back to the default. The resolution order is **constructor argument → `MODULEX_BASE_URL` → default**. The same fallback exists for `MODULEX_API_KEY` and `MODULEX_ORGANIZATION_ID`. **JavaScript SDK.** Set it on the constructor (`baseUrl`) or omit it for the default. The JavaScript SDK has **no environment-variable fallback** — there is no `MODULEX_BASE_URL` lookup, and likewise no env fallback for the API key or organization ID. Read any environment variables in your own code and pass the value to the constructor. ```javascript JavaScript — env-driven base URL (you read the env var) import { Modulex } from "modulex"; // The JS SDK does not read process.env itself — pass it through explicitly. const client = new Modulex({ apiKey: process.env.MODULEX_API_KEY ?? "", organizationId: process.env.MODULEX_ORGANIZATION_ID ?? "", baseUrl: process.env.MODULEX_BASE_URL ?? "https://api.modulex.dev", }); ``` **🎬 MEDIA PLACEHOLDER · MX-MEDIA-1070 · [IMAGE]** **[IMAGE_DESCRIPTION]:** A side-by-side diagram of base-URL resolution for the Python and JavaScript SDKs. **[IMAGE_DETAILS]:** Two columns. Python column shows a resolution chain: `base_url` argument → `MODULEX_BASE_URL` env var → default `https://api.modulex.dev`. JavaScript column shows: `baseUrl` argument → (no env fallback) → default `https://api.modulex.dev`. Highlight in a contrasting color that JS skips the env-var step. Clean, light theme, 16:9, suitable for inline placement under the "Configuring the base URL per SDK" heading. ## Versioning ModuleX versions the API and the SDKs separately. Neither uses a version segment in the URL path. ### API versioning The API is **unversioned in the URL** — there is no `/v1` path segment and no API-version request header to send. The backend application advertises an internal version string of `0.1.2` (visible at the service root `GET /` and the health endpoint `GET /system/health`), but you do not pass a version on a request, and you cannot pin to a specific API version from the client side. Because there is no version negotiation, ModuleX evolves the API in a backward-compatible way wherever possible. Track changes that affect you through the [changelog](/reference/changelog), and use the [SDK ⇄ API parity matrix](/sdks/parity) to confirm which routes a given SDK release covers. ### SDK versioning (semantic versioning) The official SDKs follow semantic versioning and ship at version **1.0.0**: | SDK | Package | Version | Page | | --- | --- | --- | --- | | JavaScript / TypeScript | `modulex` | `1.0.0` | [JavaScript SDK](/sdks/javascript) | | Python | `modulex` | `1.0.0` | [Python SDK](/sdks/python) | Pin the SDK version in your dependency manifest so a major release does not change behavior under you. Breaking changes land in a new major version; consult the changelog before upgrading across a major boundary. Notable changes already shipped in the 1.0 line include the retirement of the organization `member` role (invites accept only `admin`; see [Roles & permissions](/security/roles-permissions)), realtime SSE frames discriminating on `type` rather than `event`, and the removal of client-side billing helpers from the JavaScript SDK. The Python SDK sends a `User-Agent` of `modulex-python/` on every request; the JavaScript SDK does not set a `User-Agent`. Neither identifies an API version, because the API is unversioned. ## Interactive API documentation The running backend also serves machine- and human-readable API references at the base URL: | Path | Surface | | --- | --- | | `https://api.modulex.dev/docs` | Swagger UI (interactive). | | `https://api.modulex.dev/redoc` | ReDoc reference. | | `https://api.modulex.dev/system/health` | Public liveness check returning `{ "status": "healthy", "service": "ModuleX", "version": "0.1.2" }`. | For the curated, language-tabbed reference and a live "Try it" playground inside these docs, use the [API overview](/api-reference/overview) and the endpoint reference. To check live availability, see [System status & health](/reference/status). ## What does not change per environment Switching environments changes only the base URL. The following are the same across production and a local or self-hosted backend: Every request sends `Authorization: Bearer mx_live_…`, and organization-scoped requests also send `X-Organization-ID`. See [Authentication](/api-reference/authentication). The same paths with no `/v1` segment, called without a trailing slash. The same three error shapes, including the billing `DenialEnvelope` returned as 402 / 403 / 429 on metered surfaces. See [Errors & status codes](/api-reference/errors). The same per-key, per-user, and per-organization limits and `429` responses. See [Rate limiting](/api-reference/rate-limiting). ## Next steps Authenticate and run a workflow with cURL, Python, and JavaScript. The `Authorization: Bearer` header and `X-Organization-ID` org context. The request lifecycle, content types, and how each operation is shown three ways. Which REST routes each SDK release maps to, with gaps called out. --- # Make your first API call URL: https://docs.modulex.dev/get-started/first-api-call Run a ModuleX workflow programmatically and stream its result with cURL, Python, and JavaScript — authenticated with Authorization: Bearer and X-Organization-ID. This page runs a deployed workflow from code and streams its result, end to end, in three languages. You will send one authenticated `POST` to start a run, then open a Server-Sent Events (SSE) stream to watch it finish. Every operation is shown once as a `` with cURL, Python, and JavaScript tabs. If you have not yet created an organization and an API key, start with the [Quickstart](/get-started/quickstart) and come back here. For the full request lifecycle, base URLs, and the unified SDK reference, see the [API overview](/api-reference/overview) and the [SDKs overview](/sdks/overview). You need a saved workflow with an **active deployment** to run it by `workflow_id`. Deploying a workflow snapshots its schema and marks one deployment live; `POST /workflows/run` loads that live snapshot. Build and deploy one first in the [workflow builder](/workflow-builder/overview), then run it here. You can also run an inline ad-hoc definition — see [Running by ID vs. inline definition](#running-by-id-vs-inline-definition). ## Before you begin You need three things: A user API key with the `mx_live_` prefix. Create one in the app under your organization's API Keys settings, or with `POST /api-keys`. Treat it like a password — it carries your organization's permissions. The UUID of the organization the call runs against. Every org-scoped request requires it in the `X-Organization-ID` header. Retrieve it from `GET /auth/me` (the `primary_organization_id` field) or `GET /organizations`. A workflow with an active deployment, identified by its `workflow_id`. If the workflow has no live deployment, `POST /workflows/run` returns `400` — deploy it first. The base URL is `https://api.modulex.dev`. Routers mount at the root, so there is **no `/v1` or `/api` path segment** — the run endpoint is exactly `https://api.modulex.dev/workflows/run`. See [Base URLs, environments & versioning](/get-started/environments) for details. ## Authentication Authenticate every request with two headers: ```http Authorization: Bearer mx_live_xxx X-Organization-ID: 11111111-1111-1111-1111-111111111111 ``` The auth header is `Authorization: Bearer`, **not** `X-Authorization`. There is no `X-Authorization` header anywhere in ModuleX. The backend accepts your API key as `Authorization: Bearer mx_live_…` or, alternatively, as `X-API-KEY: mx_live_…`. Both official SDKs send the `Authorization: Bearer` form. See [Authentication](/api-reference/authentication) for the complete model. `Bearer mx_live_`. The same scheme carries either a user API key (`mx_live_` prefix) or a Clerk JWT (browser sessions); the backend distinguishes them by the prefix. For programmatic calls, use your `mx_live_` key. The organization UUID the request is scoped to. **Required** on org-scoped endpoints, including `/workflows/run`. If it is missing, the request fails with `400` and `{"detail": "X-Organization-ID header is required"}`. Optional alternative to `Authorization: Bearer` for the API key. Send either one, not both. The SDKs do not use this header. `application/json` on the `POST` body. Omit it on the SSE `GET` (the listen call has no body). `POST /workflows/run` requires the caller to be an **owner or admin** of the organization. A non-admin caller receives `403`. The `member` role is retired and is not a current first-class role — see [Roles & permissions](/security/roles-permissions). ## Install an SDK The cURL examples need no dependencies. To use a typed client, install one of the official SDKs. ```bash Python pip install modulex-python ``` ```bash JavaScript npm install modulex-js ``` The Python SDK is **async-only** and reads `MODULEX_API_KEY`, `MODULEX_BASE_URL`, and `MODULEX_ORGANIZATION_ID` from the environment when the matching constructor argument is omitted. The JavaScript SDK has **no environment-variable fallback** — pass `apiKey` (and `organizationId`) to the constructor explicitly. Both default `baseUrl` to `https://api.modulex.dev`. This divergence is documented on the [JavaScript SDK](/sdks/javascript) and [Python SDK](/sdks/python) pages. ## Step 1 — Start a run Send a `POST` to `/workflows/run` with the `workflow_id` of your deployed workflow and the `input` your workflow's state expects. The request returns immediately — the run executes in the background — so the response status is `running`, not a final result. In the SDKs this operation is `client.executions.run(...)` (the execution-control methods live under `/workflows` on the backend but are grouped as `executions` in both clients). ```bash cURL curl -X POST https://api.modulex.dev/workflows/run \ -H "Authorization: Bearer mx_live_xxx" \ -H "X-Organization-ID: 11111111-1111-1111-1111-111111111111" \ -H "Content-Type: application/json" \ -d '{ "workflow_id": "550e8400-e29b-41d4-a716-446655440000", "input": { "query": "Summarize this week'\''s AI news" }, "config": { "recursion_limit": 50 } }' ``` ```python Python import asyncio from modulex import Modulex async def main(): async with Modulex( api_key="mx_live_xxx", organization_id="11111111-1111-1111-1111-111111111111", ) as client: run = await client.executions.run( workflow_id="550e8400-e29b-41d4-a716-446655440000", input={"query": "Summarize this week's AI news"}, config={"recursion_limit": 50}, ) print(run.status, run.run_id, run.thread_id) asyncio.run(main()) ``` ```javascript JavaScript import { Modulex } from "modulex-js"; const client = new Modulex({ apiKey: "mx_live_xxx", organizationId: "11111111-1111-1111-1111-111111111111", }); const run = await client.executions.run({ workflowId: "550e8400-e29b-41d4-a716-446655440000", input: { query: "Summarize this week's AI news" }, config: { recursionLimit: 50 }, }); console.log(run.status, run.run_id, run.thread_id); ``` In the JavaScript SDK, request parameters are **camelCase** (`workflowId`, `recursionLimit`) and are converted to snake_case on the wire — but **response fields stay snake_case** (`run.run_id`, `run.thread_id`). The Python SDK is snake_case in both directions. This is why the JS example reads `run.run_id`, not `run.runId`. ### Request body The `/workflows/run` body is a JSON object. Provide **exactly one** execution mode (`workflow_id`, `workflow`, or `system_workflow`); the rest of the fields are optional. The UUID of a saved workflow. ModuleX loads that workflow's **live deployment** snapshot. Returns `400` if the workflow has no active deployment. Mutually exclusive with `workflow` and `system_workflow`. An inline ad-hoc workflow definition (a full `WorkflowDefinition`) to run without saving it first. The run is marked `is_ad_hoc=true`. Mutually exclusive with `workflow_id` and `system_workflow`. The name of a built-in system workflow. When used, both `input` and `config` are required (`400` otherwise). Mutually exclusive with the other two modes. The initial state passed to the workflow's entry node, keyed by your `state_schema` fields. Per-run execution config. Recognized keys: `thread_id` (string, reuse a checkpoint thread), `recursion_limit` (integer, caps graph steps; the workflow's own default is `500`), and `batch_interval_ms` (integer, event batching cadence). When running by `workflow_id`, these merge over the deployment's stored config. Echoed back in the response. Run events are always observed by opening the SSE stream in step 2; this flag does not toggle inline token streaming. When `true`, the run is not attached to a chat record: `chat_id` is `null` and `thread_id` is a fresh UUID. When `true`, scopes the created chat as private. Sent as `is_private` on the wire (`isPrivate` in the JS SDK). Attributes an inline (`workflow`) run to a saved workflow's run history without switching to deployment-load mode. ### Response A successful start returns `200` with the run metadata. The run is not finished — you receive identifiers to track it. ```json { "status": "running", "run_id": "6a7b8c9d-1e2f-3a4b-5c6d-7e8f9a0b1c2d", "thread_id": "550e8400-e29b-41d4-a716-446655440000", "chat_id": "550e8400-e29b-41d4-a716-446655440000", "ephemeral": false, "stream": true, "workflow_name": "Research", "workflow_version": "1.0", "workflow_source": "database", "elapsed_ms": 142.7, "human_message": { "id": "...", "role": "human", "content": {} }, "ai_message": { "id": "...", "role": "ai", "running_status": "running" }, "message": "Workflow execution started in background. Use /workflows/listen/{run_id} to track progress." } ``` Always `"running"` on a successful start. The terminal outcome arrives over the SSE stream, not in this response. The per-execution identifier. Use it to listen, cancel, and look up the run. This is the id you pass to `GET /workflows/listen/{run_id}`. The LangGraph checkpoint thread. Equals `chat_id` when `ephemeral=false`. Use it for `GET /workflows/state/{thread_id}` and to resume after an interrupt. The chat the run is attached to, or `null` for an ephemeral run. Where the executed schema came from: `database`, `request` (inline), or `system:`. The name of the workflow being executed. The version label of the executed workflow. Wall-clock time spent setting up the run before responding. The persisted user message envelope, or `null` for an ephemeral run. The persisted assistant message envelope (with `running_status`), or `null` for an ephemeral run. The three identifiers above are **not interchangeable**, and ModuleX uses the word "run id" in three distinct senses. Pass the right one to each call: - `run_id` — the per-execution id for `GET /workflows/listen/{run_id}` and `POST /workflows/cancel/{run_id}`. - `thread_id` — the checkpoint thread for `GET /workflows/state/{thread_id}` and `POST /workflows/resume/{thread_id}`. - The run-history **primary key** (the `id` field on a `GET /workflow-runs` row) — for `GET /workflow-runs/{run_pk}`. Passing the `run_id` there returns `404`. See [Workflows & runs](/concepts/workflows-and-runs) for the full breakdown. ### Running by ID vs. inline definition Most calls run a saved, deployed workflow by `workflow_id`. To run a definition without saving it, send it inline under `workflow` instead. ```bash cURL curl -X POST https://api.modulex.dev/workflows/run \ -H "Authorization: Bearer mx_live_xxx" \ -H "X-Organization-ID: 11111111-1111-1111-1111-111111111111" \ -H "Content-Type: application/json" \ -d '{ "workflow": { "metadata": { "name": "Ad-hoc research", "version": "1.0" }, "state_schema": { "fields": { "query": { "type": "string" } } }, "nodes": [], "edges": [], "entry_point": "__start__" }, "input": { "query": "Summarize this week'\''s AI news" } }' ``` ```python Python run = await client.executions.run( workflow={ "metadata": {"name": "Ad-hoc research", "version": "1.0"}, "state_schema": {"fields": {"query": {"type": "string"}}}, "nodes": [], "edges": [], "entry_point": "__start__", }, input={"query": "Summarize this week's AI news"}, ) ``` ```javascript JavaScript const run = await client.executions.run({ workflow: { metadata: { name: "Ad-hoc research", version: "1.0" }, state_schema: { fields: { query: { type: "string" } } }, nodes: [], edges: [], entry_point: "__start__", }, input: { query: "Summarize this week's AI news" }, }); ``` Inside the inline `workflow` definition, field names are already snake_case (`state_schema`, `entry_point`) in the JavaScript SDK too — only the outer parameters use camelCase. For the complete `WorkflowDefinition` contract, see [Workflow engine & nodes](/concepts/workflow-engine) and [Variables & references](/workflow-builder/variables-and-references). ## Step 2 — Stream the result Open `GET /workflows/listen/{run_id}` to receive run events over SSE. The stream replays any buffered history first, then tails live, and closes after a terminal event (`done`, `error`, or `cancelled`). Multiple clients can listen to the same `run_id` concurrently. In the SDKs this is `client.executions.listen(run_id)`, which yields parsed events you iterate over. ```bash cURL curl -N https://api.modulex.dev/workflows/listen/6a7b8c9d-1e2f-3a4b-5c6d-7e8f9a0b1c2d \ -H "Authorization: Bearer mx_live_xxx" \ -H "X-Organization-ID: 11111111-1111-1111-1111-111111111111" ``` ```python Python async with client.executions.listen(run.run_id) as stream: async for event in stream: if event.event == "node_update": print("node", event.data.get("node"), "→", event.data.get("output")) if event.is_terminal: print("finished:", event.event) break ``` ```javascript JavaScript for await (const event of client.executions.listen(run.run_id)) { if (event.type === "node_update") { console.log("node", event.node, "→", event.output); } if (event.type === "done" || event.type === "error" || event.type === "cancelled") { console.log("finished:", event.type); break; } } ``` The two SDKs surface the event discriminator differently. The Python `SSEEvent` normalizes it to `event.event` and exposes an `is_terminal` property; the JavaScript event uses `event.type`. Both read the same underlying wire field. The Python SDK filters heartbeat frames out by default; the JavaScript SDK skips the SSE comment lines used as keepalives. ### SSE frame format Run events are **data-only** SSE frames: each frame is `data: {json}\n\n` with **no `event:` line**. The discriminator is the `type` key inside the JSON. Here is a raw stream for a run that completes: ```text data: {"type":"metadata","data":{"run_id":"6a7b...","thread_id":"550e...","workflow_name":"Research","workflow_version":"1.0","workflow_type":"workflow","timestamp":"2026-06-21T10:00:00.000000+00:00"}} data: {"type":"node_update","node":"plan","output":{"messages":[{"type":"ai","content":"..."}]}} data: {"type":"heartbeat"} data: {"type":"node_update","node":"summarize","output":{"summary":"This week in AI: ..."}} data: {"type":"done","data":{"message":"Workflow completed successfully"}} ``` A failed run ends with `data: {"type":"error","message":"Execution failed: "}` instead of `done`; a cancelled run ends with `data: {"type":"cancelled","data":{...}}`. ### Run event types These are the event shapes the executor publishes on the wire. Note that some payloads are **wrapped** under a `data` key while others are **flat** alongside `type` — the table below reflects what a live client actually receives. First frame. `data` carries `run_id`, `thread_id`, `workflow_name`, `workflow_version`, `workflow_type`, and `timestamp`. A node produced output. Carries `node` (the node **name**) and `output` (the serialized state delta or messages). The knowledge node emits a richer `output` object with retrieval details. A node began executing. Carries `node`, `name`, `timestamp`, and `metadata`. Emitted by some node types (for example, knowledge). The run paused at an interrupt (human-in-the-loop) node. `data` carries `thread_id`, `message`, and an optional `resume_schema`. This event does **not** close the live stream — resume with `POST /workflows/resume/{thread_id}` to continue. See [Human-in-the-loop (HITL) resume](/realtime/hitl). Emitted after a resume. `data` carries `run_id`, `thread_id`, `resume_value`, and `timestamp`. A resumed workflow run keeps the **same** `run_id`. The run completed successfully. `data` is `{"message": "Workflow completed successfully"}`. Closes the stream. The run failed. Carries a `message` string. Closes the stream. The run was cancelled. `data` carries `run_id`, `reason`, and `cancelled_at` (or is `null` if the cancel record already expired). Closes the stream. A `{"type":"heartbeat"}` keepalive injected after every 15 seconds of silence so a long pause does not idle-close the connection. Not a workflow event — ignore it. The SDKs handle this for you. The durable run status uses different wording from the live stream: the SSE `done` event corresponds to the durable status `succeeded` you see later in `GET /workflow-runs`. The live status string is `completed`; the persisted status is `succeeded`. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-1050 · [IMAGE]** **[IMAGE_DESCRIPTION]:** Sequence diagram of the first API call: client → `POST /workflows/run` → background executor → Redis → SSE `GET /workflows/listen/{run_id}` → terminal `done`. **[IMAGE_DETAILS]:** Three lanes (Your code, ModuleX API, Background run). Show the `POST` returning `{status:"running", run_id}` immediately, the executor publishing `metadata` → `node_update` → `heartbeat` → `done` frames, and the client iterating the SSE stream until the terminal event. Label headers `Authorization: Bearer` + `X-Organization-ID` on both HTTP arrows. Landscape 16:9, light theme, neutral palette, no app chrome. ## Step 3 — Look up the run afterward Once the stream closes, you can read the durable run record from history. List recent runs, then fetch one by its history **primary key** (the `id` field, not the `run_id`). ```bash cURL # List recent runs for a workflow curl https://api.modulex.dev/workflow-runs?workflow_id=550e8400-e29b-41d4-a716-446655440000&limit=10 \ -H "Authorization: Bearer mx_live_xxx" \ -H "X-Organization-ID: 11111111-1111-1111-1111-111111111111" ``` ```python Python runs = await client.executions.list_runs( workflow_id="550e8400-e29b-41d4-a716-446655440000", limit=10 ) latest = runs.runs[0] detail = await client.executions.get_run(latest.id) # latest.id is the PK, not run_id print(detail.status, detail.output_summary) ``` ```javascript JavaScript const { runs } = await client.workflowRuns.list({ workflowId: "550e8400-e29b-41d4-a716-446655440000", limit: 10, }); const detail = await client.workflowRuns.get(runs[0].id); // runs[0].id is the PK, not run_id console.log(detail.status, detail.output_summary); ``` Run history is grouped differently in the two SDKs: the Python SDK folds it into `client.executions` (`list_runs` / `get_run`), while the JavaScript SDK exposes a separate `client.workflowRuns` resource (`list` / `get`). Both call the same `GET /workflow-runs` routes. ## Errors `POST /workflows/run` is a **billing-gated** surface. Besides the usual validation and auth errors, it can return a billing denial before any run is created. ### Standard errors These use the FastAPI envelope `{"detail": ""}`. | Status | When it happens | | --- | --- | | `400` | Missing `X-Organization-ID`; no execution mode supplied; running by `workflow_id` with no active deployment; `system_workflow` without `input`/`config`. | | `401` | Missing or invalid token (`WWW-Authenticate: Bearer` is returned). | | `403` | The caller is not an organization owner/admin. | | `404` | The workflow (or, on listen, the run) is not found in your organization. The same `404` covers both not-found and cross-tenant access — there is no existence leak. | | `410` | Legacy LLM-only mode (an `llm`-only body with no workflow) is removed. Use the [Assistant](/assistant/overview) instead. | | `422` | Request validation failed (FastAPI validation array). | | `500` | A malformed inline `workflow` schema surfaces as `500` (it is validated inside the handler, not as a typed request model), as do other unexpected failures. | ### Billing denials (the billing gate) When your organization is over a credit, quota, or rate limit, the gate rejects the call **before any run record, chat row, or background task is created**. The response is a **flat** `DenialEnvelope` — note there is **no `detail` wrapper**: ```json { "code": "credit_plan_exhausted", "layer": "credit", "key": "credit:org:11111111-1111-1111-1111-111111111111", "current": 5000.0, "limit": 5000.0, "reason": "credit_plan_exhausted" } ``` The HTTP status is determined by the `layer`: | `layer` | Status | `code` values | | --- | --- | --- | | `rate` | `429` | `rate_limit_exceeded` (also sends `Retry-After` and `X-RateLimit-*` headers) | | `quota` | `403` | `quota_exceeded` | | `credit` | `402` | `credit_plan_exhausted`, `upgrade_payment_failed` | | `wallet` | `402` | `wallet_overage_disabled`, `wallet_insufficient` | The flat `DenialEnvelope` shape is specific to billing-gated surfaces (run, composer, assistant, managed knowledge). Plain CRUD and org-settings routes do **not** return it — they return the `{"detail": …}` shape. Branch on both. The complete taxonomy of all three error-envelope shapes is on the [Errors & status codes](/api-reference/errors) page; usage limits are detailed in [Usage gating & limits](/billing/usage-gating). In the SDKs, the Python client maps these to typed exceptions: `402` → `PaymentRequiredError` (or `CreditExhaustedError` / `WalletError` by layer), `403` → `PermissionError` (or `QuotaExceededError`), and `429` → `RateLimitError` carrying the retry headers. The JavaScript client maps `403` → `PermissionError` and `429` → `RateLimitError`; `402` falls through to the base `ModulexError`, where you can still read `code`, `layer`, and `reason`. See [SDK errors & retries](/sdks/errors-retries). ### Retries and idempotency The SDKs retry only safe, idempotent requests (`GET`/`HEAD`) on `429`, `500`, `502`, and `503`, with backoff that honors `Retry-After`. A `POST /workflows/run` is **not** auto-retried. The Python SDK accepts an `idempotency_key` argument and sends it as the `Idempotency-Key` header on mutating calls, but `POST /workflows/run` mints its own `run_id` server-side and does not read that header — so it is a **no-op for run de-duplication**. Do not rely on `Idempotency-Key` to prevent duplicate runs. See [SDK errors & retries](/sdks/errors-retries). ## Next steps The full guide: authenticate, run, stream, and handle interrupts and the billing gate end to end. The complete SSE frame format, event taxonomy, and reconnect/replay behavior. The JavaScript and Python clients, unified by operation, with the full parity matrix. Base URLs, the request lifecycle, content types, and how every operation is shown three ways. --- # Run your first workflow URL: https://docs.modulex.dev/get-started/first-workflow Build your first ModuleX workflow with no code — describe it to the AI Composer or drag nodes onto the canvas, then run it and watch the live result stream in. This is the fastest way to see ModuleX work end to end. You will create a workflow in the [workflow builder](/workflow-builder/overview), run it, and watch each step report its result live — all without writing code or touching the API. You have two ways to build, and you can mix them: Type what you want in plain English. The [AI Composer](/concepts/ai-composer) generates an editable workflow on the canvas for you. Drag [nodes](/workflow-builder/nodes/overview) onto the [canvas](/workflow-builder/canvas) and connect them yourself. Full control, no AI. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-1040 · [APP_VIDEO]** **[APP_VIDEO_DESCRIPTION]:** A complete no-code first-workflow walkthrough: open the builder, describe a workflow to the Composer, watch it build the canvas, click Run, and watch the live stream finish. **[APP_VIDEO_DETAILS]:** Screen recording of the live ModuleX app. Start on an empty `/workflow-builder` state, create a workflow, open the Composer panel, type "Summarize the input text and post the summary to a Slack channel", show streamed build steps and the canvas filling in, then click Run and show node statuses going pending → running → completed with output. 60–90s, 16:9, dark theme, light cursor highlight, no audio narration needed. ## Before you start Open the app and sign in. If this is your first time, create an account and an organization — every workflow belongs to an organization. Go to the workflow builder. If you have no workflows yet, you will see an empty state with a **Create your first workflow** button. Building and running workflows in the app requires an **owner** or **admin** role in your organization. If you do not have it, ask an organization owner to grant access. See [organizations, roles & membership](/concepts/organizations-roles). ## Option A — Describe it to the Composer The [AI Composer](/concepts/ai-composer) turns a plain-English description into a real, editable workflow on the canvas. It is the quickest way to get a working draft. From the builder, create a new workflow. The right-hand panel includes a **Chat** tab — this is the Composer. Type a request in your own words, for example: ```text Example Composer prompt Create a workflow that summarizes the input text and posts the summary to a Slack channel. ``` The Composer streams its work back to you: you will see it plan, add nodes, connect them, and explain what it changed. The Composer may pause to ask you something — for example, which option to use or to connect a credential for an integration like Slack. Answer in the chat and it continues. This pause-and-ask behavior is [human-in-the-loop](/concepts/ai-composer); the Composer waits for your reply before moving on. When the Composer finishes, its edits appear on the canvas as **pending changes**. Use **Save** to keep them or **Revert** to roll back to the previous version. Nothing is permanent until you save. The Composer edits a real workflow graph, not a throwaway preview. After it finishes you can keep refining by hand on the canvas, or ask it for more changes in the same chat. For a longer, guided build, see [build a workflow with Composer](/guides/build-with-composer). **🎬 MEDIA PLACEHOLDER · MX-MEDIA-1041 · [APP_VIDEO]** **[APP_VIDEO_DESCRIPTION]:** The Composer generating a workflow from a single prompt and pausing once to ask the user to connect a credential. **[APP_VIDEO_DETAILS]:** Screen recording of the live ModuleX app, focused on the builder right panel (Chat tab) and the canvas. Show the user typing a one-line prompt, streamed text and build steps, nodes appearing on the canvas, a single human-in-the-loop question card (e.g. "Connect Slack?"), the user answering, and the pending-changes Save/Revert bar appearing. 30–45s, 16:9, dark theme, no audio. ## Option B — Build it by hand on the canvas Prefer to drive it yourself? Build directly on the [canvas](/workflow-builder/canvas). Drag [nodes](/workflow-builder/nodes/overview) from the node dock onto the canvas. Each node is one step. Common starting points: Call a language model with a prompt to draft, summarize, or classify text. Call an integration action, such as sending a Slack message or creating a record. Retrieve relevant context from a knowledge base. Branch the flow or loop over a list of items. Draw an edge from the **start** marker into your first node, then connect each node to the next. The start marker is added for you — you connect your steps to it. Every node passes its result forward so later steps can use it. Select a node to open the detail panel and fill in its settings — the prompt for an LLM node, the action and inputs for a tool node, and so on. If a node needs a connected account, the builder flags it so you can connect a [credential](/integrations/authentication) before running. The canvas validates as you build. It will flag a node that has no path from the start marker, a node that is missing a required credential, or a knowledge node with no query — so you can fix issues before you run. ## Run it and watch the stream When your workflow is ready, run it from the builder and watch each step report back in real time. Select **Run**. If your workflow takes input, enter the values it asks for first. The builder runs the workflow exactly as it appears on your canvas — you do not need to deploy it first to test it. Each node moves through **pending → running → completed** as the run progresses, and its output appears as it finishes. Behind the scenes this is a live event stream, so you see results arrive step by step rather than waiting for the whole run to end. If your workflow includes an [interrupt node](/workflow-builder/nodes/interrupt), the run pauses and asks you a question. Answer it and the run continues from where it stopped — it stays the same run. When the run finishes, open the node outputs to see what each step produced. The **Runs** panel keeps a history of past runs so you can revisit any of them. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-1042 · [APP_VIDEO]** **[APP_VIDEO_DESCRIPTION]:** A workflow running in the builder with node statuses updating live and outputs appearing as each step completes. **[APP_VIDEO_DETAILS]:** Screen recording of the live ModuleX app. Click Run, enter one input value, then show nodes transitioning pending → running → completed with their output rendering in sequence, ending on a completed run and the Runs panel. 20–30s, 16:9, dark theme, no audio. ### What if a run is blocked? Some steps use ModuleX-managed models and storage, which are metered in [credits](/concepts/credits-billing). If your organization has run out of its plan allowance and has no wallet balance to cover the overage, a run can be refused before it starts — you will see a billing message instead of a run, and nothing is charged. To fix it, top up or upgrade. Learn how this works in [usage gating & limits](/billing/usage-gating). Using your own provider keys (BYOK) is not metered in credits. Managed models and managed knowledge are. See [credits & the billing model](/concepts/credits-billing) for what consumes credits. ## Next steps Learn the canvas, nodes, and controls in depth. A full walkthrough from prompt to running workflow. Trigger and watch a run directly inside a chat. Do the same thing programmatically with cURL, Python, or JavaScript. --- # Quickstart: your first authenticated API call URL: https://docs.modulex.dev/get-started/quickstart Create an organization, mint a mx_live_ API key, and make your first authenticated ModuleX call with Authorization: Bearer and X-Organization-ID — in cURL, Python, and JavaScript. This guide takes you from a fresh account to your first authenticated request. You will create an [organization](/concepts/organizations-roles), mint a `mx_live_` [API key](/api-reference/authentication), and call the API three ways — with cURL, the [Python SDK](/sdks/python), and the [JavaScript SDK](/sdks/javascript). Every ModuleX request authenticates with two pieces: - **`Authorization: Bearer mx_live_…`** — your API key, sent as a Bearer token. - **`X-Organization-ID: `** — the organization the request acts in, required on every organization-scoped endpoint. The auth header is **`Authorization: Bearer`**, not `X-Authorization`. ModuleX does not recognize an `X-Authorization` header. If you are migrating from older notes or third-party snippets that use it, switch to `Authorization: Bearer`. The backend also accepts the key in an `X-API-KEY` header as an alternative, but the official SDKs always send `Authorization: Bearer`. ## Prerequisites Sign in to the ModuleX app. On first sign-in, a personal organization is provisioned for you automatically, so you always have at least one organization to act in. You can create more from the organization switcher. Every API call is scoped to exactly one organization — see [Organizations, roles & membership](/concepts/organizations-roles). API keys are created by a signed-in user, and they inherit that user's access. The live organization roles are **`owner`** and **`admin`** (the `member` role has been retired). You do not need to be an owner to create a personal API key, but the actions a key can perform are still gated by your role and by [usage limits](/billing/usage-gating). See [Roles & permissions](/security/roles-permissions). For the SDK examples you need Node.js 18+ (for the JavaScript SDK) or Python 3.9+ (for the async Python SDK). For the cURL examples you only need a terminal. ## Step 1 — Create an API key API keys are managed in the app, under your account's API keys settings. Creating a key is the only time the full secret is shown — copy it immediately and store it somewhere safe. ModuleX stores only a salted hash and a short hint (for example `mx_live_2J9vK4xM********`), so a lost key cannot be recovered and must be replaced. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-1030 · [SCREENSHOT]** **[SCREENSHOT_DESCRIPTION]:** The API keys settings page in the ModuleX app, mid-creation, showing the create-key dialog and a freshly generated key that is masked except for its hint. **[SCREENSHOT_DETAILS]:** Capture the account/API-keys settings screen. Show the "Create API key" form (name field, optional organization scope, optional expiry, rate-limit field) and the success state where the full `mx_live_…` key is displayed once with a copy button. Redact the real secret. Light mode, 16:9, annotate the "copy now — shown once" callout and the masked-key hint (`mx_live_2J9vK4xM********`). When you create a key you can set the following fields. A human-readable label for the key (1–255 characters), for example `CI/CD` or `Production worker`. Shown in the key list to help you identify it later. Optional. Scopes the key to a single organization that you are a member of. When set, the key can only be used with a matching `X-Organization-ID` header — any other organization returns `403`. When omitted (`null`), the key works across all organizations you belong to, and you choose the organization per request via the header. Optional expiry timestamp. After this time the key is rejected with `401`. Omit (`null`) for a key that never expires. Optional per-key request limit (1–1000). Defaults to `60` requests per minute. A separate per-user limit of `300` requests per minute applies across all of your keys combined. The create response is the **only** place the full key appears. The key's unique identifier (UUID). Use it to fetch or revoke the key later. The label you provided. The full secret, for example `mx_live_2J9vK4xM8nP3wQ7tR5sL1yB6cF0dG2h4vN8pX5mK9rT3wY7u`. Returned **once**, only on creation. Every other endpoint returns the masked form instead. The first 8 characters of the random part, used to recognize the key without exposing it. The display form, for example `mx_live_2J9vK4xM********`. The organization the key is scoped to, or `null` for an unscoped key. The per-key limit applied to this key. The expiry timestamp, or `null` if the key never expires. Whether the key is currently usable. Becomes `false` after revocation. When the key was created (ISO 8601). A `mx_live_` key is `mx_live_` followed by roughly 43 random Base62 characters. Treat it like a password: never commit it to source control or paste it into client-side code. Read it from an environment variable or a secrets manager instead. ## Step 2 — Find your organization ID Every organization-scoped request needs the `X-Organization-ID` header. You can read your organizations from `GET /auth/me/organizations`, which lists each organization's `id`, `slug`, `name`, and your `role` in it. The `id` value is what goes in the header. `GET /auth/me/organizations` is **not** organization-scoped, so it does not itself require the `X-Organization-ID` header — it returns every organization you belong to. ```bash cURL curl https://api.modulex.dev/auth/me/organizations \ -H "Authorization: Bearer mx_live_2J9vK4xM8nP3wQ7tR5sL1yB6cF0dG2h4vN8pX5mK9rT3wY7u" ``` ```python Python import asyncio from modulex import Modulex async def main(): async with Modulex(api_key="mx_live_2J9vK4xM8nP3wQ7tR5sL1yB6cF0dG2h4vN8pX5mK9rT3wY7u") as client: result = await client.auth.organizations() for org in result["organizations"]: print(org["id"], org["role"], org["name"]) asyncio.run(main()) ``` ```javascript JavaScript import { Modulex } from 'modulex-js'; const client = new Modulex({ apiKey: 'mx_live_2J9vK4xM8nP3wQ7tR5sL1yB6cF0dG2h4vN8pX5mK9rT3wY7u', }); const { organizations } = await client.auth.organizations(); for (const org of organizations) { console.log(org.id, org.role, org.name); } ``` A successful response looks like this: ```json { "success": true, "user_id": "550e8400-e29b-41d4-a716-446655440000", "organizations": [ { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "slug": "acme", "name": "ACME", "domain": "acme.modulex.dev", "role": "owner", "joined_at": "2026-01-01T10:00:00", "is_default": false } ], "total": 1 } ``` Copy the `id` of the organization you want to work in. You will pass it as `X-Organization-ID` on every organization-scoped call. ## Step 3 — Make your first authenticated call With your key and organization ID in hand, call an organization-scoped endpoint. `GET /organizations/llms` returns the language-model catalog for your organization, including which models have credentials connected — a good first call that exercises both the `Authorization` and `X-Organization-ID` headers. In the SDKs you set the organization once on the client (it is sent as `X-Organization-ID` automatically) and override it per call when you need a different organization. ```bash cURL curl https://api.modulex.dev/organizations/llms \ -H "Authorization: Bearer mx_live_2J9vK4xM8nP3wQ7tR5sL1yB6cF0dG2h4vN8pX5mK9rT3wY7u" \ -H "X-Organization-ID: a1b2c3d4-e5f6-7890-abcd-ef1234567890" ``` ```python Python import asyncio from modulex import Modulex async def main(): async with Modulex( api_key="mx_live_2J9vK4xM8nP3wQ7tR5sL1yB6cF0dG2h4vN8pX5mK9rT3wY7u", organization_id="a1b2c3d4-e5f6-7890-abcd-ef1234567890", ) as client: catalog = await client.organizations.llms() print(catalog["active_llm_total"], "models with credentials") asyncio.run(main()) ``` ```javascript JavaScript import { Modulex } from 'modulex-js'; const client = new Modulex({ apiKey: 'mx_live_2J9vK4xM8nP3wQ7tR5sL1yB6cF0dG2h4vN8pX5mK9rT3wY7u', organizationId: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', }); const catalog = await client.organizations.llms(); console.log(catalog.active_llm_total, 'models with credentials'); ``` A `200` response confirms your credentials work. If you get an error instead, jump to [Troubleshooting](#troubleshooting) below. `GET /organizations/llms` requires the `admin` or `owner` role. If your role is not admin or owner you will get a `403` — use `GET /auth/me` (which needs no role and no organization header) to confirm authentication on its own first. ## Install the SDKs The SDK examples above assume you have the relevant package installed. ```bash Python pip install modulex-python ``` ```bash JavaScript (npm) npm install modulex-js ``` ```bash JavaScript (pnpm) pnpm add modulex-js ``` Both SDKs are thin, typed clients over the same REST API — every method maps to one HTTP call. The base URL defaults to `https://api.modulex.dev` and has **no version prefix**: do not append `/api` or `/v1`. See [Base URLs, environments & versioning](/get-started/environments). ### Client configuration The two SDKs take the same options under language-idiomatic names (camelCase in JavaScript, snake_case in Python). | Option (JS / Python) | Type | Required | Default | Notes | | --- | --- | --- | --- | --- | | `apiKey` / `api_key` | string | yes | — | Your `mx_live_` key, sent as `Authorization: Bearer`. Missing → the client throws on construction. | | `organizationId` / `organization_id` | string | no | none | Default `X-Organization-ID` for every request; overridable per call. | | `baseUrl` / `base_url` | string | no | `https://api.modulex.dev` | API root, no version prefix. | | `timeout` | number (ms / s) | no | `30000` ms (JS) / `30.0` s (Python) | Per-request timeout. | | `maxRetries` / `max_retries` | number | no | `3` | Retries for transient errors (`429`, `500`, `502`, `503`) and network errors. | **Environment-variable fallback differs by SDK.** The **Python** SDK reads `MODULEX_API_KEY`, `MODULEX_ORGANIZATION_ID`, and `MODULEX_BASE_URL` when the matching argument is omitted, so you can construct `Modulex()` with no arguments if those are set. The **JavaScript** SDK has **no** environment-variable fallback — you must pass `apiKey` explicitly; reading `process.env.MODULEX_API_KEY` yourself is just your own code, not SDK behavior. Full details: [JavaScript SDK](/sdks/javascript) and [Python SDK](/sdks/python). Reading the key from the environment looks like this: ```python Python import os import asyncio from modulex import Modulex async def main(): # Picks up MODULEX_API_KEY and MODULEX_ORGANIZATION_ID automatically. async with Modulex() as client: me = await client.auth.me() print(me["email"]) # Or pass them explicitly: async with Modulex( api_key=os.environ["MODULEX_API_KEY"], organization_id=os.environ["MODULEX_ORGANIZATION_ID"], ) as client: me = await client.auth.me() print(me["email"]) asyncio.run(main()) ``` ```javascript JavaScript import { Modulex } from 'modulex-js'; // The JS SDK has no env fallback — read process.env yourself and pass it in. const client = new Modulex({ apiKey: process.env.MODULEX_API_KEY, organizationId: process.env.MODULEX_ORGANIZATION_ID, }); const me = await client.auth.me(); console.log(me.email); ``` Response fields stay **snake_case** on the wire (`active_llm_total`, `created_at`, `organization_ids`). The JavaScript SDK converts your request keys from camelCase to snake_case automatically, but it does **not** convert responses back — read response fields in snake_case in both SDKs. ## How authentication is evaluated Understanding the order the backend checks credentials helps you read errors correctly. The backend reads `Authorization: Bearer ` first. If the token starts with `mx_live_` it is treated as an API key; otherwise it is treated as a Clerk JWT (the app's user-login path). If no Bearer credential resolves, it falls back to the `X-API-KEY` header (which must also start with `mx_live_`). With neither present, the request is rejected with `401`. The key is looked up by its hash and must be active and unexpired. Each request consumes from the key's per-minute limit (default `60`) and your per-user limit (`300` across all keys). Exceeding either returns `429` with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers. For organization-scoped endpoints, the backend reads `X-Organization-ID`. A missing header returns `400`. If your key is scoped to a specific organization, the header must match that organization or you get `403`. If you are not a member of the organization in the header, you get `403`. For the full authentication reference — including the Clerk JWT path used inside the app — see [Authentication](/api-reference/authentication) and the [JWT vs API key auth model](/security/authentication). ## Troubleshooting No valid credential was found, or the key is wrong, revoked, or expired. Confirm you are sending `Authorization: Bearer mx_live_…` (not `X-Authorization`), that the key is copied in full, and that it has not been revoked or passed its `expires_at`. Test the key in isolation with `GET /auth/me`, which needs no organization header. You called an organization-scoped endpoint without the `X-Organization-ID` header. Add it with an organization ID from `GET /auth/me/organizations`. In the SDKs, set `organizationId` / `organization_id` on the client or pass it per call. Three causes share this status: the organization in `X-Organization-ID` is not one you belong to; your key is scoped to a different organization than the header; or the endpoint requires the `owner` or `admin` role and yours is lower. Verify your membership and role with `GET /auth/me/organizations`. You exceeded the per-key (default `60`/min) or per-user (`300`/min) limit. Read the `Retry-After` header and back off; both SDKs retry `429` automatically up to `maxRetries` / `max_retries`, honoring `Retry-After`. See [Rate limiting](/api-reference/rate-limiting). On run, Composer, Assistant, and managed-knowledge surfaces, a usage denial returns a flat envelope `{code, layer, key, current, limit, reason}` (not wrapped in `detail`). The `layer` maps to the status: `credit`/`wallet` → `402`, `quota` → `403`, `rate` → `429`. This is distinct from the plain `{"detail": "…"}` shape on standard CRUD routes. See [Errors & status codes](/api-reference/errors) and [Usage gating & limits](/billing/usage-gating). ## Next steps Run a workflow programmatically and stream its result in cURL, Python, and JavaScript. The complete header, token, and error reference for authenticating every request. Base URLs, the request lifecycle, and how every operation is shown three ways. Install and configure the JavaScript and Python SDKs once, then use them everywhere. --- # Why ModuleX URL: https://docs.modulex.dev/get-started/why-modulex What makes ModuleX different: build with natural language or a visual canvas, collaborate in realtime, bring your own model keys, ground answers in your own knowledge, and connect 175 integrations. ModuleX is an AI workflow orchestration platform. You describe a process in plain English or lay it out on a visual canvas, connect the tools you already use, and run it from chat, the API, or an SDK. This page covers what sets ModuleX apart. For a step-by-step picture of how the pieces fit together, read [How ModuleX works](/concepts/overview). **🎬 MEDIA PLACEHOLDER · MX-MEDIA-1010 · [IMAGE]** **[IMAGE_DESCRIPTION]:** A positioning diagram that places ModuleX between hand-rolled LLM glue code and rigid no-code automation tools. **[IMAGE_DETAILS]:** Three-column layout. Left column "Write it all yourself" (LLM APIs, vendor SDKs, automation glue, your own orchestration); right column "Rigid no-code tools" (fixed templates, one model, no code escape hatch); center column "ModuleX" showing the five differentiators as labeled chips (two ways to build, realtime collaboration, your own keys, your own knowledge, 175 integrations). 1600x900, light theme, no photography, ModuleX brand greys (#1A1A1A on #E8E8E8). Center column visually emphasized. ## What makes ModuleX different Describe the workflow to the AI Composer in plain English, or build it node by node on the visual canvas. Both produce the same editable workflow graph. Your team works on the same canvas at once, with live cursors, presence, and node locks so two people never overwrite each other. Connect your own model and provider accounts. Providers bill you directly, with no ModuleX markup, or use ModuleX-managed models and pay in credits. Ground answers in your company's documents. ModuleX retrieves the relevant context automatically inside chats and workflows. Call the services you already use from any workflow or agent. The integration registry is open source, so you can read and extend it. Official JavaScript and Python SDKs put every workflow, run, and stream behind a typed client you can embed in your own product. ## The problem ModuleX solves If you have built with AI before, you have probably stitched together model APIs, vendor SDKs, and automation tools by hand, then written your own glue to make them run in order, retry on failure, and stream results. No-code automation tools remove the glue, but they tend to lock you into fixed templates, a single model, and no escape hatch when you need real code. ModuleX sits between the two. You get a visual canvas and natural-language building for the parts that should be fast, plus an API, SDKs, and a code-capable node set for the parts that need control. ## Build the way that suits the task You do not have to pick one building style for everything. Tell the [AI Composer](/concepts/ai-composer) what you want in plain English. It lays out the steps, tools, and logic as an editable workflow graph. Open the [workflow builder](/workflow-builder/overview) and adjust any node directly. Composer edits and manual edits change the same graph. Run the finished workflow from [chat](/platform/chat/workflow-run), the [REST API](/workflow-builder/execution/api-endpoint), or an [SDK](/sdks/overview), and stream the result live. There is also an [Assistant](/concepts/assistant): an agentic chat that uses your connected tools step by step to get work done, with no workflow to set up first. ## Work together, live Workflows are rarely a solo effort. In ModuleX, several people can edit the same canvas at the same time. See who else is on the canvas and where their cursor is, in realtime. When a teammate is editing a node, it locks so your changes never collide. Edits are versioned, so the team can see what changed and roll back when needed. For how the canvas stays in sync across collaborators, see the [realtime and collaboration model](/concepts/realtime-model). **🎬 MEDIA PLACEHOLDER · MX-MEDIA-1011 · [APP_VIDEO]** **[APP_VIDEO_DESCRIPTION]:** Two people co-editing the same workflow canvas in realtime. **[APP_VIDEO_DETAILS]:** Screen recording of the ModuleX workflow builder split to show two browser sessions side by side. One user drags in and connects a node while the other moves a different node; show live cursors, presence avatars, and a node lock badge appearing on the node being edited. 20 to 30 seconds, 16:9, light theme, no narration, captions only. Capture in the live app. ## Your models, your keys ModuleX works with every major AI provider, including Anthropic, OpenAI, Google, and xAI. You choose how usage is billed. Connect your own provider accounts. The provider bills you directly with no ModuleX markup, so you keep full cost transparency. Skip the setup and use ModuleX-provisioned providers. Managed usage is metered in [credits](/billing/credits). ## Answers grounded in your knowledge Connect your company's documents as knowledge bases, and ModuleX retrieves the relevant context for you, inside both chats and workflows. You can use ModuleX-managed vector storage, billed in credits, or bring your own vector store. Let ModuleX host the vector storage and retrieval. Ingest and retrieval are billed in credits. Point ModuleX at your own Qdrant, Pinecone, MongoDB Atlas, or Weaviate. Read more in [Knowledge & RAG](/concepts/knowledge-rag). ## Connect the tools you already use ModuleX ships with 175 integrations you can call from any workflow or agent. The integration registry is open source, so you can read how each connector works and add your own. See all 175 integrations grouped by category, from source control and messaging to CRMs and databases. ## Pay only for managed usage ModuleX meters managed usage in credits. When you bring your own keys, that usage is not credited, so your provider bill stays your provider bill. Running a workflow or an agent turn, ingesting and retrieving from a ModuleX-managed knowledge base, and calling ModuleX-managed models all consume credits. BYOK model usage is billed by your provider, not by ModuleX. See [Credits & metering](/billing/credits) for the full breakdown. Each plan includes a monthly credit allowance. Paid plans can spend beyond it from a prepaid wallet only if you turn on overage. If you bring your own keys, model usage runs on your provider account at provider rates. See [Billing & credits overview](/billing/overview). ## Built for teams and enterprises As your team grows, ModuleX adds the controls larger organizations need: organization roles, access control, and audit-ready usage tracking. Single sign-on, role-based permissions, and the security posture larger teams require. ## Where to go next The end-to-end mental model, from a prompt or canvas to a running, observable workflow. Create an account, get an API key, and make your first authenticated call. Build a workflow in the canvas or with the Composer, then run it from the app. The handful of ideas you need: workflows, runs, nodes, Composer, Assistant, knowledge, and credits. --- # Build a RAG knowledge base URL: https://docs.modulex.dev/guides/build-a-knowledge-base Create a knowledge base (managed modulexdb or BYOK), ingest documents through the parse-chunk-embed pipeline, and query it with vector, hybrid, or RAG-context search over the REST API and the JavaScript and Python SDKs. This guide takes you end to end: create a [knowledge base](/concepts/knowledge-rag), ingest your documents through the parse-chunk-embed pipeline, and query the result with vector, hybrid, or RAG-context search — from the REST API and both SDKs. It documents every request field, response shape, error, and credit cost so you can wire retrieval into a workflow or an agent with no surprises. For the conceptual model behind retrieval (knowledge bases, managed vs BYOK, ingest and retrieval), read [Knowledge & RAG](/concepts/knowledge-rag). To manage knowledge bases in the app instead of by API, see the [Knowledge overview](/platform/knowledge/overview). A **knowledge base** is a `knowledge_bases` row with its own embedding and chunking configuration — the unit that retrieval searches over. A **document** is one uploaded file; ingest splits it into **chunks**, each carrying a vector **embedding**. The vocabulary on this page matches the [glossary](/reference/glossary). ## Before you start Every endpoint on this page lives under the `/knowledge-bases` router and is **org-scoped and admin-gated**. You need: - A ModuleX API key (`mx_live_…`). Get one from the app or see [Authentication](/api-reference/authentication). - The **owner** or **admin** role in the organization. Every knowledge route depends on `organization_admin_required`, so a non-admin caller is rejected with `403`. The legacy `member` role is retired — see [Roles & permissions](/security/roles-permissions). - The base URL `https://api.modulex.dev`. There is **no `/v1` path segment** — paths are exactly `/knowledge-bases/...`. Authenticate every request with two headers (the backend also accepts `X-API-KEY` as an alternative to the bearer token): ```http Authorization: Bearer mx_live_xxx X-Organization-ID: org_123 ``` The header is `X-Organization-ID` with a capital `ID`. It is **required** on every knowledge endpoint except the single unauthenticated info endpoint [`GET /knowledge-bases/info/supported-file-types`](#supported-file-types). A missing or wrong org context resolves to a `403` (or `404` for cross-org resources). All request bodies and responses are snake_case JSON. ## Step 1 — choose managed or BYOK The single decision that drives cost is whether the knowledge base is **managed** or **BYOK** (bring your own key). It is set by `embedding_config.integration_name`: Set `integration_name` to `modulexai`. ModuleX provisions the embedding provider and stores vectors in its managed store. **Ingest and retrieval are billed in credits.** See [modulexdb (managed)](/integrations/knowledge-providers/modulexdb). Point `embedding_config` at one of your own LLM-provider credentials (for example your own OpenAI or Cohere key). **Ingest and retrieval are uncosted** by ModuleX — you pay the provider directly. See [Knowledge providers](/integrations/knowledge-providers/overview). Only **managed** knowledge bases (those whose `embedding_config.integration_name` is `modulexai`) are metered. BYOK retrieval and ingest consume **no** ModuleX credits — they are analytics-only. The credit columns throughout this page apply to managed knowledge bases only. If you omit `embedding_config.credential_id` entirely, ModuleX **auto-discovers** an org credential that exposes an embedding-capable model (it looks for an `llm_provider` credential whose integration has a model flagged `is_embedding`). If none exists, create fails — see the errors below. **Embedding-config key drift.** Two key conventions coexist in the backend and both are read defensively. The model-level default uses `provider` + `model_id`; the service default uses `integration_name` + `provider_id` + `model_id` + `credential_id`. When you write `embedding_config`, the embed code accepts either `provider` or `provider_id`, either `model` or `model_id`, and either `provider_credential_id` or `credential_id`. Pick one convention and stay consistent. Managed is detected specifically on `integration_name == "modulexai"`. ## Step 2 — create the knowledge base `POST /knowledge-bases` creates the base and returns `201 Created`. There is **no credit gate** on create. On success ModuleX also auto-creates a linked internal credential (the "native KB = credential" link) so a [knowledge node](/workflow-builder/nodes/knowledge) can reference the base, and sets `status` to `active`. ### Request fields Display name, 1–255 characters. Optional free-text description. Per-knowledge-base embedding settings. Omit it to auto-discover an embedding credential and use the defaults below. Set to `modulexai` for a **managed** knowledge base (billed in credits). Any other value, or a `credential_id` pointing at your own provider, makes it **BYOK**. Embedding provider. Accepted values are `openai`, `cohere`, `azure`, and `huggingface`, but only `openai` and `cohere` have a working embed implementation — anything else fails ingest with an embedding error. Read as `provider` or `provider_id`. Embedding model. Read as `model` or `model_id`. Vector dimension, 64–4096. The chunk column is Postgres `VECTOR(1536)`; keep ingest-time and query-time dimensions identical. The org credential to embed with. Read as `credential_id` or `provider_credential_id`. If omitted, ModuleX auto-discovers an embedding-capable credential. Per-knowledge-base chunking settings used by the ingest pipeline. One of `recursive`, `token`, or `simple`. `recursive` splits on `separators`; `token` uses a `cl100k_base` token window; `simple` is a fixed character window. Target chunk size, 100–4000. Overlap between adjacent chunks, 0–500. Must be **at most 50%** of `chunk_size`, or validation fails. Split points for `recursive`. Default `["\n\n", "\n", " ", ""]`. ### Create a knowledge base ```bash cURL curl -X POST https://api.modulex.dev/knowledge-bases \ -H "Authorization: Bearer mx_live_xxx" \ -H "X-Organization-ID: org_123" \ -H "Content-Type: application/json" \ -d '{ "name": "Product Docs", "description": "Technical documentation", "embedding_config": { "integration_name": "modulexai", "model_id": "text-embedding-3-small", "dimension": 1536 }, "chunking_config": { "strategy": "recursive", "chunk_size": 1000, "chunk_overlap": 200 } }' ``` ```python Python import asyncio from modulex import AsyncModulex client = AsyncModulex( api_key="mx_live_xxx", organization_id="org_123", ) async def main(): kb = await client.knowledge.create( name="Product Docs", description="Technical documentation", embedding_config={ "integration_name": "modulexai", "model_id": "text-embedding-3-small", "dimension": 1536, }, chunking_config={"strategy": "recursive", "chunk_size": 1000, "chunk_overlap": 200}, ) print(kb["id"]) asyncio.run(main()) ``` ```javascript JavaScript import { Modulex } from "modulex-js"; const client = new Modulex({ apiKey: "mx_live_xxx", organizationId: "org_123", }); const kb = await client.knowledge.create({ name: "Product Docs", description: "Technical documentation", embeddingConfig: { integration_name: "modulexai", model_id: "text-embedding-3-small", dimension: 1536 }, chunkingConfig: { strategy: "recursive", chunk_size: 1000, chunk_overlap: 200 }, }); console.log(kb.id); ``` The JS SDK takes camelCase method arguments but returns snake_case fields (so the response `id`/`created_at` stay snake_case); the Python SDK is snake_case both ways. Nested config keys like `integration_name` and `model_id` are already snake_case on both sides. ### Response (`201 Created`) The knowledge-base UUID. Owning organization. Creator user id. The auto-created internal credential that links this base to workflow nodes. The display name. The description, or `null`. The resolved embedding settings. The resolved chunking settings. One of `active`, `processing`, `error`, `archived`. New bases are `active`. Document count (rolled up). Chunk count across documents. Token count across documents. Creation timestamp. Last-update timestamp. ```json Example response { "id": "7c1e...", "organization_id": "org_123", "created_by_user_id": "user_456", "credential_id": "a1b2...", "name": "Product Docs", "description": "Technical documentation", "embedding_config": { "integration_name": "modulexai", "provider_id": "openai", "model_id": "text-embedding-3-small", "credential_id": "a1b2...", "dimension": 1536 }, "chunking_config": { "strategy": "recursive", "chunk_size": 1000, "chunk_overlap": 200, "separators": ["\n\n", "\n", " ", ""] }, "document_count": 0, "total_chunks": 0, "total_tokens": 0, "status": "active", "created_at": "2026-06-20T10:00:00", "updated_at": "2026-06-20T10:00:00" } ``` ### Create errors | Status | Condition | | --- | --- | | `400` | No embedding credential could be found or auto-discovered; or config validation failed (for example `chunk_overlap` over 50% of `chunk_size`). | | `403` | Caller is not owner/admin, **or** the plan's knowledge-base quota is exhausted (`KnowledgeBaseQuotaExceededError`). | | `422` | Pydantic validation (for example `name` outside 1–255 characters). | | `500` | Unexpected service error. | The knowledge-base count quota is a **plan entitlement** (`max_knowledge_bases`): unlimited for admin and Enterprise, Max 50, Pro 3, Free uses the config default. Quota counts **non-archived** bases — archiving a base frees a slot. Quota and storage denials use the plain `403` `{"detail": ...}` shape, **not** the billing [`DenialEnvelope`](/api-reference/errors). **🎬 MEDIA PLACEHOLDER · MX-MEDIA-4460 · [IMAGE]** **[IMAGE_DESCRIPTION]:** A diagram of the knowledge-base lifecycle: create, ingest, query. **[IMAGE_DETAILS]:** Left-to-right flow with three labeled stages. Stage 1 "Create" box (POST /knowledge-bases, sets embedding_config + chunking_config, status active). Stage 2 "Ingest" box showing the worker pipeline parse to chunk to embed to pgvector, with a document status lane pending to processing to completed/failed. Stage 3 "Query" box listing the four search modes (vector, multi-KB, hybrid, retrieve-context) returning ranked chunks. Mark the managed credit points: 1 credit on ingest, 1 credit per retrieval, on the relevant arrows. Light theme, 16:9, clean flowchart, no UI chrome. ## Step 3 — ingest documents Upload a file with `POST /knowledge-bases/{knowledge_base_id}/documents` as `multipart/form-data`. The upload returns `201` with the document in `pending` status; a background worker then runs the parse-chunk-embed pipeline asynchronously. You poll the [document status](#poll-document-status) until it reaches `completed` or `failed`. ### Form parts The document to ingest. Supported types: `pdf`, `docx`, `doc`, `txt`, `md`, `html`, `csv`, `json`, `xlsx`, `pptx`. The type is resolved from the extension first, then the MIME type. Optional JSON **string** of arbitrary metadata. Invalid JSON returns `400` with `{"detail": "Invalid metadata JSON"}`. **Per-file size cap is a plan entitlement, not a fixed 50 MB.** The static info endpoint advertises 50 MB, but the actually enforced per-file cap is your plan's storage entitlement: Free 10 MB, Pro 50 MB, Max 100 MB, Enterprise unlimited. Plan-storage and per-base document-count breaches return `403` `{"detail": ...}` (not the billing envelope). The hard 50 MB constant is only a fallback when the plan does not meter file size. A base also accepts at most 500 documents. **Deduplication.** ModuleX computes a SHA-256 of the file. Uploading the same content to the same base again returns `400` (`Duplicate file...`). To re-ingest changed content, delete the existing document first. ### Upload a document ```bash cURL curl -X POST https://api.modulex.dev/knowledge-bases/7c1e.../documents \ -H "Authorization: Bearer mx_live_xxx" \ -H "X-Organization-ID: org_123" \ -F "file=@manual.pdf" \ -F 'metadata={"tags":["product"]}' ``` ```python Python import asyncio from modulex import AsyncModulex client = AsyncModulex(api_key="mx_live_xxx", organization_id="org_123") async def main(): with open("manual.pdf", "rb") as f: doc = await client.knowledge.upload_document( knowledge_base_id="7c1e...", file=f, metadata={"tags": ["product"]}, ) print(doc["id"], doc["status"]) # -> "pending" asyncio.run(main()) ``` ```javascript JavaScript import { readFileSync } from "node:fs"; import { Modulex } from "modulex-js"; const client = new Modulex({ apiKey: "mx_live_xxx", organizationId: "org_123" }); const doc = await client.knowledge.uploadDocument("7c1e...", { file: new Blob([readFileSync("manual.pdf")]), metadata: { tags: ["product"] }, }); console.log(doc.id, doc.status); // -> "pending" ``` ### Upload response (`201 Created`) The upload endpoint returns the router `DocumentResponse` shape: Document UUID. Parent base. Original filename. Resolved type (for example `pdf`). File size in bytes. Lifecycle status; `pending` immediately after upload. Chunks produced; `0` until ingest completes. Tokens counted; `0` until ingest completes. Failure reason, or `null`. Upload timestamp. ```json Example response { "id": "d1...", "knowledge_base_id": "7c1e...", "filename": "manual.pdf", "file_type": "pdf", "file_size_bytes": 820341, "status": "pending", "chunk_count": 0, "token_count": 0, "error_message": null, "created_at": "2026-06-20T12:00:00" } ``` ### What the worker does The background `ingest_document` task (retried up to 3 times) drives the document from `pending` to a terminal state: Decodes text formats; uses `pypdf` for PDF, `python-docx` for Word, and the `unstructured` library for HTML and unknown types. Splits text per `chunking_config`, recording `token_count`, `start_char`, and `end_char` for each chunk. Generates embeddings with OpenAI or Cohere. A **managed** base routes through the same underlying model used at query time, so ingest-time and search-time vectors match. Writes chunk rows with their vectors, sets the document `completed`, and updates `chunk_count` and `token_count`. On any exception the document is set `failed` with an `error_message` and the task retries. The document status lifecycle is `pending → processing → completed | failed`, and a `failed` document can be returned to `pending` with [retry](#retry-a-failed-document). ### Poll document status `GET /knowledge-bases/{kb_id}/documents/{document_id}/status` returns a status dict whose extra fields vary by state. Poll it until `status` is `completed` or `failed`. ```json Example response (completed) { "document_id": "d1...", "status": "completed", "filename": "manual.pdf", "file_type": "pdf", "message": "Processing completed successfully", "processing_completed_at": "2026-06-20T12:01:30", "chunk_count": 42, "token_count": 18000 } ``` - `pending` — `message: "Waiting to be processed"`. - `processing` — adds `processing_started_at`. - `completed` — adds `processing_completed_at`, `chunk_count`, `token_count`. - `failed` — adds `error` with the failure reason. ### Retry a failed document `POST /knowledge-bases/{kb_id}/documents/{document_id}/retry` re-enqueues ingest. It is valid **only** when the document status is `failed` (otherwise `400`); it resets the document to `pending` and clears the error and timestamps. There is **no credit gate** on retry — the per-chunk embedding cost is keyed on `{doc_id}:embedding:{token_count}`, so an unchanged reprocess does not re-charge. ### Other document operations | Operation | Endpoint | Notes | | --- | --- | --- | | List documents | `GET /knowledge-bases/{kb_id}/documents` | `status`, `limit` (1–500, default 100), `offset` query params. | | Get a document | `GET /knowledge-bases/{kb_id}/documents/{document_id}` | Wrong-org access returns `404`, not `403`. | | List chunks | `GET /knowledge-bases/{kb_id}/documents/{document_id}/chunks` | `limit`/`offset`; returns `{"chunks": [...], "count": N}`. | | Delete a document | `DELETE /knowledge-bases/{kb_id}/documents/{document_id}` | `delete_file` query (default `true`). Returns `204`. | ### Ingest errors | Status | Condition | | --- | --- | | `400` | Empty file, duplicate (same hash in same base), unsupported type, or bad `metadata` JSON. | | `403` | Caller not owner/admin, plan storage cap exceeded, or per-base document cap (500) reached. | | `402` / `429` | **Managed only** — the ingest credit gate denied the upload (see [Costs](#step-5-costs)). Returns the `DenialEnvelope`; no document is created. | | `422` | Missing the `file` part. | | `500` | Unexpected service error. | ## Step 4 — query the knowledge base ModuleX offers four retrieval modes. All reserve **1 retrieval credit on a managed base** (see [Costs](#step-5-costs)) and accept the same auth headers. Pick the mode that fits: `POST /knowledge-bases/{kb_id}/search` — semantic (cosine) similarity over one base. `POST /knowledge-bases/search` — the same query across several bases, merged by score. `POST /knowledge-bases/{kb_id}/hybrid-search` — vector plus PostgreSQL keyword ranking. `POST /knowledge-bases/{kb_id}/retrieve-context` — a single token-budgeted RAG context string. ### Vector search The search text (minimum length 1). Number of results, 1–50. Cosine-similarity floor, 0.0–1.0. Computed as `1 - distance`. Optional filter; supports `document_id` or `document_ids` to scope to specific documents. Return chunk text in each match. Return chunk metadata in each match. ```bash cURL curl -X POST https://api.modulex.dev/knowledge-bases/7c1e.../search \ -H "Authorization: Bearer mx_live_xxx" \ -H "X-Organization-ID: org_123" \ -H "Content-Type: application/json" \ -d '{ "query": "how to install", "top_k": 5, "min_score": 0.2 }' ``` ```python Python import asyncio from modulex import AsyncModulex client = AsyncModulex(api_key="mx_live_xxx", organization_id="org_123") async def main(): result = await client.knowledge.search( knowledge_base_id="7c1e...", query="how to install", top_k=5, min_score=0.2, ) for match in result["matches"]: print(match["score"], match["document_filename"]) asyncio.run(main()) ``` ```javascript JavaScript import { Modulex } from "modulex-js"; const client = new Modulex({ apiKey: "mx_live_xxx", organizationId: "org_123" }); const result = await client.knowledge.search("7c1e...", { query: "how to install", top_k: 5, min_score: 0.2, }); for (const match of result.matches) { console.log(match.score, match.document_filename); } ``` The query you sent. The base searched. The requested result count. Number of matches returned. Ranked matches, highest score first. Chunk UUID. Source document UUID. Source filename. Position of the chunk in its document. Cosine similarity, 0.0–1.0. Chunk text (when `include_content` is true). Chunk metadata (when `include_metadata` is true). ```json Example response { "query": "how to install", "knowledge_base_id": "7c1e...", "top_k": 5, "total_matches": 3, "matches": [ { "chunk_id": "c1...", "document_id": "d1...", "document_filename": "manual.pdf", "chunk_index": 4, "score": 0.83, "content": "To install, run...", "metadata": {} } ] } ``` ### Multi-base search `POST /knowledge-bases/search` runs the query across several bases and merges results by score. It takes `knowledge_base_ids` (array of UUIDs, required), `query` (required), `top_k` (1–50, default 5), and `min_score` (0.0–1.0, default 0.0). Bases your org cannot access are silently skipped. The response is `{ "query", "knowledge_bases_searched", "top_k", "total_matches", "matches" }`. Billing reserves **one** retrieval credit if **any** queried base is managed (regardless of ordering). One known in-code limitation: the per-base query-embedding token cost is recorded for the **last** searched base only. ### Hybrid search `POST /knowledge-bases/{kb_id}/hybrid-search` blends semantic similarity with PostgreSQL full-text ranking. The search text. Number of results, 1–50. Weight of the keyword score, 0.0–1.0. Weight of the semantic score, 0.0–1.0. Combined weighted-score floor, 0.0–1.0. Optional document filter, as in vector search. Each match adds `semantic_score` and `keyword_score` alongside the combined `score`, and the response echoes `search_type: "hybrid"` and the `weights` you used. `min_score` is **not the same scale** across modes. In vector search it is a cosine- similarity floor; in hybrid search it is a floor on the **combined weighted** score. Tune it per mode. ### Retrieve context (for RAG) `POST /knowledge-bases/{kb_id}/retrieve-context` returns a single ready-to-prompt string instead of a match array — ideal for feeding an LLM. The search text. Token budget for the assembled context, 100–10000. Chunks to consider, 1–50. Cosine-similarity floor, 0.0–1.0. It runs the same vector search, then concatenates chunks within the token budget, each prefixed with a `[Source: , Chunk ]` header and joined by a separator. The response is `{ "context": "", "query": "" }`. ```bash cURL curl -X POST https://api.modulex.dev/knowledge-bases/7c1e.../retrieve-context \ -H "Authorization: Bearer mx_live_xxx" \ -H "X-Organization-ID: org_123" \ -H "Content-Type: application/json" \ -d '{ "query": "how to install", "max_tokens": 2000, "top_k": 10, "min_score": 0.3 }' ``` ```python Python import asyncio from modulex import AsyncModulex client = AsyncModulex(api_key="mx_live_xxx", organization_id="org_123") async def main(): result = await client.knowledge.retrieve_context( knowledge_base_id="7c1e...", query="how to install", max_tokens=2000, top_k=10, min_score=0.3, ) print(result["context"]) asyncio.run(main()) ``` ```javascript JavaScript import { Modulex } from "modulex-js"; const client = new Modulex({ apiKey: "mx_live_xxx", organizationId: "org_123" }); const result = await client.knowledge.retrieveContext("7c1e...", { query: "how to install", max_tokens: 2000, top_k: 10, min_score: 0.3, }); console.log(result.context); ``` ### Query from a workflow or agent Inside a workflow, a [knowledge node](/workflow-builder/nodes/knowledge) retrieves from a base by its linked credential and returns `chunks`, `context`, or `both` (set by the node's `output_format`). The Assistant retrieves through its own `search_knowledge` tool. Both reuse the same managed-billing path as the REST endpoints, so the credit costs below apply identically. ### Search errors | Status | Condition | | --- | --- | | `403` | Caller not owner/admin, or the base belongs to another org (`SearchAccessDeniedError`). | | `402` / `429` | **Managed only** — the retrieval credit gate denied the call. Returns the `DenialEnvelope`. | | `422` | Invalid body (for example empty `query`, or `top_k` out of range). | | `500` | Embedding error (for example an unsupported provider) or search error. | ## Step 5 — costs Cost depends entirely on the managed-vs-BYOK choice from [Step 1](#step-1-choose-managed-or-byok). For the full credit model, see [Credits & metering](/billing/credits); for how the gate denies calls, see [Usage gating & limits](/billing/usage-gating). | Operation | Managed (`modulexai`) base | BYOK base | | --- | --- | --- | | Create knowledge base | Free (no credit gate) | Free | | Document ingest (per document) | **1 credit** base, reserved before the file is written, plus the per-chunk embedding token cost recorded by the worker | Uncosted | | Vector / hybrid / retrieve-context (per call) | **1 credit** base, reserved before the embed, plus the query-embedding token cost | Uncosted | | Multi-base search (per call) | **1 credit** if any queried base is managed | Uncosted | | Retry a failed document | No base charge; embedding cost not re-charged for unchanged content | Uncosted | A credit is the managed-usage unit: **100 credits = \$1.00** (1 credit = \$0.01). The gate works **reserve → record → release**: it reserves the credit before doing the work and records it on success, so an exhausted balance is rejected *before* any document is written or any embedding is generated. On a **managed** base, ingest and retrieval flow through the live billing gate. When the balance is exhausted (or rate-limited, or the plan quota is hit), the call is denied with the flat `DenialEnvelope` — `{code, layer, key, current, limit, reason}` — at **402** (credit/wallet), **403** (quota), or **429** (rate). This is **not** the `{"detail": ...}` shape used by CRUD and access errors. A `429` also carries `Retry-After` and `X-RateLimit-*` headers. See [Errors & status codes](/api-reference/errors) for all three error-envelope shapes. **Knowledge-base count quota vs storage cap** are plan **entitlements**, enforced separately from credits and returned as `403` `{"detail": ...}` — not as the billing envelope. Counts and caps are described under [Step 2](#step-2-create-the-knowledge-base) and [Step 3](#step-3-ingest-documents). ## Manage and clean up | Operation | Endpoint | Notes | | --- | --- | --- | | List bases | `GET /knowledge-bases` | `status`, `limit`, `offset` query params. The default unfiltered listing is cached. | | Get one base | `GET /knowledge-bases/{kb_id}` | Includes a `stats` object with a `documents_by_status` breakdown. | | Org-wide stats | `GET /knowledge-bases/stats` | Returns base, document, chunk, token, and total-size counts. | | Update a base | `PUT /knowledge-bases/{kb_id}` | All fields optional; configs are merged then re-validated. | | Archive (soft delete) | `POST /knowledge-bases/{kb_id}/archive` | Sets `status` to `archived` and frees a count-quota slot. | | Delete (hard) | `DELETE /knowledge-bases/{kb_id}` | `delete_files` query (default `true`). Cascades chunks, documents, the base, and its linked credential. Returns `204`. | **Supported file types — no auth.** `GET /knowledge-bases/info/supported-file-types` is the one knowledge endpoint with no authentication. It returns the 10 supported types and the fallback `max_file_size_bytes` (52428800) / `max_file_size_mb` (50.0). Remember that the **enforced** per-file cap is your plan entitlement, not this advertised 50 MB. ## Next steps The conceptual model: knowledge bases, managed vs BYOK, ingest and retrieval. Manage knowledge bases, documents, and processing in the app. Retrieve from a base inside a workflow. Exactly what consumes credits, and the reserve-record-release lifecycle. --- # Build a workflow with AI Composer URL: https://docs.modulex.dev/guides/build-with-composer A guided walkthrough: describe the workflow you want in plain English, refine it by answering the AI Composer's questions, then run it and iterate — no node-by-node building required. This walkthrough takes you from a blank canvas to a running [workflow](/concepts/workflows-and-runs) using the [AI Composer](/concepts/ai-composer) — without dragging a single node yourself. You describe the outcome you want in plain English, the Composer builds it, asks you a few questions along the way, and you run the result and refine it until it does what you need. If you've never met the Composer before, the short version is this: it's an AI agent that reads your request and assembles the workflow for you, edit by edit, right on the [canvas](/workflow-builder/canvas). Write what you want to happen, in your own words. The Composer turns it into a workflow. The Composer pauses to ask when it needs a choice or a connection. You answer; it keeps building. Run the workflow, see the result, and send follow-up requests until it's right. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-4380 · [APP_VIDEO]** **[APP_VIDEO_DESCRIPTION]:** A full Composer build, end to end — from typing a plain-English prompt to running the finished workflow. **[APP_VIDEO_DETAILS]:** Screen-record the workflow builder with the Composer chat tab open. Type a prompt such as "When a new lead is added, summarize it and post a short note to the #sales Slack channel". Show the Composer streaming its build steps, nodes appearing and connecting on the canvas, one inline question being answered, the save action bar, and finally a Run that streams to a completed result. 60 to 90s, 16:9, dark theme, no audio. Highlight the three beats: describe, refine, run. ## Before you start You need only a couple of things in place. Everything else, the Composer handles. The Composer is available to organization **owners and admins**. The previous **member** role has been retired, so any account that can open the Composer can also use it. See [Organizations, roles & membership](/concepts/organizations-roles). The Composer runs on managed AI usage, so each turn it takes consumes [credits](/concepts/credits-billing) — one credit per turn, plus the cost of the AI model it uses. See [Credits & the billing model](/concepts/credits-billing). You don't need any of your integrations connected up front. If a step needs a service you haven't connected yet, the Composer will pause and walk you through connecting it (see [Step 2](#step-2-refine-with-the-composers-questions)). ## Step 1: Describe the goal in plain English Open a workflow in the [builder](/workflow-builder/overview) and switch the right-hand panel to the **chat** tab — that's the Composer. A brand-new workflow opens a fresh conversation, ready for your first request. Then write what you want to happen. Describe the *outcome*, not the steps. You don't need to know what a "node" is or which pieces go where — that's the Composer's job. In the message box, describe the result in your own words. For example: "Summarize new support emails and post a daily digest to Slack." Press send. The Composer plans the workflow, then adds and connects the steps live. New nodes appear on the canvas as it works, and a build-step indicator shows how far along it is. You don't need to refresh anything. Alongside the build, the Composer streams a plain-English summary of what it made and why, so you understand the workflow you ended up with. ### Write a request that gets good results The clearer your request, the closer the first draft lands. A few habits help: Say what kicks the workflow off and what it should produce. "When a form is submitted, send the answers to a Google Sheet" gives the Composer both ends to build between. Concrete details beat vague ones. "Post to the `#support` channel" is better than "post to Slack." You can always change specifics later, but giving them up front saves a round-trip. You don't have to capture everything in one message. Ask for a first version, look at it, then add to it. The Composer keeps working on the same canvas across the whole conversation. If you know you want a particular tool — a spreadsheet, a chat app, a CRM — name it. The Composer looks at the [integrations](/integrations/overview) your organization has connected and picks the right tools for the job. The Composer edits the workflow live, but its changes are held as **pending changes** until you decide what to do with them. Nothing is locked in during this step — you review and confirm later in [Step 3](#step-3-run-and-iterate). ## Step 2: Refine with the Composer's questions The Composer doesn't guess on the decisions that matter. When it needs a choice, a piece of text, or access to a service you haven't connected, it pauses and asks you **inline in the chat**. This back-and-forth is called **human-in-the-loop** — it's how you steer the build without having to touch the canvas yourself. While the Composer is waiting on you, the conversation stays open and nothing else changes until you respond. The Composer offers a short list and you pick one or several — for example, which model provider a step should use. A quick confirmation before it does something, such as approving a sensitive action while it test-builds. A short written answer, like the exact wording of a message or the name of a channel. If a step needs a service you haven't connected, the Composer asks you to connect it — and for services that sign in with OAuth, it picks up on its own as soon as you finish. Most questions are optional. When the Composer can carry on without an answer, it shows a **Skip for now** action — skip it and the Composer makes a reasonable choice and keeps building. ### Two rules that keep questions tidy Only **one question** can be open per conversation at a time, and only the **person who was asked** can answer it. If you try to send a new request while a question is still open, the Composer asks you to answer the open one first. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-4390 · [SCREENSHOT]** **[SCREENSHOT_DESCRIPTION]:** A Composer human-in-the-loop question shown inline in the builder chat. **[SCREENSHOT_DETAILS]:** Capture the Composer chat panel mid-build showing a single-choice question (for example "Which Slack channel should I post to?") with a few selectable options and a visible "Skip for now" button, with partially-built nodes on the canvas behind it. Dark theme, crop tightly to the question card. ### Connecting a service mid-build If a step in your workflow needs a service your organization hasn't connected yet, the Composer asks for it right when it reaches that step. You connect the service from the question card, and the Composer continues from where it paused. For services that sign in with OAuth — like Google, Slack, or GitHub — connecting opens the provider's sign-in flow in a new window. As soon as you finish signing in, the Composer **resumes on its own**: you don't have to re-send your request or restart the build. Reconnecting an integration whose access has already **expired** can't be refreshed automatically from the app today. If a connection has lapsed, reconnect the integration rather than relying on an in-app refresh. This is a documented gap — see [Known limitations](/reference/known-limitations). ## Step 3: Run and iterate When the Composer finishes a turn, its edits sit on the canvas as **pending changes** with an action bar. This is your checkpoint: look the workflow over before you commit to it. The workflow shows everything the Composer built this turn. Nothing is saved yet — read through the nodes and the Composer's summary and decide whether it matches what you asked for. **Save** keeps the changes and updates the workflow's saved snapshot — this becomes the baseline a later revert returns to. **Revert** discards this turn's edits and restores the workflow to its last saved snapshot. Run the workflow from the builder and watch it execute live. Each step reports as it runs, so you can see what happened and where any output came from. See [Running workflows](/workflow-builder/execution/running). If the result isn't quite right, send the Composer a follow-up request — "filter out spam before summarizing" or "post the digest at 9am instead." It works on the same canvas, and you run again to check the change. Confirms the Composer's edits and refreshes your workflow's saved snapshot. The new baseline for the next revert. Restores the workflow to the snapshot from before this turn and clears the pending changes — a clean undo of the Composer's latest edits. Iterating is the whole point. Build a first version, run it, then keep refining with follow-up requests and running again. You only have to save when you're happy — until then you can revert back to your last good baseline at any time. Each turn the Composer takes is a fresh run on managed AI usage, so it consumes [credits](/concepts/credits-billing). If your organization is out of credits or over a limit, the turn is refused **before any work begins** with a billing response — a `402`, `403`, or `429`. See [Usage gating & limits](/billing/usage-gating) and [Errors & status codes](/api-reference/errors) for what those mean and how to clear them. ## A worked example Here's how the three steps fit together for a common task: turning new leads into a Slack note. "When a new lead is added, write a one-line summary of who they are and post it to the #sales Slack channel." The Composer asks which Slack workspace to use and prompts you to connect Slack if you haven't. You connect it from the question card and the Composer continues. It may also ask you to confirm the channel name — you reply `#sales`. You save the pending changes and run the workflow with a sample lead. The Slack note arrives. The summary is a little long, so you send one more request: "make the summary shorter — one sentence max." The Composer adjusts, you run again, and you're done. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-4400 · [APP_VIDEO]** **[APP_VIDEO_DESCRIPTION]:** The worked "new lead to Slack" example, including one refinement pass. **[APP_VIDEO_DETAILS]:** Screen-record the worked example above: prompt, a Slack connect-and-continue question, save, a run that posts to Slack, then a follow-up "make it shorter" request and a second run. 45 to 60s, 16:9, dark theme, no audio. Highlight that the second request edits the same canvas rather than starting over. ## Tips for getting more out of the Composer The Composer shows each step as it builds. If it's heading the wrong way, stop the current turn from the chat and start over with a clearer request. Stopping a turn doesn't undo edits it already made — use **Revert** for that. When the Composer edits a workflow you're co-editing, its changes are shared with everyone in the room automatically, so your teammates see the new nodes appear just as you do. See [Canvas collaboration](/platform/collaboration/canvas). The Composer and manual editing share the same canvas. Drag a node yourself, then ask the Composer to extend what you built — or the other way around. Deleting nodes **one at a time** is fully supported and syncs to everyone editing with you. A multi-node bulk delete in a single action isn't handled by the realtime collaboration server today, so delete nodes individually when working with others. See [Known limitations](/reference/known-limitations). ## Next steps The concept behind the Composer, and how it compares to the Assistant. The hands-on reference for the Composer panel inside the canvas. Build and run a workflow in the app, with or without the Composer. Trigger a workflow you built and watch it run inside a chat. --- # Connect an integration URL: https://docs.modulex.dev/guides/connect-an-integration Connect a third-party service to ModuleX with OAuth or your own key, then call its tool from a workflow. A step-by-step guide for connecting GitHub, Slack, and 175 other integrations. ModuleX connects to 175 services — GitHub, Slack, Gmail, Notion, Salesforce, and many more — so your workflows can act on them: open an issue, send a message, read a row, create a contact. This guide walks you through it end to end: connect a service once, then call one of its actions from a [tool node](/workflow-builder/nodes/tool) in a workflow. You connect a service **once**, for your whole organization. After that, every workflow and the [Assistant](/assistant/overview) can use it. For the bigger picture of what integrations are, start with the [integrations overview](/integrations/overview). **🎬 MEDIA PLACEHOLDER · MX-MEDIA-4450 · [APP_VIDEO]** **[APP_VIDEO_DESCRIPTION]:** End-to-end walkthrough: connecting GitHub with OAuth, then calling a GitHub action from a tool node in a workflow. **[APP_VIDEO_DETAILS]:** Screen recording in the ModuleX app. Start on the integrations catalog, pick GitHub, connect it via OAuth (show the provider consent screen, then the success toast), then switch to the workflow builder, add a tool node, pick GitHub and an action, and run it. 16:9, light theme, no personal data visible. ~60 seconds. Annotate the Connect button, the consent screen, and the tool node's integration and action pickers. ## Before you start A few things make this go smoothly. Integrations are connected per organization, not per person. Make sure you are working in the right one — the organization switcher is in the app header. Only an **owner** or an **admin** can connect and manage credentials for an organization. If you do not see the connect controls, ask an owner or admin to connect the service. See [roles & permissions](/security/roles-permissions). For most services you sign in with your existing account during the connect step. Some services need an API key instead — you can paste one in. You do not have to connect everything yourself. ModuleX offers managed keys for many services — see [Three ways to connect](#three-ways-to-connect) below — so you can try a tool before bringing your own account. ## How connecting works Connecting a service stores a **credential**: an encrypted record that links your organization to that service. You create it once; ModuleX encrypts it, and uses it automatically every time a workflow or the Assistant calls one of that service's tools. You never paste a secret into a workflow. Behind the scenes there are a few canonical terms worth knowing. An **integration** is the connection to one service, for example GitHub. Each integration exposes many **tools** — the individual actions you can call, such as `github.create_issue` or `github.list_repositories`. ModuleX has 175 integrations and 600+ tools in total. Browse them in the [catalog](/integrations/catalog). A **credential** is the encrypted, organization-scoped record that proves your identity to a service. One integration can hold more than one credential — for example a production GitHub and a test GitHub — and you mark one as the **default**. When a tool runs and you have not picked a specific credential, ModuleX uses the one you marked as **default** for that integration. Set or change the default any time from [managing credentials](/integrations/managing-credentials). ## Three ways to connect A service tells ModuleX which methods it supports. Most integrations offer one or more of these three. The connect screen shows you only the options that apply. Sign in to the service and grant access — no copying secrets. This is the smoothest path and is covered step by step below. The technical details live in [integration authentication](/integrations/authentication). Paste an API key or token you already have from the service. Usage is billed directly by the provider, with no ModuleX markup. For supported services, use a key ModuleX provides — nothing to set up. Managed usage is metered in [credits](/billing/credits). ## Connect a service with OAuth OAuth lets you connect by signing in to the service, instead of handling secrets yourself. The example below uses GitHub; the steps are the same for any service that offers OAuth. In the app, go to your organization settings and open **Browse integrations**. Find the service — search by name or filter by category — and open it. See the [integrations overview](/integrations/overview) for a tour of the catalog. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-4451 · [SCREENSHOT]** **[SCREENSHOT_DESCRIPTION]:** The Browse integrations catalog with GitHub highlighted. **[SCREENSHOT_DETAILS]:** Capture the Browse integrations page in the ModuleX app. Show the search box, the category filter, and a grid of integration cards. Highlight the GitHub card. 16:9, light theme, no personal data visible. Choose **Add credential**, then pick **OAuth** as the method. Give the credential a clear name — for example `Production GitHub` — so you can tell it apart later if you connect more than one account. Turning on **Make default** lets workflows use it automatically. ModuleX sends you to the service's own sign-in and consent screen. Review the access it asks for, then approve. This grant uses OAuth2 with PKCE, an industry-standard secure flow — you stay on the service's site to sign in, and ModuleX never sees your password. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-4452 · [SCREENSHOT]** **[SCREENSHOT_DESCRIPTION]:** A third-party OAuth consent screen (for example GitHub) shown during the connect flow. **[SCREENSHOT_DETAILS]:** Capture the provider consent screen the user sees after choosing OAuth — the authorize/approve button and the requested-permissions list. Use a generic example account, no personal data. 16:9, light theme. After you approve, the service sends you back to ModuleX and the credential is saved automatically. You will see a success message, and the integration now shows as connected. That is it — the service is ready to use across your workflows and the Assistant. The integration appears under your connected credentials with a name, the OAuth method, and a default badge if you set one. You are ready to [use the tool in a workflow](#use-the-tool-in-a-workflow). ### Connect with your own key instead If a service does not offer OAuth, or you would rather bring your own account, choose **API key** (or **bearer token**) on the same connect screen and paste the value from the service. ModuleX checks the key before saving so you catch a typo early. The secret is encrypted at rest and never shown again in full — only a masked preview. For the full list of methods a service can offer and how each is stored and resolved, see [integration authentication](/integrations/authentication). ## Use the tool in a workflow With the service connected, you can call its actions from a workflow. The unit that does this is the [tool node](/workflow-builder/nodes/tool) — it runs one action, with parameters you control, and writes the result into your run so later steps can use it. Open a workflow in the [workflow builder](/workflow-builder/overview) and add a **tool node** to the canvas. In the node's detail panel, pick the connected integration (for example GitHub) and then the action you want (for example `create_issue`). ModuleX shows the credential it will use — the default for that integration unless you choose another. Enter the action's inputs — for `create_issue`, that is the repository, the title, and the body. You can type fixed values, or pull a value from an earlier step using a `{{node_id.field}}` reference. See [variables & references](/workflow-builder/variables-and-references) for the reference syntax. Run the workflow. The tool node calls the service, and the action's result is written into run state under the node's own id, ready for the next step. For how runs stream and how failures surface, see [running workflows](/workflow-builder/execution/running). Want a step where the AI decides which tools to call, rather than a fixed action? Use the [agent node](/workflow-builder/nodes/agent), or let the [Assistant](/assistant/overview) use your connected tools directly in chat — no workflow required. ## Connect a service from code You can also create a credential through the API. Authenticate every request with your API key as `Authorization: Bearer mx_live_…` plus the `X-Organization-ID` header for your organization. The example below creates an API-key credential for a service; the response masks the secret. OAuth connections involve a browser sign-in step, so they are made in the app, not from a single API call. Use the app's connect flow for OAuth, and the API for key-based credentials. ```bash cURL curl -X POST https://api.modulex.dev/credentials \ -H "Authorization: Bearer mx_live_yourkey" \ -H "X-Organization-ID: 8f2b1c4e-1a2b-4c3d-9e8f-7a6b5c4d3e2f" \ -H "Content-Type: application/json" \ -d '{ "integration_name": "tavily", "display_name": "Production Tavily", "make_default": true, "auth_data": { "api_key": "tvly-your-service-key" } }' ``` ```python Python import httpx resp = httpx.post( "https://api.modulex.dev/credentials", headers={ "Authorization": "Bearer mx_live_yourkey", "X-Organization-ID": "8f2b1c4e-1a2b-4c3d-9e8f-7a6b5c4d3e2f", }, json={ "integration_name": "tavily", "display_name": "Production Tavily", "make_default": True, "auth_data": {"api_key": "tvly-your-service-key"}, }, ) resp.raise_for_status() print(resp.json()) ``` ```javascript JavaScript const resp = await fetch("https://api.modulex.dev/credentials", { method: "POST", headers: { Authorization: "Bearer mx_live_yourkey", "X-Organization-ID": "8f2b1c4e-1a2b-4c3d-9e8f-7a6b5c4d3e2f", "Content-Type": "application/json", }, body: JSON.stringify({ integration_name: "tavily", display_name: "Production Tavily", make_default: true, auth_data: { api_key: "tvly-your-service-key" }, }), }); if (!resp.ok) throw new Error(`HTTP ${resp.status}`); console.log(await resp.json()); ``` Creating and managing credentials requires the **owner** or **admin** role. A request from a non-admin member is rejected. ModuleX never reads the organization from the request body — it always comes from the `X-Organization-ID` header. For the full credential API — listing, setting a default, testing, and deleting — see [managing credentials](/integrations/managing-credentials). ## Keep a connection working A connection lasts until the service's access expires or is revoked. For OAuth connections, ModuleX refreshes access automatically in the background as tokens near expiry, so most connections keep working without any action from you. If an OAuth connection stops working — for example because access was revoked on the service — **reconnect it** rather than looking for a manual "refresh" button. Connect the service again with OAuth, and set the new credential as the default. A manual one-click OAuth refresh in the app is a [known limitation](/reference/known-limitations) and should not be relied on. To rotate a key-based credential, connect the service again with the new key, mark it as the default, and delete the old one. See [managing credentials](/integrations/managing-credentials). ## What it costs How a connected service is billed depends on which method you used to connect it. Usage is billed directly by the service provider, with no ModuleX markup. It is not metered in ModuleX credits. Tool calls made through a ModuleX-managed key are metered in [credits](/billing/credits). When your plan's allowance runs out, calls are subject to the usage gate — see [usage gating & limits](/billing/usage-gating). ## Troubleshooting Connecting integrations requires the **owner** or **admin** role for the organization. Switch to the correct organization in the header, or ask an owner or admin to connect the service. See [roles & permissions](/security/roles-permissions). Start the connection again from the catalog. The sign-in step is time-limited, so if you waited too long on the consent screen, the attempt expires and you simply retry. If the service reports that access was denied, approve the requested permissions when prompted. Double-check you copied the full key from the service, with no extra spaces, and that it has the permissions the action needs. ModuleX tests a key before saving, so a rejection here means the service did not accept it. Confirm the credential was saved in the **same organization** you are building the workflow in, and that the integration shows as connected. If you connected more than one account, check which credential is set as the default. Tools that use a ModuleX-managed key consume [credits](/billing/credits). If your plan's allowance is exhausted, the run is stopped by the usage gate. See [usage gating & limits](/billing/usage-gating) and [errors & troubleshooting](/help/errors-troubleshooting). ## Next steps What integrations are, how the catalog is organized, and how to browse 175 services. The full reference for calling one integration action from a workflow. List, set defaults for, test, and delete the credentials your organization has connected. The connect methods a service can offer and how each secret is stored and resolved. --- # Realtime collaboration walkthrough URL: https://docs.modulex.dev/guides/realtime-collaboration A hands-on walkthrough: invite a teammate to your organization, co-edit the same workflow canvas live with presence and node locks, and resolve an edit conflict the way ModuleX does it for you. This walkthrough takes you through building together in ModuleX from end to end: you invite a teammate into your organization, you both open the same workflow, you co-edit it live, and you watch ModuleX handle an edit conflict on its own. By the end you will know what every cursor, avatar, and badge on the canvas means. It is the practical, do-it-yourself companion to two reference pages. For the product tour of what co-editing looks like, see [Canvas collaboration](/platform/collaboration/canvas). For the wire-level detail behind presence, locks, and the conflict model, see [Presence, locks & versioning](/realtime/presence-locks). **🎬 MEDIA PLACEHOLDER · MX-MEDIA-4410 · [APP_VIDEO]** **[APP_VIDEO_DESCRIPTION]:** The full walkthrough end to end: invite a teammate, both open the same workflow, co-edit live, then a conflict resolves itself. **[APP_VIDEO_DETAILS]:** Screen recording with two browser sessions side by side, User A "Ada" and User B "Lin". Show, in order: Ada inviting Lin by email from organization settings; Lin opening the invitation link and accepting; both opening the same workflow; Lin's presence avatar and colored cursor appearing on Ada's canvas; Lin moving a node and it sliding into place on Ada's side; Lin opening a node so a "being edited by Lin" badge shows on Ada's side; Ada trying the same node and seeing it held; then a brief conflict toast on Ada's side that reloads to the latest version. End on both avatars in the top corner. 16:9, light theme, ~60s, annotate the invite email field, the presence avatars, the colored cursor, the editing badge, and the conflict toast. ## What you will do Add a person to your organization by email so they can open the same workflows you can. You and your teammate both open one workflow in the builder and join the same live canvas. Move nodes, add steps, and edit configuration while seeing each other's cursors and node locks in realtime. Watch what happens when two edits collide, and learn how ModuleX reconciles them for you. ## Before you start You invite teammates into an [organization](/concepts/organizations-roles). Only an organization **owner** or **admin** can send invitations. Have at least one workflow saved in the [Workflow Builder](/workflow-builder/overview). Everyone in the organization can open it. You invite by email address. Your teammate signs in (or signs up) with that email to accept. To see live co-editing yourself, open the workflow in two windows signed in as two different people. ## Step 1 — Invite a teammate People collaborate inside an **organization**. Adding a teammate to your organization is what gives them access to your shared workflows. Go to your organization settings and open the members area. You see this only if you are an **owner** or **admin** of the organization. Enter your teammate's email address and send the invitation. New teammates are added with the **admin** role. ModuleX organizations have two roles: **owner** and **admin**. The older `member` role has been retired, so an invited teammate joins as an admin. See [Roles & permissions](/security/roles-permissions) for what each role can do. Your teammate opens the invitation link and accepts it while signed in with the invited email address. If they are not signed in yet, the link prompts them to sign in or sign up first, then completes the acceptance. Acceptance is tied to the invited email address. If your teammate is signed in with a different account, ModuleX tells them which account the invitation was sent to so they can switch to the right one and try again. After accepting, your teammate selects the organization from the organization switcher. Everything they do from then on is scoped to that organization, and your shared workflows become visible to them. A freshly accepted role can take up to a few minutes to take effect on the live collaboration server, which caches membership briefly. If a brand-new teammate cannot edit immediately, have them refresh after a minute. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-4411 · [SCREENSHOT]** **[SCREENSHOT_DESCRIPTION]:** The organization members screen with the invite-by-email field. **[SCREENSHOT_DETAILS]:** Capture the organization settings members area showing the email input used to invite a teammate, the role label (admin), and the list of existing members with their roles. Light theme, redact any real email addresses. Highlight the email field and the send-invitation button. ## Step 2 — Open the same workflow Now both of you open the same workflow so you share one live canvas. Each person opens the workflow from the [Workflow Builder](/workflow-builder/overview) list. Opening a workflow joins you to its live collaboration room automatically — there is no separate "start collaborating" button. When your teammate joins, their **presence avatar** appears in the canvas, and you appear on theirs. Each person is assigned a color that is used consistently for their avatar, their cursor, and any node they are editing. You do not save and reload to see each other's work. Everyone is editing the **same live graph**, so changes appear as they happen. There are no separate copies to merge later. For the full picture of the two realtime planes (live canvas editing versus run streaming), see [Realtime & collaboration model](/concepts/realtime-model). ## Step 3 — Co-edit live With both of you in the same workflow, you can build together. Here is what you will see, and what each signal means. ### Presence and cursors Everyone currently in the workflow shows up as a colored avatar. When someone leaves or closes the tab, their avatar disappears. You see your teammate's cursor move across the canvas in their assigned color, so you always know where their attention is. If a teammate stops interacting for a while, they are shown as **Away** rather than dropping off entirely. They come back to active as soon as they interact again. A person's color is the same for their avatar, cursor, and edit badge, so it is easy to tell at a glance who is doing what. ### Editing nodes safely with locks To stop two people from editing the same node at the same instant, ModuleX uses **node locks**. When you open a node in the [Detail Panel](/workflow-builder/canvas) to change its configuration, ModuleX claims a lock on that node for you. While you hold the lock, your teammates see that node marked as being edited by you, in your color. If they try to open it, ModuleX tells them who is currently editing it instead of letting them collide with you. Your lock releases when you finish editing the node. It also releases automatically if you leave the workflow or disconnect, and it expires on its own after a few minutes of being held, so a node never stays stuck if someone closes their laptop mid-edit. Locks apply to **editing one node's configuration**. Moving nodes around the canvas, adding new nodes, and connecting edges are not locked — those changes simply broadcast to everyone live. The lock exists to protect the inside of a node while one person has it open. ### What broadcasts live Most edits flow to everyone in the room as you make them: A node your teammate adds appears on your canvas moments later, already in place. Dragging a node to a new position updates it for everyone, so the layout stays in sync. New edges between nodes show up for the whole room as they are drawn. Configuration changes save to the shared workflow and reach your teammates once the editor finishes with the node. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-4412 · [APP_VIDEO]** **[APP_VIDEO_DESCRIPTION]:** Two teammates co-editing one canvas, showing cursors, a node move, and a node lock. **[APP_VIDEO_DETAILS]:** Side-by-side recording of Ada and Lin in the same workflow. Show Lin's colored cursor moving on Ada's canvas, Lin adding and moving a node that appears on Ada's side, then Lin opening a node so a colored "being edited by Lin" badge shows on Ada's canvas, and Ada hovering the same node to see it is held. 16:9, light theme, ~25s, annotate the cursor, the moved node, and the editing badge. ## Step 4 — Resolve a conflict Because everyone edits the same live graph, ModuleX keeps every collaborator on the same version. Most of the time this is invisible — edits stream back and forth and stay in order. Occasionally two people make changes so close together, or one person's connection lags far enough behind, that their version of the workflow has drifted from the shared one. That is a **conflict**, and ModuleX resolves it for you. ### What you will see The person whose edits fell behind sees a brief notice that their changes conflicted and the workflow is reloading. ModuleX pulls the current shared version of the workflow and shows it. The other collaborators are unaffected — they keep working. After the reload, both people are looking at the same up-to-date graph, and editing continues normally. You do not resolve a conflict by hand and there is no "accept mine / accept theirs" prompt. ModuleX always reconciles by reloading the behind collaborator to the latest shared version. The reliable way to avoid losing in-progress edits to a single node is to use the node lock: hold the node while you edit it, and your teammates will not edit it underneath you. ModuleX tracks a version number for the live workflow and advances it on every accepted edit. If a collaborator's edits arrive while their version has drifted too far behind the shared one, ModuleX cannot safely apply them in place, so it reloads that person to the current version rather than risk applying a stale change. Locking a node before you edit its configuration keeps your work safe while you have it open. The exact version and conflict mechanics are documented on [Presence, locks & versioning](/realtime/presence-locks). ### Changes from Composer or the API Not every change to a workflow comes from a person dragging nodes. The [AI Composer](/workflow-builder/composer) can rewrite the graph, and so can changes made elsewhere. When that happens while you have the workflow open, those edits **sync into your open canvas live** so you are always looking at the current version — you do not have to reload to pick them up. See [Realtime co-editing & external sync](/workflow-builder/realtime-coediting) for how that external sync works. ## A few things to know No. Live co-editing happens entirely inside the ModuleX web app, which signs you in with your account session. There is no REST endpoint or SDK method for presence, cursors, or locks — they exist only on the live collaboration connection the app makes for you. API keys (`mx_live_*`) are for running workflows programmatically; see [Authentication](/api-reference/authentication). Everyone in your organization who opens the same workflow joins the same live room and can edit together. There is no separate per-workflow collaborator list — organization membership is what grants access. Any node locks you hold are released automatically when you leave the workflow or disconnect, and they also expire on their own after a few minutes. A node never stays locked because someone walked away. The live collaboration server caches organization membership for a short time. A brand-new teammate may need to wait a minute and refresh before their edit access takes effect. See [Account & access — Help](/help/account-access). ## Where to go next The product tour of what co-editing looks and feels like. The wire-level reference: every event, payload, timing, and edge case. Go from a plain-English prompt to a running workflow. --- # Run a workflow (REST + SDK) URL: https://docs.modulex.dev/guides/run-a-workflow Authenticate, trigger a workflow run, and stream its result over SSE with cURL, the Python SDK, and the JavaScript SDK — including the live billing-gate responses. This guide takes you end to end: authenticate a request, start a workflow run with `POST /workflows/run`, and stream its events over Server-Sent Events (SSE). Every operation is shown three ways — cURL, Python, and JavaScript — so you can wire it into a script or a server in minutes. A run is asynchronous. The run endpoint returns immediately with a `run_id` while the workflow executes in the background; you then open an SSE stream on that `run_id` to watch nodes execute and receive the final result. For the full event taxonomy and frame format, see [SSE run streaming](/realtime/sse-streaming). For base URLs, the request lifecycle, and how operations map across surfaces, see the [API overview](/api-reference/overview). **Before you start**, you need three things: an `mx_live_*` API key, your organization ID, and a workflow with an active deployment (or an inline workflow definition). API keys are created in the dashboard at `https://app.modulex.dev`; see the [Quickstart](/get-started/quickstart) and [Authentication](/api-reference/authentication). ## Prerequisites | Requirement | Where it comes from | Notes | | --- | --- | --- | | API key (`mx_live_*`) | Dashboard → API keys | Sent as `Authorization: Bearer mx_live_…`. See [Authentication](/api-reference/authentication). | | Organization ID | Dashboard, or `GET /organizations` | Sent as the `X-Organization-ID` header. Required on every org-scoped route. See [Org context](/security/org-context). | | A runnable workflow | A deployed workflow ID, **or** an inline definition | A saved `workflow_id` must have an active deployment, or the run returns `400`. See [Deploy & versions](/workflow-builder/execution/deploy). | | Role | `owner` or `admin` | All `/workflows` routes require an org owner or admin. The `member` role is retired. See [Roles & permissions](/security/roles-permissions). | **🎬 MEDIA PLACEHOLDER · MX-MEDIA-4440 · [SCREENSHOT]** **[SCREENSHOT_DESCRIPTION]:** The dashboard API keys screen with a freshly created `mx_live_` key and the organization ID visible. **[SCREENSHOT_DETAILS]:** Capture the API keys settings page at `app.modulex.dev`. Show the create-key dialog with the one-time secret masked except the `mx_live_` prefix, and highlight where the organization ID is displayed. Light theme, 16:9, annotate the two values a developer copies into the examples below. ## Step 1 — Authenticate Every request carries two headers. This is the same scheme across REST, the Python SDK, and the JavaScript SDK — there is no `X-Authorization` header. `Bearer mx_live_…` — your API key. The backend also accepts the key in an `X-API-KEY` header, but `Authorization: Bearer` is the documented form. A missing or invalid token returns `401` with a `WWW-Authenticate: Bearer` header. The organization (tenant) the run belongs to. Required on every org-scoped route. If it is missing, the request returns `400` with `{"detail": "X-Organization-ID header is required"}`. `application/json` for the `POST /workflows/run` body. The SDKs take the key and organization ID once, at construction, and attach both headers to every call. The Python SDK additionally falls back to the `MODULEX_API_KEY`, `MODULEX_BASE_URL`, and `MODULEX_ORGANIZATION_ID` environment variables; the JavaScript SDK has **no** environment-variable fallback — you must pass the values to the constructor. See the [JavaScript SDK](/sdks/javascript) and [Python SDK](/sdks/python) pages for the full configuration surface. ```bash cURL # Reused by every request below. The base URL has no /v1 path segment. export MODULEX_API_KEY="mx_live_xxx" export MODULEX_ORG_ID="11111111-1111-1111-1111-111111111111" export MODULEX_BASE_URL="https://api.modulex.dev" # Sanity check: confirm the key + org resolve. curl "$MODULEX_BASE_URL/auth/me" \ -H "Authorization: Bearer $MODULEX_API_KEY" \ -H "X-Organization-ID: $MODULEX_ORG_ID" ``` ```python Python import asyncio from modulex import Modulex client = Modulex( api_key="mx_live_xxx", organization_id="11111111-1111-1111-1111-111111111111", # base_url defaults to https://api.modulex.dev ) async def main(): me = await client.auth.me() print(me["user_id"]) asyncio.run(main()) ``` ```javascript JavaScript import { Modulex } from 'modulex-js'; const client = new Modulex({ apiKey: 'mx_live_xxx', organizationId: '11111111-1111-1111-1111-111111111111', // baseUrl defaults to https://api.modulex.dev }); const me = await client.auth.me(); console.log(me.user_id); ``` ## Step 2 — Start the run Send `POST /workflows/run`. The endpoint admits the run through the billing gate, returns a `200` with run metadata, and executes the workflow in a background task. Pick exactly one execution mode. ### Execution modes Run a saved workflow. The backend loads the schema from the workflow's **active deployment** snapshot. If the workflow has no live deployment, the run returns `400` (`Workflow has no active deployment. Deploy the workflow first…`). Request `input` overrides the deployment default; request `config` merges over the deployment config. Run an inline, ad-hoc [`WorkflowDefinition`](/concepts/workflow-engine) without saving it. Sets `is_ad_hoc: true` on the run record. Use `attribution_workflow_id` to make an ad-hoc run appear in a saved workflow's Runs panel without switching to deployment-load mode. Run a built-in system workflow by name. In this mode, both `input` and `config` are **required** (otherwise `400`). Exposed by the Python SDK and cURL; the JavaScript SDK does not surface this mode. There is no LLM-only run mode. A request that carries only an `llm` config (no `workflow`, `workflow_id`, or `system_workflow`) returns `410 Gone`. Use the [Assistant](/assistant/overview) instead — `POST /assistant/chat`, documented in [Streaming responses](/assistant/streaming). ### Other body fields State input values passed to the workflow's entry node. Keys match your workflow's `state_schema`. Required for `system_workflow` mode. Runtime overrides for this execution. Recognized keys: `thread_id`, `recursion_limit`, and `batch_interval_ms`. Required for `system_workflow` mode. Echoed back in the response. Run events are always consumed by opening the SSE stream in Step 3; this flag does not change that. When `true`, no chat record is created, `chat_id` is `null`, and `thread_id` is a fresh value. When `false`, `thread_id` equals `chat_id` (the same UUID). When `true`, the run and its messages are visible only to the creator. For ad-hoc (inline) runs, the saved workflow to attribute this run to in run history. The Python SDK accepts an `idempotency_key` argument on `executions.run(...)`. It is sent as an `Idempotency-Key` header, but `POST /workflows/run` mints its own `run_id` and does **not** read that header — so it is a no-op for run deduplication. The JavaScript SDK does not send it. See [SDK errors & retries](/sdks/errors-retries). ### Request ```bash cURL # Run a saved, deployed workflow. curl -X POST "$MODULEX_BASE_URL/workflows/run" \ -H "Authorization: Bearer $MODULEX_API_KEY" \ -H "X-Organization-ID: $MODULEX_ORG_ID" \ -H "Content-Type: application/json" \ -d '{ "workflow_id": "550e8400-e29b-41d4-a716-446655440000", "input": { "query": "AI trends" }, "config": { "recursion_limit": 50 } }' ``` ```python Python run = await client.executions.run( workflow_id="550e8400-e29b-41d4-a716-446655440000", input={"query": "AI trends"}, config={"recursion_limit": 50}, ) print(run.status, run.run_id) # "running" 6a7b8c9d-... ``` ```javascript JavaScript const run = await client.executions.run({ workflowId: '550e8400-e29b-41d4-a716-446655440000', input: { query: 'AI trends' }, config: { recursion_limit: 50 }, }); console.log(run.status, run.run_id); // "running" 6a7b8c9d-... ``` The JavaScript SDK takes camelCase parameter names (for example `workflowId`) and converts them to snake_case on the wire — but **responses stay snake_case** (`run.run_id`, `run.thread_id`). The Python SDK is snake_case in both directions. ### Response A `200` is returned immediately while the workflow runs in the background. The status here reflects only the synchronous portion — it is `running`, never a terminal status. Terminal outcomes arrive on the SSE stream (Step 3) or via run history (Step 5). ```json 200 — run started { "status": "running", "run_id": "6a7b8c9d-1e2f-3g4h-5i6j-7k8l9m0n1o2p", "thread_id": "550e8400-e29b-41d4-a716-446655440000", "chat_id": "550e8400-e29b-41d4-a716-446655440000", "ephemeral": false, "stream": true, "workflow_name": "Research", "workflow_version": "1.0", "workflow_source": "database", "elapsed_ms": 142.7, "human_message": { "id": "...", "role": "human", "run_id": "...", "running_status": null }, "ai_message": { "id": "...", "role": "ai", "run_id": "...", "running_status": "running" }, "message": "Workflow execution started in background. Use /workflows/listen/{run_id} to track progress." } ``` Always `running` for a started run. Terminal status is observed via the stream or run history. The per-execution identifier you pass to `GET /workflows/listen/{run_id}`. This is also the durable run identifier reused on resume. It is **not** the run-history primary key — see [Workflows & runs](/concepts/workflows-and-runs) for the three distinct run-id identities. The LangGraph checkpoint thread. Equals `chat_id` when `ephemeral` is `false`. Used by `GET /workflows/state/{thread_id}` and `POST /workflows/resume/{thread_id}`. The chat record for this run, or `null` when `ephemeral` is `true`. Origin of the executed definition: `database`, `request`, or `system:`. The persisted human message, or `null` for ephemeral runs (or if chat creation failed). The persisted AI message stub (`running_status: "running"`), or `null` for ephemeral runs. ### Run errors Missing `input`/`config` for `system_workflow` mode; a `workflow_id` with no active deployment; or no execution mode supplied. Shape: `{"detail": ""}`. The system workflow file does not exist, or the `workflow_id` is not in your organization. Cross-tenant IDs return `404` (not `403`), so they do not leak existence. Shape: `{"detail": "..."}`. An `llm`-only request — LLM mode was removed. Use `POST /assistant/chat`. Billing gate denial. See [Handle the billing gate](#step-4-handle-the-billing-gate) below — these responses are live on this route. Wrapped failure (`{"detail": "Failed to start workflow: …"}`). Because the run body is an untyped object, a malformed inline `workflow` schema also surfaces as `500` rather than `422`. ## Step 3 — Stream the run Open `GET /workflows/listen/{run_id}` to receive run events over SSE. The stream is **data-only**: each frame is `data: \n\n` with **no** `event:` line. The event type is the `type` key inside the JSON. On (re)connect, the buffered run history (1-hour TTL) is replayed in order first, then the live tail — so reconnecting mid-run is replay-safe. An ownership guard runs before the stream opens; an unknown or cross-tenant `run_id` returns `404` `{"detail": "Run not found"}` as a normal HTTP response (not an in-stream frame). The full frame format, reconnection, and heartbeat mechanics live on [SSE run streaming](/realtime/sse-streaming). ```bash cURL # -N disables buffering so frames arrive as they are emitted. curl -N "$MODULEX_BASE_URL/workflows/listen/6a7b8c9d-1e2f-3g4h-5i6j-7k8l9m0n1o2p" \ -H "Authorization: Bearer $MODULEX_API_KEY" \ -H "X-Organization-ID: $MODULEX_ORG_ID" ``` ```python Python # executions.listen() yields the parsed data object of each frame. async for event in client.executions.listen(run.run_id): etype = event.get("type") if etype == "node_update": print("node:", event.get("node")) elif etype == "done": print("done:", event["data"]["message"]) break elif etype == "error": print("error:", event.get("message")) break ``` ```javascript JavaScript // listen() is an async generator yielding each frame's parsed data. for await (const event of client.executions.listen(run.run_id)) { if (event.type === 'node_update') { console.log('node:', event.node); } else if (event.type === 'done') { console.log('done:', event.data.message); break; } else if (event.type === 'error') { console.log('error:', event.message); break; } } ``` ### Event types on this stream Document the **wire** shapes below — they are what a live client receives. The typed event models and generated TypeScript types describe a different, stale shape (for example, a typed `node_update` uses `node_id`/`status`, but the wire frame uses `node`/`output`). First frame. `data: {run_id, thread_id, workflow_name, workflow_version, workflow_type, timestamp}`. Emitted as each node produces output. Flat shape: `{type, node, output}` where `node` is the node **name** and `output` is the serialized state delta. Emitted by some nodes (for example, the knowledge node) before they run: `{type, node, name, timestamp, metadata}`. The workflow hit an interrupt (HITL) node and is awaiting input. The stream stays **open**. `data: {thread_id, message, data, resume_schema?}`. Resume with `POST /workflows/resume/{thread_id}` — see [Step 5](#step-5-optional-resume-an-interrupt-and-read-history) and [Human-in-the-loop resume](/realtime/hitl). Published after a resume; the run continues under the **same** `run_id`. `data: {run_id, thread_id, resume_value, timestamp}`. A `{"type": "heartbeat"}` keepalive emitted after 15 seconds of silence so a long interrupt pause does not idle-close the connection. Treat it as a no-op. Terminal. `data: {message: "Workflow completed successfully"}`. Closes the stream. Terminal. Flat shape: `{type, message}` (for example, `Execution failed: …`). Closes the stream. Terminal. `data: {run_id, reason, cancelled_at}` (or `data: null` if the cancel blob already expired). Closes the stream. The durable status string differs from the live event. The SSE `done` event corresponds to a durable run status of `succeeded` (not `completed` or `done`) when you read it back from run history in Step 5. ### Raw frame trace A run that hits an interrupt, is resumed, then completes looks like this on the wire (no `event:` lines): ```text Wire frames data: {"type":"metadata","data":{"run_id":"6a7b...","thread_id":"550e...","workflow_name":"Research","workflow_version":"1.0","workflow_type":"workflow","timestamp":"2026-06-21T10:00:00.000000+00:00"}} data: {"type":"node_update","node":"plan","output":{"messages":[{"type":"ai","content":"..."}]}} data: {"type":"heartbeat"} data: {"type":"interrupt","data":{"thread_id":"550e...","message":"Approve this plan?","data":{},"resume_schema":{"type":"object","properties":{"approved":{"type":"boolean"}}}}} data: {"type":"resumed","data":{"run_id":"6a7b...","thread_id":"550e...","resume_value":{"approved":true},"timestamp":"2026-06-21T10:00:30.000000+00:00"}} data: {"type":"node_update","node":"summarize","output":{"summary":"..."}} data: {"type":"done","data":{"message":"Workflow completed successfully"}} ``` ## Step 4 — Handle the billing gate Managed runs pass through a billing admission gate **before** any database write. On denial, the run is rejected with no run record and no background task, and the response is a flat `DenialEnvelope` — **not** the `{"detail": …}` shape. The envelope has no `detail` wrapper: ```json DenialEnvelope (402 example) { "code": "credit_plan_exhausted", "layer": "credit", "key": "credit:org:...", "current": 1000.0, "limit": 1000.0, "reason": "credit_plan_exhausted" } ``` The `layer` field maps to the HTTP status: | `layer` | Status | `code` values | Meaning | | --- | --- | --- | --- | | `rate` | `429` | `rate_limit_exceeded` | Run rate limit hit. Carries `Retry-After` and `X-RateLimit-*` headers. | | `quota` | `403` | `quota_exceeded` | A plan quota was exceeded. | | `credit` | `402` | `credit_plan_exhausted`, `upgrade_payment_failed` | Plan credit allowance exhausted with no wallet overage, or an upgrade charge failed. | | `wallet` | `402` | `wallet_overage_disabled`, `wallet_insufficient` | Overage is off, or the prepaid wallet has insufficient balance. | These billing responses are **live** on the run, composer, assistant, and managed-knowledge surfaces. They are **absent** on plain CRUD and org-settings routes, which return the `{"detail": ""}` `HTTPException` shape instead. For the complete picture of all error-envelope shapes and which surface emits each, see [Errors & status codes](/api-reference/errors); for credit mechanics and the gate, see [Usage gating & limits](/billing/usage-gating). The two SDKs map these differently. The Python SDK raises `PaymentRequiredError` for `402` (with subclasses `CreditExhaustedError`, `WalletError`, `QuotaExceededError`), `PermissionError`-style for `403`, and `RateLimitError` for `429`. The JavaScript SDK has no payment error class — `402` falls through to the base `ModulexError`; `429` maps to its rate-limit error. See [SDK errors & retries](/sdks/errors-retries). ```bash cURL # A 429 includes a Retry-After header; back off and retry. curl -i -X POST "$MODULEX_BASE_URL/workflows/run" \ -H "Authorization: Bearer $MODULEX_API_KEY" \ -H "X-Organization-ID: $MODULEX_ORG_ID" \ -H "Content-Type: application/json" \ -d '{"workflow_id":"550e8400-e29b-41d4-a716-446655440000","input":{"query":"AI trends"}}' # HTTP/1.1 402 Payment Required # {"code":"credit_plan_exhausted","layer":"credit",...} ``` ```python Python from modulex import ( PaymentRequiredError, QuotaExceededError, RateLimitError, ) try: run = await client.executions.run( workflow_id="550e8400-e29b-41d4-a716-446655440000", input={"query": "AI trends"}, ) except RateLimitError as exc: # 429 print("rate limited; back off", exc) except QuotaExceededError as exc: # 403 print("quota exceeded", exc) except PaymentRequiredError as exc: # 402 (credit/wallet) print("out of credits", exc) ``` ```javascript JavaScript import { ModulexError, RateLimitError } from 'modulex-js'; try { const run = await client.executions.run({ workflowId: '550e8400-e29b-41d4-a716-446655440000', input: { query: 'AI trends' }, }); } catch (err) { if (err instanceof RateLimitError) { console.log('rate limited; back off', err); // 429 } else if (err instanceof ModulexError && err.status === 402) { console.log('out of credits', err); // 402 — no dedicated payment class } else { throw err; } } ``` ## Step 5 (optional) — Resume an interrupt and read history If your workflow contains an [interrupt node](/workflow-builder/nodes/interrupt), the stream emits an `interrupt` event and waits. Inspect the checkpoint with `GET /workflows/state/{thread_id}`, then resume with `POST /workflows/resume/{thread_id}`, passing `resume_value`, the same `run_id`, and a schema source (`workflow_id` or inline `workflow`). Resume reuses the **same** `run_id`, so the run is charged only once — re-open the stream on the same `run_id` to keep watching. ```bash cURL — resume curl -X POST "$MODULEX_BASE_URL/workflows/resume/550e8400-e29b-41d4-a716-446655440000" \ -H "Authorization: Bearer $MODULEX_API_KEY" \ -H "X-Organization-ID: $MODULEX_ORG_ID" \ -H "Content-Type: application/json" \ -d '{ "workflow_id": "550e8400-e29b-41d4-a716-446655440000", "run_id": "6a7b8c9d-1e2f-3g4h-5i6j-7k8l9m0n1o2p", "resume_value": { "approved": true } }' ``` After a run finishes, fetch durable history with `GET /workflow-runs` (list) and `GET /workflow-runs/{run_pk}` (detail, including `input_snapshot` and `output_summary`). The detail route's path parameter `run_pk` is the run-history table primary key, **not** the execution `run_id` you streamed. They are different identifiers — see [Workflows & runs](/concepts/workflows-and-runs). ```bash cURL — history curl "$MODULEX_BASE_URL/workflow-runs?workflow_id=550e8400-e29b-41d4-a716-446655440000&limit=20" \ -H "Authorization: Bearer $MODULEX_API_KEY" \ -H "X-Organization-ID: $MODULEX_ORG_ID" ``` ```python Python runs = await client.executions.list_runs( workflow_id="550e8400-e29b-41d4-a716-446655440000", limit=20, ) for row in runs["runs"]: print(row["run_id"], row["status"]) # e.g. "succeeded" ``` ```javascript JavaScript const runs = await client.workflowRuns.list({ workflowId: '550e8400-e29b-41d4-a716-446655440000', limit: 20, }); for (const row of runs.runs) { console.log(row.run_id, row.status); // e.g. "succeeded" } ``` Run history is grouped differently across the SDKs: the JavaScript SDK exposes it as `client.workflowRuns`, while the Python SDK folds it into `client.executions` (`list_runs` / `iter_runs` / `get_run`). Both call the same `GET /workflow-runs` routes. See the [SDK parity matrix](/sdks/parity). ## Next steps The full event taxonomy, frame format, heartbeats, and reconnect semantics. Base URLs, the request lifecycle, and how every operation is shown three ways. Pause and resume runs that ask a human for input or approval. All three error-envelope shapes and which surface emits each. --- # Schedule a workflow URL: https://docs.modulex.dev/guides/schedule-a-workflow Run a deployed ModuleX workflow automatically on a cron schedule: deploy, create the schedule, write a cron expression, and monitor scheduled runs — with cURL, Python, and JavaScript and full parameter and error coverage. This guide walks you through running a workflow on a cron schedule, end to end: deploy the workflow, create a schedule, write a correct cron expression, then watch the scheduled runs and retry any that fail. Every step is shown via cURL, Python, and JavaScript. If you want the full parameter, response, and error catalog for every schedule endpoint instead of a walkthrough, go straight to the reference, [Schedules](/workflow-builder/execution/schedule). This page focuses on the common task: a workflow that fires on a calendar cadence. A schedule fires a workflow's live deployment, never its draft. Attach a cron cadence to the deployed workflow. The 5-field cron format, timezones, and common patterns. Inspect run history and stats, and retry failed runs. ## What you will build A schedule that runs a deployed workflow automatically at a fixed calendar time — for example, a report workflow every weekday at 09:00 in your timezone. ModuleX fires the workflow's live deployment in the background on that cadence and records each firing as a scheduled run you can inspect. This guide uses a `cron` schedule. ModuleX also supports a fixed-`interval` cadence (every N seconds); the reference covers both in [Cron and interval cadence](/workflow-builder/execution/schedule#cron-and-interval-cadence). ## Before you start **Every** schedule endpoint requires the `owner` or `admin` role in the organization — including the read-only ones that list schedules and runs. A plain member receives a `403`, even though they may be able to view the underlying workflow. See [Roles & permissions](/security/roles-permissions) and [Organizations, roles & membership](/concepts/organizations-roles). A schedule references a workflow's **live deployment**, not its draft. If the workflow has no live deployment when you create the schedule, the call fails with a `400` (not a `404`). [Step 1](#step-1-deploy-the-workflow) covers deploying. Programmatic calls authenticate with `Authorization: Bearer mx_live_…` plus `X-Organization-ID`. Create a key from the app, or see [Authentication](/api-reference/authentication) and the [Quickstart](/get-started/quickstart). Schedules are gated to **owner/admin on every route, reads included**, which is broader than most read endpoints in the API. If a teammate gets a `403` listing or reading schedules, check their organization role first. _(Doc-blocking finding §2.9 — schedules require owner/admin; the `member` role is retired.)_ **🎬 MEDIA PLACEHOLDER · MX-MEDIA-4470 · [APP_VIDEO]** **[APP_VIDEO_DESCRIPTION]:** End-to-end walkthrough of scheduling a deployed workflow on a weekday-morning cron and watching the first scheduled run land in history. **[APP_VIDEO_DETAILS]:** Record in the live app as an owner/admin. Sequence: open a workflow that has a live deployment, open the schedules panel, create a cron schedule with expression `0 9 * * 1-5` and timezone `America/New_York`, show the computed next run time, then cut to the schedule's run history showing a `succeeded` run with its duration. Highlight the cadence field, the timezone field, and the next-run value. Light theme, 16:9, ModuleX docs chrome. Redact any real organization, workflow, or schedule ID. ### Set your variables The cURL examples below reuse these placeholders. Replace them with your own values; the workflow and organization IDs are UUIDs. ```bash export MODULEX_API_KEY="mx_live_xxx" export MODULEX_ORG_ID="0a1b2c3d-4e5f-6071-8293-a4b5c6d7e8f9" export WORKFLOW_ID="2b9e7c10-aaaa-bbbb-cccc-1234567890ab" ``` The Python and JavaScript examples create a client once and reuse it. The Python SDK is async, so every call is awaited; it also reads `MODULEX_API_KEY` and `MODULEX_ORGANIZATION_ID` from the environment if you omit the arguments. The JavaScript SDK has no environment-variable fallback — pass the values explicitly. ```python Python import asyncio from modulex import Modulex client = Modulex( api_key="mx_live_xxx", organization_id="0a1b2c3d-4e5f-6071-8293-a4b5c6d7e8f9", ) WORKFLOW_ID = "2b9e7c10-aaaa-bbbb-cccc-1234567890ab" ``` ```javascript JavaScript import { Modulex } from "modulex-js"; const client = new Modulex({ apiKey: "mx_live_xxx", organizationId: "0a1b2c3d-4e5f-6071-8293-a4b5c6d7e8f9", }); const WORKFLOW_ID = "2b9e7c10-aaaa-bbbb-cccc-1234567890ab"; ``` ## Step 1: deploy the workflow A schedule runs the workflow's **live deployment**. Before scheduling, make sure the workflow is deployed — from the builder, or via the API. Skipping this is the most common reason a `POST /schedules` returns `400` with a message about a missing live deployment. For how deployments work and how to create one, see [Deploy & versions](/workflow-builder/execution/deploy). Once the workflow has a live deployment, continue. You can confirm a deployment exists by running the workflow once on demand through [Run via API](/workflow-builder/execution/api-endpoint) before you schedule it. A workflow that runs manually has the live deployment a schedule needs. ## Step 2: create the schedule Create the schedule with `POST /schedules`. The example below fires the workflow at 09:00 every weekday in `America/New_York` and pins a per-run `input` of `{"mode": "full"}`. ```bash cURL curl -X POST https://api.modulex.dev/schedules \ -H "Authorization: Bearer $MODULEX_API_KEY" \ -H "X-Organization-ID: $MODULEX_ORG_ID" \ -H "Content-Type: application/json" \ -d '{ "workflow_id": "'"$WORKFLOW_ID"'", "name": "Nightly report", "schedule_type": "cron", "cron_expression": "0 9 * * 1-5", "timezone": "America/New_York", "input": { "mode": "full" } }' ``` ```python Python async def create_schedule(): schedule = await client.schedules.create( workflow_id=WORKFLOW_ID, name="Nightly report", schedule_type="cron", cron_expression="0 9 * * 1-5", timezone="America/New_York", input={"mode": "full"}, ) print(schedule.id, schedule.next_run_at) return schedule asyncio.run(create_schedule()) ``` ```javascript JavaScript const schedule = await client.schedules.create({ workflowId: WORKFLOW_ID, name: "Nightly report", scheduleType: "cron", cronExpression: "0 9 * * 1-5", timezone: "America/New_York", input: { mode: "full" }, }); console.log(schedule.id, schedule.next_run_at); ``` The SDKs accept **camelCase** arguments (`workflowId`, `scheduleType`, `cronExpression`) and send them as snake_case on the wire. Responses always come back **snake_case** (`workflow_id`, `next_run_at`). Creating a schedule returns immediately with `200 OK` (not `201`); the workflow itself runs later, on cadence. ### Create request fields These are the fields you send to `POST /schedules`. For a cron schedule, supply `cron_expression` and leave `interval_seconds` unset. The workflow to run. It must have a live deployment, or the call returns `400`. A human-readable name. Length 1–255 characters. `cron` for a calendar cadence, or `interval` for a fixed number of seconds. This guide uses `cron`. Required when `schedule_type` is `cron`. A standard 5-field cron string (see [Cron syntax](#cron-syntax)). Max 100 characters. Validated server-side; an unparseable expression returns `400`. Required only when `schedule_type` is `interval`. Minimum **60**. Leave unset for cron schedules. An IANA timezone name (for example `America/New_York`). Max 50 characters. The cron expression is evaluated in this timezone. An unknown name returns `400`. Optional free-text description. Per-run state input. On every firing it is merged over the deployment's default input — schedule input wins on conflicting keys. Plain values only; `{{node_id.field}}` references do not resolve at trigger time because there is no upstream node. Per-run execution config such as `timeout` (the per-run timeout, default one hour) and `recursion_limit`. Merged over the deployment's config on every firing. Supply exactly one cadence field for the type you chose: `cron_expression` for `cron`, `interval_seconds` for `interval`. A mismatch — for example `schedule_type: "cron"` with no `cron_expression` — returns `400`. ### What you get back The response is a `ScheduleResponse`. The key field to note now is `next_run_at`: ModuleX computes the first firing from the current time and marks the schedule active (`is_active: true`). The schedule's ID. Use it for every follow-up call. Whether the schedule is firing. `false` while paused. When the schedule will next fire, always stored in UTC. When it last fired. `null` until the first firing. Status of the most recent run. Lifetime count of runs produced. `0` at creation. Lifetime count of succeeded runs. Lifetime count of failed runs. The response also echoes the fields you sent (`workflow_id`, `name`, `schedule_type`, `cron_expression`, `timezone`, `input`, `config`) plus `organization_id`, `created_at`, and `updated_at`. For the full field list, see the [reference response section](/workflow-builder/execution/schedule#response). ```json { "id": "8f1c2a40-1d3e-4b9a-9c77-1a2b3c4d5e6f", "workflow_id": "2b9e7c10-aaaa-bbbb-cccc-1234567890ab", "organization_id": "0a1b2c3d-4e5f-6071-8293-a4b5c6d7e8f9", "name": "Nightly report", "description": null, "schedule_type": "cron", "interval_seconds": null, "cron_expression": "0 9 * * 1-5", "timezone": "America/New_York", "input": { "mode": "full" }, "config": {}, "is_active": true, "next_run_at": "2026-06-22T13:00:00Z", "last_run_at": null, "last_run_status": null, "total_runs": 0, "successful_runs": 0, "failed_runs": 0, "created_at": "2026-06-21T18:30:00Z", "updated_at": "2026-06-21T18:30:00Z" } ``` In the example above the schedule is created on a Sunday, so the first weekday firing at 09:00 `America/New_York` lands the following morning. 09:00 Eastern is stored as `13:00Z` because `next_run_at` is always UTC. ### Create errors | Status | Cause | | --- | --- | | `400` | Workflow not found, no live deployment, invalid cron, invalid timezone, `interval_seconds < 60`, or a type/cadence mismatch. Shape: `{"detail": ""}`. | | `400` | `X-Organization-ID` header missing. | | `401` | Missing or invalid token. | | `403` | Caller is not owner/admin, the user is inactive, or the API key's org scope does not match `X-Organization-ID`. | | `422` | Request-body validation, for example `schedule_type` outside `cron`/`interval` or a name length out of range. Shape: `{"detail": [{"loc": [...], "msg": "...", "type": "..."}]}`. | | `429` | Rate limit exhausted. The `detail` is an object: `{code, layer, key, current, limit, reason}`. | | `500` | Unexpected server error: `{"detail": "An unexpected internal server error occurred."}`. | Schedule routes use FastAPI's `HTTPException` envelope and are **not** behind the credits billing gate, so they never return the flat `DenialEnvelope`. Credits are consumed only when a scheduled run actually executes the workflow (see [Cost of a schedule](#cost-of-a-schedule)). For the full error model, see [Errors & status codes](/api-reference/errors). ## Cron syntax A `cron_expression` is a **standard 5-field cron string**, evaluated in the schedule's `timezone`: ```text ┌───────────── minute (0–59) │ ┌───────────── hour (0–23) │ │ ┌───────────── day-of-month (1–31) │ │ │ ┌───────────── month (1–12) │ │ │ │ ┌───────────── day-of-week (0–6, Sunday = 0) │ │ │ │ │ * * * * * ``` Each field accepts a value, a list (`1,15`), a range (`1-5`), a step (`*/15`), or `*` for "every". ModuleX parses and validates the expression server-side; anything it cannot parse returns a `400`. Maximum length is 100 characters. ### Common patterns | Expression | Meaning | | --- | --- | | `0 9 * * 1-5` | 09:00 every weekday (Monday–Friday) | | `0 * * * *` | At the top of every hour | | `*/15 * * * *` | Every 15 minutes | | `0 0 1 * *` | 00:00 on the 1st of each month | | `30 2 * * 0` | 02:30 every Sunday | | `0 9,17 * * 1-5` | 09:00 and 17:00 on weekdays | ### Timezones and `next_run_at` The cron expression is evaluated in the schedule's `timezone` (default `UTC`). ModuleX computes the next occurrence in that local timezone and then stores it in `next_run_at` as **UTC**. So a schedule with `0 9 * * 1-5` and `America/New_York` shows `13:00Z` (or `14:00Z` during standard time) — that is the same 09:00 local moment, expressed in UTC. When you change the cadence or timezone with an update, `next_run_at` is recalculated. The base for the next firing is the last run if there is one, otherwise the current time. For the exact recurrence model, see [How `next_run_at` is computed](/workflow-builder/execution/schedule#how-next_run_at-is-computed). A background **scheduler tick** drives firing, and it runs roughly **every 30 to 60 seconds** depending on deployment. A schedule fires at or shortly after `next_run_at`, never before. Treat sub-minute cron fields and intervals near the 60-second minimum as best-effort — the effective floor on how often a workflow can fire is the tick, not the value you set. ### Edit the cadence later To change the cron expression, timezone, name, input, or config after creation, send `PUT /schedules/{schedule_id}` with only the fields you want to change. Changing the cadence or timezone recalculates `next_run_at`. ```bash cURL curl -X PUT https://api.modulex.dev/schedules/8f1c2a40-1d3e-4b9a-9c77-1a2b3c4d5e6f \ -H "Authorization: Bearer $MODULEX_API_KEY" \ -H "X-Organization-ID: $MODULEX_ORG_ID" \ -H "Content-Type: application/json" \ -d '{ "cron_expression": "0 8 * * 1-5" }' ``` ```python Python updated = await client.schedules.update( "8f1c2a40-1d3e-4b9a-9c77-1a2b3c4d5e6f", cron_expression="0 8 * * 1-5", ) ``` ```javascript JavaScript const updated = await client.schedules.update( "8f1c2a40-1d3e-4b9a-9c77-1a2b3c4d5e6f", { cronExpression: "0 8 * * 1-5" }, ); ``` A `null` field in an update is **dropped**, not applied, so you cannot clear `description`, `input`, or `config` back to empty through update. `is_active` is not updatable here — use [pause and resume](#pause-or-resume-a-schedule) instead. If every field resolves to `null`, the call returns `400` with `{"detail": "No updates provided"}`. Full update rules are in the [reference](/workflow-builder/execution/schedule#update-a-schedule). ## Step 3: monitor scheduled runs Every firing produces a **scheduled run** record. Use the run endpoints to confirm your schedule is working and to act on failures. ### List the schedule's runs `GET /schedules/{schedule_id}/runs` lists runs newest-first by scheduled time. Filter by `status` while debugging, and page with `limit` (1–100, default 50) and `offset`. ```bash cURL curl "https://api.modulex.dev/schedules/8f1c2a40-1d3e-4b9a-9c77-1a2b3c4d5e6f/runs?limit=20" \ -H "Authorization: Bearer $MODULEX_API_KEY" \ -H "X-Organization-ID: $MODULEX_ORG_ID" ``` ```python Python runs = await client.schedules.list_runs( "8f1c2a40-1d3e-4b9a-9c77-1a2b3c4d5e6f", limit=20, ) for r in runs.runs: print(r.scheduled_at, r.status, r.duration_seconds) ``` ```javascript JavaScript const { runs } = await client.schedules.runs( "8f1c2a40-1d3e-4b9a-9c77-1a2b3c4d5e6f", { limit: 20 }, ); for (const r of runs) { console.log(r.scheduled_at, r.status, r.duration_seconds); } ``` Each item is a `ScheduleRunResponse`. The fields most useful for monitoring: The scheduled-run record ID. This is the `run_id` argument you pass to the get-run and retry endpoints. When the run was supposed to fire. When execution began. When execution finished. `completed_at − started_at`, in seconds. The run status (see the table below). The failure reason, when the run failed. What triggered the run: `scheduler` for a normal firing, or `retry` for a manual retry. The workflow execution's run ID, prefixed `sched_`. Populated once execution starts. This is distinct from the record `id` above. The execution thread ID, prefixed `sched_thread_`. A scheduled run carries **three different IDs** — the record `id`, the execution `run_id` (`sched_…`), and the `thread_id` (`sched_thread_…`). When you call get-run or retry, the `run_id` argument is the record's `id`, **not** the execution `run_id`. Keep them straight; see [Workflows & runs](/concepts/workflows-and-runs) for the full run-identity model. _(Doc-blocking finding §2.9 — three distinct run-id identities.)_ #### Run status values | Status | Meaning | | --- | --- | | `pending` | The run record exists; execution has not started. | | `running` | Execution is in progress. | | `succeeded` | The workflow completed successfully. | | `failed` | Execution raised an error; see `error_message`. | | `cancelled` | The run was cancelled. | | `skipped` | The firing was skipped — for example the workflow lost its live deployment between scheduling and execution. | ### Check the success rate `GET /schedules/{schedule_id}/runs/stats` rolls up recent runs over a `days` window (1–90, default 7). Use it to spot a schedule that is silently failing. ```bash cURL curl "https://api.modulex.dev/schedules/8f1c2a40-1d3e-4b9a-9c77-1a2b3c4d5e6f/runs/stats?days=30" \ -H "Authorization: Bearer $MODULEX_API_KEY" \ -H "X-Organization-ID: $MODULEX_ORG_ID" ``` ```python Python stats = await client.schedules.run_stats( "8f1c2a40-1d3e-4b9a-9c77-1a2b3c4d5e6f", days=30, ) print(stats.success_rate, stats.avg_duration_seconds) ``` ```javascript JavaScript const stats = await client.schedules.runStats( "8f1c2a40-1d3e-4b9a-9c77-1a2b3c4d5e6f", { days: 30 }, ); console.log(stats.success_rate, stats.avg_duration_seconds); ``` ```json { "period_days": 30, "total_runs": 168, "successful_runs": 160, "failed_runs": 8, "success_rate": 95.24, "avg_duration_seconds": 38.7, "min_duration_seconds": 12.1, "max_duration_seconds": 91.4 } ``` The scale of `success_rate` is reported inconsistently across our sources: the API computes it as a **percentage** (for example `95.24`), while the JavaScript SDK types describe it as a **fraction** (0.0–1.0). Read it defensively — branch on whether the value exceeds `1` — until this is reconciled. ### Retry a failed run If a firing failed or was cancelled, retry it with `POST /schedules/{schedule_id}/runs/{run_id}/retry`, where `run_id` is the scheduled-run record's `id`. The retry queues a brand-new background execution with `triggered_by` set to `retry`; it does not run inline and does not modify the original run. ```bash cURL # 1. Find the failed runs curl "https://api.modulex.dev/schedules/8f1c2a40-1d3e-4b9a-9c77-1a2b3c4d5e6f/runs?status=failed" \ -H "Authorization: Bearer $MODULEX_API_KEY" \ -H "X-Organization-ID: $MODULEX_ORG_ID" # 2. Retry one by its record id curl -X POST https://api.modulex.dev/schedules/8f1c2a40-1d3e-4b9a-9c77-1a2b3c4d5e6f/runs/11111111-2222-3333-4444-555555555555/retry \ -H "Authorization: Bearer $MODULEX_API_KEY" \ -H "X-Organization-ID: $MODULEX_ORG_ID" ``` ```python Python failed = await client.schedules.list_runs( "8f1c2a40-1d3e-4b9a-9c77-1a2b3c4d5e6f", status="failed", ) for r in failed.runs: result = await client.schedules.retry_run( "8f1c2a40-1d3e-4b9a-9c77-1a2b3c4d5e6f", r.id, ) print("retried", result.original_run_id) ``` ```javascript JavaScript const { runs: failed } = await client.schedules.runs( "8f1c2a40-1d3e-4b9a-9c77-1a2b3c4d5e6f", { status: "failed" }, ); for (const r of failed) { const result = await client.schedules.retryRun( "8f1c2a40-1d3e-4b9a-9c77-1a2b3c4d5e6f", r.id, ); console.log("retried", result.original_run_id); } ``` On success the response is `{ "message": "Retry scheduled", "original_run_id": "" }`. Retrying a run whose status is anything other than `failed` or `cancelled` returns `400` with `{"detail": "Can only retry failed or cancelled runs. Current status: "}`. There is no public "run now" endpoint for a single schedule. To trigger an ad-hoc execution, either retry a finished run, or run the workflow directly through [Run via API](/workflow-builder/execution/api-endpoint). For the get-one-run endpoint and the full run schema, see the [reference run section](/workflow-builder/execution/schedule#run-history-and-statistics). ## Pause or resume a schedule To stop a schedule firing without deleting its history, pause it; bring it back with resume. While paused (`is_active: false`), the scheduler tick skips it. ```bash cURL # Pause curl -X POST https://api.modulex.dev/schedules/8f1c2a40-1d3e-4b9a-9c77-1a2b3c4d5e6f/pause \ -H "Authorization: Bearer $MODULEX_API_KEY" \ -H "X-Organization-ID: $MODULEX_ORG_ID" # Resume curl -X POST https://api.modulex.dev/schedules/8f1c2a40-1d3e-4b9a-9c77-1a2b3c4d5e6f/resume \ -H "Authorization: Bearer $MODULEX_API_KEY" \ -H "X-Organization-ID: $MODULEX_ORG_ID" ``` ```python Python await client.schedules.pause("8f1c2a40-1d3e-4b9a-9c77-1a2b3c4d5e6f") await client.schedules.resume("8f1c2a40-1d3e-4b9a-9c77-1a2b3c4d5e6f") ``` ```javascript JavaScript await client.schedules.pause("8f1c2a40-1d3e-4b9a-9c77-1a2b3c4d5e6f"); await client.schedules.resume("8f1c2a40-1d3e-4b9a-9c77-1a2b3c4d5e6f"); ``` Resume **restarts the clock**: it recomputes `next_run_at` from now rather than resuming from where the schedule left off. A paused daily schedule resumed at 14:00 computes its next firing from 14:00. If you need the original cadence to hold exactly, leave the schedule active and rely on its computed `next_run_at` instead of pausing. Deleting a schedule (`DELETE /schedules/{schedule_id}`) removes it **and all of its run history** and cannot be undone — pause instead when you want to keep history. ## Cost of a schedule The schedule API itself — create, read, update, pause, resume, run history — does **not** consume credits. Credits are consumed when a scheduled firing **executes the workflow**, exactly as for a manual run: any managed-model, managed-knowledge, or other metered work inside the workflow draws down credits at execution time. A schedule that fires every minute meters the same as running that workflow manually every minute. Size the cadence accordingly, and review [Credits & metering](/billing/credits) and [Usage gating & limits](/billing/usage-gating) before scheduling a high-frequency, credit-heavy workflow. ## Troubleshooting The workflow has no live deployment yet. Deploy it first (see [Deploy & versions](/workflow-builder/execution/deploy)), then create the schedule. This is a `400`, not a `404`. Schedule routes require the `owner` or `admin` role on every endpoint, reads included. A plain member cannot list, read, or manage schedules. Check the teammate's organization role — see [Roles & permissions](/security/roles-permissions). Confirm `is_active` is `true` (a paused schedule is skipped) and that `next_run_at` is in the future and in the expected UTC time. Remember the scheduler tick runs every 30–60 seconds, so firings land at or just after `next_run_at`, never before. Sub-minute cadences are best-effort. A `skipped` run means the firing was reached but could not execute — most often because the workflow lost its live deployment between scheduling and execution. Re-deploy the workflow. The expression must be a valid 5-field cron string (max 100 characters) that ModuleX can parse, and the `timezone` must be a valid IANA name. Re-check both against [Cron syntax](#cron-syntax). ## Related pages The complete endpoint reference: every parameter, response field, and error. Create the live deployment a schedule needs. Trigger a workflow on demand instead of on a cadence. Authenticate, run a workflow, and stream the result. Why schedules require the owner or admin role. How scheduled runs consume credits when they execute. --- # Account & access — help URL: https://docs.modulex.dev/help/account-access How to sign in, switch organizations, understand roles, and recover access in ModuleX. Short answers about signing in, working across organizations, and what each role can do. For the full picture, follow the links in each answer to the canonical page. What owners and admins can each do. How `X-Organization-ID` picks which organization you act in. ## Common questions In the app, use the organization switcher to change which organization you are working in. From the API or an SDK, set the `X-Organization-ID` header (the organization's UUID) on each request. See [org context](/security/org-context). **🎬 MEDIA PLACEHOLDER · MX-MEDIA-4510 · [SCREENSHOT]** **[SCREENSHOT_DESCRIPTION]:** The organization switcher in the ModuleX app. **[SCREENSHOT_DETAILS]:** Capture the top-bar / sidebar organization switcher open, showing a list of organizations the user belongs to with the active one marked. Crop tightly to the switcher control and its dropdown. Light theme, desktop width, no personal data visible. There are two roles: **owner** and **admin**. Owners manage billing and own the organization; admins can invite people, manage credentials, and build and run workflows, the AI Composer, and the Assistant. The `member` role has been retired, so there is no longer a read-only role. See [roles & permissions](/security/roles-permissions). A few older organizations may still show a leftover `member` value on some records. It is deprecated and not a role you can assign; treat owner and admin as the live roles. ModuleX uses Clerk as its identity provider, so you sign in through Clerk. There is no separate ModuleX username and password. Your account and a starter organization are created on your first sign-in. See [getting started](/help/getting-started). No. API keys (they start with `mx_live_`) are shown only once when you create them and are not stored in a way that lets us show them again. Delete the old key and create a new one in Settings, then update wherever the old key was used. See [authentication](/api-reference/authentication). The organization you are calling is not one you belong to. This usually means `X-Organization-ID` is set to the wrong organization, or your API key is scoped to a different one. Set the header to an organization you are a member of. See [org context](/security/org-context). Use Clerk's sign-in and recovery options (password reset, single sign-on, and any other method your organization has enabled). If you still cannot get in after that, reach out through [getting help](/help/getting-help). ## Selecting your organization from code Every organization-scoped request needs an organization. Send your API key in the `Authorization` header and the organization UUID in `X-Organization-ID`. The SDKs take the same two values when you create the client. ```bash cURL curl https://api.modulex.dev/auth/me \ -H "Authorization: Bearer mx_live_2J9vK4xM..." \ -H "X-Organization-ID: a1b2c3d4-0000-0000-0000-000000000000" ``` ```python Python import asyncio from modulex import Modulex async def main(): async with Modulex( api_key="mx_live_2J9vK4xM...", organization_id="a1b2c3d4-0000-0000-0000-000000000000", ) as client: me = await client.auth.me() print(me) asyncio.run(main()) ``` ```javascript JavaScript import { Modulex } from "modulex-js"; const client = new Modulex({ apiKey: "mx_live_2J9vK4xM...", organizationId: "a1b2c3d4-0000-0000-0000-000000000000", }); const me = await client.auth.me(); console.log(me); ``` If you leave `X-Organization-ID` off an organization-scoped request, the API returns `400` with `X-Organization-ID header is required`. If it points at an organization you are not in, you get `403` with `User is not a member of organization`. See [org context](/security/org-context). ## Keep reading How organizations, membership, and roles fit together. Signing up and making your first call. --- # API & SDKs — help URL: https://docs.modulex.dev/help/api-sdks Use ModuleX from your own code: the JavaScript and Python SDKs, how to authenticate, the base URL, streaming run output, and rate limits. Everything you can do in the ModuleX app, you can also do from code. The REST API, the JavaScript SDK, and the Python SDK all talk to the same backend, so a workflow you build in the app runs the same way when you call it from a script. This page answers the questions people ask most when they start using ModuleX from code. For the full developer reference, see the [API overview](/api-reference/overview) and the [SDKs overview](/sdks/overview). ## Quick answers JavaScript and Python, both with streaming. A few features differ between them. Send your `mx_live_` API key as a Bearer token plus your organization ID. Every request goes to `https://api.modulex.dev` by default. Follow a run event by event with `listen()` or the listen endpoint. ## Common questions Yes — ModuleX publishes an official JavaScript SDK (`modulex-js`) and an official Python SDK (`modulex-python`), and both support live streaming. They cover the same operations with a handful of differences. The biggest one to know: managing **subscriptions** (plans, billing, checkout, the customer portal) is available in the **Python SDK only** — the JavaScript SDK has no subscriptions methods. For the full side-by-side comparison, see the [SDK parity matrix](/sdks/parity); to get started, see the [SDKs overview](/sdks/overview). Send two things on every request: your API key as `Authorization: Bearer mx_live_…`, and your organization in the `X-Organization-ID` header. In the SDKs you pass your API key and organization ID when you create the client, and it adds both headers for you. Create and copy your key from Settings, API Keys (it is shown only once). Full details are on the [authentication](/api-reference/authentication) page. `https://api.modulex.dev`, with no version segment in the path — do not add `/v1` or `/api`. The SDKs use this base URL by default, so you normally do not set it yourself. See [base URLs & versioning](/api-reference/environments) for the details. Instead of waiting for a run to finish, you can follow it event by event. In the SDKs, call `listen()` and read the events as they arrive; over plain HTTP, open the run's listen endpoint, which streams server-sent events. This is how the app shows live progress. See [SSE run streaming](/realtime/sse-streaming) and [streaming & HITL](/sdks/streaming-hitl). Yes — a per-minute limit applies to your API key (and a higher combined limit across all your keys). If you go over it, ModuleX returns a `429` response with a `Retry-After` header telling you how many seconds to wait before retrying. The SDKs read that header and back off automatically. See [rate limiting](/api-reference/rate-limiting). In the Python SDK, yes: if you do not pass them in code, it reads `MODULEX_API_KEY`, `MODULEX_ORGANIZATION_ID`, and `MODULEX_BASE_URL` from the environment. The JavaScript SDK does **not** read environment variables on its own — you must pass your API key (and organization ID) to the client explicitly. See the [JavaScript SDK](/sdks/javascript) and [Python SDK](/sdks/python) pages. ## Authenticate and make a call Every operation looks the same in all three: send your `mx_live_` key as a Bearer token plus your `X-Organization-ID`. Here is listing your workflows. ```bash cURL curl https://api.modulex.dev/workflows \ -H "Authorization: Bearer mx_live_your_api_key" \ -H "X-Organization-ID: your-org-id" ``` ```python Python from modulex import Modulex async with Modulex( api_key="mx_live_your_api_key", organization_id="your-org-id", ) as client: page = await client.workflows.list() ``` ```javascript JavaScript import { Modulex } from "modulex-js"; const client = new Modulex({ apiKey: "mx_live_your_api_key", organizationId: "your-org-id", }); const { workflows } = await client.workflows.list(); ``` The Python SDK is async — call its methods with `await` inside an `async` function. The JavaScript SDK uses promises, so `await` its methods too. ## Stream a run live To watch a run as it happens, call `listen()` with the run's id and read events until the run finishes. ```bash cURL curl -N https://api.modulex.dev/workflows/listen/your-run-id \ -H "Authorization: Bearer mx_live_your_api_key" \ -H "X-Organization-ID: your-org-id" \ -H "Accept: text/event-stream" ``` ```python Python async for event in client.executions.listen("your-run-id"): print(event.event, event.data) ``` ```javascript JavaScript for await (const event of client.executions.listen("your-run-id")) { console.log(event.type, event.data); } ``` ## Handle rate limits When you exceed the per-minute limit, ModuleX returns `429` with a `Retry-After` header (seconds to wait). The SDKs honor it and retry for you; if you call the API directly, wait that long before retrying. A `429` means too many requests in the current window — not an error in your request itself. The `Retry-After` response header tells you how many seconds to wait before trying again. Pause for that many seconds and send the request again. The SDKs do this automatically. For what other status codes mean (`401`, `402`, `403`, and more), see [errors & troubleshooting](/help/errors-troubleshooting) and the full [errors reference](/api-reference/errors). **🎬 MEDIA PLACEHOLDER · MX-MEDIA-4580 · [SCREENSHOT]** **[SCREENSHOT_DESCRIPTION]:** the API Keys screen where a user creates a `mx_live_` key **[SCREENSHOT_DETAILS]:** Capture Settings, API Keys in the live ModuleX app showing the create-key dialog with the one-time key reveal. Light mode, 16:9, redact any real key value. Highlight the "copy" action and the note that the key is shown only once. ## Where to go next The full authentication reference: Bearer keys, organization context, and headers. Install and configure the JavaScript and Python SDKs. An end-to-end walkthrough in cURL, Python, and JavaScript. Exactly which operations each SDK supports, including the Python-only subscriptions. --- # Billing, plans & credits — help URL: https://docs.modulex.dev/help/billing-credits How ModuleX bills: plans, what a credit is, what uses credits, and what happens when you run out. Quick answers for managing cost and usage. Short answers to the questions people ask most about paying for ModuleX and tracking what they use. For the full detail, follow the link in each answer. Free, Pro, Max, and Enterprise — what each includes. What a credit is and exactly what consumes credits. The prepaid balance that covers usage beyond your plan. ## Billing, plans & credits There is a Free plan, paid Pro and Max plans, and custom Enterprise pricing. Pro is `$25/month` and includes 5,000 credits per month; Max is `$100/month` and includes 20,000 credits per month. See the full breakdown in [Plans & pricing](/billing/plans). A credit is how managed usage is measured: `100 credits = $1.00` (one credit is `$0.01`). Running workflows, AI turns, and knowledge searches all draw down credits. See [Credits & metering](/billing/credits). Each workflow run or AI turn costs a base credit, plus the cost of any managed AI model usage, knowledge searches, and tool calls. If you bring your own model key (BYOK), that usage is billed by the provider and is not charged in ModuleX credits. See [Credits & metering](/billing/credits). Managed runs are blocked with a `402` "payment required" response until you have budget again. Top up your [wallet](/billing/wallet), turn on overage, or upgrade your plan in [Plans & pricing](/billing/plans). Your plan gives a monthly credit allowance. The wallet is a separate prepaid balance that covers usage after the allowance runs out — this is called overage, and you can turn it on for paid plans. See [Wallet & top-ups](/billing/wallet). Open your dashboard to see credit spend and remaining balance, with per-run detail in your logs. For what each line item means, see [Credits & metering](/billing/credits). **🎬 MEDIA PLACEHOLDER · MX-MEDIA-4520 · [SCREENSHOT]** **[SCREENSHOT_DESCRIPTION]:** the usage/analytics view showing credit spend and remaining balance **[SCREENSHOT_DETAILS]:** capture the dashboard analytics page in the ModuleX app for an org with some recorded usage; show the credits-spent and credits-remaining figures and the spend-over-time chart; light mode; 16:9; no real customer data Contact support to request a refund. Refunds are usually returned as wallet credit rather than to your card. See [Wallet & top-ups](/billing/wallet). Pro and Max can be billed annually. The marketed annual price and the amount charged at checkout do not currently agree, so confirm the exact annual amount on the Stripe checkout screen before you pay. Pricing details are on [Plans & pricing](/billing/plans). BYOK (bring your own key) usage is never charged in ModuleX credits — it is tracked for analytics only and billed directly by the upstream provider. ## Keep reading What a `402`, `403`, or `429` means and how to clear it. The admission gate and the responses it returns when limits are hit. --- # AI Composer & Assistant — help URL: https://docs.modulex.dev/help/composer-assistant Quick answers about ModuleX's two AI helpers: the AI Composer that builds workflows from plain English, and the Assistant that uses your connected tools step by step — including who can run them and what to do when one pauses. ModuleX has two AI helpers, and they do different jobs. The **AI Composer** turns a plain-English description into an editable workflow. The **Assistant** is a chat that uses your connected tools step by step to get something done, without building a workflow. This page answers the questions people ask most about both. Each answer links to a fuller guide. The text-to-workflow agent that drafts an editable workflow from a prompt. The agentic chat that uses your tools to finish a task — no workflow needed. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-4540 · [APP_VIDEO]** **[APP_VIDEO_DESCRIPTION]:** The AI Composer generating a workflow from a plain-English prompt. **[APP_VIDEO_DETAILS]:** Screen recording in the workflow builder. Show a user typing a prompt into the Composer panel (for example, "Search GitHub and summarize the top result"), then the Composer streaming its work — adding nodes onto the canvas one at a time — and finishing with an editable workflow the user can save. Highlight the Composer chat panel on the left and the canvas updating live on the right. 30 to 45 seconds, 16:9, light mode, annotate the prompt box and the "save changes" step. ## Common questions The **AI Composer** turns a plain-English description into a complete, editable workflow that you save and run later. The **Assistant** is a chat that uses your connected tools step by step to finish a task right now, and it does not build or change any workflow. In short: use the Composer to *build* something reusable; use the Assistant to *get something done* in the moment. See [AI Composer](/concepts/ai-composer) and the [Assistant overview](/assistant/overview). Open the Composer in the workflow builder and describe what you want — for example, "Search GitHub and summarize the top result." It builds the workflow on the canvas for you, then you review the changes and **save** them (or **revert** to undo). You can keep chatting to refine it. See [AI Composer in the builder](/workflow-builder/composer) and the [build with Composer guide](/guides/build-with-composer). Both the Composer and the Assistant require an **owner** or **admin** role in the organization. There is no longer a read-only `member` role, so anyone without owner or admin access cannot run them. Ask an owner or admin to change the teammate's role, or to run the task for them. See [roles & permissions](/security/roles-permissions). A tool it wants to use needs a credential for that service, so it pauses and asks you to connect the account. Connect it when prompted — for an OAuth service this sends you to the provider to approve access — and the Assistant continues on its own from where it stopped. See [credentials & integrations help](/help/credentials-integrations). It is almost always **paused waiting for your input**, not failed. The Composer and the Assistant pause to ask a question — a choice, a yes/no, some text, or a request to connect a service. Answer the prompt and the run picks up where it left off. See the [Assistant overview](/assistant/overview). The Composer and the Assistant each charge **one run credit per turn** (one message you send), plus the cost of any AI model and tool usage. Bringing your own model key (BYOK) is not charged in credits. See [credits & metering](/billing/credits). ## Keep reading A full walkthrough from a prompt to a running workflow. Who can use the Composer, the Assistant, and schedules. --- # Credentials & integrations — help URL: https://docs.modulex.dev/help/credentials-integrations Quick answers for connecting external services to ModuleX: add an OAuth or API-key credential, browse the 175 integrations, find a tool, bring your own keys (BYOK), how credentials are encrypted, fix a broken OAuth connection, and build your own integration. Short answers to the questions people ask most about connecting ModuleX to the services you already use, so your workflows and the Assistant can act on your behalf. For the full detail, follow the link in each answer. Connect ModuleX to 175 services and call their tools from workflows and the Assistant. How integrations authenticate: OAuth2, API keys, bearer tokens, and managed keys. Browse the full catalog of 175 integrations by category. Create, set a default, test, and remove credentials in the app or via the API. ## Credentials & integrations Add a credential for the service you want to use. For an OAuth integration you click connect and approve access on the provider's site (for example GitHub); for a key-based integration you paste an API key or token. Adding and managing credentials needs an owner or admin role on the organization. See [Authentication & credentials](/integrations/authentication) for each connection type. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-4560 · [SCREENSHOT]** **[SCREENSHOT_DESCRIPTION]:** connecting an OAuth integration (GitHub) from the credentials screen in the ModuleX app **[SCREENSHOT_DETAILS]:** capture the credentials area in the ModuleX app while connecting GitHub; show the connect/authorize action that hands off to the provider's OAuth consent screen, and the connected credential appearing in the list afterward; light mode; 16:9; no real customer data or tokens visible There are 175 integrations available today, spanning developer tools, communication, CRM, data, and more. Browse them all in the [integration catalog](/integrations/catalog), or read the [integrations overview](/integrations/overview) for what they can do. A tool usually only becomes available once you have connected its integration. Add a credential for that service and its actions appear for use in workflows and the Assistant. See [Managing credentials](/integrations/managing-credentials). Yes. With bring-your-own-key (BYOK) you supply your own credential for a provider, and that usage is billed by the provider directly — it is not charged in ModuleX credits. ModuleX-managed keys are the alternative and are metered in credits. See [Authentication & credentials](/integrations/authentication) and [Credits & metering](/billing/credits). Credentials are encrypted at rest with a key scoped to your organization and the specific credential, so they cannot be reused outside it. Secrets are decrypted only when a tool runs, and the API returns masked values (for example `start***end`) rather than the raw secret. See [Data security & encryption](/security/data-encryption) and [Credentials & OAuth2](/concepts/credentials-oauth). For most providers, OAuth tokens refresh automatically just before they expire, so you do not need to do anything. If a connection breaks, reconnect the credential — remove it and connect the service again. The manual "refresh" action in the app is a [known limitation](/help/known-limitations) and may not work, so prefer reconnecting. See [Managing credentials](/integrations/managing-credentials). Yes, but not from inside the app yet. You add a custom integration by packaging it as code (a tool package the runtime discovers) or by connecting a [custom MCP server](/integrations/building/custom-mcp). See [Build an integration](/integrations/building/overview) to get started. Connecting, testing, and removing credentials all require an owner or admin role on the organization. If a teammate cannot add or see a credential, check their role under [Account & access](/help/account-access). ## Related help Connect a provider key for BYOK embeddings or an external vector store. The OAuth refresh button and other things to be aware of. What a `401`, `402`, or `403` means and how to clear it. --- # Errors & troubleshooting — help URL: https://docs.modulex.dev/help/errors-troubleshooting What the common ModuleX error codes mean — 401, 402, 403, 404, 429, and 500 — and how to fix them, plus how to debug a failed node or tool. Most errors fall into a handful of codes, and each one has a clear fix. This page explains what the common codes mean in plain terms and what to do about them. For the full technical reference, follow the link in each answer. The complete reference: every status code and the response shapes behind it. The admission gate and the `402` / `403` / `429` responses it returns. ## Errors & troubleshooting Here is the short version: - `401` — you are not signed in, or your API key or token is invalid. - `402` — your organization is out of credits (payment required). - `403` — you do not have permission, or you are calling the wrong organization. - `404` — the thing you asked for does not exist, or it is not in your organization. - `429` — too many requests, or a usage limit was hit. - `500` — something went wrong on our side. For the precise meaning and response body of each code, see [errors & status codes](/api-reference/errors). Your API key or token is missing or invalid. API keys start with `mx_live_` and go in the `Authorization: Bearer` header, alongside the `X-Organization-ID` header. Check the value is correct and not expired — see [authentication](/api-reference/authentication). Either you do not have the right role for that action, or your key is scoped to a different organization than the one you are calling. Building or running with Composer, the Assistant, and schedules needs an owner or admin role; see [roles & permissions](/security/roles-permissions). A `403` on a run, Composer, Assistant, or managed-knowledge request can also mean a plan limit was reached — see [usage gating & limits](/billing/usage-gating). Your organization is out of credits, so the request was blocked with a "payment required" response. Top up your [wallet](/billing/wallet), turn on overage, or upgrade your plan in [plans & pricing](/billing/plans). For what counts as managed usage, see [credits & metering](/billing/credits). You have hit a rate limit, or a usage limit on a run. Wait for the number of seconds given in the `Retry-After` header on the response, then retry. See [rate limiting](/api-reference/rate-limiting). Open the run in your run history and look at the failed node's error message. The usual causes are a missing or expired credential, bad input passed into the node, or the external service returning its own error. To reconnect a credential, see [credentials & integrations](/help/credentials-integrations); for retry behavior in a workflow, see [error handling & retries](/workflow-builder/error-handling-retries). **🎬 MEDIA PLACEHOLDER · MX-MEDIA-4590 · [SCREENSHOT]** **[SCREENSHOT_DESCRIPTION]:** the run history view with a failed node expanded to show its error message **[SCREENSHOT_DETAILS]:** capture the run detail / run history page in the ModuleX app for a run that has one failed node; expand the failed node so its error message and inputs are visible; light mode; 16:9; no real customer data ## A note on 402, 403, and 429 These three codes mean different things depending on where they come from, so it helps to know which is which. On the **run, Composer, Assistant, and managed-knowledge** surfaces, a `402`, `403`, or `429` from a usage limit returns a flat response that tells you exactly which limit was hit — for example `{code, layer, key, current, limit, reason}`. Plain create/read/update/delete and settings requests instead return the simpler `{detail}` message shape. The full breakdown of these response shapes is in [errors & status codes](/api-reference/errors). ## Debug a failed request from code When you call ModuleX from the API or an SDK, every request uses `Authorization: Bearer mx_live_…` together with the `X-Organization-ID` header. If a request fails, read the HTTP status code and the response body — the SDKs raise typed errors you can catch so you can branch on the status and the included fields. ```bash cURL # Add -i to see the status line and headers (such as Retry-After on a 429) curl -i -X POST https://api.modulex.dev/workflows/run \ -H "Authorization: Bearer mx_live_your_api_key" \ -H "X-Organization-ID: org_your_org_id" \ -H "Content-Type: application/json" \ -d '{ "workflow_id": "wf_29ab83c1", "input": { "topic": "release notes" } }' ``` ```python Python from modulex import Modulex from modulex import RateLimitError, BillingError, AuthenticationError, ModulexError client = Modulex( api_key="mx_live_your_api_key", organization_id="org_your_org_id", ) try: run = await client.executions.run( workflow_id="wf_29ab83c1", input={"topic": "release notes"}, ) except AuthenticationError: print("401 — check your API key and organization id") except RateLimitError as err: print(f"429 — retry after {err.retry_after} seconds") except BillingError as err: # 402 / 403 / 429 usage limit — err.code and err.layer say which one print(f"Usage limit hit: {err.code} ({err.layer})") except ModulexError as err: print(f"Request failed with status {err.status_code}") ``` ```javascript JavaScript import { Modulex } from "modulex-js"; import { AuthenticationError, RateLimitError, ModulexError } from "modulex-js"; const client = new Modulex({ apiKey: "mx_live_your_api_key", organizationId: "org_your_org_id", }); try { const run = await client.executions.run({ workflowId: "wf_29ab83c1", input: { topic: "release notes" }, }); } catch (err) { if (err instanceof AuthenticationError) { console.log("401 — check your API key and organization id"); } else if (err instanceof RateLimitError) { console.log(`429 — retry after ${err.retryAfter} seconds`); } else if (err instanceof ModulexError) { // 402 / 403 usage limits expose .code, .layer, and .reason on the error console.log(`Request failed (${err.status}): ${err.code ?? "error"}`); } } ``` The SDK error classes differ slightly between languages — for example, the Python SDK has a dedicated billing error family that the JavaScript SDK folds into its base error. See [errors & status codes](/api-reference/errors) for the exact mapping. ## Related help Signing in, organizations, and the roles behind a `403`. What it costs, what uses credits, and how to clear a `402`. Reconnect an expired credential behind a failed tool or node. Behaviors to be aware of so you do not chase a non-bug. --- # Getting help — Help URL: https://docs.modulex.dev/help/getting-help Reach a human at ModuleX: how to contact support, what to include in your message, where to find the community, and how to check whether the platform is up. When something is not working — or you just want a person to look at it — this page tells you who to contact, what to include so they can help fast, and how to check whether the issue is on ModuleX's side. Email the team. Include your organization ID, what you were doing, and any error code. See whether the platform and its health checks are up before you report a problem. Ask questions and share workflows with other ModuleX users. Found a gap or want to add an integration? Here is how to help. ## Frequently asked questions Email **support@modulex.dev**. To get help fast, include three things: your **organization ID**, **what you were doing** when it happened, and **any error code** you saw (for example `402` or `403`). See [what to include](#what-should-i-include-in-a-support-request) below for the full list, and [Errors & troubleshooting](/help/errors-troubleshooting) to decode an error code first. Your organization ID, a short description of what you were doing, and any error code or message. The more specific you are, the faster the team can reproduce it. See [the checklist](#what-should-i-include-in-a-support-request) below. It is the value you send in the `X-Organization-ID` header on API calls, and it is shown in the app's organization settings. Learn how the org context works in [Org context & X-Organization-ID](/security/org-context). ModuleX exposes a public liveness check at `GET /system/health`, documented on the [System status & health](/reference/status) page, so you can confirm the API is up. A hosted status / incident page (with uptime history and live incident updates) is not confirmed yet — see [Is there a hosted status or incident page?](#is-there-a-hosted-status-or-incident-page) below. Support depends on your plan: Free uses the community, Pro adds Discord, Max adds email plus Discord, and Enterprise includes dedicated support. See [Support by plan](#what-support-do-i-get-on-my-plan) below, and [Plans & pricing](/billing/plans) for the full comparison. Join the ModuleX Discord at `discord.gg/jEyCABwU9E` to ask questions and share workflows. You can also open issues on GitHub at `github.com/ModuleXAI`. More detail in [Where can I ask the community?](#where-can-i-ask-the-community) below. File it on GitHub at `github.com/ModuleXAI`, or email **support@modulex.dev** if it involves your account or data. If you want to fix or add something yourself, see [Contributing](/resources/contributing). A few flows are documented gaps rather than bugs (for example, deleting several nodes at once, or the "refresh OAuth" button). Check [Known limitations](/help/known-limitations) before you file a report — your issue may already be listed with a workaround. ## How do I contact support? Email **support@modulex.dev**. A clear first message saves a round trip — include the details below. Support is a paid-plan benefit. Free accounts are pointed to the [community channels](#where-can-i-ask-the-community); paid plans add direct support. See [Support by plan](#what-support-do-i-get-on-my-plan). ### What should I include in a support request? The ID of the organization you were working in — the same value you send as the `X-Organization-ID` header. It tells the team exactly which account and data to look at. See [Org context & X-Organization-ID](/security/org-context). A short, concrete description: the action you took, the workflow or chat involved, and what you expected to happen versus what actually happened. Copy the exact error code (for example `402`, `403`, or `429`) and any message text. If you are not sure what it means, [Errors & troubleshooting](/help/errors-troubleshooting) explains the common ones first. The approximate date and time, and whether it is still happening or was a one-off. This helps the team line your report up with logs. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-4610 · [SCREENSHOT]** **[SCREENSHOT_DESCRIPTION]:** where to find your organization ID in the app's organization settings. **[SCREENSHOT_DETAILS]:** capture the organization settings screen with the organization ID field visible and labelled; light mode; redact any real ID with a placeholder such as `org_…`; highlight the ID field with a callout. 16:9. ## Is there a status page? To check whether the platform itself is up, use the health endpoints documented on the [System status & health](/reference/status) page — `GET /system/health` is a public liveness check that returns the service status without authentication. ### Is there a hosted status or incident page? A hosted status / incident page (uptime history, live incident posts) is **not confirmed** in the current sources. Until one is published, use the [health endpoints](/reference/status) to verify the API is reachable, then contact [support](#how-do-i-contact-support) if it is up but your action still fails. **TBD** — this page will link to a hosted status page once one exists. ## What support do I get on my plan? The support channel scales with your plan. Confirm the current details on [Plans & pricing](/billing/plans). | Plan | Support channel | | --- | --- | | Free | Community (Discord, GitHub) | | Pro | Discord | | Max | Email + Discord | | Enterprise | Dedicated support | Enterprise teams also get hands-on onboarding and a dedicated support contact. See [ModuleX for enterprise](/enterprise/overview) and [Contact sales](/enterprise/contact-sales). ## Where can I ask the community? For questions, ideas, and sharing workflows, the community is the fastest place to start — and the primary support channel on the Free plan. Chat with other ModuleX users and the team at `discord.gg/jEyCABwU9E`. Report bugs, request features, and browse the code at `github.com/ModuleXAI`. ## Before you reach out A quick self-check often resolves the issue faster than waiting for a reply. Common codes like `401`, `402`, `403`, and `429` each have a known cause and fix. Some behaviours are documented gaps with workarounds, not bugs. Use the health endpoints to rule out a platform-wide issue. --- # Getting started — help URL: https://docs.modulex.dev/help/getting-started Answers for people brand new to ModuleX: how to sign up, create your first API key, try the product, choose between the app and the API, and the free plan. New to ModuleX? This page answers the first questions most people have — how to sign up, where your API key comes from, the fastest way to try things, and what the free plan includes. Each answer links to a fuller guide if you want the details. Sign in, create an API key, and make your first authenticated call. Run a workflow from code and stream the result. See what the Free, Pro, Max, and Enterprise plans include. Account, billing, workflows, integrations, and troubleshooting. ## Common questions You sign in through Clerk, the identity provider ModuleX uses. The first time you sign in, ModuleX creates your account and a personal starter organization for you automatically, so you always have somewhere to work right away. See the [quickstart](/get-started/quickstart) for the full first-run walkthrough. Open your account's API keys settings and create a key. It starts with `mx_live_` and the full secret is shown only once, at creation time, so copy it immediately and store it somewhere safe. ModuleX keeps only a hashed copy and a short hint, so a lost key cannot be recovered — you delete it and make a new one. Step-by-step instructions are in the [quickstart](/get-started/quickstart). **🎬 MEDIA PLACEHOLDER · MX-MEDIA-4500 · [SCREENSHOT]** **[SCREENSHOT_DESCRIPTION]:** The API keys settings screen in the ModuleX app, showing the create-key flow and a newly generated key displayed once. **[SCREENSHOT_DETAILS]:** Capture the account API-keys settings page. Show the "Create API key" form (name field, optional organization scope, optional expiry, rate-limit field) and the success state where the full `mx_live_*` key appears once with a copy button. Redact the real secret. Light mode, 16:9, annotate the "copy now — shown once" callout and the masked hint (`mx_live_2J9vK4xM********`). Two fast paths: run the [quickstart](/get-started/quickstart) to make your first authenticated request, or [make your first API call](/get-started/first-api-call) to run a workflow and stream its result. If you would rather click than code, open the workflow builder in the app and run a sample workflow. Use whichever fits the task — they all reach the same backend. Build visually in the web app, or do the same things from code with the REST API or the JavaScript and Python SDKs. Many people build in the app and then automate with the [API](/get-started/first-api-call). Yes. The Free plan includes a one-time batch of 300 trial credits so you can try ModuleX before paying — no card required. Paid plans add a recurring monthly credit allowance: Pro includes 5,000 credits per month and Max includes 20,000 credits per month. Compare them on [plans & pricing](/billing/plans). Pro and Max can also be billed annually. The annual amount shown in marketing may differ from the amount charged at checkout, so confirm the exact yearly price at checkout. See [plans & pricing](/billing/plans). --- # Knowledge (RAG) — help URL: https://docs.modulex.dev/help/knowledge-rag Quick answers for using your own documents in ModuleX: set up a knowledge base, search it from a workflow, what it costs, indexing time, file types and limits, and deleting documents. Short answers to the questions people ask most about connecting your own documents to ModuleX so your chats and workflows can answer from them. For the full detail, follow the link in each answer. Manage the knowledge bases that power retrieval across chats and workflows. The mental model: ingest, embed, retrieve, and answer from your own content. Upload, watch processing, and remove documents and their chunks. A step-by-step walkthrough from empty base to first answer. ## Knowledge (RAG) Create a knowledge base in the app, then upload documents to it. Each file is parsed, split into chunks, and indexed automatically — no setup beyond the upload. Creating and managing knowledge bases needs an owner or admin role. See [Knowledge overview](/platform/knowledge/overview) for the walkthrough. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-4550 · [SCREENSHOT]** **[SCREENSHOT_DESCRIPTION]:** the knowledge base screen in the ModuleX app, with the create-base action and an uploaded document list **[SCREENSHOT_DETAILS]:** capture the Knowledge area in the ModuleX app for an org that has one knowledge base with a few documents in mixed states; show the create-knowledge-base button and a document showing its processing status; light mode; 16:9; no real customer data Add a [Knowledge node](/workflow-builder/nodes/knowledge), pick the knowledge base it should search, and give it a search query. The node returns the matching passages so a later node can answer from them. You can also ask questions in [chat with your knowledge](/platform/chat/knowledge-chat). **🎬 MEDIA PLACEHOLDER · MX-MEDIA-4551 · [SCREENSHOT]** **[SCREENSHOT_DESCRIPTION]:** a Knowledge node selected on the workflow canvas with its knowledge base and query fields shown **[SCREENSHOT_DETAILS]:** capture the workflow builder in the ModuleX app with a Knowledge node selected; show the detail panel where you pick the knowledge base and enter the search query; light mode; 16:9; no real customer data For ModuleX-managed knowledge, yes — each search and each document you add draws down credits (one credit each, plus the model cost of embedding the text). If you bring your own embedding key (BYOK), that usage is billed by your provider and is not charged in ModuleX credits. See [Credits & metering](/billing/credits). A newly uploaded document has to finish indexing before it can be searched — it moves from `pending` to `processing` to `completed`. Until it reaches `completed`, its content will not show up in results. Once indexing finishes, later searches are fast. You can watch each document's status in [Managing documents](/platform/knowledge/documents). Common document types are supported: `pdf`, `docx`, `doc`, `txt`, `md`, `html`, `csv`, `json`, `xlsx`, and `pptx`. The per-file size cap and the number of knowledge bases you can create depend on your plan, not a single fixed limit — see [Managing documents](/platform/knowledge/documents) and [Plans & pricing](/billing/plans). Yes — remove a document from its knowledge base and its indexed chunks are deleted with it. Deleting is permanent, so the content stops appearing in results right away. See [Managing documents](/platform/knowledge/documents). Knowledge bases, documents, and search all require an owner or admin role on the organization. If a teammate cannot see or change a knowledge base, check their role under [Account & access](/help/account-access). ## Related help Connect a provider key for BYOK embeddings or external vector stores. What managed search and ingest cost, and how usage is measured. What a `402` or `403` means and how to clear it. --- # Known limitations — help URL: https://docs.modulex.dev/help/known-limitations Plain-language answers about the things to know in ModuleX today: deleting one node at a time, templates not yet available, reconnecting OAuth, owner/admin-only features, the annual price at checkout, and empty install extras. A few things in ModuleX work differently than you might expect, or are not ready yet. None of them block your work — each has a simple thing to do instead. This page answers the questions people ask most, in plain language. Looking for the full developer reference, with the reason behind each item and the supported workaround? See [Known limitations](/reference/known-limitations). Multi-select delete can miss some nodes — remove them one by one. The template gallery isn't shipped — build directly or use the Composer. If a connection breaks, disconnect and reconnect it. Composer, the Assistant, and schedules require an owner or admin. The yearly amount can differ — check the total at checkout. `pip` extras add nothing yet — install the base package. ## Common questions Selecting several nodes on the canvas and deleting them in one go can leave some of them behind. Delete nodes **one at a time** and confirm each one is gone before removing the next. The full explanation is in the [known limitations reference](/reference/known-limitations), and you can learn more about co-editing in [realtime co-editing & external sync](/workflow-builder/realtime-coediting). **🎬 MEDIA PLACEHOLDER · MX-MEDIA-4600 · [SCREENSHOT]** **[SCREENSHOT_DESCRIPTION]:** The workflow builder canvas showing several nodes selected, with a caption that only one was removed after a multi-delete. **[SCREENSHOT_DETAILS]:** Capture in the live ModuleX builder. Show 3 nodes multi-selected (selection outline) in one frame and a second frame after a delete where 2 remain. Annotate "delete one node at a time". Dark mode, 16:9. Templates aren't available yet — the gallery isn't shipped, so don't plan a workflow around it. Build your workflow directly in the [workflow builder](/workflow-builder/overview), or describe what you want and let [AI Composer in the builder](/workflow-builder/composer) generate it for you. See the [known limitations reference](/reference/known-limitations) for the canonical status. The in-app button that refreshes an OAuth connection can fail. When a connection breaks, **disconnect the credential and reconnect it** — reconnecting runs the sign-in flow again and issues fresh tokens. See [credentials & integrations help](/help/credentials-integrations) for the reconnect steps and the [known limitations reference](/reference/known-limitations) for why this happens. These features need an **owner or admin** — a regular member can't run them (the `member` role has been retired). Promote your teammate to owner or admin, or run the action from an owner/admin account. For the full permission model, see [roles & permissions](/security/roles-permissions) and [account & access help](/help/account-access). The yearly price shown in marketing can differ from the amount you're actually charged, so always **confirm the total on the checkout page** before subscribing annually. Monthly prices are consistent; only the annual figure can disagree. See [plans & pricing](/billing/plans) and [billing, plans & credits help](/help/billing-credits) for both figures side by side. Not yet — `pip` extras such as `modulex-integrations[all]` or `modulex-integrations[github,slack]` are empty and add nothing beyond the core package. Install the base package, then add any extra dependencies a specific tool needs yourself. See [installing & using integrations](/integrations/install) and the [known limitations reference](/reference/known-limitations) for details. ```bash Do this # Install the base package (core dependencies only) pip install modulex-integrations ``` ```bash Not this # These extras are empty / undefined today — they warn and add nothing pip install "modulex-integrations[all]" pip install "modulex-integrations[github,slack]" ``` Hit something that isn't here? Check [errors & troubleshooting help](/help/errors-troubleshooting) first, then [get in touch](/help/getting-help). --- # Realtime collaboration — help URL: https://docs.modulex.dev/help/realtime-collaboration Answers to common questions about co-editing a ModuleX workflow with your team: live cursors and presence, edit conflicts and how to re-sync, locking a node, and what happens to your locks when you disconnect. Work on the same workflow as your teammates, at the same time. This page answers the questions people ask most about live co-editing in the [Workflow Builder](/workflow-builder/overview). If you want the full picture, two pages go deeper: The friendly tour of live co-editing: cursors, presence, node locks, and automatic saving. The reference behind it: every realtime event, payload, timing, and edge case. ## Frequently asked questions Yes. When two or more people open the same workflow, everyone edits the same live canvas at once — you see each other's cursors move, watch nodes appear and connect, and know who is working where, each person in their own color. Editing is available to the **owner** and **admin** roles; the `member` role is retired and read-only. See [Co-edit a workflow canvas](/platform/collaboration/canvas). Your view fell too far behind the latest version of the workflow — usually after a burst of edits or a change made outside the canvas (for example by the [AI Composer](/concepts/ai-composer) or the API). When that happens, leave and re-join the workflow to re-sync with the current state, then continue editing. See [Presence, locks & versioning](/realtime/presence-locks). Yes. Lock a node and you become its holder; everyone else sees it as held by you and cannot edit it until you release it. A lock also releases on its own after about five minutes of inactivity, so a node is never stuck. See [Presence, locks & versioning](/realtime/presence-locks). Any node locks you were holding are released automatically and your presence disappears for everyone else. Nothing you saved is lost — edits are saved in the background as you work. Just re-open the workflow to rejoin and continue. See [Co-edit a workflow canvas](/platform/collaboration/canvas). Co-editing is a feature of the **app** — it works automatically whenever two people open the same workflow, with nothing to turn on and no API or SDK call involved. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-4570 · [APP_VIDEO]** **[APP_VIDEO_DESCRIPTION]:** Short walkthrough answering the four collaboration FAQs in order: co-editing, an edit conflict and re-sync, locking a node, and a disconnect releasing locks. **[APP_VIDEO_DETAILS]:** Side-by-side screen recording of two browser sessions (User A "Ada" and User B "Lin") with the same workflow open. Show, in order: (1) B's presence avatar and colored cursor appearing on A's canvas as both edit; (2) A getting an edit conflict and re-joining the workflow to re-sync to the current state; (3) B locking a node and A seeing a "being edited by Lin" badge, then B releasing it; (4) B's tab closing and B's locks and presence clearing on A's canvas. 16:9, light theme, ~30s, annotate the presence avatars, the conflict/re-sync moment, and the lock badge. ## Related help Building, running, and watching workflows. Organizations and the owner/admin roles that can edit. --- # Workflows & runs — help URL: https://docs.modulex.dev/help/workflows-runs Answers to common questions about building and running workflows in ModuleX: the nine node types, passing data with node references, pausing for input, cancelling a run, and watching a run live. A workflow is a graph of steps you connect on a canvas; a run is one execution of that workflow. This page answers the questions people ask most often when building and running workflows. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-4530 · [APP_VIDEO]** **[APP_VIDEO_DESCRIPTION]:** Building and running a workflow end to end in the Workflow Builder. **[APP_VIDEO_DETAILS]:** Screen recording of the live ModuleX app. Show adding two or three nodes to the canvas, connecting them, saving, deploying, then clicking Run and watching the live run stream. End on a completed run. 60-90s, 16:9, light mode, with short caption annotations on each step. ## Quick links Where you build workflows on the visual canvas. The nine node types and what each one does. ## Common questions Open the [Workflow Builder](/workflow-builder/overview), add nodes to the canvas, connect them in the order you want them to run, then save and deploy. Click Run to execute it in the app, or trigger it from the API. See [running workflows](/workflow-builder/execution/running) for the in-app run view and [run via API](/workflow-builder/execution/api-endpoint) to trigger one from code. There are nine node types: LLM, Tool, Function, Agent, Conditional, Interrupt, Transformer, Guardrails, and Knowledge. Each one does a different kind of work in a run. The [node types overview](/workflow-builder/nodes/overview) explains what each does and when to use it. Reference an earlier node's output with the `{{node_id.field}}` syntax — for example, `{{search.results}}` pulls the `results` value from the node with the id `search`. Every node writes its result into the run under its own id, so any later node can read it. See [variables & references](/workflow-builder/variables-and-references). Yes. Add an Interrupt node where you want the run to wait. When the run reaches it, the run pauses, asks your question, and waits for an answer before continuing. See the [Interrupt node](/workflow-builder/nodes/interrupt). Cancel it from the run view in the app, or call the cancel endpoint from code. Cancellation is graceful: the run stops after the current node finishes, not mid-step. Only runs that are still running or paused can be cancelled. See [running workflows](/workflow-builder/execution/running). Stream it. In the app, the run view shows each step as it happens. From code, open the run's event stream — in the SDKs this is the `listen()` method, which yields events as they occur until the run finishes. See [running workflows](/workflow-builder/execution/running). ## Run a workflow from code When you trigger a run, ModuleX returns immediately with the run details, then you open a separate stream to watch progress. Authenticate every request with `Authorization: Bearer mx_live_…` and the `X-Organization-ID` header. In the SDKs, pass your API key and organization id when you create the client. ```bash cURL # 1. Start the run — returns immediately with a run_id curl -X POST https://api.modulex.dev/workflows/run \ -H "Authorization: Bearer mx_live_your_api_key" \ -H "X-Organization-ID: org_your_org_id" \ -H "Content-Type: application/json" \ -d '{ "workflow_id": "wf_29ab83c1", "input": { "topic": "release notes" } }' # 2. Watch it live over the event stream curl -N https://api.modulex.dev/workflows/listen/run_7k8l9m0n \ -H "Authorization: Bearer mx_live_your_api_key" \ -H "X-Organization-ID: org_your_org_id" # 3. Cancel it (graceful — stops after the current node) curl -X POST https://api.modulex.dev/workflows/cancel/run_7k8l9m0n \ -H "Authorization: Bearer mx_live_your_api_key" \ -H "X-Organization-ID: org_your_org_id" \ -H "Content-Type: application/json" \ -d '{ "reason": "User requested cancellation" }' ``` ```python Python from modulex import Modulex client = Modulex( api_key="mx_live_your_api_key", organization_id="org_your_org_id", ) # 1. Start the run run = await client.executions.run( workflow_id="wf_29ab83c1", input={"topic": "release notes"}, ) # 2. Watch it live — listen() yields events until the run finishes async for event in client.executions.listen(run.run_id): print(event["type"]) # 3. Cancel it (graceful) await client.executions.cancel(run.run_id, reason="User requested cancellation") ``` ```javascript JavaScript import { Modulex } from "modulex-js"; const client = new Modulex({ apiKey: "mx_live_your_api_key", organizationId: "org_your_org_id", }); // 1. Start the run const run = await client.executions.run({ workflowId: "wf_29ab83c1", input: { topic: "release notes" }, }); // 2. Watch it live — listen() yields events until the run finishes for await (const event of client.executions.listen(run.runId)) { console.log(event.type); } // 3. Cancel it (graceful) await client.executions.cancel(run.runId, { reason: "User requested cancellation" }); ``` Running a workflow on ModuleX-managed models and tools consumes credits, and the run is blocked if your organization is out of credits. See [usage gating & limits](/billing/usage-gating) and [errors & troubleshooting](/help/errors-troubleshooting). ## Related help Generate a workflow from a plain-English prompt, or let the Assistant work step by step. What common errors mean and how to fix a failed run. --- # ModuleX documentation URL: https://docs.modulex.dev/ Build, run, and automate AI workflows with ModuleX — the no-code canvas, AI Composer, managed knowledge (RAG), 175 integrations, and a developer API with JavaScript and Python SDKs. ModuleX is an AI workflow orchestration platform. You describe a process in plain language or draw it on a visual canvas, connect the tools and data you already use, and run it with any major AI model — from chat, the API, or an SDK. There are two ways to work with ModuleX, and you can mix them freely. Build workflows on a collaborative visual canvas, generate them from a prompt with the [AI Composer](/concepts/ai-composer), and chat with the agentic [Assistant](/concepts/assistant) — no engineering required. Run the same workflows from your own software with the REST API or the official [JavaScript](/sdks/javascript) and [Python](/sdks/python) SDKs. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-1000 · [APP_VIDEO]** **[APP_VIDEO_DESCRIPTION]:** A 60-second product tour of the ModuleX app, from a prompt to a running workflow. **[APP_VIDEO_DETAILS]:** Screen recording of the live app. Show: (1) opening the AI Composer and typing a plain-English request, (2) the Composer laying out nodes on the canvas, (3) a teammate's cursor co-editing the same canvas, (4) running the workflow and watching the live stream of node updates. ~60s, 16:9, dark theme to match the docs, no audio narration required, light captions for each step. ## Start here Pick the path that matches how you want to begin. Create an account, get an API key, and make your first authenticated call. Build a workflow in the canvas or with the Composer, then run it from the app. Trigger a workflow from code in cURL, Python, or JavaScript. ## Explore the docs The mental model behind ModuleX: workflows and runs, the workflow engine and its nodes, the Composer and Assistant, knowledge and RAG, organizations, and credits. The request lifecycle, authentication, errors, and every operation shown once in cURL, Python, and JavaScript — plus a live endpoint playground. Connect ModuleX to 175 services and call their tools from workflows and the Assistant. A tour of the app: chat, the workflow builder, knowledge bases, realtime collaboration, and the agentic Assistant. ## What you can do with ModuleX The [AI Composer](/concepts/ai-composer) turns a plain-English description into an editable workflow graph — steps, tools, and logic laid out on the canvas for you to refine. Multiple people can edit the same [workflow builder](/workflow-builder/overview) canvas at once, with live cursors, presence, and node locks. Connect documents to a [knowledge base](/platform/knowledge/overview) and let workflows and chat retrieve the right context automatically with RAG. The [Assistant](/assistant/overview) is an agentic chat that searches your connected tools, drafts outputs, and pauses for approval before sensitive actions — no workflow required. ## Run a workflow from code Every workflow you build in the app can be triggered programmatically. Each request authenticates with an API key in the `Authorization` header and selects your organization with the `X-Organization-ID` header. ```bash cURL curl https://api.modulex.dev/workflows/run \ -H "Authorization: Bearer mx_live_4f8c2a1b9e7d6c5b4a3f2e1d0c9b8a7f" \ -H "X-Organization-ID: org_3a7f19e2c4b8" \ -H "Content-Type: application/json" \ -d '{ "workflow_id": "wf_9c1d4e7a2b6f", "input": { "topic": "quarterly sales summary" }, "stream": true }' ``` ```python Python import asyncio from modulex import Modulex async def main(): client = Modulex( api_key="mx_live_4f8c2a1b9e7d6c5b4a3f2e1d0c9b8a7f", organization_id="org_3a7f19e2c4b8", ) run = await client.executions.run( workflow_id="wf_9c1d4e7a2b6f", input={"topic": "quarterly sales summary"}, stream=True, ) print(run.run_id, run.status) await client.close() asyncio.run(main()) ``` ```javascript JavaScript import { Modulex } from 'modulex-js'; const client = new Modulex({ apiKey: 'mx_live_4f8c2a1b9e7d6c5b4a3f2e1d0c9b8a7f', organizationId: 'org_3a7f19e2c4b8', }); const run = await client.executions.run({ workflowId: 'wf_9c1d4e7a2b6f', input: { topic: 'quarterly sales summary' }, stream: true, }); console.log(run.run_id, run.status); ``` Running a saved workflow requires an active deployment. Managed runs pass through ModuleX's billing gate and can return a `402`, `403`, or `429` denial when your plan's credits or limits are reached. See [errors & status codes](/api-reference/errors) and [usage gating & limits](/billing/usage-gating). For the full walkthrough — authenticate, run, and stream the result in three languages — see [Run a workflow (REST + SDK)](/guides/run-a-workflow). ## How billing works, in one line Managed usage is metered in **credits**: 100 credits equal $1.00. Each plan includes a monthly credit allowance, and paid organizations can add a prepaid wallet for usage beyond it. If you bring your own model and tool keys (BYOK), that usage is billed directly by the provider and is not credited. See [billing & credits overview](/billing/overview) and [plans & pricing](/billing/plans). ## Get help Short answers for getting going, using ModuleX, and troubleshooting. Canonical ModuleX terminology in one place. Documented gaps and paths you should not rely on yet. --- # Integration authentication & credentials URL: https://docs.modulex.dev/integrations/authentication How ModuleX integrations authenticate: the six auth schema variants, the OAuth2 PKCE connect flow, encryption at rest, and how credentials are resolved at run time. Every integration in ModuleX needs a way to prove who it is to the service it calls. An integration declares the auth methods it supports in its manifest; you supply the secret once, ModuleX encrypts it, and the runtime resolves and decrypts it on every tool call. This page is the exhaustive reference for that contract: the six auth schema variants a manifest can expose, the OAuth2 authorization-code flow with PKCE, how secrets are encrypted at rest, and the precedence rules that pick a credential at run time. For the conceptual model and a guided OAuth walkthrough, see [Credentials & OAuth2](/concepts/credentials-oauth). To create, set defaults for, test, and delete credentials in the app and over the API, see [Managing credentials](/integrations/managing-credentials). A credential is **organization-scoped**, never user-scoped. Every credential belongs to one organization and is encrypted, resolved, and billed against that org. All credential endpoints require the **owner** or **admin** role (`organization_admin_required`); the `member` role is retired. The org is selected by the `X-Organization-ID` header on every request — see [Org context & X-Organization-ID](/security/org-context). ## The six auth schema variants An integration manifest exposes one or more **auth schemas** under `auth_schemas`. The list is a discriminated union keyed on `auth_type`, so each entry is exactly one of six variant classes. A single integration may ship several (GitHub ships both OAuth2 and a bearer-token personal-access-token option; Exa ships both an API key and a ModuleX-managed key). | `auth_type` | Variant class | What you supply | Typical use | | --- | --- | --- | --- | | `oauth2` | `OAuth2AuthSchema` | Nothing directly — you complete a consent flow | GitHub, Google Calendar, Netlify | | `bearer_token` | `BearerTokenAuthSchema` | A long-lived token (e.g. a personal access token) | GitHub PAT | | `api_key` | `ApiKeyAuthSchema` | A provider-issued API key | Exa, ConvertAPI, Hunter, Freshdesk | | `modulex_key` | `ModulexKeyAuthSchema` | Nothing — ModuleX supplies a managed key, billed in credits | Exa (managed), Firecrawl, Jina AI, Hacker News | | `custom` | `CustomAuthSchema` | Free-form fields defined by the integration | PostgreSQL, WooCommerce, Coinbase | | `internal` | `InternalAuthSchema` | Reserved — not supplied by you | System-managed only | `internal` is a **reserved/forward** variant. It is a valid `auth_type` and a defined schema class, but no shipped integration in the catalog uses it, and `internal` credentials are excluded from credential listings (they back system-managed resources such as the managed knowledge store). Treat it as read-only and do not author manifests against it. There is one casing edge case to know about. The integration manifest schema defines **six** `auth_type` values. The backend credential table's CHECK constraint allows **seven** — it additionally accepts a legacy `bearer` value with no corresponding manifest variant. New integrations must emit only the six schema variants above; `bearer` is a legacy database value, not a manifest option. ### Shared fields on every variant Every auth schema, regardless of `auth_type`, inherits the same base fields. Human-readable label shown in the connect UI (e.g. `"OAuth2 Authentication"`). One-line explanation of the method shown next to the label. An ordered list of steps shown to the person connecting the integration (e.g. how to create a personal access token). Operator- or user-supplied secrets and settings for this method. See [`EnvVar`](#envvar-operator-or-user-supplied-values) below. An optional HTTP call the runtime makes to validate a configured credential. Some `modulex_key` public-API integrations ship none, in which case credential testing is skipped. See [`TestEndpoint`](#testendpoint-credential-validation). ### OAuth2 variant (`oauth2`) `OAuth2AuthSchema` adds a single extra field, `oauth_config`, that describes the authorization-code flow. The provider's authorization endpoint the user is redirected to (e.g. `https://github.com/login/oauth/authorize`). The provider's token-exchange endpoint where the authorization code is swapped for tokens (e.g. `https://github.com/login/oauth/access_token`). OAuth scopes requested at consent (e.g. `["repo", "user", "read:org", "workflow"]`). A connect request may override these with its own `scope` string. How the client credentials are presented at token exchange. `body` sends `client_id`/`client_secret` in the form body (RFC 6749 `client_secret_post`); `basic` sends them as HTTP Basic auth (used by providers such as Notion). Extra authorize parameter for providers that need it. Google requires `"offline"` to issue a `refresh_token`. Extra authorize parameter. `"consent"` re-issues a refresh token on every reconnect (Google). Whether to use PKCE (RFC 7636, S256). On by default. A manifest can set this to `false` for providers that reject an unexpected `code_verifier` (Netlify does). **PKCE-opt-out (`use_pkce: false`) may not be honored end to end.** The manifest field exists, but the runtime is documented as currently hardcoding PKCE on, so a manifest that sets `use_pkce: false` may still send a `code_verifier`. This is an [open question](/reference/known-limitations) pending verification against the live runtime — do not rely on opting out of PKCE until it is confirmed wired. For providers that accept PKCE (the large majority), the default behavior is correct. ### Bearer-token variant (`bearer_token`) `BearerTokenAuthSchema` adds no extra fields. You supply a long-lived token (e.g. a GitHub personal access token). The runtime sends it as `Authorization: Bearer ` to the service. On the wire the stored `auth_type` is `bearer_token`. ### API-key variant (`api_key`) `ApiKeyAuthSchema` adds no extra fields. You supply a provider-issued API key. How the key is presented (header name, query parameter, etc.) is defined by the integration's `test_endpoint` and its tool functions, not by a fixed convention. ### ModuleX-managed-key variant (`modulex_key`) `ModulexKeyAuthSchema` adds no extra fields, and you supply **nothing** — ModuleX provisions a managed key from a pooled key store. Usage of a `modulex_key` credential is **metered in credits** and passes through the billing gate at run time (see [Resolution at run time](#how-credentials-are-resolved-at-run-time) below and [Credits & metering](/billing/credits)). This is the difference that matters: a `modulex_key` credential always carries a usage/credit gate; a credential you supply yourself never does. ### Custom variant (`custom`) `CustomAuthSchema` adds no extra fields at the schema level. The integration defines the fields it needs through `setup_environment_variables`, and the connect UI renders one input per declared `EnvVar`. PostgreSQL (host, port, database, user, password) is a typical example. ## `EnvVar` — operator- or user-supplied values Each auth schema can declare a list of `EnvVar` entries under `setup_environment_variables`. An `EnvVar` is a single secret or setting, and two of its flags — `only_for_custom` and `inject_into_auth_data` — decide where the value comes from and whether a tool function can read it. Env-var-style key (e.g. `GITHUB_OAUTH2_CLIENT_ID`). Label shown in the UI. Help text shown under the field. Whether the value must be supplied. Note this defaults to `true` — the opposite of `ParameterDef.required`, which defaults to `false`. Whether the value is a secret. Sensitive values are masked in the UI. `true` marks the value as a **server-level** secret: the ModuleX-managed app resolves it from the server environment, while a bring-your-own-app user supplies their own. Examples: a GitHub OAuth client id/secret, a Google Ads `developer_token`. `true` guarantees the value is present in `auth_data` at action-execution time so a tool function can read it (the key is prefix-stripped and lowercased). `false` (the default) means the value is used only for OAuth provider config and the test endpoint, and never reaches a tool call. Placeholder hint shown in the input (e.g. `"ghp_xxxx..."`). Link to where the user obtains the value. The interaction of the two flags determines handling: | `inject_into_auth_data` | `only_for_custom` | Behavior | | --- | --- | --- | | `false` (default) | any | OAuth/test-only; never reaches a tool function. | | `true` | `false` | Per-credential **user input**, persisted into `auth_data` at credential creation. | | `true` | `true` | **Server-level secret**; the managed app injects it from server env at resolution time; a BYO-app user supplies their own. | ## `TestEndpoint` — credential validation When an auth schema declares a `test_endpoint`, ModuleX can validate a credential by making one HTTP call before relying on it. Placeholders in the URL, headers, params, or body — such as `{access_token}`, `{token}`, `{api_key}`, `{bearer_token}`, or any `auth_data`/`EnvVar` key — are substituted by the runtime. The endpoint to call. May embed query placeholders (e.g. `?Secret={api_key}`). HTTP method for the validation call. Request headers, with placeholder substitution (e.g. `{"Authorization": "Bearer {access_token}"}`). URL query parameters, for query-parameter credentials. JSON payload for non-GET validation calls. Declarative HTTP Basic auth. When set, the runtime builds `Authorization: Basic ` itself and ignores any `Authorization` header you set. A placeholder that is not an `auth_data` key is treated as a literal string. What counts as success: a list of acceptable `status_codes` (e.g. `[200]`) and an optional list of `response_fields` that must appear in the response body (e.g. `["login", "id"]`). Free-form cost hint. Observed values include `"free"` and `"minimal"`; the schema does not constrain the set. Human-readable note describing what the validation checks. When you test a credential, the response reports `test_method`, which is one of `api_call` (an actual request was made), `basic`, or `none` (the integration ships no `test_endpoint`, so testing is skipped and the credential is treated as valid). See [Managing credentials](/integrations/managing-credentials) for the full test-and-save flow. ## How an integration reads the credential A tool function receives its credential through one of two run-time conventions, chosen by the auth type: - **Token-based** (`oauth2`, `bearer_token`) — the function signature leads with `auth_type: str, auth_data: dict[str, Any]`, then its action parameters. It builds request headers from `(auth_type, auth_data)`. - **Key-based** (`api_key`, `modulex_key`) — the function takes the action parameters plus `api_key: str` directly (often sent as `x-api-key: {api_key}`), and checks the key is non-empty before calling out. This is part of the `@tool` function contract; for the full decorator order, output models, and worked examples, see [The @tool function contract](/integrations/building/tool-contract) and [Manifest & schema contract](/integrations/building/manifest-schema). ## The OAuth2 connect flow (PKCE) OAuth2 integrations are connected through an authorization-code flow with PKCE. You never paste a token; ModuleX initiates the flow, the provider redirects the browser back, and ModuleX exchanges the code and stores the resulting credential. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-4020 · [IMAGE]** **[IMAGE_DESCRIPTION]:** Sequence diagram of the OAuth2 PKCE connect flow. **[IMAGE_DETAILS]:** Five lanes left to right — User/Browser, ModuleX app (frontend), ModuleX backend, Redis (5-min state store), OAuth provider. Show: (1) `POST /credentials/oauth2/initiate` returning `authorization_url` + `state`, with `code_verifier`/`code_challenge` (S256) generated and flow state written to Redis with a 5-minute TTL; (2) browser redirected to the provider, user consents; (3) provider redirects browser to `GET /credentials/oauth2/callback?code&state`; (4) backend one-time reads + deletes the Redis `state`, exchanges code at `token_url` (PKCE `code_verifier`), encrypts and stores the credential; (5) backend issues a 302 back to the frontend landing page with `?status=success&credential_id=`. Annotate the 5-minute TTL and the one-time state read. Light theme, 16:9. ### Step 1 — initiate `POST /credentials/oauth2/initiate` starts the flow. The backend loads the integration's `oauth2` schema, builds the authorization URL (generating a PKCE `code_verifier` and S256 `code_challenge`), stores the flow state in Redis with a **5-minute TTL**, and returns the URL for the browser to open. The integration to connect. Must expose an `oauth2` auth schema, or the request fails with `400`. `true` uses the ModuleX-registered OAuth app for the provider (the managed path). `false` uses your own OAuth app and requires `custom_oauth_config`. Required only when `use_modulex_oauth` is `false`. Must contain `client_id` and `client_secret` for your own OAuth app. The `auth_url`/`token_url` come from the integration schema. The callback URL the provider redirects to after consent. This is the ModuleX backend callback (e.g. `https://api.modulex.dev/credentials/oauth2/callback`). Space-separated scopes. Defaults to the provider/schema scopes joined by spaces. A label for the credential that gets created. Whether the new credential becomes the default for its integration. Per-user `EnvVar` values (string-to-string). Only values whose `EnvVar` has `inject_into_auth_data: true` and `only_for_custom: false` are folded into the persisted `auth_data`. Composer auto-resume linkage. Must be supplied together with `composer_request_id` and `composer_llm_config`; supplying some but not all returns `400`. Composer auto-resume linkage — see `composer_chat_id`. Composer auto-resume linkage — see `composer_chat_id`. Authenticate with a key or a Clerk JWT plus the org header, exactly as for any ModuleX API call (see [Authentication](/api-reference/authentication)): ```bash cURL curl -X POST https://api.modulex.dev/credentials/oauth2/initiate \ -H "Authorization: Bearer mx_live_abc123" \ -H "X-Organization-ID: 8f2c1d9e-0000-4a11-9c3d-2b6e7f4a1234" \ -H "Content-Type: application/json" \ -d '{ "integration_name": "github", "use_modulex_oauth": true, "redirect_uri": "https://api.modulex.dev/credentials/oauth2/callback", "display_name": "Production GitHub", "make_default": true }' # → {"authorization_url": "https://github.com/login/oauth/authorize?...", "state": ""} ``` ```python Python import os import httpx resp = httpx.post( "https://api.modulex.dev/credentials/oauth2/initiate", headers={ "Authorization": f"Bearer {os.environ['MODULEX_API_KEY']}", "X-Organization-ID": "8f2c1d9e-0000-4a11-9c3d-2b6e7f4a1234", }, json={ "integration_name": "github", "use_modulex_oauth": True, "redirect_uri": "https://api.modulex.dev/credentials/oauth2/callback", "display_name": "Production GitHub", "make_default": True, }, ) data = resp.json() # {"authorization_url": "...", "state": "..."} ``` ```javascript JavaScript const resp = await fetch( "https://api.modulex.dev/credentials/oauth2/initiate", { method: "POST", headers: { Authorization: `Bearer ${process.env.MODULEX_API_KEY}`, "X-Organization-ID": "8f2c1d9e-0000-4a11-9c3d-2b6e7f4a1234", "Content-Type": "application/json", }, body: JSON.stringify({ integration_name: "github", use_modulex_oauth: true, redirect_uri: "https://api.modulex.dev/credentials/oauth2/callback", display_name: "Production GitHub", make_default: true, }), }, ); const data = await resp.json(); // { authorization_url, state } ``` The provider authorize URL to open in the browser. Includes the `state` and PKCE `code_challenge`. A one-time CSRF/correlation token. ModuleX stores the flow state in Redis keyed by this value with a 5-minute TTL. Connecting an OAuth2 integration is not directly available through the official JavaScript and Python SDKs — initiate is a browser-driven flow. The cURL example above shows the underlying REST call; in the app, the connect UI performs the same request and opens the consent page for you. See [Managing credentials](/integrations/managing-credentials). ### Step 2 — provider consent and callback The browser opens `authorization_url`, the user approves the requested scopes, and the provider redirects back to the `redirect_uri`, which is the backend callback: `GET /credentials/oauth2/callback?code=...&state=...` This endpoint is the only credential route that is **anonymous** — the browser arriving from the provider carries no ModuleX session. Trust comes entirely from the one-time Redis `state` value. The callback: Reads and deletes the Redis `state` (one-time read). If it is missing — expired past five minutes or already used — the flow ends in an error redirect with `error_code=invalid_state`. Posts the authorization code (and PKCE `code_verifier`) to the provider's `token_url`, presenting client credentials per the schema's `token_auth_method` (`body` or `basic`). Failure ends in `error_code=token_exchange_failed`. Assembles `auth_data` (`access_token`, `token_type` defaulting to `bearer`, an optional `refresh_token`, and `expires_at` derived from `expires_in`), folds in any injected `EnvVar` values, encrypts it, and creates the credential. The credential is created with no `created_by` because the call is anonymous. Always returns a **302** to the frontend landing page — never JSON. On success the URL carries `?status=success&integration=&credential_id=`; on failure `?status=error&integration=&error_code=&message=`. The callback **always** responds with a 302 redirect, even on error, so the app can show the outcome. Recognized `error_code` values include `oauth_denied`, `oauth_provider_error`, `missing_params`, `invalid_state`, `token_exchange_failed`, `credential_creation_failed`, `invalid_credentials`, and `internal_error`. ### Token refresh — and why you reconnect instead ModuleX refreshes OAuth2 access tokens **automatically at run time**. When a credential is resolved for a tool call and its `expires_at` is within five minutes of expiring, the resolver uses the stored `refresh_token` and decrypted `oauth_config` to obtain a new access token, re-encrypts the credential, and continues. You do not trigger this; it happens inside resolution. **Do not rely on a manual "refresh OAuth" action — it is broken.** The manual OAuth-refresh path in the app is a known limitation: the frontend route that the "refresh" control would call does not exist, so the call fails before it reaches the backend. If an OAuth credential ever needs to be re-established (for example, the provider revoked the grant, or the credential never received a `refresh_token` because the provider needs `access_type=offline`/`prompt=consent`), **reconnect the integration** — run the connect flow again from Step 1, which issues fresh tokens and updates the credential. See [Known limitations](/reference/known-limitations). There is also a behavioral gap to be aware of in automatic refresh. The automatic in-resolution refresh always presents client credentials in the request body and ignores the schema's `token_auth_method`. A provider that requires `basic`-auth at the token endpoint (such as Notion) would fail an automatic refresh with `invalid_client`; reconnecting re-issues a working credential. This is logged as a parity gap. ## How credentials are stored (encrypted at rest) Credentials are never stored in plaintext. ModuleX uses two distinct encryption schemes depending on what is being protected. The `auth_data` and `oauth_config` you supply for a credential are encrypted with a key derived **per credential**: - The key is derived with HKDF-SHA256 (length 32), scoped to `(organization_id, credential_id)`, from the server master key `ENCRYPTION_KEY`. - The data is JSON-serialized and sealed with Fernet (AES-128-CBC + HMAC-SHA256). Because the key is scoped to the specific organization and credential, ciphertext cannot be moved between credentials — it fails to decrypt. The master `ENCRYPTION_KEY` is required in production and must be at least 32 characters (256 bits). The ModuleX-registered OAuth app client ids and secrets (used by the managed OAuth path) are encrypted with PBKDF2-HMAC-SHA256 (length 32, 100000 iterations) using a **random 16-byte salt per value**, then sealed with Fernet. The output is `base64url(salt || fernet_token)`. Decryption tries the salt-prepended format first and falls back to a legacy static salt for older values. Each encrypted table carries an `encryption_key_version` column that defaults to `1` — scaffolding for future key rotation; it is currently always `1`. When a credential is read back, secrets are **masked**, never returned in clear: an `oauth2` credential reports the literal label `OAuth2`; an `api_key` reports a masked key; a `bearer_token`/`bearer` reports a masked token; anything else reports `Credential`. For the broader encryption and key-management posture, see [Data security & encryption](/security/data-encryption). The `modulex_key` (ModuleX-managed) credential stores only a **UUID** in its encrypted blob, not a real provider key. The UUID points into a system-managed key pool; the real key is fetched from the pool at resolution time and is never persisted on your credential. See the resolution rules below. ## How credentials are resolved at run time When a tool or LLM node executes, ModuleX resolves a credential in two phases: **pick** one, then **prepare** it for execution (decrypt, refresh, and — for managed keys — gate on credits). ### Phase A — pick a credential If the caller passes a `credential_id`, that exact credential is used. It must belong to the org and match the integration; otherwise resolution fails with "no credential found." If no id is given, the credential marked `is_default` for the `(organization, integration_name, integration_type)` is used. If there is no default, ModuleX falls back to any valid credential, ordered to prefer a **user credential over a `modulex_key`**, then most recent first. If nothing valid is found, resolution fails with "no credential found." **Precedence summary:** explicit `credential_id` > `is_default` row > most-recent valid **user** credential > most-recent valid `modulex_key`. ### Phase B — prepare for execution The prepare step differs by whether the chosen credential is a managed key or one you supplied. 1. Decrypt `auth_data`. 2. If `auth_type` is `oauth2`, refresh the access token when it is within five minutes of expiring (re-encrypt, update, commit, and clear the Redis cache for the credential). This needs a `refresh_token` and the decrypted `oauth_config`. 3. Return the decrypted `auth_data` for the tool function to use. **No credit check applies to user credentials.** Usage of a credential you supply is not metered in ModuleX credits — the provider bills you directly (BYOK). 1. Decrypt the stored blob, which holds a `modulex_key` UUID (not a real key). 2. Verify that UUID is registered for the `(organization, integration, key)` — if not, resolution fails with a verification error. 3. Fetch the **real** API key from the system key pool. 4. **Check the credit limit** for the org. The check happens here, before execution. If the org is over its limit, resolution raises a credit-limit error; with no active subscription it raises a resolution error. 5. Return `auth_data` containing the real key. For `tool` integrations, post- execution usage logging is required so the call can be billed. A `modulex_key` credential **always** carries this credit gate — see [Usage gating & limits](/billing/usage-gating) and [Credits & metering](/billing/credits). ## Errors Credential CRUD and OAuth endpoints raise the standard FastAPI `HTTPException` envelope — `{"detail": }` — for everything except the org-level rate-limit deny, whose `detail` is a dict. These routes do **not** emit the flat `DenialEnvelope`; the credit gate fires inside credential *resolution* at run time, not on these CRUD routes. For the full taxonomy of all three error envelope shapes and which surface emits each, see [Errors & status codes](/api-reference/errors). | Status | When | | --- | --- | | `201` | Credential created. | | `204` | Credential deleted. | | `302` | OAuth2 callback — always a redirect back to the app, even on error. | | `400` | Missing `X-Organization-ID`; invalid `auth_data`/`auth_type`; integration has no OAuth2 schema; missing/partial composer linkage; invalid custom OAuth config. | | `401` / `403` | Not authenticated; not a member; not owner/admin. | | `404` | Integration or credential not found. | | `429` | Org-class `api` rate limit (fail-open); includes `X-RateLimit-*` and `Retry-After` headers. | | `500` | Unexpected error during create/test. | | `503` | OAuth state storage (Redis) unavailable at initiate. | There is **no `402`** on these credential routes. A `402` from credit exhaustion appears on the **run / composer / assistant / managed-knowledge** surfaces when a `modulex_key` credential is resolved during execution — that is where the credit gate is enforced. See [Usage gating & limits](/billing/usage-gating). ## Next steps The conceptual model and a guided walkthrough of connecting an OAuth integration. Create, set defaults for, test, and delete credentials in the app and over the API. The full pydantic contract behind the six auth schema variants. How ModuleX encrypts credentials and manages keys. --- # Custom MCP servers URL: https://docs.modulex.dev/integrations/building/custom-mcp Connect an external Model Context Protocol (MCP) server to ModuleX as a credential, auto-discover its tools, and call them from workflows and agents. Exhaustive reference: the three REST routes, every request and response field, the discovery metadata shape, transport and auth, error envelopes, and SDK equivalents. A **Model Context Protocol (MCP) server** is an external service that exposes a set of tools over a standard wire protocol. ModuleX can connect to one as a **credential**, discover the tools it advertises, and make those tools callable from a [Tool node](/workflow-builder/nodes/tool), the [AI Composer](/concepts/ai-composer), and the [Assistant](/assistant/overview) — exactly like a built-in catalog integration, but without writing any code. This is the second of the two ways to add tools to ModuleX. The first is to author a package integration with the `@tool` contract (see [Build an integration](/integrations/building/overview)). Connecting an MCP server is the lighter-weight path: you point ModuleX at a server URL, it lists the server's tools, and it stores them on a credential your organization can use. There is **no in-app builder for authoring integrations** today. You add your own tools either by publishing a package to the `modulex-integrations` catalog (code) or by connecting an external MCP server (this page). Both paths are code/configuration only. ## How it works When you register an MCP server, ModuleX connects to it once, lists its tools, and persists the connection plus the discovered tool definitions as a single credential. At run time, a [Tool node](/workflow-builder/nodes/tool) that references that credential reconnects to the server, loads the live tools, and invokes the one you named. Call `POST /credentials/mcp-server` with the server URL (and any auth headers). ModuleX connects, discovers the tools, and stores them on a new credential whose `integration_name` is the literal `mcp_server` and whose `auth_type` is `custom`. The create response includes the discovered tools in `credentials_metadata`. You can also read them any time with `GET /credentials/{credential_id}/mcp-tools`. In a [Tool node](/workflow-builder/nodes/tool), set `tool.integration_name` to `mcp_server`, set `tool.credential_id` to your MCP credential, and set `tool.service_name` to the MCP tool's name. When the server adds or removes tools, call `POST /credentials/{credential_id}/refresh-discovery` to re-list and update the stored tool definitions. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-4200 · [IMAGE]** **[IMAGE_DESCRIPTION]:** Diagram of the MCP credential lifecycle in ModuleX. **[IMAGE_DETAILS]:** Three-stage flow — (1) `POST /credentials/mcp-server` → ModuleX connects to the external MCP server and discovers tools; (2) tools stored on an `mcp_server` credential (`auth_type: custom`) in the organization; (3) a Tool node references the credential by `credential_id` and `service_name`, ModuleX reconnects at run time and invokes the tool. Show the server URL masked. Light theme, 16:9, labelled arrows. ## What a discovered tool looks like Each tool ModuleX discovers is reduced to three fields, captured at discovery time and stored on the credential: The MCP tool's name, as advertised by the server. This is the value you put in a Tool node's `service_name`. The tool's human/LLM-facing description (empty string if the server provides none). The tool's input parameters as a JSON Schema object (typically `{type, properties, required}`). ModuleX reads this from the tool's `args_schema` (or `args` / `tool_call_schema` fallbacks); if the server advertises no schema, this is an empty object `{}`. At run time, ModuleX parses each tool's MCP `TextContent` response — a shape like `[{"type": "text", "text": "{...}"}]` — and decodes the stringified JSON so that an MCP tool returns structured data just like a built-in tool. This parsing is handled internally by `MCPToolWrapper`; you do not configure it. ## Transport and protocol ModuleX connects to the server with the `langchain-mcp-adapters` client over a configurable transport. Two transports exist in the protocol — `sse` and `streamable_http` — but the **REST route does not expose a transport field**: a server registered through `POST /credentials/mcp-server` always uses the default **`streamable_http`** transport. The discovered `server_info.protocol_version` ModuleX records is `2024-11-05`. Because the create route fixes the transport to `streamable_http`, an MCP server that speaks only the older `sse` transport cannot currently be registered through the public API. If you need `sse`, raise it with support — it is set on the stored credential's `auth_data`, not on the request body. (TBD: a public way to choose the transport at registration time.) ## Authentication Every ModuleX API request authenticates with your API key as a bearer token plus your organization context header. See [API authentication](/api-reference/authentication) for the full model. ```bash Request headers Authorization: Bearer mx_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx X-Organization-ID: org_xxxxxxxxxxxxxxxxxxxxxxxx Content-Type: application/json ``` All three MCP routes require the caller to be an **owner or admin** of the organization. The `member` role cannot register or refresh MCP servers. See [Roles & permissions](/security/roles-permissions). The MCP server itself is authenticated separately, with the `headers` you supply on registration. ModuleX sends those headers to the server on every connection — for example a bearer token the server expects: ```json headers field { "Authorization": "Bearer your-mcp-server-token" } ``` The server URL and headers can carry secrets (some hosted MCP servers encode a per-user token in the URL path or query). ModuleX **encrypts** them at rest on the credential and **masks** the URL in logs to scheme and host only — for example `https://mcp.example.com`. The full URL is never logged. ## Register an MCP server `POST /credentials/mcp-server` connects to the server, discovers its tools, and creates one `mcp_server` credential. ### Request body The MCP server endpoint URL, for example `https://mcp.example.com/mcp`. ModuleX connects to this URL during discovery and again on every run. Optional HTTP headers sent to the MCP server on every request, including auth — for example `{"Authorization": "Bearer token"}`. Keys and values are strings. Omit for an unauthenticated server. Optional label for the credential. If omitted, ModuleX generates one from the server's reported name and tool count, for example `MCP Server - example (8 tools)`. Whether to make this the default credential for the `mcp_server` integration in this organization. A Tool node that omits `credential_id` cannot resolve an MCP server, so for MCP this mostly affects which credential the app preselects. There is no `transport_type` field on this request — the route always registers the server with the `streamable_http` transport (see [Transport and protocol](#transport-and-protocol)). ```bash cURL curl https://api.modulex.dev/credentials/mcp-server \ -X POST \ -H "Authorization: Bearer mx_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \ -H "X-Organization-ID: org_xxxxxxxxxxxxxxxxxxxxxxxx" \ -H "Content-Type: application/json" \ -d '{ "server_url": "https://mcp.example.com/mcp", "headers": { "Authorization": "Bearer your-mcp-server-token" }, "display_name": "Example docs MCP", "make_default": false }' ``` ```python Python import asyncio from modulex import AsyncModulex client = AsyncModulex( api_key="mx_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", organization_id="org_xxxxxxxxxxxxxxxxxxxxxxxx", ) async def main(): credential = await client.credentials.create_mcp_server( server_url="https://mcp.example.com/mcp", headers={"Authorization": "Bearer your-mcp-server-token"}, display_name="Example docs MCP", make_default=False, ) print(credential.credential_id) print(credential.credentials_metadata["tool_count"]) asyncio.run(main()) ``` ```javascript JavaScript import { Modulex } from "modulex"; const client = new Modulex({ apiKey: "mx_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", organizationId: "org_xxxxxxxxxxxxxxxxxxxxxxxx", }); const credential = await client.credentials.mcpServer({ serverUrl: "https://mcp.example.com/mcp", headers: { Authorization: "Bearer your-mcp-server-token" }, displayName: "Example docs MCP", makeDefault: false, }); console.log(credential.credential_id); ``` ### Response A successful call returns `201`-style credential data shaped as `MCPServerCredentialResponse`. This is a narrower shape than the standard credential response — it omits `updated_at`, `integration_type`, `last_used_at`, and `expires_at`. The new credential's ID. Use it as `tool.credential_id` in a Tool node and as the path parameter for refresh and list calls. Always `mcp_server`. The label, either the one you supplied or the generated default. Always `custom` for MCP server credentials. Whether this is the default `mcp_server` credential for the organization. ISO 8601 creation timestamp. The discovery metadata, including the discovered tools. ISO 8601 timestamp of the first discovery. ISO 8601 timestamp of the most recent discovery (equals `discovered_at` on first create). How many times discovery has been re-run; `0` on create. `{name, version, protocol_version, url, transport_type}`. `version` is `unknown` unless the server reports one; `protocol_version` is `2024-11-05`; `transport_type` is `streamable_http`. The discovered tools, each `{name, description, input_schema}` (see [What a discovered tool looks like](#what-a-discovered-tool-looks-like)). MCP resources. Currently always empty `[]`. MCP prompts. Currently always empty `[]`. The number of discovered tools. ```json Example response { "credential_id": "9f2a6b1c-4d3e-4a8f-9c10-2e7b5a1d8f33", "integration_name": "mcp_server", "display_name": "Example docs MCP", "auth_type": "custom", "is_default": false, "created_at": "2026-06-21T10:14:00.000Z", "credentials_metadata": { "discovered_at": "2026-06-21T10:14:00.000Z", "last_refreshed_at": "2026-06-21T10:14:00.000Z", "refresh_count": 0, "server_info": { "name": "example-docs", "version": "unknown", "protocol_version": "2024-11-05", "url": "https://mcp.example.com/mcp", "transport_type": "streamable_http" }, "tools": [ { "name": "search_documents", "description": "Full-text search across the docs corpus.", "input_schema": { "type": "object", "properties": { "query": { "type": "string" }, "limit": { "type": "integer" } }, "required": ["query"] } } ], "resources": [], "prompts": [], "tool_count": 1 } } ``` ## List discovered tools `GET /credentials/{credential_id}/mcp-tools` returns the tools currently stored on the credential. It reads the persisted `credentials_metadata.tools` — it does **not** reconnect to the server (use [refresh](#refresh-tool-discovery) for that). The MCP server credential's ID. ```bash cURL curl https://api.modulex.dev/credentials/9f2a6b1c-4d3e-4a8f-9c10-2e7b5a1d8f33/mcp-tools \ -H "Authorization: Bearer mx_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \ -H "X-Organization-ID: org_xxxxxxxxxxxxxxxxxxxxxxxx" ``` ```python Python tools = await client.credentials.mcp_tools( "9f2a6b1c-4d3e-4a8f-9c10-2e7b5a1d8f33", ) print(tools.total_count) for tool in tools.tools: print(tool["name"]) ``` ```javascript JavaScript const tools = await client.credentials.mcpTools( "9f2a6b1c-4d3e-4a8f-9c10-2e7b5a1d8f33", ); console.log(tools.total_count); ``` ### Response The credential queried. The discovered tool descriptors, each `{name, description, input_schema}`. The number of tools returned. ## Refresh tool discovery `POST /credentials/{credential_id}/refresh-discovery` reconnects to the server, re-lists its tools, updates the stored `credentials_metadata`, and returns a summary of what changed. Run it after the server adds or removes tools. It is valid only for `mcp_server` credentials; any other credential returns `400`. The MCP server credential to refresh. This route takes **no request body** — the server URL, headers, and transport are read from the stored credential. ```bash cURL curl https://api.modulex.dev/credentials/9f2a6b1c-4d3e-4a8f-9c10-2e7b5a1d8f33/refresh-discovery \ -X POST \ -H "Authorization: Bearer mx_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \ -H "X-Organization-ID: org_xxxxxxxxxxxxxxxxxxxxxxxx" ``` ```python Python result = await client.credentials.refresh_mcp_discovery( "9f2a6b1c-4d3e-4a8f-9c10-2e7b5a1d8f33", ) print(result.changes) print(result.total_tools) ``` ```javascript JavaScript const result = await client.credentials.refreshDiscovery( "9f2a6b1c-4d3e-4a8f-9c10-2e7b5a1d8f33", ); console.log(result.changes); ``` ### Response The credential refreshed. ISO 8601 timestamp of this refresh. A diff against the previous tool set. Names of tools newly present on the server. Names of tools no longer present. Tool count before the refresh. Tool count after the refresh. The tool count after the refresh. Whether the refresh completed. ```json Example response { "credential_id": "9f2a6b1c-4d3e-4a8f-9c10-2e7b5a1d8f33", "refreshed_at": "2026-06-21T11:02:13.000Z", "changes": { "added": ["summarize_document"], "removed": [], "total_before": 1, "total_after": 2 }, "total_tools": 2, "success": true } ``` ## Use an MCP tool in a workflow A [Tool node](/workflow-builder/nodes/tool) calls an MCP tool the same way it calls a catalog action, with three differences: `integration_name` is the literal `mcp_server`, `credential_id` is **required** (the URL, headers, and transport live on the credential, not the catalog), and `service_name` is the MCP tool's name. ```json Tool node — MCP tool definition { "tool": { "integration_name": "mcp_server", "service_name": "search_documents", "credential_id": "9f2a6b1c-4d3e-4a8f-9c10-2e7b5a1d8f33" }, "input_mapping": { "query": "{{intake_1.question}}", "limit": 5 } } ``` At run time ModuleX loads the credential, connects to the server, lists its tools, selects the one matching `service_name`, and invokes it with the mapped inputs. The output is parsed back into structured data, so you reference it like any other node output. See the [Tool node](/workflow-builder/nodes/tool) reference for `input_mapping`, parameter defaults and overrides, and the output shape. ## Errors These routes use the standard `{detail}` HTTPException envelope (a string or, for the org rate limit, an object) — **not** the flat billing `DenialEnvelope`. The MCP credential routes are CRUD-style and have **no per-call credit gate** (the credit gate applies to `modulex_key` resolution at execution time, not to these routes). For the complete error model, see [Errors & status codes](/api-reference/errors). | Status | When | Detail | | --- | --- | --- | | `400` | Connection or discovery failed; or refresh called on a non-`mcp_server` credential | `CredentialValidationError` message | | `401` / `403` | Missing/invalid API key, or caller is not an owner/admin | auth dependency message | | `404` | Credential not found in this organization (refresh / list) | `CredentialNotFoundError` message | | `429` | Organization-class API rate limit (fail-open; includes `X-RateLimit-*` headers) | rate-limit object | | `500` | Internal failure persisting the credential | `CredentialServiceError` message | A connection or discovery failure surfaces as `400` with a `Failed to connect to MCP server: ...` message. Verify the server URL is reachable and that any auth header the server expects is present and correct before retrying. ## SDK reference Every operation maps to a method in both official SDKs. | REST route | Python (`modulex-python`) | JavaScript (`modulex-js`) | | --- | --- | --- | | `POST /credentials/mcp-server` | `credentials.create_mcp_server(...)` | `credentials.mcpServer(...)` | | `POST /credentials/{id}/refresh-discovery` | `credentials.refresh_mcp_discovery(id)` | `credentials.refreshDiscovery(id)` | | `GET /credentials/{id}/mcp-tools` | `credentials.mcp_tools(id)` | `credentials.mcpTools(id)` | Parity note: the JS `MCPServerCredentialResponse` mirrors the backend's narrower MCP shape (it omits `updated_at`, `integration_type`, `last_used_at`, and `expires_at` relative to a standard credential response). For the full cross-language matrix, see the [SDK ⇄ API parity matrix](/sdks/parity). ## Limitations `POST /credentials/mcp-server` always registers a server with the `streamable_http` transport; the `sse` transport is not selectable through the request body. (TBD: a public way to choose the transport.) The tool list is captured at registration and persisted on the credential. A `GET .../mcp-tools` call reads that snapshot; it does not reconnect. Run [refresh](#refresh-tool-discovery) to pick up tools the server has added or removed. MCP `resources` and `prompts` are recorded as empty arrays; only `tools` are discovered and usable today. Registering, refreshing, and listing MCP servers require the owner or admin role. The `member` role is retired and cannot perform these actions — see [Roles & permissions](/security/roles-permissions). For other documented gaps, see [Known limitations](/reference/known-limitations). ## Related Wire an MCP tool into a workflow with `integration_name: mcp_server`. The code path: author a package integration with the `@tool` contract. How ModuleX stores and resolves credentials, including `custom` types. Create, scope, and rotate credentials in the app and via API. --- # Manifest & schema contract URL: https://docs.modulex.dev/integrations/building/manifest-schema The complete Pydantic contract every ModuleX integration manifest must satisfy — IntegrationManifest, ActionDefinition, ParameterDef, the six auth schema variants, EnvVar, and TestEndpoint. 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](/integrations/building/tool-contract) for the executable side, and [Build an integration](/integrations/building/overview) 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.` 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](#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:-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`](#actiondefinition). The credential methods the integration supports, as a discriminated union on `auth_type`. See [Auth schemas](#auth-schemas-the-discriminated-union). 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 `_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`. 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). ### `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`](#envvar), which defaults to `true`. Here is a single action with two parameters: ```python 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](/integrations/building/tool-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`: 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`](#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`](#testendpoint). ### The six variants Each variant adds only its `auth_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 | `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. 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. Here is a complete OAuth2 auth schema: ```python 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](/concepts/credentials-oauth) and [Authentication & credentials](/integrations/authentication). ## `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`](#parameterdef), 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: - **`inject_into_auth_data=false`** (default) — the value is OAuth or test-endpoint only. It never reaches `tools.py`. - **`inject_into_auth_data=true` + `only_for_custom=false`** — per-credential **user input**. The runtime persists the user-entered value into `auth_data` at credential creation (for example a Google Merchant Center `merchant_id`). - **`inject_into_auth_data=true` + `only_for_custom=true`** — a **server-level secret**. The managed app injects it from the server environment at credential-resolution time, while a bring-your-own-app operator supplies their own (for example a Google Ads `developer_token`). ## `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 ` header itself and ignores any `Authorization` entry in `headers`. See [`BasicAuthSpec`](#basicauthspec). How the runtime decides the validation call succeeded. See [`SuccessIndicators`](#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. Here is a Basic-auth test endpoint: ```python 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](/integrations/building/tool-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. ```python manifest.py 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 The decorator order, auth parameter patterns, and Pydantic output models that pair with this manifest. The end-to-end workflow for authoring and registering your own integration. How the six auth schema variants map onto credential setup and OAuth2 (PKCE). Canonical definitions for every manifest term used on this page. --- # Build an integration URL: https://docs.modulex.dev/integrations/building/overview Author your own ModuleX integration end to end: the per-integration directory layout, the manifest, the @tool function contract, entry-point registration in the modulex.tools group, and how to test it locally before the runtime loads it. An integration is a connector to an external service that exposes one or more **tools** — callable actions an [LLM node](/workflow-builder/nodes/llm), an [Agent node](/workflow-builder/nodes/agent), the [AI Composer](/concepts/ai-composer), and the [Assistant](/assistant/overview) can invoke. Every integration ships from the public `modulex-integrations` package as a Python package, and the ModuleX runtime discovers it through the `modulex.tools` entry-point group and loads it at startup. This page is the end-to-end author's guide: the directory the runtime expects, the manifest it reads, the `@tool` function contract it executes, how you register your integration so the runtime can find it, and how to test it locally. Two companion pages go deeper on the contracts referenced throughout: the [manifest and schema contract](/integrations/building/manifest-schema) (every field, type, default, and the six auth schema variants) and the [@tool function contract](/integrations/building/tool-contract) (decorator order, auth-parameter conventions, output models, and error patterns). If instead of authoring a package you want to plug in an external server, see [custom MCP servers](/integrations/building/custom-mcp). The `modulex-integrations` package targets **Python 3.12+** and is built with Hatchling + hatch-vcs (the version is derived from git tags; there is no central version file). Its three always-installed core dependencies are `pydantic>=2.5.0`, `httpx>=0.25.0`, and `langchain-core>=0.3.0`. Everything a specific tool needs beyond those is per-tool. ## Before you start Choose a lowercase, snake-case integration name that matches the pattern `^[a-z][a-z0-9_]*$` — for example `github`, `google_ads`, `mongodb_atlas`. This one string is your directory name, your entry-point key, your manifest `name`, and the credential/catalog key the runtime uses. They must all be identical. The `dev` extras group pulls in `pytest`, `pytest-asyncio`, `pytest-httpx`, `ruff`, and `mypy`, plus a hand-maintained mirror of several vendor SDKs so type-checking can import them. ```bash git clone https://github.com/ModuleXAI/modulex-integrations.git cd modulex-integrations pip install -e ".[dev]" pytest ``` The fastest path is to copy a folder under `src/modulex_integrations/tools/` (for example `github` for a token-auth REST API, or `exa` for an API-key service), then edit the manifest, tools, outputs, and tests for your service. Every integration ships the identical file set, so reviews are mechanical. ## Anatomy of an integration Every integration lives at `src/modulex_integrations/tools//` and ships the same fixed set of files. The contract is enforced by pydantic types in `schema.py` with `extra="forbid"` everywhere — a typo in a manifest fails at **import time**, not at runtime. ```text The per-integration directory layout src/modulex_integrations/tools// ├── __init__.py # re-exports `manifest` + `TOOLS` ├── manifest.py # IntegrationManifest instance bound to the name `manifest` ├── tools.py # LangChain @tool functions (one per action) ├── outputs.py # pydantic response models (output schema derived from these) ├── dependencies.toml # per-integration runtime deps ├── README.md # 5-section strict template └── tests/ ├── __init__.py # empty └── test_.py # ≥1 happy-path test per @tool ``` Builds one `IntegrationManifest` instance named `manifest`: metadata, `actions`, and `auth_schemas`. The single source of truth for the integration's identity and how it authenticates. The per-action `args_schema` input models and the `@tool` functions, plus an auth-header helper. The `@tool` function names must match the manifest `actions`. Typed pydantic output models, one per action, used as `@tool` return annotations. The runtime derives each action's output schema from these. The public surface the runtime imports: it re-exports `manifest` and a `TOOLS` tuple of the `@tool` objects. A `dependencies = [...]` list of the vendor SDKs this tool needs (often empty when only `httpx` is used). A five-section human doc (title, Authentication, Tools, Limits & Quotas, Maintainer). CI enforces the section list. ### The two symbols the runtime reads The runtime imports your integration's `__init__.py` and reads exactly two names. Nothing else in the module is part of the runtime contract. An `IntegrationManifest` instance (re-exported from `manifest.py`) describing the integration's metadata, actions, and auth schemas. See the [manifest and schema contract](/integrations/building/manifest-schema) for every field. A **tuple** of LangChain `StructuredTool` objects, one per action — the `@tool`-decorated functions from `tools.py`. Both the runtime and the docs export script read `getattr(module, "TOOLS", ())`, so the attribute must be a tuple named exactly `TOOLS`. A minimal `__init__.py` re-exports both symbols: ```python src/modulex_integrations/tools//__init__.py """ integration — discovered by the modulex runtime via the ``modulex.tools`` entry point declared in the root ``pyproject.toml``. The runtime imports this module, then reads: - ``manifest`` — an IntegrationManifest describing metadata, actions, and auth schemas. - ``TOOLS`` — a tuple of LangChain StructuredTool objects, one per action. """ from modulex_integrations.tools..manifest import manifest from modulex_integrations.tools..tools import ( create_thing, list_things, ) TOOLS = ( list_things, create_thing, ) __all__ = ["TOOLS", "create_thing", "list_things", "manifest"] ``` ## Step 1 — Write the manifest `manifest.py` builds exactly one `IntegrationManifest` instance bound to the module-level name `manifest`. The model rejects unknown fields (`extra="forbid"`), so import will fail if you add a field that is not in the schema. There is **no** `output_schema`, `docs_url`, `features`, `rate_limits`, `pricing`, or `metadata` field — those belong in the README, not the manifest. ### `IntegrationManifest` fields Always `tool` — the only allowed value. (LLM and knowledge providers are not authored here; they stay in the `modulex` backend.) The integration name. Must match the pattern `^[a-z][a-z0-9_]*$` and be identical to the directory name and the entry-point key (for example `github`). The human-facing label shown in the catalog and UI (for example `GitHub`). A one-line description of what the integration does. The per-integration manifest version — not the package version. The author/maintainer name. Logo reference. Convention is `modulex:-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"]`). One `ActionDefinition` per callable action. Each action's `name` must match a `@tool` function name in `TOOLS`. A list from the discriminated union of the six auth variants (keyed on `auth_type`). An integration may expose more than one — GitHub ships OAuth2 plus a personal-access-token bearer schema. See the [six auth schema variants](/integrations/building/manifest-schema). Each action is an `ActionDefinition`. The action's output shape is **not** declared here — it is derived from the matching `@tool` function's return annotation at backend startup, so the manifest never carries an `output_schema`. The action name. Must match `^[a-z][a-z0-9_]*$` and equal a `@tool` function name in `TOOLS`. A one-line description of the action. A mapping of parameter name to `ParameterDef`. Each `ParameterDef` describes one parameter: 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. Note this defaults to `false` — the opposite of `EnvVar.required`, which defaults to `true`. The manifest `parameters` are **not** the authoritative list of what a tool accepts — the `@tool` function's `args_schema` is. In several shipped integrations a parameter exists on the `args_schema` (and reaches the upstream API) but is omitted from the manifest. Keep both in sync; the `args_schema` is the callable surface of truth. A trimmed manifest for a token-auth REST integration looks like this: ```python src/modulex_integrations/tools//manifest.py from modulex_integrations.schema import ( ActionDefinition, BearerTokenAuthSchema, EnvVar, IntegrationManifest, ParameterDef, SuccessIndicators, TestEndpoint, ) manifest = IntegrationManifest( name="acme", display_name="Acme", description="Acme task and project management", version="1.0.0", author="ModuleX", logo="modulex:acme-themed", app_url="https://acme.example.com", categories=["Productivity"], actions=[ ActionDefinition( name="list_tasks", description="List tasks in a project", parameters={ "project_id": ParameterDef(type="string", description="Project id", required=True), "limit": ParameterDef(type="integer", description="Max tasks to return", default=30), }, ), ], auth_schemas=[ BearerTokenAuthSchema( display_name="Personal Access Token", description="Use your Acme API token", setup_environment_variables=[ EnvVar( name="ACME_TOKEN", display_name="API Token", description="Acme personal access token", sensitive=True, sample_format="acme_xxxxxxxx", about_url="https://acme.example.com/settings/tokens", ), ], test_endpoint=TestEndpoint( url="https://api.acme.example.com/me", method="GET", headers={"Authorization": "Bearer {token}"}, success_indicators=SuccessIndicators(status_codes=[200], response_fields=["id"]), cost_level="free", ), ), ], ) ``` For the full set of auth variants (`oauth2`, `bearer_token`, `api_key`, `modulex_key`, `custom`, `internal`), the `OAuthConfig`, `EnvVar`, `TestEndpoint`, `SuccessIndicators`, and `BasicAuthSpec` fields, and the `only_for_custom` versus `inject_into_auth_data` behavior, see the [manifest and schema contract](/integrations/building/manifest-schema). ## Step 2 — Write the `@tool` functions `tools.py` holds one `async def` per action, each carrying a paired pydantic `args_schema` input model and a typed pydantic return annotation from `outputs.py`. Every function uses the same mandatory two-decorator stack. The decorator order is non-negotiable: `@tool` is the **outer** decorator (applied last, closest to the call site) and `@serialize_pydantic_return` is the **inner** decorator. Reverse them and `@tool` receives the typed pydantic-returning function, the dict coercion never fires, and the runtime's `json.dumps()` boundary raises `TypeError: Object of type ... is not JSON serializable`. ```python src/modulex_integrations/tools//tools.py from __future__ import annotations from typing import Any import httpx from langchain_core.tools import tool from pydantic import BaseModel, Field from modulex_integrations import serialize_pydantic_return from modulex_integrations.tools.acme.outputs import ListTasksOutput class ListTasksInput(BaseModel): auth_type: str = Field(description="Authentication type (oauth2, bearer_token)") auth_data: dict[str, Any] = Field(description="Authentication data containing tokens") project_id: str = Field(description="Project id") limit: int = Field(default=30, description="Max tasks to return") @tool(args_schema=ListTasksInput) # OUTER — applied last, closest to the call site @serialize_pydantic_return # INNER — wraps the raw function async def list_tasks( auth_type: str, auth_data: dict[str, Any], project_id: str, limit: int = 30, ) -> ListTasksOutput: # return annotation = the output model """List tasks in a project.""" headers = _get_auth_headers(auth_type, auth_data) async with httpx.AsyncClient() as client: response = await client.get( f"https://api.acme.example.com/projects/{project_id}/tasks", headers=headers, params={"limit": limit}, ) response.raise_for_status() data = response.json() return ListTasksOutput(success=True, tasks=data.get("tasks", [])) ``` Use `from __future__ import annotations` at the top of both `tools.py` and `outputs.py`. The backend reads each `@tool`'s return annotation via `typing.get_type_hints(...)` (not raw `__annotations__`) precisely to resolve these stringified PEP 563 annotations into the real pydantic class. ### Auth parameter conventions The runtime injects credentials at call time and strips the credential fields from the schema the model sees, so the LLM never reads tokens. There are two conventions; pick the one that matches your auth schemas. For `oauth2` and `bearer_token` schemas, the first two parameters are `auth_type: str` and `auth_data: dict[str, Any]`. A module-level helper builds the provider headers, branching on `auth_type`: ```python def _get_auth_headers(auth_type: str, auth_data: dict[str, Any]) -> dict[str, str]: headers = {"Accept": "application/json"} if auth_type == "oauth2": token = auth_data.get("access_token") if token: headers["Authorization"] = f"Bearer {token}" elif auth_type == "bearer_token": token = auth_data.get("token") if token: headers["Authorization"] = f"Bearer {token}" return headers ``` For `api_key` and `modulex_key` schemas, take the credential parameter directly — for example `api_key: str` as the second parameter — instead of the `auth_type`/`auth_data` pair. The runtime auto-fills any function parameter whose name matches a key in the resolved credential (`api_key`, `token`, `access_token`). Add a non-empty check before any HTTP call: ```python if not api_key or not api_key.strip(): return SearchOutput(success=False, error="API key is empty.") headers = {"x-api-key": api_key} ``` ### Output models `outputs.py` defines one typed pydantic model per action. Each model subclasses a shared base with `model_config = ConfigDict(extra="forbid")`. The only field guaranteed across every integration is `success: bool`; an `error: str | None` field exists only on integrations whose tools return a failure payload rather than raising. ```python src/modulex_integrations/tools//outputs.py from __future__ import annotations from pydantic import BaseModel, ConfigDict, Field class _Base(BaseModel): model_config = ConfigDict(extra="forbid") class TaskSummary(_Base): id: str | None = None title: str | None = None status: str | None = None class ListTasksOutput(_Base): success: bool # required on every output model tasks: list[TaskSummary] = Field(default_factory=list) error: str | None = None # present only on inline-error integrations ``` The backend derives each action's LLM-facing output schema by calling `model_json_schema()` on the resolved return type at startup — which is why the return annotation must stay the pydantic class even though `@serialize_pydantic_return` coerces the runtime value to a dict. ### Choose one error pattern Pick the pattern that fits how your upstream service signals failure and apply it consistently across the integration. Call `response.raise_for_status()` after each request and only ever construct `success=True` models; HTTP errors propagate as exceptions. Output models have no `error` field. Use this when the upstream API returns standard HTTP error status codes. The upstream returns HTTP 200 with a body like `{"ok": false, "error": "..."}`. Read the JSON, check the flag, and on failure return the output model with `success=False` and `error` set; on success leave `error` as `None`. Do not call `raise_for_status()` — inspect the body instead. Wrap every call in `try/except` so non-2xx responses, timeouts, and unexpected exceptions all surface as `success=False` with `error` set, never raising. Pre-validate the credential and set explicit per-endpoint timeouts. Use this when you want the tool to always return a structured result. For the fully annotated end-to-end examples (GitHub `create_issue`, Slack, Exa), the exact `serialize_pydantic_return` behavior, and how the annotation becomes the output schema, see the [@tool function contract](/integrations/building/tool-contract). ## Step 3 — Register the entry point The ModuleX runtime discovers integrations through the Python **`modulex.tools` entry-point group**, not by scanning the filesystem. Add one line for your integration to the root `pyproject.toml`. The key is your integration name; the value is the `` **package** path (not the `.tools` module — the runtime appends `.tools` itself when it needs the tool module). ```toml pyproject.toml [project.entry-points."modulex.tools"] github = "modulex_integrations.tools.github" slack = "modulex_integrations.tools.slack" acme = "modulex_integrations.tools.acme" ``` After installing or upgrading the package, the runtime enumerates `entry_points(group="modulex.tools")`, imports each module, reads its `manifest` and `TOOLS`, converts them into the legacy action-dict shape it expects, and caches the result for the process lifetime. Restart the worker to pick up a freshly installed package version. The docs/CI export script (`scripts/export_manifests.py`) uses a separate discovery mechanism: it **walks the filesystem** for `tools/*/manifest.py` and uses the entry-point table only as a cross-check. So a tool can exist on disk and export to the docs catalog even before its entry point is registered — but the **runtime will not load it** until the entry-point line is present. Always add the entry-point line. ### Declare runtime dependencies List any vendor SDKs your tool imports in its `dependencies.toml`. Leave it as an empty list when the tool only uses `httpx` (already a core dependency). ```toml src/modulex_integrations/tools//dependencies.toml dependencies = ["acme-sdk>=2.0.0"] ``` Do not rely on `pip install` extras to pull these in today. The `all` extra resolves to an empty list and there are **no** per-tool extras groups (`[github]`, `[slack]`, and the like are not defined), because the script that assembles `dependencies.toml` files into installable extras does not yet exist. Installing `modulex-integrations[all]` or `modulex-integrations[acme]` installs only the core package — pip will warn about the unknown extra. Until the assemble step lands, install `modulex-integrations` and then install each tool's SDKs manually per its `dependencies.toml`. See [installing integrations](/integrations/install). ## Step 4 — Write the README Every integration's `README.md` follows the same five-section template, in order. CI enforces the section list, and the export script splits the README into the prose that keeps each generated catalog page from being thin content. The display name as an H1, plus a one-to-two sentence summary naming the upstream API and its base URL. One subsection per auth method: the environment variables, scopes, and token/authorize URLs. A markdown table of `name | description | required params`, plus the note that the runtime injects the auth parameters. Rate limits and the integration's error-model behavior (raise versus inline `ok:false` versus try/except). Your GitHub handle, or "ModuleX core team". ## Step 5 — Test it locally Each integration ships at least one happy-path test per `@tool`, plus a manifest sanity check. HTTP tools use `pytest-httpx` to mock responses; tools that call a vendor SDK stub the client with `unittest.mock.patch`. Tools are invoked exactly as the runtime invokes them — via LangChain `.ainvoke({...})` with a single input dict — and they return a **dict** (because `@serialize_pydantic_return` coerced the model). ```python src/modulex_integrations/tools//tests/test_.py from __future__ import annotations from typing import Any import pytest from modulex_integrations.tools.acme import TOOLS, list_tasks, manifest API = "https://api.acme.example.com" _AUTH: dict[str, Any] = {"auth_type": "bearer_token", "auth_data": {"token": "acme-fake"}} def _args(**extra: Any) -> dict[str, Any]: return dict(_AUTH, **extra) class TestManifest: def test_actions_match_tools_tuple(self) -> None: assert {a.name for a in manifest.actions} == {t.name for t in TOOLS} @pytest.mark.asyncio async def test_list_tasks(httpx_mock): # type: ignore[no-untyped-def] httpx_mock.add_response( method="GET", url=f"{API}/projects/p1/tasks?limit=30", json={"tasks": [{"id": "t1", "title": "Ship docs", "status": "open"}]}, ) result = await list_tasks.ainvoke(_args(project_id="p1")) assert result["success"] is True assert result["tasks"][0]["id"] == "t1" ``` Run the suite, then the linters and type checker before opening a pull request: ```bash All tests pytest ``` ```bash One integration pytest src/modulex_integrations/tools/acme/tests/ ``` ```bash Lint & type-check ruff check src tests mypy src/modulex_integrations ``` **🎬 MEDIA PLACEHOLDER · MX-MEDIA-4170 · [IMAGE]** **[IMAGE_DESCRIPTION]:** The author-to-runtime lifecycle of a custom ModuleX integration. **[IMAGE_DETAILS]:** A left-to-right flow with five labeled stages: (1) author the directory (`manifest.py` / `tools.py` / `outputs.py` / `__init__.py`), (2) register the `modulex.tools` entry point in `pyproject.toml`, (3) `pip install` / package build, (4) runtime startup enumerates entry points and reads `manifest` + `TOOLS`, (5) the tool becomes callable from the LLM node, Agent node, Composer, and Assistant. Show the docs/CI filesystem-walk export branch as a side path feeding the catalog. 16:9, light and dark variants, ModuleX brand palette, no UI chrome. ## Validate the contract before you ship Because every schema model uses `extra="forbid"`, an unknown or misspelled field fails when the module is imported. Importing `manifest.py` (or running `pytest`, which imports it) surfaces these errors immediately — there is no runtime-only failure mode for schema typos. The integration name must be identical across the directory name, the manifest `name`, the entry-point key, and the `` segment of the entry-point value. Each `ActionDefinition.name` must equal a `@tool` function name listed in `TOOLS`. The test `assert {a.name for a in manifest.actions} == {t.name for t in TOOLS}` catches drift. The `args_schema` is the authoritative list of what the tool accepts; the manifest `parameters` is descriptive. If a parameter reaches the upstream API, it should appear on the `args_schema`. Avoid documenting a parameter in the manifest that the function does not accept. Use one of Pattern A / B / C consistently, set explicit timeouts on outbound calls, and never let the LLM see credential fields — the runtime strips `auth_type`, `auth_data`, `api_key`, `token`, `access_token`, and similar from the model-facing schema. ## Next steps Every manifest field, type, and default; the six auth schema variants; `OAuthConfig`, `EnvVar`, `TestEndpoint`, and `BasicAuthSpec`. Decorator order, the two auth conventions, output models, the three error patterns, and fully annotated GitHub, Slack, and Exa examples. Connect an external MCP server and use its tools in ModuleX without packaging an integration. How to open a pull request against `modulex-integrations` and what reviewers check. --- # The @tool function contract URL: https://docs.modulex.dev/integrations/building/tool-contract The exhaustive @tool decorator contract for ModuleX integrations: the mandatory decorator order, the two auth-parameter conventions, typed Pydantic output models, the three error patterns, and worked GitHub, Slack, and Exa examples. A ModuleX integration exposes its actions as **tools**: async Python functions wrapped by a LangChain `StructuredTool`. Each tool pairs a Pydantic input model (`args_schema`) with a typed Pydantic **output model** named as the function's return annotation. This page is the exhaustive reference for that function contract — the exact decorator stack and why its order is non-negotiable, the two ways auth is injected, how the typed output model becomes the LLM-facing output schema, the three error-handling patterns, and three fully worked examples (GitHub, Slack, Exa). For the metadata side of an integration — `IntegrationManifest`, `ActionDefinition`, `ParameterDef`, and the six auth schema variants — see [Manifest & schema contract](/integrations/building/manifest-schema). For the end-to-end anatomy of an integration package, see [Build an integration](/integrations/building/overview). The **manifest** is not the source of truth for what a tool can be called with. The callable surface — every parameter, its type and default — is defined by the tool function's `args_schema`, not by the manifest's `parameters`. A manifest may list fewer parameters than the function accepts. When the two disagree, the `args_schema` wins. See [The args_schema is the callable surface](#the-args_schema-is-the-callable-surface). ## Where a tool lives Every integration lives at `src/modulex_integrations/tools//` and ships a fixed set of files. The three you write a tool against are `tools.py`, `outputs.py`, and `__init__.py`. | File | Role in the tool contract | | --- | --- | | `tools.py` | The `args_schema` input models, the auth-header helper, and the `@tool` functions. | | `outputs.py` | The typed Pydantic **output models** — one per action — used as each tool's return annotation. | | `manifest.py` | Builds the single `IntegrationManifest` named `manifest`. Documented in [Manifest & schema contract](/integrations/building/manifest-schema). | | `__init__.py` | Re-exports `manifest` and a `TOOLS` tuple — the only two names the runtime reads. | | `dependencies.toml` | A `dependencies = [...]` list (often empty). | ### The module surface the runtime reads The runtime imports your integration module and reads **exactly two names**: - `manifest` — one `IntegrationManifest` instance. - `TOOLS` — a tuple of the `@tool` objects (LangChain `StructuredTool`s), one per action. Nothing else in the module is part of the contract. A minimal `__init__.py` re-exports both: ```python title="tools/github/__init__.py" from modulex_integrations.tools.github.manifest import manifest from modulex_integrations.tools.github.tools import ( create_issue, list_repositories, # ... one import per action ) TOOLS = (create_issue, list_repositories) # ... all @tool objects __all__ = ["manifest", "TOOLS", "create_issue", "list_repositories"] ``` ModuleX discovers integrations through the `modulex.tools` **entry-point group** declared in the package's root `pyproject.toml`, not by importing files directly. Registering a new integration means adding one entry-point line there. Discovery and registration are covered in [Build an integration](/integrations/building/overview). ## The @tool decorator stack Every action is an `async def` wrapped by **two decorators, in a fixed order**. ```python title="The mandatory two-decorator stack" from typing import Any from langchain_core.tools import tool from modulex_integrations import serialize_pydantic_return @tool(args_schema=ListRepositoriesInput) # OUTER — applied last, closest to the call site @serialize_pydantic_return # INNER — applied first, wraps the raw function async def list_repositories( auth_type: str, auth_data: dict[str, Any], visibility: str = "all", # ... remaining typed parameters with defaults ) -> ListRepositoriesOutput: # return annotation = the Pydantic output model ... return ListRepositoriesOutput(success=True, repositories=[...]) ``` **The order is not stylistic — reversing it breaks the tool.** `@tool` must be the **outer** decorator and `@serialize_pydantic_return` the **inner** one. Because decorators apply bottom-up, `serialize_pydantic_return` wraps the raw function first; `@tool` then wraps the dict-coercing version. Reverse them and `@tool` would wrap the typed Pydantic-returning function, the dict coercion would never fire, and serialization downstream would fail. ### What each decorator does The LangChain decorator. It turns the function into a `StructuredTool` whose input schema is the Pydantic model you pass as `args_schema`. The tool is invoked with a single input dict via `await tool.ainvoke({...})`. `args_schema` is **required** for every ModuleX tool — it is the callable surface (see below). A ModuleX async wrapper (imported from the package root: `from modulex_integrations import serialize_pydantic_return`). At run time, if the function returns a Pydantic `BaseModel` it returns `result.model_dump()` — a plain dict — instead. Non-`BaseModel` returns pass through unchanged. Crucially, it does **not** change the function's return **annotation**: the annotation still points at the Pydantic class, so static analysis and the backend's output-schema derivation keep reading the model, while the runtime value becomes a JSON-serializable dict. Returning a bare `BaseModel` broke the JSON boundary in the backend — a raw model is not JSON-serializable, so `json.dumps()` raised a `TypeError`. The wrapper dict-coerces the value at the tool boundary while preserving the typed annotation, which the backend reads to derive the output schema. This is exactly why the annotation must keep pointing at the model class even though the value at run time is a dict — see [The output model becomes the output schema](#the-output-model-becomes-the-output-schema). ## The args_schema is the callable surface Each `@tool` takes a paired Pydantic input model via `args_schema=`. Its fields **mirror the function parameters** — same names, same defaults. Required parameters have no default; optional ones use `Field(default=..., description=...)`. ```python title="tools/github/tools.py — input model mirrors the signature" from typing import Any from pydantic import BaseModel, Field class ListRepositoriesInput(BaseModel): auth_type: str = Field(description="Authentication type (oauth2, bearer_token)") auth_data: dict[str, Any] = Field(description="Authentication data containing tokens") visibility: str = Field(default="all", description="all, public, or private") affiliation: str = Field( default="owner,collaborator,organization_member", description="Comma-separated affiliations", ) sort: str = Field(default="full_name", description="Sort field") direction: str = Field(default="asc", description="Sort direction") per_page: int = Field(default=30, description="Results per page") page: int = Field(default=1, description="Page number") ``` **The manifest's `parameters` are NOT the callable surface — the `args_schema` is.** A manifest's `ActionDefinition.parameters` may list fewer fields than the function actually accepts. For example, GitHub's `create_issue` accepts and forwards `assignees`, and `list_repositories` accepts `affiliation`, `direction`, and `page` — all declared in the `args_schema` and reaching the upstream API — yet the manifest omits them. When you document or call a tool, read the `args_schema`, not the manifest. (Tracked as discrepancy D1.) ### How a tool is invoked Tools are not called as plain functions. The runtime invokes them through LangChain with a single input dict: ```python title="Invocation shape" result = await create_issue.ainvoke({ "owner": "octocat", "repo": "hello-world", "title": "Track the docs rebuild", "body": "First issue from a ModuleX tool.", # auth params are injected by the runtime — see below }) # result is a dict (serialize_pydantic_return dict-coerced the output model) ``` The returned value is a **dict** because `serialize_pydantic_return` coerced the output model. The auth parameters are not passed by the caller — the runtime injects them. ## Auth parameter patterns A tool never receives raw credentials from the model. The model-facing schema omits all credential fields (`api_key`, `token`, `auth_token`, `access_token`, `bearer_token`, `auth_type`, `auth_data`); the runtime resolves the org's stored credential and injects the auth parameters at call time. There are **two conventions** for how those parameters appear on the function signature. The first two parameters are the auth pair: `auth_type: str` and `auth_data: dict[str, Any]`. A module-level helper builds provider headers by branching on `auth_type`. ```python title="tools/github/tools.py — header helper" def _get_auth_headers(auth_type: str, auth_data: dict[str, Any]) -> dict[str, str]: headers = {"Accept": "application/vnd.github+json"} if auth_type == "oauth2": access_token = auth_data.get("access_token") # oauth2 reads access_token if access_token: headers["Authorization"] = f"Bearer {access_token}" elif auth_type == "bearer_token": token = auth_data.get("token") # bearer_token reads token if token: headers["Authorization"] = f"Bearer {token}" return headers ``` The two `auth_type` string values these integrations accept are `oauth2` and `bearer_token`, matching the two `auth_schemas` the manifest declares (`OAuth2AuthSchema` + `BearerTokenAuthSchema`). Some integrations take a single `api_key: str` as the second parameter instead of the auth pair. The runtime auto-fills any parameter whose **name** matches a key in the resolved credential, so a parameter named `api_key` receives the resolved key (from either an `api_key` or a `modulex_key` credential) directly. ```python title="tools/exa/tools.py — header helper" def _headers(api_key: str) -> dict[str, str]: return {"x-api-key": api_key, "Content-Type": "application/json"} @tool(args_schema=SearchInput) @serialize_pydantic_return async def search(query: str, api_key: str, num_results: int = 10) -> SearchOutput: ... ``` Exa's manifest declares `ApiKeyAuthSchema` + `ModulexKeyAuthSchema` for this convention. **How the runtime injects auth.** Before calling the tool, the runtime strips the credential fields from the model-facing schema (so the LLM never sees them), then auto-fills any signature parameter whose name matches a key in the resolved credential's data — `api_key`, `token`, `access_token`, and so on land on the matching parameter; the `auth_type`/`auth_data` pair is injected for tools that declare it. The full credential-resolution path — encryption, the OAuth2 PKCE flow, the six auth schema variants, and credit gating on `modulex_key` resolution — is documented in [Integration authentication & credentials](/integrations/authentication). Both auth conventions appear in the catalog today, but a manifest's auth schema and the function signature must agree: declare `oauth2` / `bearer_token` schemas with the `auth_type` + `auth_data` pair, and declare `api_key` / `modulex_key` schemas with a named `api_key` parameter. The `custom` and `internal` auth variants have no reference integration among GitHub, Slack, or Exa, so their signature shape is not demonstrated here — `internal` is a reserved variant with no shipped catalog usage. ## Typed Pydantic output models Each tool's return annotation is a typed output model defined in `outputs.py`. Every output model subclasses a private base configured with `model_config = ConfigDict(extra="forbid")`, so an unexpected field fails fast. There are two layers of model. ```python title="tools/github/outputs.py — the two layers" from pydantic import BaseModel, ConfigDict, Field class _Base(BaseModel): model_config = ConfigDict(extra="forbid") # Layer 1 — nested resource model. Fields are intentionally permissive # (X | None = None) because the upstream API omits fields. class IssueCreated(_Base): number: int | None = None title: str | None = None body: str | None = None state: str | None = None url: str | None = None created_at: str | None = None # Layer 2 — per-action output wrapper carrying the success flag + payload. class CreateIssueOutput(_Base): success: bool # required; no `error` field — this model raises on failure issue: IssueCreated ``` **Universal.** Every output model carries `success: bool` as the one stable, cross-integration field. It is required. **Conditional.** Present only on integrations whose error model returns a failure payload rather than raising — see the table below. Do not assume every tool returns an `error` field. **`error` field presence is integration-dependent.** A generic "tool output" claim that every tool returns `{success, error}` is wrong: GitHub's models have **no** `error` field (failures raise before a model is constructed), while Slack and Exa both carry `error: str | None`. The only field you can rely on across every integration is `success: bool`. (Tracked as discrepancy D6.) | Integration | `success` | `error` field | Why | | --- | --- | --- | --- | | GitHub | `success: bool` (required) | **Absent** | Failures raise before the model is built. | | Slack | `success: bool` | `error: str \| None = None` | Failure path returns a `success=False` model. | | Exa | `success: bool` | `error: str \| None = None` | All exceptions are caught and returned as a model. | ### The output model becomes the output schema The output **shape** is deliberately **not** declared in the manifest. Neither `ActionDefinition` nor `IntegrationManifest` carries an `output_schema` field — a reader of the manifest alone will not find output shapes; they live in `outputs.py`. (Tracked as discrepancy D2.) Instead, the backend derives the output schema at startup from the `@tool` function's **return annotation**: The loader resolves the tool's underlying coroutine (the function wrapped by `@tool`). It calls `typing.get_type_hints(func)` to resolve the stringified `-> CreateIssueOutput` return annotation. This is why `serialize_pydantic_return` must preserve the annotation even though it dict-coerces the value. If the resolved return type has `model_json_schema()`, the loader sets the action's output schema to `return_type.model_json_schema()`. The chain is the whole reason the decorator order is mandatory: the model-facing output schema comes from the **annotation**, while the JSON-serializable value comes from the **dict coercion**. `@tool` outer + `@serialize_pydantic_return` inner is what lets both be true at once. ## The three error patterns A tool function must decide what happens on an upstream failure. The three reference integrations demonstrate the three sanctioned patterns. Pick one and apply it consistently across all of an integration's tools. Call `response.raise_for_status()` after each request and only ever construct `success=True` models. HTTP errors propagate as exceptions; the runtime handles them. Output models have **no** `error` field. ```python title="Pattern A — raise on non-2xx" async with httpx.AsyncClient() as client: response = await client.post(url, headers=headers, json=payload) response.raise_for_status() # non-2xx raises here data = response.json() return CreateIssueOutput(success=True, issue=IssueCreated(...)) ``` Use this when the upstream API returns proper HTTP status codes and you want failures surfaced as exceptions. Some APIs return **HTTP 200** with an `{"ok": false, "error": "..."}` body. Read the JSON, check the success flag, and on the failure branch return the output model with `success=False` and `error` set; the success branch leaves `error` as `None`. Do **not** call `raise_for_status()` here — inspect the body instead. ```python title="Pattern B — check the body, not the status" data = response.json() if not data.get("ok"): return ListChannelsOutput( success=False, error=data.get("error", "Unknown error"), ) # success branch populates fields; error stays None return ListChannelsOutput(success=True, channels=[...]) ``` Wrap every call in `try/except` so non-2xx responses, timeouts, and unexpected exceptions all surface as `success=False` + `error` and **never** raise. Validate inputs (such as a present `api_key`) before any HTTP call, and set explicit per-endpoint timeouts. ```python title="Pattern C — catch everything, return a model" if not api_key or not api_key.strip(): return SearchOutput(success=False, error="Exa API key is empty.") # pre-check try: async with httpx.AsyncClient(timeout=30.0) as client: response = await client.post(f"{_EXA_API_BASE}/search", headers=..., json=payload) if response.status_code != 200: return SearchOutput( success=False, error=f"Exa API error ({response.status_code}): {response.text}", ) data = response.json() except httpx.TimeoutException: return SearchOutput(success=False, error="Request timed out.") except Exception as exc: return SearchOutput(success=False, error=f"Search failed: {exc}") return SearchOutput(success=True, results=[...]) ``` **🎬 MEDIA PLACEHOLDER · MX-MEDIA-4190 · [IMAGE]** **[IMAGE_DESCRIPTION]:** Decision diagram mapping the three tool error patterns to when each is used. **[IMAGE_DETAILS]:** Three labeled columns — A "raise" (GitHub), B "inline ok:false" (Slack), C "try/except" (Exa). For each, show the trigger (proper HTTP status codes / HTTP 200 with `ok:false` body / any failure must be non-raising), the action taken (`response.raise_for_status()` / check `data["ok"]` / wrap in try/except + pre-validate), and the resulting output-model shape (no `error` field / `success=False` + `error` / `success=False` + `error`). Add a footer note: "`success: bool` is universal; `error` exists only on B and C." Light theme, 16:9, monospace for code tokens. ## Worked example — GitHub `create_issue` end to end This ties the whole contract together: the input model, the output model, and the `@tool` function, with the manifest action for context. ```python tools.py from typing import Any import httpx from langchain_core.tools import tool from pydantic import BaseModel, Field from modulex_integrations import serialize_pydantic_return from modulex_integrations.tools.github.outputs import ( CreateIssueOutput, IssueCreated, ) class CreateIssueInput(BaseModel): auth_type: str = Field(description="Authentication type") auth_data: dict[str, Any] = Field(description="Authentication data") owner: str = Field(description="Repository owner") repo: str = Field(description="Repository name") title: str = Field(description="Issue title") body: str | None = Field(default=None, description="Issue body/description") labels: list[str] | None = Field(default=None, description="List of label names") assignees: list[str] | None = Field(default=None, description="Usernames to assign") @tool(args_schema=CreateIssueInput) # OUTER @serialize_pydantic_return # INNER async def create_issue( auth_type: str, auth_data: dict[str, Any], owner: str, repo: str, title: str, body: str | None = None, labels: list[str] | None = None, assignees: list[str] | None = None, ) -> CreateIssueOutput: # annotation read by the loader """Create an issue in a repository.""" headers = _get_auth_headers(auth_type, auth_data) payload: dict[str, Any] = {"title": title} if body: payload["body"] = body if labels: payload["labels"] = labels if assignees: payload["assignees"] = assignees async with httpx.AsyncClient() as client: response = await client.post( f"https://api.github.com/repos/{owner}/{repo}/issues", headers=headers, json=payload, ) response.raise_for_status() # Pattern A i = response.json() return CreateIssueOutput( success=True, issue=IssueCreated( number=i.get("number"), title=i.get("title"), body=i.get("body"), state=i.get("state"), url=i.get("html_url"), # wire field html_url -> model field url created_at=i.get("created_at"), ), ) ``` ```python outputs.py from pydantic import BaseModel, ConfigDict class _Base(BaseModel): model_config = ConfigDict(extra="forbid") class IssueCreated(_Base): number: int | None = None title: str | None = None body: str | None = None state: str | None = None url: str | None = None created_at: str | None = None class CreateIssueOutput(_Base): success: bool issue: IssueCreated ``` ```python manifest.py (action excerpt) from modulex_integrations.schema import ActionDefinition, ParameterDef ActionDefinition( name="create_issue", # must match ^[a-z][a-z0-9_]*$ description="Create a new issue", parameters={ "owner": ParameterDef(type="string", description="Repository owner", required=True), "repo": ParameterDef(type="string", description="Repository name", required=True), "title": ParameterDef(type="string", description="Issue title", required=True), "body": ParameterDef(type="string", description="Issue body"), # required defaults to False "labels": ParameterDef(type="array", description="Issue labels"), # NOTE: `assignees` is intentionally absent here but present in the # args_schema and forwarded to the API — the args_schema is canonical. }, ) ``` The GitHub API field `html_url` is mapped onto the model's `url` field; similar wire-to-model remapping appears across the integration (for example `stargazers_count` becomes `stars`). Map upstream field names to your model field names explicitly in the tool body. ## Worked example — Slack `post_message` Slack uses the **inline `ok:false`** error pattern (Pattern B) and an output model that carries both `success` and `error`. Required input fields (`channel_id`, `text`) have no default. ```python tools.py @tool(args_schema=PostMessageInput) @serialize_pydantic_return async def post_message( auth_type: str, auth_data: dict[str, Any], channel_id: str, text: str, ) -> PostMessageOutput: """Post a message to a Slack channel.""" headers = _get_auth_headers(auth_type, auth_data) async with httpx.AsyncClient() as client: response = await client.post( "https://slack.com/api/chat.postMessage", headers=headers, json={"channel": channel_id, "text": text}, ) data = response.json() # no raise_for_status — Slack returns 200 if not data.get("ok"): return PostMessageOutput(success=False, error=data.get("error", "Unknown error")) return PostMessageOutput(success=True, ts=data.get("ts"), channel=data.get("channel")) ``` ```python outputs.py class PostMessageOutput(_Base): success: bool error: str | None = None ts: str | None = None channel: str | None = None ``` Slack returns HTTP 200 even for failures, so the tool checks `data["ok"]` rather than the status code. A failure such as `{"ok": false, "error": "channel_not_found"}` becomes `PostMessageOutput(success=False, error="channel_not_found")`. Slack's migrated output models also flatten the legacy `{"success", "action", "result": {...}}` wrapper — there is no `result` or `action` key on the package output. ## Worked example — Exa `search` Exa uses the **single `api_key` parameter** auth convention and the **try/except wrapper** error pattern (Pattern C). Every failure becomes a model; nothing raises. ```python tools.py _EXA_API_BASE = "https://api.exa.ai" _SEARCH_TIMEOUT = 30.0 @tool(args_schema=SearchInput) @serialize_pydantic_return async def search(query: str, api_key: str, num_results: int = 10) -> SearchOutput: """Run a neural search over the web with Exa.""" if not api_key or not api_key.strip(): return SearchOutput(success=False, error="Exa API key is empty.") payload = {"query": query, "numResults": num_results} try: async with httpx.AsyncClient(timeout=_SEARCH_TIMEOUT) as client: response = await client.post( f"{_EXA_API_BASE}/search", headers=_headers(api_key), json=payload, ) if response.status_code != 200: return SearchOutput( success=False, error=f"Exa API error ({response.status_code}): {response.text}", ) data = response.json() except httpx.TimeoutException: return SearchOutput(success=False, error="Request timed out.") except Exception as exc: return SearchOutput(success=False, error=f"Search failed: {exc}") return SearchOutput( success=True, results=[SearchResult(**r) for r in data.get("results", [])], ) ``` ```python outputs.py class SearchResult(_Base): title: str | None = None url: str | None = None text: str | None = None score: float | None = None class SearchOutput(_Base): success: bool error: str | None = None results: list[SearchResult] = Field(default_factory=list) ``` Exa takes `api_key` directly rather than the `auth_type`/`auth_data` pair, and it catches every exception so the agent always receives a structured model. It also sets explicit per-endpoint timeouts (search uses `30.0s`; content fetches use a longer timeout). Pricing figures in Exa's README are approximate and live only in prose — they are not encoded in any manifest or output field, and managed-key usage is metered in ModuleX credits; see [Credits & metering](/billing/credits). ## Contract checklist Before you register an integration, confirm each tool satisfies the contract. `@tool(args_schema=...)` is the outer decorator; `@serialize_pydantic_return` is the inner one. The function is `async def`. The `args_schema` model's fields match the function parameters (names + defaults). Required parameters have no default. Remember the `args_schema` — not the manifest — is the callable surface. Use `auth_type` + `auth_data` for `oauth2` / `bearer_token` schemas, or a named `api_key` parameter for `api_key` / `modulex_key` schemas. Never accept raw credentials from the model. The return annotation is a Pydantic model from `outputs.py` (a subclass of your `extra="forbid"` base). Every model has `success: bool`; add `error: str | None` only if your error pattern returns a failure model. Choose Pattern A (raise), B (inline `ok:false`), or C (try/except) and use it across all of the integration's tools. `manifest` and `TOOLS` are re-exported, and every `@tool` object is in `TOOLS`. Do not advertise per-tool `pip install` extras (such as `[github,slack]` or `[all]`) for your integration. The packaging extras are currently empty — installing them emits pip warnings and installs only the core package. Until the dependency-assembly step ships, integration dependencies belong in your `dependencies.toml` and the core install. See [Installing & using integrations](/integrations/install). ## Next steps The metadata side: `IntegrationManifest`, `ActionDefinition`, `ParameterDef`, and the six auth schema variants. The full package anatomy and how an integration is discovered and registered. How the runtime resolves and injects the credentials your tool's auth parameters receive. Expose tools from an external MCP server instead of authoring a Python integration. --- # Browse the integration catalog URL: https://docs.modulex.dev/integrations/catalog Browse all 175 ModuleX integrations by category, search for a service, and learn how to read a tool's catalog page before you connect it. The catalog is the directory of everything ModuleX can connect to. Each entry is an **integration** — a connector to an outside service such as Slack, GitHub, or Salesforce — and each integration exposes one or more **tools**, the individual actions your workflows and the Assistant can call. ModuleX ships **175 integrations** today. Browse them by category, search for the service you need, then open its page to see what it can do before you connect it. New to integrations? Start with [the integrations overview](/integrations/overview) for the bigger picture of how connectors, tools, and credentials fit together. This page is for finding a specific integration once you know you want one. ## What counts as an integration An integration packages everything ModuleX needs to talk to one service: How ModuleX signs in to the service on your behalf — an API key, an OAuth connection, or a managed key. You set this up once as a credential. The individual actions the integration can perform — "send a message", "create an issue", "search records". One integration usually exposes several tools. A generated reference page that lists every tool, its inputs, how it authenticates, and any limits — so you know what you are connecting before you connect it. The service's display name, logo, and link to its own site, so the catalog stays easy to scan. Once an integration is connected, its tools become available to your workflows through the [Tool node](/workflow-builder/nodes/tool) and to the [Assistant](/assistant/overview) when it works through a task. ## Browse by category Each integration is tagged with one or more categories so you can scan the catalog by the kind of work you do rather than by remembering exact product names. The largest groups are: Team and document tools — messaging, calendars, docs, and project trackers. The largest group in the catalog. Source control, cloud platforms, observability, and deployment services. Email, campaigns, analytics, and audience tools. Customer records, pipelines, and outreach platforms. Connectors that move data between other services and trigger downstream work. Payments, data warehouses, AI services, support desks, and many more specialised connectors. Categories are a browsing aid, not a fixed taxonomy. Each integration sets its own categories, so the exact labels and counts shift as the catalog grows. Use search when you already know the service you want. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-4040 · [SCREENSHOT]** **[SCREENSHOT_DESCRIPTION]:** The integration catalog in the ModuleX app, showing the category sidebar and a grid of integration cards. **[SCREENSHOT_DETAILS]:** Capture the catalog/browse view in the live app with the category filter visible on the left and integration logo cards on the right. Show the search box at the top. Light theme, 16:9, no personal data. ## Find an integration In the app, go to the integrations area and open **Browse**. You'll see every available integration as a card with its logo and a short description. Type the service name in the search box, or pick a category to narrow the grid. Search matches the display name and description, so "email", "calendar", or "database" all surface relevant results. Click a card to open its catalog page. Review the tools it exposes and how it authenticates before you connect it. Add a credential for the integration, then use its tools from a workflow or the Assistant. See [connect an integration](/guides/connect-an-integration) for the full walkthrough. ## What's on a tool's catalog page Every integration has its own page, generated directly from the integration's manifest so it always matches what the service actually exposes. When you open one, you'll find: The service's display name, logo, a short description of what it does, and a link to the service's own website. Every tool the integration exposes, each with a plain description, its inputs (name, type, and whether each is required), and the shape of what it returns. This is the menu your workflows and the Assistant choose from. How the integration signs in — an API key, an OAuth connection, or a ModuleX-managed key — and any setup steps or values you need to provide. See [authentication & credentials](/integrations/authentication) for how each method works. Service-specific rate limits, size caps, and other constraints to keep in mind, drawn from the integration's own notes. Who maintains the integration. Most are maintained by ModuleX. Catalog pages are generated from each integration's manifest, not written by hand. If a page looks out of date, the fix is to update the source integration and regenerate — the page will follow. ## Browse the catalog from code You can also list the catalog programmatically — useful for building your own picker or syncing the available integrations into another tool. The browse endpoint returns the integrations available to your organization. Every request authenticates with `Authorization: Bearer mx_live_…` plus your `X-Organization-ID`. See [authentication](/api-reference/authentication) for how to get and use these. ```bash cURL curl https://api.modulex.dev/integrations/browse \ -H "Authorization: Bearer mx_live_your_api_key" \ -H "X-Organization-ID: org_your_org_id" ``` ```python Python import asyncio from modulex import Modulex async def main(): async with Modulex( api_key="mx_live_your_api_key", organization_id="org_your_org_id", ) as client: catalog = await client.integrations.browse() for integration in catalog: print(integration.name, integration.display_name) asyncio.run(main()) ``` ```javascript JavaScript import { Modulex } from "modulex-js"; const client = new Modulex({ apiKey: "mx_live_your_api_key", organizationId: "org_your_org_id", }); const catalog = await client.integrations.browse(); for (const integration of catalog) { console.log(integration.name, integration.displayName); } ``` Browsing the catalog returns the integrations you can connect — it does not connect anything or move data. To work with credentials, see [managing credentials](/integrations/managing-credentials). ## Don't see what you need Point ModuleX at an external Model Context Protocol server to use its tools alongside the built-in catalog. Author a new integration and expose its tools to ModuleX. Connecting a custom MCP server is a separate path from the built-in catalog above — it is not one of the 175 catalog integrations. Use it when the service you need isn't listed. ## Next steps How connectors, tools, and credentials work together. A step-by-step walkthrough from connecting to calling a tool. The connection methods every integration uses. --- # Installing & using integrations URL: https://docs.modulex.dev/integrations/install How ModuleX integrations are discovered through Python entry points, how to install the modulex-integrations package and its tool SDK dependencies, and the difference between managed and bring-your-own-key tool usage. ModuleX integrations are connectors to external services. Each **integration** (for example `github`) ships a manifest plus a set of **tools** — the callable actions you run from a workflow's [Tool node](/workflow-builder/nodes/tool) or from the Assistant. The full set of integrations lives in one installable Python distribution, **`modulex-integrations`**, which the ModuleX backend loads at startup. This page is for people running or extending the ModuleX backend: it covers how integrations are discovered at runtime, how to install the package and the per-tool SDK dependencies you actually need, and how a tool call is authenticated either with ModuleX-managed credentials or your own key. If you only want to connect a service in the app, see [Authentication & credentials](/integrations/authentication) and [Connect an integration](/guides/connect-an-integration) instead — you do not install anything for that. The hosted ModuleX service already runs `modulex-integrations` for you. You install the package yourself only when you self-host the backend or develop integrations locally. To author a new integration, start at [Build an integration](/integrations/building/overview). ## How integrations load at runtime Integrations are not configured by hand or read from a directory you point at. They are discovered through standard Python **entry points** in the group `modulex.tools`. Every integration in the package declares one entry point that maps its catalog name to its module: ```toml pyproject.toml [project.entry-points."modulex.tools"] aws = "modulex_integrations.tools.aws" github = "modulex_integrations.tools.github" slack = "modulex_integrations.tools.slack" # ... 175 entries total ``` At startup the backend enumerates this group, imports each module, and reads two required symbols from it: The integration's manifest — name, display name, categories, actions, and auth schemas. Defined in `manifest.py` and re-exported from the package `__init__.py`. See the [manifest & schema contract](/integrations/building/manifest-schema). A tuple of LangChain tool objects, one per action. See the [`@tool` function contract](/integrations/building/tool-contract). The mapping key (for example `github`) is the integration's catalog and credential name. The mapping value (for example `modulex_integrations.tools.github`) is the **package module**, not the tools module — the runtime appends `.tools` to it when it needs to import the callable (resolving to `modulex_integrations.tools.github.tools`). The backend calls `entry_points(group="modulex.tools")` to list every registered integration. For each entry point it imports the module and reads `manifest` and `TOOLS`. The resolved catalog is cached for the lifetime of the worker process. A newly installed package version is not picked up until you restart the worker. Because the catalog is cached per process, upgrading `modulex-integrations` while a worker is running has no effect until that worker restarts. Restart all backend workers after installing or upgrading the package. ### Two catalog sources The runtime resolves tools from two places, and entry points are not the only one: - **Package entry points** — the `modulex.tools` group described above. This is the primary source and **wins on a name collision**. - **Legacy on-disk JSON** — a small set of integrations not yet migrated to the package, loaded from `*_integration.json` files in the backend. The Model Context Protocol connector (`mcp_server`) is handled this way rather than as a package entry point. See [Custom MCP servers](/integrations/building/custom-mcp). So "every tool comes from `modulex-integrations`" is almost true — treat the legacy JSON integrations as documented exceptions. There are **two distinct discovery mechanisms** in this system, and they are not interchangeable. The runtime uses **entry points** (above). The documentation and CI pipeline that generates the [integration catalog](/integrations/catalog) instead walks the filesystem for `tools/*/manifest.py`, using the entry-point table only as a cross-check. If you are reading manifests for tooling, match the mechanism to your goal. ## Install the package `modulex-integrations` requires **Python 3.12 or newer**. The base install pulls in only three core dependencies — `pydantic`, `httpx`, and `langchain-core` — which is everything most integrations need. ```bash pip pip install modulex-integrations ``` ```bash uv uv pip install modulex-integrations ``` The backend pins an exact version of this package (it does not float with `>=`), because the manifest schema can change between minor versions and the runtime rejects unknown manifest fields at import time. When self-hosting, install the version that matches the backend you are running rather than the latest release. ### Do not rely on install extras The package declares an `all` extra and the README advertises per-tool extras such as `[github,slack]`. **Do not use them.** They do not work as written today: The `all` extra is an **empty list** in the package, and there are **no per-tool extras** (`[github]`, `[slack]`, `[aws]`, and so on are not defined). Installing them does not add any tool SDKs: - `pip install "modulex-integrations[all]"` installs only the three core dependencies — the same as the base install. - `pip install "modulex-integrations[github,slack]"` emits pip warnings like `WARNING: modulex-integrations does not provide the extra 'github'` and installs only the base package. A script to auto-assemble these extras from each tool's declared dependencies is planned but **not implemented**. Until it lands, treat `[all]` and per-tool extras as non-functional and install tool SDKs manually (below). This limitation is also recorded in [Known limitations](/reference/known-limitations) and the [Help known-limitations page](/help/known-limitations). ### Install tool SDK dependencies manually Because extras are inert, the working path is: install the base package, then install whatever client SDK each integration you intend to use needs. Many integrations need **nothing extra** — they call their service over plain HTTP through the core `httpx` dependency. For example, both `github` and `slack` declare no additional dependencies. Others do need a vendor SDK. Each integration declares what it needs in a `dependencies.toml` file in its source directory: ```toml src/modulex_integrations/tools/aws/dependencies.toml dependencies = ["boto3>=1.34.0"] ``` Install those yourself. For an AWS-backed workflow, for example: ```bash pip install modulex-integrations boto3 ``` There is **no REST endpoint that runs a tool directly**. Tools execute only inside workflow runs and Assistant/Composer turns. Installing the package makes the catalog available to the backend; you call individual tools through the [Tool node](/workflow-builder/nodes/tool), the Assistant, or the AI Composer. ## Managed usage vs bring-your-own-key Whether a tool needs an SDK installed is separate from **how it is authenticated and billed**. Every tool call resolves a [credential](/integrations/managing-credentials) for the integration in your organization, and that credential is one of two kinds. The call runs through a ModuleX-provisioned key from a managed pool. Usage is **metered in credits** and is subject to the billing gate. The managed providers appear on the wire as `modulexai` (models) and `modulexdb` (knowledge); managed tool calls draw from your credit balance. You connect your own provider account. Usage is billed **directly by that provider with no ModuleX markup**, and BYOK calls are **not** charged in ModuleX credits — they are tracked for analytics only. BYOK is available on every plan; there is no entitlement that gates it. The runtime injects the resolved credential into the tool call internally, so the model and the tool function never see raw secrets. The behavior differs by credential kind: Before the call runs, the runtime checks the organization's credit limit (without recording yet). If the monthly budget is exhausted and no wallet overage is available, the call is denied. On success it records the per-call tool cost. See [Credits & metering](/billing/credits). The runtime decrypts your stored credential, refreshes the OAuth2 token if it is close to expiry, and runs the call. There is **no credit check or credit charge** — only usage tracking. ModuleX-managed tool calls are part of the live billing gate. When the credit limit is exhausted, a managed call is denied rather than running. Plan for this in any workflow that depends on managed tool usage. BYOK calls are never credit-gated. For how a tool resolves credentials, refreshes OAuth2 tokens, and maps auth into the call, see [Credentials & OAuth2](/concepts/credentials-oauth) and [Authentication & credentials](/integrations/authentication). For model and knowledge providers specifically, see [LLM providers](/integrations/llm-providers/overview) and [Knowledge providers](/integrations/knowledge-providers/overview). ## Verify the catalog loaded After installing the package and restarting your workers, confirm the integrations you expect are present. The catalog the backend exposes reflects the entry points found at startup. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-4010 · [SCREENSHOT]** **[SCREENSHOT_DESCRIPTION]:** The integrations catalog in the ModuleX app after the package has loaded, showing connected and available integrations. **[SCREENSHOT_DETAILS]:** Capture the integrations catalog page in the live ModuleX app. Show the grid of integrations with a few connected (credential present) and the rest available to connect. Dark theme, full-width, no personal data. Highlight that the count reflects the installed package, not a hardcoded list. If an integration you expect is missing, check, in order: The catalog is cached per process. Restart all backend workers after installing or upgrading `modulex-integrations`. The backend pins an exact version. A mismatched install can fail to import a manifest with an unknown field, because the schema rejects unknown fields at import time. Install the version that matches your backend. An integration whose `dependencies.toml` lists a vendor SDK will fail to load its tools if that SDK is missing, because extras do not install it for you. Install the SDK manually (see above). The integration's manifest may still appear while individual actions fail to load. A few integrations (such as `mcp_server`) load from legacy on-disk JSON, not from the package entry points. See [Custom MCP servers](/integrations/building/custom-mcp). ## Next steps Connect a service with an API key or OAuth2 and store the credential. See every integration ModuleX can connect to, grouped by category. Call an integration's action from the Tool node. Author your own integration and expose its tools. --- # modulexdb — the ModuleX-managed vector store URL: https://docs.modulex.dev/integrations/knowledge-providers/modulexdb modulexdb is the ModuleX-managed vector store: a fully hosted pgvector store with managed embeddings, used as the knowledge provider for a knowledge base or a workflow knowledge node. Reference for the provider config (every field, type, and default), the credit cost of ingest and retrieval, and the billing gate that returns 402/403/429. `modulexdb` is the **ModuleX-managed vector store** — the built-in knowledge provider you select when you want ModuleX to host the vectors and run the embeddings for you, with no external vector database to provision. It is one of the [knowledge providers](/integrations/knowledge-providers/overview) ModuleX can retrieve from; the others ([Qdrant](/integrations/knowledge-providers/qdrant), [Pinecone](/integrations/knowledge-providers/pinecone), [MongoDB Atlas](/integrations/knowledge-providers/mongodb-atlas), and [Weaviate](/integrations/knowledge-providers/weaviate)) are bring-your-own-key (BYOK) stores that you connect with your own credentials. Choosing `modulexdb` is the difference between **managed** and **BYOK** retrieval. When a [knowledge base](/platform/knowledge/overview) embeds on the managed pool, ModuleX provisions the embedding model, stores the vectors in pgvector, serves retrieval, and **meters that work in [credits](/billing/credits)**. BYOK providers are **never credited** — that usage is billed by your upstream vector store and embedding provider. This page is the **provider-catalog entry** for `modulexdb`: how to select it as a provider for a knowledge base and for a [workflow knowledge node](/workflow-builder/nodes/knowledge), what it costs, and how the billing gate behaves. The exhaustive reference for the managed store itself — the full embedding and chunking schema, the ingest and retrieval pipelines, document and chunk records, and the per-operation REST surface — is [Managed knowledge (modulexdb)](/platform/knowledge/managed). Read that page for the deep mechanics; this page tells you how to wire `modulexdb` in. ## When to choose modulexdb ModuleX hosts the vectors in pgvector and provisions the embedding model. No external store to run. Ingest and retrieval are billed in credits. Best when you want RAG to work out of the box. You bring the vector store and the embedding credential. ModuleX queries it on your behalf. Usage is not credited by ModuleX — your provider bills you directly. Best when you already run a vector database. A knowledge base is **managed** when its `embedding_config.integration_name` is `modulexai` (the managed integration name on the wire). The provider name `modulexdb` is how the same managed store is referenced from a [knowledge node](/workflow-builder/nodes/knowledge) and in the provider catalog. Both point at the same hosted pgvector store; everything on this page about credit cost and the billing gate applies **only** to it. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-4120 · [IMAGE]** **[IMAGE_DESCRIPTION]:** Managed vs BYOK knowledge provider decision diagram for ModuleX. **[IMAGE_DETAILS]:** Two columns. Left: "modulexdb (managed)" with sub-items "ModuleX-hosted pgvector", "managed embeddings", "billed in credits". Right: "BYOK (Qdrant / Pinecone / Atlas / Weaviate)" with "your vector store", "your embedding credential", "not credited by ModuleX". A shared box at the top reads "Knowledge base / knowledge node". Light theme, 16:9, brand palette. ## Selecting modulexdb for a knowledge base You select the managed store at the knowledge-base level through `embedding_config`. Set `integration_name` to `modulexai` and ModuleX treats the knowledge base as managed; on create it also auto-creates a linked credential so [workflow knowledge nodes](/workflow-builder/nodes/knowledge) can reference the knowledge base by `credential_id`. The ModuleX integration that owns the embedding pool. Set to `modulexai` to make the knowledge base **managed** (`modulexdb`, billed in credits). Any other value — for example `openai` or `cohere` — is treated as BYOK and is not credited. This single field is what decides managed vs BYOK. The actual embedding provider behind the managed pool. Validated against `openai`, `cohere`, `azure`, `huggingface`, but only `openai` and `cohere` have an embedding implementation; any other value raises `Unsupported embedding provider` at embed time. Also accepted under the key `provider`. The embedding model. Also accepted under the key `model`. For managed knowledge bases the model resolves through the ModuleX model pool so that ingest-time and search-time vectors come from the same model. The embedding vector dimension. Range `64`–`4096`. The pgvector column is sized for the default `1536` (the `text-embedding-3-small` dimension). Out-of-range values are rejected at create or update with a `KnowledgeBaseValidationError` → `400`. The embedding credential. Also accepted as `provider_credential_id`. If omitted, ModuleX auto-discovers an org credential whose integration exposes an embedding-capable model. If none exists, create fails with `NoEmbeddingCredentialError` → `400`. **Two accepted key spellings.** ModuleX reads `embedding_config` defensively: `provider` or `provider_id`, `model` or `model_id`, `provider_credential_id` or `credential_id`. The managed-vs-BYOK decision is made **only** on `integration_name == "modulexai"`, so a managed knowledge base must set `integration_name`. A managed `embedding_config` looks like this: ```json { "integration_name": "modulexai", "provider_id": "openai", "model_id": "text-embedding-3-small", "dimension": 1536, "credential_id": "a1b2c3d4-0000-4000-8000-000000000001" } ``` For the chunking schema, document and chunk records, storage backends, and the full ingest and retrieval pipelines, see [Managed knowledge (modulexdb)](/platform/knowledge/managed). ## Selecting modulexdb in a workflow knowledge node A [knowledge node](/workflow-builder/nodes/knowledge) retrieves from a provider inside a running workflow. To retrieve from the managed store, set the node's `provider_type` to `modulexdb` and point `credential_id` at the managed knowledge base's linked credential. `modulexdb` is the **default** `provider_type` for a knowledge node. The credential ID for the knowledge base. For `modulexdb` this is the credential ModuleX auto-created when the managed knowledge base was created. ModuleX resolves the knowledge base from the credential's stored `knowledge_base_id`. The knowledge provider. One of `modulexdb`, `qdrant`, `pinecone`, `weaviate`, `mongodb_atlas`. Use `modulexdb` for the managed store. The external values require a BYOK credential and, for most of them, a `collection_name`. Number of results to retrieve. Range `1`–`20`. Minimum cosine-similarity score threshold. Range `0.0`–`1.0`. Matches below this score are dropped. Maximum tokens in the formatted context string, when the node returns a RAG context. Range `100`–`8000`. Collection or index name. **Not used** for `modulexdb` (the managed store needs no collection). Required for external providers such as Qdrant and Pinecone. Namespace for Pinecone or similar providers. **Not used** for `modulexdb`. Embedding configuration for external providers that do not embed internally. **Not used** for `modulexdb`, which carries its own `embedding_config` on the knowledge base. A managed knowledge-node config: ```json { "credential_id": "939f74dc-7b2f-473c-b87d-b95c30c32fd3", "provider_type": "modulexdb", "top_k": 5, "min_score": 0.3 } ``` ## Credit cost of managed knowledge `modulexdb` is the only knowledge provider that consumes [credits](/billing/credits). A credit is the managed-usage billing unit: **100 credits = $1.00** (`1` credit = `$0.01`). Two operations are metered, each as a flat base credit plus the input-only embedding token cost. | Operation | Flat base credit | Plus | When it is charged | | --- | :-: | --- | --- | | **File ingest** | `1` per document | the document's embedding token cost (input-only), recorded by the ingest worker | Once per document, idempotent on the document ID. A retry of an unchanged document is **not** re-charged. | | **Retrieval** | `1` per call | the query-embedding token cost (input-only), recorded on success | Once per `search`, `hybrid-search`, `retrieve-context`, or multi-knowledge-base search. | A multi-knowledge-base search charges **one** retrieval base if **any** queried knowledge base is managed, regardless of order. Creating, listing, or updating a knowledge base is **not** credit-gated. The embedding token cost uses the standard token-metering formula with input tokens only (embeddings have no completion tokens): `cost = prompt_tokens · in_rate / 1e6 · MARGIN · SCALE`, where `MARGIN` is `1.05` (a 5% system margin) and `SCALE` is `100`. BYOK providers run the same searches but record **no** credit cost. For the full credit model, see [Credits & metering](/billing/credits); for what each plan allows, see [Plans & pricing](/billing/plans). ## The billing gate (402 / 403 / 429) Managed ingest and managed retrieval pass through the ModuleX billing admission gate **before** any embedding runs. The gate fails closed: when your plan allowance or wallet is exhausted, a quota is exceeded, or a rate limit is hit, it returns a flat `DenialEnvelope` and **no document or search result is produced**. The `DenialEnvelope` is a flat object — there is no `detail` wrapper — of the shape `{code, layer, key, current, limit, reason}`: The machine-readable denial reason, for example `credit_plan_exhausted`, `quota_exceeded`, or `rate_limit_exceeded`. The gate layer that denied the call. Maps to the HTTP status: `credit` → `402`, `wallet` → `402`, `quota` → `403`, `rate` → `429`. The denied resource key, for example the organization UUID or the rate class. May be `null`. The current usage at the time of denial. May be `null`. The limit that was hit. May be `null`. The denial reason, mirroring `code`. ```json 402 — plan credits exhausted { "code": "credit_plan_exhausted", "layer": "credit", "key": "", "current": null, "limit": 5000.0, "reason": "credit_plan_exhausted" } ``` ```json 403 — quota exceeded { "code": "quota_exceeded", "layer": "quota", "key": "", "current": null, "limit": null, "reason": "quota_exceeded" } ``` ```json 429 — rate limited { "code": "rate_limit_exceeded", "layer": "rate", "key": "api", "current": 500, "limit": 500, "reason": "rate_limit_exceeded" } ``` A `429` carries `Retry-After` (default `60`) and `X-RateLimit-Limit` / `X-RateLimit-Remaining` / `X-RateLimit-Reset` headers. **Two different denial shapes coexist for managed knowledge.** The credit, wallet, quota, and rate gate denials use the flat `DenialEnvelope` above. Document-level limits — a file larger than your plan's `max_file_size_bytes`, exceeding the documents-per-knowledge-base cap, or running out of storage quota — are raised as a `KnowledgeBaseQuotaExceededError` → `403` with the **ordinary** `{detail}` shape, **not** the `DenialEnvelope`. Branch on both. See [Usage gating & limits](/billing/usage-gating) and [Errors & status codes](/api-reference/errors). ## Worked example — search a managed knowledge base Every request authenticates with `Authorization: Bearer mx_live_…` plus the `X-Organization-ID` header (see [Authentication](/api-reference/authentication)). Searching a managed knowledge base reserves `1` retrieval credit before the query is embedded. ```bash cURL curl -X POST https://api.modulex.dev/knowledge-bases/7c1e.../search \ -H "Authorization: Bearer mx_live_xxx" \ -H "X-Organization-ID: org_123" \ -H "Content-Type: application/json" \ -d '{"query":"how to install","top_k":5,"min_score":0.3}' ``` ```python Python import asyncio from modulex import Modulex mx = Modulex(api_key="mx_live_xxx", organization_id="org_123") async def main(): result = await mx.knowledge_bases.search( "7c1e...", query="how to install", top_k=5, min_score=0.3, ) print(result) asyncio.run(main()) ``` ```javascript JavaScript import { Modulex } from "modulex-js"; const mx = new Modulex({ apiKey: "mx_live_xxx", organizationId: "org_123", }); const result = await mx.knowledgeBases.search("7c1e...", { query: "how to install", topK: 5, minScore: 0.3, }); console.log(result); ``` The SDK method names and parameter casing for the knowledge-base search operation are not fully pinned in the research base. The cURL request and its body are verified against the backend; the Python and JavaScript snippets above show the standard ModuleX client shape and should be confirmed against the [JavaScript SDK](/sdks/javascript) and [Python SDK](/sdks/python) references before relying on the exact method names. {/* TODO: confirm knowledge-base search SDK method names/casing against modulex-js and modulex-python */} A successful response returns the matching chunks with cosine-similarity scores. The full request and response schema for every managed knowledge operation — search, hybrid search, retrieve-context, multi-knowledge-base search, ingest, and document management — lives in [Managed knowledge (modulexdb)](/platform/knowledge/managed). ## Limits These document limits apply to every knowledge base, managed or BYOK. | Limit | Value | | --- | --- | | Supported file types | `pdf`, `docx`, `doc`, `txt`, `md`, `html`, `csv`, `json`, `xlsx`, `pptx` | | Documents per knowledge base | `500` | | Per-file size cap | Your plan's `max_file_size_bytes` (Free 10 MB, Pro 50 MB, Max 100 MB, Enterprise unlimited) | | Total org storage | Your plan's storage entitlement (Free 1 GiB, Pro 50 GiB, Max 500 GiB, Enterprise unlimited) | **The per-file cap is a plan entitlement, not a fixed 50 MB.** The `/knowledge-bases/info/supported-file-types` endpoint advertises 50 MB as a fallback, but the enforced cap is your plan value. See [Managing documents](/platform/knowledge/documents). ## Related pages The full technical reference: embedding and chunking schema, ingest and retrieval pipelines, and the per-operation REST surface. Compare modulexdb with the BYOK vector stores. Retrieve from modulexdb inside a workflow. What a credit is and exactly what managed knowledge consumes. --- # MongoDB Atlas Vector Search URL: https://docs.modulex.dev/integrations/knowledge-providers/mongodb-atlas Connect MongoDB Atlas Vector Search to ModuleX as a bring-your-own-key knowledge provider: create the credential from a connection string, configure a workflow knowledge node against a database.collection, and run uncosted $vectorSearch retrieval. Every field, type, default, and error. MongoDB Atlas Vector Search is a **bring-your-own-key (BYOK) knowledge provider** for ModuleX. You connect your own Atlas cluster with a connection string, and ModuleX runs semantic retrieval against an existing collection that already holds vector embeddings — ModuleX does not host or ingest the vectors. The provider name on the wire is `mongodb_atlas`. It is one of the [knowledge providers](/integrations/knowledge-providers/overview) ModuleX can retrieve from. Unlike the managed store [modulexdb](/integrations/knowledge-providers/modulexdb) — where ModuleX hosts the vectors, runs the embeddings, and meters the work in [credits](/billing/credits) — MongoDB Atlas is **BYOK and uncosted**: retrieval runs against your cluster and is **never credited** by ModuleX. Your upstream Atlas and embedding-provider usage is billed by those providers directly. The other BYOK stores are [Qdrant](/integrations/knowledge-providers/qdrant), [Pinecone](/integrations/knowledge-providers/pinecone), and [Weaviate](/integrations/knowledge-providers/weaviate). This page is the **provider-catalog entry** for `mongodb_atlas`: how to connect your cluster, how to configure a [workflow knowledge node](/workflow-builder/nodes/knowledge) to retrieve from it, and the exact request and result shapes. For the broader managed-vs-BYOK model and the RAG concept, see [Knowledge & RAG](/concepts/knowledge-rag) and [External knowledge providers](/platform/knowledge/external-providers). ## What Atlas does in ModuleX MongoDB Atlas is a **retrieval-only** provider in ModuleX. ModuleX queries your cluster's existing vector index; it does not upload documents, create indexes, or chunk and embed content for you. You are responsible for populating the collection with documents that contain an embedding field and for creating an Atlas Vector Search index over that field. Because Atlas requires a precomputed query vector for similarity search, ModuleX embeds the search text first (using an embedding credential you configure) and then runs Atlas's `$vectorSearch` aggregation. Two surfaces use the provider: A [knowledge node](/workflow-builder/nodes/knowledge) with `provider_type` set to `mongodb_atlas` retrieves matching documents inside a running workflow and feeds them to a downstream LLM or agent node. This is the primary BYOK retrieval path. Atlas appears in the [integration catalog](/integrations/catalog) as a `knowledge_provider` with `query`, `list_databases`, and `list_collections` actions and a connection-string credential. The catalog is the browse and credential-setup surface. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-4150 · [IMAGE]** **[IMAGE_DESCRIPTION]:** BYOK retrieval flow from a ModuleX knowledge node to MongoDB Atlas Vector Search. **[IMAGE_DETAILS]:** Left-to-right flow. Box 1 "Knowledge node (provider_type: mongodb_atlas)". Arrow to Box 2 "ModuleX embeds the query text via embedding_config (e.g. OpenAI)". Arrow to Box 3 "Atlas $vectorSearch on database.collection". Arrow back to Box 4 "matches: id, score, content, metadata". A small lock icon labeled "connection string credential" sits over the arrow into Atlas, and a side note reads "BYOK — not credited by ModuleX". Light theme, 16:9, brand palette. ## Connect MongoDB Atlas You connect Atlas by storing a credential that holds your cluster connection string. The credential is created against the `mongodb_atlas` integration with `auth_type` set to `custom`. You can create it in the app or over the API. ### Credential fields The Atlas auth schema has a single field. Your MongoDB Atlas connection string, in `mongodb+srv://...` form (for example `mongodb+srv://user:password@cluster.mongodb.net/`). This value is **sensitive** — it is encrypted at rest and never returned in full by the API. ModuleX uses it to open an async client (`motor`) against your cluster for every retrieval. The connecting database user needs read access to the database and collection you intend to query. The connection string embeds your cluster credentials. Scope the Atlas database user to the data ModuleX needs to read, and rotate the credential if the string is exposed. ModuleX encrypts stored credential data; see [Data security & encryption](/security/data-encryption). ### Connect in the app Connecting a knowledge provider is an org-level write, so it requires the **owner** or **admin** role (see [Roles & permissions](/security/roles-permissions); the `member` role is retired). In the org settings, open the integration catalog, select **MongoDB Atlas Vector Search**, choose **Add credential**, paste the connection string, and save. ModuleX validates the credential by connecting and listing databases before it is stored. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-4151 · [SCREENSHOT]** **[SCREENSHOT_DESCRIPTION]:** The Add-credential dialog for MongoDB Atlas Vector Search. **[SCREENSHOT_DETAILS]:** ModuleX org settings, integration catalog, MongoDB Atlas detail open with the "Add credential" dialog showing the single "Connection String" field (masked sample `mongodb+srv://user:password@cluster.mongodb.net/`), a display-name field, a "Set as default" toggle, and a "Test" button. Light theme, desktop width, no real secrets visible. ### Connect over the API Every request authenticates with `Authorization: Bearer mx_live_…` plus the `X-Organization-ID` header (the backend also accepts `X-API-KEY`); see [Authentication](/api-reference/authentication). Creating a credential requires the owner/admin role. Send `auth_type: "custom"` and put the connection string in `auth_data` keyed by the field name `connection_string`. A successful create returns `201` with a `CredentialResponse`; keep the returned `credential_id` — the knowledge node references it. ```bash cURL curl -X POST https://api.modulex.dev/credentials \ -H "Authorization: Bearer mx_live_xxx" \ -H "X-Organization-ID: org_123" \ -H "Content-Type: application/json" \ -d '{ "integration_name": "mongodb_atlas", "auth_type": "custom", "auth_data": { "connection_string": "mongodb+srv://user:password@cluster.mongodb.net/" }, "display_name": "Atlas production", "make_default": true }' ``` ```python Python import asyncio from modulex import Modulex mx = Modulex(api_key="mx_live_xxx", organization_id="org_123") async def main(): credential = await mx.credentials.create( integration_name="mongodb_atlas", auth_type="custom", auth_data={ "connection_string": "mongodb+srv://user:password@cluster.mongodb.net/", }, display_name="Atlas production", make_default=True, ) print(credential["credential_id"]) asyncio.run(main()) ``` ```javascript JavaScript import { Modulex } from "modulex-js"; const mx = new Modulex({ apiKey: "mx_live_xxx", organizationId: "org_123", }); const credential = await mx.credentials.create({ integrationName: "mongodb_atlas", authType: "custom", authData: { connection_string: "mongodb+srv://user:password@cluster.mongodb.net/", }, displayName: "Atlas production", makeDefault: true, }); console.log(credential.credential_id); ``` The `POST /credentials` request body and the `custom` auth type are verified against the backend. The exact SDK method names and parameter casing for credential creation are not fully pinned in the research base; confirm them against the [JavaScript SDK](/sdks/javascript) and [Python SDK](/sdks/python) references before relying on the snippet shapes. {/* TODO: confirm credentials.create SDK method names/casing against modulex-js and modulex-python */} ### Credential response `POST /credentials` returns a `CredentialResponse`. The connection string is never echoed back. The credential identifier. Pass this as the knowledge node's `credential_id`. `mongodb_atlas`. `knowledge_provider`. The display name you supplied, or a generated default. `custom`. Whether this is the default credential for `mongodb_atlas` in the organization. Creation timestamp. Last-update timestamp. When the credential was last used for a retrieval. `null` until first use. Expiry, if any. Connection-string credentials do not expire on their own. Any metadata you attached at create time. ### Test the connection before saving To validate a connection string without persisting it, call `POST /credentials/test-temporary` with the same `integration_name`, `auth_type`, and `auth_data`. For Atlas, the test connects to the cluster, pings the server, and lists databases; it has no HTTP test endpoint. ```bash cURL curl -X POST https://api.modulex.dev/credentials/test-temporary \ -H "Authorization: Bearer mx_live_xxx" \ -H "X-Organization-ID: org_123" \ -H "Content-Type: application/json" \ -d '{ "integration_name": "mongodb_atlas", "auth_type": "custom", "auth_data": { "connection_string": "mongodb+srv://user:password@cluster.mongodb.net/" } }' ``` The response is a `TestTemporaryCredentialResponse` with `is_valid`, a human-readable `message`, `tested_at`, `test_method`, `integration_name`, and `auth_type`. A failed connection reports `is_valid: false` with the reason in `message`. ## Catalog actions In the [integration catalog](/integrations/catalog), `mongodb_atlas` is a `knowledge_provider` that advertises three actions. These describe the provider's capability in the catalog; retrieval inside a workflow is driven by the [knowledge node](/workflow-builder/nodes/knowledge) config in the next section, which maps onto the `query` action. Performs vector similarity search using MongoDB Atlas `$vectorSearch`. Database name. Collection name. The Atlas Vector Search index name. The query embedding vector. The field path that holds the stored vectors. Number of candidates Atlas considers before returning results. Maximum number of results to return. Pre-filter conditions in MongoDB query-language (MQL) form. Returns an array of objects, each `{_id, score, document}`. Takes no parameters. Returns an array of `{name, sizeOnDisk}` objects. Database name. Returns an array of collection-name strings. ## Configure BYOK retrieval in a knowledge node A [knowledge node](/workflow-builder/nodes/knowledge) retrieves from a provider inside a running workflow. To retrieve from Atlas, set the node's `provider_type` to `mongodb_atlas`, point `credential_id` at your Atlas credential, set `collection_name` to a `database.collection` value, and supply an `embedding_config` so ModuleX can embed the query. ### How retrieval runs When the node executes, ModuleX: It loads the org credential by `credential_id`, decrypts the stored connection string, and opens an async Atlas client. Atlas requires a precomputed query vector, so ModuleX embeds the resolved `query` text using `embedding_config`. If `embedding_config` is missing, the node fails with a validation error. It splits `collection_name` on the first `.` into database and collection, then runs an aggregation: a `$vectorSearch` stage followed by `$addFields` that exposes the `vectorSearchScore` as `score`. The vector field is excluded from results unless vectors are requested. Matches scoring below `min_score` are dropped. Each remaining document becomes a chunk with `content`, `score`, `metadata`, and `id`, then the node formats them per `output_format`. **BYOK retrieval is uncosted.** Unlike [modulexdb](/integrations/knowledge-providers/modulexdb), an Atlas knowledge node consumes **no ModuleX credits** — there is no billing gate on the retrieval. Your Atlas query cost and your embedding-provider token cost are billed by those providers directly. See [Credits & metering](/billing/credits). ### Knowledge-node fields These are the `KnowledgeNodeConfig` fields that apply when `provider_type` is `mongodb_atlas`. Set to `mongodb_atlas` to retrieve from Atlas. One of `modulexdb`, `qdrant`, `pinecone`, `weaviate`, `mongodb_atlas`. The Atlas credential ID from the credential you created. ModuleX decrypts its connection string to reach your cluster. The target collection in **`database.collection`** format (for example `support.kb_chunks`). It must contain the first `.` separator; a value with no `.` fails with a `database.collection format` error. Required for Atlas — a node with no `collection_name` fails before it queries. The search text. Supports `{{nodeId.path}}` references so the query can come from an upstream node's output. ModuleX embeds this text to build the Atlas query vector. When `true`, the workflow input is used as the query instead of the `query` field. **Required** for Atlas. The embedding model ModuleX uses to embed the query text before the vector search. The embedding model and dimension you configure here must match the model and dimension used to build the vectors stored in your Atlas collection, or scores will be meaningless. See the sub-fields below. Number of results to retrieve. Range `1`–`50`. Minimum similarity score threshold. Range `0.0`–`1.0`. Matches from Atlas scoring below this value are dropped. Maximum tokens in the formatted context string, when `output_format` returns a context. Range `100`–`10000`. Provider-specific pre-filter passed through to Atlas as the `$vectorSearch` filter (MQL form). `null` by default. **Not used** for Atlas (it applies to Pinecone-style providers). Leave unset. Filter to specific document IDs. **Native (modulexdb) knowledge bases only** — has no effect on Atlas. Use `filters` for Atlas pre-filtering. How the node returns results. One of `chunks` (individual matches with metadata), `context` (a single formatted RAG string), or `both`. Include each document's metadata (every field except `_id`, `score`, and the vector field) in the results. Include source-document info in the formatted context string. The integration that owns the embedding provider, for example `openai` or `cohere`. The provider identifier, for example `openai`. The embedding model, for example `text-embedding-3-small`. Match this to the model that produced the vectors stored in your Atlas collection. The credential for the embedding provider. If omitted, ModuleX uses the organization's default credential for `integration_name`. ### Atlas index defaults ModuleX builds the `$vectorSearch` stage with these defaults. Make sure your Atlas Vector Search index and stored documents match them, or override the index in your collection setup accordingly. | Setting | Default ModuleX uses | Notes | | --- | --- | --- | | Vector Search index name | `vector_index` | The index name passed to `$vectorSearch`. Create your Atlas index with this name. | | Vector field path | `embedding` | The document field that holds the stored vectors. | | `numCandidates` | `top_k × 10` | Candidate pool size; scales with `top_k`. | | `limit` | `top_k` | Results returned by the aggregation. | ModuleX queries Atlas with a fixed index name of `vector_index` and a fixed vector field of `embedding` for the knowledge-node retrieval path. Your collection must have an Atlas Vector Search index named `vector_index` over a field named `embedding`. {/* TODO: confirm whether index_name/vector_field are surfaced as overridable node-config fields in the UI; the workflow-engine call uses the adapter defaults */} ### Example node config ```json { "provider_type": "mongodb_atlas", "credential_id": "939f74dc-7b2f-473c-b87d-b95c30c32fd3", "collection_name": "support.kb_chunks", "query": "{{trigger.question}}", "top_k": 5, "min_score": 0.3, "output_format": "context", "embedding_config": { "integration_name": "openai", "provider_id": "openai", "model_id": "text-embedding-3-small" } } ``` ## Retrieval results Each Atlas match is normalized into a chunk before the node formats it. The matched document's `_id`, stringified. The Atlas `vectorSearchScore` for the match. Matches below `min_score` are excluded. The text content extracted from the document. ModuleX looks for a text value in common fields — `content`, `text`, `chunk_text`, `page_content`, `data`, `body`, `summary`, `document` — and returns the first match, or `null` if none is present. Every document field except `_id`, `score`, and the vector field. Present only when `include_metadata` is `true`. The node then returns `chunks`, a formatted `context` string, or `both`, per `output_format`. The downstream LLM or agent node consumes this as RAG context. ## Errors Atlas retrieval runs inside the workflow executor, so failures surface as the node failing the run rather than as an HTTP envelope. Because BYOK retrieval is uncosted, there is **no** `DenialEnvelope` (`{code, layer, …}`) on this path — that envelope only appears on managed (`modulexdb`) knowledge operations. The credential-management endpoints return the standard `{detail}` `HTTPException` shape. See [Errors & status codes](/api-reference/errors). | Condition | What you see | | --- | --- | | `collection_name` missing | The node fails with `collection_name is required for external provider`. | | `collection_name` has no `.` | `Collection name must be in 'database.collection' format`. | | `embedding_config` missing | `embedding_config required for providers that don't handle embeddings`. | | No query vector reached Atlas | `MongoDB Atlas requires query_vector for search`. | | Bad or unauthorized connection string | `MongoDB authentication failed: …` (an authentication error). | | Cluster unreachable / `motor` not installed | `Failed to connect to MongoDB: …` (a connection error). | | Aggregation failure (for example a missing or misnamed Atlas index) | `MongoDB query failed: …` (a query error). | | Credential not found in the org | `Credential not found: `. | The `motor` driver must be available in the runtime for Atlas connections. If it is missing, connections fail with an instruction to install it. This is an environment dependency of the ModuleX backend, not something you configure per credential. ## Related pages Compare MongoDB Atlas with modulexdb and the other BYOK vector stores. The managed alternative: ModuleX hosts the vectors and meters usage in credits. Retrieve from Atlas inside a workflow. The product overview of bringing your own vector store. How ModuleX stores and resolves integration credentials. The retrieval-augmented-generation model behind knowledge nodes. --- # Knowledge providers: managed and external vector stores URL: https://docs.modulex.dev/integrations/knowledge-providers/overview The vector stores ModuleX retrieves from for RAG: the managed modulexdb store versus bring-your-own Qdrant, Pinecone, MongoDB Atlas, and Weaviate, with the exact connection fields, the knowledge node contract, and how retrieval is billed. A knowledge provider is the vector store ModuleX searches when a workflow or chat needs retrieval. ModuleX supports two families: the **managed store** (`modulexdb`), where ModuleX hosts your embeddings and chunks and bills retrieval in credits, and **external (bring-your-own) stores** — Qdrant, Pinecone, MongoDB Atlas, and Weaviate — that you connect with your own credentials and query without ModuleX markup. This page is the exhaustive reference for both: how each is identified, the exact fields you supply to connect, the knowledge node contract that consumes them, and the retrieval billing model. For the conceptual model of retrieval-augmented generation in ModuleX, see [Knowledge & RAG](/concepts/knowledge-rag). To create and manage a managed knowledge base in the app, see [Knowledge overview](/platform/knowledge/overview) and [Managed knowledge (modulexdb)](/platform/knowledge/managed). To wire a provider into a graph, see the [Knowledge node](/workflow-builder/nodes/knowledge). Knowledge bases and their provider credentials are **organization-scoped**. Every knowledge endpoint and every credential operation requires the **owner** or **admin** role (`organization_admin_required`); the `member` role is retired. The organization is selected by the `X-Organization-ID` header on every request — see [Org context & X-Organization-ID](/security/org-context). ## The two provider families A provider is identified on the wire by a `provider_type` value. The backend enumerates exactly five (`KnowledgeProviderType`): one managed and four external. | `provider_type` | Family | Display name | How you connect | Retrieval billing | | --- | --- | --- | --- | --- | | `modulexdb` | Managed | modulexdb (managed) | Auto-created when you create a knowledge base | Billed in credits | | `qdrant` | External (BYOK) | Qdrant | `custom` credential (URL + API key) | Uncosted | | `pinecone` | External (BYOK) | Pinecone | `custom` credential (API key + environment) | Uncosted | | `mongodb_atlas` | External (BYOK) | MongoDB Atlas Vector Search | `custom` credential (connection string) | Uncosted | | `weaviate` | External (BYOK) | Weaviate | `custom` credential (URL + API key) | Uncosted | This page is the cross-provider reference. For the field-by-field connect steps, open the page for your store: [modulexdb (managed)](/integrations/knowledge-providers/modulexdb), [Qdrant](/integrations/knowledge-providers/qdrant), [Pinecone](/integrations/knowledge-providers/pinecone), [MongoDB Atlas](/integrations/knowledge-providers/mongodb-atlas), and [Weaviate](/integrations/knowledge-providers/weaviate). ### Managed: modulexdb The managed store is the default. When you create a knowledge base without choosing an external provider, ModuleX provisions a `modulexdb` knowledge base: it parses, chunks, and embeds your documents, stores the chunk vectors in its own pgvector-backed store, and serves vector, hybrid, and RAG-context search over them. You do not connect anything — the store is created for you, and an internal credential is linked to the knowledge base automatically (the "native knowledge base = credential" pattern). A knowledge base counts as **managed** when its `embedding_config.integration_name` is `modulexai`. Managed retrieval and managed ingest are metered in credits (see [Retrieval billing](#retrieval-billing) below). Choose modulexdb when you want ModuleX to own ingestion and storage end to end and you do not already run a vector database. ### External: bring your own vector store An external provider connects ModuleX to a vector database you already operate. ModuleX does **not** ingest or store documents for an external provider — you populate and maintain your own collection/index, and ModuleX only **queries** it at run time. Because ModuleX provides no storage and no embeddings for these, external retrieval is **uncosted** (your vector-database vendor and your embedding provider bill you directly — this is bring-your-own-key usage; see [BYOK in the glossary](/reference/glossary)). All four external providers connect with a `custom` auth credential. Each declares its own connection fields and exposes catalog actions (`query`, plus a list/describe pair) so the catalog UI can show what the provider can do. Choose an external provider when you already have vectors in Qdrant, Pinecone, MongoDB Atlas, or Weaviate and want to retrieve over them without re-ingesting into ModuleX. **Important capability boundary.** External providers query an existing collection; they have no ModuleX-side ingestion pipeline. The document upload, chunking, embedding, and processing flows described in [Managing documents](/platform/knowledge/documents) apply to **managed** knowledge bases only. You are responsible for keeping an external collection populated and its embedding model consistent with the one you configure on the ModuleX side. ## How a provider is connected The connection mechanism differs by family. Create a knowledge base — in the app under Knowledge, or with `POST /knowledge-bases`. ModuleX provisions the managed store, auto-discovers an embedding-capable credential if you do not supply one, and links an internal `modulexdb` credential to the new knowledge base. There is nothing else to connect. See [Build a RAG knowledge base](/guides/build-a-knowledge-base). Create a `custom` credential for the provider with its connection fields (URL, API key, connection string — see the per-provider tables below), in the app under Settings → Credentials, or with `POST /credentials`. ModuleX encrypts the secret at rest and tests the connection before saving where a test endpoint is declared. See [Managing credentials](/integrations/managing-credentials). In a workflow, add a [Knowledge node](/workflow-builder/nodes/knowledge), set its `provider_type`, and point `credential_id` at the credential (or, for managed, the auto-linked knowledge-base credential). For external providers, also set `collection_name` and an `embedding_config`. The node configuration is documented in [The knowledge node contract](#the-knowledge-node-contract) below. ### Creating an external-provider credential External providers use the `custom` auth schema. Create the credential with the provider's `name` and an `auth_data` object holding its fields. Auth in every request follows the standard model: `Authorization: Bearer mx_live_…` plus `X-Organization-ID`. ```bash cURL curl -X POST https://api.modulex.dev/credentials \ -H "Authorization: Bearer mx_live_xxx" \ -H "X-Organization-ID: org_123" \ -H "Content-Type: application/json" \ -d '{ "integration_name": "qdrant", "auth_type": "custom", "display_name": "Prod Qdrant", "make_default": true, "auth_data": { "url": "https://xyz-abc.aws.cloud.qdrant.io:6333", "api_key": "qdrant-api-key-..." } }' ``` ```python Python from modulex import ModuleX client = ModuleX(api_key="mx_live_xxx", organization_id="org_123") credential = client.credentials.create( integration_name="qdrant", auth_type="custom", display_name="Prod Qdrant", make_default=True, auth_data={ "url": "https://xyz-abc.aws.cloud.qdrant.io:6333", "api_key": "qdrant-api-key-...", }, ) ``` ```javascript JavaScript import { ModuleX } from "modulex"; const client = new ModuleX({ apiKey: "mx_live_xxx", organizationId: "org_123", }); const credential = await client.credentials.create({ integration_name: "qdrant", auth_type: "custom", display_name: "Prod Qdrant", make_default: true, auth_data: { url: "https://xyz-abc.aws.cloud.qdrant.io:6333", api_key: "qdrant-api-key-...", }, }); ``` The exact SDK method surface for credential creation may vary between the JavaScript and Python SDKs; the credential resource is not guaranteed to be at parity across both. Confirm against the [SDK ⇄ API parity matrix](/sdks/parity) and fall back to the REST call above when a method is absent. The cURL request is the authoritative shape. ## Connection fields by provider Each external provider declares its connection fields in its manifest under a `custom` auth schema. The fields below are the exact `auth_data` keys you supply. URL of your Qdrant instance, including the port. Not treated as a secret. Example: `https://xyz-abc.aws.cloud.qdrant.io:6333`. API key for Qdrant Cloud. Optional for local/unauthenticated instances. Stored encrypted. Example format: `qdrant-api-key-...`. ModuleX validates the connection by listing collections (`GET {url}/collections`, expecting a `result` field in the response). Catalog actions: `query`, `list_collections`, `get_collection_info`. Your Pinecone API key. Stored encrypted. Example format: `pcsk_...`. Pinecone environment. Not treated as a secret. Examples: `us-east-1-aws`, `gcp-starter`. ModuleX validates the connection by listing indexes (`GET https://api.pinecone.io/indexes`, expecting an `indexes` field). Catalog actions: `query`, `list_indexes`, `describe_index`. Pinecone queries accept an optional `namespace`. MongoDB Atlas connection string. Stored encrypted (it embeds your credentials). Format: `mongodb+srv://user:password@cluster.mongodb.net/`. ModuleX validates by listing databases. Catalog actions: `query`, `list_databases`, `list_collections`. A query targets a `database`, `collection`, vector-search `index_name`, and the `path` field that holds the vectors. URL of your Weaviate instance. Not treated as a secret. Example: `https://your-cluster.weaviate.cloud`. API key for Weaviate Cloud. Optional for local/unauthenticated instances. Stored encrypted. Example format: `weaviate-api-key-...`. ModuleX validates by checking cluster status. Catalog actions: `query`, `list_classes`, `get_class_stats`. Weaviate is the one provider that can search from text directly (`nearText`) when a `text2vec` module is enabled, so a query embedding may be optional; otherwise supply a `query_vector`. The connection fields above arrive in each manifest under a `fields` array, not the `setup_environment_variables` array used by tool and LLM-provider auth schemas. If you read the raw manifest from the catalog API, expect `fields` for the four knowledge providers. The manifest's `provider_type` value (`external`) is dropped during catalog sync and never appears in API responses — do not rely on it. ## Browsing providers in the catalog The catalog exposes knowledge providers as read-only metadata for discovery; it is not the execution surface. List providers with `GET /integrations/knowledge-providers` or fetch one with `GET /integrations/knowledge-providers/{provider_name}`. Both require the owner or admin role and `X-Organization-ID`. The detail endpoint returns the provider's `actions` and its `auth_schemas` (with OAuth flags enriched where applicable — not relevant to these `custom`-auth providers). ```bash cURL curl 'https://api.modulex.dev/integrations/knowledge-providers' \ -H 'Authorization: Bearer mx_live_xxx' \ -H 'X-Organization-ID: org_123' ``` ```python Python from modulex import ModuleX client = ModuleX(api_key="mx_live_xxx", organization_id="org_123") providers = client.integrations.list(type="knowledge_provider") ``` ```javascript JavaScript import { ModuleX } from "modulex"; const client = new ModuleX({ apiKey: "mx_live_xxx", organizationId: "org_123" }); const providers = await client.integrations.list({ type: "knowledge_provider" }); ``` Unique provider id, e.g. `qdrant`, `pinecone`, `mongodb_atlas`, `weaviate`. Human-readable name, e.g. `MongoDB Atlas Vector Search`. Short summary of the provider. Always `knowledge_provider` for these entries. Tags such as `Vector Database`, `semantic-search`. The provider's catalog actions (returned by the detail endpoint), each with `name`, `description`, `parameters`, and a derived `output_schema`. Action id, e.g. `query`, `list_indexes`. What the action does. Map of parameter name to `{type, description, required, default}`. JSON-schema description of the action's result shape. The auth variants the provider supports. For all four external providers this is a single `custom` schema whose connection fields live under `fields` (see the warning above). The catalog SDK method names and exact filter arguments may differ between SDKs and are not guaranteed to be at parity. Treat the cURL request as authoritative; verify SDK signatures against the [SDK ⇄ API parity matrix](/sdks/parity). ## The knowledge node contract A [Knowledge node](/workflow-builder/nodes/knowledge) is how a provider is consumed inside a workflow. The node configuration (`KnowledgeNodeConfig`) is shared across families; a handful of fields apply only to external providers. Credential for the knowledge base. For managed, this is the internal credential auto-linked to the knowledge base; for external, the `custom` credential you created for the provider. One of `modulexdb`, `qdrant`, `pinecone`, `weaviate`, `mongodb_atlas`. The search query. Supports `{{nodeId.path}}` references to upstream node outputs for dynamic queries. When `true`, use the workflow input as the query instead of the `query` field. Collection/index/class name on the external store. **Required for external providers**; ignored by modulexdb. Namespace for Pinecone (or similar). External only. Number of results to retrieve. Range 1–50. Minimum similarity-score threshold, 0.0–1.0. Score scales are normalized per provider, so the effective floor differs by vector store. Maximum tokens in the formatted context string (for the `context` output format). Range 100–10000. Provider-specific filter conditions applied to the search. Restrict retrieval to specific document ids. **Managed knowledge bases only.** One of `chunks` (individual chunks with metadata), `context` (a single formatted RAG context string), or `both`. Include chunk metadata in results. Include source-document info in the formatted context. Embedding settings for providers that do not embed internally. **Required for external vector databases** (Qdrant, Pinecone, MongoDB Atlas, and Weaviate when not using a text vectorizer), because ModuleX must turn the query text into a vector before searching your store. Embedding provider integration, e.g. `openai`, `cohere`. Provider identifier, e.g. `openai`. Embedding model id, e.g. `text-embedding-3-small`. Credential for the embedding provider. If omitted, the organization's default credential for that integration is used. Match the embedding model in `embedding_config` to the model your external collection was indexed with. ModuleX embeds the query with the model you configure; if it differs from the one used to build the collection (or the dimension does not match), similarity scores are meaningless and retrieval returns poor or empty results. For managed (modulexdb) knowledge bases this is handled for you — ingest and query use the same configured embedding model. **Known limitation.** External-provider retrieval is implemented in the workflow engine's knowledge node, which embeds the query (per `embedding_config`) and calls the provider adapter. A separate, secondary executor helper path does **not** yet support external providers and returns "External provider not yet supported" if it is reached. If an external-provider knowledge node returns that error, treat it as a known gap; see [Known limitations](/reference/known-limitations). Managed (modulexdb) retrieval is supported on every path. ## Retrieval billing Whether a provider is metered depends entirely on the family. | Operation | Managed (modulexdb) | External (BYOK) | | --- | --- | --- | | Document ingest (parse → chunk → embed) | `FILE_INGEST_BASE = 1` credit, plus per-chunk embedding token cost | Not applicable — you ingest into your own store | | Retrieval (search / hybrid / retrieve-context) | `RETRIEVAL_BASE = 1` credit, plus query-embedding token cost | Uncosted by ModuleX | A knowledge base is billed only when it is managed — that is, when `embedding_config.integration_name == "modulexai"`. One credit equals $0.01. External (BYOK) retrieval is not credited; your vector-database vendor and your embedding provider bill you directly. For the full credit model see [Credits & metering](/billing/credits). On the interactive knowledge API, a managed-store credit or rate denial returns the flat billing `DenialEnvelope` as **402 / 403 / 429** (reject-before-write — no search runs). Inside a workflow run, the managed-retrieval gate is best-effort so a billing hiccup never crashes an in-flight run. The error envelope shapes are documented on [Errors & status codes](/api-reference/errors); the gate itself on [Usage gating & limits](/billing/usage-gating). **🎬 MEDIA PLACEHOLDER · MX-MEDIA-4110 · [IMAGE]** **[IMAGE_DESCRIPTION]:** Decision diagram contrasting the managed modulexdb path with the external BYOK path. **[IMAGE_DETAILS]:** Two side-by-side lanes. Left lane "Managed (modulexdb)": documents -> ModuleX ingest (parse/chunk/embed) -> pgvector store -> retrieval, with a credit-coin icon on ingest and retrieval. Right lane "External (BYOK)": your own collection in Qdrant/Pinecone/Atlas/Weaviate -> ModuleX embeds query -> adapter query -> results, marked "uncosted by ModuleX". Light and dark variants; 16:9; label the wire identifiers modulexdb / qdrant / pinecone / mongodb_atlas / weaviate. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-4111 · [SCREENSHOT]** **[SCREENSHOT_DESCRIPTION]:** The credential-add dialog connecting an external knowledge provider. **[SCREENSHOT_DETAILS]:** Capture Settings -> Credentials -> Add credential with a knowledge provider (e.g. Qdrant) selected, showing the custom connection fields (URL, API key) and the test-before-save state. Dark theme; crop to the dialog; redact any real secret values. ## Next steps Set up ModuleX-hosted storage and retrieval, billed in credits. Bring your own Qdrant, Pinecone, MongoDB Atlas, or Weaviate. Wire a provider into a workflow and shape its output. End to end: create a knowledge base, ingest documents, and query it. --- # Pinecone vector store (BYOK) URL: https://docs.modulex.dev/integrations/knowledge-providers/pinecone Connect Pinecone as a bring-your-own vector store in ModuleX: store an API key credential, configure a knowledge node, and run BYOK vector retrieval inside workflows. Pinecone is a managed vector database you can bring to ModuleX as an external [knowledge provider](/integrations/knowledge-providers/overview). When you connect Pinecone, ModuleX queries **your** Pinecone index at retrieval time using a stored credential — your vectors stay in your Pinecone project, and Pinecone bills you directly. This is the **bring-your-own-key (BYOK)** model: unlike [managed knowledge (modulexdb)](/integrations/knowledge-providers/modulexdb), BYOK retrieval through Pinecone is **not** metered in ModuleX credits. Use this page to store a Pinecone credential, configure a [knowledge node](/workflow-builder/nodes/knowledge) to point at your index, and understand exactly how ModuleX issues the query. Pinecone is a **retrieval-only** provider in ModuleX. ModuleX queries an index you already populated; it does **not** upload, chunk, embed, or ingest documents into Pinecone for you. Document ingest (parse, chunk, embed) only applies to [managed knowledge bases](/platform/knowledge/managed). For Pinecone you own the indexing pipeline. ## How BYOK retrieval works At a high level, a Pinecone-backed retrieval inside a workflow runs in five steps: The [knowledge node](/workflow-builder/nodes/knowledge) reads its `credential_id`, loads the matching org credential, and decrypts the stored `auth_data` (your `api_key` and `environment`). Pinecone stores vectors but does not embed text. ModuleX generates a query embedding first, using the node's `embedding_config` (for example OpenAI `text-embedding-3-small`). If `embedding_config` is missing, the node fails before any call to Pinecone. ModuleX `POST`s the query vector to your Pinecone index endpoint with your `Api-Key` header, requesting `topK` matches in the configured `namespace`, with any metadata `filter` applied. Each Pinecone match is mapped into a standard result with `id`, `score`, `content` (pulled from common metadata text fields), and `metadata`. Matches below `min_score` are dropped client-side. Results are returned to the workflow as `chunks`, a formatted `context` string, or `both`, depending on the node's `output_format`. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-4140 · [IMAGE]** **[IMAGE_DESCRIPTION]:** BYOK retrieval flow diagram for Pinecone. **[IMAGE_DETAILS]:** Horizontal flow: workflow knowledge node to "embed query (embedding_config)" to ModuleX backend to your Pinecone index, then matches flowing back and being normalized to chunks/context. Label the embedding step "billed only if the embedding provider is managed" and the Pinecone call "BYOK, not metered in ModuleX credits". Light theme, 16:9, annotated. ## Before you start You will need the following from your own Pinecone account: - A **Pinecone API key**. Pinecone keys look like `pcsk_...`. - The **environment** for your project (for example `us-east-1-aws` or `gcp-starter`). - An existing, **populated index** whose vector dimension matches the embedding model you plan to use in ModuleX. A 1536-dimension index pairs with OpenAI `text-embedding-3-small`; mismatched dimensions cause Pinecone to reject the query. - The index `namespace` you want to search, if you use namespaces. You also need the **owner** or **admin** role in the ModuleX organization. Credential and knowledge routes require an admin/owner role; the retired `member` role cannot manage them. See [roles & permissions](/security/roles-permissions). ## Connect Pinecone Connecting Pinecone means storing an encrypted credential in your organization. ModuleX persists it as a **custom** credential (`auth_type` is `custom`) under the `pinecone` integration, which the catalog classifies as a `knowledge_provider`. ### Credential fields Pinecone declares a single `custom` auth schema with two fields: Your Pinecone API key. Stored encrypted and never returned in plaintext. Sample format `pcsk_...`. This is the `Api-Key` header value ModuleX sends on every request to Pinecone. Your Pinecone environment, for example `us-east-1-aws` or `gcp-starter`. ModuleX uses it to build the Pinecone host URL (see [Index URL resolution](#index-url-resolution)). Optional, undocumented in the connect UI. If present in `auth_data`, this exact URL overrides the environment-derived index host. Set this when your index host does not match the legacy `environment`-based pattern — for example for serverless indexes. See [Index URL resolution](#index-url-resolution) for why this matters. {/* TODO: index_host is read by pinecone_adapter.py:59-60 but is not a declared manifest field; confirm whether the connect UI exposes it. */} ### Connect in the app In the ModuleX app, go to the integrations area and select **Pinecone** under knowledge providers. Paste your Pinecone **API key** and **environment**, give the credential a display name, and optionally mark it as the default for Pinecone. ModuleX validates the credential by listing your indexes (`GET https://api.pinecone.io/indexes`). A `200` with an `indexes` field means the key works. This test is free. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-4141 · [SCREENSHOT]** **[SCREENSHOT_DESCRIPTION]:** The Pinecone connect form in the ModuleX app. **[SCREENSHOT_DETAILS]:** Show the Pinecone knowledge-provider credential form with the API key field (masked, placeholder `pcsk_...`), the environment field (placeholder `us-east-1-aws`), a display-name field, and a "test connection" action. Capture the success state after testing. Light theme. ### Connect via the API Create the credential with [`POST /credentials`](/api-reference/overview). The request body is read raw and the credential type is inferred from `integration_name`, `auth_type`, and `auth_data`. For Pinecone, send `auth_type: custom` with both fields nested under `auth_data`. ModuleX resolves `integration_type` to `knowledge_provider` from the catalog automatically. All requests authenticate with `Authorization: Bearer mx_live_…` plus the `X-Organization-ID` header. See [authentication](/api-reference/authentication). ```bash cURL curl -X POST https://api.modulex.dev/credentials \ -H "Authorization: Bearer mx_live_xxx" \ -H "X-Organization-ID: org_123" \ -H "Content-Type: application/json" \ -d '{ "integration_name": "pinecone", "auth_type": "custom", "display_name": "Production Pinecone", "make_default": true, "auth_data": { "api_key": "pcsk_xxx", "environment": "us-east-1-aws" } }' ``` ```python Python import httpx resp = httpx.post( "https://api.modulex.dev/credentials", headers={ "Authorization": "Bearer mx_live_xxx", "X-Organization-ID": "org_123", }, json={ "integration_name": "pinecone", "auth_type": "custom", "display_name": "Production Pinecone", "make_default": True, "auth_data": { "api_key": "pcsk_xxx", "environment": "us-east-1-aws", }, }, ) resp.raise_for_status() credential = resp.json() print(credential["credential_id"]) ``` ```javascript JavaScript const resp = await fetch("https://api.modulex.dev/credentials", { method: "POST", headers: { Authorization: "Bearer mx_live_xxx", "X-Organization-ID": "org_123", "Content-Type": "application/json", }, body: JSON.stringify({ integration_name: "pinecone", auth_type: "custom", display_name: "Production Pinecone", make_default: true, auth_data: { api_key: "pcsk_xxx", environment: "us-east-1-aws", }, }), }); if (!resp.ok) throw new Error(`Create credential failed: ${resp.status}`); const credential = await resp.json(); console.log(credential.credential_id); ``` Creating, listing, and querying a Pinecone credential is a plain CRUD operation, so it returns the standard `{detail}` error envelope on failure (for example a `400` `"Invalid auth_data or auth_type…"` if the body is malformed). The flat billing `DenialEnvelope` does **not** apply here. See [errors & status codes](/api-reference/errors) for the full envelope reference. Pinecone credentials are stored with `auth_type` `custom`, so they appear in your credential list (only `internal` credentials are hidden). #### Create-credential request fields Must be `pinecone`. ModuleX looks this up in the integration catalog to resolve `integration_type` to `knowledge_provider`. An unknown name falls back to `tool`. Must be `custom` for Pinecone. This routes the request to the custom-credential path, which stores arbitrary `auth_data` fields. The Pinecone credential fields. Must not be empty. Carries `api_key` and `environment` (and optionally `index_host`). A human-readable label for the credential. Defaults to `Pinecone Custom Credential` if omitted. When `true`, sets this credential as the default for the `pinecone` integration, unsetting any prior default. #### Test the saved credential Validate a stored credential against Pinecone with `POST /credentials/{credential_id}/test`. ModuleX calls the integration's test endpoint — `GET https://api.pinecone.io/indexes` — and reports whether the key is valid. ```bash cURL curl -X POST https://api.modulex.dev/credentials/cred_abc/test \ -H "Authorization: Bearer mx_live_xxx" \ -H "X-Organization-ID: org_123" ``` ```python Python import httpx resp = httpx.post( "https://api.modulex.dev/credentials/cred_abc/test", headers={ "Authorization": "Bearer mx_live_xxx", "X-Organization-ID": "org_123", }, ) print(resp.json()["is_valid"]) ``` ```javascript JavaScript const resp = await fetch( "https://api.modulex.dev/credentials/cred_abc/test", { method: "POST", headers: { Authorization: "Bearer mx_live_xxx", "X-Organization-ID": "org_123", }, }, ); const result = await resp.json(); console.log(result.is_valid); ``` A successful test response has the shape `{credential_id, is_valid, message, tested_at}`. ## Configure a knowledge node Once the credential exists, point a [knowledge node](/workflow-builder/nodes/knowledge) at your Pinecone index by setting `provider_type` to `pinecone` and supplying the index name, an embedding configuration, and the credential. Because Pinecone stores only vectors, the knowledge node **requires** an `embedding_config` for Pinecone. ModuleX uses it to embed the query text before calling Pinecone. If `embedding_config` is omitted, the node raises a validation error and never reaches Pinecone. ### Knowledge node fields for Pinecone The `credential_id` of your stored Pinecone credential. Set to `pinecone`. Valid knowledge provider types are `modulexdb`, `qdrant`, `pinecone`, `weaviate`, and `mongodb_atlas`. The Pinecone **index name** to search. ModuleX uses Pinecone's `index_name` terminology here as `collection_name`. Required for all external providers; the node fails with `"collection_name is required for external provider: pinecone"` if it is missing. Embedding settings used to turn the query text into a vector. Required for Pinecone because Pinecone does not embed text. See [Embedding configuration](#embedding-configuration) for fields. Use a model whose output dimension matches your Pinecone index. The search query. Supports `{{nodeId.path}}` references so the query can come from an earlier node's output. When `true`, the workflow input is used as the query instead of the `query` field. The Pinecone namespace to search within the index. Omit to search the default namespace. Number of matches to return. Range 1–50. Sent to Pinecone as `topK`. Minimum similarity score, 0.0–1.0. Pinecone has no native score threshold, so ModuleX drops matches below this value **after** Pinecone returns them. The score is Pinecone's raw match score for your index's metric; ModuleX does not re-normalize it. A Pinecone metadata filter, passed through to Pinecone's query `filter` field verbatim. Use Pinecone filter syntax, for example `{"category": {"$eq": "docs"}}`. Token budget for the formatted context string. Range 100–10000. Only applies when `output_format` is `context` or `both`. How results are returned: `chunks` (individual matches with metadata), `context` (a single RAG-ready string), or `both`. Include each match's metadata in the result. ModuleX requests `includeMetadata` from Pinecone accordingly. Include source document headers in the formatted `context` output. Filter to specific document IDs. **Native (modulexdb) KB only** — ignored for Pinecone. Use `filters` for Pinecone-side metadata filtering instead. ### Embedding configuration The `embedding_config` object tells ModuleX which model to use when embedding the query. The model's output dimension must match your Pinecone index dimension. The embedding provider integration, for example `openai` or `cohere`. The provider identifier, for example `openai`. The embedding model id, for example `text-embedding-3-small` (1536 dimensions) or `text-embedding-3-large` (3072 dimensions). Credential for the embedding provider. If `null`, ModuleX uses your organization's default credential for that integration. Dimension mismatch is the most common Pinecone failure. If your index was created at 1536 dimensions, the `embedding_config.model_id` must produce 1536-dimension vectors. A mismatch produces a Pinecone query error surfaced as a `500` with the message `"Pinecone query failed: …"`. ### Example knowledge node configuration A workflow knowledge node configured for Pinecone looks like this: ```json Pinecone knowledge node config { "credential_id": "cred_abc", "provider_type": "pinecone", "collection_name": "product-docs", "namespace": "v1", "query": "{{trigger.question}}", "top_k": 5, "min_score": 0.3, "filters": { "category": { "$eq": "docs" } }, "output_format": "context", "embedding_config": { "integration_name": "openai", "provider_id": "openai", "model_id": "text-embedding-3-small", "credential_id": null } } ``` **🎬 MEDIA PLACEHOLDER · MX-MEDIA-4142 · [SCREENSHOT]** **[SCREENSHOT_DESCRIPTION]:** Knowledge node inspector configured for Pinecone. **[SCREENSHOT_DETAILS]:** Capture the knowledge node inspector with provider type set to Pinecone, the index (collection) name, namespace, top_k, min_score, and the embedding model selector populated. Highlight that an embedding model is required. Light theme. ## How ModuleX queries Pinecone When the knowledge node runs, ModuleX builds and sends the Pinecone query directly. Understanding the exact request helps when debugging connectivity or 404s. ### Request to Pinecone ModuleX `POST`s to `{index_url}/query` with these headers: ```http Headers sent to Pinecone Api-Key: Content-Type: application/json ``` and this body: ```json Pinecone query body { "vector": [0.0123, -0.0456, "..."], "topK": 5, "includeMetadata": true, "includeValues": false, "namespace": "v1", "filter": { "category": { "$eq": "docs" } } } ``` `namespace` and `filter` are only included when set. The request times out after 30 seconds. ### Index URL resolution ModuleX builds the index host in this order: 1. If your credential `auth_data` contains `index_host`, ModuleX uses that exact URL. 2. Otherwise, ModuleX derives the host from the environment as `https://{index_name}-{environment}.svc.pinecone.io`. The connection test and `list_indexes` instead call the controller URL `https://controller.{environment}.pinecone.io/indexes`, while the saved-credential test uses `https://api.pinecone.io/indexes`. The environment-derived host (`https://{index_name}-{environment}.svc.pinecone.io`) and the controller host (`https://controller.{environment}.pinecone.io`) follow Pinecone's **legacy pod-based** URL conventions. Pinecone **serverless** indexes use a different host. If your queries return connection errors or `404` despite a valid key, set `index_host` in `auth_data` to the exact host shown in your Pinecone console. {/* TODO: confirm the connect UI exposes index_host; today it is read by the adapter (pinecone_adapter.py:59-60) but not declared in the manifest fields. */} ### Normalized result ModuleX maps each Pinecone match into a standard result object before formatting: The Pinecone match id (the vector id). Pinecone's raw similarity score for the match. Matches below `min_score` are dropped before this point. Text extracted from the match metadata. ModuleX checks common metadata fields in order: `content`, `text`, `chunk_text`, `page_content`, `data`, `body`, `summary`, `document`. If none are present, `content` is empty — store your chunk text under one of these keys in Pinecone metadata so retrieved chunks carry usable text. The match metadata, included when `include_metadata` is `true`. The stored vector values, included only when vector values are requested. These results are then shaped into `chunks`, `context`, or `both` per the node's `output_format`. ## Billing BYOK retrieval through Pinecone is **not metered in ModuleX credits**. Only knowledge bases whose embedding provider is managed (`modulexdb` / `modulexai`) reserve retrieval credits. Two cost notes still apply: - **Pinecone bills you directly** for queries and storage on your own account. - **The query embedding may be metered.** If your `embedding_config` uses a managed embedding model, generating the query vector is billed in ModuleX credits like any other managed model call. Using a BYOK embedding credential avoids that. See [credits & metering](/billing/credits). ## Errors and edge cases A bad or revoked key returns `401` from Pinecone. ModuleX surfaces this as an authentication error (`"Invalid Pinecone API key"`). Re-test the credential with `POST /credentials/{credential_id}/test` and rotate the key in Pinecone if needed. If Pinecone returns `404` for the query, ModuleX raises `"Index not found: "`. Verify the `collection_name` matches an index in your project, and check the [Index URL resolution](#index-url-resolution) rules — a serverless index on the legacy host pattern is the usual cause. Set `index_host` to fix it. Pinecone requires a pre-computed query vector. Without `embedding_config`, the node raises `"embedding_config required for providers that don't handle embeddings…"` and never calls Pinecone. Add an `embedding_config` whose dimension matches your index. The node raises `"collection_name is required for external provider: pinecone"`. Set `collection_name` to your index name. Any other Pinecone error (for example a vector-dimension mismatch) surfaces as a `500` with `"Pinecone query failed: "`. Confirm your embedding model dimension equals the index dimension. Pinecone returns vector ids, scores, and metadata — not separate text. If retrieved chunks have empty `content`, store the chunk text in metadata under one of the recognized keys (`content`, `text`, `chunk_text`, `page_content`, `data`, `body`, `summary`, `document`). ## Provider actions reference Beyond workflow retrieval, the Pinecone integration declares three catalog actions, surfaced through the knowledge-provider catalog and the [tool node](/workflow-builder/nodes/tool). Authentication is the same `custom` API-key schema described above. Performs a vector similarity search on a Pinecone index. **Parameters** Name of the Pinecone index to search. Namespace within the index. Query embedding vector. Number of results to return. Metadata filter conditions. Include metadata in results. Include vector values in results. **Output** Array of matches, each with `id`, `score`, `metadata`, and `values`. The namespace searched. Lists all indexes in your Pinecone project. Takes no parameters. **Output:** an array of indexes, each with `name`, `dimension`, `metric`, and `status`. Returns statistics about one index. **Parameters** Name of the index. **Output** The index vector dimension. Total vectors in the index. Per-namespace vector counts. ## Related pages Compare managed and BYOK vector stores and see all supported providers. Configure retrieval inside a workflow. Create, rotate, and scope credentials in the app and via API. How ModuleX retrieves company knowledge end to end. --- # Qdrant URL: https://docs.modulex.dev/integrations/knowledge-providers/qdrant Connect Qdrant as a bring-your-own vector store in ModuleX: create a Qdrant connection credential, configure a Knowledge node to query your collections, and understand BYOK retrieval with no ModuleX credit cost. Qdrant is a **bring-your-own (BYOK) external knowledge provider** in ModuleX. You connect your own Qdrant instance — Qdrant Cloud or self-hosted — as a [credential](/concepts/credentials-oauth), then point a workflow [Knowledge node](/workflow-builder/nodes/knowledge) at one of your collections to run vector similarity search at run time. Because retrieval runs against your own Qdrant, it is **uncosted** in ModuleX: it consumes **no [credits](/concepts/credits-billing)** and never hits the billing gate. This page covers connecting a Qdrant credential, the connection fields and Knowledge-node configuration, how BYOK retrieval works under the hood, the catalog actions Qdrant exposes, and the errors you can hit. For the cross-provider picture see [Knowledge providers](/integrations/knowledge-providers/overview); for the managed (credit-metered) alternative see [modulexdb](/integrations/knowledge-providers/modulexdb); for the retrieval concept see [Knowledge & RAG](/concepts/knowledge-rag). Qdrant is the integration named `qdrant` on the wire, with `integration_type` `knowledge_provider`. It ships **one auth schema** of type `custom` ("Qdrant Connection"). Unlike [managed knowledge](/platform/knowledge/managed) (the `modulexdb` provider), ModuleX does **not** ingest, chunk, embed, or store documents for you in Qdrant — you own the collections, the points, and the embeddings. ModuleX only **queries** what is already in your Qdrant. ## How Qdrant fits in ModuleX ModuleX treats a vector store one of two ways: ModuleX hosts the vectors, ingests and embeds your documents, and bills retrieval and ingest in [credits](/concepts/credits-billing). See [Managed knowledge](/platform/knowledge/managed). You host the vectors in your own Qdrant. ModuleX queries them through an adapter using a [credential](/concepts/credentials-oauth) you create. Retrieval is **uncosted** — no credits, no billing gate. Qdrant requires a **query vector** — it does not embed text for you. So a Knowledge node pointed at Qdrant first embeds your query with the embedding model you configure (for example an [OpenAI](/integrations/llm-providers/openai) embedding model), then sends that vector to Qdrant's search API. This means a Qdrant Knowledge node needs an `embedding_config`; ModuleX raises a configuration error without one. The embedding model used at query time **must match** the model that produced the vectors already stored in your Qdrant collection — same provider, same model, same dimension. If they differ, Qdrant returns either a dimension-mismatch error or low-quality results. ModuleX does not validate this for you because it never wrote those vectors. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-4130 · [IMAGE]** **[IMAGE_DESCRIPTION]:** Diagram of the BYOK Qdrant retrieval path inside a workflow run. **[IMAGE_DETAILS]:** Show: workflow Knowledge node receives a query string, then "embed query" using the configured embedding model (e.g. OpenAI), producing a vector; the vector plus collection name go to the user's Qdrant instance over its REST API (`POST /collections/{name}/points/search`); matched points return to the node, which formats them as chunks or a context string into run state. Annotate "BYOK — runs on your Qdrant, no ModuleX credits". Light theme, 16:9, left-to-right flow. ## Before you start A Qdrant Cloud cluster or a self-hosted Qdrant that ModuleX can reach over HTTP(S), with at least one collection that already contains embedded points. Creating and managing credentials, and browsing the integration catalog, require the **owner** or **admin** role in your [organization](/concepts/organizations-roles). The retired `member` role cannot do this. See [Roles & permissions](/security/roles-permissions). Every API call sends `Authorization: Bearer mx_live_…` and `X-Organization-ID`. See [Authentication](/api-reference/authentication). Know which embedding model and dimension produced the vectors in your collection, so you can configure the same model for query embedding. ## Connect Qdrant You connect Qdrant by creating a `custom` [credential](/concepts/credentials-oauth) for the `qdrant` integration. The credential stores your Qdrant URL and (optionally) an API key. ModuleX encrypts the credential at rest and never returns the secret material in clear text. Note your Qdrant **URL** (including the port — Qdrant's REST API listens on `6333` by default) and, for Qdrant Cloud, your **API key**. A local instance typically needs no API key. Call `POST /credentials/test-temporary` to check the connection **before** persisting it. ModuleX runs Qdrant's configured test — a `GET {url}/collections` with your `api-key` header — and reports `is_valid`. See [Validate a connection before saving](#validate-a-connection-before-saving). Call `POST /credentials` with `integration_name: "qdrant"`, `auth_type: "custom"`, and an `auth_data` object holding `url` and optional `api_key`. Set `make_default: true` to make it the default Qdrant credential for the organization. Use the returned `credential_id` (plus a `collection_name` and an `embedding_config`) in a workflow [Knowledge node](/workflow-builder/nodes/knowledge). See [Configure a Knowledge node for Qdrant](#configure-a-knowledge-node-for-qdrant). **🎬 MEDIA PLACEHOLDER · MX-MEDIA-4131 · [SCREENSHOT]** **[SCREENSHOT_DESCRIPTION]:** The ModuleX app credential dialog for connecting Qdrant. **[SCREENSHOT_DETAILS]:** Capture the "Connect Qdrant" / "Qdrant Connection" credential form in the integrations/credentials area of the app. Show the Qdrant logo, the **Qdrant URL** field (placeholder `https://xyz-abc.aws.cloud.qdrant.io:6333`), the optional **API Key** field (masked, marked optional, placeholder `qdrant-api-key-...`), the "Set as default" toggle, and the Test/Save buttons. Light theme, 16:9, crop to the dialog. ### Connection fields The Qdrant auth schema is a single `custom` schema named **"Qdrant Connection"** with two fields. Send them inside `auth_data`. The base URL of your Qdrant instance, including the port — for example `https://xyz-abc.aws.cloud.qdrant.io:6333`. A trailing slash is stripped. When omitted at run time the adapter falls back to `http://localhost:6333`, but the field is **required** when you create a credential. Not sensitive. Your Qdrant Cloud API key. Optional — leave it unset for local or unauthenticated instances. Sent to Qdrant as the `api-key` request header. Sensitive; stored encrypted and masked in responses. ### Create the credential `POST /credentials` returns `201`. For Qdrant you must send `auth_type: "custom"` because the connection is not an `api_key`-only schema (the URL is part of the connection). See the full credential model in [Credentials & OAuth2](/concepts/credentials-oauth). Must be `qdrant`. `custom`. The connection material — `{"url": "...", "api_key": "..."}`. `api_key` may be omitted for instances that do not require one. A human-readable label. Defaults to the integration's display name (`Qdrant`) when omitted. Set this credential as the default Qdrant credential for the organization. Only one credential per integration can be the default. **Response** — `201 Created` (secret masked): The credential's UUID. Pass this as `credential_id` in a Knowledge node to use this Qdrant connection. `qdrant`. `knowledge_provider`. `custom`. Whether this is the organization's default Qdrant credential. ISO-8601 timestamp of the last resolution, or `null` if never used. ```bash cURL curl -X POST https://api.modulex.dev/credentials \ -H "Authorization: Bearer mx_live_xxx" \ -H "X-Organization-ID: 11111111-1111-1111-1111-111111111111" \ -H "Content-Type: application/json" \ -d '{ "integration_name": "qdrant", "auth_type": "custom", "display_name": "Production Qdrant", "make_default": true, "auth_data": { "url": "https://xyz-abc.aws.cloud.qdrant.io:6333", "api_key": "qdrant-api-key-abc123" } }' ``` ```python Python from modulex import Modulex async with Modulex( api_key="mx_live_xxx", organization_id="11111111-1111-1111-1111-111111111111", ) as client: credential = await client.credentials.create( "qdrant", auth_type="custom", display_name="Production Qdrant", make_default=True, auth_data={ "url": "https://xyz-abc.aws.cloud.qdrant.io:6333", "api_key": "qdrant-api-key-abc123", }, ) print(credential.credential_id) ``` ```javascript JavaScript import { Modulex } from "modulex-js"; const client = new Modulex({ apiKey: "mx_live_xxx", organizationId: "11111111-1111-1111-1111-111111111111", }); const credential = await client.credentials.create({ integrationName: "qdrant", authType: "custom", displayName: "Production Qdrant", makeDefault: true, authData: { url: "https://xyz-abc.aws.cloud.qdrant.io:6333", api_key: "qdrant-api-key-abc123", }, }); console.log(credential.credential_id); ``` ### Validate a connection before saving `POST /credentials/test-temporary` validates the connection **before** you persist it. ModuleX runs Qdrant's configured test endpoint — `GET {url}/collections`, sending the `api-key` header — and treats HTTP `200` with a `result` field present as success. The test is free (`cost_level: "free"`). `qdrant`. `custom`. `{"url": "...", "api_key": "..."}`. The response reports `is_valid`, a human-readable `message`, `tested_at`, the `test_method`, and the `status_code` returned by Qdrant. ```bash cURL curl -X POST https://api.modulex.dev/credentials/test-temporary \ -H "Authorization: Bearer mx_live_xxx" \ -H "X-Organization-ID: 11111111-1111-1111-1111-111111111111" \ -H "Content-Type: application/json" \ -d '{ "integration_name": "qdrant", "auth_type": "custom", "auth_data": { "url": "https://xyz-abc.aws.cloud.qdrant.io:6333", "api_key": "qdrant-api-key-abc123" } }' ``` ```python Python result = await client.credentials.test_temporary( "qdrant", "custom", { "url": "https://xyz-abc.aws.cloud.qdrant.io:6333", "api_key": "qdrant-api-key-abc123", }, ) print(result.is_valid, result.message) ``` ```javascript JavaScript const result = await client.credentials.testTemporary({ integrationName: "qdrant", authType: "custom", authData: { url: "https://xyz-abc.aws.cloud.qdrant.io:6333", api_key: "qdrant-api-key-abc123", }, }); console.log(result.isValid, result.message); ``` ## Configure a Knowledge node for Qdrant A workflow [Knowledge node](/workflow-builder/nodes/knowledge) is the surface that retrieves from Qdrant during a [run](/concepts/workflows-and-runs). For an external provider you set `provider_type` to `qdrant`, point it at a `collection_name`, and supply an `embedding_config` so ModuleX can embed the query before searching. The full node configuration is below; fields marked **(external)** apply to Qdrant and the other [external providers](/integrations/knowledge-providers/overview), and fields marked **(native only)** apply only to [modulexdb](/integrations/knowledge-providers/modulexdb). The UUID of the Qdrant [credential](/concepts/credentials-oauth) to use. One of `modulexdb`, `qdrant`, `pinecone`, `weaviate`, `mongodb_atlas`. Set it to `qdrant`. The search query. Supports `{{nodeId.path}}` references so the query can come from an earlier node's output. See [Variables & references](/workflow-builder/variables-and-references). When `true`, use the workflow input as the query instead of the `query` field. **(external)** The Qdrant collection to search. Required for Qdrant — the node raises a configuration error without it. **(external)** Namespace, used by [Pinecone](/integrations/knowledge-providers/pinecone)-style providers. Qdrant does not use namespaces; leave it unset. **(external)** Required for Qdrant. The model ModuleX uses to embed the query into a vector before searching Qdrant. See [The `embedding_config` object](#the-embedding-config-object). Number of results to retrieve. Range `1`–`50`. Passed to Qdrant as the search `limit`. Minimum similarity-score floor, `0.0`–`1.0`. When greater than `0`, sent to Qdrant as `score_threshold`; Qdrant drops points below it. Maximum tokens in the formatted context string (used when `output_format` is `context` or `both`). Range `100`–`10000`. **(external)** A provider-specific filter, passed straight to Qdrant as its `filter` clause. Use Qdrant's filter syntax (for example `must`/`should`/`must_not` conditions on payload fields). **(native only)** Restrict retrieval to specific document IDs. Applies to modulexdb knowledge bases, not Qdrant. How results are written into run state — `chunks` (individual matches with metadata), `context` (one RAG-ready context string), or `both`. Include each match's payload/metadata in the results. Maps to Qdrant's `with_payload`. Include source-document info in the formatted context string. ### The `embedding_config` object Qdrant stores vectors but does not embed text, so the Knowledge node embeds your query first. `embedding_config` selects the embedding model and is **required** for Qdrant. The embedding provider integration, for example `openai`. See [LLM providers](/integrations/llm-providers/overview). The provider routing slug, for example `openai`. The embedding model id, for example `text-embedding-3-small`. Must match the model that produced the vectors in your collection. The credential for the embedding provider. When `null`, ModuleX uses the organization's default credential for `integration_name`. ```json Knowledge node config (Qdrant) { "credential_id": "c0ffee00-1111-2222-3333-444455556666", "provider_type": "qdrant", "collection_name": "product_docs", "query": "{{trigger.question}}", "top_k": 5, "min_score": 0.3, "output_format": "context", "embedding_config": { "integration_name": "openai", "provider_id": "openai", "model_id": "text-embedding-3-small", "credential_id": null } } ``` The Knowledge node is configured on the canvas in the [workflow builder](/workflow-builder/nodes/knowledge), or programmatically as part of a workflow's node graph. There is no standalone REST endpoint that queries an external provider directly — Qdrant retrieval happens **inside a workflow run**. The native [knowledge-bases search API](/platform/knowledge/managed) (`POST /knowledge-bases/{id}/search`) targets modulexdb, not Qdrant. ## How BYOK retrieval works When a Knowledge node with `provider_type: "qdrant"` executes during a run, ModuleX: Loads your Qdrant credential for the current organization and decrypts the `url` and `api_key`. A missing or wrong-org credential fails the node with a credential error. See [Data security & encryption](/security/data-encryption). Because Qdrant requires a query vector, ModuleX embeds the resolved `query` using your `embedding_config`. Without `embedding_config`, the node raises a configuration error. The Qdrant adapter calls `POST {url}/collections/{collection_name}/points/search` with the query vector, `limit` (`top_k`), `with_payload` (`include_metadata`), `with_vector`, an optional `score_threshold` (`min_score`), and any `filters`. It sends the `api-key` header when an API key is set. Each Qdrant point becomes a standard match (`id`, `score`, extracted text content, payload metadata). Text content is pulled from common payload fields (`content`, `text`, `chunk_text`, `page_content`, `data`, `body`, `summary`, `document`). The node then writes them into run state as chunks, a context string, or both per `output_format`. See [Workflow engine & nodes](/concepts/workflow-engine). Text-content extraction depends on your payload using one of the recognized field names. If your Qdrant points store their text under a different key, the returned `content` is empty (the metadata still comes through when `include_metadata` is on). Store retrievable text under a field such as `content` or `text`. ## BYOK billing Qdrant is **bring-your-own**. Retrieval runs against your Qdrant instance and is **not** metered in ModuleX [credits](/concepts/credits-billing). Only **managed** knowledge — `modulexdb` knowledge bases — is credit-billed for retrieval and ingest. Querying Qdrant draws down no plan allowance and no [wallet](/billing/wallet) balance. There is no per-retrieval credit cost. Because BYOK retrieval is uncosted, a Qdrant Knowledge node never produces the `402`/`403`/`429` `DenialEnvelope` that managed knowledge can. See [Usage gating & limits](/billing/usage-gating). The query-embedding step uses your `embedding_config`. If that points at a [BYOK model](/integrations/llm-providers/overview) (for example OpenAI), the embedding is billed by that provider. If it points at a managed model, the embedding is credit-metered. Storage, indexing, and query throughput for your Qdrant are governed by your Qdrant Cloud plan or self-hosted capacity, not by ModuleX. ## Browse Qdrant in the catalog The [integration catalog](/integrations/catalog) is the read-only discovery surface. Use it to fetch Qdrant's metadata, actions, and auth schema. These catalog endpoints authenticate with a **Clerk JWT** (the app token) rather than an `mx_live_*` API key, and still require `X-Organization-ID` and the owner/admin role. `qdrant`. **Response** — `200 OK`, an `IntegrationDetail`: `qdrant`. `Qdrant`. `knowledge_provider`. `["Vector Database", "semantic-search"]`. The catalog actions Qdrant declares — `query`, `list_collections`, `get_collection_info`. See [Catalog actions](#catalog-actions). The single `custom` "Qdrant Connection" schema, with its `fields` (`url`, `api_key`) and a `test_endpoint`. `null` for Qdrant (the package/JSON manifest carries `app_url` `https://qdrant.tech` but no `docs_url`). The integration's `extra_metadata`. Note `provider_type` (`external`) is present in the source manifest but is **dropped on sync** and is not returned by the catalog API. Knowledge-provider auth schemas use a `fields` array (not the `setup_environment_variables` array used by LLM/tool manifests). The catalog returns `auth_schemas` verbatim, so when you read a knowledge-provider detail, look for `fields`. ```bash cURL curl https://api.modulex.dev/integrations/knowledge-providers/qdrant \ -H "Authorization: Bearer " \ -H "X-Organization-ID: 11111111-1111-1111-1111-111111111111" ``` ```bash cURL — list all knowledge providers curl https://api.modulex.dev/integrations/knowledge-providers \ -H "Authorization: Bearer " \ -H "X-Organization-ID: 11111111-1111-1111-1111-111111111111" ``` ```json { "name": "qdrant", "display_name": "Qdrant", "description": "High-performance vector database with advanced filtering capabilities for semantic search", "logo": "logos:qdrant-icon", "app_url": "https://qdrant.tech", "docs_url": null, "categories": ["Vector Database", "semantic-search"], "integration_type": "knowledge_provider", "version": "1.0.0", "auth_schemas": [ { "auth_type": "custom", "display_name": "Qdrant Connection", "description": "Connect to your Qdrant instance using URL and optional API key", "fields": [ { "name": "url", "display_name": "Qdrant URL", "type": "string", "required": true, "sensitive": false, "sample_format": "https://xyz-abc.aws.cloud.qdrant.io:6333" }, { "name": "api_key", "display_name": "API Key", "type": "string", "required": false, "sensitive": true, "sample_format": "qdrant-api-key-..." } ], "test_endpoint": { "url": "{url}/collections", "method": "GET", "headers": { "api-key": "{api_key}", "Content-Type": "application/json" }, "success_indicators": { "status_codes": [200], "response_fields": ["result"] }, "cost_level": "free" } } ], "actions": [ { "name": "query", "description": "Perform vector similarity search on your Qdrant collection" }, { "name": "list_collections", "description": "List all collections in the Qdrant instance" }, { "name": "get_collection_info", "description": "Get information about a specific collection" } ] } ``` ### Catalog actions Qdrant declares three actions in its manifest. These describe the operations the provider exposes; in practice they are driven by the [Knowledge node](#configure-a-knowledge-node-for-qdrant) (`query`) and used for connection discovery (`list_collections`, `get_collection_info`). Performs vector similarity search on a Qdrant collection. Maps to `POST {url}/collections/{collection_name}/points/search`. Name of the Qdrant collection to search. The query embedding vector. Qdrant does not embed text — supply a vector. In a Knowledge node, ModuleX computes this from your `embedding_config`. Maximum number of results to return. Minimum similarity score (`0`–`1`). Applied only when greater than `0`. Optional Qdrant filter conditions for the search. Include each point's payload in the results. Include the stored vectors in the results. **Result** — an array of points, each: The point id. The similarity score. The point payload (when `with_payload` is true). The stored vector (when `with_vectors` is true). Lists all collections in the Qdrant instance. Takes no parameters. Maps to `GET {url}/collections` (with a per-collection info lookup for counts). **Result** — an array, each: The collection name. Number of vectors (points) in the collection. Number of points in the collection. Returns details about one collection. Maps to `GET {url}/collections/{collection_name}`. Name of the collection. **Result**: The collection name. Number of vectors in the collection. Number of points in the collection. The collection configuration, including its vector parameters (size/dimension and distance metric). ## Errors and edge cases A Qdrant Knowledge node without `collection_name` fails with a configuration error before any call to Qdrant. Qdrant requires a query vector. Without `embedding_config`, the node fails with an error asking you to configure `integration_name`, `provider_id`, `model_id`, and optionally `credential_id`. If the `credential_id` does not resolve in the current organization, the node fails with a credential-not-found error. Confirm the credential belongs to the org set in `X-Organization-ID`. Qdrant returning `401` surfaces as an authentication error ("Invalid API key"). Re-check the `api_key` in your credential. Qdrant returning `404` for the search surfaces as a query error (`Collection not found: {name}`). Verify the collection exists on that instance. If ModuleX cannot reach the Qdrant URL, the node fails with a connection error. Confirm the URL, port (`6333` by default), and that the instance is reachable from ModuleX. If the query vector's dimension does not match the collection's vector size, Qdrant rejects the search. Use an embedding model whose dimension matches the collection. These errors arise inside a [workflow run](/concepts/workflows-and-runs), so they appear as node/run errors in the run stream rather than as a top-level HTTP error envelope. For the error-envelope shapes on the REST surface, see [Errors & status codes](/api-reference/errors). ## Managing the credential Day-to-day operations use the standard [credentials API](/concepts/credentials-oauth#the-credentials-api). For Qdrant specifically: - **Rotate the URL or key.** Secrets are immutable through `PUT /credentials/{id}` (that route updates only `display_name` and metadata). To change the URL or API key, create a new Qdrant credential and delete the old one. - **Set the default.** `POST /credentials/{id}/set-default` makes one Qdrant credential the organization default, used when a node resolves to the default. - **Test a saved connection.** `POST /credentials/{id}/test` re-runs Qdrant's test endpoint against the stored URL and key. - **Delete.** `DELETE /credentials/{id}` removes it and returns `204`. Knowledge nodes pointing at it will fail credential resolution until another valid Qdrant credential exists. See [Managing credentials](/integrations/managing-credentials) for the app workflow and [Credentials & OAuth2](/concepts/credentials-oauth) for the complete API. ## Where to go next Compare Qdrant with modulexdb and the other external vector stores. The credit-metered, ModuleX-hosted alternative that ingests and embeds for you. Configure retrieval inside a workflow, including external providers like Qdrant. How ModuleX retrieves company knowledge — managed and BYOK. --- # Weaviate as a vector store URL: https://docs.modulex.dev/integrations/knowledge-providers/weaviate Connect a Weaviate cluster to ModuleX as a bring-your-own-key knowledge provider, configure the knowledge node, and run vector retrieval against your own classes. Weaviate is an open-source vector database with a GraphQL query API. In ModuleX, Weaviate is a **bring-your-own-key (BYOK) knowledge provider**: you store the connection details for your own cluster as a credential, and a [knowledge node](/workflow-builder/nodes/knowledge) in a workflow runs vector similarity search against one of your Weaviate classes. ModuleX never hosts your vectors and does not charge credits for retrieval against a Weaviate cluster you own — see [Billing](#billing-byok-retrieval-is-not-metered). If you want ModuleX to host the vectors, ingest, embed, and search for you, use [modulexdb (managed)](/integrations/knowledge-providers/modulexdb) instead. For the full list of supported stores, see [Knowledge providers](/integrations/knowledge-providers/overview). Weaviate retrieval in ModuleX runs **inside a workflow knowledge node**, not through the managed `/knowledge-bases` search API. The managed search, ingest, and document endpoints under `/knowledge-bases` operate only on `modulexdb`-backed knowledge bases. With Weaviate you bring vectors that already exist in your own cluster. ## How Weaviate fits into ModuleX ModuleX treats Weaviate as an external provider behind a single adapter. At run time, a knowledge node: The node looks up your stored Weaviate credential by `credential_id`, scoped to the organization in `X-Organization-ID`, and decrypts the connection details (`url` and optional `api_key`). Weaviate retrieval in ModuleX requires a query vector. ModuleX generates the embedding from the node's `embedding_config` (for example an OpenAI `text-embedding-3-small` model) before calling Weaviate. See [embedding configuration](#embedding-configuration). The adapter issues a `Get` GraphQL query with a `nearVector` argument against your class, applies a `certainty` floor derived from `min_score`, and limits results to `top_k`. Matches are normalized into ModuleX's standard chunk shape and returned as `chunks`, a single `context` string, or `both`, depending on `output_format`. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-4160 · [IMAGE]** **[IMAGE_DESCRIPTION]:** Data-flow diagram of BYOK Weaviate retrieval inside a knowledge node. **[IMAGE_DETAILS]:** Show: workflow knowledge node -> resolve Weaviate credential (url + optional api_key) -> embed query via embedding_config -> nearVector GraphQL POST to the customer's Weaviate cluster -> normalized chunks back into run state. Mark the cluster boundary as customer-owned (BYOK, uncosted). Match the docs accent palette; light and dark variants; 16:9. ## Connect Weaviate A Weaviate connection is stored as a credential with the `custom` auth type and the integration name `weaviate`. Creating a credential requires the **owner** or **admin** role on the organization (`organization_admin_required`); the `member` role is retired and cannot create credentials. See [Roles & permissions](/security/roles-permissions). Authenticate every request to the ModuleX API with `Authorization: Bearer mx_live_…` and the `X-Organization-ID` header, as described in [Authentication](/api-reference/authentication). ### Connection fields These are the two fields the Weaviate auth schema exposes. They are stored, encrypted at rest, inside the credential's `auth_data`. The base URL of your Weaviate instance, with no trailing path. Example: `https://your-cluster.weaviate.cloud`. If omitted at query time the adapter falls back to `http://localhost:8080`, but you should always set it explicitly. Stored unencrypted-flagged (`sensitive: false`). The Weaviate API key. **Required for Weaviate Cloud**; optional for an unauthenticated self-hosted cluster. When present, ModuleX sends it to Weaviate as `Authorization: Bearer `. Stored as a secret (`sensitive: true`). ### Create the credential Send the connection fields under `auth_data` with `auth_type` set to `custom`. The response returns the new `credential_id`, which you then reference from the knowledge node. ```bash cURL curl -X POST https://api.modulex.dev/credentials \ -H "Authorization: Bearer mx_live_xxx" \ -H "X-Organization-ID: org_123" \ -H "Content-Type: application/json" \ -d '{ "integration_name": "weaviate", "auth_type": "custom", "display_name": "Production Weaviate", "auth_data": { "url": "https://your-cluster.weaviate.cloud", "api_key": "weaviate-api-key-..." } }' ``` ```python Python from modulex import AsyncModulex client = AsyncModulex( api_key="mx_live_xxx", organization_id="org_123", ) credential = await client.credentials.create( integration_name="weaviate", auth_type="custom", display_name="Production Weaviate", auth_data={ "url": "https://your-cluster.weaviate.cloud", "api_key": "weaviate-api-key-...", }, ) print(credential["credential_id"]) ``` ```javascript JavaScript import { Modulex } from "modulex-js"; const client = new Modulex({ apiKey: "mx_live_xxx", organizationId: "org_123", }); const credential = await client.credentials.create({ integration_name: "weaviate", auth_type: "custom", display_name: "Production Weaviate", auth_data: { url: "https://your-cluster.weaviate.cloud", api_key: "weaviate-api-key-...", }, }); console.log(credential.credential_id); ``` Send `auth_type: "custom"` explicitly. Without it, the backend infers the credential type from `auth_data`: a payload containing `api_key` is treated as a standard API-key credential, which is the wrong shape for Weaviate (it would drop the `url`). The explicit `custom` type keeps both fields together in one credential. For the broader credential lifecycle — listing, testing, setting a default, and rotation — see [Managing credentials](/integrations/managing-credentials). ### Test the connection Testing a Weaviate credential validates connectivity rather than running a search. ModuleX calls your cluster's readiness probe at `GET {url}/v1/.well-known/ready` and, on success, reads `GET {url}/v1/schema` to count your classes. | Outcome | Meaning | | --- | --- | | `status: "connected"` plus `classes_count` | The cluster is reachable and the API key (if any) is valid. | | `401` from Weaviate | The `api_key` is missing or invalid for a cluster that requires authentication. | | Connection refused / not ready | The `url` is wrong, the cluster is down, or it is not reachable from ModuleX. | **🎬 MEDIA PLACEHOLDER · MX-MEDIA-4161 · [SCREENSHOT]** **[SCREENSHOT_DESCRIPTION]:** The Weaviate connection form in the ModuleX credentials UI. **[SCREENSHOT_DETAILS]:** Capture the "Add credential" panel for Weaviate showing the URL field (required) and API Key field (optional, masked), with the Test connection result showing "connected" and a class count. Settings > Credentials in the live app; dark theme; crop to the form. ## Configure the knowledge node Once the credential exists, add a [knowledge node](/workflow-builder/nodes/knowledge) to a workflow and point it at Weaviate. The node config (`KnowledgeNodeConfig`) is shared across all providers; the fields below are the ones that matter for Weaviate. Every node value supports `{{nodeId.path}}` references so a query can come from a previous step — see [Variables & references](/workflow-builder/variables-and-references). ### Connection and provider Set to `weaviate` to route the node through the Weaviate adapter. The other accepted values are `modulexdb`, `qdrant`, `pinecone`, and `mongodb_atlas`. The `credential_id` of the Weaviate credential you created above. The Weaviate **class** to search (Weaviate's term for a collection). Required for every external provider; the node raises a validation error if it is missing for a non-`modulexdb` provider. Accepted on the node config for Pinecone-style providers. **The Weaviate adapter ignores it** — Weaviate has no namespace concept, so leave it unset. ### Query The search text. Supports `{{nodeId.path}}` references for dynamic queries. ModuleX embeds this text into a vector before calling Weaviate (see [embedding configuration](#embedding-configuration)). When `true`, the node uses the workflow input as the query instead of the `query` field, reading the first of `query`, `question`, `input`, `user_input`, or `message` found in run state. ### Retrieval settings Number of results to return. Range `1`–`50`. Maps to the GraphQL `limit` argument. Minimum similarity threshold, `0.0`–`1.0`. For Weaviate the adapter converts this to a `certainty` floor as `certainty = 1.0 - min_score` (only when `min_score > 0`; otherwise `certainty` is `0.0` and no floor is applied). The returned `score` for each match is Weaviate's `certainty` value. Token budget for the assembled `context` string. Range `100`–`10000`. Applies when `output_format` is `context` or `both`. Provider-specific filter conditions passed through to the adapter. For Weaviate these correspond to GraphQL `where` conditions. Restrict results to specific document IDs. **Native (`modulexdb`) knowledge bases only** — this filter is not applied to Weaviate. ### Output How retrieved knowledge is returned: `chunks` (individual matches with metadata), `context` (a single formatted RAG string), or `both`. Include each match's properties (returned by Weaviate as object properties) in the chunk metadata. Include source document headers in the formatted `context` string. ### Embedding configuration Weaviate retrieval in ModuleX **always requires a query vector** — the adapter reports `requires_query_embedding() = true` and raises `Weaviate requires query_vector for search` if none is supplied. ModuleX builds that vector from the node's `embedding_config` before calling your cluster, so this object is required for Weaviate. Use the **same embedding model** that produced the vectors stored in your Weaviate class. Mismatched models yield dimension errors or meaningless similarity scores. ModuleX does not read your class's vectorizer config to infer the model; you declare it in `embedding_config`. Embedding settings for generating the query vector. Integration name of the embedding provider, for example `openai` or `cohere`. Provider identifier, for example `openai`. Embedding model id, for example `text-embedding-3-small`. Must match the model used to index your Weaviate class. Credential for the embedding provider. If omitted, ModuleX uses the organization's default credential for `integration_name`. ModuleX's `text2vec`-style server-side text search is **not** wired through this adapter. Although the catalog metadata for Weaviate lists a `query_text` parameter and mentions `nearText`, the live retrieval path uses `nearVector` only and rejects a search with no `query_vector`. Plan to provide vectors via `embedding_config`. ## BYOK retrieval A complete Weaviate knowledge node configuration, ready to drop into a workflow's node definition `config`: ```json Knowledge node config (Weaviate) { "provider_type": "weaviate", "credential_id": "a1b2c3d4-0000-0000-0000-000000000000", "collection_name": "ProductDocs", "query": "{{trigger.question}}", "top_k": 5, "min_score": 0.3, "output_format": "context", "include_metadata": true, "include_source": true, "embedding_config": { "integration_name": "openai", "provider_id": "openai", "model_id": "text-embedding-3-small", "credential_id": null } } ``` When this node runs, ModuleX embeds `{{trigger.question}}`, issues the `nearVector` GraphQL query against the `ProductDocs` class, and writes the result to run state under the node's own `id`. A downstream [LLM node](/workflow-builder/nodes/llm) or [Agent node](/workflow-builder/nodes/agent) can then reference the retrieved context with `{{.context}}`. To run the workflow itself over the API or an SDK, see [Run via API](/workflow-builder/execution/api-endpoint) and the [Run a workflow](/guides/run-a-workflow) guide. The run streams over SSE — see [SSE run streaming](/realtime/sse-streaming). ### What the adapter returns Each match is normalized into a chunk. The `score` is Weaviate's `certainty`; `metadata` carries the object's returned properties; `content` is the adapter's best-effort text extraction from those properties. The Weaviate object id (`_additional.id`). The match's `certainty` from Weaviate (`0`–`1`, higher is better). Text extracted from the object's properties. The adapter checks common field names in order: `content`, `text`, `chunk_text`, `page_content`, `data`, `body`, `summary`, `document`. If none is present, `content` may be empty — name a text property accordingly in your class. The object's returned properties (everything except `_additional`). Present only when `include_metadata` is `true`. ### Returning specific properties The Weaviate adapter requests result properties for you. If your class stores its text under a non-default property name, set `properties` in the node `filters`/config path appropriate to your build, or ensure one of the recognized field names above exists so `content` is populated. The catalog `query` action documents these tunables: The Weaviate class to search. The query embedding (`nearVector`). Supplied by ModuleX from `embedding_config`. Maximum number of results. Minimum certainty threshold (`0`–`1`). At run time this is derived from the node's `min_score`. Properties to return in results. Defaults to an empty list, in which case the adapter returns the object id, certainty, and distance. GraphQL `where` filter conditions. ## Billing: BYOK retrieval is not metered Searching a Weaviate cluster you own does **not** consume ModuleX credits. ModuleX meters retrieval only for managed (`modulexdb`) knowledge — a managed search reserves one retrieval credit before the embed and records it on success. BYOK providers, including Weaviate, are uncosted for retrieval. See [Credits & metering](/billing/credits) and [Knowledge & RAG](/concepts/knowledge-rag). The Weaviate **search** is uncosted, but the **workflow run** that contains the knowledge node is still subject to the run-admission billing gate. A run on the run / composer / assistant / knowledge-managed surfaces can be denied with a flat `DenialEnvelope` (`{code, layer, key, current, limit, reason}`) returned as `402`, `403`, or `429` when your plan allowance or wallet is exhausted. See [Usage gating & limits](/billing/usage-gating) and [Errors & status codes](/api-reference/errors). Embedding the query (via a managed embedding model) can itself consume credits even when the vector store is BYOK. ## Errors and edge cases The adapter and node surface the following conditions. Adapter exceptions become the node's `error` field (the node returns empty `chunks`/`context` with an `error` rather than failing the whole run), while credential and config problems raise before the search. - **`401` from Weaviate** — surfaces as an authentication error (`Invalid Weaviate API key`). Check the `api_key`; Weaviate Cloud requires one. - **Connection refused / request error** — the `url` is unreachable from ModuleX or the cluster is down. Re-test the credential. - **`Credential not found: `** — the `credential_id` is wrong or belongs to another organization. Credentials are strictly scoped to `X-Organization-ID`. - **`collection_name is required for external provider`** — set `collection_name` to your Weaviate class. - **`embedding_config required …`** — Weaviate needs a query vector; provide a valid `embedding_config`. - **`Weaviate requires query_vector for search`** — raised if embedding produced no vector. Confirm the embedding model and its credential resolve. - **GraphQL error** — surfaces as `Weaviate query failed: `. The most common cause is a class or property name that does not exist; verify the class with the cluster's schema and confirm property names. - **Empty `content`** — the matched objects have no property among the recognized text field names; rename a property or store text under one of `content`/`text`/`chunk_text`/`page_content`. - **No matches** — `min_score` may be too high (it tightens the `certainty` floor). Lower `min_score` toward `0` to widen results. ## Related pages Compare every vector store ModuleX can use for retrieval. The node that runs Weaviate retrieval inside a workflow. How BYOK vector stores plug into ModuleX. The retrieval model behind chats and workflows. --- # Anthropic Claude models URL: https://docs.modulex.dev/integrations/llm-providers/anthropic Connect Anthropic to ModuleX with your own API key, see the available Claude models, and understand how BYOK usage is billed. Anthropic is a [BYOK (bring-your-own-key)](/reference/glossary) LLM provider in ModuleX. You connect your own Anthropic API key, then use Claude models in the [LLM node](/workflow-builder/nodes/llm), the [Agent node](/workflow-builder/nodes/agent), [chat](/platform/chat/model-selection), and the [Assistant](/assistant/models-and-settings). Because Anthropic is BYOK, Anthropic bills you directly for token usage and ModuleX adds no credit charge on top — see [BYOK billing](#byok-billing) below. For the full list of provider types and how managed versus BYOK selection works, see [LLM providers](/integrations/llm-providers/overview). If you would rather have ModuleX provision and meter the model for you in credits, use [ModuleX-managed models](/integrations/llm-providers/modulexai) instead. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-4070 · [SCREENSHOT]** **[SCREENSHOT_DESCRIPTION]:** The Anthropic connection card in the ModuleX integrations/credentials UI. **[SCREENSHOT_DETAILS]:** Capture the API Key Authentication form for Anthropic in the credential-management UI: the `API Key` field (masked), the optional `Base URL` field defaulted to `https://api.anthropic.com`, and the "Test connection" / "Save" controls. Light theme, 16:9, crop to the card. ## What you need Create a key in the [Anthropic Console](https://console.anthropic.com/settings/keys). Anthropic keys start with `sk-ant-` (for example `sk-ant-api03-…`). ModuleX validates the key against the pattern `^sk-ant-[a-zA-Z0-9\-_]{20,}$` before saving it. Credentials are scoped to an [organization](/concepts/organizations-roles). Connecting and managing integrations requires the **owner** or **admin** role — the `member` role is retired. See [roles & permissions](/security/roles-permissions). To connect Anthropic over the API or an SDK, you need a ModuleX API key (`mx_live_…`) and your organization id. See [authentication](/api-reference/authentication). ## Connect Anthropic You connect Anthropic by storing your Anthropic API key as a [credential](/integrations/managing-credentials) for the `anthropic` integration. You can do this in the app or programmatically. ### In the app 1. Open the [integrations catalog](/integrations/catalog) and select **Anthropic**, or open **Settings → Credentials**. 2. Choose **API Key Authentication** and paste your Anthropic key. 3. Optionally set a custom **Base URL** (defaults to `https://api.anthropic.com`) — only needed for Anthropic-compatible gateways. 4. Save. ModuleX validates the key with a minimal request before storing it (see [Test a key before saving](#test-a-key-before-saving)). Anthropic exposes a single authentication scheme — `api_key`. Its fields are: Your Anthropic API key. Stored encrypted and never returned in plaintext. Pattern `^sk-ant-[a-zA-Z0-9\-_]{20,}$`; sample format `sk-ant-api03-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`. Optional custom API base URL, only needed when pointing at an Anthropic-compatible API. Must match `^https?://.*`. ### Over the API or an SDK Create the credential with `POST /credentials`. ModuleX detects the credential type from the body: an `auth_data.api_key` value is stored as an `api_key` credential. Set `make_default: true` to make this the default Anthropic credential for the organization. Every request authenticates with `Authorization: Bearer mx_live_…` plus the `X-Organization-ID` header (see [authentication](/api-reference/authentication)). ```bash cURL curl -X POST https://api.modulex.dev/credentials \ -H "Authorization: Bearer mx_live_xxxxxxxxxxxxxxxxxxxxxxxx" \ -H "X-Organization-ID: 6f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f" \ -H "Content-Type: application/json" \ -d '{ "integration_name": "anthropic", "display_name": "Anthropic production key", "make_default": true, "auth_data": { "api_key": "sk-ant-api03-..." } }' ``` ```python Python import asyncio from modulex import Modulex async def main(): async with Modulex( api_key="mx_live_xxxxxxxxxxxxxxxxxxxxxxxx", # or MODULEX_API_KEY organization_id="6f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f", ) as client: cred = await client.credentials.create( integration_name="anthropic", display_name="Anthropic production key", make_default=True, auth_data={"api_key": "sk-ant-api03-..."}, ) print(cred.id) asyncio.run(main()) ``` ```javascript JavaScript import { Modulex } from "modulex-js"; const client = new Modulex({ apiKey: "mx_live_xxxxxxxxxxxxxxxxxxxxxxxx", // or process.env.MODULEX_API_KEY organizationId: "6f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f", }); const cred = await client.credentials.create({ integration_name: "anthropic", display_name: "Anthropic production key", make_default: true, auth_data: { api_key: "sk-ant-api03-..." }, }); console.log(cred.id); ``` The new credential's identifier. A `201` response returns the credential metadata; the secret is never echoed back. Creating, listing, and managing credentials and the integration catalog requires the **owner** or **admin** role in the organization. A caller without that role receives a `403`. The integration-catalog read routes (for example `GET /integrations/llm-providers/anthropic`) are also owner/admin-gated. ### Test a key before saving Use `POST /credentials/test-temporary` to validate an Anthropic key without storing it. ModuleX runs the provider's test call — a 10-token `POST` to `https://api.anthropic.com/v1/messages` against `claude-haiku-4-5-20251001` with the header `anthropic-version: 2023-06-01` — and reports success when the response status is `200` and includes the fields `id`, `type`, and `content`. ```bash cURL curl -X POST https://api.modulex.dev/credentials/test-temporary \ -H "Authorization: Bearer mx_live_xxxxxxxxxxxxxxxxxxxxxxxx" \ -H "X-Organization-ID: 6f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f" \ -H "Content-Type: application/json" \ -d '{ "integration_name": "anthropic", "auth_type": "api_key", "auth_data": { "api_key": "sk-ant-api03-..." } }' ``` ```python Python result = await client.credentials.test_temporary( integration_name="anthropic", auth_type="api_key", auth_data={"api_key": "sk-ant-api03-..."}, ) print(result.is_valid, result.status_code) ``` ```javascript JavaScript const result = await client.credentials.testTemporary({ integration_name: "anthropic", auth_type: "api_key", auth_data: { api_key: "sk-ant-api03-..." }, }); console.log(result.is_valid, result.status_code); ``` `true` when the test call succeeds. The HTTP status returned by Anthropic's test endpoint (`200` on success). How the credential was validated. For Anthropic this is `api_call`. The cost class of the test call. For Anthropic this is `minimal` (a 10-token request). ## Available models ModuleX serves Claude models from the catalog at `GET /integrations/llm-providers/anthropic`. The catalog is read-only metadata used by the credential UI and the builder — it is not the execution path. The models below are the ones ModuleX currently advertises for Anthropic. | Model id | Display name | Max input tokens | Max output tokens | Vision | Input $/1M | Output $/1M | | --- | --- | --- | --- | --- | --- | --- | | `claude-opus-4.7` | Claude Opus 4.7 | 1,000,000 | 128,000 | Yes | `$5.00` | `$25.00` | | `claude-sonnet-4.6` | Claude Sonnet 4.6 | 200,000 | 64,000 | Yes | `$3.00` | `$15.00` | | `claude-haiku-4.5` | Claude Haiku 4.5 | 200,000 | 64,000 | Yes | `$1.00` | `$5.00` | Prices in the table are **Anthropic's per-token rates** as recorded in the ModuleX catalog, in USD per 1,000,000 tokens. Because Anthropic is BYOK, these are the rates Anthropic charges you directly — they are not ModuleX credits. See [BYOK billing](#byok-billing). Each model id is the catalog id you select in the app and in node configuration. ModuleX maps the catalog id to the underlying Anthropic wire model and request settings: `claude-opus-4.7` — the catalog id you select. `Claude Opus 4.7` `Anthropic` `anthropic` — the routing provider slug. `1000000` `128000` `true` `5` (quality score, 1–5). `1` (latency score, 1–5; higher is faster). `2026-01-01` (knowledge cutoff). `5.0` `25.0` ModuleX sends the wire model `claude-opus-4-7` with `max_tokens: 128000` and the header `anthropic-version: 2023-06-01`. `claude-sonnet-4.6` `Claude Sonnet 4.6` `anthropic` `200000` `64000` `true` `4` `3` `2026-01-01` `3.0` `15.0` Wire model `claude-sonnet-4-6` with `max_tokens: 64000` and `anthropic-version: 2023-06-01`. `claude-haiku-4.5` `Claude Haiku 4.5` `anthropic` `200000` `64000` `true` `2` `5` (fastest). `2025-07-01` `1.0` `5.0` Wire model `claude-haiku-4-5-20251001` with `max_tokens: 64000` and `anthropic-version: 2023-06-01`. This is the model ModuleX uses for the connection test. The model list is served from the ModuleX catalog and may change as Anthropic releases or deprecates models. Always read `GET /integrations/llm-providers/anthropic` for the current set rather than hard-coding ids. Catalog reads are cached for up to 10 minutes. ### Read the model catalog ```bash cURL curl https://api.modulex.dev/integrations/llm-providers/anthropic \ -H "Authorization: Bearer mx_live_xxxxxxxxxxxxxxxxxxxxxxxx" \ -H "X-Organization-ID: 6f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f" ``` ```python Python detail = await client.integrations.llm_provider_detail("anthropic") for model in detail.models: print(model["id"], model["display_name"]) ``` ```javascript JavaScript const detail = await client.integrations.llmProvider("anthropic"); for (const model of detail.models) { console.log(model.id, model.display_name); } ``` The response is an integration-detail object whose `models` array contains the entries shown above, plus an `auth_schemas` array describing the `api_key` scheme. A truncated example: ```json Catalog detail (truncated) { "name": "anthropic", "display_name": "Anthropic", "description": "Anthropic's Claude models for advanced reasoning and long-context tasks", "integration_type": "llm_provider", "version": "1.0.0", "categories": ["AI & LLM Providers", "ai", "chat", "reasoning"], "auth_schemas": [ { "auth_type": "api_key", "display_name": "API Key Authentication", "description": "Connect using Anthropic API Key" } ], "models": [ { "id": "claude-opus-4.7", "display_name": "Claude Opus 4.7", "max_input_tokens": 1000000, "max_output_tokens": 128000, "supports_vision": true, "input_usd_per_1m_tokens": 5.0, "output_usd_per_1m_tokens": 25.0 } ] } ``` ## Use Claude in ModuleX Once Anthropic is connected, select a Claude model id wherever a model can be chosen: Call Claude with prompts, variables, and structured output inside a workflow. Run an autonomous step where Claude can call tools and loop. Pick Claude as the model for a chat conversation. Configure the Assistant to reason and act with a Claude model. If your organization holds more than one Anthropic credential, ModuleX uses the credential marked default for the `anthropic` integration. Set a different default with `POST /credentials/{credential_id}/set-default`. See [managing credentials](/integrations/managing-credentials). ## BYOK billing Anthropic is **bring-your-own-key**, which changes how usage is billed compared with [ModuleX-managed models](/integrations/llm-providers/modulexai). Token usage is billed by **Anthropic** against your own API key. ModuleX adds **no credit charge and no markup** for the model call itself. BYOK usage is recorded for analytics only — it does not consume ModuleX [credits](/billing/credits). ModuleX provisions the model and bills it in **credits** through the [usage gate](/billing/usage-gating), applying a system margin. Use this when you do not want to manage a provider account. What this means in practice: - **No ModuleX credit cost for tokens.** A Claude call made with your Anthropic key is not metered in credits. Anthropic invoices you directly at its own rates (the per-million-token prices in the [model table](#available-models)). - **Other ModuleX usage still applies.** Running a workflow or a turn through the Assistant can still consume the flat per-run credit and may pass through the billing gate even when the model itself is BYOK. See [credits & metering](/billing/credits) and [usage gating & limits](/billing/usage-gating). - **Rate limits and quotas are Anthropic's.** Token-rate limits, monthly spend caps, and model availability for a BYOK key are governed by your Anthropic account, not by ModuleX. BYOK is available on all plans — there is no separate ModuleX entitlement that gates connecting your own Anthropic key. The pricing matrix on the marketing site lists BYOK for every tier; the only ModuleX-side billing that applies to a BYOK call is the non-model usage described above. **TBD:** confirm with the billing owner that no per-call ModuleX surcharge applies to BYOK model calls before treating "no markup" as a guarantee. ## Errors Catalog and credential operations return ModuleX's standard error shapes. See [errors & status codes](/api-reference/errors) for the full taxonomy. | Status | When it happens | Shape | | --- | --- | --- | | `400` | Invalid `auth_data`/`auth_type` on credential create, or asking a typed endpoint for the wrong integration type (for example `anthropic` is not a tool). | `{detail: string}` | | `401` | Missing or invalid `Authorization` / missing `X-Organization-ID`. | `{detail: string}` | | `403` | Caller is not an owner/admin in the organization. | `{detail: string}` | | `404` | Unknown provider, for example `GET /integrations/llm-providers/` returns `LLM provider not found`. | `{detail: string}` | | `422` | Query/parameter validation error. | `{detail: [ ... ]}` | | `500` | Unhandled server error. | `{detail: string}` | A bad Anthropic key does not surface as a ModuleX `401`. Connection validation happens against Anthropic: `POST /credentials/test-temporary` (or saving with validation) returns `is_valid: false` with the status Anthropic returned (for example `401` from `api.anthropic.com`). The ModuleX request itself still succeeds. ## Related All providers, and how managed vs BYOK selection works. The credit-billed managed alternative to BYOK. How integrations authenticate and the auth schema variants. Create, default, rotate, and scope credentials. --- # Google Gemini models URL: https://docs.modulex.dev/integrations/llm-providers/gemini Connect Google Gemini to ModuleX with your own API key, browse the available Gemini models, and understand how BYOK usage is billed. Google Gemini is a [BYOK (bring-your-own-key)](/reference/glossary) LLM provider in ModuleX. You connect your own Google AI API key, then use Gemini models in the [LLM node](/workflow-builder/nodes/llm), the [Agent node](/workflow-builder/nodes/agent), [chat](/platform/chat/model-selection), and the [Assistant](/assistant/models-and-settings). Because Gemini is BYOK, Google bills you directly for token usage and ModuleX adds no credit charge on top — see [BYOK billing](#byok-billing) below. For the full list of provider types and how managed versus BYOK selection works, see [LLM providers](/integrations/llm-providers/overview). If you would rather have ModuleX provision and meter the model for you in credits, use [ModuleX-managed models](/integrations/llm-providers/modulexai) instead. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-4080 · [SCREENSHOT]** **[SCREENSHOT_DESCRIPTION]:** The Google Gemini connection card in the ModuleX integrations/credentials UI. **[SCREENSHOT_DETAILS]:** Capture the API Key Authentication form for Gemini in the credential-management UI: the `API Key` field (masked), the optional `Base URL` field defaulted to `https://generativelanguage.googleapis.com/v1beta`, and the "Test connection" / "Save" controls. Light theme, 16:9, crop to the card. ## What you need Create a key in [Google AI Studio](https://aistudio.google.com/app/apikey): click **Create API Key**, select (or create) a Google Cloud project, then copy the key. Google AI keys start with `AIza` (for example `AIzaSy…`). ModuleX validates the key against the pattern `^AIza[0-9A-Za-z\-_]{35}$` before saving it. Credentials are scoped to an [organization](/concepts/organizations-roles). Connecting and managing integrations requires the **owner** or **admin** role — the `member` role is retired. See [roles & permissions](/security/roles-permissions). To connect Gemini over the API or an SDK, you need a ModuleX API key (`mx_live_…`) and your organization id. See [authentication](/api-reference/authentication). ## Connect Gemini You connect Gemini by storing your Google AI API key as a [credential](/integrations/managing-credentials) for the `gemini` integration. You can do this in the app or programmatically. ### In the app 1. Open the [integrations catalog](/integrations/catalog) and select **Gemini**, or open **Settings → Credentials**. 2. Choose **API Key Authentication** and paste your Google AI key. 3. Optionally set a custom **Base URL** (defaults to `https://generativelanguage.googleapis.com/v1beta`) — only needed for Google AI-compatible gateways. 4. Save. ModuleX validates the key with a minimal request before storing it (see [Test a key before saving](#test-a-key-before-saving)). Gemini exposes a single authentication scheme — `api_key`. Its fields are: Your Google AI API key. Stored encrypted and never returned in plaintext. Marked `sensitive`. Pattern `^AIza[0-9A-Za-z\-_]{35}$`; sample format `AIzaSyXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX`. Optional custom API base URL, only needed when pointing at a Google AI-compatible API. Not required and not sensitive. Must match `^https?://.*`. ### Over the API or an SDK Create the credential with `POST /credentials`. ModuleX detects the credential type from the body: an `auth_data.api_key` value is stored as an `api_key` credential. Set `make_default: true` to make this the default Gemini credential for the organization. Every request authenticates with `Authorization: Bearer mx_live_…` plus the `X-Organization-ID` header (see [authentication](/api-reference/authentication)). ```bash cURL curl -X POST https://api.modulex.dev/credentials \ -H "Authorization: Bearer mx_live_xxxxxxxxxxxxxxxxxxxxxxxx" \ -H "X-Organization-ID: 6f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f" \ -H "Content-Type: application/json" \ -d '{ "integration_name": "gemini", "display_name": "Gemini production key", "make_default": true, "auth_data": { "api_key": "AIzaSy..." } }' ``` ```python Python import asyncio from modulex import Modulex async def main(): async with Modulex( api_key="mx_live_xxxxxxxxxxxxxxxxxxxxxxxx", # or MODULEX_API_KEY organization_id="6f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f", ) as client: cred = await client.credentials.create( integration_name="gemini", display_name="Gemini production key", make_default=True, auth_data={"api_key": "AIzaSy..."}, ) print(cred.id) asyncio.run(main()) ``` ```javascript JavaScript import { Modulex } from "modulex-js"; const client = new Modulex({ apiKey: "mx_live_xxxxxxxxxxxxxxxxxxxxxxxx", // or process.env.MODULEX_API_KEY organizationId: "6f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f", }); const cred = await client.credentials.create({ integration_name: "gemini", display_name: "Gemini production key", make_default: true, auth_data: { api_key: "AIzaSy..." }, }); console.log(cred.id); ``` The new credential's identifier. A `201` response returns the credential metadata; the secret is never echoed back. Creating, listing, and managing credentials and the integration catalog requires the **owner** or **admin** role in the organization. A caller without that role receives a `403`. The integration-catalog read routes (for example `GET /integrations/llm-providers/gemini`) are also owner/admin-gated. ### Test a key before saving Use `POST /credentials/test-temporary` to validate a Gemini key without storing it. ModuleX runs the provider's test call — a 10-token `POST` to `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-lite:generateContent` with the header `x-goog-api-key` set to your key — and reports success when the response status is `200` and includes a `candidates` field. ```bash cURL curl -X POST https://api.modulex.dev/credentials/test-temporary \ -H "Authorization: Bearer mx_live_xxxxxxxxxxxxxxxxxxxxxxxx" \ -H "X-Organization-ID: 6f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f" \ -H "Content-Type: application/json" \ -d '{ "integration_name": "gemini", "auth_type": "api_key", "auth_data": { "api_key": "AIzaSy..." } }' ``` ```python Python result = await client.credentials.test_temporary( integration_name="gemini", auth_type="api_key", auth_data={"api_key": "AIzaSy..."}, ) print(result.is_valid, result.status_code) ``` ```javascript JavaScript const result = await client.credentials.testTemporary({ integration_name: "gemini", auth_type: "api_key", auth_data: { api_key: "AIzaSy..." }, }); console.log(result.is_valid, result.status_code); ``` `true` when the test call succeeds. The HTTP status returned by Google's test endpoint (`200` on success). How the credential was validated. For Gemini this is `api_call`. The cost class of the test call. For Gemini this is `minimal` (a 10-token request). ## Available models ModuleX serves Gemini models from the catalog at `GET /integrations/llm-providers/gemini`. The catalog is read-only metadata used by the credential UI and the builder — it is not the execution path. The models below are the ones ModuleX currently advertises for Gemini. Every Gemini model supports vision (image input) and a 1,048,576-token context window. | Model id | Display name | Max input tokens | Max output tokens | Vision | Input $/1M | Output $/1M | | --- | --- | --- | --- | --- | --- | --- | | `gemini-2.5-pro` | Gemini 2.5 Pro | 1,048,576 | 65,536 | Yes | `$2.50` | `$15.00` | | `gemini-2.5-flash` | Gemini 2.5 Flash | 1,048,576 | 65,536 | Yes | `$0.30` | `$2.50` | | `gemini-2.5-flash-lite` | Gemini 2.5 Flash Lite | 1,048,576 | 65,536 | Yes | `$0.10` | `$0.40` | Prices in the table are **Google's per-token rates** as recorded in the ModuleX catalog, in USD per 1,000,000 tokens. Because Gemini is BYOK, these are the rates Google charges you directly — they are not ModuleX credits. See [BYOK billing](#byok-billing). Each model id is the catalog id you select in the app and in node configuration. ModuleX maps the catalog id to the underlying Gemini wire model and request settings: `gemini-2.5-pro` — the catalog id you select. `Gemini 2.5 Pro` `Gemini` `gemini` — the routing provider slug. `1048576` `65536` `true` `5` (quality score, 1–5). `2` (latency score, 1–5; higher is faster). `2025-01-01` (knowledge cutoff). `2.5` `15.0` ModuleX sends the wire model `gemini-2.5-pro` (the `body_extra.model` value). No extra headers are set (`headers_extra` is empty). `gemini-2.5-flash` `Gemini 2.5 Flash` `gemini` `1048576` `65536` `true` `3` `3` `2025-01-01` `0.3` `2.5` Wire model `gemini-2.5-flash` (the `body_extra.model` value); no extra headers. `gemini-2.5-flash-lite` `Gemini 2.5 Flash Lite` `gemini` `1048576` `65536` `true` `2` `4` `2025-01-01` `0.1` `0.4` Wire model `gemini-2.5-flash-lite` (the `body_extra.model` value); no extra headers. The catalog `max_output_tokens` is the model's hard ceiling. Separately, ModuleX applies a per-scenario output cap that is **lower** than the model ceiling and is **not** exposed through any catalog API response — for every Gemini model it is `8000` for simple chat, `10000` for chat with knowledge, `4000` for an LLM node, `12000` for an Agent node, `32000` for the main Composer agent, and `4000` for a Composer subagent. These caps are internal defaults and may change. The model list is served from the ModuleX catalog and may change as Google releases or deprecates models. Always read `GET /integrations/llm-providers/gemini` for the current set rather than hard-coding ids. Catalog reads are cached for up to 10 minutes. ### Read the model catalog ```bash cURL curl https://api.modulex.dev/integrations/llm-providers/gemini \ -H "Authorization: Bearer mx_live_xxxxxxxxxxxxxxxxxxxxxxxx" \ -H "X-Organization-ID: 6f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f" ``` ```python Python detail = await client.integrations.llm_provider_detail("gemini") for model in detail.models: print(model["id"], model["display_name"]) ``` ```javascript JavaScript const detail = await client.integrations.llmProvider("gemini"); for (const model of detail.models) { console.log(model.id, model.display_name); } ``` The response is an integration-detail object whose `models` array contains the entries shown above, plus an `auth_schemas` array describing the `api_key` scheme. A truncated example: ```json Catalog detail (truncated) { "name": "gemini", "display_name": "Gemini", "description": "Google's Gemini models for advanced reasoning, multimodal tasks, and long-context processing", "integration_type": "llm_provider", "version": "1.0.0", "categories": ["AI & LLM Providers", "ai", "chat", "reasoning", "multimodal"], "auth_schemas": [ { "auth_type": "api_key", "display_name": "API Key Authentication", "description": "Connect using Google AI API Key" } ], "models": [ { "id": "gemini-2.5-pro", "display_name": "Gemini 2.5 Pro", "provider_id": "gemini", "max_input_tokens": 1048576, "max_output_tokens": 65536, "supports_vision": true, "input_usd_per_1m_tokens": 2.5, "output_usd_per_1m_tokens": 15.0 } ] } ``` ## Use Gemini in ModuleX Once Gemini is connected, select a Gemini model id wherever a model can be chosen: Call Gemini with prompts, variables, and structured output inside a workflow. Run an autonomous step where Gemini can call tools and loop. Pick Gemini as the model for a chat conversation. Configure the Assistant to reason and act with a Gemini model. If your organization holds more than one Gemini credential, ModuleX uses the credential marked default for the `gemini` integration. Set a different default with `POST /credentials/{credential_id}/set-default`. See [managing credentials](/integrations/managing-credentials). ## BYOK billing Gemini is **bring-your-own-key**, which changes how usage is billed compared with [ModuleX-managed models](/integrations/llm-providers/modulexai). Token usage is billed by **Google** against your own API key. ModuleX adds **no credit charge and no markup** for the model call itself. BYOK usage is recorded for analytics only — it does not consume ModuleX [credits](/billing/credits). ModuleX provisions the model and bills it in **credits** through the [usage gate](/billing/usage-gating), applying a system margin. Use this when you do not want to manage a provider account. What this means in practice: - **No ModuleX credit cost for tokens.** A Gemini call made with your Google AI key is not metered in credits. Google invoices you directly at its own rates (the per-million-token prices in the [model table](#available-models)). - **Other ModuleX usage still applies.** Running a workflow or a turn through the Assistant can still consume the flat per-run credit and may pass through the billing gate even when the model itself is BYOK. See [credits & metering](/billing/credits) and [usage gating & limits](/billing/usage-gating). - **Rate limits and quotas are Google's.** Token-rate limits, monthly spend caps, and model availability for a BYOK key are governed by your Google AI account, not by ModuleX. BYOK is available on all plans — there is no separate ModuleX entitlement that gates connecting your own Gemini key. The pricing matrix on the marketing site lists BYOK for every tier; the only ModuleX-side billing that applies to a BYOK call is the non-model usage described above. **TBD:** confirm with the billing owner that no per-call ModuleX surcharge applies to BYOK model calls before treating "no markup" as a guarantee. ## Errors Catalog and credential operations return ModuleX's standard error shapes. See [errors & status codes](/api-reference/errors) for the full taxonomy. | Status | When it happens | Shape | | --- | --- | --- | | `400` | Invalid `auth_data`/`auth_type` on credential create, or asking a typed endpoint for the wrong integration type (for example `gemini` is not a tool). | `{detail: string}` | | `401` | Missing or invalid `Authorization` / missing `X-Organization-ID`. | `{detail: string}` | | `403` | Caller is not an owner/admin in the organization. | `{detail: string}` | | `404` | Unknown provider, for example `GET /integrations/llm-providers/` returns `LLM provider not found`. | `{detail: string}` | | `422` | Query/parameter validation error. | `{detail: [ ... ]}` | | `500` | Unhandled server error. | `{detail: string}` | A bad Gemini key does not surface as a ModuleX `401`. Connection validation happens against Google: `POST /credentials/test-temporary` (or saving with validation) returns `is_valid: false` with the status Google returned (for example `400` from `generativelanguage.googleapis.com` for an invalid key). The ModuleX request itself still succeeds. ## Related All providers, and how managed vs BYOK selection works. The credit-billed managed alternative to BYOK. How integrations authenticate and the auth schema variants. Create, default, rotate, and scope credentials. --- # ModuleX-managed models URL: https://docs.modulex.dev/integrations/llm-providers/modulexai Use ModuleX-managed models (modulexai) — the default LLM provider that needs no API key, ships curated models, and is metered in credits. Includes the full model catalog, credit-cost formula, and how to switch to BYOK. `modulexai` is the **ModuleX-managed model** provider — the default option for every surface that calls a language model. You do not supply or manage any provider key: ModuleX provisions the upstream keys for you and meters usage in [credits](/billing/credits) against your plan allowance and [wallet](/billing/wallet). Managed models are available out of the box on the [Chat model selector](/platform/chat/model-selection), the [Assistant](/assistant/models-and-settings), the [LLM node](/workflow-builder/nodes/llm), the [Agent node](/workflow-builder/nodes/agent), and the [AI Composer](/concepts/ai-composer). The alternative is [BYOK (bring your own key)](/integrations/llm-providers/overview): you connect a first-party provider account and that vendor bills you directly, with no ModuleX credit charge. This page covers managed models end to end — the auth model, the curated catalog, exactly what each call costs in credits, and how to switch a selection to BYOK. For how managed-versus-BYOK selection works across all providers, see [Connect LLM providers](/integrations/llm-providers/overview). **🎬 MEDIA PLACEHOLDER · MX-MEDIA-4100 · [SCREENSHOT]** **[SCREENSHOT_DESCRIPTION]:** The ModuleX-managed (`modulexai`) entry in the model selector. **[SCREENSHOT_DETAILS]:** Capture the model picker in chat (or Assistant settings) showing the ModuleX-managed models grouped together — for example `Claude Sonnet 4.6`, `GPT 5.5`, `Claude Opus 4.6` — with no API-key/connect prompt, signalling they are ready to use. Light theme, 16:9, crop to the dropdown. ## How managed models work A managed model is one whose provider wire name is `modulexai`. When you pick such a model, the request runs through ModuleX-provisioned upstream keys and the call is **costed in credits**: Choose a `modulexai` model id (for example `claude-sonnet-4.6` or `gpt-5.5`) anywhere a model can be picked. No credential is required — `modulexai` is exempt from credential validation because it uses ModuleX's own model-level keys. ModuleX maps the model id to its upstream wire model and request settings. Several managed models are routed through an aggregator with a Bedrock-then-Anthropic fallback order; you select by ModuleX `model_id` and the routing is handled for you. Deprecated ids are resolved to their replacement automatically — see [Deprecated models route automatically](#deprecated-models-route-automatically). Every managed turn passes through the [usage gate](/billing/usage-gating): it reserves credit before any work, charges on success, and denies with a [`DenialEnvelope`](/api-reference/errors) on `402` / `403` / `429` when your allowance and wallet are exhausted. See [Credit cost](#credit-cost). **🎬 MEDIA PLACEHOLDER · MX-MEDIA-4101 · [IMAGE]** **[IMAGE_DESCRIPTION]:** Request path for a managed (`modulexai`) model call versus a BYOK call. **[IMAGE_DETAILS]:** Show two lanes from a single "model call" box. Managed lane: ModuleX-provisioned key, routed through the aggregator, passing the credit usage gate (reserve to charge to settle) and metered in credits. BYOK lane: your own credential, routed straight to the upstream vendor, billed by the vendor, not credited. Label which lane consumes credits. 16:9, light and dark variants, ModuleX brand palette, no UI chrome. ### No key required: the `modulex_key` auth schema Managed access uses a single auth schema, `modulex_key`. Unlike BYOK providers, it has **no fields for you to fill in** — its `setup_environment_variables` array is empty. ModuleX supplies the upstream key automatically and tracks usage against your subscription plan's monthly credit allowance. `modulex_key` — the managed-key scheme. ModuleX provides the API keys automatically; no key configuration is needed. `ModuleX Managed Key`. Empty (`[]`). There is nothing to enter to use managed models. Because `modulexai` needs no credential, you do **not** call `POST /credentials` for it. Managed models are usable as soon as your organization is on any plan (Free included). You still need to be an [owner or admin](/security/roles-permissions) to manage the integration catalog or change defaults; the `member` role is retired. ## Available models ModuleX serves the managed model list from the catalog at `GET /integrations/llm-providers/modulexai`. The catalog is **read-only metadata** used by the model selectors and the builder — it is not the execution path, and it never returns or stores a secret key. The catalog read is cached for up to 10 minutes. The active models below are the ones ModuleX currently advertises for `modulexai`. There are six deprecated `bedrock-*` ids in addition; see [Deprecated models](#deprecated-models). ### Chat and reasoning models | Model id | Display name | Upstream | Max input | Max output | Vision | Input `$/1M` | Output `$/1M` | | --- | --- | --- | --- | --- | --- | --- | --- | | `claude-sonnet-4.6` | Claude Sonnet 4.6 | Anthropic | 200,000 | 64,000 | Yes | `$3.00` | `$15.00` | | `claude-opus-4.6` | Claude Opus 4.6 | Anthropic | 200,000 | 128,000 | Yes | `$5.00` | `$25.00` | | `claude-haiku-4.5` | Claude Haiku 4.5 | Anthropic | 200,000 | 64,000 | Yes | `$0.80` | `$4.00` | | `gpt-5.5` | GPT 5.5 | OpenAI | 1,050,000 | 128,000 | Yes | `$5.00` | `$30.00` | | `gpt-5.4` | GPT 5.4 | OpenAI | 1,050,000 | 128,000 | Yes | `$2.50` | `$15.00` | | `gpt-5.4-mini` | GPT 5.4 mini | OpenAI | 400,000 | 128,000 | Yes | `$0.75` | `$4.50` | | `gpt-5.4-nano` | GPT 5 nano | OpenAI | 400,000 | 128,000 | Yes | `$0.20` | `$1.25` | | `gpt-5-chat-latest` | GPT 5 chat latest | OpenAI | 128,000 | 16,384 | Yes | `$1.25` | `$10.00` | | `gpt-5.3-codex` | GPT 5.3 codex | OpenAI | 400,000 | 128,000 | Yes | `$1.75` | `$14.00` | | `o3` | o3 | OpenAI | 200,000 | 100,000 | Yes | `$2.00` | `$8.00` | | `gemma-3-27b` | Gemma 3 27B | Google | 128,000 | 8,192 | Yes | `$0.10` | `$0.10` | | `gemma-3-12b` | Gemma 3 12B | Google | 128,000 | 8,192 | Yes | `$0.05` | `$0.05` | | `gemma-3-4b` | Gemma 3 4B | Google | 128,000 | 8,192 | No | `$0.02` | `$0.02` | ### Embedding models | Model id | Display name | Upstream | Max input | Dimensions | Input `$/1M` | Output `$/1M` | | --- | --- | --- | --- | --- | --- | --- | | `text-embedding-3-large` | Text Embedding 3 Large | OpenAI | 8,191 | 3,072 | `$0.13` | `$0.00` | | `text-embedding-3-small` | Text Embedding 3 Small | OpenAI | 8,191 | 1,536 | `$0.02` | `$0.00` | The per-million-token prices above are the **upstream provider's USD rates** recorded in the ModuleX catalog. For managed (`modulexai`) usage they are **inputs to the credit-cost formula**, not amounts you pay directly — your actual charge is in [credits](/billing/credits) and includes a 5% system margin. See [Credit cost](#credit-cost). Embedding models report a zero output price because embedding forces `completion_tokens = 0`. The model list is served from the catalog and changes as ModuleX adds, routes, or deprecates models. Read `GET /integrations/llm-providers/modulexai` for the current set rather than hard-coding ids; catalog reads are cached for up to 10 minutes. ### The model record Each entry in the provider's `models[]` array carries the fields below. The ModuleX `model_id` you select (for example `claude-sonnet-4.6`). Human-readable name shown in selectors. Display vendor — `Anthropic`, `OpenAI`, or `Google` on active managed models. Routing provider slug. Managed chat/reasoning models route through `openrouter`; the OpenAI embedding models route through `openai`. Maximum context window in tokens. Maximum tokens the model can generate. `0` for embedding models. Knowledge-cutoff date (for example `2026-01-01`). This field, not a `knowledge_cutoff` key, carries the cutoff. Quality score, 1–5. Latency score, 1–5 (higher is faster). Whether the model accepts image input. Upstream input price per 1M tokens, as a flat number (not a nested `pricing` object). Upstream output price per 1M tokens. `0.0` on embedding models. `true` on the two embedding models. Embedding models also report `embedding_dimension` and a zero output price. Vector dimension for embedding models (`3072` for `text-embedding-3-large`, `1536` for `text-embedding-3-small`). Lifecycle state. Present and set to `deprecated` on the retired `bedrock-*` ids; active models omit it (treated as healthy). On a deprecated model, the successor `model_id` ModuleX routes the call to instead. ```json { "id": "claude-sonnet-4.6", "display_name": "Claude Sonnet 4.6", "provider": "Anthropic", "provider_id": "openrouter", "max_input_tokens": 200000, "max_output_tokens": 64000, "data_freshness": "2026-01-01", "intelligence": 4, "speed": 3, "supports_vision": true, "input_usd_per_1m_tokens": 3.0, "output_usd_per_1m_tokens": 15.0 } ``` ### Read the model catalog ```bash cURL curl https://api.modulex.dev/integrations/llm-providers/modulexai \ -H "Authorization: Bearer mx_live_xxxxxxxxxxxxxxxxxxxxxxxx" \ -H "X-Organization-ID: 6f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f" ``` ```python Python import asyncio from modulex import Modulex async def main(): async with Modulex( api_key="mx_live_xxxxxxxxxxxxxxxxxxxxxxxx", # or MODULEX_API_KEY organization_id="6f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f", ) as client: detail = await client.integrations.llm_provider_detail("modulexai") for model in detail.models: print(model["id"], model["display_name"]) asyncio.run(main()) ``` ```javascript JavaScript import { Modulex } from "modulex-js"; const client = new Modulex({ apiKey: "mx_live_xxxxxxxxxxxxxxxxxxxxxxxx", // or process.env.MODULEX_API_KEY organizationId: "6f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f", }); const detail = await client.integrations.llmProvider("modulexai"); for (const model of detail.models) { console.log(model.id, model.display_name); } ``` The response is an integration-detail object whose `models` array contains the entries above plus an `auth_schemas` array describing the single `modulex_key` scheme. A truncated example: ```json Catalog detail (truncated) { "name": "modulexai", "display_name": "ModulexAI", "description": "ModuleX's managed LLM provider with curated models and usage tracking", "integration_type": "llm_provider", "version": "1.0.0", "categories": ["AI & LLM Providers", "ai", "chat", "managed"], "auth_schemas": [ { "auth_type": "modulex_key", "display_name": "ModuleX Managed Key", "description": "Use ModuleX's managed API keys with usage tracked against your monthly credit limit", "setup_environment_variables": [] } ], "models": [ { "id": "claude-sonnet-4.6", "display_name": "Claude Sonnet 4.6", "max_input_tokens": 200000, "max_output_tokens": 64000, "supports_vision": true, "input_usd_per_1m_tokens": 3.0, "output_usd_per_1m_tokens": 15.0 } ] } ``` The catalog detail endpoints are owner/admin-gated and org-scoped: every request needs `Authorization: Bearer mx_live_…` plus `X-Organization-ID`, and the caller must be an owner or admin. See [Authentication](/api-reference/authentication). ## Credit cost Managed (`modulexai`) usage is the only LLM-provider usage that consumes [credits](/billing/credits). A credit is the managed-usage billing unit: **100 credits = $1.00** (1 credit = `$0.01`). BYOK calls are never credited. A managed call is metered in two parts. ### Per-turn run charge Each logical run or chat turn is charged a flat `RUN_CREDIT` of **1** credit, recorded once and deduplicated by an idempotency key so a resumed turn is not double-charged. ### Token metering The model's tokens are converted to credits using the upstream per-token rates plus a system margin: ```text Token-to-credit formula cost = (prompt_tokens · input_rate / 1e6 + completion_tokens · output_rate / 1e6) · MARGIN · SCALE ``` where: | Symbol | Value | Meaning | | --- | --- | --- | | `input_rate` / `output_rate` | the model's `input_usd_per_1m_tokens` / `output_usd_per_1m_tokens` | upstream USD rates from the [model record](#the-model-record) | | `MARGIN` | `1.05` | a 5% system margin applied to managed usage | | `SCALE` | `100` | `100 credits = $1.00`, so 1 credit = `$0.01` | Embedding calls force `completion_tokens = 0`, so only input tokens are metered. If a model id is unknown to the pricing table, it meters to `0` rather than a silent fallback rate. Other managed operations carry their own flat base charges, also billed in credits: a managed **knowledge retrieval** reserves `RETRIEVAL_BASE` = 1 credit, a managed **document ingest** reserves `FILE_INGEST_BASE` = 1 credit, and an **integration tool** call has a `TOOL_BASE` of 1 credit (`$0.01`). These apply to the corresponding managed surfaces, not to a plain LLM call. See [Credits & metering](/billing/credits). ### Worked example A single `claude-sonnet-4.6` turn that reads 10,000 prompt tokens and writes 1,000 completion tokens: - Token cost = `(10000 · 3.0 / 1e6 + 1000 · 15.0 / 1e6) · 1.05 · 1000` = `(0.03 + 0.015) · 1.05 · 1000` ≈ **47.25 credits** (≈ `$0.047`). - Plus the flat per-turn `RUN_CREDIT` of **1** credit. So the turn costs roughly **48 credits**. The exact charge is quantized to eight decimal places when recorded. ### When a managed call is denied When managed usage exhausts your plan allowance and wallet, the billing admission gate denies the call with a flat [`DenialEnvelope`](/api-reference/errors) — `{code, layer, key, current, limit, reason}` — returned on: | Status | `layer` | When | | --- | --- | --- | | `402` | `credit` / `wallet` | Plan credits exhausted, overage disabled, or wallet balance insufficient. | | `403` | `quota` | A plan quota is exceeded. | | `429` | `rate` | A per-plan rate class (for example `sync_exec`) is exceeded; includes `Retry-After` and `X-RateLimit-*` headers. | These envelopes appear only on **managed-usage surfaces** — the run, Composer, Assistant, and managed-knowledge calls that actually meter credits. Plain credential and catalog CRUD return the FastAPI `{detail}` `HTTPException` shape instead. See [Errors & status codes](/api-reference/errors) and [Usage gating & limits](/billing/usage-gating). ## Where you select a managed model A managed model id is selectable anywhere a model is chosen — no connection step: Pick a managed model in the chat composer. Set a managed model for the Assistant to reason and act with. Set `model_id` on the node to a managed id. Run an autonomous step on a managed model. ### Deprecated models route automatically When you select a model whose `status` is `deprecated` (or `maintenance`), ModuleX resolves it to its `replacement_id` before the call — for example a `bedrock-claude-sonnet-4.6` id routes to `claude-sonnet-4.6`. Routing is cycle- and depth-guarded, so a chain of replacements terminates at a live model. For a managed integration, if a requested id is unknown or its chain cannot resolve, ModuleX softens to the integration's first serving (healthy, non-deprecated) model rather than failing. You do not need to update saved selections immediately, but prefer a live id for clarity. #### Deprecated models These six ids exist only for backward compatibility and route to their replacements. Do not select them for new work. | Deprecated id | Routes to | | --- | --- | | `bedrock-claude-sonnet-4.6` | `claude-sonnet-4.6` | | `bedrock-claude-opus-4.6` | `claude-opus-4.6` | | `bedrock-claude-haiku-4.5` | `claude-haiku-4.5` | | `bedrock-gemma-3-27b` | `gemma-3-27b` | | `bedrock-gemma-3-12b` | `gemma-3-12b` | | `bedrock-gemma-3-4b` | `gemma-3-4b` | ## Switching to BYOK Managed and BYOK are a per-selection choice — you switch by changing which model a surface uses, not by a global toggle. To move a chat, Assistant, or node off managed credits and onto a vendor-direct bill, select a model from a connected BYOK provider instead of a `modulexai` model. Create a credential for a first-party provider (`openai`, `anthropic`, `gemini`, or `xai`) with `POST /credentials`, supplying your own provider key. You must be an owner or admin. See [Connect LLM providers](/integrations/llm-providers/overview) and the per-provider pages, for example [Anthropic](/integrations/llm-providers/anthropic) and [OpenAI](/integrations/llm-providers/openai). In the [chat model selector](/platform/chat/model-selection), [Assistant settings](/assistant/models-and-settings), or a node, choose a model from the connected provider in place of the `modulexai` model. BYOK token usage is billed by the upstream vendor with no ModuleX markup and is recorded for analytics only — it does not consume credits. Non-model ModuleX usage (such as the flat per-run charge, or managed knowledge/tool calls) can still apply. Switching a model **moves cost between billing systems**. A `modulexai` selection consumes ModuleX credits and is subject to the billing gate; a BYOK selection is billed by your provider and is not credited. Switching back to a `modulexai` model returns that cost to credits. Confirm which model is selected before relying on either path. All providers, and how managed vs BYOK selection works. What a credit is and exactly what consumes credits. ## Errors Catalog reads for `modulexai` return ModuleX's standard error shapes. Managed model **calls** can additionally return the billing `DenialEnvelope` described in [When a managed call is denied](#when-a-managed-call-is-denied). | Status | When it happens | Shape | | --- | --- | --- | | `400` | Asking a typed endpoint for the wrong integration type (for example `modulexai` is not a tool). | `{detail: string}` | | `401` | Missing or invalid `Authorization`, or missing `X-Organization-ID`. | `{detail: string}` | | `403` | Caller is not an owner or admin in the organization. | `{detail: string}` | | `404` | Unknown provider, for example `GET /integrations/llm-providers/` returns `LLM provider not found`. | `{detail: string}` | | `422` | Query/parameter validation error. | `{detail: [ ... ]}` | | `402` / `403` / `429` | A managed model call hits the billing gate (credit, quota, or rate). | `DenialEnvelope` — `{code, layer, key, current, limit, reason}` | | `500` | Unhandled server error. | `{detail: string}` | See [Errors & status codes](/api-reference/errors) for all three envelope shapes and which surface emits each. ## Related Managed vs BYOK, and the full provider list. The credit unit and what consumes credits. The billing admission gate and its 402/403/429 responses. The bring-your-own-key alternative for Claude models. ## Open questions (TBD) - **Upstream routing detail.** Several managed models route through an aggregator (`openrouter`) with a Bedrock-then-Anthropic fallback order, and the OpenAI embedding models route through `openai`. The exact upstream selected for a given call is handled internally and is not exposed in any catalog API response; treat the routing as an implementation detail subject to change. - **Public catalog base URL.** The catalog and SDK examples use `https://api.modulex.dev` per the repo-wide convention; the exact public host for these endpoints is not pinned from source and should be confirmed against the deployed environment. - **Per-model max-output overrides.** ModuleX applies scenario-specific maximum-output-token limits to managed calls (for example a smaller cap for simple chat than for an agent step). These limits are backend-only and are never returned in a catalog response, so the catalog's `max_output_tokens` is the model ceiling, not necessarily the per-scenario limit applied at run time. --- # OpenAI URL: https://docs.modulex.dev/integrations/llm-providers/openai Connect your OpenAI account to ModuleX, browse the GPT, o-series, and embedding models in the catalog, select them in workflows, the Assistant, and chat, and understand BYOK billing. OpenAI is a **bring-your-own-key (BYOK) LLM provider** in ModuleX. You connect your own OpenAI API key as a [credential](/concepts/credentials-oauth), and ModuleX uses it to call OpenAI's GPT, o-series reasoning, and embedding models from your [workflows](/concepts/workflows-and-runs), the [Assistant](/concepts/assistant), the [AI Composer](/concepts/ai-composer), and [chat](/platform/chat/overview). Because OpenAI usage runs through your own key, it is **billed by OpenAI directly** and consumes **no ModuleX [credits](/concepts/credits-billing)**. This page covers connecting an OpenAI credential, the OpenAI models ModuleX exposes in its catalog, how to select an OpenAI model on each surface, and how BYOK billing works. For the cross-provider picture see [LLM providers](/integrations/llm-providers/overview); for the credential model and OAuth see [Credentials & OAuth2](/concepts/credentials-oauth). OpenAI is the integration named `openai` on the wire (`integration_type: "llm_provider"`). It exposes **one auth schema** — `api_key` — so there is no ModuleX-managed (pooled-key) option for OpenAI. For credit-metered managed models, use [ModuleX-managed models](/integrations/llm-providers/modulexai) instead. ## Before you start Create a secret key at the [OpenAI API keys page](https://platform.openai.com/api-keys). It starts with `sk-` (for example `sk-proj-…`). ModuleX validates it against the format `^sk-[a-zA-Z0-9\-_]{20,}$`. Creating and managing credentials and browsing the integration catalog require the **owner** or **admin** role in your [organization](/concepts/organizations-roles). The retired `member` role cannot perform these actions. See [Roles & permissions](/security/roles-permissions). Every API call sends `Authorization: Bearer mx_live_…` and `X-Organization-ID`. See [Authentication](/api-reference/authentication). BYOK usage is charged by OpenAI to your OpenAI account, not by ModuleX. Make sure your OpenAI account has billing enabled and sufficient quota. ## Connect OpenAI You connect OpenAI by creating an `api_key` [credential](/concepts/credentials-oauth) for the `openai` integration. ModuleX auto-detects the credential type from the body you send: an `auth_data` object containing `api_key` is stored as an `api_key` credential. In the OpenAI dashboard, go to [API keys](https://platform.openai.com/api-keys), choose **Create new secret key**, name it, set its permissions, and copy the value. You only see it once — store it securely until you paste it into ModuleX. Call `POST /credentials/test-temporary` to check the key works **before** persisting it. ModuleX runs OpenAI's configured test — a minimal `POST https://api.openai.com/v1/chat/completions` with `gpt-5.4-mini` and `max_tokens: 10` — and reports `is_valid`. Call `POST /credentials` with `integration_name: "openai"` and `auth_data.api_key`. Set `make_default: true` to make it the default OpenAI credential for the organization. ModuleX encrypts the key at rest and never returns it in clear text. The key is now resolvable for any surface that runs an OpenAI model — workflows, the Assistant, the Composer, and chat. See [Select an OpenAI model](#select-an-openai-model). **🎬 MEDIA PLACEHOLDER · MX-MEDIA-4060 · [SCREENSHOT]** **[SCREENSHOT_DESCRIPTION]:** The ModuleX app credential dialog for connecting OpenAI with an API key. **[SCREENSHOT_DETAILS]:** Capture the "Connect OpenAI" credential form in the integrations/credentials area of the ModuleX app. Show the OpenAI logo, the API Key field (masked, placeholder `sk-proj-…`), the optional Base URL field defaulting to `https://api.openai.com/v1`, the "Set as default" toggle, and the Test/Save buttons. Light theme, 16:9, crop to the dialog. ### Create the credential `POST /credentials` returns `201`. The type is auto-detected from the body — passing `auth_data.api_key` yields an `api_key` credential. See the full credentials reference in [Credentials & OAuth2](/concepts/credentials-oauth). Must be `openai`. The secret material. For OpenAI, `{"api_key": "sk-proj-…"}`. The presence of `api_key` selects the `api_key` auth type. To target an OpenAI-compatible endpoint, also include `base_url` (see [OpenAI-compatible endpoints](#openai-compatible-endpoints)). A human-readable label. Defaults to the integration's display name (`OpenAI`) when omitted. Set this credential as the default OpenAI credential for the organization. Only one credential per integration can be the default. Optional ISO-8601 datetime after which ModuleX treats the credential as expired. **Response** — `201 Created` (secret masked): The credential's UUID. Pass this as `credential_id` to pin a specific OpenAI key on a run. `openai`. `llm_provider`. `api_key`. Whether this is the organization's default OpenAI credential. ISO-8601 timestamp of the last resolution, or `null` if never used. ```bash cURL curl -X POST https://api.modulex.dev/credentials \ -H "Authorization: Bearer mx_live_xxx" \ -H "X-Organization-ID: 11111111-1111-1111-1111-111111111111" \ -H "Content-Type: application/json" \ -d '{ "integration_name": "openai", "display_name": "Production OpenAI key", "make_default": true, "auth_data": { "api_key": "sk-proj-abc123" } }' ``` ```python Python from modulex import Modulex async with Modulex( api_key="mx_live_xxx", organization_id="11111111-1111-1111-1111-111111111111", ) as client: credential = await client.credentials.create( "openai", display_name="Production OpenAI key", make_default=True, auth_data={"api_key": "sk-proj-abc123"}, ) print(credential.credential_id) ``` ```javascript JavaScript import { Modulex } from "modulex-js"; const client = new Modulex({ apiKey: "mx_live_xxx", organizationId: "11111111-1111-1111-1111-111111111111", }); const credential = await client.credentials.create({ integrationName: "openai", displayName: "Production OpenAI key", makeDefault: true, authData: { api_key: "sk-proj-abc123" }, }); console.log(credential.credential_id); ``` ### Validate a key before saving `POST /credentials/test-temporary` validates auth data **before** you persist it. ModuleX runs OpenAI's configured test endpoint (`POST https://api.openai.com/v1/chat/completions`, model `gpt-5.4-mini`, `max_tokens: 10`) and treats HTTP `200` with `id`, `choices`, and `model` present as success. `openai`. `api_key`. `{"api_key": "sk-proj-…"}`. Optionally include `base_url`. The response reports `is_valid`, a human-readable `message`, `tested_at`, and a `test_method` of `api_call`. ```bash cURL curl -X POST https://api.modulex.dev/credentials/test-temporary \ -H "Authorization: Bearer mx_live_xxx" \ -H "X-Organization-ID: 11111111-1111-1111-1111-111111111111" \ -H "Content-Type: application/json" \ -d '{ "integration_name": "openai", "auth_type": "api_key", "auth_data": { "api_key": "sk-proj-abc123" } }' ``` ```python Python result = await client.credentials.test_temporary( "openai", "api_key", {"api_key": "sk-proj-abc123"}, ) print(result.is_valid, result.message) ``` ```javascript JavaScript const result = await client.credentials.testTemporary({ integrationName: "openai", authType: "api_key", authData: { api_key: "sk-proj-abc123" }, }); console.log(result.isValid, result.message); ``` ### OpenAI-compatible endpoints OpenAI's auth schema includes an optional `base_url` field, so you can point the same credential at any OpenAI-compatible API (a proxy, gateway, or self-hosted server that speaks the OpenAI wire protocol). The API base URL. Only change this for an OpenAI-compatible endpoint. ModuleX validates it against `^https?://.*`. Include `base_url` inside `auth_data` when you create the credential. Leave it unset to use OpenAI directly. ```bash cURL curl -X POST https://api.modulex.dev/credentials \ -H "Authorization: Bearer mx_live_xxx" \ -H "X-Organization-ID: 11111111-1111-1111-1111-111111111111" \ -H "Content-Type: application/json" \ -d '{ "integration_name": "openai", "display_name": "OpenAI-compatible gateway", "auth_data": { "api_key": "sk-proxy-abc123", "base_url": "https://gateway.example.com/v1" } }' ``` The catalog model ids on this page (for example `gpt-5.5`) map to OpenAI's own model names via each model's `body_extra.model`. A custom `base_url` must serve those same model names, or the upstream call returns a model-not-found error from your endpoint. ## Available models ModuleX serves OpenAI's models from its integration catalog. The list below is the catalog as shipped in the backend OpenAI manifest; OpenAI updates models over time, so always treat the **live catalog** ([browse it via the API](#browse-openai-in-the-catalog)) as authoritative. Each model carries a standardized `id` (the value you pass as `model_id`), pricing in **USD per 1M tokens** (input and output), a context window (`max_input_tokens`), an output cap (`max_output_tokens`), a knowledge cutoff (`data_freshness`), `intelligence` and `speed` scores (1–5), and vision support. Pricing shown is **OpenAI's list price** that ModuleX records for analytics — your actual charge comes from OpenAI, not ModuleX. ### Chat and reasoning models | `model_id` | Display name | Context (in) | Max output | Input $/1M | Output $/1M | Vision | Cutoff | | --- | --- | --: | --: | --: | --: | :-: | --- | | `gpt-5.5` | GPT 5.5 | 1,050,000 | 128,000 | $5.00 | $30.00 | Yes | 2025-12-01 | | `gpt-5.5-pro` | GPT 5.5 Pro | 1,050,000 | 128,000 | $30.00 | $180.00 | Yes | 2025-12-01 | | `gpt-5.4` | GPT 5.4 | 1,050,000 | 128,000 | $2.50 | $15.00 | Yes | 2025-08-31 | | `gpt-5.4-pro` | GPT 5.4 Pro | 1,050,000 | 128,000 | $30.00 | $180.00 | Yes | 2025-08-31 | | `gpt-5.4-mini` | GPT 5.4 mini | 400,000 | 128,000 | $0.75 | $4.50 | Yes | 2024-05-31 | | `gpt-5.4-nano` | GPT 5 nano | 400,000 | 128,000 | $0.20 | $1.25 | Yes | 2024-08-31 | | `gpt-5-chat-latest` | GPT 5 chat latest | 128,000 | 16,384 | $1.25 | $10.00 | Yes | 2024-09-30 | | `gpt-5.3-codex` | GPT 5.3 codex | 400,000 | 128,000 | $1.75 | $14.00 | Yes | 2025-08-31 | | `o3-pro` | o3-pro | 200,000 | 100,000 | $20.00 | $80.00 | Yes | 2024-06-01 | | `o3` | o3 | 200,000 | 100,000 | $2.00 | $8.00 | Yes | 2024-06-01 | ### Embedding models Embedding models return a vector instead of text (`max_output_tokens` is `0`). Use them to build a [knowledge base](/concepts/knowledge-rag) with OpenAI embeddings, or in a workflow that needs vectors. | `model_id` | Display name | Dimensions | Max input | Input $/1M | Output $/1M | | --- | --- | --: | --: | --: | --: | | `text-embedding-3-large` | Text Embedding 3 Large | 3072 | 8,191 | $0.13 | $0.00 | | `text-embedding-3-small` | Text Embedding 3 Small | 1536 | 8,191 | $0.02 | $0.00 | `text-embedding-3-small` produces 1536-dimensional vectors, which match ModuleX's managed vector storage column width. `text-embedding-3-large` produces 3072-dimensional vectors. Pick the embedding model that matches your vector store's dimension when you configure a knowledge base. See [Managed knowledge](/platform/knowledge/managed) and [Knowledge & RAG](/concepts/knowledge-rag). Every entry in the OpenAI `models[]` array carries these fields. ModuleX stores them verbatim and returns them on the [detail endpoint](#browse-openai-in-the-catalog). The standardized catalog model id you pass as `model_id` (for example `gpt-5.5`). The human-readable model name. The display vendor — `OpenAI`. The routing provider slug — `openai`. Pass this as `provider_id` in an `llm` config. The context-window size in tokens. The maximum tokens the model can return. `0` for embedding models. The provider request-body fragment, including the real OpenAI wire `model` name. For OpenAI the catalog `id` and `body_extra.model` are the same. Extra request headers. Empty (`{}`) for every OpenAI model. A usage blurb describing the model's strengths. The knowledge cutoff date (ISO date). There is no separate `knowledge_cutoff` field. A quality score, `1`–`5`. A latency score, `1`–`5` (higher is faster). Whether the model accepts image input. OpenAI's list input price per 1M tokens (flat field, not a nested `pricing` object). OpenAI's list output price per 1M tokens. `0` for embedding models. Present and `true` only on embedding models. The output vector dimension — present only on embedding models (`3072` for large, `1536` for small). ## Select an OpenAI model Across the API and SDKs, you choose a model with an `llm` configuration object. For OpenAI, every field uses the `openai` slug: `openai`. `openai`. A catalog model id from [Available models](#available-models), for example `gpt-5.5`. The id of the OpenAI credential to use. Omit it to let ModuleX [resolve a credential](/concepts/credentials-oauth#credential-resolution-at-run-time): your `is_default` OpenAI credential, then the most-recent valid one. ModuleX prefers your own keys over managed keys — and OpenAI is BYOK-only, so resolution always lands on a key you connected. This is the same shape the [Assistant](/concepts/assistant) and [AI Composer](/concepts/ai-composer) accept for their `llm` field: ```json llm config { "integration_name": "openai", "provider_id": "openai", "model_id": "gpt-5.5", "credential_id": "c0ffee00-1111-2222-3333-444455556666" } ``` In the [LLM node](/workflow-builder/nodes/llm) (and the [Agent node](/workflow-builder/nodes/agent)), set the node's `llm_config` to an OpenAI model. ModuleX resolves the standardized `model_id` to OpenAI's wire model via the catalog's `body_extra.model`, then calls OpenAI with your resolved credential. See [Workflow engine & nodes](/concepts/workflow-engine). Pass the `llm` object when you start an Assistant chat. The Assistant rebuilds the model from this config on every turn and on resume, so the same `llm` object must be supplied each time. See [Models & settings](/assistant/models-and-settings). The Composer takes the same `llm` object to generate and edit your workflow graph. If you omit it, ModuleX falls back to the organization's configured Composer default model. See [AI Composer in the builder](/workflow-builder/composer). In the app's [chat](/platform/chat/overview), pick an OpenAI model from the [model selector](/platform/chat/model-selection). The connected OpenAI credential is used automatically. **Deprecation routing applies only to managed models.** For managed integrations (`modulexai`), a model marked `deprecated` routes to its `replacement_id` automatically, and unknown ids fall back to the integration default. OpenAI is BYOK (a passthrough integration), so its `model_id` is used as-is — an unknown id is sent straight to OpenAI and fails there. None of the OpenAI catalog models are currently marked deprecated. ## Browse OpenAI in the catalog The [integration catalog](/integrations/catalog) is the read-only discovery surface. Use it to fetch OpenAI's live models, pricing, and auth schema. These catalog endpoints authenticate with a **Clerk JWT** (the app token), not an `mx_live_*` API key, and still require `X-Organization-ID` and the owner/admin role. `openai`. **Response** — `200 OK`, an `IntegrationDetail`: `openai`. `OpenAI`. `llm_provider`. The OpenAI model list described in [Available models](#available-models). The OpenAI auth schemas. OpenAI ships a single `api_key` schema with `setup_instructions`, the `api_key` and `base_url` `setup_environment_variables`, and a `test_endpoint`. `https://platform.openai.com/docs`. The integration's `extra_metadata` (`status`, `recommended`, timestamps, popularity score). ```bash cURL curl https://api.modulex.dev/integrations/llm-providers/openai \ -H "Authorization: Bearer " \ -H "X-Organization-ID: 11111111-1111-1111-1111-111111111111" ``` ```bash cURL — list all LLM providers curl https://api.modulex.dev/integrations/llm-providers \ -H "Authorization: Bearer " \ -H "X-Organization-ID: 11111111-1111-1111-1111-111111111111" ``` ```json { "name": "openai", "display_name": "OpenAI", "description": "OpenAI's GPT models for chat, completion, and embeddings", "logo": "modulex:openai-themed", "app_url": "https://platform.openai.com", "docs_url": "https://platform.openai.com/docs", "categories": ["AI & LLM Providers", "ai", "chat", "completion"], "integration_type": "llm_provider", "version": "1.0.0", "auth_schemas": [ { "auth_type": "api_key", "display_name": "API Key Authentication", "description": "Connect using OpenAI API Key", "setup_environment_variables": [ { "name": "api_key", "required": true, "sensitive": true }, { "name": "base_url", "required": false, "default": "https://api.openai.com/v1" } ], "test_endpoint": { "url": "https://api.openai.com/v1/chat/completions", "method": "POST" } } ], "models": [ { "id": "gpt-5.5", "display_name": "GPT 5.5", "provider": "OpenAI", "provider_id": "openai", "max_input_tokens": 1050000, "max_output_tokens": 128000, "supports_vision": true, "input_usd_per_1m_tokens": 5.0, "output_usd_per_1m_tokens": 30.0 } ], "metadata": { "status": "active", "recommended": true, "popularity_score": 92 } } ``` ModuleX maintains a backend-only `llm_max_tokens` resolver that caps output tokens per scenario (for example chat vs workflow node). Those values are **never returned by any catalog API response** — do not rely on the catalog `max_output_tokens` as the effective per-scenario output cap. ## BYOK billing OpenAI is **bring-your-own-key**. Usage on your OpenAI credential is billed by OpenAI to your OpenAI account and is **not** metered in ModuleX [credits](/concepts/credits-billing). BYOK calls — OpenAI included — carry no ModuleX credit gate. There is no markup, and these calls do not draw down your plan allowance or [wallet](/billing/wallet). Because there is no credit gate on BYOK calls, OpenAI usage does not produce the `402`/`403`/`429` `DenialEnvelope` that managed usage can. See [Usage gating & limits](/billing/usage-gating). Token costs accrue on your OpenAI account at OpenAI's rates. Set spend limits and monitor usage in the OpenAI dashboard. ModuleX records token counts and the catalog list price for OpenAI usage for **analytics**, so you can see consumption per workflow or run — but no credits are deducted. **Want credit-metered, no-key-required models instead?** Use [ModuleX-managed models](/integrations/llm-providers/modulexai) (the `modulexai` provider), which run on ModuleX-provisioned keys and bill in credits — gated by your plan. There is no managed (pooled-key) path for `openai`; OpenAI is always BYOK. See [Credits & metering](/billing/credits). Rate limits, quotas, and model availability for BYOK calls are governed by **your OpenAI account**, not by ModuleX. A `429` or quota error from OpenAI surfaces as an upstream error on the run, distinct from ModuleX's own [rate limiting](/api-reference/rate-limiting). ## Managing the credential Day-to-day operations use the standard [credentials API](/concepts/credentials-oauth#the-credentials-api). For OpenAI specifically: - **Rotate a key.** Secrets are immutable through `PUT /credentials/{id}` (that route updates only `display_name` and metadata). To rotate, create a new OpenAI credential with the new key and delete the old one — there is no rotate endpoint. - **Set the default.** `POST /credentials/{id}/set-default` makes one OpenAI credential the organization default, used when no `credential_id` is supplied. - **Test a saved key.** `POST /credentials/{id}/test` re-runs OpenAI's test endpoint against the stored key. - **Delete.** `DELETE /credentials/{id}` removes it and returns `204`. Runs that relied on it will fail credential resolution until another valid OpenAI credential exists. See [Managing credentials](/integrations/managing-credentials) for the app workflow and [Credentials & OAuth2](/concepts/credentials-oauth) for the complete API. ## Where to go next Compare OpenAI with the other model providers, managed and BYOK. The credit-metered, no-key-required default provider. The full credential model, resolution precedence, and credentials API. Call an OpenAI model from a workflow with prompts, variables, and structured output. --- # Connect LLM providers URL: https://docs.modulex.dev/integrations/llm-providers/overview Connect language-model providers to ModuleX as managed (modulexai, billed in credits) or BYOK (your own key, uncosted), and select models for chat, the Assistant, and workflow nodes. ModuleX runs every language-model call through an **LLM provider**: a connector that exposes a set of **models** and one or more **auth schemas**. You connect a provider once, then any surface that calls a model — the [Chat model selector](/platform/chat/model-selection), the [Assistant](/assistant/models-and-settings), the [LLM node](/workflow-builder/nodes/llm), the [Agent node](/workflow-builder/nodes/agent), and the [AI Composer](/concepts/ai-composer) — can use its models. There are two ways to connect a provider, and the difference is who pays and how: ModuleX provisions the upstream provider keys for you. Usage is metered in **credits** against your plan allowance and wallet. No provider account or key required. You supply your own provider API key. Usage is **billed directly by the provider** with no ModuleX markup, and is **not** charged in ModuleX credits (analytics-only). **🎬 MEDIA PLACEHOLDER · MX-MEDIA-4050 · [IMAGE]** **[IMAGE_DESCRIPTION]:** Diagram of the two connection paths for an LLM provider. **[IMAGE_DETAILS]:** Show a single "LLM provider" box fanning into two lanes — "ModuleX-managed (modulexai, modulex_key auth)" routed through ModuleX-provisioned keys and metered in credits, and "BYOK (api_key auth)" routed directly to the upstream vendor and billed by the vendor. Label which lane consumes credits. 16:9, light and dark variants, ModuleX brand palette, no UI chrome. ## Supported providers The catalog ships these LLM providers. The **wire name** is the value you send as `integration_name` when you create a credential or pick a model. | Provider | Wire name | Connect with | Billing | Page | | --- | --- | --- | --- | --- | | ModuleX-managed | `modulexai` | `modulex_key` (no key needed) | Credits | [ModuleX-managed models](/integrations/llm-providers/modulexai) | | OpenAI | `openai` | `api_key` (BYOK) | Provider-direct | [OpenAI](/integrations/llm-providers/openai) | | Anthropic | `anthropic` | `api_key` (BYOK) | Provider-direct | [Anthropic](/integrations/llm-providers/anthropic) | | Google Gemini | `gemini` | `api_key` (BYOK) | Provider-direct | [Google Gemini](/integrations/llm-providers/gemini) | | xAI Grok | `xai` | `api_key` (BYOK) | Provider-direct | [xAI Grok](/integrations/llm-providers/xai) | Two further providers exist on the wire but are routing layers, not separately documented destinations. `openrouter` is a BYOK aggregator used internally to route many of the managed models, and `bedrock` carries only deprecated model ids that route to their replacements. Connect `modulexai` for managed access or a first-party provider above for BYOK; you do not need to connect `openrouter` or `bedrock` directly. See [Selecting a model](#selecting-a-model) for how deprecated ids are handled, and the [open questions](#open-questions-tbd) below. Each provider's detail record is read-only metadata served by the catalog API (`GET /integrations/llm-providers/{provider_name}`). It carries the provider's `models[]` and its `auth_schemas[]`; it never returns or stores your secret key. ## Managed vs BYOK The two modes differ in authentication, billing, and what you have to supply. - **Wire name:** `modulexai`. - **Auth schema:** `modulex_key`. ModuleX provides the upstream key automatically — `setup_environment_variables` is empty, so there is nothing for you to enter. - **Billing:** **credited.** Every managed turn is metered (see [Credit cost](#credit-cost-for-managed-usage)) and flows through the billing admission gate, which can return a [`DenialEnvelope`](/api-reference/errors) on `402` / `403` / `429` when your allowance and wallet are exhausted. - **Use it when** you want one provider with no key management, curated models, and unified usage tracking. It is the default and recommended provider. - Models are curated and may be routed across upstreams (for example, several `modulexai` models route through OpenRouter with a Bedrock-then-Anthropic fallback order). You select by ModuleX `model_id`; the routing is handled for you. - **Wire names:** `openai`, `anthropic`, `gemini`, `xai`. - **Auth schema:** `api_key`. You supply the provider's key (and optionally a custom `base_url` for compatible endpoints). - **Billing:** **uncosted in ModuleX credits.** The upstream provider bills your account directly, with no ModuleX markup. ModuleX records BYOK calls for analytics only; the credit gate does not charge them. - **Use it when** you have provider accounts, want vendor-direct pricing, or need a specific model not offered under `modulexai`. - There is no `feature.byok` plan entitlement — BYOK is not plan-gated. You still need the [owner or admin role](/security/roles-permissions) to manage the provider credential. Managed (`modulexai`) usage **consumes credits and is subject to the billing gate**; BYOK usage is **not** credited. Switching a chat, Assistant, or node from a `modulexai` model to a BYOK model moves that cost off your ModuleX credits and onto your provider bill, and vice versa. Confirm which model is selected before you rely on either billing path. ## Connecting a provider Managed access needs no setup — `modulexai` is available out of the box and requires no credential. For BYOK, you create a **credential** that stores your provider key (encrypted at rest), in the app or over the API. ### In the app Open the integration catalog, find the provider (for example, OpenAI), and choose to connect it. You must be an **owner or admin** of the organization. Paste the provider API key into the field defined by the provider's `api_key` auth schema. Some providers also accept an optional **Base URL** for compatible endpoints. ModuleX validates the key against the provider's `test_endpoint` before saving (a minimal, low-cost request). On success the credential is stored and becomes selectable wherever models are chosen. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-4051 · [SCREENSHOT]** **[SCREENSHOT_DESCRIPTION]:** The connect-credential dialog for a BYOK LLM provider. **[SCREENSHOT_DETAILS]:** Capture the OpenAI provider connect modal showing the API Key field (masked), the optional Base URL field, and the validate/save action. Use a placeholder key value, never a real secret. Light theme, cropped to the dialog. ### Over the API Create the credential with `POST /credentials`. The request body is read raw and the **auth type is auto-detected from the body** — sending `auth_data.api_key` creates an `api_key` credential. Authenticate every request per [Authentication](/api-reference/authentication): `Authorization: Bearer mx_live_…` plus `X-Organization-ID`. The provider wire name — `openai`, `anthropic`, `gemini`, or `xai`. Use `modulexai` only for managed access, which needs no key. The secret payload. For a BYOK provider, include `api_key`. The presence of `auth_data.api_key` is what selects the `api_key` credential type. Your provider API key (for example, a key starting `sk-ant-` for Anthropic or `sk-` for OpenAI). Stored encrypted; never returned in responses. Optional custom base URL for a provider-compatible endpoint. Defaults to the provider's standard API host when omitted. A human-readable label for the credential, shown in the app and credential list. When `true`, this credential becomes the default for the integration; any prior default for the same integration is unset. ```bash cURL curl -X POST https://api.modulex.dev/credentials \ -H "Authorization: Bearer mx_live_xxxxxxxxxxxxxxxxxxxxxxxx" \ -H "X-Organization-ID: 8f1c2b3a-4d5e-6f70-8192-a3b4c5d6e7f8" \ -H "Content-Type: application/json" \ -d '{ "integration_name": "openai", "display_name": "Production OpenAI key", "make_default": true, "auth_data": { "api_key": "sk-proj-xxxxxxxxxxxxxxxxxxxx" } }' ``` ```python Python import os from modulex import Modulex client = Modulex( api_key=os.environ["MODULEX_API_KEY"], # mx_live_... organization_id=os.environ["MODULEX_ORG_ID"], ) credential = await client.credentials.create( integration_name="openai", display_name="Production OpenAI key", make_default=True, auth_data={"api_key": os.environ["OPENAI_API_KEY"]}, ) print(credential.id) ``` ```javascript JavaScript import { Modulex } from "modulex-js"; const client = new Modulex({ apiKey: process.env.MODULEX_API_KEY, // mx_live_... organizationId: process.env.MODULEX_ORG_ID, }); const credential = await client.credentials.create({ integration_name: "openai", display_name: "Production OpenAI key", make_default: true, auth_data: { api_key: process.env.OPENAI_API_KEY }, }); console.log(credential.id); ``` The SDK method names and shapes above follow the documented credential surface. Confirm the exact SDK method path against the [SDK ⇄ API parity matrix](/sdks/parity); where an SDK lacks a method, call `POST /credentials` directly with cURL. A successful create returns `201` with the credential record. To check a key before saving it, use `POST /credentials/test-temporary` with `integration_name`, `auth_type: "api_key"`, and `auth_data`; it runs the provider's validation endpoint and returns `is_valid`. For rotation, scoping, and listing, see [Managing credentials](/integrations/managing-credentials). #### Errors | Status | Shape | When | | --- | --- | --- | | `400` | `{detail}` | `auth_data`/`auth_type` could not be resolved to a known credential type, or validation failed. | | `401` | `{detail}` | Missing or invalid `Authorization`, or missing `X-Organization-ID`. | | `403` | `{detail}` | You are not an owner or admin of the organization. | | `500` | `{detail}` | Unexpected server error while creating the credential. | The credential routes return the FastAPI `{detail}` `HTTPException` envelope (a string message). The flat `DenialEnvelope` billing shape (`{code, layer, key, current, limit, reason}`) appears only on **managed-usage** surfaces — the run, Composer, Assistant, and managed-knowledge calls that actually meter credits — not on credential CRUD. See [Errors & status codes](/api-reference/errors) for all three envelope shapes. ## Selecting a model A provider exposes its models in a `models[]` array. You select a model by its ModuleX `model_id` (for example, `claude-sonnet-4.6`), and ModuleX resolves that to the upstream wire model and request shape for you. ### The model record Each entry in `models[]` carries the fields below. Values come from the provider's catalog record; pricing fields apply to BYOK provider-direct billing and inform managed credit metering. The ModuleX `model_id` you select (for example, `claude-opus-4.7`, `gpt-5.5`, `gemini-2.5-pro`). Human-readable model name shown in selectors. Display vendor, for example `Anthropic` or `OpenAI`. Routing provider slug — `anthropic`, `openai`, `gemini`, `xai`, or `openrouter` for models routed through the aggregator under `modulexai`. Maximum context window in tokens. Maximum tokens the model can generate. `0` for embedding models. Knowledge-cutoff date (for example, `2026-01-01`). This field, not a `knowledge_cutoff` key, carries the cutoff. Quality score, 1–5. Latency score, 1–5 (higher is faster). Whether the model accepts image input. Input price per 1M tokens, as a flat number (not a nested `pricing` object). Output price per 1M tokens. Present on some models — whether the model supports tool/function calling. Present on some models — whether JSON-schema structured output is supported. `true` for embedding models. Embedding models report `embedding_dimension` and a zero output price. Vector dimension for embedding models (for example, `3072` for `text-embedding-3-large`). Lifecycle state. Present and set to `deprecated` on retired model ids. On a deprecated model, the successor `model_id` that ModuleX routes the call to instead. ```json { "id": "claude-opus-4.7", "display_name": "Claude Opus 4.7", "provider": "Anthropic", "provider_id": "anthropic", "max_input_tokens": 1000000, "max_output_tokens": 128000, "data_freshness": "2026-01-01", "intelligence": 5, "speed": 1, "supports_vision": true, "input_usd_per_1m_tokens": 5.0, "output_usd_per_1m_tokens": 25.0 } ``` ### Where you select a model - **Chat** — pick the model in the chat composer. See [Model selection](/platform/chat/model-selection). - **Assistant** — set the model in Assistant settings. See [Models & settings](/assistant/models-and-settings). - **LLM node** — set `model_id` on the node. See [LLM node](/workflow-builder/nodes/llm). - **Agent node** — set `model_id` on the node. See [Agent node](/workflow-builder/nodes/agent). ### Deprecated models route automatically When you select a model whose `status` is `deprecated` (or `maintenance`), ModuleX resolves it to its `replacement_id` before the call — for example, a `bedrock-claude-sonnet-4.6` id routes to `claude-sonnet-4.6`. Routing is cycle- and depth-guarded, so a chain of replacements terminates at a live model. You do not need to update saved selections immediately, but prefer the live id for clarity. ## Credit cost for managed usage BYOK calls are **not** credited. Managed (`modulexai`) calls are metered two ways and charged against your [credits](/billing/credits): - **Per-turn run charge** — each logical run or chat turn is charged a flat `RUN_CREDIT` of **1** credit. - **Token metering** — the model's tokens are converted to credits: ```text Token-to-credit formula cost = (prompt_tokens · input_rate / 1e6 + completion_tokens · output_rate / 1e6) · MARGIN · SCALE ``` where `input_rate`/`output_rate` are the model's `input_usd_per_1m_tokens` / `output_usd_per_1m_tokens`, `MARGIN` is `1.05` (a 5% system margin), and `SCALE` is `100` (so **100 credits = $1.00**, and 1 credit = $0.01). Embedding calls force `completion_tokens = 0`. An unknown model meters to `0`, never a silent fallback rate. When managed usage exhausts your plan allowance and wallet, the call is denied with a [`DenialEnvelope`](/api-reference/errors) on `402` / `403` / `429`. See [Credits & metering](/billing/credits) and [Usage gating & limits](/billing/usage-gating) for the full model. ## Permissions and access Connecting, listing, and managing LLM-provider credentials requires the **owner** or **admin** role in the organization (`member` is retired). Every request also needs the org context header `X-Organization-ID`; a missing header is rejected. See [Roles & permissions](/security/roles-permissions) and [Org context & X-Organization-ID](/security/org-context). ## Per-provider pages The default managed provider (`modulexai`), billed in credits — no key required. Connect your OpenAI account (BYOK) and use GPT models. Connect your Anthropic account (BYOK) and use Claude models. Connect your Google account (BYOK) and use Gemini models. Connect your xAI account (BYOK) and use Grok models. ## Related - [Authentication & credentials](/integrations/authentication) — the auth-schema variants and OAuth2 flow for integrations. - [Managing credentials](/integrations/managing-credentials) — create, rotate, scope, and test credentials. - [Knowledge providers](/integrations/knowledge-providers/overview) — the vector-store side of managed vs BYOK. - [Credits & metering](/billing/credits) — what consumes credits and how it is measured. ## Open questions (TBD) - **OpenRouter and Bedrock pages.** Both names exist on the wire (`openrouter` as the BYOK aggregator behind several managed models; `bedrock` carrying only deprecated, replacement-routed ids), but neither has a canonical documentation page in the current information architecture. Whether to document them as standalone connectable providers is **TBD** pending a product decision. - **Public catalog base URL.** The provider catalog and credential examples use `https://api.modulex.dev` per the repo-wide convention; the exact public host for these endpoints is not pinned from source and should be confirmed against the deployed environment. --- # xAI Grok models URL: https://docs.modulex.dev/integrations/llm-providers/xai Connect xAI to ModuleX with your own API key, see the available Grok models, and understand how BYOK usage is billed. xAI is a [BYOK (bring-your-own-key)](/reference/glossary) LLM provider in ModuleX. You connect your own xAI API key, then use Grok models in the [LLM node](/workflow-builder/nodes/llm), the [Agent node](/workflow-builder/nodes/agent), [chat](/platform/chat/model-selection), and the [Assistant](/assistant/models-and-settings). Because xAI is BYOK, xAI bills you directly for token usage and ModuleX adds no credit charge on top — see [BYOK billing](#byok-billing) below. For the full list of provider types and how managed versus BYOK selection works, see [LLM providers](/integrations/llm-providers/overview). If you would rather have ModuleX provision and meter the model for you in credits, use [ModuleX-managed models](/integrations/llm-providers/modulexai) instead. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-4090 · [SCREENSHOT]** **[SCREENSHOT_DESCRIPTION]:** The xAI connection card in the ModuleX integrations/credentials UI. **[SCREENSHOT_DETAILS]:** Capture the API Key Authentication form for xAI in the credential-management UI: the `API Key` field (masked, sample `xai-…`), the optional `Base URL` field defaulted to `https://api.x.ai/v1`, and the "Test connection" / "Save" controls. Light theme, 16:9, crop to the card. ## What you need Create a key in the [xAI Console](https://console.x.ai/team) under **API Keys → Create new API key**. xAI keys start with `xai-` (for example `xai-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`). ModuleX validates the key against the pattern `^xai-[a-zA-Z0-9\-_]{20,}$` before saving it. Credentials are scoped to an [organization](/concepts/organizations-roles). Connecting and managing integrations requires the **owner** or **admin** role — the `member` role is retired. See [roles & permissions](/security/roles-permissions). To connect xAI over the API or an SDK, you need a ModuleX API key (`mx_live_…`) and your organization id. See [authentication](/api-reference/authentication). ## Connect xAI You connect xAI by storing your xAI API key as a [credential](/integrations/managing-credentials) for the `xai` integration. You can do this in the app or programmatically. ### In the app 1. Open the [integrations catalog](/integrations/catalog) and select **xAI**, or open **Settings → Credentials**. 2. Choose **API Key Authentication** and paste your xAI key. 3. Optionally set a custom **Base URL** (defaults to `https://api.x.ai/v1`) — only needed for xAI-compatible gateways. 4. Save. ModuleX validates the key with a minimal request before storing it (see [Test a key before saving](#test-a-key-before-saving)). xAI exposes a single authentication scheme — `api_key`. Its fields are: Your xAI API key. Stored encrypted and never returned in plaintext. Pattern `^xai-[a-zA-Z0-9\-_]{20,}$`; sample format `xai-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`. Optional custom API base URL, only needed when pointing at an xAI-compatible API. Must match `^https?://.*`. ### Over the API or an SDK Create the credential with `POST /credentials`. ModuleX detects the credential type from the body: an `auth_data.api_key` value is stored as an `api_key` credential. Set `make_default: true` to make this the default xAI credential for the organization. Every request authenticates with `Authorization: Bearer mx_live_…` plus the `X-Organization-ID` header (see [authentication](/api-reference/authentication)). ```bash cURL curl -X POST https://api.modulex.dev/credentials \ -H "Authorization: Bearer mx_live_xxxxxxxxxxxxxxxxxxxxxxxx" \ -H "X-Organization-ID: 6f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f" \ -H "Content-Type: application/json" \ -d '{ "integration_name": "xai", "display_name": "xAI production key", "make_default": true, "auth_data": { "api_key": "xai-..." } }' ``` ```python Python import asyncio from modulex import Modulex async def main(): async with Modulex( api_key="mx_live_xxxxxxxxxxxxxxxxxxxxxxxx", # or MODULEX_API_KEY organization_id="6f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f", ) as client: cred = await client.credentials.create( integration_name="xai", display_name="xAI production key", make_default=True, auth_data={"api_key": "xai-..."}, ) print(cred.id) asyncio.run(main()) ``` ```javascript JavaScript import { Modulex } from "modulex-js"; const client = new Modulex({ apiKey: "mx_live_xxxxxxxxxxxxxxxxxxxxxxxx", // or process.env.MODULEX_API_KEY organizationId: "6f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f", }); const cred = await client.credentials.create({ integration_name: "xai", display_name: "xAI production key", make_default: true, auth_data: { api_key: "xai-..." }, }); console.log(cred.id); ``` The new credential's identifier. A `201` response returns the credential metadata; the secret is never echoed back. Creating, listing, and managing credentials and the integration catalog requires the **owner** or **admin** role in the organization. A caller without that role receives a `403`. The integration-catalog read routes (for example `GET /integrations/llm-providers/xai`) are also owner/admin-gated. ### Test a key before saving Use `POST /credentials/test-temporary` to validate an xAI key without storing it. ModuleX runs the provider's test call — a 10-token `POST` to `https://api.x.ai/v1/chat/completions` against `grok-4-fast` — and reports success when the response status is `200` and includes the fields `id`, `choices`, and `model`. ```bash cURL curl -X POST https://api.modulex.dev/credentials/test-temporary \ -H "Authorization: Bearer mx_live_xxxxxxxxxxxxxxxxxxxxxxxx" \ -H "X-Organization-ID: 6f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f" \ -H "Content-Type: application/json" \ -d '{ "integration_name": "xai", "auth_type": "api_key", "auth_data": { "api_key": "xai-..." } }' ``` ```python Python result = await client.credentials.test_temporary( integration_name="xai", auth_type="api_key", auth_data={"api_key": "xai-..."}, ) print(result.is_valid, result.status_code) ``` ```javascript JavaScript const result = await client.credentials.testTemporary({ integration_name: "xai", auth_type: "api_key", auth_data: { api_key: "xai-..." }, }); console.log(result.is_valid, result.status_code); ``` `true` when the test call succeeds. The HTTP status returned by xAI's test endpoint (`200` on success). How the credential was validated. For xAI this is `api_call`. The cost class of the test call. For xAI this is `minimal` (a 10-token request). ## Available models ModuleX serves Grok models from the catalog at `GET /integrations/llm-providers/xai`. The catalog is read-only metadata used by the credential UI and the builder — it is not the execution path. The models below are the ones ModuleX currently advertises for xAI. | Model id | Display name | Max input tokens | Max output tokens | Vision | Input $/1M | Output $/1M | | --- | --- | --- | --- | --- | --- | --- | | `grok-4-0709` | Grok 4 | 256,000 | 256,000 | Yes | `$3.00` | `$15.00` | | `grok-code-fast-1` | Grok Code Fast | 256,000 | 10,000 | No | `$0.20` | `$1.50` | | `grok-4-fast` | Grok 4 Fast | 2,000,000 | 30,000 | Yes | `$0.20` | `$0.50` | Prices in the table are **xAI's per-token rates** as recorded in the ModuleX catalog, in USD per 1,000,000 tokens. Because xAI is BYOK, these are the rates xAI charges you directly — they are not ModuleX credits. See [BYOK billing](#byok-billing). Each model id is the catalog id you select in the app and in node configuration. ModuleX maps the catalog id to the underlying xAI wire model and request settings: `grok-4-0709` — the catalog id you select. `Grok 4` `xAI` `xai` — the routing provider slug. `256000` `256000` `true` `5` (quality score, 1–5). `3` (latency score, 1–5; higher is faster). `2024-10-01` (knowledge cutoff). `3.0` `15.0` ModuleX sends the wire model `grok-4-0709`. `grok-code-fast-1` `Grok Code Fast` `xAI` `xai` `256000` `10000` `false` `4` `3` `2024-10-01` `0.2` `1.5` Wire model `grok-code-fast-1`. This is a coding-focused model and does not accept image input. `grok-4-fast` `Grok 4 Fast` `xAI` `xai` `2000000` `30000` `true` `3` `4` `2024-10-01` `0.2` `0.5` Wire model `grok-4-fast`. This is the model ModuleX uses for the connection test. The model list is served from the ModuleX catalog and may change as xAI releases or deprecates models. Always read `GET /integrations/llm-providers/xai` for the current set rather than hard-coding ids. Catalog reads are cached for up to 10 minutes. ### Read the model catalog ```bash cURL curl https://api.modulex.dev/integrations/llm-providers/xai \ -H "Authorization: Bearer mx_live_xxxxxxxxxxxxxxxxxxxxxxxx" \ -H "X-Organization-ID: 6f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f" ``` ```python Python detail = await client.integrations.llm_provider_detail("xai") for model in detail.models: print(model["id"], model["display_name"]) ``` ```javascript JavaScript const detail = await client.integrations.llmProvider("xai"); for (const model of detail.models) { console.log(model.id, model.display_name); } ``` The response is an integration-detail object whose `models` array contains the entries shown above, plus an `auth_schemas` array describing the `api_key` scheme. A truncated example: ```json Catalog detail (truncated) { "name": "xai", "display_name": "xAI", "description": "xAI's Grok models for advanced reasoning, coding, and real-time information access", "integration_type": "llm_provider", "version": "1.0.0", "categories": ["AI & LLM Providers", "ai", "chat", "reasoning", "coding"], "auth_schemas": [ { "auth_type": "api_key", "display_name": "API Key Authentication", "description": "Connect using xAI API Key" } ], "models": [ { "id": "grok-4-0709", "display_name": "Grok 4", "max_input_tokens": 256000, "max_output_tokens": 256000, "supports_vision": true, "input_usd_per_1m_tokens": 3.0, "output_usd_per_1m_tokens": 15.0 } ] } ``` ## Use Grok in ModuleX Once xAI is connected, select a Grok model id wherever a model can be chosen: Call Grok with prompts, variables, and structured output inside a workflow. Run an autonomous step where Grok can call tools and loop. Pick Grok as the model for a chat conversation. Configure the Assistant to reason and act with a Grok model. If your organization holds more than one xAI credential, ModuleX uses the credential marked default for the `xai` integration. Set a different default with `POST /credentials/{credential_id}/set-default`. See [managing credentials](/integrations/managing-credentials). ## BYOK billing xAI is **bring-your-own-key**, which changes how usage is billed compared with [ModuleX-managed models](/integrations/llm-providers/modulexai). Token usage is billed by **xAI** against your own API key. ModuleX adds **no credit charge and no markup** for the model call itself. BYOK usage is recorded for analytics only — it does not consume ModuleX [credits](/billing/credits). ModuleX provisions the model and bills it in **credits** through the [usage gate](/billing/usage-gating), applying a system margin. Use this when you do not want to manage a provider account. What this means in practice: - **No ModuleX credit cost for tokens.** A Grok call made with your xAI key is not metered in credits. xAI invoices you directly at its own rates (the per-million-token prices in the [model table](#available-models)). - **Other ModuleX usage still applies.** Running a workflow or a turn through the Assistant can still consume the flat per-run credit and may pass through the billing gate even when the model itself is BYOK. See [credits & metering](/billing/credits) and [usage gating & limits](/billing/usage-gating). - **Rate limits and quotas are xAI's.** Token-rate limits, monthly spend caps, and model availability for a BYOK key are governed by your xAI account, not by ModuleX. BYOK is available on all plans — there is no separate ModuleX entitlement that gates connecting your own xAI key. The pricing matrix on the marketing site lists BYOK for every tier; the only ModuleX-side billing that applies to a BYOK call is the non-model usage described above. **TBD:** confirm with the billing owner that no per-call ModuleX surcharge applies to BYOK model calls before treating "no markup" as a guarantee. ## Errors Catalog and credential operations return ModuleX's standard error shapes. See [errors & status codes](/api-reference/errors) for the full taxonomy. | Status | When it happens | Shape | | --- | --- | --- | | `400` | Invalid `auth_data`/`auth_type` on credential create, or asking a typed endpoint for the wrong integration type (for example `xai` is not a tool). | `{detail: string}` | | `401` | Missing or invalid `Authorization` / missing `X-Organization-ID`. | `{detail: string}` | | `403` | Caller is not an owner/admin in the organization. | `{detail: string}` | | `404` | Unknown provider, for example `GET /integrations/llm-providers/` returns `LLM provider not found`. | `{detail: string}` | | `422` | Query/parameter validation error. | `{detail: [ ... ]}` | | `500` | Unhandled server error. | `{detail: string}` | A bad xAI key does not surface as a ModuleX `401`. Connection validation happens against xAI: `POST /credentials/test-temporary` (or saving with validation) returns `is_valid: false` with the status xAI returned (for example `401` from `api.x.ai`). The ModuleX request itself still succeeds. ## Related All providers, and how managed vs BYOK selection works. The credit-billed managed alternative to BYOK. How integrations authenticate and the auth schema variants. Create, default, rotate, and scope credentials. --- # Managing credentials URL: https://docs.modulex.dev/integrations/managing-credentials Create, connect, test, rotate, and revoke ModuleX integration credentials in the app and via the API. Credentials are organization-scoped, encrypted at rest, and require an owner or admin role. 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](/integrations/authentication) and [Credentials & OAuth2](/concepts/credentials-oauth). For request lifecycle and base URLs, see the [API overview](/api-reference/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](/security/org-context). ## 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](/security/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](/api-reference/errors) and [Usage gating & limits](/billing/usage-gating). ## 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](/api-reference/authentication). ```bash cURL curl -s https://api.modulex.dev/credentials \ -H "Authorization: Bearer mx_live_your_api_key" \ -H "X-Organization-ID: 9a1f0c7e-2b44-4f1a-9c3d-7e5b2a1d8f60" ``` ```python Python from modulex import Modulex # Reads MODULEX_API_KEY / MODULEX_ORGANIZATION_ID from the environment, # or pass them explicitly: async with Modulex( api_key="mx_live_your_api_key", organization_id="9a1f0c7e-2b44-4f1a-9c3d-7e5b2a1d8f60", ) as client: grouped = await client.credentials.list() ``` ```javascript JavaScript import { Modulex } from "modulex-js"; const client = new Modulex({ apiKey: "mx_live_your_api_key", organizationId: "9a1f0c7e-2b44-4f1a-9c3d-7e5b2a1d8f60", }); const grouped = await client.credentials.list(); ``` ## Create and connect a credential ### In the app 1. Open **Settings → Credentials** (or **Browse** to start from the integration catalog), then choose the integration you want to connect. 2. Pick the auth type the integration supports — `oauth2`, `api_key`, `bearer_token`, `modulex_key`, or a `custom` schema with fields from the integration's manifest. 3. For API key, bearer, and custom types, fill in the secret fields and select **Test** to validate before saving (see [Test a credential](#test-a-credential)). For OAuth2 you are redirected to the provider's consent screen, then back to ModuleX. 4. Optionally set a **display name** and toggle **Make default** so this credential is used automatically for the integration. 5. 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](/integrations/authentication). The secret payload, encrypted at rest. The presence of specific keys selects the credential type: - `auth_data.api_key` present → `api_key` credential. - `auth_data.token` or `auth_data.bearer_token` present → `bearer_token` credential. - `auth_data.access_token` present **and** a top-level `oauth_config` present → `oauth2` credential. - For `auth_type: "custom"`, `auth_data` holds the custom fields declared by the integration's manifest (must be non-empty). - If none of the above match, the request fails with `400` (`Invalid auth_data or auth_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](/integrations/authentication) 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). ```bash cURL curl -X POST https://api.modulex.dev/credentials \ -H "Authorization: Bearer mx_live_your_api_key" \ -H "X-Organization-ID: 9a1f0c7e-2b44-4f1a-9c3d-7e5b2a1d8f60" \ -H "Content-Type: application/json" \ -d '{ "integration_name": "slack", "display_name": "Production Slack", "make_default": true, "auth_data": { "api_key": "xoxb-your-slack-token" } }' ``` ```python Python from modulex import Modulex async with Modulex() as client: credential = await client.credentials.create( "slack", auth_data={"api_key": "xoxb-your-slack-token"}, display_name="Production Slack", make_default=True, ) print(credential.credential_id) ``` ```javascript JavaScript import { Modulex } from "modulex-js"; const client = new Modulex(); const credential = await client.credentials.create({ integration_name: "slack", auth_data: { api_key: "xoxb-your-slack-token" }, display_name: "Production Slack", make_default: true, }); console.log(credential.credential_id); ``` 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. **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. 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`. ```bash cURL curl -s "https://api.modulex.dev/credentials?auth_type=oauth2&limit=50" \ -H "Authorization: Bearer mx_live_your_api_key" \ -H "X-Organization-ID: 9a1f0c7e-2b44-4f1a-9c3d-7e5b2a1d8f60" ``` ```python Python from modulex import Modulex async with Modulex() as client: # Grouped by integration grouped = await client.credentials.list() # Flat list for one integration slack_creds = await client.credentials.list(integration_name="slack") ``` ```javascript JavaScript import { Modulex } from "modulex-js"; const client = new Modulex(); // Grouped by integration const grouped = await client.credentials.list(); // Flat list for one integration const slackCreds = await client.credentials.list({ integrationName: "slack" }); ``` 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: ```json Grouped response { "integrations": { "github": { "integration_name": "github", "integration_type": "tool", "logo": "https://.../github.svg", "credentials": [ { "credential_id": "b3f1c2d4-aa11-4e55-8c90-12ab34cd56ef", "integration_name": "github", "integration_type": "tool", "display_name": "Production GitHub", "auth_type": "oauth2", "is_default": true, "created_at": "2026-01-19T12:00:00", "updated_at": "2026-01-19T12:00:00", "last_used_at": null, "expires_at": "2026-02-19T12:00:00", "credentials_metadata": null } ], "total_count": 1, "auth_types": ["oauth2"] } }, "total_credentials": 1, "total_integrations": 1, "filters": { "auth_type": null } } ``` 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. ```bash cURL curl -s "https://api.modulex.dev/credentials/b3f1c2d4-aa11-4e55-8c90-12ab34cd56ef" \ -H "Authorization: Bearer mx_live_your_api_key" \ -H "X-Organization-ID: 9a1f0c7e-2b44-4f1a-9c3d-7e5b2a1d8f60" ``` ```python Python from modulex import Modulex async with Modulex() as client: detail = await client.credentials.get( "b3f1c2d4-aa11-4e55-8c90-12ab34cd56ef", include_masked=True, ) ``` ```javascript JavaScript import { Modulex } from "modulex-js"; const client = new Modulex(); const detail = await client.credentials.get( "b3f1c2d4-aa11-4e55-8c90-12ab34cd56ef", { includeMasked: true }, ); ``` 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. ```bash cURL # Validate before saving curl -X POST https://api.modulex.dev/credentials/test-temporary \ -H "Authorization: Bearer mx_live_your_api_key" \ -H "X-Organization-ID: 9a1f0c7e-2b44-4f1a-9c3d-7e5b2a1d8f60" \ -H "Content-Type: application/json" \ -d '{ "integration_name": "tavily", "auth_type": "api_key", "auth_data": { "api_key": "tvly-your-key" } }' # Test an existing credential curl -X POST "https://api.modulex.dev/credentials/b3f1c2d4-aa11-4e55-8c90-12ab34cd56ef/test" \ -H "Authorization: Bearer mx_live_your_api_key" \ -H "X-Organization-ID: 9a1f0c7e-2b44-4f1a-9c3d-7e5b2a1d8f60" ``` ```python Python from modulex import Modulex async with Modulex() as client: # Validate before saving temp = await client.credentials.test_temporary( integration_name="tavily", auth_type="api_key", auth_data={"api_key": "tvly-your-key"}, ) # Test an existing credential result = await client.credentials.test("b3f1c2d4-aa11-4e55-8c90-12ab34cd56ef") print(result.is_valid) ``` ```javascript JavaScript import { Modulex } from "modulex-js"; const client = new Modulex(); // Validate before saving const temp = await client.credentials.testTemporary({ integration_name: "tavily", auth_type: "api_key", auth_data: { api_key: "tvly-your-key" }, }); // Test an existing credential const result = await client.credentials.test("b3f1c2d4-aa11-4e55-8c90-12ab34cd56ef"); console.log(result.is_valid); ``` ```json test-temporary response { "is_valid": true, "message": "Credential is valid for tavily (test cost: minimal)", "tested_at": "2026-01-19T12:00:00", "test_method": "api_call", "integration_name": "tavily", "auth_type": "api_key", "test_endpoint": "https://api.tavily.com/search", "status_code": 200, "cost_level": "minimal" } ``` Whether the credential passed validation. Human-readable result detail. How validation ran: `api_call`, `basic`, or `none`. 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. ```bash cURL curl -X POST "https://api.modulex.dev/credentials/b3f1c2d4-aa11-4e55-8c90-12ab34cd56ef/set-default" \ -H "Authorization: Bearer mx_live_your_api_key" \ -H "X-Organization-ID: 9a1f0c7e-2b44-4f1a-9c3d-7e5b2a1d8f60" ``` ```python Python from modulex import Modulex async with Modulex() as client: updated = await client.credentials.set_default( "b3f1c2d4-aa11-4e55-8c90-12ab34cd56ef", ) ``` ```javascript JavaScript import { Modulex } from "modulex-js"; const client = new Modulex(); const updated = await client.credentials.setDefault( "b3f1c2d4-aa11-4e55-8c90-12ab34cd56ef", ); ``` 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. 1. **Create** a new credential for the same integration with the new secret (`POST /credentials`). 2. **Promote** it with `make_default: true` on create, or `POST /credentials/{new_id}/set-default` afterward, so resolution prefers it immediately. 3. **Revoke** the old credential with `DELETE /credentials/{old_id}` once the new one is verified. ```bash cURL # 1. Create the replacement as the new default NEW_ID=$(curl -s -X POST https://api.modulex.dev/credentials \ -H "Authorization: Bearer mx_live_your_api_key" \ -H "X-Organization-ID: 9a1f0c7e-2b44-4f1a-9c3d-7e5b2a1d8f60" \ -H "Content-Type: application/json" \ -d '{"integration_name":"slack","display_name":"Production Slack (rotated)","make_default":true,"auth_data":{"api_key":"xoxb-new-token"}}' \ | python3 -c "import sys,json;print(json.load(sys.stdin)['credential_id'])") # 2. (Optional) verify the new credential curl -X POST "https://api.modulex.dev/credentials/$NEW_ID/test" \ -H "Authorization: Bearer mx_live_your_api_key" \ -H "X-Organization-ID: 9a1f0c7e-2b44-4f1a-9c3d-7e5b2a1d8f60" # 3. Revoke the old credential curl -X DELETE "https://api.modulex.dev/credentials/OLD_CREDENTIAL_ID" \ -H "Authorization: Bearer mx_live_your_api_key" \ -H "X-Organization-ID: 9a1f0c7e-2b44-4f1a-9c3d-7e5b2a1d8f60" ``` ```python Python from modulex import Modulex async with Modulex() as client: # 1. Create the replacement as the new default new = await client.credentials.create( "slack", auth_data={"api_key": "xoxb-new-token"}, display_name="Production Slack (rotated)", make_default=True, ) # 2. Verify it check = await client.credentials.test(new.credential_id) # 3. Revoke the old credential once verified if check.is_valid: await client.credentials.delete("OLD_CREDENTIAL_ID") ``` ```javascript JavaScript import { Modulex } from "modulex-js"; const client = new Modulex(); // 1. Create the replacement as the new default const fresh = await client.credentials.create({ integration_name: "slack", auth_data: { api_key: "xoxb-new-token" }, display_name: "Production Slack (rotated)", make_default: true, }); // 2. Verify it const check = await client.credentials.test(fresh.credential_id); // 3. Revoke the old credential once verified if (check.is_valid) { await client.credentials.delete("OLD_CREDENTIAL_ID"); } ``` 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](/reference/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**. ```bash cURL curl -X DELETE "https://api.modulex.dev/credentials/b3f1c2d4-aa11-4e55-8c90-12ab34cd56ef" \ -H "Authorization: Bearer mx_live_your_api_key" \ -H "X-Organization-ID: 9a1f0c7e-2b44-4f1a-9c3d-7e5b2a1d8f60" ``` ```python Python from modulex import Modulex async with Modulex() as client: await client.credentials.delete("b3f1c2d4-aa11-4e55-8c90-12ab34cd56ef") ``` ```javascript JavaScript import { Modulex } from "modulex-js"; const client = new Modulex(); await client.credentials.delete("b3f1c2d4-aa11-4e55-8c90-12ab34cd56ef"); ``` 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](/security/data-encryption). - **Owner/admin only.** Because credentials are organization-wide, only owners and admins can create, change, or revoke them. See [Roles & permissions](/security/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](#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`. ```bash cURL curl -s "https://api.modulex.dev/credentials/b3f1c2d4-aa11-4e55-8c90-12ab34cd56ef/audit?limit=50" \ -H "Authorization: Bearer mx_live_your_api_key" \ -H "X-Organization-ID: 9a1f0c7e-2b44-4f1a-9c3d-7e5b2a1d8f60" ``` ```python Python from modulex import Modulex async with Modulex() as client: logs = await client.credentials.audit( "b3f1c2d4-aa11-4e55-8c90-12ab34cd56ef", limit=50, ) ``` ```javascript JavaScript import { Modulex } from "modulex-js"; const client = new Modulex(); const logs = await client.credentials.audit( "b3f1c2d4-aa11-4e55-8c90-12ab34cd56ef", { limit: 50 }, ); ``` 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](/reference/known-limitations). ## Errors Credential endpoints return the standard `{"detail": }` 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) | 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-reference/errors). ## Related pages Request lifecycle, base URLs, and how every operation is shown three ways. The auth-type variants and the OAuth2 (PKCE) connect flow. How ModuleX stores and resolves credentials, conceptually. Which actions require an owner or admin role. --- # Integrations: connect your tools to ModuleX URL: https://docs.modulex.dev/integrations/overview Connect ModuleX to 175 services and call their tools from your workflows and the Assistant. Learn what integrations are, browse the catalog, and connect credentials with OAuth or your own keys. An integration is how ModuleX talks to a service you already use — GitHub, Slack, Gmail, Notion, Salesforce, and many more. Once a service is connected, ModuleX can run its actions for you: open an issue, send a message, read a row, create a contact. You connect the service once, and every workflow and the [Assistant](/assistant/overview) can use it from then on. **🎬 MEDIA PLACEHOLDER · MX-MEDIA-4000 · [SCREENSHOT]** **[SCREENSHOT_DESCRIPTION]:** The ModuleX integration catalog: a grid of integration cards with logos, names, and a category filter. **[SCREENSHOT_DETAILS]:** Capture the integrations catalog page in the ModuleX app. Show a grid of logo cards (for example GitHub, Slack, Gmail, Notion, Salesforce, HubSpot), the search box, and the category filter rail. 16:9, light theme, no personal data visible. Highlight the search and category controls. ## Integrations and tools, in plain terms Two words come up everywhere in ModuleX. They are easy to keep straight. A connection to one external service — for example **GitHub** or **Slack**. It bundles the service's logo, its actions, and how to authenticate to it. One callable action a connected integration exposes — for example "create an issue" or "send a message." A single integration usually offers many tools. So you connect an **integration** once, and that unlocks all of its **tools**. ModuleX ships **175 integrations** today, and together they expose 600+ tool actions you can call. ModuleX has **175 integrations** as of this writing. You may see other counts in older material — this is the current, verified figure. ## Where you use integration tools A connected tool is available in two places, and you do not have to choose up front. Drop a [tool node](/workflow-builder/nodes/tool) onto the canvas, pick the integration and the action, and the workflow calls it on every run. This is the reusable, repeatable path. Ask the [Assistant](/assistant/overview) in plain language. It finds the right tool among your connected integrations, calls it for you, and pauses for approval when an action is sensitive. The catalog is shared. A service you connect for the Assistant is the same connection a workflow uses, and the credentials you store work for both. ## Browse the catalog The catalog is the home for every integration ModuleX can connect to. You can search by name and filter by category to find what you need. Browse all 175 integrations by category, see each one's logo and description, and find the tools it offers. Categories in the catalog are descriptive labels (such as productivity, developer tools, marketing, and CRM), and a single integration can appear under more than one. Use the search box if you already know the service you want. ## Connect a service: credentials and BYOK Before ModuleX can act on your behalf, you tell it how to authenticate to the service. ModuleX stores that connection as an encrypted **credential** scoped to your [organization](/concepts/organizations-roles), so your whole team can reuse it without re-entering secrets. There are two ways a service authenticates, depending on what it supports. For services that support it, you click **Connect**, sign in on the provider's own page, and approve access. ModuleX never sees your password — it receives a scoped token. Many connectors use this flow, with PKCE where the provider supports it. For services that use API keys, you paste a key (or other fields the service needs) into ModuleX. The same idea applies to model and knowledge providers: connect your own provider account and your usage is billed directly by that provider, with no ModuleX markup. Under the hood, an integration declares one or more **auth schemas**, and ModuleX shows you the right form for each. There are six schema variants in total — OAuth2, bearer token, API key, a ModuleX-managed key, custom, and internal — but as a user you simply see the connect button or the fields a service asks for. See the full picture: OAuth flows, API keys, BYOK, and how ModuleX stores and resolves your credentials. ModuleX-managed model and knowledge usage is metered in [credits](/concepts/credits-billing). BYOK usage is billed by the provider you connected, not by ModuleX. See [credits and the billing model](/concepts/credits-billing) for the details. ## Use a tool in a workflow Connecting a service and using one of its tools takes a few steps. Here is the path from zero to a running tool call. Open the [catalog](/integrations/catalog), search for the service you want, and open it to see the tools it offers. Click **Connect** and either approve the OAuth flow or paste your API key. ModuleX saves the credential to your organization so it is reusable. See [authentication and credentials](/integrations/authentication). In the [workflow builder](/workflow-builder/overview), add a [tool node](/workflow-builder/nodes/tool), then pick the integration and the specific action you want to run. Fill in the action's parameters, pulling values from earlier nodes with references such as `{{node_id.field}}` where you need them. Run the workflow. The tool node calls the service, and its result is written into run state for the next node to use. Learn how the tool node selects an action, maps parameters, and returns results inside a workflow. ## Call a tool from your own code You can also list the catalog programmatically — for example to discover which integrations exist before wiring up a run. Every request authenticates with `Authorization: Bearer mx_live_…` plus your `X-Organization-ID` header. See [authentication](/api-reference/authentication). ```bash cURL curl https://api.modulex.dev/integrations/tools \ -H "Authorization: Bearer mx_live_your_api_key" \ -H "X-Organization-ID: org_your_organization_id" ``` ```python Python import httpx resp = httpx.get( "https://api.modulex.dev/integrations/tools", headers={ "Authorization": "Bearer mx_live_your_api_key", "X-Organization-ID": "org_your_organization_id", }, ) for integration in resp.json(): print(integration["name"], "-", integration["display_name"]) ``` ```javascript JavaScript const resp = await fetch("https://api.modulex.dev/integrations/tools", { headers: { Authorization: "Bearer mx_live_your_api_key", "X-Organization-ID": "org_your_organization_id", }, }); const integrations = await resp.json(); for (const integration of integrations) { console.log(integration.name, "-", integration.display_name); } ``` The catalog endpoint returns a list of integrations, each with its `name`, `display_name`, `description`, and categories: ```json Response [ { "name": "github", "display_name": "GitHub", "description": "Manage repositories, issues, and pull requests.", "logo": "modulex:github", "categories": ["Developer Tools & Infrastructure"], "integration_type": "tool", "version": "1.0.0" } ] ``` Listing the catalog requires the **owner** or **admin** role in the organization you pass in `X-Organization-ID`. The retired `member` role is not a current role — see [roles and permissions](/security/roles-permissions). ## A note on installing the package If you are running ModuleX yourself, the integrations ship in a Python package that the runtime loads at startup. Installing it pulls in the core dependencies only. ModuleX does **not** currently provide working install extras such as `[all]` or per-tool extras like `[github,slack]`. Those groups are not populated yet, so asking for them only emits a pip warning and installs the core package. Install the base package, then add any extra SDKs a specific tool needs by hand. See [installing and using integrations](/integrations/install) for the current, accurate steps. ## Where to go next Browse all 175 integrations by category and find the tools you need. Connect a service with OAuth or your own API key, and learn how ModuleX stores credentials. Call a connected integration's action from inside a workflow. A step-by-step walkthrough: connect a service and call its tool from a workflow. --- # AgentMail Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/agentmail Integrate AgentMail into your workflow. Create and manage email inboxes, send and receive messages, reply to threads, manage drafts, and organize threads with labels. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/agentmail/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} AgentMail logo ## Overview Add **AgentMail** to any ModuleX agent or workflow. API-first email for agents and automation: create inboxes, send and receive messages, reply to and forward threads, manage drafts, and organize conversations with labels — over the AgentMail REST API (`api.agentmail.to`). **Categories**: Communication · Email · Automation · **Auth**: API Key · **Actions**: 21 ## Authentication ### API Key Authentication Authenticate using your AgentMail API key Sign up or log in at https://agentmail.to Open the dashboard and navigate to the API Keys section Create a new API key or copy your existing one Paste the API key below #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | AgentMail API Key | Your AgentMail API key | Yes | `-` | ## Available Actions ### Parameters ID of the inbox to send from Recipient email address (comma-separated for multiple) Email subject line Plain text email body HTML email body CC recipient email addresses (comma-separated) BCC recipient email addresses (comma-separated) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "thread_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Thread Id" }, "message_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message Id" }, "subject": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Subject" }, "to": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "To" } }, "required": [ "success" ], "title": "SendMessageOutput", "type": "object" } ``` ### Parameters ID of the inbox to reply from ID of the message to reply to Plain text reply body HTML reply body Override recipient email addresses (comma-separated) CC email addresses (comma-separated) BCC email addresses (comma-separated) Reply to all recipients of the original message (Default: `false`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "message_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message Id" }, "thread_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Thread Id" } }, "required": [ "success" ], "title": "ReplyMessageOutput", "type": "object" } ``` ### Parameters ID of the inbox containing the message ID of the message to forward Recipient email addresses (comma-separated) Override subject line Additional plain text to prepend Additional HTML to prepend CC recipient email addresses (comma-separated) BCC recipient email addresses (comma-separated) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "message_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message Id" }, "thread_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Thread Id" } }, "required": [ "success" ], "title": "ForwardMessageOutput", "type": "object" } ``` ### Parameters ID of the inbox to list messages from Maximum number of results to return Pagination token for the next page of results ### Response ```json { "$defs": { "MessageSummary": { "additionalProperties": false, "description": "A single message row in ``list_messages``.", "properties": { "message_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message Id" }, "from_": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "From" }, "to": { "items": { "type": "string" }, "title": "To", "type": "array" }, "subject": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Subject" }, "preview": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Preview" }, "timestamp": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Timestamp" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" } }, "title": "MessageSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "messages": { "items": { "$ref": "#/$defs/MessageSummary" }, "title": "Messages", "type": "array" }, "count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Count" }, "next_page_token": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Next Page Token" } }, "required": [ "success" ], "title": "ListMessagesOutput", "type": "object" } ``` ### Parameters ID of the inbox containing the message ID of the message to retrieve ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "message_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message Id" }, "thread_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Thread Id" }, "from_": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "From" }, "to": { "items": { "type": "string" }, "title": "To", "type": "array" }, "cc": { "items": { "type": "string" }, "title": "Cc", "type": "array" }, "bcc": { "items": { "type": "string" }, "title": "Bcc", "type": "array" }, "subject": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Subject" }, "text": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Text" }, "html": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Html" }, "labels": { "items": { "type": "string" }, "title": "Labels", "type": "array" }, "timestamp": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Timestamp" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" } }, "required": [ "success" ], "title": "GetMessageOutput", "type": "object" } ``` ### Parameters ID of the inbox containing the message ID of the message to update Comma-separated labels to add to the message Comma-separated labels to remove from the message ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "message_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message Id" }, "labels": { "items": { "type": "string" }, "title": "Labels", "type": "array" } }, "required": [ "success" ], "title": "UpdateMessageOutput", "type": "object" } ``` ### Parameters ID of the inbox to list threads from Maximum number of results to return Pagination token for the next page of results Comma-separated labels to filter threads by Filter threads before this ISO 8601 timestamp Filter threads after this ISO 8601 timestamp ### Response ```json { "$defs": { "ThreadSummary": { "additionalProperties": false, "description": "A single thread row in ``list_threads``.", "properties": { "thread_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Thread Id" }, "subject": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Subject" }, "senders": { "items": { "type": "string" }, "title": "Senders", "type": "array" }, "recipients": { "items": { "type": "string" }, "title": "Recipients", "type": "array" }, "message_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Message Count" }, "last_message_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Message At" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "updated_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated At" } }, "title": "ThreadSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "threads": { "items": { "$ref": "#/$defs/ThreadSummary" }, "title": "Threads", "type": "array" }, "count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Count" }, "next_page_token": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Next Page Token" } }, "required": [ "success" ], "title": "ListThreadsOutput", "type": "object" } ``` ### Parameters ID of the inbox containing the thread ID of the thread to retrieve ### Response ```json { "$defs": { "ThreadMessage": { "additionalProperties": false, "description": "A single message inside ``get_thread``.", "properties": { "message_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message Id" }, "from_": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "From" }, "to": { "items": { "type": "string" }, "title": "To", "type": "array" }, "cc": { "items": { "type": "string" }, "title": "Cc", "type": "array" }, "bcc": { "items": { "type": "string" }, "title": "Bcc", "type": "array" }, "subject": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Subject" }, "text": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Text" }, "html": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Html" }, "labels": { "items": { "type": "string" }, "title": "Labels", "type": "array" }, "timestamp": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Timestamp" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" } }, "title": "ThreadMessage", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "thread_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Thread Id" }, "subject": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Subject" }, "senders": { "items": { "type": "string" }, "title": "Senders", "type": "array" }, "recipients": { "items": { "type": "string" }, "title": "Recipients", "type": "array" }, "message_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Message Count" }, "labels": { "items": { "type": "string" }, "title": "Labels", "type": "array" }, "last_message_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Message At" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "updated_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated At" }, "messages": { "items": { "$ref": "#/$defs/ThreadMessage" }, "title": "Messages", "type": "array" } }, "required": [ "success" ], "title": "GetThreadOutput", "type": "object" } ``` ### Parameters ID of the inbox containing the thread ID of the thread to update Comma-separated labels to add to the thread Comma-separated labels to remove from the thread ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "thread_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Thread Id" }, "labels": { "items": { "type": "string" }, "title": "Labels", "type": "array" } }, "required": [ "success" ], "title": "UpdateThreadOutput", "type": "object" } ``` ### Parameters ID of the inbox containing the thread ID of the thread to delete Force permanent deletion instead of moving to trash (Default: `false`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "deleted": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Deleted" } }, "required": [ "success" ], "title": "DeleteThreadOutput", "type": "object" } ``` ### Parameters ID of the inbox to create the draft in Recipient email addresses (comma-separated) Draft subject line Plain text draft body HTML draft body CC recipient email addresses (comma-separated) BCC recipient email addresses (comma-separated) ID of message being replied to ISO 8601 timestamp to schedule sending ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "draft_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Draft Id" }, "inbox_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Inbox Id" }, "subject": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Subject" }, "to": { "items": { "type": "string" }, "title": "To", "type": "array" }, "cc": { "items": { "type": "string" }, "title": "Cc", "type": "array" }, "bcc": { "items": { "type": "string" }, "title": "Bcc", "type": "array" }, "text": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Text" }, "html": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Html" }, "preview": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Preview" }, "labels": { "items": { "type": "string" }, "title": "Labels", "type": "array" }, "in_reply_to": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "In Reply To" }, "send_status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Send Status" }, "send_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Send At" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "updated_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated At" } }, "required": [ "success" ], "title": "CreateDraftOutput", "type": "object" } ``` ### Parameters ID of the inbox to list drafts from Maximum number of results to return Pagination token for the next page of results ### Response ```json { "$defs": { "DraftSummary": { "additionalProperties": false, "description": "A single draft row in ``list_drafts``.", "properties": { "draft_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Draft Id" }, "inbox_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Inbox Id" }, "subject": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Subject" }, "to": { "items": { "type": "string" }, "title": "To", "type": "array" }, "cc": { "items": { "type": "string" }, "title": "Cc", "type": "array" }, "bcc": { "items": { "type": "string" }, "title": "Bcc", "type": "array" }, "preview": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Preview" }, "send_status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Send Status" }, "send_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Send At" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "updated_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated At" } }, "title": "DraftSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "drafts": { "items": { "$ref": "#/$defs/DraftSummary" }, "title": "Drafts", "type": "array" }, "count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Count" }, "next_page_token": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Next Page Token" } }, "required": [ "success" ], "title": "ListDraftsOutput", "type": "object" } ``` ### Parameters ID of the inbox the draft belongs to ID of the draft to retrieve ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "draft_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Draft Id" }, "inbox_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Inbox Id" }, "subject": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Subject" }, "to": { "items": { "type": "string" }, "title": "To", "type": "array" }, "cc": { "items": { "type": "string" }, "title": "Cc", "type": "array" }, "bcc": { "items": { "type": "string" }, "title": "Bcc", "type": "array" }, "text": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Text" }, "html": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Html" }, "preview": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Preview" }, "labels": { "items": { "type": "string" }, "title": "Labels", "type": "array" }, "in_reply_to": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "In Reply To" }, "send_status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Send Status" }, "send_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Send At" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "updated_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated At" } }, "required": [ "success" ], "title": "GetDraftOutput", "type": "object" } ``` ### Parameters ID of the inbox containing the draft ID of the draft to update Recipient email addresses (comma-separated) Draft subject line Plain text draft body HTML draft body CC recipient email addresses (comma-separated) BCC recipient email addresses (comma-separated) ISO 8601 timestamp to schedule sending ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "draft_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Draft Id" }, "inbox_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Inbox Id" }, "subject": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Subject" }, "to": { "items": { "type": "string" }, "title": "To", "type": "array" }, "cc": { "items": { "type": "string" }, "title": "Cc", "type": "array" }, "bcc": { "items": { "type": "string" }, "title": "Bcc", "type": "array" }, "text": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Text" }, "html": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Html" }, "preview": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Preview" }, "labels": { "items": { "type": "string" }, "title": "Labels", "type": "array" }, "in_reply_to": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "In Reply To" }, "send_status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Send Status" }, "send_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Send At" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "updated_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated At" } }, "required": [ "success" ], "title": "UpdateDraftOutput", "type": "object" } ``` ### Parameters ID of the inbox containing the draft ID of the draft to delete ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "deleted": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Deleted" } }, "required": [ "success" ], "title": "DeleteDraftOutput", "type": "object" } ``` ### Parameters ID of the inbox containing the draft ID of the draft to send ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "message_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message Id" }, "thread_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Thread Id" } }, "required": [ "success" ], "title": "SendDraftOutput", "type": "object" } ``` ### Parameters Username for the inbox email address Domain for the inbox email address Display name for the inbox ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "inbox_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Inbox Id" }, "email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email" }, "display_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Display Name" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "updated_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated At" } }, "required": [ "success" ], "title": "CreateInboxOutput", "type": "object" } ``` ### Parameters Maximum number of results to return Pagination token for the next page of results ### Response ```json { "$defs": { "InboxSummary": { "additionalProperties": false, "description": "A single inbox row in ``list_inboxes``.", "properties": { "inbox_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Inbox Id" }, "email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email" }, "display_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Display Name" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "updated_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated At" } }, "title": "InboxSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "inboxes": { "items": { "$ref": "#/$defs/InboxSummary" }, "title": "Inboxes", "type": "array" }, "count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Count" }, "next_page_token": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Next Page Token" } }, "required": [ "success" ], "title": "ListInboxesOutput", "type": "object" } ``` ### Parameters ID of the inbox to retrieve ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "inbox_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Inbox Id" }, "email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email" }, "display_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Display Name" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "updated_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated At" } }, "required": [ "success" ], "title": "GetInboxOutput", "type": "object" } ``` ### Parameters ID of the inbox to update New display name for the inbox ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "inbox_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Inbox Id" }, "email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email" }, "display_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Display Name" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "updated_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated At" } }, "required": [ "success" ], "title": "UpdateInboxOutput", "type": "object" } ``` ### Parameters ID of the inbox to delete ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "deleted": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Deleted" } }, "required": [ "success" ], "title": "DeleteInboxOutput", "type": "object" } ``` ## Limits & Quotas - All calls go to `https://api.agentmail.to/v0` over HTTPS with a 30s client timeout. - List endpoints are paginated: pass `limit` and the returned `next_page_token` (via `page_token`) to page through results. - **Error model**: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. Plan for retries on the agent side based on the error string. ## Related integrations ## Links - [AgentMail](https://agentmail.to) --- # Ahrefs Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/ahrefs SEO toolset providing backlink analysis, referring domain data, and site explorer metrics via the Ahrefs REST API {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/ahrefs/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Ahrefs logo ## Overview Add **Ahrefs** to any ModuleX agent or workflow. SEO backlink analysis and referring domain data via the Ahrefs REST API (`api.ahrefs.com/v3`). **Categories**: Marketing & Advertising · SEO · Marketing · Web Search & Scraping · **Auth**: API Key · **Actions**: 3 ## Authentication ### API Key Authenticate with an Ahrefs API v3 key (Account settings -> API keys; requires an eligible paid plan, key valid 1 year) Sign in to Ahrefs as a workspace owner or admin Go to Account settings -> API keys Create a new API key and copy it (each key is valid for 1 year) Paste the key into ModuleX #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Ahrefs API Key | Ahrefs API v3 key, sent as Authorization: Bearer <key> | Yes | `-` | ## Available Actions ### Parameters Domain or URL to get backlinks for List of columns to return (e.g. url_from, url_to, ahrefs_rank, anchor, page_title) Mode of operation: exact, domain, subdomains, or prefix (Default: `domain`) Number of results to return (Default: `1000`) ### Response ```json { "$defs": { "BacklinkItem": { "additionalProperties": false, "description": "A single backlink record returned by the Ahrefs API.", "properties": { "url_from": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url From" }, "url_to": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url To" }, "ahrefs_rank": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Ahrefs Rank" }, "anchor": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Anchor" }, "page_title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Page Title" }, "first_seen": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "First Seen" }, "last_seen": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Seen" }, "domain_rating": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Domain Rating" }, "extra": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "description": "Additional fields returned by the API based on the select parameter", "title": "Extra" } }, "title": "BacklinkItem", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "backlinks": { "items": { "$ref": "#/$defs/BacklinkItem" }, "title": "Backlinks", "type": "array" }, "total": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Total" } }, "required": [ "success" ], "title": "GetBacklinksOutput", "type": "object" } ``` ### Parameters Domain or URL to get backlinks for List of columns to return (e.g. url_from, url_to, ahrefs_rank, anchor, page_title) Mode of operation: exact, domain, subdomains, or prefix (Default: `domain`) Number of results to return (Default: `1000`) ### Response ```json { "$defs": { "BacklinkItem": { "additionalProperties": false, "description": "A single backlink record returned by the Ahrefs API.", "properties": { "url_from": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url From" }, "url_to": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url To" }, "ahrefs_rank": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Ahrefs Rank" }, "anchor": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Anchor" }, "page_title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Page Title" }, "first_seen": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "First Seen" }, "last_seen": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Seen" }, "domain_rating": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Domain Rating" }, "extra": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "description": "Additional fields returned by the API based on the select parameter", "title": "Extra" } }, "title": "BacklinkItem", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "backlinks": { "items": { "$ref": "#/$defs/BacklinkItem" }, "title": "Backlinks", "type": "array" }, "total": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Total" } }, "required": [ "success" ], "title": "GetBacklinksOnePerDomainOutput", "type": "object" } ``` ### Parameters Domain or URL to get referring domains for List of columns to return for referring domains (e.g. domain, domain_rating, backlinks) Mode of operation: exact, domain, subdomains, or prefix (Default: `domain`) Number of results to return (Default: `1000`) ### Response ```json { "$defs": { "ReferringDomainItem": { "additionalProperties": false, "description": "A single referring domain record returned by the Ahrefs API.", "properties": { "domain": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Domain" }, "domain_rating": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Domain Rating" }, "backlinks": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Backlinks" }, "first_seen": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "First Seen" }, "last_seen": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Seen" }, "extra": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "description": "Additional fields returned by the API based on the select parameter", "title": "Extra" } }, "title": "ReferringDomainItem", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "refdomains": { "items": { "$ref": "#/$defs/ReferringDomainItem" }, "title": "Refdomains", "type": "array" }, "total": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Total" } }, "required": [ "success" ], "title": "GetReferringDomainsOutput", "type": "object" } ``` ## Limits & Quotas - Rate limits depend on your Ahrefs subscription plan (Lite, Standard, Advanced, Enterprise). - API usage is metered against monthly "API rows" quota included in your plan. - Error model: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. ## Related integrations ## Links - [Ahrefs](https://ahrefs.com) --- # Airtable Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/airtable Cloud-based database platform for organizing, storing, and collaborating on structured data with spreadsheet-like interface and powerful automation capabilities. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/airtable/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Airtable logo ## Overview Add **Airtable** to any ModuleX agent or workflow. CRUD against the Airtable REST API (`api.airtable.com/v0`): base + table discovery via `/meta/`, record list/get/create/update/delete via `//`. **Categories**: Project & Task Management · Project Management · Workflow · Automation · **Auth**: Personal Access Token · **Actions**: 7 ## Authentication ### Personal Access Token Authenticate using your Airtable Personal Access Token. Create one at airtable.com/create/tokens #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Airtable Personal Access Token | Your Airtable Personal Access Token for API authentication | Yes | `patXXXXXXXXXXXXXX.XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX` | ## Available Actions ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "bases": { "items": { "additionalProperties": true, "type": "object" }, "title": "Bases", "type": "array" }, "count": { "default": 0, "title": "Count", "type": "integer" } }, "required": [ "success" ], "title": "ListBasesOutput", "type": "object" } ``` ### Parameters The ID of the Airtable base (starts with 'app') ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "tables": { "items": { "additionalProperties": true, "type": "object" }, "title": "Tables", "type": "array" }, "count": { "default": 0, "title": "Count", "type": "integer" }, "base_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Base Id" } }, "required": [ "success" ], "title": "ListTablesOutput", "type": "object" } ``` ### Parameters The ID of the Airtable base Name or ID of the table Maximum number of records to return (1-100) (Default: `100`) Airtable formula to filter records Field name to sort by Sort direction ('asc' or 'desc') (Default: `asc`) Name or ID of a view to use for filtering/sorting ### Response ```json { "$defs": { "AirtableRecord": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "fields": { "additionalProperties": true, "title": "Fields", "type": "object" }, "createdTime": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Createdtime" } }, "title": "AirtableRecord", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "records": { "items": { "$ref": "#/$defs/AirtableRecord" }, "title": "Records", "type": "array" }, "count": { "default": 0, "title": "Count", "type": "integer" }, "table": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Table" }, "base_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Base Id" } }, "required": [ "success" ], "title": "ListRecordsOutput", "type": "object" } ``` ### Parameters The ID of the Airtable base Name or ID of the table The ID of the record (starts with 'rec') ### Response ```json { "$defs": { "AirtableRecord": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "fields": { "additionalProperties": true, "title": "Fields", "type": "object" }, "createdTime": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Createdtime" } }, "title": "AirtableRecord", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "record": { "anyOf": [ { "$ref": "#/$defs/AirtableRecord" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "GetRecordOutput", "type": "object" } ``` ### Parameters The ID of the Airtable base Name or ID of the table List of {field: value} record objects If true, Airtable converts string values to the appropriate cell types (Default: `false`) ### Response ```json { "$defs": { "AirtableRecord": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "fields": { "additionalProperties": true, "title": "Fields", "type": "object" }, "createdTime": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Createdtime" } }, "title": "AirtableRecord", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "records": { "items": { "$ref": "#/$defs/AirtableRecord" }, "title": "Records", "type": "array" }, "count": { "default": 0, "title": "Count", "type": "integer" }, "table": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Table" }, "base_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Base Id" } }, "required": [ "success" ], "title": "CreateRecordsOutput", "type": "object" } ``` ### Parameters The ID of the Airtable base Name or ID of the table Records to update; each is {'id': 'rec…', 'fields': {…}} or {'id': 'rec…', 'field_a': value, …} If true, Airtable converts string values to the appropriate cell types (Default: `false`) ### Response ```json { "$defs": { "AirtableRecord": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "fields": { "additionalProperties": true, "title": "Fields", "type": "object" }, "createdTime": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Createdtime" } }, "title": "AirtableRecord", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "records": { "items": { "$ref": "#/$defs/AirtableRecord" }, "title": "Records", "type": "array" }, "count": { "default": 0, "title": "Count", "type": "integer" }, "table": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Table" }, "base_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Base Id" }, "updated_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Updated Count" } }, "required": [ "success" ], "title": "UpdateRecordsOutput", "type": "object" } ``` ### Parameters The ID of the Airtable base Name or ID of the table List of record IDs to delete (each starts with 'rec') ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "deleted_ids": { "items": { "type": "string" }, "title": "Deleted Ids", "type": "array" }, "count": { "default": 0, "title": "Count", "type": "integer" }, "table": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Table" }, "base_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Base Id" }, "deleted_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Deleted Count" } }, "required": [ "success" ], "title": "DeleteRecordsOutput", "type": "object" } ``` ## Limits & Quotas - Airtable caps batch record operations at **10 records per request**; `create/update/delete_records` split larger inputs automatically. - `update_records` accepts both shapes per record: `{"id": "rec…", "fields": {"X": 1}}` (canonical) and `{"id": "rec…", "X": 1, "Y": 2}` (flat top-level fields). Both normalize to the canonical wire shape internally. - `delete_records` uses `?records[]=rec1&records[]=rec2` query parameters (Airtable's documented batch-delete contract). - Partial-batch failures surface as `success=False` + the count of records that completed before the error (via `updated_count` / `deleted_count`). ## Related integrations ## Links - [Airtable](https://airtable.com) --- # Airweave Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/airweave Search across your synced data sources using Airweave. Supports semantic search with hybrid, neural, or keyword retrieval strategies. Optionally generate AI-powered answers from search results. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/airweave/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Airweave logo ## Overview Add **Airweave** to any ModuleX agent or workflow. AI-powered semantic search across your synced data collections via the Airweave Search REST API (`api.airweave.ai`). One tool retrieves relevant content from a collection using hybrid, neural, or keyword strategies, and can optionally synthesize an AI-generated answer. **Categories**: Web Search & Scraping · Vector Search · Knowledge Base · **Auth**: API Key · **Actions**: 1 ## Authentication ### API Key Authentication Authenticate using your Airweave API key Go to https://app.airweave.ai and sign up or log in Open the API Keys section of your dashboard Create a new API key or copy your existing one Paste the API key below #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Airweave API Key | Your Airweave API key from app.airweave.ai | Yes | `-` | ## Available Actions ### Parameters The readable ID of the collection to search The search query text Maximum number of results to return Retrieval strategy: 'hybrid' (default), 'neural', or 'keyword' Generate query variations to improve recall Reorder results for improved relevance using an LLM Generate a natural-language answer to the query ### Response ```json { "$defs": { "SearchResultItem": { "additionalProperties": false, "description": "A single result row returned by ``search``.", "properties": { "entity_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Entity Id" }, "source_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Source Name" }, "md_content": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Md Content" }, "score": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Score" }, "metadata": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Metadata" }, "breadcrumbs": { "items": { "type": "string" }, "title": "Breadcrumbs", "type": "array" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" } }, "title": "SearchResultItem", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "results": { "items": { "$ref": "#/$defs/SearchResultItem" }, "title": "Results", "type": "array" }, "completion": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Completion" }, "total_results": { "default": 0, "title": "Total Results", "type": "integer" } }, "required": [ "success" ], "title": "SearchOutput", "type": "object" } ``` ## Limits & Quotas - **Collection-scoped**: every search runs against one collection's readable ID; results carry per-source attribution (`source_name`, `breadcrumbs`, `url`). - **Answer generation**: setting `generate_answer=true` runs an extra LLM synthesis step and populates the `completion` field; expect higher latency. - **Error model**: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. Plan for retries on the agent side based on the error string. ## Related integrations ## Links - [Airweave](https://airweave.ai) --- # Algolia Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/algolia Search and indexing platform for building fast, relevant search experiences {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/algolia/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Algolia logo ## Overview Add **Algolia** to any ModuleX agent or workflow. Search and indexing platform for building fast, relevant search experiences via the Algolia REST API (`{application_id}-dsn.algolia.net` for reads, `{application_id}.algolia.net` for writes). **Categories**: Developer Tools & Infrastructure · Search · **Auth**: Algolia API Credentials · **Actions**: 4 ## Authentication ### Algolia API Credentials Authenticate using your Algolia Application ID and API Key Go to https://dashboard.algolia.com and sign in Navigate to Settings > API Keys Copy your Application ID and Admin API Key #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Application ID | Your Algolia Application ID from the dashboard | Yes | `ABCDEF1234` | | Admin API Key | Your Algolia Admin API Key (or a key with appropriate permissions) | Yes | `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | ## Available Actions ### Parameters The name of the Algolia index to browse ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "hits": { "items": { "additionalProperties": true, "type": "object" }, "title": "Hits", "type": "array" }, "cursor": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Cursor" } }, "required": [ "success" ], "title": "BrowseRecordsOutput", "type": "object" } ``` ### Parameters The name of the Algolia index List of object IDs (strings) to delete from the index ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "task_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Task Id" }, "object_ids": { "items": { "type": "string" }, "title": "Object Ids", "type": "array" } }, "required": [ "success" ], "title": "DeleteRecordsOutput", "type": "object" } ``` ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "index_names": { "items": { "type": "string" }, "title": "Index Names", "type": "array" } }, "required": [ "success" ], "title": "ListIndexNameOptionsOutput", "type": "object" } ``` ### Parameters The name of the Algolia index List of JSON objects to save. Each must have an 'objectID' field. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "task_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Task Id" }, "object_ids": { "items": { "type": "string" }, "title": "Object Ids", "type": "array" } }, "required": [ "success" ], "title": "SaveRecordsOutput", "type": "object" } ``` ## Limits & Quotas - Rate limits depend on the Algolia plan (Community, Standard, Premium). Community plans have lower indexing and search operation limits. - Search operations: typically thousands per second on paid plans. - Indexing operations: batch limits vary by plan (10 MB per batch maximum payload size). - Error model: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. ## Related integrations ## Links - [Algolia](https://www.algolia.com) --- # Amazon Alexa Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/amazon-alexa Simulate and test Alexa skills via the Alexa Skills Management API (SMAPI). {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/amazon_alexa/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Amazon Alexa logo ## Overview Add **Amazon Alexa** to any ModuleX agent or workflow. Simulate and test Alexa skills via the Alexa Skills Management API (`api.amazonalexa.com/v2`). **Categories**: AI & Machine Learning · Voice Assistants · IoT · Smart Home · **Auth**: OAuth2 · **Actions**: 2 ## Authentication ### OAuth2 Authentication Connect using Amazon OAuth (recommended) #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Client ID | Amazon OAuth App Client ID from the Amazon Developer Console | Yes | `amzn1.application-oa2-client.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | | Client Secret | Amazon OAuth App Client Secret | Yes | `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | #### OAuth Configuration - **Authorization URL**: `https://www.amazon.com/ap/oa` - **Token URL**: `https://api.amazon.com/auth/o2/token` - **Scopes**: `alexa::ask:skills:readwrite`, `alexa::ask:skills:test` ## Available Actions ### Parameters The unique identifier for the Alexa skill. The stage of the skill: development or live. Utterance text from a user to Alexa. Locale for the virtual device used in the simulation (e.g. en-US, en-GB, de-DE, fr-FR, ja-JP). (Default: `en-US`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "simulation_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Simulation Id" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" } }, "required": [ "success" ], "title": "SimulateSkillOutput", "type": "object" } ``` ### Parameters The unique identifier for the Alexa skill. The stage of the skill: development or live. The identifier for the simulation. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "simulation_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Simulation Id" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "description": "Simulation result data from the Alexa API", "title": "Result" } }, "required": [ "success" ], "title": "GetSimulationResultsOutput", "type": "object" } ``` ## Limits & Quotas - The Alexa SMAPI is rate-limited per developer account; exact limits are not publicly documented but throttling may occur at high request volumes. - Simulation requests are asynchronous: `simulate_skill` returns a simulation ID immediately; poll with `get_simulation_results` to retrieve outcomes. - Error model: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. ## Related integrations ## Links - [Amazon Alexa](https://developer.amazon.com/alexa) --- # Amazon Selling Partner Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/amazon-selling-partner Amazon Selling Partner API for managing orders, inventory, pricing, and reports on Amazon marketplaces {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/amazon_selling_partner/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Amazon Selling Partner logoAmazon Selling Partner logo ## Overview Add **Amazon Selling Partner** to any ModuleX agent or workflow. Manage orders, inventory, pricing, and reports on Amazon marketplaces via the Amazon Selling Partner API (`sellingpartnerapi-na.amazon.com`). **Categories**: E-Commerce · Marketplace · Fulfillment · **Auth**: OAuth2 · **Actions**: 8 ## Authentication ### OAuth2 Authentication Connect using Amazon Selling Partner OAuth (Login with Amazon) #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Client ID | Amazon SP-API OAuth App Client ID from your developer application | Yes | `-` | | Client Secret | Amazon SP-API OAuth App Client Secret | Yes | `-` | | SP-API Base URL | Selling Partner API regional endpoint. Use https://sellingpartnerapi-na.amazon.com for North America (default), https://sellingpartnerapi-eu.amazon.com for Europe, or https://sellingpartnerapi-fe.amazon.com for the Far East. Pick the endpoint that matches your seller account's region. | Yes | `https://sellingpartnerapi-na.amazon.com` | #### OAuth Configuration - **Authorization URL**: `https://www.amazon.com/ap/oa` - **Token URL**: `https://api.amazon.com/auth/o2/token` ## Available Actions ### Parameters The Amazon Marketplace ID (e.g. ATVPDKIKX0DER for US) Set to true to return inventory summaries with additional summarized inventory details and quantities (Default: `false`) A start date and time in ISO8601 format. If specified, all inventory summaries that have changed since then are returned. Must be no earlier than 18 months prior to the current date. A list of seller SKUs for which to return inventory summaries (up to 50 SKUs). Elements are strings. ### Response ```json { "$defs": { "InventorySummary": { "additionalProperties": false, "properties": { "asin": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Asin" }, "fn_sku": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Fn Sku" }, "seller_sku": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Seller Sku" }, "condition": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Condition" }, "total_quantity": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Total Quantity" }, "product_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Product Name" } }, "title": "InventorySummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "inventory_summaries": { "items": { "$ref": "#/$defs/InventorySummary" }, "title": "Inventory Summaries", "type": "array" } }, "required": [ "success" ], "title": "CheckFbaInventoryLevelsOutput", "type": "object" } ``` ### Parameters The Amazon Marketplace ID Fetch orders created after this ISO date Fetch orders created before this ISO date The email address of a buyer to filter orders An order identifier specified by the seller. If provided, other filters like FulfillmentChannels, OrderStatuses, PaymentMethod cannot be used. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "orders": { "items": { "additionalProperties": true, "type": "object" }, "title": "Orders", "type": "array" } }, "required": [ "success" ], "title": "FetchOrdersByDateRangeOutput", "type": "object" } ``` ### Parameters A list of report types used to filter reports. Refer to SP-API Report Type Values documentation. Elements are strings. The Amazon Marketplace ID to filter reports A list of processing statuses used to filter reports. Valid values: CANCELLED, DONE, FATAL, IN_PROGRESS, IN_QUEUE. Elements are strings. The earliest report creation date and time in ISO 8601 format. Default is 90 days ago. The latest report creation date and time in ISO 8601 format. Default is now. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "reports": { "items": { "additionalProperties": true, "type": "object" }, "title": "Reports", "type": "array" } }, "required": [ "success" ], "title": "GenerateSalesInventoryReportsOutput", "type": "object" } ``` ### Parameters The Amazon Marketplace ID The Amazon order ID to fetch details for ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "order": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Order" } }, "required": [ "success" ], "title": "GetOrderDetailsOutput", "type": "object" } ``` ### Parameters The Amazon Marketplace ID Filter inbound shipments by status. Valid values: WORKING, SHIPPED, RECEIVING, CANCELLED, DELETED, CLOSED, ERROR, IN_TRANSIT, DELIVERED, CHECKED_IN. Elements are strings. A date for selecting inbound shipments that were last updated after (or at) a specified time A date for selecting inbound shipments that were last updated before (or at) a specified time ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "shipments": { "items": { "additionalProperties": true, "type": "object" }, "title": "Shipments", "type": "array" } }, "required": [ "success" ], "title": "ListInboundShipmentsOutput", "type": "object" } ``` ### Response ```json { "$defs": { "MarketplaceOption": { "additionalProperties": false, "properties": { "label": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Label" }, "value": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Value" } }, "title": "MarketplaceOption", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "marketplaces": { "items": { "$ref": "#/$defs/MarketplaceOption" }, "title": "Marketplaces", "type": "array" } }, "required": [ "success" ], "title": "ListMarketplaceIdOptionsOutput", "type": "object" } ``` ### Parameters The Amazon Marketplace ID Indicates whether ASIN values or seller SKU values are used to identify items. Valid values: Asin, Sku A list of ASINs or seller SKUs depending on item_type (up to 20 identifiers). Elements are strings. Filters the offer listings based on customer type. Valid values: Consumer, Business ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "pricing_data": { "items": { "additionalProperties": true, "type": "object" }, "title": "Pricing Data", "type": "array" } }, "required": [ "success" ], "title": "OptimizeProductPricingOutput", "type": "object" } ``` ### Parameters The Amazon Marketplace ID A time interval for selecting order metrics. Two dates separated by two hyphens (first date inclusive; second exclusive) in ISO8601 format. Example: 2018-09-01T00:00:00-07:00--2018-09-04T00:00:00-07:00 The granularity of the grouping of order metrics. Valid values: Hour, Day, Week, Month, Year, Total An IANA-compatible time zone for determining the day boundary. Required when specifying a granularity value greater than Hour. Filters results by buyer type. Valid values: All, B2B, B2C Filters results by fulfillment network. Valid values: MFN (merchant fulfillment), AFN (Amazon fulfillment) Specifies the day that the week starts on when granularity=Week. Valid values: Monday, Sunday. Default: Monday Filters results by ASIN. Cannot be specified together with sku. Filters results by SKU. Cannot be specified together with asin. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "order_metrics": { "items": { "additionalProperties": true, "type": "object" }, "title": "Order Metrics", "type": "array" } }, "required": [ "success" ], "title": "RetrieveSalesPerformanceReportsOutput", "type": "object" } ``` ## Limits & Quotas - SP-API enforces per-endpoint rate limits varying by selling partner type (standard vs. grantless). Typical burst: 1-30 requests/second depending on the endpoint. - Orders API: 1 request per second burst, with a restore rate of 1 request per second. - Inventory API: 2 requests per second burst. - Pricing API: 10 requests per 1 second burst (for `getCompetitivePrice`). - Reports API: varies per report type; creation is throttled at a lower rate than retrieval. - Error model: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. ## Related integrations ## Links - [Amazon Selling Partner](https://sellercentral.amazon.com) --- # Amplitude Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/amplitude Track events, identify users and groups, search for users, query analytics, and retrieve revenue data from Amplitude {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/amplitude/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Amplitude logo ## Overview Add **Amplitude** to any ModuleX agent or workflow. Track events, identify users and groups, search for users, query analytics, and retrieve revenue data from Amplitude via its HTTP V2, Identify, Dashboard REST, and User Profile APIs. **Categories**: Analytics & Data · Product Analytics · Marketing · **Auth**: Amplitude API Key + Secret Key · **Actions**: 11 ## Authentication ### Amplitude API Key + Secret Key Authenticate with your Amplitude project's API Key (used to send events and identify users) and Secret Key (required for the Dashboard REST API and User Profile analytics endpoints). Sign in to Amplitude and open the project you want to use. Go to Settings -> Projects -> (your project) -> General. Copy the 'API Key' and the 'Secret Key' shown for the project. Provide the API Key for event tracking; the Secret Key is also needed for analytics/dashboard queries. #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | API Key | Amplitude project API Key (used for event tracking and Dashboard Basic auth). | Yes | `-` | | Secret Key | Amplitude project Secret Key (required for Dashboard REST API and User Profile queries). | No | `-` | ## Available Actions ### Parameters Name of the event (e.g., "page_view", "purchase"). User ID (required if no device_id). Device ID (required if no user_id). Custom event properties as a JSON object. User properties to set (supports $set, $setOnce, $add, $append, $unset). Event timestamp in milliseconds since epoch. Session start time in milliseconds since epoch (-1 for no session). Unique ID for deduplication (within a 7-day window). Application version string. Platform (e.g., "Web", "iOS", "Android"). Two-letter country code. Language code (e.g., "en"). IP address for geo-location (use "$remote" for request IP). Price of the item purchased. Quantity of items purchased. Revenue amount. Product identifier. Revenue type (e.g., "purchase", "refund"). ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "code": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Code" }, "events_ingested": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Events Ingested" }, "payload_size_bytes": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Payload Size Bytes" }, "server_upload_time": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Server Upload Time" } }, "required": [ "success" ], "title": "SendEventOutput", "type": "object" } ``` ### Parameters User properties to set. Use operations like $set, $setOnce, $add, $append, $unset. User ID (required if no device_id). Device ID (required if no user_id). ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "code": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Code" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" } }, "required": [ "success" ], "title": "IdentifyUserOutput", "type": "object" } ``` ### Parameters Group classification (e.g., "company", "org_id"). Specific group identifier (e.g., "Acme Corp"). Group properties as a JSON object. Use operations like $set, $setOnce, $add, $append, $unset. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "code": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Code" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" } }, "required": [ "success" ], "title": "GroupIdentifyOutput", "type": "object" } ``` ### Parameters User ID, Device ID, or Amplitude ID to search for. ### Response ```json { "$defs": { "UserSearchMatch": { "additionalProperties": false, "description": "A single user-search match entry.", "properties": { "amplitude_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Amplitude Id" }, "user_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "User Id" } }, "title": "UserSearchMatch", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "matches": { "items": { "$ref": "#/$defs/UserSearchMatch" }, "title": "Matches", "type": "array" }, "type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Type" } }, "required": [ "success" ], "title": "UserSearchOutput", "type": "object" } ``` ### Parameters Amplitude internal user ID. Offset for pagination (default 0). Maximum number of events to return (default 1000, max 1000). Sort direction: "latest" or "earliest" (default: latest). ### Response ```json { "$defs": { "UserActivityEvent": { "additionalProperties": false, "description": "A single event in a user's activity stream.", "properties": { "event_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Event Type" }, "event_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Event Time" }, "event_properties": { "additionalProperties": true, "title": "Event Properties", "type": "object" }, "user_properties": { "additionalProperties": true, "title": "User Properties", "type": "object" }, "session_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Session Id" }, "platform": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Platform" }, "country": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Country" }, "city": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "City" } }, "title": "UserActivityEvent", "type": "object" }, "UserData": { "additionalProperties": false, "description": "User metadata returned alongside an activity stream.", "properties": { "user_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "User Id" }, "canonical_amplitude_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Canonical Amplitude Id" }, "num_events": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Num Events" }, "num_sessions": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Num Sessions" }, "platform": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Platform" }, "country": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Country" } }, "title": "UserData", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "events": { "items": { "$ref": "#/$defs/UserActivityEvent" }, "title": "Events", "type": "array" }, "user_data": { "anyOf": [ { "$ref": "#/$defs/UserData" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "UserActivityOutput", "type": "object" } ``` ### Parameters External user ID (required if no device_id). Device ID (required if no user_id). Include Amplitude user properties (default: false). (Default: `false`) Include cohort IDs the user belongs to (default: false). (Default: `false`) Include computed user properties (default: false). (Default: `false`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "user_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "User Id" }, "device_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Device Id" }, "amp_props": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Amp Props" }, "cohort_ids": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Cohort Ids" }, "computations": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Computations" } }, "required": [ "success" ], "title": "UserProfileOutput", "type": "object" } ``` ### Parameters Event type name to analyze. Start date in YYYYMMDD format. End date in YYYYMMDD format. Metric type: uniques, totals, pct_dau, average, histogram, sums, value_avg, or formula (default: uniques). Time interval: 1 (daily), 7 (weekly), or 30 (monthly). Property name to group by (prefix custom user properties with "gp:"). Maximum number of group-by values (max 1000). ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "series": { "items": {}, "title": "Series", "type": "array" }, "series_labels": { "items": {}, "title": "Series Labels", "type": "array" }, "series_collapsed": { "items": {}, "title": "Series Collapsed", "type": "array" }, "x_values": { "items": { "type": "string" }, "title": "X Values", "type": "array" } }, "required": [ "success" ], "title": "EventSegmentationOutput", "type": "object" } ``` ### Parameters Start date in YYYYMMDD format. End date in YYYYMMDD format. Metric type: "active" or "new" (default: active). Time interval: 1 (daily), 7 (weekly), or 30 (monthly). ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "series": { "items": {}, "title": "Series", "type": "array" }, "series_meta": { "items": { "type": "string" }, "title": "Series Meta", "type": "array" }, "x_values": { "items": { "type": "string" }, "title": "X Values", "type": "array" } }, "required": [ "success" ], "title": "GetActiveUsersOutput", "type": "object" } ``` ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "series": { "items": {}, "title": "Series", "type": "array" }, "series_labels": { "items": { "type": "string" }, "title": "Series Labels", "type": "array" }, "x_values": { "items": { "type": "string" }, "title": "X Values", "type": "array" } }, "required": [ "success" ], "title": "RealtimeActiveUsersOutput", "type": "object" } ``` ### Response ```json { "$defs": { "ProjectEvent": { "additionalProperties": false, "description": "A single event type listed for the project.", "properties": { "value": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Value" }, "display_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Display Name" }, "totals": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Totals" }, "hidden": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Hidden" }, "deleted": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Deleted" } }, "title": "ProjectEvent", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "events": { "items": { "$ref": "#/$defs/ProjectEvent" }, "title": "Events", "type": "array" } }, "required": [ "success" ], "title": "ListEventsOutput", "type": "object" } ``` ### Parameters Start date in YYYYMMDD format. End date in YYYYMMDD format. Metric: 0 (ARPU), 1 (ARPPU), 2 (Total Revenue), 3 (Paying Users). Time interval: 1 (daily), 7 (weekly), or 30 (monthly). ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "series": { "items": {}, "title": "Series", "type": "array" }, "series_labels": { "items": { "type": "string" }, "title": "Series Labels", "type": "array" }, "x_values": { "items": { "type": "string" }, "title": "X Values", "type": "array" } }, "required": [ "success" ], "title": "GetRevenueOutput", "type": "object" } ``` ## Limits & Quotas - HTTP V2 ingestion: limit uploads to 100 batches/second and 1000 events/second. - Dashboard REST API analytics endpoints are subject to per-project rate limits and cost thresholds. - `user_activity` returns at most 1000 events per call (use `offset` to page). - Error model: non-2xx responses (and missing credentials) are returned as `success=False` + `error` rather than raising. ## Related integrations ## Links - [Amplitude](https://amplitude.com) --- # Apify Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/apify Web scraping, automation, and data extraction platform via the Apify REST API {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/apify/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Apify logoApify logo ## Overview Add **Apify** to any ModuleX agent or workflow. Web scraping, automation, and data extraction platform via the Apify REST API (`api.apify.com/v2`). **Categories**: Web Search & Scraping · Developer Tools & Infrastructure · Automation · **Auth**: API Token · **Actions**: 7 ## Authentication ### API Token Authenticate using your Apify API token Go to https://console.apify.com/account/integrations Copy your Personal API token #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | API Token | Your Apify API token from the Integrations page | Yes | `apify_api_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | ## Available Actions ### Parameters The Actor ID or tilde-separated owner/name identifier (e.g. 'apify~web-scraper') JSON object with the input for the Actor run Specifies the Actor build to run. If not provided, the default build is used. If true, returns immediately with run metadata. If false, waits for completion. (Default: `true`) Optional timeout for the run, in seconds Memory limit for the run, in megabytes (power of 2, min 128) The maximum number of items that the Actor run should return ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "run_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Run Id" }, "act_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Act Id" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "started_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Started At" }, "dataset_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Dataset Id" }, "key_value_store_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Key Value Store Id" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "RunActorOutput", "type": "object" } ``` ### Parameters The ID of the task to run Optional JSON string to override the default input for the task run Optional timeout for the run, in seconds Memory limit for the run, in megabytes (power of 2, min 128) Specifies the Actor build to run (build tag or number) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "run_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Run Id" }, "act_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Act Id" }, "task_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Task Id" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "started_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Started At" }, "dataset_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Dataset Id" }, "key_value_store_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Key Value Store Id" } }, "required": [ "success" ], "title": "RunTaskOutput", "type": "object" } ``` ### Parameters The ID of the task to run Optional timeout for the run, in seconds Memory limit for the run, in megabytes (power of 2, min 128) Specifies the Actor build to run (build tag or number) The maximum number of dataset items to return (Default: `100`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "run_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Run Id" }, "act_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Act Id" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "started_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Started At" }, "finished_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Finished At" }, "dataset_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Dataset Id" }, "items": { "items": { "additionalProperties": true, "type": "object" }, "title": "Items", "type": "array" } }, "required": [ "success" ], "title": "RunTaskSynchronouslyOutput", "type": "object" } ``` ### Parameters The ID of the dataset to retrieve items from The number of records to skip before returning results (Default: `0`) The maximum number of items to return (Default: `100`) Return only non-empty items and skip hidden fields (fields starting with #) Comma-separated list of field names to include in results ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "items": { "items": { "additionalProperties": true, "type": "object" }, "title": "Items", "type": "array" }, "count": { "default": 0, "title": "Count", "type": "integer" } }, "required": [ "success" ], "title": "GetDatasetItemsOutput", "type": "object" } ``` ### Parameters The ID of the key-value store The key of the record to retrieve ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "content_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Content Type" }, "data": { "anyOf": [ {}, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "GetKvsRecordOutput", "type": "object" } ``` ### Parameters The URL of the web page to scrape Crawling engine: 'playwright:firefox' (stealthy), 'playwright:chrome', 'cheerio' (fast, static only), 'playwright:adaptive' (auto-switches) (Default: `playwright:firefox`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" }, "text": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Text" }, "html": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Html" }, "markdown": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Markdown" } }, "required": [ "success" ], "title": "ScrapeSingleUrlOutput", "type": "object" } ``` ### Parameters The ID of the key-value store The key of the record to create or update The value to store. Valid JSON strings are stored as JSON; otherwise as plain text. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "store_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Store Id" }, "key": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Key" }, "content_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Content Type" } }, "required": [ "success" ], "title": "SetKeyValueStoreRecordOutput", "type": "object" } ``` ## Limits & Quotas - Rate limits depend on Apify plan: Free tier has limited concurrent runs; paid plans scale to hundreds. - Actor runs are billed by compute units (CU) based on memory and duration. - Synchronous run endpoints have a default server timeout of 300 seconds. - Error model: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. ## Related integrations ## Links - [Apify](https://apify.com) --- # Apollo.io Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/apollo-io B2B sales intelligence platform with 210M+ contacts and 35M+ companies. Full CRM capabilities including enrichment, prospecting, contacts, accounts, deals, sequences, and tasks. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/apollo_io/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Apollo.io logo ## Overview Add **Apollo.io** to any ModuleX agent or workflow. B2B sales intelligence integration: full CRM CRUD + enrichment + prospecting + sequences + tasks against `api.apollo.io/api/v1`. **Largest integration in the package by action count** (27 actions). **Categories**: Sales · CRM & Customer · Sales · Customer Support · **Auth**: API Key · **Actions**: 28 ## Authentication ### API Key Authentication Authenticate using your Apollo.io API key. Some endpoints require master API key. #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Apollo.io API Key | Your Apollo.io API key for authentication | Yes | `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | ## Available Actions ### Parameters First name Last name Full name Email address Employer name Employer domain LinkedIn profile URL Include personal emails (Default: `false`) Include phone numbers (Default: `false`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PeopleEnrichmentOutput", "type": "object" } ``` ### Parameters List of person detail dicts (max 10) Include personal emails (Default: `false`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "BulkPeopleEnrichmentOutput", "type": "object" } ``` ### Parameters Company domain (e.g. 'apollo.io') ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "OrganizationEnrichmentOutput", "type": "object" } ``` ### Parameters List of company domains (max 10) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "BulkOrganizationEnrichmentOutput", "type": "object" } ``` ### Parameters Job titles to search Seniority levels Person locations Company HQ locations Company domains Apollo organization IDs Employee ranges Email verification status Keyword search Page number (Default: `1`) Results per page (Default: `25`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PeopleSearchOutput", "type": "object" } ``` ### Parameters Company name HQ locations Exclude locations Employee ranges Min revenue Max revenue Technologies Industry keywords Page number (Default: `1`) Results per page (Default: `25`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "OrganizationSearchOutput", "type": "object" } ``` ### Parameters Apollo organization ID ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "OrganizationJobPostingsOutput", "type": "object" } ``` ### Parameters Contact email First name Last name Job title Company name Link to existing account Phone number LinkedIn URL Address Labels/tags ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "CreateContactOutput", "type": "object" } ``` ### Parameters Contact ID to update New email New first name New last name New job title New phone Link to account ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "UpdateContactOutput", "type": "object" } ``` ### Parameters Search keywords Filter by stage Filter by owner Page number (Default: `1`) Results per page (Default: `25`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "SearchContactsOutput", "type": "object" } ``` ### Parameters Contact ID to view ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "ViewContactOutput", "type": "object" } ``` ### Parameters Account/company name Company domain Phone number Address Owner user ID ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "CreateAccountOutput", "type": "object" } ``` ### Parameters Account ID to update New name New phone New address New owner ID ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "UpdateAccountOutput", "type": "object" } ``` ### Parameters Search by name Filter by stage Filter by owner Page number (Default: `1`) Results per page (Default: `25`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "SearchAccountsOutput", "type": "object" } ``` ### Parameters Account ID to view ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "ViewAccountOutput", "type": "object" } ``` ### Parameters Deal name Deal stage ID Deal amount Associated account ID Associated contact IDs Deal owner user ID Expected close date (YYYY-MM-DD) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "CreateDealOutput", "type": "object" } ``` ### Parameters Deal ID to update New name New stage ID New amount New owner ID ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "UpdateDealOutput", "type": "object" } ``` ### Parameters Page number (Default: `1`) Results per page (Default: `25`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "ListDealsOutput", "type": "object" } ``` ### Parameters Deal ID to view ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "ViewDealOutput", "type": "object" } ``` ### Parameters Sequence name to search Page number (Default: `1`) Results per page (Default: `25`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "SearchSequencesOutput", "type": "object" } ``` ### Parameters Sequence ID Contact IDs to enroll Email campaign ID Sender email account ID ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "AddContactsToSequenceOutput", "type": "object" } ``` ### Parameters Task name/subject Due date (YYYY-MM-DD) Priority ('low', 'normal', 'high') (Default: `normal`) Associated contact ID Associated account ID Assigned user ID Task notes ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "CreateTaskOutput", "type": "object" } ``` ### Parameters Status filter Filter by assigned users Page number (Default: `1`) Results per page (Default: `25`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "SearchTasksOutput", "type": "object" } ``` ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "GetApiUsageOutput", "type": "object" } ``` ### Parameters Page number (Default: `1`) Results per page (Default: `25`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "ListUsersOutput", "type": "object" } ``` ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "ListContactStagesOutput", "type": "object" } ``` ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "ListAccountStagesOutput", "type": "object" } ``` ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "ListDealStagesOutput", "type": "object" } ``` ## Limits & Quotas - `per_page` clamped at 100 (Apollo's cap on most search endpoints). - Bulk-enrichment actions clamp input to 10 items per request (Apollo's cap). - Enrichment-class actions consume Apollo credits; CRUD on contacts/accounts/deals does NOT. - Domain helpers strip `https://` / `www.` / paths so callers can pass full URLs or bare hostnames interchangeably. ## Related integrations ## Links - [Apollo.io](https://www.apollo.io) --- # Amazon Web Services Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/aws Interact with AWS services including DynamoDB, S3, Lambda, SNS, SQS, EventBridge, CloudWatch Logs, and Redshift. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/aws/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Amazon Web Services logoAmazon Web Services logo ## Overview Add **Amazon Web Services** to any ModuleX agent or workflow. Interact with AWS services including DynamoDB, S3, Lambda, SNS, SQS, EventBridge, CloudWatch Logs, and Redshift via the AWS SDK (`boto3`). **Categories**: Cloud Infrastructure · Developer Tools & Infrastructure · Data Storage · **Auth**: AWS Access Key · **Actions**: 21 ## Authentication ### AWS Access Key Authenticate using an AWS access key ID and secret access key. Sign in to the AWS Management Console. Go to IAM > Users > your user > Security credentials. Create an access key and save both the Access Key ID and Secret Access Key. #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Access Key ID | Your AWS Access Key ID | Yes | `AKIAIOSFODNN7EXAMPLE` | | Secret Access Key | Your AWS Secret Access Key | Yes | `wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY` | ## Available Actions ### Parameters AWS region (e.g. us-east-1, eu-west-1) (Default: `us-east-1`) Name of the CloudWatch log group Name of the log stream within the log group The log event message Unix timestamp in milliseconds for the event Sequence token from a previous PutLogEvents call (not needed for a new log stream) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "next_sequence_token": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Next Sequence Token" }, "rejected_log_events_info": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Rejected Log Events Info" } }, "required": [ "success" ], "title": "CloudwatchLogsPutLogEventOutput", "type": "object" } ``` ### Parameters AWS region (e.g. us-east-1, eu-west-1) (Default: `us-east-1`) Name of the table to create Name of the partition key attribute Data type of the partition key: S (string), N (number), or B (binary) Name of the sort key attribute (optional) Data type of the sort key: S (string), N (number), or B (binary) Billing mode: PROVISIONED or PAY_PER_REQUEST Read capacity units (required when billing_mode is PROVISIONED) Write capacity units (required when billing_mode is PROVISIONED) Whether DynamoDB Streams is enabled Stream view type: KEYS_ONLY, NEW_IMAGE, OLD_IMAGE, or NEW_AND_OLD_IMAGES ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "table_description": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Table Description" } }, "required": [ "success" ], "title": "DynamodbCreateTableOutput", "type": "object" } ``` ### Parameters AWS region (e.g. us-east-1, eu-west-1) (Default: `us-east-1`) PartiQL statement to execute List of parameter values for the PartiQL statement (strings) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "items": { "items": { "additionalProperties": true, "type": "object" }, "title": "Items", "type": "array" } }, "required": [ "success" ], "title": "DynamodbExecuteStatementOutput", "type": "object" } ``` ### Parameters AWS region (e.g. us-east-1, eu-west-1) (Default: `us-east-1`) Name of the DynamoDB table Item key in DynamoDB JSON format, e.g. {"id": {"S": "123"}} ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "item": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Item" } }, "required": [ "success" ], "title": "DynamodbGetItemOutput", "type": "object" } ``` ### Parameters AWS region (e.g. us-east-1, eu-west-1) (Default: `us-east-1`) Name of the DynamoDB table Item in DynamoDB JSON format, e.g. {"id": {"S": "123"}, "name": {"S": "foo"}} ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "attributes": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Attributes" } }, "required": [ "success" ], "title": "DynamodbPutItemOutput", "type": "object" } ``` ### Parameters AWS region (e.g. us-east-1, eu-west-1) (Default: `us-east-1`) Name of the DynamoDB table Key condition expression, e.g. partitionKey = :pk AND sortKey > :sk Comma-separated attribute names to retrieve Substitution tokens for attribute names, e.g. {"#n": "name"} Substitution values in DynamoDB JSON format, e.g. {":pk": {"S": "val"}} ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "items": { "items": { "additionalProperties": true, "type": "object" }, "title": "Items", "type": "array" }, "count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Count" }, "scanned_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Scanned Count" } }, "required": [ "success" ], "title": "DynamodbQueryOutput", "type": "object" } ``` ### Parameters AWS region (e.g. us-east-1, eu-west-1) (Default: `us-east-1`) Name of the DynamoDB table Comma-separated attribute names to retrieve Filter expression to apply after scanning Substitution tokens for attribute names, e.g. {"#n": "name"} Substitution values in DynamoDB JSON format, e.g. {":val": {"S": "x"}} Maximum number of items to evaluate ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "items": { "items": { "additionalProperties": true, "type": "object" }, "title": "Items", "type": "array" }, "count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Count" }, "scanned_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Scanned Count" } }, "required": [ "success" ], "title": "DynamodbScanOutput", "type": "object" } ``` ### Parameters AWS region (e.g. us-east-1, eu-west-1) (Default: `us-east-1`) Name of the DynamoDB table Item key in DynamoDB JSON format, e.g. {"id": {"S": "123"}} Update expression, e.g. SET #n = :val Substitution tokens for attribute names, e.g. {"#n": "name"} Substitution values in DynamoDB JSON format, e.g. {":val": {"S": "x"}} ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "attributes": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Attributes" } }, "required": [ "success" ], "title": "DynamodbUpdateItemOutput", "type": "object" } ``` ### Parameters AWS region (e.g. us-east-1, eu-west-1) (Default: `us-east-1`) Name of the DynamoDB table Billing mode: PROVISIONED or PAY_PER_REQUEST Read capacity units (required when billing_mode is PROVISIONED) Write capacity units (required when billing_mode is PROVISIONED) Whether DynamoDB Streams is enabled Stream view type: KEYS_ONLY, NEW_IMAGE, OLD_IMAGE, or NEW_AND_OLD_IMAGES ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "table_description": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Table Description" } }, "required": [ "success" ], "title": "DynamodbUpdateTableOutput", "type": "object" } ``` ### Parameters AWS region (e.g. us-east-1, eu-west-1) (Default: `us-east-1`) Name of the EventBridge event bus JSON object to send as the event detail Free-form string (max 128 chars) identifying the event type (Default: `modulex.event`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "failed_entry_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Failed Entry Count" }, "entries": { "items": { "additionalProperties": true, "type": "object" }, "title": "Entries", "type": "array" } }, "required": [ "success" ], "title": "EventbridgeSendEventOutput", "type": "object" } ``` ### Parameters AWS region (e.g. us-east-1, eu-west-1) (Default: `us-east-1`) Name for the Lambda function IAM Role ARN for the function execution role Function source code (Python handler) Lambda runtime: python3.12, python3.11, nodejs20.x, etc. (Default: `python3.12`) Handler entrypoint (e.g. lambda_function.lambda_handler) (Default: `lambda_function.lambda_handler`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "function_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Function Name" }, "function_arn": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Function Arn" }, "runtime": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Runtime" }, "role": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Role" }, "handler": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Handler" }, "code_size": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Code Size" }, "last_modified": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Modified" }, "state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "State" } }, "required": [ "success" ], "title": "LambdaCreateFunctionOutput", "type": "object" } ``` ### Parameters AWS region (e.g. us-east-1, eu-west-1) (Default: `us-east-1`) Name or ARN of the Lambda function to invoke JSON object to send as the invocation payload ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "status_code": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Status Code" }, "payload": { "default": null, "title": "Payload" }, "function_error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Function Error" }, "executed_version": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Executed Version" } }, "required": [ "success" ], "title": "LambdaInvokeFunctionOutput", "type": "object" } ``` ### Response ```json { "$defs": { "RegionInfo": { "additionalProperties": false, "properties": { "region_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Region Name" }, "endpoint": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Endpoint" }, "opt_in_status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Opt In Status" } }, "title": "RegionInfo", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "regions": { "items": { "$ref": "#/$defs/RegionInfo" }, "title": "Regions", "type": "array" } }, "required": [ "success" ], "title": "ListRegionOptionsOutput", "type": "object" } ``` ### Parameters AWS region (e.g. us-east-1, eu-west-1) (Default: `us-east-1`) Name of the Redshift Serverless workgroup Database name Schema name Table name List of column names (strings), e.g. ["id", "name"] List of row arrays, e.g. [[1, "Alice"], [2, "Bob"]] ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "statement_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Statement Id" } }, "required": [ "success" ], "title": "RedshiftCreateRowsOutput", "type": "object" } ``` ### Parameters AWS region (e.g. us-east-1, eu-west-1) (Default: `us-east-1`) Name of the Redshift Serverless workgroup Database name Schema name Table name WHERE clause with named parameters, e.g. id = :id Named parameter values, e.g. {"id": 1} ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "statement_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Statement Id" } }, "required": [ "success" ], "title": "RedshiftDeleteRowsOutput", "type": "object" } ``` ### Parameters AWS region (e.g. us-east-1, eu-west-1) (Default: `us-east-1`) Name of the Redshift Serverless workgroup Database name List of column names to retrieve (strings); omit for SELECT * FROM clause, e.g. schema_name.table_name WHERE clause with named parameters, e.g. id = :id ORDER BY clause, e.g. column_name ASC Maximum number of rows to return (Default: `10`) Named parameter values, e.g. {"id": 1} ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "records": { "items": { "items": { "additionalProperties": true, "type": "object" }, "type": "array" }, "title": "Records", "type": "array" }, "total_num_rows": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Total Num Rows" }, "column_metadata": { "items": { "additionalProperties": true, "type": "object" }, "title": "Column Metadata", "type": "array" } }, "required": [ "success" ], "title": "RedshiftQueryDatabaseOutput", "type": "object" } ``` ### Parameters AWS region (e.g. us-east-1, eu-west-1) (Default: `us-east-1`) Name of the Redshift Serverless workgroup Database name Schema name Table name Key-value pairs to set, e.g. {"col1": "new_val"} WHERE clause with named parameters, e.g. id = :id Named parameter values, e.g. {"id": 1} ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "statement_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Statement Id" } }, "required": [ "success" ], "title": "RedshiftUpdateRowsOutput", "type": "object" } ``` ### Parameters AWS region (e.g. us-east-1, eu-west-1) (Default: `us-east-1`) S3 bucket name Object key (path) to generate the URL for ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" } }, "required": [ "success" ], "title": "S3GeneratePresignedUrlOutput", "type": "object" } ``` ### Parameters AWS region (e.g. us-east-1, eu-west-1) (Default: `us-east-1`) S3 bucket name S3 object key (path including filename and extension) Base64-encoded file content ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "bucket": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Bucket" }, "key": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Key" } }, "required": [ "success" ], "title": "S3UploadBase64AsFileOutput", "type": "object" } ``` ### Parameters AWS region (e.g. us-east-1, eu-west-1) (Default: `us-east-1`) ARN of the SNS topic Message to publish ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "message_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message Id" } }, "required": [ "success" ], "title": "SnsSendMessageOutput", "type": "object" } ``` ### Parameters AWS region (e.g. us-east-1, eu-west-1) (Default: `us-east-1`) URL of the SQS queue JSON object to send as the message body ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "message_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message Id" }, "md5_of_message_body": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Md5 Of Message Body" }, "sequence_number": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Sequence Number" } }, "required": [ "success" ], "title": "SqsSendMessageOutput", "type": "object" } ``` ## Limits & Quotas - AWS service limits vary by service, region, and account. See [AWS Service Quotas](https://docs.aws.amazon.com/general/latest/gr/aws-service-information.html) for per-service details. - DynamoDB: default 40,000 RCU / 40,000 WCU per table (on-demand auto-scales). - S3: no per-request limit; 3,500 PUT/s and 5,500 GET/s per prefix. - Lambda: 1,000 concurrent executions (default, adjustable). - SNS/SQS: region-dependent throughput limits. - Redshift Data API: 200 active queries per cluster/workgroup. - Error model: all AWS SDK exceptions are caught and returned as `success=False` + `error` rather than raising. ## Related integrations ## Links - [Amazon Web Services](https://aws.amazon.com) --- # Azure Storage Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/azure-storage Manage blobs and containers in Microsoft Azure Blob Storage {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/azure_storage/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Azure Storage logo ## Overview Add **Azure Storage** to any ModuleX agent or workflow. Manage blobs and containers in Microsoft Azure Blob Storage via the Azure Blob Storage REST API (`https://.blob.core.windows.net`). **Categories**: Cloud Infrastructure · Storage · **Auth**: Microsoft OAuth2 · **Actions**: 4 ## Authentication ### Microsoft OAuth2 Connect using Microsoft OAuth (recommended) #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Client ID | Azure AD App Registration Client ID | Yes | `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` | | Client Secret | Azure AD App Registration Client Secret | Yes | `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | | Storage Account Name | Azure Storage account name (appears in the blob endpoint URL) | Yes | `mystorageaccount` | #### OAuth Configuration - **Authorization URL**: `https://login.microsoftonline.com/common/oauth2/v2.0/authorize` - **Token URL**: `https://login.microsoftonline.com/common/oauth2/v2.0/token` - **Scopes**: `https://storage.azure.com/user_impersonation`, `offline_access` ## Available Actions ### Parameters Name of the container to create (lowercase, alphanumeric and hyphens only) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" } }, "required": [ "success" ], "title": "CreateContainerOutput", "type": "object" } ``` ### Parameters Name of the container holding the blob Name of the blob to delete ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" } }, "required": [ "success" ], "title": "DeleteBlobOutput", "type": "object" } ``` ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "containers": { "items": { "type": "string" }, "title": "Containers", "type": "array" } }, "required": [ "success" ], "title": "ListContainersOutput", "type": "object" } ``` ### Parameters Name of the target container Name for the blob in the container Publicly accessible URL of the file to upload ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" } }, "required": [ "success" ], "title": "UploadBlobOutput", "type": "object" } ``` ## Limits & Quotas - Azure Storage limits vary by account type and tier; see [Azure Storage scalability targets](https://learn.microsoft.com/en-us/azure/storage/common/scalability-targets-standard-account). - Blob REST API requests are subject to per-account throughput limits (up to 20,000 requests/sec for general-purpose v2 accounts). - Error model: non-2xx responses are caught and returned as `success=False` + `error` rather than raising. ## Related integrations ## Links - [Azure Storage](https://azure.microsoft.com/en-us/products/storage/blobs) --- # Bloomerang Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/bloomerang Nonprofit donor management and fundraising CRM platform {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/bloomerang/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Bloomerang logo Bloomerang logo ## Overview Add **Bloomerang** to any ModuleX agent or workflow. Nonprofit donor management and fundraising CRM platform integration against the Bloomerang REST API (`api.bloomerang.co/v2`). **Categories**: CRM · Nonprofit · Fundraising · **Auth**: API Key · **Actions**: 3 ## Authentication ### API Key Authentication Authenticate using your Bloomerang API key Log in to your Bloomerang account Navigate to Settings > API Keys Create a new API key or copy your existing one Paste the API key below #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Bloomerang API Key | Your Bloomerang API key | Yes | `-` | ## Available Actions ### Parameters Constituent type. Valid values: Individual, Organization First name of the constituent (used when type is Individual) Last name of the constituent (used when type is Individual) Organization name (used when type is Organization) Status of the constituent. Valid values: Active, Inactive, Deceased Middle name of the constituent Prefix/title. Valid values: Mr., Mrs., Ms., Mx., Dr., Rev., etc. Suffix. Valid values: Jr., Sr., II, III, IV, V, Ph.D., M.D., Esq., etc. Job title of the constituent Gender. Valid values: Male, Female, Other Birth date of the constituent (ISO format YYYY-MM-DD) Employer of the constituent Website URL of the constituent Facebook page URL of the constituent Twitter/X handle of the constituent LinkedIn page URL of the constituent Preferred communication channel. Valid values: Email, Phone, Text Message, Mail ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Id" }, "first_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "First Name" }, "last_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Name" }, "full_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Full Name" }, "type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Type" } }, "required": [ "success" ], "title": "CreateConstituentOutput", "type": "object" } ``` ### Parameters ID of the constituent (donor) Date of the donation (ISO format YYYY-MM-DD) Donation amount as a numeric string ID of the fund for the donation Payment method. Valid values: None, Cash, Check, CreditCard, Eft, InKind, ApplePay, GooglePay, PayPal, Venmo ID of the campaign for the donation ID of the appeal for the donation Note for the donation ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Id" }, "amount": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Amount" }, "date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Date" } }, "required": [ "success" ], "title": "CreateDonationOutput", "type": "object" } ``` ### Parameters ID of the constituent Date of the interaction (ISO format YYYY-MM-DD) Subject of the interaction Channel of the interaction. Valid values: Email, InPerson, Mail, MassEmail, Other, Phone, SocialMedia, TextMessage, VideoCall, Webinar, Website Purpose of the interaction. Valid values: Acknowledgement, ImpactCultivation, Newsletter, Receipt, Solicitation, SpecialEvent, VolunteerActivity, PledgeReminder, Welcome, Other Note for the interaction Whether the interaction was initiated by the constituent ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Id" }, "subject": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Subject" }, "channel": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Channel" } }, "required": [ "success" ], "title": "AddInteractionOutput", "type": "object" } ``` ## Limits & Quotas - No publicly documented rate limits for the Bloomerang REST API v2. - Error model: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. ## Related integrations ## Links - [Bloomerang](https://bloomerang.co) --- # Brandfetch Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/brandfetch Integrate Brandfetch into your workflow. Retrieve brand logos, colors, fonts, and company data by domain, ticker, or name search. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/brandfetch/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Brandfetch logo ## Overview Add **Brandfetch** to any ModuleX agent or workflow. Look up brand assets, logos, colors, fonts, and company firmographics by domain, ticker, ISIN, or crypto symbol — and resolve brand names to domains — against the Brandfetch REST API (`api.brandfetch.io`). **Categories**: Sales · Enrichment · Marketing · **Auth**: API Key · **Actions**: 2 ## Authentication ### API Key Authentication Authenticate using your Brandfetch API key Sign up or log in at https://developers.brandfetch.com Open your dashboard and locate your API key Copy the API key and paste it below #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Brandfetch API Key | Your Brandfetch API key from the developer dashboard | Yes | `-` | ## Available Actions ### Parameters Brand identifier: domain (nike.com), stock ticker (NKE), ISIN (US6541061031), or crypto symbol (BTC) ### Response ```json { "$defs": { "BrandColor": { "additionalProperties": false, "description": "A brand color with its hex value and role.", "properties": { "hex": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Hex" }, "type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Type" }, "brightness": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Brightness" } }, "title": "BrandColor", "type": "object" }, "BrandFont": { "additionalProperties": false, "description": "A brand font with its name, role, and origin.", "properties": { "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Type" }, "origin": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Origin" }, "origin_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Origin Id" }, "weights": { "items": { "type": "integer" }, "title": "Weights", "type": "array" } }, "title": "BrandFont", "type": "object" }, "BrandLink": { "additionalProperties": false, "description": "A single social/website link entry.", "properties": { "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" } }, "title": "BrandLink", "type": "object" }, "BrandLogo": { "additionalProperties": false, "description": "A logo asset with its theme and available formats.", "properties": { "type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Type" }, "theme": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Theme" }, "formats": { "items": { "$ref": "#/$defs/BrandLogoFormat" }, "title": "Formats", "type": "array" } }, "title": "BrandLogo", "type": "object" }, "BrandLogoFormat": { "additionalProperties": false, "description": "One downloadable format for a logo asset.", "properties": { "src": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Src" }, "format": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Format" }, "width": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Width" }, "height": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Height" }, "background": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Background" }, "size": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Size" } }, "title": "BrandLogoFormat", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "domain": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Domain" }, "claimed": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Claimed" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "long_description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Long Description" }, "links": { "items": { "$ref": "#/$defs/BrandLink" }, "title": "Links", "type": "array" }, "logos": { "items": { "$ref": "#/$defs/BrandLogo" }, "title": "Logos", "type": "array" }, "colors": { "items": { "$ref": "#/$defs/BrandColor" }, "title": "Colors", "type": "array" }, "fonts": { "items": { "$ref": "#/$defs/BrandFont" }, "title": "Fonts", "type": "array" }, "company": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Company" }, "quality_score": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Quality Score" }, "is_nsfw": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Nsfw" } }, "required": [ "success" ], "title": "GetBrandOutput", "type": "object" } ``` ### Parameters Company or brand name to search for ### Response ```json { "$defs": { "SearchResultItem": { "additionalProperties": false, "description": "A single brand match returned by ``search``.", "properties": { "brand_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Brand Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "domain": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Domain" }, "claimed": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Claimed" }, "icon": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Icon" } }, "title": "SearchResultItem", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "results": { "items": { "$ref": "#/$defs/SearchResultItem" }, "title": "Results", "type": "array" }, "total_results": { "default": 0, "title": "Total Results", "type": "integer" } }, "required": [ "success" ], "title": "SearchOutput", "type": "object" } ``` ## Limits & Quotas - **Rate limit**: approximately 30 requests/minute. - **Brand API** (`get_brand`): metered per request on paid tiers; requests for the `brandfetch.com` domain are free. - **Brand Search API** (`search`): free under fair use (up to ~500,000 requests/month). - **Error model**: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. Plan for retries on the agent side based on the error string. ## Related integrations ## Links - [Brandfetch](https://brandfetch.com) --- # Browser Use Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/browser-use AI-powered cloud browser automation via the Browser Use API {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/browser_use/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Browser Use logo Browser Use logo ## Overview Add **Browser Use** to any ModuleX agent or workflow. AI-powered cloud browser automation via the Browser Use REST API (`api.browser-use.com/api/v3`). Create agent sessions to perform web tasks, manage standalone browser sessions via CDP, and organize persistent profiles and workspaces. **Categories**: AI & Machine Learning · Automation · AI · Developer Tools & Infrastructure · **Auth**: API Key · **Actions**: 25 ## Authentication ### API Key Authentication Authenticate using your Browser Use API key Go to https://cloud.browser-use.com and sign in Navigate to your project settings or API Keys section Create a new API key or copy your existing one Paste the API key below #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Browser Use API Key | Your Browser Use API key from cloud.browser-use.com | Yes | `bu_xxxxxxxxxxxxxxxxxxxxxxxxxxxx` | ## Available Actions ### Parameters Natural-language instruction for the agent Browser Use agent model. Allowed values: claude-sonnet-4.6, claude-opus-4.6, gemini-3-flash, bu-mini, bu-max, bu-ultra (Default: `claude-sonnet-4.6`) ID of an existing session to dispatch a follow-up task to If true, the session stays idle after the task completes so it can accept follow-up tasks (Default: `false`) Maximum total session cost in USD. Example: 1.50 ID of a Browser Use profile to use ID of a Browser Use workspace to attach Lowercase proxy country code for browser traffic. Examples: us, de, jp. Enter none to disable proxy (Default: `us`) Optional JSON Schema for structured output If true, the agent can create scheduled tasks tied to your project (Default: `false`) Key-value pairs available to the agent through secure placeholders. Keys are visible to the model; values are hidden If true, Browser Use records the browser session and returns recording URLs after completion (Default: `false`) If true, enables built-in Browser Use agent skills such as file management (Default: `true`) If true, provisions a temporary email inbox for the session (Default: `true`) Controls deterministic script caching. Allowed values: auto, enabled, disabled (Default: `auto`) If true, uses your configured LLM provider key instead of Browser Use managed keys (Default: `false`) When script caching is active, validates cached script output and reruns the full agent if the result looks incorrect (Default: `true`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "task": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Task" }, "live_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Live Url" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "CreateSessionOutput", "type": "object" } ``` ### Parameters ID of the Browser Use agent session ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "task": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Task" }, "output": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Output" }, "live_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Live Url" }, "screenshot_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Screenshot Url" }, "cost": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Cost" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "GetSessionOutput", "type": "object" } ``` ### Parameters Page number to fetch. The first page is 1 (Default: `1`) Number of records to return per page. Maximum: 100 (Default: `20`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "sessions": { "items": { "additionalProperties": true, "type": "object" }, "title": "Sessions", "type": "array" }, "total": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Total" } }, "required": [ "success" ], "title": "ListSessionsOutput", "type": "object" } ``` ### Parameters ID of the Browser Use agent session to delete ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" } }, "required": [ "success" ], "title": "DeleteSessionOutput", "type": "object" } ``` ### Parameters ID of the Browser Use agent session Use task to stop only the current task and keep the session alive, or session to destroy the sandbox entirely. Allowed values: task, session (Default: `session`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "StopSessionOutput", "type": "object" } ``` ### Parameters ID of the Browser Use agent session Return messages after this message ID cursor Return messages before this message ID cursor Maximum number of messages to return. Maximum: 100 (Default: `10`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "messages": { "items": { "additionalProperties": true, "type": "object" }, "title": "Messages", "type": "array" } }, "required": [ "success" ], "title": "ListSessionMessagesOutput", "type": "object" } ``` ### Parameters ID of a Browser Use profile Lowercase proxy country code for browser traffic. Examples: us, de, jp. Enter none to disable proxy (Default: `us`) Session timeout in minutes. Supported range: 1 to 240 (Default: `60`) Custom browser screen width in pixels. Supported range: 320 to 6144 Custom browser screen height in pixels. Supported range: 320 to 3456 Whether to allow browser resizing during the session (Default: `false`) Custom proxy object with host, port, username, and password fields. Requires an active subscription If true, records the browser session (Default: `false`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "live_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Live Url" }, "cdp_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Cdp Url" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "CreateBrowserSessionOutput", "type": "object" } ``` ### Parameters ID of the Browser Use browser session ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "live_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Live Url" }, "cdp_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Cdp Url" }, "timeout": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Timeout" }, "cost": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Cost" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "GetBrowserSessionOutput", "type": "object" } ``` ### Parameters Number of records to return per page. Maximum: 100 (Default: `20`) Page number to fetch. The first page is 1 (Default: `1`) Filter browser sessions by status. Allowed values: active, stopped ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "items": { "items": { "additionalProperties": true, "type": "object" }, "title": "Items", "type": "array" }, "total_items": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Total Items" } }, "required": [ "success" ], "title": "ListBrowserSessionsOutput", "type": "object" } ``` ### Parameters ID of the Browser Use browser session Action to perform on the browser session. Currently supported value: stop (Default: `stop`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "UpdateBrowserSessionOutput", "type": "object" } ``` ### Parameters Optional profile name. Maximum length: 100 characters Optional internal user identifier from your system. Maximum length: 255 characters ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "CreateProfileOutput", "type": "object" } ``` ### Parameters ID of the Browser Use profile ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "user_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "User Id" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "GetProfileOutput", "type": "object" } ``` ### Parameters Number of records to return per page. Maximum: 100 (Default: `20`) Page number to fetch. The first page is 1 (Default: `1`) Search query for profile name or user ID. Maximum length: 200 characters ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "items": { "items": { "additionalProperties": true, "type": "object" }, "title": "Items", "type": "array" }, "total_items": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Total Items" } }, "required": [ "success" ], "title": "ListProfilesOutput", "type": "object" } ``` ### Parameters ID of the Browser Use profile to delete ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" } }, "required": [ "success" ], "title": "DeleteProfileOutput", "type": "object" } ``` ### Parameters ID of the Browser Use profile Updated profile name. Maximum length: 100 characters Updated internal user identifier. Maximum length: 255 characters ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "user_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "User Id" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "UpdateProfileOutput", "type": "object" } ``` ### Parameters Optional workspace name. Maximum length: 100 characters ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "CreateWorkspaceOutput", "type": "object" } ``` ### Parameters ID of the Browser Use workspace ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "GetWorkspaceOutput", "type": "object" } ``` ### Parameters Number of records to return per page. Maximum: 100 (Default: `20`) Page number to fetch. The first page is 1 (Default: `1`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "items": { "items": { "additionalProperties": true, "type": "object" }, "title": "Items", "type": "array" }, "total_items": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Total Items" } }, "required": [ "success" ], "title": "ListWorkspacesOutput", "type": "object" } ``` ### Parameters ID of the Browser Use workspace to delete ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" } }, "required": [ "success" ], "title": "DeleteWorkspaceOutput", "type": "object" } ``` ### Parameters ID of the Browser Use workspace Updated workspace name. Maximum length: 100 characters ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "UpdateWorkspaceOutput", "type": "object" } ``` ### Parameters ID of the Browser Use workspace ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "size_bytes": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Size Bytes" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "GetWorkspaceSizeOutput", "type": "object" } ``` ### Parameters ID of the Browser Use workspace Optional directory prefix to list. Example: reports/ Maximum number of files to return. Maximum: 100 (Default: `50`) Pagination cursor from a previous response If true, include presigned download URLs for files (Default: `false`) If true, list only immediate files and folders at the prefix (Default: `false`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "files": { "items": { "additionalProperties": true, "type": "object" }, "title": "Files", "type": "array" }, "cursor": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Cursor" } }, "required": [ "success" ], "title": "ListWorkspaceFilesOutput", "type": "object" } ``` ### Parameters ID of the Browser Use workspace Relative workspace file path to delete. Example: reports/data.csv ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" } }, "required": [ "success" ], "title": "DeleteWorkspaceFileOutput", "type": "object" } ``` ### Parameters ID of the Browser Use workspace Optional directory prefix to upload into. Example: uploads/ JSON array of file metadata objects. Each object has name (required), contentType (optional), and size (optional integer). 1 to 10 files per request ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "files": { "items": { "additionalProperties": true, "type": "object" }, "title": "Files", "type": "array" } }, "required": [ "success" ], "title": "UploadWorkspaceFilesOutput", "type": "object" } ``` ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "GetAccountBillingOutput", "type": "object" } ``` ## Limits & Quotas - No publicly documented request rate limits at the time of writing. - Agent sessions are billed by runtime and model usage; `max_cost_usd` parameter available to cap spend per session. - Browser sessions are billed by runtime and can run up to 4 hours. - Error model: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. ## Related integrations ## Links - [Browser Use](https://browser-use.com) --- # Browserbase Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/browserbase Cloud browser infrastructure for running and managing headless browser sessions {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/browserbase/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Browserbase logo ## Overview Add **Browserbase** to any ModuleX agent or workflow. Cloud browser infrastructure for running and managing headless browser sessions via the Browserbase REST API (`api.browserbase.com/v1`). **Categories**: Developer Tools & Infrastructure · Automation · Browser · **Auth**: API Key · **Actions**: 3 ## Authentication ### API Key Authentication Authenticate using your Browserbase API key Go to https://www.browserbase.com and sign in Navigate to Settings > API Keys Create a new API key or copy your existing one Paste the API key below #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Browserbase API Key | Your Browserbase API key from the settings page | Yes | `bb_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | ## Available Actions ### Parameters The ID of the Browserbase project ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "project_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Project Id" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" } }, "required": [ "success" ], "title": "CreateContextOutput", "type": "object" } ``` ### Parameters The ID of the Browserbase project The uploaded Extension ID to load in the session Settings for the session (e.g. fingerprint, viewport). See Browserbase docs for schema. Duration in seconds after which the session will automatically end. Min: 60, Max: 21600. Set to true to keep the session alive even after disconnections Array of proxy configuration objects. Each element should have type and optional geolocation fields. The region where the session should run. One of: us-west-2, us-east-1, eu-central-1, ap-southeast-1. Arbitrary user metadata to attach to the session ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "project_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Project Id" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "region": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Region" }, "connect_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Connect Url" } }, "required": [ "success" ], "title": "CreateSessionOutput", "type": "object" } ``` ### Response ```json { "$defs": { "ProjectSummary": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" } }, "title": "ProjectSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "projects": { "items": { "$ref": "#/$defs/ProjectSummary" }, "title": "Projects", "type": "array" } }, "required": [ "success" ], "title": "ListProjectsOutput", "type": "object" } ``` ## Limits & Quotas - Rate limits are not publicly documented by Browserbase; contact their support for enterprise limits. - Session timeout range: 60–21600 seconds. - Error model: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. ## Related integrations ## Links - [Browserbase](https://www.browserbase.com) --- # Cal.com Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/cal-com Scheduling and booking management via the Cal.com v2 API {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/cal_com/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Cal.com logo Cal.com logo ## Overview Add **Cal.com** to any ModuleX agent or workflow. Scheduling and booking management via the Cal.com v2 REST API (`api.cal.com/v2`). **Categories**: Scheduling & Events · Productivity & Collaboration · Scheduling · Calendar · **Auth**: API Key · **Actions**: 6 ## Authentication ### API Key Authenticate using your Cal.com v2 API key Sign in to Cal.com at https://app.cal.com Navigate to Settings > Developer > API Keys Create a new API key or copy an existing one Paste the API key below #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Cal.com API Key | Your Cal.com v2 API key from Settings > Developer > API Keys | Yes | `cal_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | ## Available Actions ### Parameters Type of booking: booking, instant, or recurring Full name of the attendee Time zone of the attendee, e.g. America/New_York Booking start time in ISO 8601 UTC format, e.g. 2024-08-13T09:00:00Z Email address of the attendee Phone number in international format, e.g. +919876543210 Language for the booking confirmation ID of the event type (alternative to slug-based identification) Slug of the event type (use with username or team_slug) Username of the individual event owner Team slug for team event type identification Organization slug for slug-based event type identification Booking end time in ISO 8601 format Override the event type default duration in minutes Additional guest email addresses to invite Meeting location type: attendeeAddress, attendeeDefined, attendeePhone, integration Physical address if location is attendeeAddress Location string if location is attendeeDefined Phone number if location is attendeePhone Video conferencing integration if location is integration Number of occurrences for recurring booking type Custom key-value metadata, e.g. {"source": "website"} Responses to custom booking form fields keyed by field slug Bypass availability checks and allow double-booking Allow booking outside the event type scheduling window Required when event type has email verification enabled ### Response ```json { "$defs": { "BookingItem": { "additionalProperties": false, "description": "A booking object returned by the Cal.com API.", "properties": { "uid": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Uid" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "start": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Start" }, "end": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "End" }, "attendees": { "items": { "additionalProperties": true, "type": "object" }, "title": "Attendees", "type": "array" }, "hosts": { "items": { "additionalProperties": true, "type": "object" }, "title": "Hosts", "type": "array" }, "event_type_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Event Type Id" }, "meeting_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Meeting Url" }, "location": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Location" } }, "title": "BookingItem", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "booking": { "anyOf": [ { "$ref": "#/$defs/BookingItem" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "CreateBookingOutput", "type": "object" } ``` ### Parameters The UID of the booking to cancel The reason for cancelling the booking Whether to cancel all subsequent occurrences of a recurring booking ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" } }, "required": [ "success" ], "title": "DeleteBookingOutput", "type": "object" } ``` ### Parameters Maximum number of pages to fetch (1-500, default 50) Filter by status: upcoming, recurring, past, cancelled, unconfirmed Return bookings that start after this time (ISO 8601) Return bookings that end before this time (ISO 8601) Return bookings created after this time (ISO 8601) Return bookings created before this time (ISO 8601) Filter bookings by attendee email Filter bookings by attendee name Filter bookings by booking UID Filter bookings by event type ID Sort bookings by start time: asc or desc Sort bookings by end time: asc or desc Sort bookings by creation time: asc or desc ### Response ```json { "$defs": { "BookingItem": { "additionalProperties": false, "description": "A booking object returned by the Cal.com API.", "properties": { "uid": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Uid" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "start": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Start" }, "end": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "End" }, "attendees": { "items": { "additionalProperties": true, "type": "object" }, "title": "Attendees", "type": "array" }, "hosts": { "items": { "additionalProperties": true, "type": "object" }, "title": "Hosts", "type": "array" }, "event_type_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Event Type Id" }, "meeting_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Meeting Url" }, "location": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Location" } }, "title": "BookingItem", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "bookings": { "items": { "$ref": "#/$defs/BookingItem" }, "title": "Bookings", "type": "array" }, "total": { "default": 0, "title": "Total", "type": "integer" } }, "required": [ "success" ], "title": "GetAllBookingsOutput", "type": "object" } ``` ### Parameters Start date/time in ISO 8601 format (UTC) End date/time in ISO 8601 format (UTC) ID of the event type to get slots for Slug of the event type (requires username or team_slug) Username of the individual event owner List of at least 2 usernames for a dynamic event Slug of the team for team event type slot lookup Organization slug for slug-based event type lookup The time zone for the slot lookup Override the default slot duration in minutes Response format: range (start and end) or time (start only) UID of the booking being rescheduled to exclude from busy calculations ### Response ```json { "$defs": { "GetBookableSlot": { "additionalProperties": false, "description": "A single available time slot.", "properties": { "time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Time" }, "start": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Start" }, "end": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "End" } }, "title": "GetBookableSlot", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "slots": { "additionalProperties": { "items": { "$ref": "#/$defs/GetBookableSlot" }, "type": "array" }, "title": "Slots", "type": "object" } }, "required": [ "success" ], "title": "GetBookableSlotsOutput", "type": "object" } ``` ### Parameters The UID of the booking to retrieve ### Response ```json { "$defs": { "BookingItem": { "additionalProperties": false, "description": "A booking object returned by the Cal.com API.", "properties": { "uid": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Uid" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "start": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Start" }, "end": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "End" }, "attendees": { "items": { "additionalProperties": true, "type": "object" }, "title": "Attendees", "type": "array" }, "hosts": { "items": { "additionalProperties": true, "type": "object" }, "title": "Hosts", "type": "array" }, "event_type_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Event Type Id" }, "meeting_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Meeting Url" }, "location": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Location" } }, "title": "BookingItem", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "booking": { "anyOf": [ { "$ref": "#/$defs/BookingItem" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "GetBookingOutput", "type": "object" } ``` ### Response ```json { "$defs": { "EventTypeOption": { "additionalProperties": false, "description": "An event type with its ID and title.", "properties": { "label": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Label" }, "value": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Value" } }, "title": "EventTypeOption", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "event_types": { "items": { "$ref": "#/$defs/EventTypeOption" }, "title": "Event Types", "type": "array" } }, "required": [ "success" ], "title": "ListEventTypeIdOptionsOutput", "type": "object" } ``` ## Limits & Quotas - Cal.com does not publicly document rate limits for the v2 API; enterprise plans may have higher thresholds. - The free tier has a limited number of event types and team members. - Error model: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. ## Related integrations ## Links - [Cal.com](https://cal.com) --- # Calendly Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/calendly Calendly scheduling platform integration for managing events, invitees, event types, and scheduling links. Automate meeting scheduling workflows and access scheduling data. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/calendly/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Calendly logo ## Overview Add **Calendly** to any ModuleX agent or workflow. Scheduling-platform integration for Calendly: events, invitees, event types, scheduling links, availability, organization members, groups, and webhook subscriptions. All against `api.calendly.com`. **Categories**: Scheduling & Events · Productivity · Calendar · Productivity · Meetings · **Auth**: OAuth 2.0, Personal Access Token · **Actions**: 11 ## Authentication ### OAuth 2.0 Authentication Authenticate using Calendly OAuth 2.0 flow #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Client ID | Calendly App Client ID | Yes | `-` | | Client Secret | Calendly App Client Secret | Yes | `-` | #### OAuth Configuration - **Authorization URL**: `https://auth.calendly.com/oauth/authorize` - **Token URL**: `https://auth.calendly.com/oauth/token` - **Scopes**: `default` ### Personal Access Token Authenticate using a Calendly Personal Access Token #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Calendly Personal Access Token | Your Calendly Personal Access Token | Yes | `-` | ## Available Actions ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "resource": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Resource" } }, "required": [ "success" ], "title": "GetCurrentUserOutput", "type": "object" } ``` ### Parameters User URI to filter events. Defaults to the authenticated user. Organization URI to filter events Filter by invitee email Filter by event status ('active' or 'canceled') Minimum start time (ISO 8601) Maximum start time (ISO 8601) Number of results to return (max 100) (Default: `20`) Token for pagination Sort order ('start_time:asc' or 'start_time:desc') ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "count": { "default": 0, "title": "Count", "type": "integer" }, "next_page_token": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Next Page Token" }, "next_page": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Next Page" }, "events": { "items": { "additionalProperties": true, "type": "object" }, "title": "Events", "type": "array" } }, "required": [ "success" ], "title": "ListEventsOutput", "type": "object" } ``` ### Parameters The UUID of the event to retrieve ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "resource": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Resource" } }, "required": [ "success" ], "title": "GetEventOutput", "type": "object" } ``` ### Parameters The UUID of the event Filter by invitee email Filter by invitee status ('active' or 'canceled') Number of results to return (max 100) (Default: `20`) Token for pagination Sort order ('created_at:asc' or 'created_at:desc') ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "count": { "default": 0, "title": "Count", "type": "integer" }, "next_page_token": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Next Page Token" }, "next_page": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Next Page" }, "invitees": { "items": { "additionalProperties": true, "type": "object" }, "title": "Invitees", "type": "array" } }, "required": [ "success" ], "title": "ListEventInviteesOutput", "type": "object" } ``` ### Parameters User URI to filter event types Organization URI to filter event types Filter by active status Number of results to return (max 100) (Default: `20`) Token for pagination Sort order ('name:asc' or 'name:desc') ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "count": { "default": 0, "title": "Count", "type": "integer" }, "next_page_token": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Next Page Token" }, "next_page": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Next Page" }, "event_types": { "items": { "additionalProperties": true, "type": "object" }, "title": "Event Types", "type": "array" } }, "required": [ "success" ], "title": "ListEventTypesOutput", "type": "object" } ``` ### Parameters URI or UUID of the event type Maximum events that can be scheduled with this link (Default: `1`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "booking_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Booking Url" }, "owner": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Owner" }, "owner_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Owner Type" } }, "required": [ "success" ], "title": "CreateSchedulingLinkOutput", "type": "object" } ``` ### Parameters URI of the invitee to mark as no-show ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "resource": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Resource" } }, "required": [ "success" ], "title": "CreateInviteeNoShowOutput", "type": "object" } ``` ### Parameters URI or UUID of the user ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "schedules": { "items": { "additionalProperties": true, "type": "object" }, "title": "Schedules", "type": "array" }, "count": { "default": 0, "title": "Count", "type": "integer" } }, "required": [ "success" ], "title": "ListUserAvailabilitySchedulesOutput", "type": "object" } ``` ### Parameters Organization URI. Defaults to the authenticated user's organization. Filter by user URI Number of results to return (max 100) (Default: `20`) Token for pagination ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "count": { "default": 0, "title": "Count", "type": "integer" }, "next_page_token": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Next Page Token" }, "next_page": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Next Page" }, "members": { "items": { "additionalProperties": true, "type": "object" }, "title": "Members", "type": "array" } }, "required": [ "success" ], "title": "ListOrganizationMembersOutput", "type": "object" } ``` ### Parameters Organization URI to list groups for Number of results to return (max 100) (Default: `20`) Token for pagination ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "count": { "default": 0, "title": "Count", "type": "integer" }, "next_page_token": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Next Page Token" }, "next_page": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Next Page" }, "groups": { "items": { "additionalProperties": true, "type": "object" }, "title": "Groups", "type": "array" } }, "required": [ "success" ], "title": "ListGroupsOutput", "type": "object" } ``` ### Parameters Organization URI Scope ('organization' or 'user') User URI (required if scope is 'user') Number of results to return (max 100) (Default: `20`) Token for pagination Sort order ('created_at:asc' or 'created_at:desc') ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "count": { "default": 0, "title": "Count", "type": "integer" }, "next_page_token": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Next Page Token" }, "next_page": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Next Page" }, "webhooks": { "items": { "additionalProperties": true, "type": "object" }, "title": "Webhooks", "type": "array" } }, "required": [ "success" ], "title": "ListWebhookSubscriptionsOutput", "type": "object" } ``` ## Limits & Quotas - Auto-resolves missing `user` / `organization` filters via a side call to `/users/me` (same as legacy). This costs one extra request per call but matches Calendly's expected pattern when the operator doesn't pass a filter explicitly. - Count parameters are clamped to 100 (Calendly's max page size). - `owner` / `user` parameters accept either a bare UUID or a full Calendly URI; bare UUIDs are promoted to the canonical `https://api.calendly.com//` form. - Failures (non-2xx, exceptions) surface as `success=False` + `error`. ## Related integrations ## Links - [Calendly](https://calendly.com) --- # Canva Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/canva Design platform for creating visual content, presentations, and documents {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/canva/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Canva logo ## Overview Add **Canva** to any ModuleX agent or workflow. Design platform integration for creating, listing, exporting, and importing visual content via the Canva Connect REST API (`api.canva.com/rest/v1`). **Categories**: Productivity & Collaboration · Design & Creative Tools · **Auth**: OAuth2 · **Actions**: 5 ## Authentication ### OAuth2 Authentication Connect using Canva OAuth (recommended) #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Client ID | Canva Connect API OAuth App Client ID | Yes | `-` | | Client Secret | Canva Connect API OAuth App Client Secret | Yes | `-` | #### OAuth Configuration - **Authorization URL**: `https://www.canva.com/api/oauth/authorize` - **Token URL**: `https://api.canva.com/rest/v1/oauth/token` - **Scopes**: `design:content:read`, `design:content:write`, `design:meta:read`, `asset:read`, `asset:write` ## Available Actions ### Parameters The desired design type: 'preset' (provide design_type_name) or 'custom' (provide width and height) The name of the design The ID of the asset to add to the new design The preset design type name: 'doc', 'whiteboard', or 'presentation'. Only applicable when design_type is 'preset' Width in pixels (40-8000). Only applicable when design_type is 'custom' Height in pixels (40-8000). Only applicable when design_type is 'custom' ### Response ```json { "$defs": { "DesignSummary": { "additionalProperties": false, "description": "A design object returned by Canva.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "owner": { "anyOf": [ { "additionalProperties": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "type": "object" }, { "type": "null" } ], "default": null, "title": "Owner" }, "thumbnail": { "anyOf": [ { "additionalProperties": { "anyOf": [ { "type": "string" }, { "type": "integer" }, { "type": "null" } ] }, "type": "object" }, { "type": "null" } ], "default": null, "title": "Thumbnail" }, "urls": { "anyOf": [ { "additionalProperties": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "type": "object" }, { "type": "null" } ], "default": null, "title": "Urls" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "updated_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated At" } }, "title": "DesignSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "design": { "anyOf": [ { "$ref": "#/$defs/DesignSummary" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "CreateDesignOutput", "type": "object" } ``` ### Parameters The name for the imported design URL of the file to import into Canva ### Response ```json { "$defs": { "ImportJob": { "additionalProperties": false, "description": "A design import job status object.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "design_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Design Id" } }, "title": "ImportJob", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "job": { "anyOf": [ { "$ref": "#/$defs/ImportJob" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "CreateDesignImportJobOutput", "type": "object" } ``` ### Parameters The ID of the design to export Export format: 'pdf', 'jpg', 'png', 'pptx', 'gif', or 'mp4' Array of page numbers (integers) to export. First page is 1. If omitted, all pages are exported JPG quality 1-100. Only applicable for 'jpg' format MP4 resolution: 'horizontal_480p', 'horizontal_720p', 'horizontal_1080p', 'horizontal_4k', 'vertical_480p', 'vertical_720p', 'vertical_1080p', 'vertical_4k' PDF paper size: 'a4', 'a3', 'letter', or 'legal' Use lossless compression for PNG exports Merge multi-page designs into a single PNG image Export quality tier: 'regular' or 'pro'. Pro may fail for premium elements Export height in pixels. Applicable for jpg, png, gif formats Export width in pixels. Applicable for jpg, png, gif formats ### Response ```json { "$defs": { "ExportJob": { "additionalProperties": false, "description": "An export job status object.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "urls": { "items": { "type": "string" }, "title": "Urls", "type": "array" } }, "title": "ExportJob", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "job": { "anyOf": [ { "$ref": "#/$defs/ExportJob" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "ExportDesignOutput", "type": "object" } ``` ### Parameters Search keyword or phrase (max 255 characters) Pagination cursor from a previous response to get the next page Filter by ownership: 'any', 'owned', or 'shared' (Default: `any`) Sort order: 'relevance', 'modified_descending', 'modified_ascending', 'title_descending', 'title_ascending' (Default: `relevance`) Maximum designs to return per request (1-100) (Default: `25`) ### Response ```json { "$defs": { "DesignSummary": { "additionalProperties": false, "description": "A design object returned by Canva.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "owner": { "anyOf": [ { "additionalProperties": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "type": "object" }, { "type": "null" } ], "default": null, "title": "Owner" }, "thumbnail": { "anyOf": [ { "additionalProperties": { "anyOf": [ { "type": "string" }, { "type": "integer" }, { "type": "null" } ] }, "type": "object" }, { "type": "null" } ], "default": null, "title": "Thumbnail" }, "urls": { "anyOf": [ { "additionalProperties": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "type": "object" }, { "type": "null" } ], "default": null, "title": "Urls" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "updated_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated At" } }, "title": "DesignSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "items": { "items": { "$ref": "#/$defs/DesignSummary" }, "title": "Items", "type": "array" }, "continuation": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Continuation" } }, "required": [ "success" ], "title": "ListDesignsOutput", "type": "object" } ``` ### Parameters The asset's name URL of the file to upload as an asset to Canva ### Response ```json { "$defs": { "UploadJob": { "additionalProperties": false, "description": "An asset upload job status object.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "asset": { "anyOf": [ { "additionalProperties": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "type": "object" }, { "type": "null" } ], "default": null, "title": "Asset" } }, "title": "UploadJob", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "job": { "anyOf": [ { "$ref": "#/$defs/UploadJob" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "UploadAssetOutput", "type": "object" } ``` ## Limits & Quotas - **Rate limits**: Canva Connect API enforces per-app rate limits (varies by plan; typically 100 requests/minute for standard apps). - **Export/Import jobs**: Asynchronous — the tool polls until completion (up to ~60 seconds). Large designs may take longer. - **File uploads**: Maximum file size varies by asset type (images up to 25 MB, videos up to 1 GB). - **Error model**: Non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. ## Related integrations ## Links - [Canva](https://www.canva.com) --- # Clay Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/clay Push records into a Clay table via its inbound webhook so Clay can run its data-enrichment waterfall on prospects and accounts. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/clay/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Clay logo ## Overview Add **Clay** to any ModuleX agent or workflow. Push records into a [Clay](https://www.clay.com) table through that table's inbound webhook so Clay can run its data-enrichment waterfall on prospects and accounts. **Categories**: Sales · CRM · Enrichment · **Auth**: Webhook Auth Token · **Actions**: 1 ## Authentication ### Webhook Auth Token Authenticate with the optional auth token from a Clay table's webhook source. Sent in the x-clay-webhook-auth header. Leave empty if the table's webhook does not require authentication. In a Clay workbook, click + Add and search for 'Webhooks', then 'Monitor webhook'. Copy the webhook URL Clay shows for the table — this is the webhook_url action parameter. If the table has webhook authentication enabled, copy the auth token Clay displays (it is shown only once). Paste the auth token below; otherwise leave it blank. #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Clay Webhook Auth Token | Optional auth token for a Clay table webhook, sent in the x-clay-webhook-auth header | No | `-` | ## Available Actions ### Parameters The Clay table webhook URL to send the record to The record to populate, as a JSON object whose keys map to the Clay table column names (e.g. name, email, company, domain) ### Response ```json { "$defs": { "PopulateMetadata": { "additionalProperties": false, "description": "Transport-level metadata about the webhook response.", "properties": { "status": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Status" }, "status_text": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status Text" }, "headers": { "additionalProperties": { "type": "string" }, "title": "Headers", "type": "object" }, "timestamp": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Timestamp" }, "content_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Content Type" } }, "title": "PopulateMetadata", "type": "object" } }, "additionalProperties": false, "description": "Result of pushing a record to a Clay table webhook.", "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ {}, { "type": "null" } ], "default": null, "title": "Data" }, "metadata": { "anyOf": [ { "$ref": "#/$defs/PopulateMetadata" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "PopulateOutput", "type": "object" } ``` ## Limits & Quotas - **Asynchronous enrichment**: the webhook acknowledges receipt immediately; Clay runs its enrichment waterfall afterward, so the enriched columns appear inside Clay, not in the tool response. - **Response shape**: the tool returns whatever the webhook acknowledges (`data`) plus transport `metadata` (HTTP status, status text, response headers, a call-time ISO-8601 UTC timestamp, and the content type). A non-JSON acknowledgment is wrapped as `{"message": ""}`. - **Error model**: non-2xx responses, timeouts, and unexpected exceptions are caught and returned as `success=False` + `error` rather than raising. ## Related integrations ## Links - [Clay](https://www.clay.com) --- # ClickUp Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/clickup ClickUp integration: workspaces, spaces, folders, lists, tasks, comments, tags, and team members. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/clickup/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} ClickUp logo ## Overview Add **ClickUp** to any ModuleX agent or workflow. ClickUp project management integration via the v2 REST API. Pure HTTP. 23 actions across workspaces, spaces, folders, lists, tasks, comments, tags, and team members. **Categories**: Project & Task Management · Project Management · Productivity · Communication & Collaboration · **Auth**: API Key · **Actions**: 23 ## Authentication ### API Key Authentication Authenticate using your ClickUp Personal API Token #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | ClickUp API Key | Your ClickUp Personal API Token from Settings > Apps | Yes | `pk_xxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | ## Available Actions ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "teams": { "items": { "additionalProperties": true, "type": "object" }, "title": "Teams", "type": "array" }, "count": { "default": 0, "title": "Count", "type": "integer" } }, "required": [ "success" ], "title": "GetTeamsOutput", "type": "object" } ``` ### Parameters Team (workspace) ID Include archived spaces (Default: `false`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "spaces": { "items": { "additionalProperties": true, "type": "object" }, "title": "Spaces", "type": "array" }, "count": { "default": 0, "title": "Count", "type": "integer" }, "team_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Team Id" } }, "required": [ "success" ], "title": "GetSpacesOutput", "type": "object" } ``` ### Parameters Space ID ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "GetSpaceOutput", "type": "object" } ``` ### Parameters Team (workspace) ID Space name Allow multiple assignees (Default: `true`) Optional features (due_dates, time_tracking, …) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "CreateSpaceOutput", "type": "object" } ``` ### Parameters Space ID Include archived folders (Default: `false`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "folders": { "items": { "additionalProperties": true, "type": "object" }, "title": "Folders", "type": "array" }, "count": { "default": 0, "title": "Count", "type": "integer" }, "space_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Space Id" } }, "required": [ "success" ], "title": "GetFoldersOutput", "type": "object" } ``` ### Parameters Folder ID ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "GetFolderOutput", "type": "object" } ``` ### Parameters Space ID Folder name ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "CreateFolderOutput", "type": "object" } ``` ### Parameters Folder ID ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "deleted": { "default": false, "title": "Deleted", "type": "boolean" }, "folder_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Folder Id" } }, "required": [ "success" ], "title": "DeleteFolderOutput", "type": "object" } ``` ### Parameters Folder ID Space ID Include archived (Default: `false`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "lists": { "items": { "additionalProperties": true, "type": "object" }, "title": "Lists", "type": "array" }, "count": { "default": 0, "title": "Count", "type": "integer" }, "folder_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Folder Id" }, "space_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Space Id" } }, "required": [ "success" ], "title": "GetListsOutput", "type": "object" } ``` ### Parameters List ID ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "GetListOutput", "type": "object" } ``` ### Parameters List name Folder ID Space ID List description Due date (epoch ms) Priority (1=Urgent..4=Low) Assignee user ID List status ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "CreateListOutput", "type": "object" } ``` ### Parameters List ID Whether to archive (Default: `false`) Include closed tasks (Default: `false`) Include subtasks (Default: `false`) Page number (0-indexed) (Default: `0`) id / created / updated / due_date Reverse sort order (Default: `false`) Filter by status names Filter by assignee IDs ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "tasks": { "items": { "additionalProperties": true, "type": "object" }, "title": "Tasks", "type": "array" }, "count": { "default": 0, "title": "Count", "type": "integer" }, "list_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "List Id" } }, "required": [ "success" ], "title": "GetTasksOutput", "type": "object" } ``` ### Parameters Task ID If true, task_id is a custom task ID (e.g. 'ABC-123') (Default: `false`) Required when custom_task_ids=true Include subtasks in response (Default: `false`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "GetTaskOutput", "type": "object" } ``` ### Parameters List ID Task name Markdown description Status name 1=Urgent..4=Low Due date (epoch ms) Whether due_date includes time (Default: `false`) Start date (epoch ms) Whether start_date includes time (Default: `false`) Assignee user IDs Tag names Parent task ID (for subtasks) Notify all assignees (Default: `true`) Time estimate (ms) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "CreateTaskOutput", "type": "object" } ``` ### Parameters Task ID If true, task_id is a custom task ID (e.g. 'ABC-123') (Default: `false`) Required when custom_task_ids=true Updated task name Updated task description Updated status Updated priority (1=Urgent..4=Low) Updated due date (epoch ms) Whether due_date includes time Updated start date (epoch ms) Whether start_date includes time User IDs to add User IDs to remove Whether to archive Time estimate (ms) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "UpdateTaskOutput", "type": "object" } ``` ### Parameters Task ID If true, task_id is a custom task ID (e.g. 'ABC-123') (Default: `false`) Required when custom_task_ids=true ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "deleted": { "default": false, "title": "Deleted", "type": "boolean" }, "task_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Task Id" } }, "required": [ "success" ], "title": "DeleteTaskOutput", "type": "object" } ``` ### Parameters Task ID If true, task_id is a custom task ID (e.g. 'ABC-123') (Default: `false`) Required when custom_task_ids=true Pagination start position Comment ID to start from ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "comments": { "items": { "additionalProperties": true, "type": "object" }, "title": "Comments", "type": "array" }, "count": { "default": 0, "title": "Count", "type": "integer" }, "task_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Task Id" } }, "required": [ "success" ], "title": "GetTaskCommentsOutput", "type": "object" } ``` ### Parameters Task ID If true, task_id is a custom task ID (e.g. 'ABC-123') (Default: `false`) Required when custom_task_ids=true Comment body Notify all assignees (Default: `false`) Assigned user ID ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "CreateTaskCommentOutput", "type": "object" } ``` ### Parameters Team (workspace) ID Search query Status filter Assignee user IDs Tag names List IDs Folder IDs Space IDs Include closed tasks (Default: `false`) Page number (0-indexed) (Default: `0`) Order field ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "tasks": { "items": { "additionalProperties": true, "type": "object" }, "title": "Tasks", "type": "array" }, "count": { "default": 0, "title": "Count", "type": "integer" }, "team_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Team Id" } }, "required": [ "success" ], "title": "SearchTasksOutput", "type": "object" } ``` ### Parameters Space ID ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "tags": { "items": { "additionalProperties": true, "type": "object" }, "title": "Tags", "type": "array" }, "count": { "default": 0, "title": "Count", "type": "integer" }, "space_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Space Id" } }, "required": [ "success" ], "title": "GetSpaceTagsOutput", "type": "object" } ``` ### Parameters Task ID Tag name If true, task_id is a custom task ID (e.g. 'ABC-123') (Default: `false`) Required when custom_task_ids=true ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "added": { "default": false, "title": "Added", "type": "boolean" }, "task_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Task Id" }, "tag_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Tag Name" } }, "required": [ "success" ], "title": "AddTagToTaskOutput", "type": "object" } ``` ### Parameters Task ID Tag name If true, task_id is a custom task ID (e.g. 'ABC-123') (Default: `false`) Required when custom_task_ids=true ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "removed": { "default": false, "title": "Removed", "type": "boolean" }, "task_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Task Id" }, "tag_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Tag Name" } }, "required": [ "success" ], "title": "RemoveTagFromTaskOutput", "type": "object" } ``` ### Parameters Team (workspace) ID ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "members": { "items": { "additionalProperties": true, "type": "object" }, "title": "Members", "type": "array" }, "count": { "default": 0, "title": "Count", "type": "integer" }, "team_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Team Id" } }, "required": [ "success" ], "title": "GetTeamMembersOutput", "type": "object" } ``` ## Related integrations ## Links - [ClickUp](https://clickup.com) --- # Cloudflare Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/cloudflare Cloudflare DNS, WAF, zones, firewall rules, and load balancer management. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/cloudflare/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Cloudflare logo ## Overview Add **Cloudflare** to any ModuleX agent or workflow. Cloudflare v4 API integration: DNS, WAF lists, zones, firewall rules, load balancer monitors & pools, account discovery. All against `api.cloudflare.com/client/v4`. **Categories**: Developer Tools & Infrastructure · Security · Infrastructure · Networking · **Auth**: API Token · **Actions**: 13 ## Authentication ### API Token Authentication Authenticate using your Cloudflare API Token (recommended) #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Cloudflare API Token | Your Cloudflare API Token for authentication | Yes | `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | ## Available Actions ### Parameters Zone name filter Zone status: active, pending, initializing, moved, deleted, deactivated Account identifier filter Page number (Default: `1`) Results per page (max 50 for most endpoints) (Default: `20`) Sort order: name, status, account.id, account.name Sort direction (asc/desc) (Default: `asc`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" }, "total": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Total" }, "page": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Page" }, "per_page": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Per Page" } }, "required": [ "success" ], "title": "ListZonesOutput", "type": "object" } ``` ### Parameters Zone identifier DNS record type (A, AAAA, CNAME, TXT, MX, NS, SRV, etc.) DNS record name DNS record content TTL (1 = automatic) (Default: `1`) Priority for MX/SRV records Proxy through Cloudflare (Default: `false`) DNS record comment DNS record tags ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" }, "total": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Total" }, "page": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Page" }, "per_page": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Per Page" } }, "required": [ "success" ], "title": "CreateDNSRecordOutput", "type": "object" } ``` ### Parameters Zone identifier DNS record identifier DNS record type DNS record name DNS record content TTL Priority for MX/SRV records Proxy through Cloudflare DNS record comment DNS record tags ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" }, "total": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Total" }, "page": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Page" }, "per_page": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Per Page" } }, "required": [ "success" ], "title": "UpdateDNSRecordOutput", "type": "object" } ``` ### Parameters Zone identifier DNS record identifier to delete ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" }, "total": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Total" }, "page": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Page" }, "per_page": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Per Page" } }, "required": [ "success" ], "title": "DeleteDNSRecordOutput", "type": "object" } ``` ### Parameters Account identifier ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" }, "total": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Total" }, "page": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Page" }, "per_page": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Per Page" } }, "required": [ "success" ], "title": "ListWAFListsOutput", "type": "object" } ``` ### Parameters Account identifier Name of the list List type: ip, redirect, hostname, asn Description of the list ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" }, "total": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Total" }, "page": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Page" }, "per_page": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Per Page" } }, "required": [ "success" ], "title": "CreateWAFListOutput", "type": "object" } ``` ### Parameters Account identifier List identifier New description for the list ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" }, "total": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Total" }, "page": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Page" }, "per_page": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Per Page" } }, "required": [ "success" ], "title": "UpdateWAFListOutput", "type": "object" } ``` ### Parameters Account identifier List identifier to delete ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" }, "total": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Total" }, "page": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Page" }, "per_page": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Per Page" } }, "required": [ "success" ], "title": "DeleteWAFListOutput", "type": "object" } ``` ### Parameters Page number (Default: `1`) Results per page (max 50 for most endpoints) (Default: `20`) Sort direction (asc/desc) (Default: `asc`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" }, "total": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Total" }, "page": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Page" }, "per_page": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Per Page" } }, "required": [ "success" ], "title": "ListAccountsOutput", "type": "object" } ``` ### Parameters Account identifier Page number (Default: `1`) Results per page (max 50 for most endpoints) (Default: `20`) Sort: user.first_name, user.last_name, user.email, status (Default: `status`) Sort direction (asc/desc) (Default: `asc`) Filter by status: accepted, pending, rejected ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" }, "total": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Total" }, "page": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Page" }, "per_page": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Per Page" } }, "required": [ "success" ], "title": "ListAccountMembersOutput", "type": "object" } ``` ### Parameters Zone identifier Page number (Default: `1`) Results per page (max 50 for most endpoints) (Default: `25`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" }, "total": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Total" }, "page": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Page" }, "per_page": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Per Page" } }, "required": [ "success" ], "title": "ListFirewallRulesOutput", "type": "object" } ``` ### Parameters Account identifier Page number (Default: `1`) Results per page (max 50 for most endpoints) (Default: `25`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" }, "total": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Total" }, "page": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Page" }, "per_page": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Per Page" } }, "required": [ "success" ], "title": "ListMonitorsOutput", "type": "object" } ``` ### Parameters Account identifier Page number (Default: `1`) Results per page (max 50 for most endpoints) (Default: `25`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" }, "total": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Total" }, "page": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Page" }, "per_page": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Per Page" } }, "required": [ "success" ], "title": "ListPoolsOutput", "type": "object" } ``` ## Limits & Quotas - Cloudflare wraps every response in `{"success", "errors", "messages", "result", "result_info"}`. The shared `_call` helper checks the envelope's `success` flag (not just HTTP status), pulls `errors[0].message` on failure, and lifts `result_info` pagination (`total_count`, `page`, `per_page`) onto the output's top-level `total`/`page`/`per_page` fields for list actions. - Most list endpoints cap `per_page` at 50; firewall rules cap at 100. - `result` is open: list endpoints return a JSON array; CRUD endpoints return the object directly. Mirrors the upstream shape. ## Related integrations ## Links - [Cloudflare](https://cloudflare.com) --- # Coinbase Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/coinbase Cryptocurrency trading and wallet management platform for buying, selling, and storing digital assets via the Coinbase Developer Platform (CDP) API. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/coinbase/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Coinbase logo ## Overview Add **Coinbase** to any ModuleX agent or workflow. Cryptocurrency trading and wallet management via the **Coinbase Developer Platform (CDP) API** (v2 + v3 brokerage). Pure HTTP; the only runtime dependency added is `cryptography` for local JWT signing. **Categories**: Finance & Payments · Business Services · Finance · Trading · Market · **Auth**: CDP API Key + Secret · **Actions**: 8 ## Authentication ### CDP API Key + Secret Authenticate using a Coinbase Developer Platform API key pair: the key ID and either an ECDSA (PEM) or Ed25519 (base64) private key. JWT signing happens locally. #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | API Key ID | Your Coinbase CDP API Key ID (e.g. organizations/{org_id}/apiKeys/{key_id}) | Yes | `organizations/{org_id}/apiKeys/{key_id}` | | API Secret | Coinbase CDP API Secret (PEM for ECDSA or base64 for Ed25519). Used to sign JWTs locally. | Yes | `-` | ## Available Actions ### Parameters Maximum accounts to return (1-100) (Default: `25`) ### Response ```json { "$defs": { "AccountRow": { "additionalProperties": false, "properties": { "uuid": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Uuid" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Type" }, "currency": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Currency" }, "available_balance": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Available Balance" }, "available_balance_currency": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Available Balance Currency" }, "hold_balance": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Hold Balance" }, "hold_balance_currency": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Hold Balance Currency" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "updated_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated At" }, "active": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Active" }, "ready": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Ready" }, "default": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Default" } }, "title": "AccountRow", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "accounts": { "items": { "$ref": "#/$defs/AccountRow" }, "title": "Accounts", "type": "array" }, "total": { "default": 0, "title": "Total", "type": "integer" }, "has_next": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Has Next" }, "cursor": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Cursor" } }, "required": [ "success" ], "title": "GetAccountsOutput", "type": "object" } ``` ### Parameters The ID of the account ### Response ```json { "$defs": { "AccountRow": { "additionalProperties": false, "properties": { "uuid": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Uuid" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Type" }, "currency": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Currency" }, "available_balance": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Available Balance" }, "available_balance_currency": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Available Balance Currency" }, "hold_balance": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Hold Balance" }, "hold_balance_currency": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Hold Balance Currency" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "updated_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated At" }, "active": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Active" }, "ready": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Ready" }, "default": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Default" } }, "title": "AccountRow", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "account": { "anyOf": [ { "$ref": "#/$defs/AccountRow" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "GetAccountOutput", "type": "object" } ``` ### Parameters The ID of the account Maximum transactions to return (1-100) (Default: `25`) Pagination cursor (last transaction ID) ### Response ```json { "$defs": { "TransactionRow": { "additionalProperties": false, "properties": { "entry_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Entry Id" }, "trade_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Trade Id" }, "order_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Order Id" }, "trade_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Trade Time" }, "trade_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Trade Type" }, "price": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Price" }, "size": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Size" }, "commission": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Commission" }, "product_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Product Id" }, "sequence_timestamp": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Sequence Timestamp" }, "liquidity_indicator": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Liquidity Indicator" }, "size_in_quote": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Size In Quote" }, "user_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "User Id" }, "side": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Side" } }, "title": "TransactionRow", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "transactions": { "items": { "$ref": "#/$defs/TransactionRow" }, "title": "Transactions", "type": "array" }, "total": { "default": 0, "title": "Total", "type": "integer" }, "cursor": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Cursor" } }, "required": [ "success" ], "title": "GetTransactionsOutput", "type": "object" } ``` ### Parameters The ID of the account The amount The currency code (e.g. 'BTC', 'ETH', 'USD') Optional payment method ID ### Response ```json { "$defs": { "_OrderInfo": { "additionalProperties": false, "properties": { "order_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Order Id" }, "product_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Product Id" }, "side": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Side" }, "client_order_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Client Order Id" } }, "title": "_OrderInfo", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "order": { "anyOf": [ { "$ref": "#/$defs/_OrderInfo" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "PlaceBuyOrderOutput", "type": "object" } ``` ### Parameters The ID of the account The amount The currency code (e.g. 'BTC', 'ETH', 'USD') Destination address (or payment method ID) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "amount": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Amount" }, "asset": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Asset" }, "fee": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Fee" }, "transaction_hash": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Transaction Hash" }, "destination_address": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Destination Address" }, "network": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Network" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" } }, "required": [ "success" ], "title": "WithdrawFundsOutput", "type": "object" } ``` ### Parameters Base currency for the rate table (Default: `USD`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "currency": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Currency" }, "rates": { "additionalProperties": true, "title": "Rates", "type": "object" } }, "required": [ "success" ], "title": "GetExchangeRatesOutput", "type": "object" } ``` ### Parameters Currency pair (e.g. 'BTC-USD') ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "base": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Base" }, "currency": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Currency" }, "amount": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Amount" } }, "required": [ "success" ], "title": "GetSpotPriceOutput", "type": "object" } ``` ### Response ```json { "$defs": { "PaymentMethodRow": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Type" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "currency": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Currency" }, "allow_buy": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Allow Buy" }, "allow_sell": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Allow Sell" }, "allow_deposit": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Allow Deposit" }, "allow_withdraw": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Allow Withdraw" }, "verified": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Verified" } }, "title": "PaymentMethodRow", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "payment_methods": { "items": { "$ref": "#/$defs/PaymentMethodRow" }, "title": "Payment Methods", "type": "array" }, "total": { "default": 0, "title": "Total", "type": "integer" } }, "required": [ "success" ], "title": "GetPaymentMethodsOutput", "type": "object" } ``` ## Related integrations ## Links - [Coinbase](https://www.coinbase.com) --- # CoinMarketCap Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/coinmarketcap Cryptocurrency market data, quotes, and metadata from the CoinMarketCap API {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/coinmarketcap/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} CoinMarketCap logo CoinMarketCap logo ## Overview Add **CoinMarketCap** to any ModuleX agent or workflow. Cryptocurrency market data, quotes, and metadata from the CoinMarketCap REST API (`pro-api.coinmarketcap.com`). **Categories**: Finance & Payments · Finance · Cryptocurrency · Market Data · **Auth**: API Key · **Actions**: 4 ## Authentication ### API Key Authentication Authenticate using your CoinMarketCap API key Go to https://coinmarketcap.com/api/ and sign up for an account Navigate to your API dashboard Copy your API key Paste the API key below #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | CoinMarketCap API Key | Your CoinMarketCap API key from the developer dashboard | Yes | `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` | ## Available Actions ### Parameters One or more comma-separated CoinMarketCap cryptocurrency IDs. Example: 1,2,1027 When true, invalid lookups will be skipped allowing valid cryptocurrencies to still be returned (Default: `false`) Comma-separated supplemental data fields to return. Valid values: urls, logo, description, tags, platform, date_added, notice, status ### Response ```json { "$defs": { "CryptocurrencyMetadata": { "additionalProperties": false, "description": "Metadata for a single cryptocurrency.", "properties": { "id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "symbol": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Symbol" }, "slug": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Slug" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "logo": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Logo" }, "date_added": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Date Added" }, "category": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Category" } }, "title": "CryptocurrencyMetadata", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "additionalProperties": { "$ref": "#/$defs/CryptocurrencyMetadata" }, "title": "Data", "type": "object" } }, "required": [ "success" ], "title": "GetCryptocurrencyMetadataOutput", "type": "object" } ``` ### Parameters Filter by status. Valid values: active, inactive, untracked. Comma-separated for multiple. Offset the start (1-based index) of the paginated list of items to return Number of results to return. Default 100 (Default: `100`) Sort field. Valid values: cmc_rank, id Comma-separated list of cryptocurrency symbols to return IDs for. If passed, other options are ignored. Comma-separated supplemental data fields. Valid values: platform, first_historical_data, last_historical_data, is_active, status ### Response ```json { "$defs": { "CryptocurrencyMapItem": { "additionalProperties": false, "description": "A cryptocurrency mapping entry.", "properties": { "id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "symbol": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Symbol" }, "slug": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Slug" }, "is_active": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Is Active" }, "first_historical_data": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "First Historical Data" }, "last_historical_data": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Historical Data" } }, "title": "CryptocurrencyMapItem", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "items": { "$ref": "#/$defs/CryptocurrencyMapItem" }, "title": "Data", "type": "array" } }, "required": [ "success" ], "title": "IdMapOutput", "type": "object" } ``` ### Parameters Offset the start (1-based index) of the paginated list of items to return Number of results to return Minimum 24 hour USD volume to filter results by Comma-separated list of cryptocurrency or fiat currency symbols to calculate market quotes in Comma-separated CoinMarketCap IDs to calculate market quotes in. Cannot be used with convert. Sort field. Valid values: market_cap, name, symbol, date_added, price, circulating_supply, total_supply, max_supply, num_market_pairs, volume_24h, percent_change_1h, percent_change_24h, percent_change_7d Sort direction. Valid values: asc, desc Type of cryptocurrency to include. Valid values: all, coins, tokens Comma-separated supplemental data fields. Valid values: num_market_pairs, cmc_rank, date_added, tags, platform, max_supply, circulating_supply, total_supply ### Response ```json { "$defs": { "ListingItem": { "additionalProperties": false, "description": "A cryptocurrency listing entry with market data.", "properties": { "id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "symbol": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Symbol" }, "slug": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Slug" }, "cmc_rank": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Cmc Rank" }, "circulating_supply": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Circulating Supply" }, "total_supply": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Total Supply" }, "max_supply": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Max Supply" }, "quote": { "additionalProperties": { "$ref": "#/$defs/QuoteData" }, "title": "Quote", "type": "object" } }, "title": "ListingItem", "type": "object" }, "QuoteData": { "additionalProperties": false, "description": "Quote data for a single currency conversion.", "properties": { "price": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Price" }, "volume_24h": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Volume 24H" }, "market_cap": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Market Cap" }, "percent_change_1h": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Percent Change 1H" }, "percent_change_24h": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Percent Change 24H" }, "percent_change_7d": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Percent Change 7D" }, "last_updated": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Updated" } }, "title": "QuoteData", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "items": { "$ref": "#/$defs/ListingItem" }, "title": "Data", "type": "array" } }, "required": [ "success" ], "title": "LatestListingsOutput", "type": "object" } ``` ### Parameters One or more comma-separated CoinMarketCap cryptocurrency IDs. Example: 1,2 Comma-separated list of cryptocurrency slugs. Example: bitcoin,ethereum Comma-separated cryptocurrency symbols. Example: BTC,ETH Comma-separated list of currency symbols to calculate quotes in Comma-separated CoinMarketCap IDs to calculate quotes in. Cannot be used with convert. ### Response ```json { "$defs": { "CryptocurrencyQuote": { "additionalProperties": false, "description": "Quote data for a specific cryptocurrency.", "properties": { "id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "symbol": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Symbol" }, "slug": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Slug" }, "cmc_rank": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Cmc Rank" }, "quote": { "additionalProperties": { "$ref": "#/$defs/QuoteData" }, "title": "Quote", "type": "object" } }, "title": "CryptocurrencyQuote", "type": "object" }, "QuoteData": { "additionalProperties": false, "description": "Quote data for a single currency conversion.", "properties": { "price": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Price" }, "volume_24h": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Volume 24H" }, "market_cap": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Market Cap" }, "percent_change_1h": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Percent Change 1H" }, "percent_change_24h": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Percent Change 24H" }, "percent_change_7d": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Percent Change 7D" }, "last_updated": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Updated" } }, "title": "QuoteData", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "additionalProperties": { "$ref": "#/$defs/CryptocurrencyQuote" }, "title": "Data", "type": "object" } }, "required": [ "success" ], "title": "LatestQuotesOutput", "type": "object" } ``` ## Limits & Quotas - **Basic (free) plan**: 333 calls/day, 10,000 calls/month. - **Hobbyist plan**: 10,000 calls/month. - **Standard plan and above**: higher limits per the pricing page. - Rate limiting is applied per API key. Exceeding limits returns HTTP 429. - **Error model**: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. ## Related integrations ## Links - [CoinMarketCap](https://coinmarketcap.com) --- # ConvertAPI Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/convertapi Powerful file conversion service supporting hundreds of format conversions including documents, images, spreadsheets, and web pages to PDF/JPG. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/convertapi/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} ConvertAPI logo ## Overview Add **ConvertAPI** to any ModuleX agent or workflow. File-format conversion (PDF, DOCX, JPG, etc.), base64 file conversion, web-page-to-PDF rendering, and format-discovery against the ConvertAPI v2 REST endpoints (`v2.convertapi.com`). **Categories**: Productivity & Collaboration · Utilities · Convert · Documents · **Auth**: API Key · **Actions**: 4 ## Authentication ### API Key Authentication Authenticate using your ConvertAPI Secret key #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | ConvertAPI Secret | Your ConvertAPI Secret key for authentication | Yes | `xxxxxxxxxxxxxxxxxxxxxxxx` | ## Available Actions ### Parameters URL of the file to convert Input file format (e.g. 'docx', 'pdf', 'png') Output file format (e.g. 'pdf', 'jpg', 'docx') Output filename without extension Conversion timeout in seconds (Default: `300`) ### Response ```json { "$defs": { "ConvertedFile": { "additionalProperties": false, "properties": { "filename": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Filename" }, "file_size": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "File Size" }, "file_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "File Url" }, "file_data_base64": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "File Data Base64" } }, "title": "ConvertedFile", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "conversion_cost": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Conversion Cost" }, "files": { "items": { "$ref": "#/$defs/ConvertedFile" }, "title": "Files", "type": "array" }, "format_from": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Format From" }, "format_to": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Format To" } }, "required": [ "success" ], "title": "ConvertFileOutput", "type": "object" } ``` ### Parameters Base64-encoded file content Input file format (e.g. 'docx', 'pdf') Output file format (e.g. 'pdf', 'jpg') Output filename without extension (Default: `converted`) Conversion timeout in seconds (Default: `300`) ### Response ```json { "$defs": { "ConvertedFile": { "additionalProperties": false, "properties": { "filename": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Filename" }, "file_size": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "File Size" }, "file_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "File Url" }, "file_data_base64": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "File Data Base64" } }, "title": "ConvertedFile", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "conversion_cost": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Conversion Cost" }, "files": { "items": { "$ref": "#/$defs/ConvertedFile" }, "title": "Files", "type": "array" }, "format_from": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Format From" }, "format_to": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Format To" } }, "required": [ "success" ], "title": "ConvertBase64FileOutput", "type": "object" } ``` ### Parameters Website URL to convert Output format: 'pdf' or 'jpg' (Default: `pdf`) Output filename without extension Conversion timeout in seconds (Default: `300`) Delay in seconds to let page fully load before conversion PDF page orientation ('portrait' or 'landscape') PDF page size (e.g. 'a4', 'letter', 'legal') Allow web pages to run JavaScript (Default: `true`) Block ads in the converting page (Default: `false`) Try to remove cookie consent popups (Default: `false`) ### Response ```json { "$defs": { "ConvertedFile": { "additionalProperties": false, "properties": { "filename": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Filename" }, "file_size": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "File Size" }, "file_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "File Url" }, "file_data_base64": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "File Data Base64" } }, "title": "ConvertedFile", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "conversion_cost": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Conversion Cost" }, "files": { "items": { "$ref": "#/$defs/ConvertedFile" }, "title": "Files", "type": "array" }, "source_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Source Url" }, "format_to": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Format To" } }, "required": [ "success" ], "title": "ConvertWebUrlOutput", "type": "object" } ``` ### Parameters Input format to check (e.g. 'docx', 'pdf', 'png') ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "format_from": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Format From" }, "supported_formats": { "items": { "type": "string" }, "title": "Supported Formats", "type": "array" }, "count": { "default": 0, "title": "Count", "type": "integer" } }, "required": [ "success" ], "title": "GetSupportedFormatsOutput", "type": "object" } ``` ## Limits & Quotas - Conversions cost credits; `get_supported_formats` is free. - Long-running conversions accept a per-call `timeout` (default 300s). - Failures (non-200, empty `Files`, exceptions) surface as `success=False` with `error`. ## Related integrations ## Links - [ConvertAPI](https://www.convertapi.com) --- # CrowdStrike Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/crowdstrike Integrate CrowdStrike Identity Protection into workflows to search sensors, fetch sensor details by device ID, and run sensor aggregate queries against the Falcon API. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/crowdstrike/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} CrowdStrike logo ## Overview Add **CrowdStrike** to any ModuleX agent or workflow. Query CrowdStrike Identity Protection sensors and aggregates through the Falcon API (`api.crowdstrike.com` and region-specific hosts). Search the sensor fleet with Falcon Query Language, pull detailed records by device ID, and run documented aggregate queries. **Categories**: Monitoring & Observability · Security · Identity · **Auth**: API Key · **Actions**: 3 ## Authentication ### API Key Authentication Authenticate using a CrowdStrike Falcon API client. The client secret is the credential; the client ID and cloud region are supplied per action. Sign in to the Falcon console and open Support and resources > API clients and keys. Create an API client granting it the Identity Protection scopes (Identity Protection Entities: Read, Identity Protection Sensor: Read). Copy the generated Client ID and Client Secret. Paste the Client Secret below as the API key; provide the Client ID and your cloud region (us-1, us-2, eu-1, us-gov-1, us-gov-2) as action parameters. #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | CrowdStrike Client Secret | Your CrowdStrike Falcon API client secret | Yes | `-` | ## Available Actions ### Parameters CrowdStrike Falcon API client ID CrowdStrike Falcon cloud region: one of 'us-1', 'us-2', 'eu-1', 'us-gov-1', 'us-gov-2' (Default: `us-1`) Falcon Query Language (FQL) filter for sensor search Maximum number of sensor records to return (1-200) Pagination offset for the sensor query Sort expression, e.g. 'status.asc' ### Response ```json { "$defs": { "Pagination": { "additionalProperties": false, "description": "Pagination metadata echoed by the Falcon API (``meta.pagination``).", "properties": { "limit": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Limit" }, "offset": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Offset" }, "total": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Total" } }, "title": "Pagination", "type": "object" }, "Sensor": { "additionalProperties": false, "description": "A single Identity Protection sensor/device record.\n\nThe Falcon API returns a rich device object; the most commonly used\nattributes are surfaced as typed fields and the complete upstream\nrecord is preserved verbatim in ``raw`` so nothing is lost. Field\nnames mirror the documented camelCase shape; values are read\npermissively with ``.get()`` (both snake_case and camelCase keys are\nattempted at parse time).", "properties": { "agent_version": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Agent Version" }, "cid": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Cid" }, "device_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Device Id" }, "heartbeat_time": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Heartbeat Time" }, "hostname": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Hostname" }, "idp_policy_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Idp Policy Id" }, "idp_policy_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Idp Policy Name" }, "ip_address": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Ip Address" }, "kerberos_config": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Kerberos Config" }, "ldap_config": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Ldap Config" }, "ldaps_config": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Ldaps Config" }, "machine_domain": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Machine Domain" }, "ntlm_config": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Ntlm Config" }, "os_version": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Os Version" }, "rdp_to_dc_config": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Rdp To Dc Config" }, "smb_to_dc_config": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Smb To Dc Config" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "status_causes": { "items": { "type": "string" }, "title": "Status Causes", "type": "array" }, "ti_enabled": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Ti Enabled" }, "raw": { "additionalProperties": true, "title": "Raw", "type": "object" } }, "title": "Sensor", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "sensors": { "items": { "$ref": "#/$defs/Sensor" }, "title": "Sensors", "type": "array" }, "device_ids": { "items": { "type": "string" }, "title": "Device Ids", "type": "array" }, "count": { "default": 0, "title": "Count", "type": "integer" }, "pagination": { "anyOf": [ { "$ref": "#/$defs/Pagination" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "QuerySensorsOutput", "type": "object" } ``` ### Parameters CrowdStrike Falcon API client ID CrowdStrike Falcon cloud region: one of 'us-1', 'us-2', 'eu-1', 'us-gov-1', 'us-gov-2' (Default: `us-1`) List of CrowdStrike sensor device IDs (max 5000) ### Response ```json { "$defs": { "Pagination": { "additionalProperties": false, "description": "Pagination metadata echoed by the Falcon API (``meta.pagination``).", "properties": { "limit": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Limit" }, "offset": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Offset" }, "total": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Total" } }, "title": "Pagination", "type": "object" }, "Sensor": { "additionalProperties": false, "description": "A single Identity Protection sensor/device record.\n\nThe Falcon API returns a rich device object; the most commonly used\nattributes are surfaced as typed fields and the complete upstream\nrecord is preserved verbatim in ``raw`` so nothing is lost. Field\nnames mirror the documented camelCase shape; values are read\npermissively with ``.get()`` (both snake_case and camelCase keys are\nattempted at parse time).", "properties": { "agent_version": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Agent Version" }, "cid": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Cid" }, "device_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Device Id" }, "heartbeat_time": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Heartbeat Time" }, "hostname": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Hostname" }, "idp_policy_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Idp Policy Id" }, "idp_policy_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Idp Policy Name" }, "ip_address": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Ip Address" }, "kerberos_config": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Kerberos Config" }, "ldap_config": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Ldap Config" }, "ldaps_config": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Ldaps Config" }, "machine_domain": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Machine Domain" }, "ntlm_config": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Ntlm Config" }, "os_version": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Os Version" }, "rdp_to_dc_config": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Rdp To Dc Config" }, "smb_to_dc_config": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Smb To Dc Config" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "status_causes": { "items": { "type": "string" }, "title": "Status Causes", "type": "array" }, "ti_enabled": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Ti Enabled" }, "raw": { "additionalProperties": true, "title": "Raw", "type": "object" } }, "title": "Sensor", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "sensors": { "items": { "$ref": "#/$defs/Sensor" }, "title": "Sensors", "type": "array" }, "count": { "default": 0, "title": "Count", "type": "integer" }, "pagination": { "anyOf": [ { "$ref": "#/$defs/Pagination" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "GetSensorDetailsOutput", "type": "object" } ``` ### Parameters CrowdStrike Falcon API client ID CrowdStrike Falcon cloud region: one of 'us-1', 'us-2', 'eu-1', 'us-gov-1', 'us-gov-2' (Default: `us-1`) Aggregate query body documented by CrowdStrike (fields such as field, filter, name, size, sort, type, date_ranges, ranges, extended_bounds, sub_aggregates) ### Response ```json { "$defs": { "AggregateBucket": { "additionalProperties": false, "description": "A single bucket within an aggregate result group.", "properties": { "count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Count" }, "from_": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "From" }, "key": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Key" }, "key_as_string": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Key As String" }, "label": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Label" }, "string_from": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "String From" }, "string_to": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "String To" }, "sub_aggregates": { "items": { "additionalProperties": true, "type": "object" }, "title": "Sub Aggregates", "type": "array" }, "to": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "To" }, "value": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Value" }, "value_as_string": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Value As String" } }, "title": "AggregateBucket", "type": "object" }, "AggregateResult": { "additionalProperties": false, "description": "One aggregate result group returned by the aggregates endpoint.", "properties": { "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "buckets": { "items": { "$ref": "#/$defs/AggregateBucket" }, "title": "Buckets", "type": "array" }, "doc_count_error_upper_bound": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Doc Count Error Upper Bound" }, "sum_other_doc_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Sum Other Doc Count" } }, "title": "AggregateResult", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "aggregates": { "items": { "$ref": "#/$defs/AggregateResult" }, "title": "Aggregates", "type": "array" }, "count": { "default": 0, "title": "Count", "type": "integer" } }, "required": [ "success" ], "title": "GetSensorAggregatesOutput", "type": "object" } ``` ## Limits & Quotas - **Token lifetime**: Falcon OAuth2 access tokens expire after ~30 minutes; each action requests a fresh token, so no token caching is required. - **Cloud region**: pick the host that matches your Falcon tenant (`us-1` → `api.crowdstrike.com`, `us-2` → `api.us-2.crowdstrike.com`, `eu-1` → `api.eu-1.crowdstrike.com`, GovCloud variants for `us-gov-1`/`us-gov-2`). - **`get_sensor_details`** accepts up to 5000 device IDs per call. - **Error model**: non-2xx responses (including auth failures) and timeouts are caught and returned as `success=False` + `error` rather than raising. Plan for retries on the agent side based on the error string. ## Related integrations ## Links - [CrowdStrike](https://www.crowdstrike.com) --- # Crunchbase Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/crunchbase Access Crunchbase company and organization data for business intelligence and research {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/crunchbase/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Crunchbase logo ## Overview Add **Crunchbase** to any ModuleX agent or workflow. Access Crunchbase company and organization data for business intelligence and research via the Crunchbase REST API (`api.crunchbase.com/v4/data`). **Categories**: Sales · Business Intelligence · Data · Research · **Auth**: API Key · **Actions**: 2 ## Authentication ### API Key Authentication Authenticate using your Crunchbase user key Go to https://data.crunchbase.com and sign in Navigate to your account settings or API key management Copy your user key Paste the key below #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Crunchbase User Key | Your Crunchbase API user key from data.crunchbase.com | Yes | `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | ## Available Actions ### Parameters UUID or permalink of the organization to retrieve ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "GetOrganizationOutput", "type": "object" } ``` ### Parameters Fields to include on the resulting entity (e.g. identifier, short_description, name, website_url) Array of query predicate objects for filtering organizations. Each object should have operator_id, type, values, and field_id keys. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "entities": { "items": { "additionalProperties": true, "type": "object" }, "title": "Entities", "type": "array" }, "total_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Total Count" } }, "required": [ "success" ], "title": "SearchOrganizationsOutput", "type": "object" } ``` ## Limits & Quotas - Rate limits depend on your Crunchbase plan tier (Basic, Pro, Enterprise). - Enterprise plans typically allow higher request volumes; check your account dashboard for current limits. - Error model: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. ## Related integrations ## Links - [Crunchbase](https://www.crunchbase.com) --- # Customer.io Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/customerio Marketing automation platform for personalized customer experiences through email, SMS, and push notifications {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/customerio/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Customer.io logo ## Overview Add **Customer.io** to any ModuleX agent or workflow. Customer profile sync, event tracking, and manual-segment membership management against the Customer.io Track API v1. **Categories**: Marketing & Advertising · Marketing & Email · Social Media · Customer Support · **Auth**: API Key · **Actions**: 3 ## Authentication ### API Key Authentication Authenticate using your Customer.io Site ID and API Key #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Site ID | Your Customer.io Site ID (Settings > Workspace Settings > API Credentials) | No | `xxxxxxxx-xxxx-xxxx` | | API Key | Your Customer.io Tracking API Key (Settings > Workspace Settings > API Credentials) | Yes | `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | | Region | Customer.io data region: 'US' (default, track.customer.io) or 'EU' (track-eu.customer.io). Must match the workspace's region. | No | `US` | ## Available Actions ### Parameters The unique identifier for the customer The customer's email address UNIX timestamp from when the user was created in your system Custom attributes (name, plan, company, etc.) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" }, "customer_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Customer Id" }, "email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email" } }, "required": [ "success" ], "title": "CreateOrUpdateCustomerOutput", "type": "object" } ``` ### Parameters The unique identifier for the customer The name of the event to track Event type modifier (set to 'page' for page views) Custom data to include with the event ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" }, "customer_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Customer Id" }, "event_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Event Name" } }, "required": [ "success" ], "title": "SendEventOutput", "type": "object" } ``` ### Parameters The segment ID List of customer IDs (max 1000) The type of IDs: 'id', 'email', or 'cio_id' (Default: `id`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" }, "segment_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Segment Id" }, "customer_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Customer Count" }, "id_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id Type" } }, "required": [ "success" ], "title": "AddCustomersToSegmentOutput", "type": "object" } ``` ## Limits & Quotas - Manual segment add: max 1000 customer IDs per call (enforced client-side; the tool returns `success=False` if the cap is exceeded). - All actions are idempotent on the Customer.io side. ## Related integrations ## Links - [Customer.io](https://customer.io) --- # Databricks Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/databricks Manage Databricks jobs, runs, SQL warehouses, and vector search indexes. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/databricks/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Databricks logo ## Overview Add **Databricks** to any ModuleX agent or workflow. Manage Databricks jobs, runs, SQL warehouses, and vector search indexes via the Databricks REST API (`{domain}.cloud.databricks.com`). **Categories**: Analytics & Data · Data Engineering · Analytics · Machine Learning · **Auth**: Databricks Personal Access Token · **Actions**: 41 ## Authentication ### Databricks Personal Access Token Authenticate using your Databricks workspace domain and a personal access token. #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Workspace Domain | Your Databricks workspace subdomain (the part before .cloud.databricks.com). | Yes | `my-workspace` | | Access Token | A Databricks personal access token. | Yes | `dapi0123456789abcdef0123456789abcdef` | ## Available Actions ### Parameters Identifier of a job. If omitted with all_queued_runs=true, cancels all queued runs in the workspace. Cancel all queued runs. If no job_id is provided, all queued runs in the workspace are canceled. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" } }, "required": [ "success" ], "title": "CancelAllRunsOutput", "type": "object" } ``` ### Parameters Identifier of the run to cancel. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" } }, "required": [ "success" ], "title": "CancelRunOutput", "type": "object" } ``` ### Parameters The name of the vector search endpoint to create. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "CreateEndpointOutput", "type": "object" } ``` ### Parameters JSON string of task specifications for this job. An optional name for the job. JSON string of tags associated with the job. JSON string of job cluster specifications. JSON string of email notification settings. JSON string of webhook notification settings. Timeout in seconds applied to each run of this job. JSON string of schedule definition with quartz_cron_expression and timezone_id. Maximum number of concurrent runs for the job. JSON string specifying a remote Git repository for task source code. JSON string of permissions to set on the job. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "job_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Job Id" } }, "required": [ "success" ], "title": "CreateJobOutput", "type": "object" } ``` ### Parameters A human-readable name for the warehouse. Size of the cluster: 2X-Small, X-Small, Small, Medium, Large, X-Large, 2X-Large, 3X-Large, 4X-Large. Minutes of inactivity before auto-stop. 0 disables. Must be 0 or >= 10. (Default: `10`) Minimum number of clusters to maintain. (Default: `1`) Maximum number of clusters for autoscaling. (Default: `1`) Whether to use Photon optimized clusters. Whether to use serverless compute. Warehouse type: TYPE_UNSPECIFIED, CLASSIC, or PRO. Spot instance policy: POLICY_UNSPECIFIED, COST_OPTIMIZED, RELIABILITY_OPTIMIZED. JSON string of channel details. JSON string of custom key-value tags. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "CreateSqlWarehouseOutput", "type": "object" } ``` ### Parameters A unique name for the index (e.g., main_catalog.docs.en_wiki_index). The name of the vector search endpoint. Type of index: DELTA_SYNC or DIRECT_ACCESS. The primary key column for the index. The Delta table backing the index (required for DELTA_SYNC). JSON array of column names to sync from the source Delta table. JSON array of embedding source column configs. The schema of the index in JSON format (required for DIRECT_ACCESS). Sent on the wire as `schema_json`. Pipeline type: TRIGGERED or CONTINUOUS. (Default: `TRIGGERED`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "CreateVectorSearchIndexOutput", "type": "object" } ``` ### Parameters The name of the vector search endpoint to delete. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" } }, "required": [ "success" ], "title": "DeleteEndpointOutput", "type": "object" } ``` ### Parameters Identifier of the job to delete. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" } }, "required": [ "success" ], "title": "DeleteJobOutput", "type": "object" } ``` ### Parameters Identifier of the run to delete. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" } }, "required": [ "success" ], "title": "DeleteRunOutput", "type": "object" } ``` ### Parameters The ID of the SQL Warehouse to delete. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" } }, "required": [ "success" ], "title": "DeleteSqlWarehouseOutput", "type": "object" } ``` ### Parameters The name of the vector search index to delete. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" } }, "required": [ "success" ], "title": "DeleteVectorSearchIndexOutput", "type": "object" } ``` ### Parameters The name of the vector search index. JSON array of primary key values to delete. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "DeleteVectorSearchIndexDataOutput", "type": "object" } ``` ### Parameters The ID of the SQL Warehouse to edit. New logical name for the warehouse. Size of clusters: 2X-Small, X-Small, Small, Medium, Large, X-Large, 2X-Large, 3X-Large, 4X-Large. Minutes of inactivity before auto-stop. 0 disables. Must be 0 or >= 10. Minimum number of clusters. Maximum number of clusters for autoscaling. Whether to use Photon optimized clusters. Whether to use serverless compute. Warehouse type: TYPE_UNSPECIFIED, CLASSIC, or PRO. Spot instance policy: POLICY_UNSPECIFIED, COST_OPTIMIZED, RELIABILITY_OPTIMIZED. JSON string of custom key-value tags. JSON string of channel details. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "EditSqlWarehouseOutput", "type": "object" } ``` ### Parameters Identifier of the run to export. Which views to export: CODE, DASHBOARDS, or ALL. Defaults to CODE. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "ExportRunOutput", "type": "object" } ``` ### Parameters The name of the vector search endpoint. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "GetEndpointOutput", "type": "object" } ``` ### Parameters Identifier of the job. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "GetJobOutput", "type": "object" } ``` ### Parameters Identifier of the job. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "GetJobPermissionsOutput", "type": "object" } ``` ### Parameters Identifier of the run. Whether to include the repair history in the response. Whether to include resolved parameter values in the response. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "GetRunOutput", "type": "object" } ``` ### Parameters Identifier of the run. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "GetRunOutputOutput", "type": "object" } ``` ### Parameters The ID of the SQL Warehouse. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "GetSqlWarehouseOutput", "type": "object" } ``` ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "GetSqlWarehouseConfigOutput", "type": "object" } ``` ### Parameters The ID of the SQL Warehouse. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "GetSqlWarehousePermissionsOutput", "type": "object" } ``` ### Parameters The name of the vector search index. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "GetVectorSearchIndexOutput", "type": "object" } ``` ### Parameters Maximum number of endpoints to return. (Default: `100`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "endpoints": { "items": { "additionalProperties": true, "type": "object" }, "title": "Endpoints", "type": "array" } }, "required": [ "success" ], "title": "ListEndpointsOutput", "type": "object" } ``` ### Parameters Whether to include task and cluster details in the response. Optional name to filter on. Maximum number of API page requests to make (1-10). ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "jobs": { "items": { "additionalProperties": true, "type": "object" }, "title": "Jobs", "type": "array" } }, "required": [ "success" ], "title": "ListJobsOutput", "type": "object" } ``` ### Parameters Identifier of a job to filter runs. Set to true to return only active runs. Maximum number of runs to return. (Default: `100`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "runs": { "items": { "additionalProperties": true, "type": "object" }, "title": "Runs", "type": "array" } }, "required": [ "success" ], "title": "ListRunsOutput", "type": "object" } ``` ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "warehouses": { "items": { "additionalProperties": true, "type": "object" }, "title": "Warehouses", "type": "array" } }, "required": [ "success" ], "title": "ListSqlWarehousesOutput", "type": "object" } ``` ### Parameters The name of the vector search endpoint. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "indexes": { "items": { "additionalProperties": true, "type": "object" }, "title": "Indexes", "type": "array" } }, "required": [ "success" ], "title": "ListVectorSearchIndexesOutput", "type": "object" } ``` ### Parameters The name of the vector search index. JSON array of column names to include in the response. Free-text query for semantic search. JSON array of floats representing the embedding vector. JSON string representing query filters. Number of results to return. (Default: `10`) Whether to include embedding vectors in the results. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "QueryVectorSearchIndexOutput", "type": "object" } ``` ### Parameters Identifier of the run to repair. JSON array of task keys to repair. If true, repair all failed tasks. Controls whether the pipeline should perform a full refresh. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "repair_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Repair Id" } }, "required": [ "success" ], "title": "RepairRunOutput", "type": "object" } ``` ### Parameters Identifier of the job. JSON string of the new settings that fully replaces all existing job settings. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" } }, "required": [ "success" ], "title": "ResetJobOutput", "type": "object" } ``` ### Parameters Identifier of the job to run. JSON array of parameters for jobs with JAR tasks. JSON object of key-value pairs for notebook tasks. JSON array of parameters for Python tasks. JSON array of parameters for spark submit tasks. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "run_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Run Id" }, "number_in_job": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Number In Job" } }, "required": [ "success" ], "title": "RunJobNowOutput", "type": "object" } ``` ### Parameters The name of the vector search index. Primary key of the last entry returned in the previous scan. Leave empty to start from the beginning. Number of results to return. (Default: `10`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "ScanVectorSearchIndexOutput", "type": "object" } ``` ### Parameters Identifier of the job. JSON string of permission objects with user_name/group_name/service_principal_name and permission_level. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "SetJobPermissionsOutput", "type": "object" } ``` ### Parameters Instance profile ARN for IAM role (AWS). Service account email for GCP workspaces. Security policy: NONE or DATA_ACCESS_CONTROL. JSON string of channel details. JSON array of enabled warehouse type objects. JSON array of general config key/value pairs. JSON array of global parameter key/value pairs. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "SetSqlWarehouseConfigOutput", "type": "object" } ``` ### Parameters The ID of the SQL Warehouse. JSON string of access control entries with user_name/group_name/service_principal_name and permission_level. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "SetSqlWarehousePermissionsOutput", "type": "object" } ``` ### Parameters The ID of the SQL Warehouse to start. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" } }, "required": [ "success" ], "title": "StartSqlWarehouseOutput", "type": "object" } ``` ### Parameters The ID of the SQL Warehouse to stop. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" } }, "required": [ "success" ], "title": "StopSqlWarehouseOutput", "type": "object" } ``` ### Parameters The name of the vector search index to synchronize. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "SyncVectorSearchIndexOutput", "type": "object" } ``` ### Parameters Identifier of the job to update. JSON string of updated job settings (only fields to change). JSON array of field paths to remove from the job settings. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" } }, "required": [ "success" ], "title": "UpdateJobOutput", "type": "object" } ``` ### Parameters The name of the vector search index. JSON array of row objects to upsert. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "UpsertVectorSearchIndexDataOutput", "type": "object" } ``` ## Limits & Quotas - Databricks API rate limits vary by endpoint and workspace tier. Consult your workspace admin for specific limits. - Jobs API: typically 30 requests/second per workspace. - Vector Search API: limits depend on endpoint configuration and provisioned capacity. - SQL Warehouses API: standard REST API limits apply. - Error model: non-2xx responses are caught and returned as `success=False` + `error` rather than raising. ## Related integrations ## Links - [Databricks](https://www.databricks.com) --- # Datadog Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/datadog Infrastructure monitoring, log management, and application performance platform {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/datadog/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Datadog logo ## Overview Add **Datadog** to any ModuleX agent or workflow. Infrastructure monitoring, log management, and application performance platform via the Datadog REST API (`api.{region}/api`). **Categories**: Monitoring & Observability · Developer Tools & Infrastructure · **Auth**: Datadog API Keys · **Actions**: 11 ## Authentication ### Datadog API Keys Authenticate using your Datadog API key and Application key Go to https://app.datadoghq.com/organization-settings/api-keys and sign in Copy or create a new API key Go to https://app.datadoghq.com/organization-settings/application-keys Copy or create a new Application key Paste both keys below #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | API Key | Your Datadog API key from Organization Settings > API Keys | Yes | `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | | Application Key | Your Datadog Application key from Organization Settings > Application Keys | Yes | `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | ## Available Actions ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "region": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Region" }, "label": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Label" }, "api_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Api Url" } }, "required": [ "success" ], "title": "GetAccountInfoOutput", "type": "object" } ``` ### Parameters The regional site for the Datadog account (e.g. datadoghq.com, us3.datadoghq.com, us5.datadoghq.com, datadoghq.eu, ddog-gov.com) Metric query string (e.g. avg:system.cpu.user{*} or sum:my.metric{env:prod} by {host}) Start of the query window as POSIX timestamp in seconds End of the query window as POSIX timestamp in seconds ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "series": { "items": { "additionalProperties": true, "type": "object" }, "title": "Series", "type": "array" }, "from_date": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "From Date" }, "to_date": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "To Date" }, "query": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Query" } }, "required": [ "success" ], "title": "GetMetricDataOutput", "type": "object" } ``` ### Parameters The regional site for the Datadog account (e.g. datadoghq.com, us3.datadoghq.com, us5.datadoghq.com, datadoghq.eu, ddog-gov.com) The name of the timeseries metric Points as a JSON object where keys are Unix timestamps (seconds) and values are numeric (e.g. {"1640995200": 1.0}) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "errors": { "items": { "type": "string" }, "title": "Errors", "type": "array" } }, "required": [ "success" ], "title": "PostMetricDataOutput", "type": "object" } ``` ### Parameters The regional site for the Datadog account (e.g. datadoghq.com, us3.datadoghq.com, us5.datadoghq.com, datadoghq.eu, ddog-gov.com) If true, only return dashboards that are shared Maximum number of dashboards to return Offset for pagination ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "dashboards": { "items": { "additionalProperties": true, "type": "object" }, "title": "Dashboards", "type": "array" } }, "required": [ "success" ], "title": "SearchDashboardsOutput", "type": "object" } ``` ### Parameters The regional site for the Datadog account (e.g. datadoghq.com, us3.datadoghq.com, us5.datadoghq.com, datadoghq.eu, ddog-gov.com) POSIX timestamp (seconds) for the start of the query window; defaults to 24 hours ago POSIX timestamp (seconds) for the end of the query window; defaults to now Filter by event priority: normal or low Comma-separated list of sources to filter events (e.g. nagios,hudson) Comma-separated list of tags to filter events (e.g. env:prod,role:db) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "events": { "items": { "additionalProperties": true, "type": "object" }, "title": "Events", "type": "array" } }, "required": [ "success" ], "title": "SearchEventsOutput", "type": "object" } ``` ### Parameters The regional site for the Datadog account (e.g. datadoghq.com, us3.datadoghq.com, us5.datadoghq.com, datadoghq.eu, ddog-gov.com) Filter hosts by name, alias, or tag (e.g. env:production or host:web-01) Field to sort hosts by: status, apps, cpu, iowait, or load Direction of sort: asc or desc Number of hosts to return (max 1000) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "host_list": { "items": { "additionalProperties": true, "type": "object" }, "title": "Host List", "type": "array" }, "total_matching": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Total Matching" } }, "required": [ "success" ], "title": "SearchHostsOutput", "type": "object" } ``` ### Parameters The regional site for the Datadog account (e.g. datadoghq.com, us3.datadoghq.com, us5.datadoghq.com, datadoghq.eu, ddog-gov.com) Search query to filter incidents using field:value syntax (e.g. state:active or severity:SEV-1) Number of incidents per page (default 10) Offset for pagination ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "incidents": { "items": { "additionalProperties": true, "type": "object" }, "title": "Incidents", "type": "array" } }, "required": [ "success" ], "title": "SearchIncidentsOutput", "type": "object" } ``` ### Parameters The regional site for the Datadog account (e.g. datadoghq.com, us3.datadoghq.com, us5.datadoghq.com, datadoghq.eu, ddog-gov.com) Search query following log search syntax (e.g. service:web-app status:error) (Default: `*`) Minimum timestamp for logs; supports date math (now-15m), ISO-8601, or epoch ms; defaults to 15 minutes ago Maximum timestamp for logs; supports date math (now), ISO-8601, or epoch ms; defaults to now List of log index names to search (defaults to all indexes); element type: string Maximum number of logs to return per page (default 10, max 1000) Sort order for results: -timestamp (newest first) or timestamp (oldest first) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "logs": { "items": { "additionalProperties": true, "type": "object" }, "title": "Logs", "type": "array" } }, "required": [ "success" ], "title": "SearchLogsOutput", "type": "object" } ``` ### Parameters The regional site for the Datadog account (e.g. datadoghq.com, us3.datadoghq.com, us5.datadoghq.com, datadoghq.eu, ddog-gov.com) Filter metrics by host name ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "metrics": { "items": { "type": "string" }, "title": "Metrics", "type": "array" } }, "required": [ "success" ], "title": "SearchMetricsOutput", "type": "object" } ``` ### Parameters The regional site for the Datadog account (e.g. datadoghq.com, us3.datadoghq.com, us5.datadoghq.com, datadoghq.eu, ddog-gov.com) Filter monitors by name, tag, or other attributes (e.g. tag:env:production or type:metric) Comma-separated list of tags to filter monitors (e.g. env:prod,team:backend) Page number to return (0-indexed) Number of monitors per page (default 100) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "monitors": { "items": { "additionalProperties": true, "type": "object" }, "title": "Monitors", "type": "array" } }, "required": [ "success" ], "title": "SearchMonitorsOutput", "type": "object" } ``` ### Parameters The regional site for the Datadog account (e.g. datadoghq.com, us3.datadoghq.com, us5.datadoghq.com, datadoghq.eu, ddog-gov.com) Number of services per page (default 10) Page number (0-indexed) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "services": { "items": { "additionalProperties": true, "type": "object" }, "title": "Services", "type": "array" } }, "required": [ "success" ], "title": "SearchServicesOutput", "type": "object" } ``` ## Limits & Quotas - **Rate limits**: Datadog enforces per-endpoint rate limits; default is 300 requests/minute for most endpoints, 60/min for logs search, and 120/hour for metric submission. - **Pagination**: Most list endpoints support pagination via `count`/`start` or `page`/`page_size` parameters. - **Regions**: The API base URL varies by account region. Use `get_account_info` to auto-detect the correct region before calling other tools. - **Error model**: Non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. ## Related integrations ## Links - [Datadog](https://www.datadoghq.com) --- # Daytona Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/daytona Run AI-generated code in secure, isolated cloud sandboxes. Create and manage sandboxes, execute shell commands, run Python, JavaScript, or TypeScript code, transfer files, and clone Git repositories. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/daytona/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Daytona logo ## Overview Add **Daytona** to any ModuleX agent or workflow. Run AI-generated code and shell commands in secure, isolated cloud sandboxes via the Daytona REST API (`app.daytona.io`) and per-sandbox toolbox API (`proxy.app.daytona.io/toolbox`). Create and manage sandboxes, execute commands, run Python/JavaScript/TypeScript, transfer files, and clone Git repositories. **Categories**: Developer Tools & Infrastructure · Cloud · Automation · **Auth**: API Key · **Actions**: 12 ## Authentication ### API Key Authentication Authenticate using your Daytona API key Go to https://app.daytona.io and sign up or log in Open the dashboard and navigate to the 'Keys' (API Keys) section Create a new API key and copy it Paste the API key below #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Daytona API Key | Your Daytona API key from app.daytona.io | Yes | `-` | ## Available Actions ### Parameters ID or name of the snapshot to create the sandbox from (uses default if empty) Name for the sandbox (defaults to the sandbox ID) Region where the sandbox will be created (e.g., us, eu) User associated with the sandbox Environment variables to set in the sandbox as key-value pairs Labels to attach to the sandbox as key-value pairs CPU cores to allocate to the sandbox Memory to allocate to the sandbox in GB Disk space to allocate to the sandbox in GB Auto-stop interval in minutes (0 disables auto-stop) Auto-archive interval in minutes (0 uses the maximum interval) Auto-delete interval in minutes (negative disables, 0 deletes immediately on stop) Whether the sandbox HTTP preview is publicly accessible ### Response ```json { "$defs": { "SandboxSummary": { "additionalProperties": false, "description": "Normalized summary of a Daytona sandbox.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "State" }, "snapshot": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Snapshot" }, "target": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Target" }, "cpu": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Cpu" }, "gpu": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Gpu" }, "memory": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Memory" }, "disk": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Disk" }, "labels": { "additionalProperties": true, "title": "Labels", "type": "object" }, "public": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Public" }, "error_reason": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error Reason" }, "auto_stop_interval": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Auto Stop Interval" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "updated_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated At" } }, "title": "SandboxSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "sandbox": { "anyOf": [ { "$ref": "#/$defs/SandboxSummary" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "CreateSandboxOutput", "type": "object" } ``` ### Parameters ID of the sandbox to run the code in Code to run Language of the code: python, javascript, or typescript Environment variables to set for the run as key-value pairs Timeout in seconds (defaults to 10 seconds) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "exit_code": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Exit Code" }, "result": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Result" }, "artifacts": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Artifacts" } }, "required": [ "success" ], "title": "RunCodeOutput", "type": "object" } ``` ### Parameters ID of the sandbox to execute the command in Shell command to execute Working directory for the command (defaults to the sandbox working directory) Environment variables to set for the command as key-value pairs Timeout in seconds (defaults to 10 seconds) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "exit_code": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Exit Code" }, "result": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "ExecuteCommandOutput", "type": "object" } ``` ### Parameters ID of the sandbox to upload the file to Destination path in the sandbox (a trailing slash uploads into that directory using the file name) Base64-encoded content of the file to upload Optional file name override ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "uploaded_path": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Uploaded Path" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "size": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Size" } }, "required": [ "success" ], "title": "UploadFileOutput", "type": "object" } ``` ### Parameters ID of the sandbox to download the file from Path of the file in the sandbox ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "mime_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Mime Type" }, "size": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Size" }, "content_base64": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Content Base64" } }, "required": [ "success" ], "title": "DownloadFileOutput", "type": "object" } ``` ### Parameters ID of the sandbox to list files in Directory path to list (defaults to the sandbox working directory) ### Response ```json { "$defs": { "FileInfo": { "additionalProperties": false, "description": "A single entry returned by ``list_files``.", "properties": { "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "is_dir": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Dir" }, "size": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Size" }, "mode": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Mode" }, "permissions": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Permissions" }, "owner": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Owner" }, "group": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Group" }, "modified_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Modified At" } }, "title": "FileInfo", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "files": { "items": { "$ref": "#/$defs/FileInfo" }, "title": "Files", "type": "array" } }, "required": [ "success" ], "title": "ListFilesOutput", "type": "object" } ``` ### Parameters ID of the sandbox to clone the repository into URL of the Git repository to clone Path in the sandbox to clone the repository into Branch to clone (defaults to the default branch) Specific commit to check out after cloning Username for authenticating to private repositories Password or personal access token for private repositories ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "repo_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Repo Url" }, "clone_path": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Clone Path" } }, "required": [ "success" ], "title": "GitCloneOutput", "type": "object" } ``` ### Parameters Maximum number of sandboxes to return (1-200) Filter sandboxes by name prefix (case-insensitive) Filter sandboxes by labels as key-value pairs Pagination cursor from a previous response ### Response ```json { "$defs": { "SandboxSummary": { "additionalProperties": false, "description": "Normalized summary of a Daytona sandbox.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "State" }, "snapshot": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Snapshot" }, "target": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Target" }, "cpu": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Cpu" }, "gpu": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Gpu" }, "memory": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Memory" }, "disk": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Disk" }, "labels": { "additionalProperties": true, "title": "Labels", "type": "object" }, "public": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Public" }, "error_reason": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error Reason" }, "auto_stop_interval": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Auto Stop Interval" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "updated_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated At" } }, "title": "SandboxSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "sandboxes": { "items": { "$ref": "#/$defs/SandboxSummary" }, "title": "Sandboxes", "type": "array" }, "next_cursor": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Next Cursor" } }, "required": [ "success" ], "title": "ListSandboxesOutput", "type": "object" } ``` ### Parameters ID or name of the sandbox ### Response ```json { "$defs": { "SandboxSummary": { "additionalProperties": false, "description": "Normalized summary of a Daytona sandbox.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "State" }, "snapshot": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Snapshot" }, "target": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Target" }, "cpu": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Cpu" }, "gpu": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Gpu" }, "memory": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Memory" }, "disk": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Disk" }, "labels": { "additionalProperties": true, "title": "Labels", "type": "object" }, "public": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Public" }, "error_reason": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error Reason" }, "auto_stop_interval": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Auto Stop Interval" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "updated_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated At" } }, "title": "SandboxSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "sandbox": { "anyOf": [ { "$ref": "#/$defs/SandboxSummary" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "GetSandboxOutput", "type": "object" } ``` ### Parameters ID or name of the sandbox ### Response ```json { "$defs": { "SandboxSummary": { "additionalProperties": false, "description": "Normalized summary of a Daytona sandbox.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "State" }, "snapshot": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Snapshot" }, "target": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Target" }, "cpu": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Cpu" }, "gpu": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Gpu" }, "memory": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Memory" }, "disk": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Disk" }, "labels": { "additionalProperties": true, "title": "Labels", "type": "object" }, "public": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Public" }, "error_reason": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error Reason" }, "auto_stop_interval": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Auto Stop Interval" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "updated_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated At" } }, "title": "SandboxSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "sandbox": { "anyOf": [ { "$ref": "#/$defs/SandboxSummary" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "StartSandboxOutput", "type": "object" } ``` ### Parameters ID or name of the sandbox ### Response ```json { "$defs": { "SandboxSummary": { "additionalProperties": false, "description": "Normalized summary of a Daytona sandbox.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "State" }, "snapshot": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Snapshot" }, "target": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Target" }, "cpu": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Cpu" }, "gpu": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Gpu" }, "memory": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Memory" }, "disk": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Disk" }, "labels": { "additionalProperties": true, "title": "Labels", "type": "object" }, "public": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Public" }, "error_reason": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error Reason" }, "auto_stop_interval": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Auto Stop Interval" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "updated_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated At" } }, "title": "SandboxSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "sandbox": { "anyOf": [ { "$ref": "#/$defs/SandboxSummary" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "StopSandboxOutput", "type": "object" } ``` ### Parameters ID or name of the sandbox ### Response ```json { "$defs": { "SandboxSummary": { "additionalProperties": false, "description": "Normalized summary of a Daytona sandbox.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "State" }, "snapshot": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Snapshot" }, "target": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Target" }, "cpu": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Cpu" }, "gpu": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Gpu" }, "memory": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Memory" }, "disk": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Disk" }, "labels": { "additionalProperties": true, "title": "Labels", "type": "object" }, "public": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Public" }, "error_reason": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error Reason" }, "auto_stop_interval": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Auto Stop Interval" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "updated_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated At" } }, "title": "SandboxSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "sandbox": { "anyOf": [ { "$ref": "#/$defs/SandboxSummary" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "DeleteSandboxOutput", "type": "object" } ``` ## Limits & Quotas - **Downloads**: capped at 100MB; larger files return `success=False` with an error. Content is returned base64-encoded inline in `content_base64`. - **Uploads**: provide file bytes via the `file_content_base64` parameter. - **Sandbox IDs**: `get_sandbox`, `start_sandbox`, `stop_sandbox`, and `delete_sandbox` accept either the sandbox ID or its name; all other sandbox-scoped operations require the ID. - **Error model**: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. ## Related integrations ## Links - [Daytona](https://www.daytona.io) --- # DigitalOcean Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/digital-ocean Cloud infrastructure platform for deploying and managing Droplets, domains, and SSH keys via the DigitalOcean API {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/digital_ocean/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} DigitalOcean logo ## Overview Add **DigitalOcean** to any ModuleX agent or workflow. Cloud infrastructure management via the DigitalOcean REST API (`api.digitalocean.com/v2`). Create and manage Droplets, domains, SSH keys, and snapshots. **Categories**: Developer Tools & Infrastructure · Cloud Infrastructure · Devops · **Auth**: OAuth2 · **Actions**: 6 ## Authentication ### OAuth2 Authentication Connect using DigitalOcean OAuth (recommended) #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Client ID | DigitalOcean OAuth App Client ID | Yes | `-` | | Client Secret | DigitalOcean OAuth App Client Secret | Yes | `-` | #### OAuth Configuration - **Authorization URL**: `https://cloud.digitalocean.com/v1/oauth/authorize` - **Token URL**: `https://cloud.digitalocean.com/v1/oauth/token` - **Scopes**: `read`, `write` ## Available Actions ### Parameters A human-readable display name for this key, used to easily identify the SSH keys when they are displayed The entire public key string that will be embedded into the root user's authorized_keys file ### Response ```json { "$defs": { "SshKeyResource": { "additionalProperties": false, "description": "An SSH key on the DigitalOcean account.", "properties": { "id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Id" }, "fingerprint": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Fingerprint" }, "public_key": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Public Key" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" } }, "title": "SshKeyResource", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "ssh_key": { "anyOf": [ { "$ref": "#/$defs/SshKeyResource" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "AddSshKeyOutput", "type": "object" } ``` ### Parameters The domain name in standard domain.TLD format (e.g. example.com) An IP address to automatically create an A record pointing to the apex domain ### Response ```json { "$defs": { "DomainResource": { "additionalProperties": false, "description": "A domain registered in DigitalOcean DNS.", "properties": { "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "ttl": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Ttl" }, "zone_file": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Zone File" } }, "title": "DomainResource", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "domain": { "anyOf": [ { "$ref": "#/$defs/DomainResource" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "CreateDomainOutput", "type": "object" } ``` ### Parameters Human-readable string used as the Droplet display name Region slug where the Droplet will be deployed (e.g. nyc1, sfo1, ams3) Image ID or slug for the base image (e.g. ubuntu-22-04-x64) Size slug for the Droplet (e.g. s-1vcpu-1gb) List of Block Storage volume IDs to attach to the Droplet List of SSH key IDs or fingerprints to embed in the Droplet's root account Whether automated backups should be enabled Whether IPv6 is enabled on the Droplet Cloud-init user data or Bash script for first-boot configuration (max 64 KiB) Whether private networking is enabled for the Droplet Whether to install the DigitalOcean monitoring agent List of tag names to apply to the Droplet after creation ### Response ```json { "$defs": { "DropletResource": { "additionalProperties": false, "description": "A DigitalOcean Droplet summary.", "properties": { "id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "memory": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Memory" }, "vcpus": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Vcpus" }, "disk": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Disk" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "region": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Region" }, "image": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Image" }, "size_slug": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Size Slug" } }, "title": "DropletResource", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "droplet": { "anyOf": [ { "$ref": "#/$defs/DropletResource" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "CreateDropletOutput", "type": "object" } ``` ### Parameters The unique identifier of the Droplet to snapshot The name to give the new snapshot ### Response ```json { "$defs": { "DropletActionResource": { "additionalProperties": false, "description": "A DigitalOcean Droplet action.", "properties": { "id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Id" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Type" }, "started_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Started At" }, "completed_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Completed At" }, "resource_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Resource Id" }, "resource_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Resource Type" }, "region_slug": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Region Slug" } }, "title": "DropletActionResource", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "action": { "anyOf": [ { "$ref": "#/$defs/DropletActionResource" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "CreateSnapshotOutput", "type": "object" } ``` ### Parameters Filter Droplets by a specific tag name Which page of paginated results to return (Default: `1`) Number of items returned per page (Default: `50`) ### Response ```json { "$defs": { "DropletResource": { "additionalProperties": false, "description": "A DigitalOcean Droplet summary.", "properties": { "id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "memory": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Memory" }, "vcpus": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Vcpus" }, "disk": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Disk" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "region": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Region" }, "image": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Image" }, "size_slug": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Size Slug" } }, "title": "DropletResource", "type": "object" }, "MetaResource": { "additionalProperties": false, "description": "Pagination meta from DigitalOcean list responses.", "properties": { "total": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Total" } }, "title": "MetaResource", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "droplets": { "items": { "$ref": "#/$defs/DropletResource" }, "title": "Droplets", "type": "array" }, "meta": { "anyOf": [ { "$ref": "#/$defs/MetaResource" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "ListAllDropletsOutput", "type": "object" } ``` ### Parameters Power action to perform: power_on or power_off The unique identifier of the Droplet to power on/off ### Response ```json { "$defs": { "DropletActionResource": { "additionalProperties": false, "description": "A DigitalOcean Droplet action.", "properties": { "id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Id" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Type" }, "started_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Started At" }, "completed_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Completed At" }, "resource_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Resource Id" }, "resource_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Resource Type" }, "region_slug": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Region Slug" } }, "title": "DropletActionResource", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "action": { "anyOf": [ { "$ref": "#/$defs/DropletActionResource" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "TurnonoffDropletOutput", "type": "object" } ``` ## Limits & Quotas - **Rate limit**: 5,000 requests per hour per OAuth token (returns HTTP 429 when exceeded). - **Droplet creation**: subject to account-level Droplet limits (default 25; request increase via support). - **Error model**: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. ## Related integrations ## Links - [DigitalOcean](https://www.digitalocean.com) --- # DocuSign Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/docusign Electronic signature and agreement management via the DocuSign eSignature REST API {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/docusign/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} DocuSign logo ## Overview Add **DocuSign** to any ModuleX agent or workflow. Electronic signature and agreement management via the DocuSign eSignature REST API (`account.docusign.com` + per-account base URIs resolved at runtime). **Categories**: Productivity & Collaboration · Legal · E Signature · Document Management · **Auth**: OAuth2 · **Actions**: 12 ## Authentication ### OAuth2 Authentication Connect using DocuSign OAuth (recommended) #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Client ID | DocuSign OAuth App Integration Key (Client ID) | Yes | `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` | | Client Secret | DocuSign OAuth App Secret Key | Yes | `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | #### OAuth Configuration - **Authorization URL**: `https://account.docusign.com/oauth/auth` - **Token URL**: `https://account.docusign.com/oauth/token` - **Scopes**: `signature`, `extended` ## Available Actions ### Parameters DocuSign Account ID Document Template ID Subject line of the signature request email Email message body to the recipient. Overrides the template setting JSON array of template role objects, each with roleName, name, and email. Example: [{"roleName":"Signer 1","name":"Jane","email":"jane@example.com"}] ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "envelope_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Envelope Id" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "status_date_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status Date Time" }, "uri": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Uri" } }, "required": [ "success" ], "title": "CreateSignatureRequestOutput", "type": "object" } ``` ### Parameters DocuSign Account ID Document Template ID Subject line of the email Email message body to the recipient. Overrides the template setting JSON array of template role objects, each with roleName, name, and email ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "envelope_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Envelope Id" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "status_date_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status Date Time" }, "uri": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Uri" } }, "required": [ "success" ], "title": "CreateDraftOutput", "type": "object" } ``` ### Parameters DocuSign Account ID Full DocuSign envelope definition as a JSON string. Example: {"emailSubject":"Please sign","status":"sent","documents":[...],"recipients":{"signers":[...]}} Envelope status override. Use 'created' to save as draft or 'sent' to send immediately. Allowed values: sent, created When creating a draft from multiple templates, merge template roles and remove empty recipients ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "envelope_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Envelope Id" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "status_date_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status Date Time" }, "uri": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Uri" } }, "required": [ "success" ], "title": "CreateEnvelopeOutput", "type": "object" } ``` ### Parameters DocuSign Account ID URL of the document to send. The file will be fetched and base64-encoded for upload to DocuSign Subject line of the email Full name of the signer Email address of the signer Use 'sent' to send immediately or 'created' to save as draft. Allowed values: sent, created (Default: `sent`) Document name shown in DocuSign. Defaults to the filename from the URL File extension. Defaults to the extension from the URL, or 'pdf' if not detected Recipient ID for the signer (Default: `1`) Routing order for the signer (Default: `1`) Set this for embedded signing recipients. The same value is required when creating a recipient view URL Anchor text in the document where DocuSign places the signature tab. Example: /sn1/ (Default: `/sn1/`) Horizontal offset in pixels for the signature tab relative to the anchor text (Default: `20`) Vertical offset in pixels for the signature tab relative to the anchor text (Default: `10`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "envelope_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Envelope Id" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "status_date_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status Date Time" }, "uri": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Uri" } }, "required": [ "success" ], "title": "CreateEnvelopeFromFileOutput", "type": "object" } ``` ### Parameters DocuSign Account ID DocuSign envelope ID URL where DocuSign redirects the signer after the signing session. Must include https:// Recipient ID on the envelope Convention string describing how your app authenticated the signer. Example: none (Default: `none`) URL DocuSign pings while the signing session is active Ping interval in seconds. DocuSign supports 60 to 1200 ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" } }, "required": [ "success" ], "title": "CreateRecipientViewOutput", "type": "object" } ``` ### Parameters DocuSign Account ID DocuSign envelope ID Comma-separated additional information to include. Allowed values: custom_fields, documents, extensions, folders, recipients, tabs ### Response ```json { "$defs": { "EnvelopeSummary": { "additionalProperties": false, "properties": { "envelope_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Envelope Id" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "email_subject": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email Subject" }, "status_date_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status Date Time" }, "uri": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Uri" }, "sender_user_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Sender User Name" }, "sender_email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Sender Email" }, "created_date_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created Date Time" }, "sent_date_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Sent Date Time" }, "completed_date_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Completed Date Time" }, "voided_date_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Voided Date Time" }, "voided_reason": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Voided Reason" } }, "title": "EnvelopeSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "envelope": { "anyOf": [ { "$ref": "#/$defs/EnvelopeSummary" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "GetEnvelopeOutput", "type": "object" } ``` ### Parameters DocuSign Account ID Return envelopes changed on or after this ISO 8601 datetime, e.g. 2026-04-01T00:00:00Z. Defaults to 30 days ago Return envelopes changed on or before this ISO 8601 datetime Comma-separated envelope statuses. Allowed values: any, completed, created, declined, deleted, delivered, processing, sent, signed, timedout, voided Filter by recipient or sender email address Search text matched against envelope metadata Comma-separated folder IDs to filter by Number of envelopes per page (Default: `100`) Zero-based result offset (Default: `0`) Sort order. Allowed values: asc, desc Field to sort by. Allowed values: action_required, created, completed, sent, signer_list, status, subject, last_modified When true, follow pagination until no pages remain or max_pages is reached (Default: `false`) Maximum pages to retrieve when fetch_all is true (Default: `10`) ### Response ```json { "$defs": { "EnvelopeSummary": { "additionalProperties": false, "properties": { "envelope_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Envelope Id" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "email_subject": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email Subject" }, "status_date_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status Date Time" }, "uri": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Uri" }, "sender_user_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Sender User Name" }, "sender_email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Sender Email" }, "created_date_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created Date Time" }, "sent_date_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Sent Date Time" }, "completed_date_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Completed Date Time" }, "voided_date_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Voided Date Time" }, "voided_reason": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Voided Reason" } }, "title": "EnvelopeSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "envelopes": { "items": { "$ref": "#/$defs/EnvelopeSummary" }, "title": "Envelopes", "type": "array" }, "result_set_size": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Result Set Size" }, "total_set_size": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Total Set Size" } }, "required": [ "success" ], "title": "ListEnvelopesOutput", "type": "object" } ``` ### Parameters DocuSign Account ID DocuSign envelope ID Whether to include tab information for each document (Default: `false`) Whether to include document metadata (Default: `false`) ### Response ```json { "$defs": { "DocumentSummary": { "additionalProperties": false, "properties": { "document_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Document Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Type" }, "uri": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Uri" }, "order": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Order" }, "pages": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Pages" } }, "title": "DocumentSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "documents": { "items": { "$ref": "#/$defs/DocumentSummary" }, "title": "Documents", "type": "array" } }, "required": [ "success" ], "title": "ListDocumentsOutput", "type": "object" } ``` ### Parameters DocuSign Account ID DocuSign envelope ID Whether to include each recipient's tabs (Default: `false`) Whether to include extended recipient information (Default: `false`) Whether to include metadata (Default: `false`) ### Response ```json { "$defs": { "RecipientSummary": { "additionalProperties": false, "properties": { "recipient_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Recipient Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "routing_order": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Routing Order" }, "client_user_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Client User Id" }, "signed_date_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Signed Date Time" }, "delivered_date_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Delivered Date Time" } }, "title": "RecipientSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "signers": { "items": { "$ref": "#/$defs/RecipientSummary" }, "title": "Signers", "type": "array" }, "carbon_copies": { "items": { "$ref": "#/$defs/RecipientSummary" }, "title": "Carbon Copies", "type": "array" }, "agents": { "items": { "$ref": "#/$defs/RecipientSummary" }, "title": "Agents", "type": "array" } }, "required": [ "success" ], "title": "ListRecipientsOutput", "type": "object" } ``` ### Parameters DocuSign Account ID DocuSign envelope ID ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "envelope_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Envelope Id" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" } }, "required": [ "success" ], "title": "SendEnvelopeOutput", "type": "object" } ``` ### Parameters DocuSign Account ID DocuSign envelope ID Download type. Allowed values: combined (All Documents PDF), archive (All Documents Zip), certificate (Certificate PDF), portfolio (Portfolio PDF) Filename including extension (.pdf or .zip) for the downloaded content ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "content_base64": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Content Base64" }, "filename": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Filename" }, "content_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Content Type" } }, "required": [ "success" ], "title": "DownloadDocumentsOutput", "type": "object" } ``` ### Parameters DocuSign Account ID DocuSign envelope ID Reason the envelope is being voided. Visible in DocuSign audit history ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "envelope_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Envelope Id" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" } }, "required": [ "success" ], "title": "VoidEnvelopeOutput", "type": "object" } ``` ## Limits & Quotas - **Rate limits**: DocuSign enforces per-account rate limits; the eSignature REST API allows approximately 1,000 requests per 15 minutes for production accounts. Demo/sandbox accounts have lower limits. - **Polling intervals**: For envelope status polling, DocuSign recommends no more than once per 15 minutes. - **File size**: Maximum document size is 25 MB per document. - **Error model**: Non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. Plan for retries on the agent side based on the error string. ## Related integrations ## Links - [DocuSign](https://www.docusign.com) --- # Dropbox Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/dropbox Cloud file storage, sharing, and collaboration platform {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/dropbox/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Dropbox logo ## Overview Add **Dropbox** to any ModuleX agent or workflow. Cloud file storage, sharing, and collaboration via the Dropbox HTTP API (`api.dropboxapi.com/2`). **Categories**: Cloud Infrastructure · Productivity & Collaboration · File Storage · Cloud Storage · **Auth**: OAuth2 · **Actions**: 12 ## Authentication ### OAuth2 Authentication Connect using Dropbox OAuth (recommended) #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Client ID | Dropbox OAuth App Client ID | Yes | `xxxxxxxxxxxxxxx` | | Client Secret | Dropbox OAuth App Client Secret | Yes | `xxxxxxxxxxxxxxx` | #### OAuth Configuration - **Authorization URL**: `https://www.dropbox.com/oauth2/authorize` - **Token URL**: `https://api.dropboxapi.com/oauth2/token` - **Scopes**: `files.metadata.read`, `files.metadata.write`, `files.content.read`, `files.content.write`, `sharing.read`, `sharing.write`, `account_info.read` ## Available Actions ### Parameters The new folder name Parent folder path where the new folder will be created (empty string for root) (Default: ``) If true, Dropbox will autorename the folder if there is a conflict (Default: `true`) ### Response ```json { "$defs": { "FileMetadata": { "additionalProperties": false, "description": "Metadata for a file or folder returned by the Dropbox API.", "properties": { ".tag": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": ".Tag" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "path_display": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Path Display" }, "path_lower": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Path Lower" }, "size": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Size" }, "rev": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Rev" }, "content_hash": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Content Hash" }, "server_modified": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Server Modified" }, "client_modified": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Client Modified" }, "is_downloadable": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Downloadable" } }, "title": "FileMetadata", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "metadata": { "anyOf": [ { "$ref": "#/$defs/FileMetadata" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "CreateFolderOutput", "type": "object" } ``` ### Parameters The search query string Folder path to restrict the search to (empty string for all) (Default: ``) Sort order: relevance, last_modified_time Restrict to file status: active, deleted If true, restricts search to filenames only Maximum number of results to return (defaults to 100) ### Response ```json { "$defs": { "FileMetadata": { "additionalProperties": false, "description": "Metadata for a file or folder returned by the Dropbox API.", "properties": { ".tag": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": ".Tag" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "path_display": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Path Display" }, "path_lower": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Path Lower" }, "size": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Size" }, "rev": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Rev" }, "content_hash": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Content Hash" }, "server_modified": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Server Modified" }, "client_modified": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Client Modified" }, "is_downloadable": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Downloadable" } }, "title": "FileMetadata", "type": "object" }, "SearchMatch": { "additionalProperties": false, "description": "A single search result match.", "properties": { "match_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Match Type" }, "metadata": { "anyOf": [ { "$ref": "#/$defs/FileMetadata" }, { "type": "null" } ], "default": null } }, "title": "SearchMatch", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "matches": { "items": { "$ref": "#/$defs/SearchMatch" }, "title": "Matches", "type": "array" }, "has_more": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Has More" } }, "required": [ "success" ], "title": "SearchFileFoldersOutput", "type": "object" } ``` ### Parameters The folder path to list contents of (empty string for root) If true, list contents of all subfolders recursively (Default: `true`) If true, include files and folders that were deleted (Default: `false`) If true, include entries under mounted folders (app, shared, team) (Default: `false`) If true, include non-downloadable files like Google Docs (Default: `true`) Maximum number of entries to return (defaults to 100) ### Response ```json { "$defs": { "FileMetadata": { "additionalProperties": false, "description": "Metadata for a file or folder returned by the Dropbox API.", "properties": { ".tag": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": ".Tag" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "path_display": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Path Display" }, "path_lower": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Path Lower" }, "size": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Size" }, "rev": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Rev" }, "content_hash": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Content Hash" }, "server_modified": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Server Modified" }, "client_modified": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Client Modified" }, "is_downloadable": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Downloadable" } }, "title": "FileMetadata", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "entries": { "items": { "$ref": "#/$defs/FileMetadata" }, "title": "Entries", "type": "array" }, "has_more": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Has More" } }, "required": [ "success" ], "title": "ListFileFoldersOutput", "type": "object" } ``` ### Parameters Path of the file or folder to delete ### Response ```json { "$defs": { "FileMetadata": { "additionalProperties": false, "description": "Metadata for a file or folder returned by the Dropbox API.", "properties": { ".tag": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": ".Tag" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "path_display": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Path Display" }, "path_lower": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Path Lower" }, "size": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Size" }, "rev": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Rev" }, "content_hash": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Content Hash" }, "server_modified": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Server Modified" }, "client_modified": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Client Modified" }, "is_downloadable": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Downloadable" } }, "title": "FileMetadata", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "metadata": { "anyOf": [ { "$ref": "#/$defs/FileMetadata" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "DeleteFileFolderOutput", "type": "object" } ``` ### Parameters Path of the file or folder to move Destination folder path (file name is appended automatically) If true, autorename to avoid conflicts (Default: `false`) Allow moves that result in ownership transfer (Default: `false`) ### Response ```json { "$defs": { "FileMetadata": { "additionalProperties": false, "description": "Metadata for a file or folder returned by the Dropbox API.", "properties": { ".tag": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": ".Tag" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "path_display": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Path Display" }, "path_lower": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Path Lower" }, "size": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Size" }, "rev": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Rev" }, "content_hash": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Content Hash" }, "server_modified": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Server Modified" }, "client_modified": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Client Modified" }, "is_downloadable": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Downloadable" } }, "title": "FileMetadata", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "metadata": { "anyOf": [ { "$ref": "#/$defs/FileMetadata" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "MoveFileFolderOutput", "type": "object" } ``` ### Parameters Path of the file or folder to rename The new name (include file extension for files) If true, autorename to avoid conflicts (Default: `false`) Allow renames that result in ownership transfer (Default: `false`) ### Response ```json { "$defs": { "FileMetadata": { "additionalProperties": false, "description": "Metadata for a file or folder returned by the Dropbox API.", "properties": { ".tag": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": ".Tag" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "path_display": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Path Display" }, "path_lower": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Path Lower" }, "size": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Size" }, "rev": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Rev" }, "content_hash": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Content Hash" }, "server_modified": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Server Modified" }, "client_modified": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Client Modified" }, "is_downloadable": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Downloadable" } }, "title": "FileMetadata", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "metadata": { "anyOf": [ { "$ref": "#/$defs/FileMetadata" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "RenameFileFolderOutput", "type": "object" } ``` ### Parameters File name including extension (e.g. notes.txt) Folder path where the file will be created (empty string for root) (Default: ``) The text content of the new file ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "path_display": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Path Display" }, "size": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Size" }, "rev": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Rev" }, "content_hash": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Content Hash" } }, "required": [ "success" ], "title": "CreateTextFileOutput", "type": "object" } ``` ### Parameters File name including extension Folder path (empty string for root) (Default: ``) The text content to write or append ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "path_display": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Path Display" }, "size": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Size" }, "rev": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Rev" }, "content_hash": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Content Hash" } }, "required": [ "success" ], "title": "CreateOrAppendToTextFileOutput", "type": "object" } ``` ### Parameters Path of the file or folder to share Enable password protection on the shared link (Default: `false`) Password for the shared link (if require_password is true) Expiration timestamp in ISO 8601 format (e.g. 2024-07-18T20:00:00Z) Link audience: public, team, no_one ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "path_lower": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Path Lower" }, "link_permissions": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Link Permissions" } }, "required": [ "success" ], "title": "CreateUpdateShareLinkOutput", "type": "object" } ``` ### Parameters File or folder path to list shared links for (optional, lists all if empty) ### Response ```json { "$defs": { "SharedLinkInfo": { "additionalProperties": false, "description": "Metadata for a shared link.", "properties": { "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "path_lower": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Path Lower" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "expires": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Expires" }, "link_permissions": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Link Permissions" } }, "title": "SharedLinkInfo", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "links": { "items": { "$ref": "#/$defs/SharedLinkInfo" }, "title": "Links", "type": "array" } }, "required": [ "success" ], "title": "ListSharedLinksOutput", "type": "object" } ``` ### Parameters The URL of the shared link Password if the shared link is password-protected ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "path_lower": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Path Lower" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "link_permissions": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Link Permissions" } }, "required": [ "success" ], "title": "GetSharedLinkMetadataOutput", "type": "object" } ``` ### Parameters The file path to list revisions for Revision mode: path (default, revisions at same path) or id (revisions with same file id) Maximum number of revision entries to return ### Response ```json { "$defs": { "FileMetadata": { "additionalProperties": false, "description": "Metadata for a file or folder returned by the Dropbox API.", "properties": { ".tag": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": ".Tag" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "path_display": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Path Display" }, "path_lower": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Path Lower" }, "size": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Size" }, "rev": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Rev" }, "content_hash": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Content Hash" }, "server_modified": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Server Modified" }, "client_modified": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Client Modified" }, "is_downloadable": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Downloadable" } }, "title": "FileMetadata", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "entries": { "items": { "$ref": "#/$defs/FileMetadata" }, "title": "Entries", "type": "array" }, "is_deleted": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Deleted" } }, "required": [ "success" ], "title": "ListFileRevisionsOutput", "type": "object" } ``` ## Limits & Quotas - **Rate limits**: Dropbox applies per-app and per-user rate limits. Individual apps are limited to approximately 25,000 HTTP requests per month for free-tier apps and higher for production apps. - **List folder**: Maximum 2,000 entries per single request (pagination via cursor for more). - **File upload**: Maximum 150 MB per single upload request (content upload endpoint). - **Error model**: Non-2xx responses are caught and returned as `success=False` + `error` rather than raising. Plan for retries on 429 (rate limit) responses. ## Related integrations ## Links - [Dropbox](https://www.dropbox.com) --- # Dropcontact Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/dropcontact Verify and enrich B2B contacts. Submit a contact with their name, company, website, or LinkedIn URL and receive a verified professional email, phone number, company firmographics, and LinkedIn profile. Enrichment is asynchronous: the request is submitted and polled until the result is ready. Credits are only charged when a verified email is returned. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/dropcontact/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Dropcontact logo ## Overview Add **Dropcontact** to any ModuleX agent or workflow. GDPR-compliant B2B contact enrichment against the Dropcontact REST API (`api.dropcontact.com`). Submit a partial contact and receive a verified professional email, phone number, company firmographics, and LinkedIn profile. **Categories**: Sales · CRM · Enrichment · **Auth**: API Key · **Actions**: 1 ## Authentication ### API Key Authentication Authenticate using your Dropcontact API key Sign in to your Dropcontact account at https://app.dropcontact.com Open the 'API & Integrations' settings Copy your personal API key Paste the API key below #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Dropcontact API Key | Your Dropcontact API key (sent as the X-Access-Token header) | Yes | `-` | ## Available Actions ### Parameters Email address of the contact to enrich First name of the contact Last name of the contact Full name (alternative to first_name + last_name) Company name Company website (e.g. acme.com) French company SIREN number Phone number LinkedIn profile URL Country code (ISO 3166-1 alpha-2, e.g. "US", "FR") Include SIREN/SIRET enrichment (France only) (Default: `false`) Language for returned data (e.g. "en", "fr") ### Response ```json { "$defs": { "EmailEntry": { "additionalProperties": false, "description": "A single email address with its deliverability qualification.", "properties": { "email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email" }, "qualification": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Qualification" } }, "title": "EmailEntry", "type": "object" } }, "additionalProperties": false, "description": "Flat enriched-contact result returned by ``enrich_contact``.", "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "request_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Request Id" }, "email_found": { "default": false, "title": "Email Found", "type": "boolean" }, "email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email" }, "emails": { "items": { "$ref": "#/$defs/EmailEntry" }, "title": "Emails", "type": "array" }, "qualification": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Qualification" }, "first_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "First Name" }, "last_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Name" }, "full_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Full Name" }, "civility": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Civility" }, "phone": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Phone" }, "mobile_phone": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Mobile Phone" }, "company": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Company" }, "website": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Website" }, "company_linkedin": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Company Linkedin" }, "linkedin": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Linkedin" }, "country": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Country" }, "siren": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Siren" }, "siret": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Siret" }, "siret_address": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Siret Address" }, "siret_zip": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Siret Zip" }, "siret_city": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Siret City" }, "vat": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Vat" }, "nb_employees": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Nb Employees" }, "employee_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Employee Count" }, "naf5_code": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Naf5 Code" }, "naf5_des": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Naf5 Des" }, "industry": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Industry" }, "job": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Job" }, "job_level": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Job Level" }, "job_function": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Job Function" }, "company_turnover": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Company Turnover" }, "company_results": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Company Results" } }, "required": [ "success" ], "title": "EnrichContactOutput", "type": "object" } ``` ## Limits & Quotas - **Rate limit**: ~60 requests/second per the vendor docs. - **Async enrichment**: requests are processed asynchronously; the tool polls every 5 seconds for up to 2 minutes before giving up. - **Pay on success**: a credit is consumed only when a verified email is returned — no charge when no email is found. - **Error model**: non-2xx responses, an API error flag, timeouts, and an expired polling window are caught and returned as `success=False` + `error` rather than raising. Plan retries on the agent side based on the error string. ## Related integrations ## Links - [Dropcontact](https://www.dropcontact.com) --- # ElevenLabs Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/elevenlabs AI voice generation, transcription, sound effects, voice cloning, and conversational AI agents. Uses the `elevenlabs` SDK. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/elevenlabs/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} ElevenLabs logo ElevenLabs logo ## Overview Add **ElevenLabs** to any ModuleX agent or workflow. AI voice integration via the **synchronous** `elevenlabs` SDK. 15 actions across TTS, STT, sound effects, voice library / cloning / isolation, subscription, and Conversational-AI agents. **Categories**: AI & Machine Learning · Audio · Voice · **Auth**: API Key, ModuleX Managed Key · **Actions**: 15 ## Authentication ### API Key Authentication Authenticate using your ElevenLabs API key #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | ElevenLabs API Key | Your ElevenLabs API key | Yes | `sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | ### ModuleX Managed Key Use ModuleX's managed API keys with usage tracked against your weekly credit limit ## Available Actions ### Parameters Text to synthesize Voice ID Voice name (alternative to voice_id) TTS model ID (Default: `eleven_multilingual_v2`) Voice stability (0-1) (Default: `0.5`) Voice similarity to original (0-1) (Default: `0.75`) Style exaggeration (0-1) (Default: `0`) Speech speed (0.7-1.2) (Default: `1`) ISO 639-1 language code (Default: `en`) Audio output format (Default: `mp3_44100_128`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "audio_base64": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Audio Base64" }, "voice": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Voice" }, "model_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Model Id" }, "format": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Format" }, "text_length": { "default": 0, "title": "Text Length", "type": "integer" }, "audio_size_bytes": { "default": 0, "title": "Audio Size Bytes", "type": "integer" } }, "required": [ "success" ], "title": "TextToSpeechOutput", "type": "object" } ``` ### Parameters Base64-encoded audio (XOR with audio_url) URL to audio file ISO 639-3 language code Identify speakers (Default: `false`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "transcript": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Transcript" }, "diarized": { "default": false, "title": "Diarized", "type": "boolean" }, "language_detected": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Language Detected" } }, "required": [ "success" ], "title": "SpeechToTextOutput", "type": "object" } ``` ### Parameters Sound description (e.g. 'thunder rumbling') Duration (0.5-5 seconds) (Default: `2`) Audio output format (Default: `mp3_44100_128`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "audio_base64": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Audio Base64" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "duration_seconds": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Duration Seconds" }, "format": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Format" }, "audio_size_bytes": { "default": 0, "title": "Audio Size Bytes", "type": "integer" } }, "required": [ "success" ], "title": "TextToSoundEffectsOutput", "type": "object" } ``` ### Parameters Search term to filter voices created_at_unix or name (Default: `name`) asc or desc (Default: `desc`) ### Response ```json { "$defs": { "VoiceEntry": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "category": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Category" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "labels": { "additionalProperties": true, "title": "Labels", "type": "object" } }, "title": "VoiceEntry", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "voices": { "items": { "$ref": "#/$defs/VoiceEntry" }, "title": "Voices", "type": "array" }, "count": { "default": 0, "title": "Count", "type": "integer" } }, "required": [ "success" ], "title": "SearchVoicesOutput", "type": "object" } ``` ### Response ```json { "$defs": { "ModelEntry": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "languages": { "items": { "additionalProperties": true, "type": "object" }, "title": "Languages", "type": "array" } }, "title": "ModelEntry", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "models": { "items": { "$ref": "#/$defs/ModelEntry" }, "title": "Models", "type": "array" }, "count": { "default": 0, "title": "Count", "type": "integer" } }, "required": [ "success" ], "title": "ListModelsOutput", "type": "object" } ``` ### Parameters Voice ID ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "category": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Category" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "labels": { "additionalProperties": true, "title": "Labels", "type": "object" }, "fine_tuning_status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Fine Tuning Status" }, "settings": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Settings" } }, "required": [ "success" ], "title": "GetVoiceOutput", "type": "object" } ``` ### Parameters Voice name Base64-encoded audio samples Optional voice description ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "category": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Category" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" } }, "required": [ "success" ], "title": "VoiceCloneOutput", "type": "object" } ``` ### Parameters Base64-encoded audio Audio URL (alternative to base64) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "audio_base64": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Audio Base64" }, "original_size_bytes": { "default": 0, "title": "Original Size Bytes", "type": "integer" }, "isolated_size_bytes": { "default": 0, "title": "Isolated Size Bytes", "type": "integer" } }, "required": [ "success" ], "title": "IsolateAudioOutput", "type": "object" } ``` ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "tier": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Tier" }, "character_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Character Count" }, "character_limit": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Character Limit" }, "can_extend_character_limit": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Can Extend Character Limit" }, "allowed_to_extend_character_limit": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Allowed To Extend Character Limit" }, "next_character_count_reset_unix": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Next Character Count Reset Unix" }, "voice_limit": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Voice Limit" }, "professional_voice_limit": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Professional Voice Limit" } }, "required": [ "success" ], "title": "CheckSubscriptionOutput", "type": "object" } ``` ### Parameters Agent name Greeting message System prompt Voice ID (Default: `cgSgspJ2msm6clMCkdW9`) ISO 639-1 (Default: `en`) LLM to power the agent (Default: `gemini-2.0-flash-001`) LLM temperature (Default: `0.5`) Max LLM tokens Voice stability (Default: `0.5`) Voice similarity (Default: `0.8`) Max conversation duration (Default: `300`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "agent_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Agent Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "voice_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Voice Id" }, "language": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Language" }, "llm": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Llm" } }, "required": [ "success" ], "title": "CreateAgentOutput", "type": "object" } ``` ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "agents": { "items": { "additionalProperties": true, "type": "object" }, "title": "Agents", "type": "array" }, "count": { "default": 0, "title": "Count", "type": "integer" } }, "required": [ "success" ], "title": "ListAgentsOutput", "type": "object" } ``` ### Parameters Agent ID ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "agent_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Agent Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "voice_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Voice Id" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" } }, "required": [ "success" ], "title": "GetAgentOutput", "type": "object" } ``` ### Parameters Agent ID Display name for the KB document Source URL (XOR text / file) Raw text content Base64 file (epub / pdf / docx / txt / html) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "knowledge_base_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Knowledge Base Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "agent_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Agent Id" }, "source_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Source Type" } }, "required": [ "success" ], "title": "AddKnowledgeBaseOutput", "type": "object" } ``` ### Parameters Filter by agent ID Results per page (1-100) (Default: `30`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "conversations": { "items": { "additionalProperties": true, "type": "object" }, "title": "Conversations", "type": "array" }, "count": { "default": 0, "title": "Count", "type": "integer" } }, "required": [ "success" ], "title": "ListConversationsOutput", "type": "object" } ``` ### Parameters Conversation ID ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "conversation_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Conversation Id" }, "agent_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Agent Id" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "transcript": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Transcript" }, "message_count": { "default": 0, "title": "Message Count", "type": "integer" }, "duration_seconds": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Duration Seconds" }, "analysis_summary": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Analysis Summary" } }, "required": [ "success" ], "title": "GetConversationOutput", "type": "object" } ``` ## Related integrations ## Links - [ElevenLabs](https://elevenlabs.io) --- # Enrow Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/enrow Find verified B2B email addresses from a full name and company, or verify the deliverability of an existing email. Enrow performs deterministic verifications including catch-all emails — no additional verifier needed. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/enrow/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Enrow logo ## Overview Add **Enrow** to any ModuleX agent or workflow. Find and verify B2B email addresses with triple-verified accuracy against the Enrow REST API (`api.enrow.io`). Resolve a professional email from a full name and company, or check the deliverability of an existing address — including deterministic handling of catch-all domains. **Categories**: Sales · Enrichment · Sales Engagement · **Auth**: API Key · **Actions**: 2 ## Authentication ### API Key Authentication Authenticate using your Enrow API key Sign in to your Enrow account at https://enrow.io Open the Integrations section of your account settings Copy your API key (or create a new one) Paste the API key below #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Enrow API Key | Your Enrow API key from the Integrations section | Yes | `-` | ## Available Actions ### Parameters Full name of the person (e.g. "John Doe") Company domain (e.g. "apple.com"). Preferred over company_name. Provide company_domain or company_name. Company name (e.g. "Apple"). Used when the company domain is unavailable. Provide company_domain or company_name. ### Response ```json { "additionalProperties": false, "description": "Result of an Enrow find-email search (submit + poll).", "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email" }, "qualification": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Qualification" }, "fullname": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Fullname" }, "company_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Company Name" }, "company_domain": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Company Domain" }, "linkedin_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Linkedin Url" } }, "required": [ "success" ], "title": "FindEmailOutput", "type": "object" } ``` ### Parameters Email address to verify (e.g. "john@example.com") ### Response ```json { "additionalProperties": false, "description": "Result of an Enrow verify-email check (submit + poll).", "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email" }, "qualification": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Qualification" } }, "required": [ "success" ], "title": "VerifyEmailOutput", "type": "object" } ``` ## Limits & Quotas - **Rate limit**: roughly ~50 requests/second; keep bursts modest, especially during result polling. - **Pricing** (approx., per the Enrow Starter plan): `find_email` charges 1 credit per valid email found; `verify_email` charges 0.25 credits per verification. - **Error model**: non-2xx responses, request timeouts, and an expired polling window are caught and returned as `success=False` + `error` rather than raising. Plan retries on the agent side based on the error string. ## Related integrations ## Links - [Enrow](https://enrow.io) --- # Etsy Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/etsy Etsy marketplace listing management via the Open API v3 {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/etsy/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Etsy logo ## Overview Add **Etsy** to any ModuleX agent or workflow. Manage Etsy marketplace listings, inventory, and properties via the Etsy Open API v3 (`openapi.etsy.com/v3`). **Categories**: E-Commerce · Marketplace · **Auth**: OAuth2 · **Actions**: 6 ## Authentication ### OAuth2 Authentication Connect using Etsy OAuth2 (recommended) #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Client ID | Etsy OAuth App Client ID (API Key) | Yes | `xxxxxxxxxxxxxxxxxxxxxxxx` | | Client Secret | Etsy OAuth App Client Secret | Yes | `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | #### OAuth Configuration - **Authorization URL**: `https://www.etsy.com/oauth/connect` - **Token URL**: `https://api.etsy.com/v3/public/oauth/token` - **Scopes**: `listings_r`, `listings_w`, `listings_d`, `transactions_r`, `shops_r` ## Available Actions ### Parameters The positive non-zero number of products available for purchase The listing's title string A description of the product for sale in the listing The positive non-zero price of the product as a decimal string (e.g. '29.99') Who made the product: i_did, collective, someone_else Era when product was made: made_to_order, 2020_2023, 2010_2019, 2004_2009, before_2004, 2000_2003, 1990s, 1980s, 1970s, 1960s, 1950s, 1940s, 1930s, 1920s, 1910s, 1900s, 1800s, 1700s, before_1700 The numerical taxonomy ID of the listing When true, tags the listing as a supply product; otherwise a finished product Listing type: physical, download, both The numeric ID of the shipping profile. Required when listing type is physical. ### Response ```json { "$defs": { "ListingResource": { "additionalProperties": false, "properties": { "listing_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Listing Id" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "State" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" }, "quantity": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Quantity" }, "price": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Price" }, "who_made": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Who Made" }, "when_made": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "When Made" }, "taxonomy_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Taxonomy Id" }, "is_supply": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Supply" }, "listing_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Listing Type" }, "views": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Views" }, "num_favorers": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Num Favorers" }, "shipping_profile_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Shipping Profile Id" } }, "title": "ListingResource", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "listing": { "anyOf": [ { "$ref": "#/$defs/ListingResource" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "CreateDraftListingProductOutput", "type": "object" } ``` ### Parameters The ID of the listing to delete ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "listing_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Listing Id" } }, "required": [ "success" ], "title": "DeleteListingOutput", "type": "object" } ``` ### Parameters The ID of the listing to retrieve ### Response ```json { "$defs": { "ListingResource": { "additionalProperties": false, "properties": { "listing_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Listing Id" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "State" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" }, "quantity": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Quantity" }, "price": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Price" }, "who_made": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Who Made" }, "when_made": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "When Made" }, "taxonomy_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Taxonomy Id" }, "is_supply": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Supply" }, "listing_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Listing Type" }, "views": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Views" }, "num_favorers": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Num Favorers" }, "shipping_profile_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Shipping Profile Id" } }, "title": "ListingResource", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "listing": { "anyOf": [ { "$ref": "#/$defs/ListingResource" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "GetListingOutput", "type": "object" } ``` ### Parameters The ID of the listing whose inventory to retrieve ### Response ```json { "$defs": { "InventoryProduct": { "additionalProperties": false, "properties": { "product_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Product Id" }, "sku": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Sku" }, "property_values": { "items": { "$ref": "#/$defs/PropertyValue" }, "title": "Property Values", "type": "array" }, "offerings": { "items": { "$ref": "#/$defs/Offering" }, "title": "Offerings", "type": "array" } }, "title": "InventoryProduct", "type": "object" }, "Offering": { "additionalProperties": false, "properties": { "offering_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Offering Id" }, "price": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Price" }, "quantity": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Quantity" }, "is_enabled": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Enabled" } }, "title": "Offering", "type": "object" }, "PropertyValue": { "additionalProperties": false, "properties": { "property_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Property Id" }, "property_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Property Name" }, "value_ids": { "items": { "type": "integer" }, "title": "Value Ids", "type": "array" }, "values": { "items": { "type": "string" }, "title": "Values", "type": "array" } }, "title": "PropertyValue", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "products": { "items": { "$ref": "#/$defs/InventoryProduct" }, "title": "Products", "type": "array" }, "price_on_property": { "items": { "type": "integer" }, "title": "Price On Property", "type": "array" }, "quantity_on_property": { "items": { "type": "integer" }, "title": "Quantity On Property", "type": "array" }, "sku_on_property": { "items": { "type": "integer" }, "title": "Sku On Property", "type": "array" } }, "required": [ "success" ], "title": "GetListingInventoryOutput", "type": "object" } ``` ### Parameters The ID of the listing whose inventory to update JSON array of product objects containing sku, property_values, and offerings List of listing property ID integers for properties that change product prices List of listing property ID integers for properties that change product quantity List of listing property ID integers for properties that change the product SKU ### Response ```json { "$defs": { "InventoryProduct": { "additionalProperties": false, "properties": { "product_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Product Id" }, "sku": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Sku" }, "property_values": { "items": { "$ref": "#/$defs/PropertyValue" }, "title": "Property Values", "type": "array" }, "offerings": { "items": { "$ref": "#/$defs/Offering" }, "title": "Offerings", "type": "array" } }, "title": "InventoryProduct", "type": "object" }, "Offering": { "additionalProperties": false, "properties": { "offering_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Offering Id" }, "price": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Price" }, "quantity": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Quantity" }, "is_enabled": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Enabled" } }, "title": "Offering", "type": "object" }, "PropertyValue": { "additionalProperties": false, "properties": { "property_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Property Id" }, "property_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Property Name" }, "value_ids": { "items": { "type": "integer" }, "title": "Value Ids", "type": "array" }, "values": { "items": { "type": "string" }, "title": "Values", "type": "array" } }, "title": "PropertyValue", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "products": { "items": { "$ref": "#/$defs/InventoryProduct" }, "title": "Products", "type": "array" }, "price_on_property": { "items": { "type": "integer" }, "title": "Price On Property", "type": "array" }, "quantity_on_property": { "items": { "type": "integer" }, "title": "Quantity On Property", "type": "array" }, "sku_on_property": { "items": { "type": "integer" }, "title": "Sku On Property", "type": "array" } }, "required": [ "success" ], "title": "UpdateListingInventoryOutput", "type": "object" } ``` ### Parameters The ID of the listing The ID of the property to update Array of integer value IDs for the property Array of string value labels for the property (e.g. 'Black', 'Christmas') ### Response ```json { "$defs": { "ListingProperty": { "additionalProperties": false, "properties": { "property_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Property Id" }, "property_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Property Name" }, "scale_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Scale Id" }, "value_ids": { "items": { "type": "integer" }, "title": "Value Ids", "type": "array" }, "values": { "items": { "type": "string" }, "title": "Values", "type": "array" } }, "title": "ListingProperty", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "listing_property": { "anyOf": [ { "$ref": "#/$defs/ListingProperty" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "UpdateListingPropertyOutput", "type": "object" } ``` ## Limits & Quotas - Etsy Open API v3 rate limit: 10,000 requests per day per API key. - Burst limit: approximately 10 requests per second. - Error model: non-2xx responses raise `httpx.HTTPStatusError` (Pattern A). The agent should retry on 429/5xx. ## Related integrations ## Links - [Etsy](https://www.etsy.com) --- # Exa Search Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/exa-search AI-powered semantic search engine for intelligent web search, content extraction, similarity matching, and answer generation {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/exa/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Exa Search logo ## Overview Add **Exa Search** to any ModuleX agent or workflow. AI-powered semantic web search, content extraction, similarity matching, and answer generation against the Exa REST API (`api.exa.ai`). **Categories**: Web Search & Scraping · Data · Search · **Auth**: API Key, ModuleX Managed Key · **Actions**: 4 ## Authentication ### API Key Authentication Authenticate using your Exa API key Go to https://dashboard.exa.ai and sign up or log in Navigate to 'API Keys' section Create a new API key or copy your existing one Paste the API key below #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Exa API Key | Your Exa API key from dashboard.exa.ai | Yes | `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` | ### ModuleX Managed Key Use ModuleX's managed API keys with usage tracked against your weekly credit limit ## Available Actions ### Parameters Search query string Number of results to return (1-100) (Default: `10`) Search type: 'auto' (default), 'neural', 'deep', or 'fast' (Default: `auto`) Filter results by content category List of domains to include in search results List of domains to exclude from search results Filter results published after this date (ISO 8601 format) Filter results published before this date (ISO 8601 format) Whether to include page text content in results (Default: `true`) Whether to include relevant text highlights (Default: `false`) Whether to include AI-generated summaries (Default: `false`) ### Response ```json { "$defs": { "SearchResultItem": { "additionalProperties": false, "description": "A single result row in ``search``.", "properties": { "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" }, "published_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Published Date" }, "author": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Author" }, "score": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Score" }, "text": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Text" }, "highlights": { "items": { "type": "string" }, "title": "Highlights", "type": "array" }, "summary": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Summary" } }, "title": "SearchResultItem", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "results": { "items": { "$ref": "#/$defs/SearchResultItem" }, "title": "Results", "type": "array" }, "total_results": { "default": 0, "title": "Total Results", "type": "integer" }, "search_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Search Type" }, "request_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Request Id" } }, "required": [ "success" ], "title": "SearchOutput", "type": "object" } ``` ### Parameters List of URLs to extract content from Whether to include full page text (Default: `true`) Whether to include relevant text highlights (Default: `false`) Number of highlight snippets per URL (Default: `3`) Whether to include AI-generated summary (Default: `false`) Live crawl option: 'never', 'fallback', 'preferred', 'always' (Default: `fallback`) ### Response ```json { "$defs": { "ContentItem": { "additionalProperties": false, "description": "A single result row in ``get_contents``.", "properties": { "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "published_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Published Date" }, "author": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Author" }, "text": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Text" }, "highlights": { "items": { "type": "string" }, "title": "Highlights", "type": "array" }, "summary": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Summary" } }, "title": "ContentItem", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "results": { "items": { "$ref": "#/$defs/ContentItem" }, "title": "Results", "type": "array" }, "total_results": { "default": 0, "title": "Total Results", "type": "integer" }, "request_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Request Id" } }, "required": [ "success" ], "title": "GetContentsOutput", "type": "object" } ``` ### Parameters URL to find similar pages for Number of similar results to return (1-100) (Default: `10`) List of domains to include in results List of domains to exclude from results Whether to exclude results from the source URL's domain (Default: `true`) Filter by content category Include page text content in results (Default: `true`) ### Response ```json { "$defs": { "SimilarResultItem": { "additionalProperties": false, "description": "A single result row in ``find_similar``.", "properties": { "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" }, "published_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Published Date" }, "author": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Author" }, "score": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Score" }, "text": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Text" } }, "title": "SimilarResultItem", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "source_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Source Url" }, "results": { "items": { "$ref": "#/$defs/SimilarResultItem" }, "title": "Results", "type": "array" }, "total_results": { "default": 0, "title": "Total Results", "type": "integer" }, "request_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Request Id" } }, "required": [ "success" ], "title": "FindSimilarOutput", "type": "object" } ``` ### Parameters Question to answer using web search Number of search results to use for generating answer (Default: `5`) Search type for finding sources (Default: `auto`) Domains to prioritize for answers Domains to exclude from answer sources ### Response ```json { "$defs": { "AnswerSource": { "additionalProperties": false, "description": "A single citation in ``answer``'s sources list.", "properties": { "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" }, "published_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Published Date" } }, "title": "AnswerSource", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "answer": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Answer" }, "sources": { "items": { "$ref": "#/$defs/AnswerSource" }, "title": "Sources", "type": "array" }, "request_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Request Id" } }, "required": [ "success" ], "title": "AnswerOutput", "type": "object" } ``` ## Limits & Quotas - **Standard tier**: ~60 req/min. - **Deep search**: ~20 req/min (higher compute cost). - **Pricing** (approx., per legacy manifest): - Neural search 1–25 results: $0.005 - Neural search 26–100: $0.025 - Deep search 1–25: $0.015 - Deep search 26–100: $0.075 - Content per page: $0.001 - **Error model**: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. Plan for retries on the agent side based on the error string. ## Related integrations ## Links - [Exa Search](https://exa.ai) --- # fal.ai Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/fal-ai Queue-based AI model inference via the fal.ai platform {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/fal_ai/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} fal.ai logo ## Overview Add **fal.ai** to any ModuleX agent or workflow. Queue-based AI model inference via the fal.ai platform API (`queue.fal.run/fal-ai`). Submit requests to run generative AI models asynchronously, monitor their status, retrieve results, and cancel pending tasks. **Categories**: AI & Machine Learning · Generative AI · **Auth**: API Key · **Actions**: 4 ## Authentication ### API Key Authentication Authenticate using your fal.ai API key Go to https://fal.ai/dashboard/keys and sign in Create a new API key or copy your existing one Paste the API key below #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | fal.ai API Key | Your fal.ai API key from https://fal.ai/dashboard/keys | Yes | `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | ## Available Actions ### Parameters The unique identifier for the fal.ai app/model (e.g. 'lora', 'fast-sdxl') Input data for the model. Fields depend on the specific app/model being called Optional URL to receive webhook updates about the request status ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "request_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Request Id" }, "response_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Response Url" }, "status_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status Url" }, "cancel_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Cancel Url" } }, "required": [ "success" ], "title": "AddRequestToQueueOutput", "type": "object" } ``` ### Parameters The unique identifier for the fal.ai app/model The unique identifier for the request to cancel ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" } }, "required": [ "success" ], "title": "CancelRequestOutput", "type": "object" } ``` ### Parameters The unique identifier for the fal.ai app/model The unique identifier for the request ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "GetRequestResponseOutput", "type": "object" } ``` ### Parameters The unique identifier for the fal.ai app/model The unique identifier for the request Whether to include logs in the status response ### Response ```json { "$defs": { "LogEntry": { "additionalProperties": false, "description": "A log entry from a queued request's status.", "properties": { "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" }, "level": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Level" }, "timestamp": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Timestamp" } }, "title": "LogEntry", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "queue_position": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Queue Position" }, "logs": { "items": { "$ref": "#/$defs/LogEntry" }, "title": "Logs", "type": "array" } }, "required": [ "success" ], "title": "GetRequestStatusOutput", "type": "object" } ``` ## Limits & Quotas - Rate limits depend on your fal.ai plan tier (free tier has limited concurrent requests). - Requests are queued; queue position is returned in status responses. - Error model: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. ## Related integrations ## Links - [fal.ai](https://fal.ai) --- # Fellow Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/fellow Meeting productivity platform for notes, action items, and meeting management {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/fellow/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Fellow logo Fellow logo ## Overview Add **Fellow** to any ModuleX agent or workflow. Meeting productivity platform for notes, action items, and meeting management via the Fellow REST API (`https://.fellow.app/api/v1`). **Categories**: Productivity & Collaboration · Meetings · **Auth**: API Key · **Actions**: 3 ## Authentication ### API Key Authentication Authenticate using your Fellow API key and workspace subdomain Sign in to your Fellow workspace at https://<subdomain>.fellow.app Navigate to Settings > Integrations > API Generate a new API key or copy your existing one Note your workspace subdomain (the part before .fellow.app in your URL) #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Workspace Subdomain | Your Fellow workspace subdomain (the part before .fellow.app) | Yes | `mycompany` | | API Key | Your Fellow API key from Settings > Integrations > API | Yes | `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | ## Available Actions ### Parameters The ID of the action item to archive ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "ArchiveActionItemOutput", "type": "object" } ``` ### Parameters The ID of the action item to mark as complete ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "CompleteActionItemOutput", "type": "object" } ``` ### Parameters The ID of the note to retrieve ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "GetNoteByIdOutput", "type": "object" } ``` ## Limits & Quotas - No documented public rate limits from Fellow's API documentation. - Error model: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. ## Related integrations ## Links - [Fellow](https://fellow.ai) --- # Figma Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/figma Design collaboration platform for creating, sharing, and commenting on design files {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/figma/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Figma logo ## Overview Add **Figma** to any ModuleX agent or workflow. Design collaboration platform for creating, sharing, and commenting on design files via the Figma REST API (`api.figma.com`). **Categories**: Productivity & Collaboration · Design · **Auth**: OAuth2 · **Actions**: 3 ## Authentication ### OAuth2 Authentication Connect using Figma OAuth (recommended) #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Client ID | Figma OAuth App Client ID | Yes | `-` | | Client Secret | Figma OAuth App Client Secret | Yes | `-` | #### OAuth Configuration - **Authorization URL**: `https://www.figma.com/oauth` - **Token URL**: `https://api.figma.com/v1/oauth/token` - **Scopes**: `file_content:read`, `file_comments:read`, `file_comments:write` ## Available Actions ### Parameters The Figma file ID (found in the file URL after /file/) ### Response ```json { "$defs": { "CommentUser": { "additionalProperties": false, "description": "A Figma user who authored a comment.", "properties": { "handle": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Handle" }, "img_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Img Url" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" } }, "title": "CommentUser", "type": "object" }, "FigmaComment": { "additionalProperties": false, "description": "A single comment on a Figma file.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "file_key": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "File Key" }, "parent_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Parent Id" }, "user": { "anyOf": [ { "$ref": "#/$defs/CommentUser" }, { "type": "null" } ], "default": null }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "resolved_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Resolved At" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" }, "order_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Order Id" } }, "title": "FigmaComment", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "comments": { "items": { "$ref": "#/$defs/FigmaComment" }, "title": "Comments", "type": "array" } }, "required": [ "success" ], "title": "ListCommentsOutput", "type": "object" } ``` ### Parameters The Figma file ID (found in the file URL after /file/) The ID of the comment to delete ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" } }, "required": [ "success" ], "title": "DeleteCommentOutput", "type": "object" } ``` ### Parameters The Figma file ID (found in the file URL after /file/) The text contents of the comment to post The ID of the comment to reply to (must be a root comment) ### Response ```json { "$defs": { "CommentUser": { "additionalProperties": false, "description": "A Figma user who authored a comment.", "properties": { "handle": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Handle" }, "img_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Img Url" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" } }, "title": "CommentUser", "type": "object" }, "FigmaComment": { "additionalProperties": false, "description": "A single comment on a Figma file.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "file_key": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "File Key" }, "parent_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Parent Id" }, "user": { "anyOf": [ { "$ref": "#/$defs/CommentUser" }, { "type": "null" } ], "default": null }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "resolved_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Resolved At" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" }, "order_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Order Id" } }, "title": "FigmaComment", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "comment": { "anyOf": [ { "$ref": "#/$defs/FigmaComment" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "PostACommentOutput", "type": "object" } ``` ## Limits & Quotas - Figma REST API rate limit: 30 requests per minute per OAuth token (may vary by endpoint and plan). - No per-request billing; API access is included with Figma Professional and above. - Error model: non-2xx responses are caught and returned as `success=False` + `error` rather than raising. ## Related integrations ## Links - [Figma](https://www.figma.com) --- # Findymail Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/findymail Find verified work emails by name, domain, or LinkedIn URL, verify deliverability, reverse-lookup profiles from emails, enrich company data, find employees by job title, look up phone numbers, search technology stacks, and check credit usage. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/findymail/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Findymail logo ## Overview Add **Findymail** to any ModuleX agent or workflow. Find and verify B2B emails, phones, employees, and company data against the Findymail REST API (`app.findymail.com`). **Categories**: Sales · Enrichment · Sales Engagement · **Auth**: API Key · **Actions**: 11 ## Authentication ### API Key Authentication Authenticate using your Findymail API key Sign in at https://app.findymail.com Open the API page in your dashboard Copy your API key Paste the API key below #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Findymail API Key | Your Findymail API key from the dashboard API page | Yes | `-` | ## Available Actions ### Parameters Person's full name (e.g., 'John Doe') Company domain (preferred) or company name (e.g., stripe.com) ### Response ```json { "$defs": { "Contact": { "additionalProperties": false, "description": "A contact discovered by an email-finder action.", "properties": { "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email" }, "domain": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Domain" } }, "title": "Contact", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "contact": { "anyOf": [ { "$ref": "#/$defs/Contact" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "FindEmailFromNameOutput", "type": "object" } ``` ### Parameters Person's LinkedIn URL or username (e.g., 'https://linkedin.com/in/johndoe' or 'johndoe') ### Response ```json { "$defs": { "Contact": { "additionalProperties": false, "description": "A contact discovered by an email-finder action.", "properties": { "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email" }, "domain": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Domain" } }, "title": "Contact", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "contact": { "anyOf": [ { "$ref": "#/$defs/Contact" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "FindEmailFromLinkedinOutput", "type": "object" } ``` ### Parameters Company domain (e.g., stripe.com) Target roles at the company (max 3, e.g., ["CEO", "Founder"]) ### Response ```json { "$defs": { "Contact": { "additionalProperties": false, "description": "A contact discovered by an email-finder action.", "properties": { "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email" }, "domain": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Domain" } }, "title": "Contact", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "contacts": { "items": { "$ref": "#/$defs/Contact" }, "title": "Contacts", "type": "array" } }, "required": [ "success" ], "title": "FindEmailsByDomainOutput", "type": "object" } ``` ### Parameters Email address to verify (e.g., john@example.com) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email" }, "verified": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Verified" }, "provider": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Provider" } }, "required": [ "success" ], "title": "VerifyEmailOutput", "type": "object" } ``` ### Parameters Work or personal email address to look up Whether to return enriched profile metadata (default: false) (Default: `false`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email" }, "linkedin_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Linkedin Url" }, "full_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Full Name" }, "username": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Username" }, "headline": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Headline" }, "job_title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Job Title" }, "summary": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Summary" }, "city": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "City" }, "region": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Region" }, "country": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Country" }, "company_linkedin_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Company Linkedin Url" }, "company_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Company Name" }, "company_website": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Company Website" }, "is_premium": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Premium" }, "is_open_profile": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Open Profile" }, "skills": { "items": {}, "title": "Skills", "type": "array" }, "jobs": { "items": {}, "title": "Jobs", "type": "array" }, "educations": { "items": {}, "title": "Educations", "type": "array" }, "certificates": { "items": {}, "title": "Certificates", "type": "array" } }, "required": [ "success" ], "title": "ReverseEmailLookupOutput", "type": "object" } ``` ### Parameters Company LinkedIn URL (e.g., https://www.linkedin.com/company/stripe/) Company domain (e.g., stripe.com) Company name (e.g., Stripe) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "domain": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Domain" }, "company_size": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Company Size" }, "industry": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Industry" }, "linkedin_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Linkedin Url" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" } }, "required": [ "success" ], "title": "GetCompanyOutput", "type": "object" } ``` ### Parameters Company website or domain (e.g., google.com) Target job titles to search for (max 10, e.g., ["Software Engineer", "CEO"]) Number of contacts to return (max 5, default 1) ### Response ```json { "$defs": { "Employee": { "additionalProperties": false, "description": "A person returned by ``find_employees``.", "properties": { "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "linkedin_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Linkedin Url" }, "company_website": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Company Website" }, "company_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Company Name" }, "job_title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Job Title" } }, "title": "Employee", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "employees": { "items": { "$ref": "#/$defs/Employee" }, "title": "Employees", "type": "array" } }, "required": [ "success" ], "title": "FindEmployeesOutput", "type": "object" } ``` ### Parameters Person's LinkedIn URL or username (e.g., 'https://linkedin.com/in/johndoe' or 'johndoe') ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "phone": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Phone" }, "line_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Line Type" } }, "required": [ "success" ], "title": "FindPhoneOutput", "type": "object" } ``` ### Parameters Search term (min 2 characters, e.g., "React") ### Response ```json { "$defs": { "Technology": { "additionalProperties": false, "description": "A technology returned by the technology endpoints.", "properties": { "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "category": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Category" }, "subcategory": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Subcategory" }, "last_detected_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Detected At" } }, "title": "Technology", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "technologies": { "items": { "$ref": "#/$defs/Technology" }, "title": "Technologies", "type": "array" } }, "required": [ "success" ], "title": "SearchTechnologiesOutput", "type": "object" } ``` ### Parameters Company domain to look up (e.g., stripe.com) Filter by technology names, case-insensitive (e.g., ["React", "TypeScript"]) ### Response ```json { "$defs": { "Technology": { "additionalProperties": false, "description": "A technology returned by the technology endpoints.", "properties": { "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "category": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Category" }, "subcategory": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Subcategory" }, "last_detected_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Detected At" } }, "title": "Technology", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "domain": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Domain" }, "technologies": { "items": { "$ref": "#/$defs/Technology" }, "title": "Technologies", "type": "array" } }, "required": [ "success" ], "title": "LookupTechnologiesOutput", "type": "object" } ``` ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "credits": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Credits" }, "verifier_credits": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Verifier Credits" } }, "required": [ "success" ], "title": "GetCreditsOutput", "type": "object" } ``` ## Limits & Quotas - **Credits**: finder and verifier credits are consumed per successful lookup (e.g. verify = 1 verifier credit, find email = 1 finder credit on a match, find phone = 10 finder credits on a match). Use `get_credits` to check the remaining balance. - **Rate limits**: `find_emails_by_domain` is limited to 5 concurrent synchronous requests; `search_technologies` is limited to ~10 requests per minute. - **Error model**: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. Plan retries on the agent side based on the error string. ## Related integrations ## Links - [Findymail](https://www.findymail.com) --- # Firecrawl Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/firecrawl AI-powered web scraping, crawling, and search tool for extracting content from websites with advanced options for JavaScript rendering, caching, and structured data extraction. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/firecrawl/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Firecrawl logo ## Overview Add **Firecrawl** to any ModuleX agent or workflow. AI-powered web scraping, crawling, and search against the Firecrawl v1 REST API (`api.firecrawl.dev/v1`). Covers single-URL scraping, URL discovery (map), web search, multi-page crawls with job-id + status polling, LLM-based structured extraction, and batch scraping. **Categories**: Web Search & Scraping · Data · Search · **Auth**: API Key, ModuleX Managed Key · **Actions**: 7 ## Authentication ### API Key Authentication Authenticate using your Firecrawl API key #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Firecrawl API Key | Your Firecrawl API key for authentication | Yes | `fc-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | ### ModuleX Managed Key Use ModuleX's managed API keys with usage tracked against your weekly credit limit ## Available Actions ### Parameters The URL to scrape Content formats: markdown, html, rawHtml, screenshot, links, summary (Default: `["markdown"]`) Extract only the main content, filtering out navigation/footers (Default: `true`) HTML tags to specifically include in extraction HTML tags to exclude from extraction Time in milliseconds to wait for dynamic content Use mobile viewport Remove base64-encoded images from output Maximum age in milliseconds for cached content. Enables faster scrapes for cached pages. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "ScrapeOutput", "type": "object" } ``` ### Parameters Starting URL for URL discovery Optional search term to filter URLs Sitemap handling: 'include', 'skip', or 'only' Include URLs from subdomains in results Maximum number of URLs to return Do not return URLs with query parameters (Default: `true`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "MapWebsiteOutput", "type": "object" } ``` ### Parameters Search query string (supports operators) Maximum number of results to return (Default: `5`) Time-based search filter Location parameter for search results Options for scraping search results ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "SearchOutput", "type": "object" } ``` ### Parameters Starting URL for the crawl URL paths to exclude from crawling Only crawl these URL paths Maximum depth to crawl relative to the entered URL Maximum number of pages to crawl (Default: `100`) Allow crawling links to external domains (Default: `false`) Allow crawling links to parent paths (Default: `false`) Ignore the website sitemap when crawling (Default: `false`) Options for scraping each page ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "CrawlOutput", "type": "object" } ``` ### Parameters Crawl job ID returned from the crawl action ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "CheckCrawlStatusOutput", "type": "object" } ``` ### Parameters Array of URLs to extract information from Custom prompt for the LLM extraction JSON schema for structured data extraction Allow extraction from external links (Default: `false`) Enable web search for additional context (Default: `false`) Include subdomains in extraction (Default: `false`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "ExtractOutput", "type": "object" } ``` ### Parameters Array of URLs to scrape Content formats to extract (Default: `["markdown"]`) Extract only the main content (Default: `true`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "BatchScrapeOutput", "type": "object" } ``` ## Limits & Quotas - HTTP timeouts: 120s for scrape/map/search/status; 180s for crawl/extract/batch (long-running jobs). - Snake_case input parameters are converted to camelCase for the upstream API (`only_main_content` → `onlyMainContent`, etc.). - Response `data` carries the upstream JSON body unchanged so callers see the rich nested metadata Firecrawl returns. - Failures (non-2xx, timeouts, parse errors) surface as `success=False` + `error`; empty/blank API keys short-circuit. ## Related integrations ## Links - [Firecrawl](https://firecrawl.dev) --- # Freshdesk Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/freshdesk Customer support helpdesk platform for managing tickets, contacts, agents, and knowledge base articles via the Freshdesk REST API. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/freshdesk/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Freshdesk logo ## Overview Add **Freshdesk** to any ModuleX agent or workflow. Customer support helpdesk platform for managing tickets, contacts, agents, and knowledge base articles via the Freshdesk REST API (`{domain}.freshdesk.com/api/v2`). **Categories**: Customer Support · Helpdesk · Productivity & Collaboration · **Auth**: API Key · **Actions**: 45 ## Authentication ### API Key Authentication Authenticate using your Freshdesk API key and subdomain Log in to your Freshdesk account Click your profile picture in the top right corner Go to Profile Settings Your API key is displayed on the right side of the page #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Freshdesk Domain | Your Freshdesk subdomain (e.g. 'mycompany' for mycompany.freshdesk.com) | Yes | `mycompany` | | Freshdesk API Key | Your Freshdesk API key from Profile Settings | Yes | `xXxXxXxXxXxXxXxXxXxX` | ## Available Actions ### Parameters Subject of the ticket HTML content of the ticket Email address of the requester Priority of the ticket: 1 (Low), 2 (Medium), 3 (High), 4 (Urgent) (Default: `1`) Status of the ticket: 2 (Open), 3 (Pending), 4 (Resolved), 5 (Closed) (Default: `2`) ID of the company to associate with the ticket ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "CreateTicketOutput", "type": "object" } ``` ### Parameters ID of the ticket to retrieve ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "GetTicketOutput", "type": "object" } ``` ### Parameters ID of the ticket to update New subject for the ticket New HTML content for the ticket Priority: 1 (Low), 2 (Medium), 3 (High), 4 (Urgent) Status: 2 (Open), 3 (Pending), 4 (Resolved), 5 (Closed) ID of the group to assign the ticket to ID of the agent to assign the ticket to ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "UpdateTicketOutput", "type": "object" } ``` ### Parameters Predefined filter: new_and_my_open, watching, spam, deleted, all_tickets Filter tickets by requester ID Filter tickets by requester email Filter tickets by company ID Maximum number of results to return (Default: `100`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "items": { "items": { "additionalProperties": true, "type": "object" }, "title": "Items", "type": "array" } }, "required": [ "success" ], "title": "ListAllTicketsOutput", "type": "object" } ``` ### Parameters ID of the ticket to close ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "CloseTicketOutput", "type": "object" } ``` ### Parameters ID of the ticket to add a note to Content of the note in HTML format Whether the note is private (true) or public (false) (Default: `true`) List of email addresses to notify about this note ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "AddNoteToTicketOutput", "type": "object" } ``` ### Parameters ID of the ticket List of tag names to add to the ticket ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "AddTicketTagsOutput", "type": "object" } ``` ### Parameters ID of the ticket List of tag names to remove from the ticket ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "RemoveTicketTagsOutput", "type": "object" } ``` ### Parameters ID of the ticket List of tag names to set on the ticket (replaces existing tags) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "SetTicketTagsOutput", "type": "object" } ``` ### Parameters ID of the ticket Priority: 1 (Low), 2 (Medium), 3 (High), 4 (Urgent) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "SetTicketPriorityOutput", "type": "object" } ``` ### Parameters ID of the ticket Status: 2 (Open), 3 (Pending), 4 (Resolved), 5 (Closed) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "SetTicketStatusOutput", "type": "object" } ``` ### Parameters ID of the ticket ID of the agent to assign the ticket to ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "AssignTicketToAgentOutput", "type": "object" } ``` ### Parameters ID of the ticket ID of the group to assign the ticket to ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "AssignTicketToGroupOutput", "type": "object" } ``` ### Parameters Email address of the contact Name of the contact Phone number of the contact ID of the company to associate with the contact ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "CreateContactOutput", "type": "object" } ``` ### Parameters ID of the contact to retrieve ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "GetContactOutput", "type": "object" } ``` ### Parameters ID of the contact to update Updated name of the contact Updated email address Updated phone number ID of the company to associate with the contact ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "UpdateContactOutput", "type": "object" } ``` ### Parameters Name of the company List of domain names associated with the company (e.g. ['example.com']) Description of the company ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "CreateCompanyOutput", "type": "object" } ``` ### Parameters Email address of the agent Ticket permission: 1 (Global Access), 2 (Group Access), 3 (Restricted Access) Set to true if this is an occasional agent Type: 1 (Support Agent), 2 (Field Agent), 3 (Collaborator) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "CreateAgentOutput", "type": "object" } ``` ### Parameters ID of the agent to update Updated email address Ticket permission: 1 (Global Access), 2 (Group Access), 3 (Restricted Access) Set to true if this is an occasional agent ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "UpdateAgentOutput", "type": "object" } ``` ### Parameters ID of the agent to retrieve ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "GetAgentOutput", "type": "object" } ``` ### Parameters Filter agents by email address Filter by state: fulltime, occasional Maximum number of results to return (Default: `100`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "items": { "items": { "additionalProperties": true, "type": "object" }, "title": "Items", "type": "array" } }, "required": [ "success" ], "title": "ListAgentsOutput", "type": "object" } ``` ### Parameters ID of the ticket to reply to Content of the reply in HTML format List of email addresses to CC List of email addresses to BCC ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "CreateReplyOutput", "type": "object" } ``` ### Parameters ID of the ticket to forward Content of the forward in HTML format List of email addresses to forward to List of email addresses to CC List of email addresses to BCC ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "ForwardTicketOutput", "type": "object" } ``` ### Parameters ID of the ticket Content of the reply in HTML format List of email addresses to reply to ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "ReplyToForwardOutput", "type": "object" } ``` ### Parameters ID of the ticket to create the thread for Type of thread: forward, discussion ID of the email config to use for the thread ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "CreateThreadOutput", "type": "object" } ``` ### Parameters ID of the ticket ID of the thread to post the message in Content of the message in HTML format Subject of the email ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "CreateMessageForThreadOutput", "type": "object" } ``` ### Parameters ID of the ticket Maximum number of results to return (Default: `100`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "items": { "items": { "additionalProperties": true, "type": "object" }, "title": "Items", "type": "array" } }, "required": [ "success" ], "title": "ListTicketConversationsOutput", "type": "object" } ``` ### Parameters Maximum number of results to return (Default: `100`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "items": { "items": { "additionalProperties": true, "type": "object" }, "title": "Items", "type": "array" } }, "required": [ "success" ], "title": "ListTicketFieldsOutput", "type": "object" } ``` ### Parameters Display name of the ticket field Label for the field as seen by customers Field type: custom_dropdown, custom_checkbox, custom_text, custom_paragraph, custom_number, custom_date, custom_decimal, custom_url ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "CreateTicketFieldOutput", "type": "object" } ``` ### Parameters ID of the ticket field to update Updated display name Updated label for customers ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "UpdateTicketFieldOutput", "type": "object" } ``` ### Parameters ID of the folder to create the article in Title of the article HTML content of the article Status: 1 (Draft), 2 (Published) List of tags for the article ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "CreateSolutionArticleOutput", "type": "object" } ``` ### Parameters ID of the article to retrieve ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "GetSolutionArticleOutput", "type": "object" } ``` ### Parameters ID of the article to update Updated title Updated HTML content Status: 1 (Draft), 2 (Published) Updated tags for the article ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "UpdateSolutionArticleOutput", "type": "object" } ``` ### Parameters ID of the article to delete ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" } }, "required": [ "success" ], "title": "DeleteSolutionArticleOutput", "type": "object" } ``` ### Parameters Search keyword to find matching articles ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "items": { "items": { "additionalProperties": true, "type": "object" }, "title": "Items", "type": "array" } }, "required": [ "success" ], "title": "SearchSolutionArticleOutput", "type": "object" } ``` ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "items": { "items": { "additionalProperties": true, "type": "object" }, "title": "Items", "type": "array" } }, "required": [ "success" ], "title": "ListSolutionCategoriesOutput", "type": "object" } ``` ### Parameters ID of the solution category ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "items": { "items": { "additionalProperties": true, "type": "object" }, "title": "Items", "type": "array" } }, "required": [ "success" ], "title": "ListCategoryFoldersOutput", "type": "object" } ``` ### Parameters ID of the solution folder Maximum number of results to return (Default: `100`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "items": { "items": { "additionalProperties": true, "type": "object" }, "title": "Items", "type": "array" } }, "required": [ "success" ], "title": "ListFolderArticlesOutput", "type": "object" } ``` ### Parameters Maximum number of results to return (Default: `100`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "items": { "items": { "additionalProperties": true, "type": "object" }, "title": "Items", "type": "array" } }, "required": [ "success" ], "title": "ListAllFoldersOutput", "type": "object" } ``` ### Parameters ID of the canned response folder ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "items": { "items": { "additionalProperties": true, "type": "object" }, "title": "Items", "type": "array" } }, "required": [ "success" ], "title": "ListFolderCannedResponsesOutput", "type": "object" } ``` ### Parameters ID of the canned response ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "GetCannedResponseOutput", "type": "object" } ``` ### Parameters ID of the canned response folder Maximum number of results to return (Default: `100`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "items": { "items": { "additionalProperties": true, "type": "object" }, "title": "Items", "type": "array" } }, "required": [ "success" ], "title": "GetFolderCannedResponsesOutput", "type": "object" } ``` ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "items": { "items": { "additionalProperties": true, "type": "object" }, "title": "Items", "type": "array" } }, "required": [ "success" ], "title": "ListCompaniesOutput", "type": "object" } ``` ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "items": { "items": { "additionalProperties": true, "type": "object" }, "title": "Items", "type": "array" } }, "required": [ "success" ], "title": "ListEmailConfigsOutput", "type": "object" } ``` ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "items": { "items": { "additionalProperties": true, "type": "object" }, "title": "Items", "type": "array" } }, "required": [ "success" ], "title": "ListRolesOutput", "type": "object" } ``` ## Limits & Quotas - **Rate limits**: Freshdesk applies per-plan rate limits. Free plans: 50 calls/min. Growth and above: 200-400+ calls/min depending on plan. - **Pagination**: List endpoints return up to 100 results per page by default. - **Error model**: non-2xx responses are caught and returned as `success=False` + `error` rather than raising. ## Related integrations ## Links - [Freshdesk](https://freshdesk.com) --- # Gamma Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/gamma Integrate Gamma into the workflow. Can generate presentations, documents, webpages, and social posts from text, create from templates, check generation status, and browse themes and folders. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/gamma/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Gamma logo ## Overview Add **Gamma** to any ModuleX agent or workflow. Generate presentations, documents, webpages, and social posts with AI from text, create from templates, check generation status, and browse workspace themes and folders against the Gamma REST API (`public-api.gamma.app`). **Categories**: Marketing & Advertising · Document Processing · Content Management · **Auth**: API Key · **Actions**: 5 ## Authentication ### API Key Authentication Authenticate using your Gamma API key Sign in to Gamma with a Pro, Ultra, Teams, or Business plan Open Account Settings and navigate to the 'API Keys' section Create a new API key (format: sk-gamma-xxxxxxxx) or copy an existing one Paste the API key below #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Gamma API Key | Your Gamma API key from Account Settings > API Keys | Yes | `sk-gamma-xxxxxxxxxxxxxxxxxxxxxxxx` | ## Available Actions ### Parameters Text and image URLs used to generate your gamma (1-100,000 tokens) How to handle input text: generate (AI expands), condense (AI summarizes), or preserve (keep as-is) Output format: presentation, document, webpage, or social (default: presentation) Custom Gamma workspace theme ID (use list_themes to find themes) Number of cards/slides to generate (1-60 for Pro, 1-75 for Ultra; default: 10) How to split content into cards: auto or inputTextBreaks Card aspect ratio. Presentation: fluid, 16x9, 4x3. Document: fluid, pageless, letter, a4. Social: 1x1, 4x5, 9x16 Additional instructions for the AI generation (max 2000 chars) Automatically export the generated gamma as pdf or pptx Comma-separated folder IDs to store the generated gamma in Amount of text per card: brief, medium, detailed, or extensive Tone of the generated text, e.g. "professional" (max 500 chars) Target audience, e.g. "executives", "students" (max 500 chars) Language code for the generated text (default: en) Where to source images: aiGenerated, pictographic, unsplash, webAllImages, webFreeToUse, webFreeToUseCommercially, giphy, placeholder, or noImages AI image generation model to use when image_source is aiGenerated Style directive for AI-generated images, e.g. "watercolor" ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "generation_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Generation Id" }, "warnings": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Warnings" } }, "required": [ "success" ], "title": "GenerateOutput", "type": "object" } ``` ### Parameters The ID of the template gamma to adapt Instructions for how to adapt the template (1-100,000 tokens) Custom Gamma workspace theme ID to apply Automatically export the generated gamma as pdf or pptx Comma-separated folder IDs to store the generated gamma in AI image generation model to use when image_source is aiGenerated Style directive for AI-generated images, e.g. "watercolor" ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "generation_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Generation Id" }, "warnings": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Warnings" } }, "required": [ "success" ], "title": "GenerateFromTemplateOutput", "type": "object" } ``` ### Parameters The generation ID returned by generate or generate_from_template ### Response ```json { "$defs": { "GammaCredits": { "additionalProperties": false, "description": "Credit usage reported on a completed generation.", "properties": { "deducted": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Deducted" }, "remaining": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Remaining" } }, "title": "GammaCredits", "type": "object" }, "GammaError": { "additionalProperties": false, "description": "Error details reported on a failed generation.", "properties": { "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" }, "status_code": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Status Code" } }, "title": "GammaError", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "generation_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Generation Id" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "gamma_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Gamma Url" }, "gamma_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Gamma Id" }, "export_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Export Url" }, "credits": { "anyOf": [ { "$ref": "#/$defs/GammaCredits" }, { "type": "null" } ], "default": null }, "generation_error": { "anyOf": [ { "$ref": "#/$defs/GammaError" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "CheckStatusOutput", "type": "object" } ``` ### Parameters Search query to filter themes by name (case-insensitive) Maximum number of themes to return per page (max 50) Pagination cursor from a previous response (next_cursor) ### Response ```json { "$defs": { "ThemeItem": { "additionalProperties": false, "description": "A single theme in ``list_themes``.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Type" }, "color_keywords": { "items": { "type": "string" }, "title": "Color Keywords", "type": "array" }, "tone_keywords": { "items": { "type": "string" }, "title": "Tone Keywords", "type": "array" } }, "title": "ThemeItem", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "themes": { "items": { "$ref": "#/$defs/ThemeItem" }, "title": "Themes", "type": "array" }, "has_more": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Has More" }, "next_cursor": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Next Cursor" } }, "required": [ "success" ], "title": "ListThemesOutput", "type": "object" } ``` ### Parameters Search query to filter folders by name (case-sensitive) Maximum number of folders to return per page (max 50) Pagination cursor from a previous response (next_cursor) ### Response ```json { "$defs": { "FolderItem": { "additionalProperties": false, "description": "A single folder in ``list_folders``.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" } }, "title": "FolderItem", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "folders": { "items": { "$ref": "#/$defs/FolderItem" }, "title": "Folders", "type": "array" }, "has_more": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Has More" }, "next_cursor": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Next Cursor" } }, "required": [ "success" ], "title": "ListFoldersOutput", "type": "object" } ``` ## Limits & Quotas - API key access requires a paid plan (Pro, Ultra, Teams, or Business). - Generation cost is metered in credits; `check_status` reports `credits.deducted` and `credits.remaining` on completion. - `list_themes` and `list_folders` cap `limit` at 50 per page and use a `next_cursor` cursor (pass it as `after`) for further pages. - Every response carries rate-limit headers (`x-ratelimit-remaining`, `x-ratelimit-remaining-daily`). - **Error model**: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. Plan for retries on the agent side based on the error string. ## Related integrations ## Links - [Gamma](https://gamma.app) --- # GitHub Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/github GitHub repository and code management platform {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/github/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} GitHub logo GitHub logo ## Overview Add **GitHub** to any ModuleX agent or workflow. Repository, issue, pull request, branch, commit, and code search actions against the GitHub REST API (v3, `api.github.com`). **Categories**: Developer Tools & Infrastructure · Version Control · Productivity · **Auth**: OAuth2, Personal Access Token · **Actions**: 16 ## Authentication ### OAuth2 Authentication Connect using GitHub OAuth (recommended for most use cases) #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Client ID | GitHub OAuth App Client ID | Yes | `Iv1.xxxxxxxxxxxxxxxx` | | Client Secret | GitHub OAuth App Client Secret | Yes | `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | #### OAuth Configuration - **Authorization URL**: `https://github.com/login/oauth/authorize` - **Token URL**: `https://github.com/login/oauth/access_token` - **Scopes**: `repo`, `user`, `read:org`, `workflow` ### Personal Access Token Use your GitHub Personal Access Token (Classic or Fine-grained) Go to GitHub Settings → Developer settings → Personal access tokens Choose 'Tokens (classic)' OR 'Fine-grained tokens (Beta)' Click 'Generate new token' For Classic: Select scopes (repo, user, read:org, workflow) For Fine-grained: Set repository access and permissions Copy the generated token #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Personal Access Token | Your GitHub Personal Access Token (Classic: ghp_xxx or Fine-grained: github_pat_xxx) | Yes | `ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | ## Available Actions ### Parameters Filter by visibility: all, public, private (Default: `all`) Sort by: created, updated, pushed, full_name (Default: `full_name`) Results per page (max 100) (Default: `30`) ### Response ```json { "$defs": { "RepositorySummary": { "additionalProperties": false, "description": "One row of ``list_repositories``.", "properties": { "id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "full_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Full Name" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "private": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Private" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" }, "clone_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Clone Url" }, "ssh_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Ssh Url" }, "language": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Language" }, "stars": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Stars" }, "forks": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Forks" }, "open_issues": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Open Issues" }, "default_branch": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Default Branch" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "updated_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated At" } }, "title": "RepositorySummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "repositories": { "items": { "$ref": "#/$defs/RepositorySummary" }, "title": "Repositories", "type": "array" }, "total": { "default": 0, "title": "Total", "type": "integer" }, "page": { "default": 1, "title": "Page", "type": "integer" }, "per_page": { "default": 30, "title": "Per Page", "type": "integer" } }, "required": [ "success" ], "title": "ListRepositoriesOutput", "type": "object" } ``` ### Parameters Repository name Repository description Whether the repository is private (Default: `false`) ### Response ```json { "$defs": { "RepositoryCreated": { "additionalProperties": false, "description": "Repository representation from ``create_repository``.", "properties": { "id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "full_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Full Name" }, "owner": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Owner" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" }, "clone_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Clone Url" }, "private": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Private" }, "default_branch": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Default Branch" } }, "title": "RepositoryCreated", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "repository": { "$ref": "#/$defs/RepositoryCreated" } }, "required": [ "success", "repository" ], "title": "CreateRepositoryOutput", "type": "object" } ``` ### Parameters Repository owner (username or organization) Repository name to delete ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "message": { "title": "Message", "type": "string" } }, "required": [ "success", "message" ], "title": "DeleteRepositoryOutput", "type": "object" } ``` ### Parameters Repository owner Repository name ### Response ```json { "$defs": { "RepositoryDetailed": { "additionalProperties": false, "description": "Repository representation from ``get_repository``.", "properties": { "id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "full_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Full Name" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "private": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Private" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" }, "clone_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Clone Url" }, "ssh_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Ssh Url" }, "language": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Language" }, "stars": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Stars" }, "forks": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Forks" }, "watchers": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Watchers" }, "open_issues": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Open Issues" }, "default_branch": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Default Branch" }, "topics": { "items": { "type": "string" }, "title": "Topics", "type": "array" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "updated_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated At" }, "pushed_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Pushed At" } }, "title": "RepositoryDetailed", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "repository": { "$ref": "#/$defs/RepositoryDetailed" } }, "required": [ "success", "repository" ], "title": "GetRepositoryOutput", "type": "object" } ``` ### Parameters Repository owner Repository name Issue state: open, closed, all (Default: `open`) ### Response ```json { "$defs": { "IssueSummary": { "additionalProperties": false, "description": "Issue row in ``list_issues`` / single fetch in ``get_issue``.", "properties": { "number": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Number" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "body": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Body" }, "state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "State" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" }, "user": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "User" }, "labels": { "items": { "type": "string" }, "title": "Labels", "type": "array" }, "assignees": { "items": { "type": "string" }, "title": "Assignees", "type": "array" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "updated_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated At" }, "closed_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Closed At" }, "comments": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Comments" } }, "title": "IssueSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "issues": { "items": { "$ref": "#/$defs/IssueSummary" }, "title": "Issues", "type": "array" }, "total": { "default": 0, "title": "Total", "type": "integer" }, "page": { "default": 1, "title": "Page", "type": "integer" }, "per_page": { "default": 30, "title": "Per Page", "type": "integer" } }, "required": [ "success" ], "title": "ListIssuesOutput", "type": "object" } ``` ### Parameters Repository owner Repository name Issue title Issue body Issue labels ### Response ```json { "$defs": { "IssueCreated": { "additionalProperties": false, "description": "Issue representation from ``create_issue``.", "properties": { "number": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Number" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "body": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Body" }, "state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "State" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" } }, "title": "IssueCreated", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "issue": { "$ref": "#/$defs/IssueCreated" } }, "required": [ "success", "issue" ], "title": "CreateIssueOutput", "type": "object" } ``` ### Parameters Repository owner Repository name Issue number ### Response ```json { "$defs": { "IssueSummary": { "additionalProperties": false, "description": "Issue row in ``list_issues`` / single fetch in ``get_issue``.", "properties": { "number": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Number" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "body": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Body" }, "state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "State" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" }, "user": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "User" }, "labels": { "items": { "type": "string" }, "title": "Labels", "type": "array" }, "assignees": { "items": { "type": "string" }, "title": "Assignees", "type": "array" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "updated_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated At" }, "closed_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Closed At" }, "comments": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Comments" } }, "title": "IssueSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "issue": { "$ref": "#/$defs/IssueSummary" } }, "required": [ "success", "issue" ], "title": "GetIssueOutput", "type": "object" } ``` ### Parameters Repository owner Repository name Issue number Issue title Issue body Issue state: open or closed ### Response ```json { "$defs": { "IssueUpdated": { "additionalProperties": false, "description": "Issue representation from ``update_issue``.", "properties": { "number": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Number" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "body": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Body" }, "state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "State" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" }, "updated_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated At" } }, "title": "IssueUpdated", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "issue": { "$ref": "#/$defs/IssueUpdated" } }, "required": [ "success", "issue" ], "title": "UpdateIssueOutput", "type": "object" } ``` ### Parameters Repository owner Repository name PR state: open, closed, all (Default: `open`) ### Response ```json { "$defs": { "PullRequestSummary": { "additionalProperties": false, "description": "PR row in ``list_pull_requests``.", "properties": { "number": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Number" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "body": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Body" }, "state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "State" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" }, "user": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "User" }, "head": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Head" }, "base": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Base" }, "draft": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Draft" }, "mergeable": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Mergeable" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "updated_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated At" }, "merged_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Merged At" }, "closed_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Closed At" } }, "title": "PullRequestSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "pull_requests": { "items": { "$ref": "#/$defs/PullRequestSummary" }, "title": "Pull Requests", "type": "array" }, "total": { "default": 0, "title": "Total", "type": "integer" }, "page": { "default": 1, "title": "Page", "type": "integer" }, "per_page": { "default": 30, "title": "Per Page", "type": "integer" } }, "required": [ "success" ], "title": "ListPullRequestsOutput", "type": "object" } ``` ### Parameters Repository owner Repository name Pull request title The name of the branch where your changes are implemented The name of the branch you want the changes pulled into Pull request description ### Response ```json { "$defs": { "PullRequestCreated": { "additionalProperties": false, "description": "PR representation from ``create_pull_request``.", "properties": { "number": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Number" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "body": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Body" }, "state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "State" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" }, "draft": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Draft" }, "head": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Head" }, "base": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Base" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" } }, "title": "PullRequestCreated", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "pull_request": { "$ref": "#/$defs/PullRequestCreated" } }, "required": [ "success", "pull_request" ], "title": "CreatePullRequestOutput", "type": "object" } ``` ### Parameters Repository owner Repository name Pull request number ### Response ```json { "$defs": { "PullRequestDetailed": { "additionalProperties": false, "description": "PR representation from ``get_pull_request``.", "properties": { "number": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Number" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "body": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Body" }, "state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "State" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" }, "user": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "User" }, "head": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Head" }, "base": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Base" }, "draft": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Draft" }, "mergeable": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Mergeable" }, "merged": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Merged" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "updated_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated At" }, "merged_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Merged At" }, "closed_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Closed At" }, "commits": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Commits" }, "additions": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Additions" }, "deletions": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Deletions" }, "changed_files": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Changed Files" } }, "title": "PullRequestDetailed", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "pull_request": { "$ref": "#/$defs/PullRequestDetailed" } }, "required": [ "success", "pull_request" ], "title": "GetPullRequestOutput", "type": "object" } ``` ### Parameters Repository owner Repository name Pull request number Commit message for the merge Merge method: merge, squash, rebase (Default: `merge`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "merged": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Merged" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" }, "sha": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Sha" } }, "required": [ "success" ], "title": "MergePullRequestOutput", "type": "object" } ``` ### Parameters Repository owner Repository name New branch name Source branch (defaults to default branch) ### Response ```json { "$defs": { "Branch": { "additionalProperties": false, "description": "Branch returned by ``create_branch``.", "properties": { "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "ref": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Ref" }, "sha": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Sha" } }, "title": "Branch", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "branch": { "$ref": "#/$defs/Branch" } }, "required": [ "success", "branch" ], "title": "CreateBranchOutput", "type": "object" } ``` ### Parameters Repository owner Repository name File path Branch name (defaults to default branch) ### Response ```json { "$defs": { "FileContent": { "additionalProperties": false, "description": "File returned by ``get_file_content`` (content is base64-decoded UTF-8).", "properties": { "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "path": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Path" }, "size": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Size" }, "content": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Content" }, "sha": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Sha" }, "download_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Download Url" } }, "title": "FileContent", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "file": { "$ref": "#/$defs/FileContent" } }, "required": [ "success", "file" ], "title": "GetFileContentOutput", "type": "object" } ``` ### Parameters Repository owner Repository name Branch name Commit message Array of file objects with path and content ### Response ```json { "$defs": { "CommitCreated": { "additionalProperties": false, "description": "Commit returned by ``create_commit``.", "properties": { "sha": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Sha" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" }, "branch": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Branch" } }, "title": "CommitCreated", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "commit": { "$ref": "#/$defs/CommitCreated" } }, "required": [ "success", "commit" ], "title": "CreateCommitOutput", "type": "object" } ``` ### Parameters Search query Sort by: indexed (default) (Default: `indexed`) Results per page (max 100) (Default: `30`) ### Response ```json { "$defs": { "SearchCodeItem": { "additionalProperties": false, "description": "One result row of ``search_code``.", "properties": { "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "path": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Path" }, "repository": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Repository" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" }, "sha": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Sha" } }, "title": "SearchCodeItem", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "total_count": { "default": 0, "title": "Total Count", "type": "integer" }, "items": { "items": { "$ref": "#/$defs/SearchCodeItem" }, "title": "Items", "type": "array" }, "page": { "default": 1, "title": "Page", "type": "integer" }, "per_page": { "default": 30, "title": "Per Page", "type": "integer" } }, "required": [ "success" ], "title": "SearchCodeOutput", "type": "object" } ``` ## Limits & Quotas - **Authenticated REST API:** 5,000 requests per hour per token. - **Search API** (`search_code`): 30 requests per minute, separate quota — and `q` must be at least one valid qualifier (e.g. `repo:owner/name` or `language:python`). - **`create_commit`** makes five sequential API calls (ref, base commit, one blob per file, tree, commit, ref update). Budget accordingly when committing many files. - **`delete_repository`** is irreversible and requires the token to carry the `delete_repo` scope; Fine-grained tokens additionally need the *Administration: write* permission. ## Related integrations ## Links - [GitHub](https://github.com) --- # GitLab Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/gitlab GitLab repository and project management platform {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/gitlab/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} GitLab logo ## Overview Add **GitLab** to any ModuleX agent or workflow. Repository and project management platform for the GitLab REST API (`gitlab.com/api/v4`). **Categories**: Developer Tools & Infrastructure · Version Control · Project Management · **Auth**: OAuth2 · **Actions**: 12 ## Authentication ### OAuth2 Authentication Connect using GitLab OAuth (recommended) #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Client ID | GitLab OAuth App Client ID | Yes | `-` | | Client Secret | GitLab OAuth App Client Secret | Yes | `-` | #### OAuth Configuration - **Authorization URL**: `https://gitlab.com/oauth/authorize` - **Token URL**: `https://gitlab.com/oauth/token` - **Scopes**: `api` ## Available Actions ### Parameters The project ID, as displayed in the main project page The branch name or commit SHA to create the new branch from The name of the branch to create ### Response ```json { "$defs": { "BranchCommit": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "short_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Short Id" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "author_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Author Name" }, "author_email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Author Email" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" } }, "title": "BranchCommit", "type": "object" }, "BranchSummary": { "additionalProperties": false, "properties": { "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "commit": { "anyOf": [ { "$ref": "#/$defs/BranchCommit" }, { "type": "null" } ], "default": null }, "merged": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Merged" }, "protected": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Protected" }, "developers_can_push": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Developers Can Push" }, "developers_can_merge": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Developers Can Merge" }, "can_push": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Can Push" }, "web_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Web Url" } }, "title": "BranchSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "branch": { "anyOf": [ { "$ref": "#/$defs/BranchSummary" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "CreateBranchOutput", "type": "object" } ``` ### Parameters The ID of the group The title of the epic The description of the epic List of label names for the epic The internal ID of the parent epic (requires GitLab Premium or Ultimate) The color of the epic (introduced in GitLab 14.8) Whether the epic should be confidential When the epic was created, ISO 8601 format (requires admin or owner privileges) Whether start date should be sourced from start_date_fixed or from milestones Whether due date should be sourced from due_date_fixed or from milestones The fixed start date of the epic, ISO 8601 format (relevant when start_date_is_fixed is true) The fixed due date of the epic, ISO 8601 format (relevant when due_date_is_fixed is true) ### Response ```json { "$defs": { "EpicSummary": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Id" }, "iid": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Iid" }, "group_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Group Id" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "State" }, "confidential": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Confidential" }, "web_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Web Url" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "updated_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated At" }, "labels": { "items": { "type": "string" }, "title": "Labels", "type": "array" } }, "title": "EpicSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "epic": { "anyOf": [ { "$ref": "#/$defs/EpicSummary" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "CreateEpicOutput", "type": "object" } ``` ### Parameters The project ID, as displayed in the main project page The title of the issue The description of the issue List of label names for the issue List of user IDs to assign the issue to ### Response ```json { "$defs": { "IssueSummary": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Id" }, "iid": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Iid" }, "project_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Project Id" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "State" }, "web_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Web Url" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "updated_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated At" }, "closed_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Closed At" }, "labels": { "items": { "type": "string" }, "title": "Labels", "type": "array" }, "assignees": { "items": { "type": "string" }, "title": "Assignees", "type": "array" } }, "title": "IssueSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "issue": { "anyOf": [ { "$ref": "#/$defs/IssueSummary" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "CreateIssueOutput", "type": "object" } ``` ### Parameters The project ID, as displayed in the main project page The internal ID of the project issue ### Response ```json { "$defs": { "IssueSummary": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Id" }, "iid": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Iid" }, "project_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Project Id" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "State" }, "web_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Web Url" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "updated_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated At" }, "closed_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Closed At" }, "labels": { "items": { "type": "string" }, "title": "Labels", "type": "array" }, "assignees": { "items": { "type": "string" }, "title": "Assignees", "type": "array" } }, "title": "IssueSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "issue": { "anyOf": [ { "$ref": "#/$defs/IssueSummary" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "GetIssueOutput", "type": "object" } ``` ### Parameters The project ID, as displayed in the main project page The name of the branch ### Response ```json { "$defs": { "BranchCommit": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "short_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Short Id" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "author_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Author Name" }, "author_email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Author Email" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" } }, "title": "BranchCommit", "type": "object" }, "BranchSummary": { "additionalProperties": false, "properties": { "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "commit": { "anyOf": [ { "$ref": "#/$defs/BranchCommit" }, { "type": "null" } ], "default": null }, "merged": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Merged" }, "protected": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Protected" }, "developers_can_push": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Developers Can Push" }, "developers_can_merge": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Developers Can Merge" }, "can_push": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Can Push" }, "web_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Web Url" } }, "title": "BranchSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "branch": { "anyOf": [ { "$ref": "#/$defs/BranchSummary" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "GetRepoBranchOutput", "type": "object" } ``` ### Parameters The project ID, as displayed in the main project page The name of a repository branch, tag, or revision range; defaults to the default branch if not given Maximum number of commits to return (per_page) (Default: `100`) ### Response ```json { "$defs": { "CommitSummary": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "short_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Short Id" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "author_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Author Name" }, "author_email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Author Email" }, "authored_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Authored Date" }, "committed_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Committed Date" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" }, "web_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Web Url" } }, "title": "CommitSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "commits": { "items": { "$ref": "#/$defs/CommitSummary" }, "title": "Commits", "type": "array" } }, "required": [ "success" ], "title": "ListCommitsOutput", "type": "object" } ``` ### Parameters Limit to groups where the user has at least this access level: 10 (Guest), 20 (Reporter), 30 (Developer), 40 (Maintainer), 50 (Owner) Limit to top-level groups, excluding all subgroups Return groups matching this search string Order groups by: name, path, id, or similarity (must be similarity when search is provided) Sort order: asc or desc (Default: `asc`) Limit to groups that are not archived and not marked for deletion Limit to groups that are archived ### Response ```json { "$defs": { "GroupSummary": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "path": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Path" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "visibility": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Visibility" }, "web_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Web Url" }, "full_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Full Name" }, "full_path": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Full Path" } }, "title": "GroupSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "groups": { "items": { "$ref": "#/$defs/GroupSummary" }, "title": "Groups", "type": "array" } }, "required": [ "success" ], "title": "ListGroupsOutput", "type": "object" } ``` ### Parameters The project ID, as displayed in the main project page Filter results by name, email, or username (partial values accepted) List of user IDs to filter results to List of user IDs to exclude from results Return seat information for each member if available ### Response ```json { "$defs": { "MemberSummary": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Id" }, "username": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Username" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "State" }, "access_level": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Access Level" }, "web_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Web Url" } }, "title": "MemberSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "members": { "items": { "$ref": "#/$defs/MemberSummary" }, "title": "Members", "type": "array" } }, "required": [ "success" ], "title": "ListProjectMembersOutput", "type": "object" } ``` ### Parameters The project ID, as displayed in the main project page Return branches containing this search string (use ^term and term$ for prefix/suffix matching) ### Response ```json { "$defs": { "BranchCommit": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "short_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Short Id" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "author_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Author Name" }, "author_email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Author Email" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" } }, "title": "BranchCommit", "type": "object" }, "BranchSummary": { "additionalProperties": false, "properties": { "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "commit": { "anyOf": [ { "$ref": "#/$defs/BranchCommit" }, { "type": "null" } ], "default": null }, "merged": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Merged" }, "protected": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Protected" }, "developers_can_push": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Developers Can Push" }, "developers_can_merge": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Developers Can Merge" }, "can_push": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Can Push" }, "web_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Web Url" } }, "title": "BranchSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "branches": { "items": { "$ref": "#/$defs/BranchSummary" }, "title": "Branches", "type": "array" } }, "required": [ "success" ], "title": "ListRepoBranchesOutput", "type": "object" } ``` ### Parameters The project ID, as displayed in the main project page Search issues against their title and description List of label names; issues must have all labels to be returned Return issues by state: all, opened, or closed (Default: `all`) Return issues assigned to the given user ID Maximum number of issues to return (per_page) (Default: `100`) ### Response ```json { "$defs": { "IssueSummary": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Id" }, "iid": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Iid" }, "project_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Project Id" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "State" }, "web_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Web Url" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "updated_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated At" }, "closed_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Closed At" }, "labels": { "items": { "type": "string" }, "title": "Labels", "type": "array" }, "assignees": { "items": { "type": "string" }, "title": "Assignees", "type": "array" } }, "title": "IssueSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "issues": { "items": { "$ref": "#/$defs/IssueSummary" }, "title": "Issues", "type": "array" } }, "required": [ "success" ], "title": "SearchIssuesOutput", "type": "object" } ``` ### Parameters The ID of the group The internal ID of the epic The title of the epic The description of the epic List of label names for the epic (set empty to unassign all) Labels to add to the epic Labels to remove from the epic The ID of a parent epic (available in GitLab 14.6+) State event for the epic: close or reopen Whether the epic should be confidential The color of the epic When the epic was updated, ISO 8601 format (requires admin or owner privileges) Whether start date should be sourced from start_date_fixed or from milestones Whether due date should be sourced from due_date_fixed or from milestones The fixed start date of the epic, ISO 8601 format (relevant when start_date_is_fixed is true) The fixed due date of the epic, ISO 8601 format (relevant when due_date_is_fixed is true) ### Response ```json { "$defs": { "EpicSummary": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Id" }, "iid": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Iid" }, "group_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Group Id" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "State" }, "confidential": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Confidential" }, "web_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Web Url" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "updated_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated At" }, "labels": { "items": { "type": "string" }, "title": "Labels", "type": "array" } }, "title": "EpicSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "epic": { "anyOf": [ { "$ref": "#/$defs/EpicSummary" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "UpdateEpicOutput", "type": "object" } ``` ### Parameters The project ID, as displayed in the main project page The internal ID of the issue The title of the issue The description of the issue List of label names for the issue (set empty to unassign all) List of user IDs to assign the issue to (set to 0 or empty to unassign all) State event for the issue: close or reopen Whether the issue discussion is locked (only project members can add or edit comments when locked) ### Response ```json { "$defs": { "IssueSummary": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Id" }, "iid": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Iid" }, "project_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Project Id" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "State" }, "web_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Web Url" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "updated_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated At" }, "closed_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Closed At" }, "labels": { "items": { "type": "string" }, "title": "Labels", "type": "array" }, "assignees": { "items": { "type": "string" }, "title": "Assignees", "type": "array" } }, "title": "IssueSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "issue": { "anyOf": [ { "$ref": "#/$defs/IssueSummary" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "UpdateIssueOutput", "type": "object" } ``` ## Limits & Quotas - **Authenticated requests**: 2,000 requests per minute per user (GitLab.com). - **Unauthenticated**: 500 requests per minute. - **Epics API**: restricted to GitLab Premium and Ultimate tiers. - **Error model**: non-2xx responses raise `httpx.HTTPStatusError` (Pattern A). The agent or runtime catches the exception at a higher layer. ## Related integrations ## Links - [GitLab](https://gitlab.com) --- # Gmail Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/gmail Google Gmail email service for sending, reading, and managing emails. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/gmail/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Gmail logo ## Overview Add **Gmail** to any ModuleX agent or workflow. Gmail integration via the Gmail REST v1 API (`www.googleapis.com/gmail/v1`). Send email + list labels. Pure HTTP — does **not** depend on the `google-api-python-client` SDK. **Categories**: Communication · Communication & Collaboration · Email · Productivity · **Auth**: OAuth2, Service Account / Access Token · **Actions**: 2 ## Authentication ### OAuth2 Authentication Connect using Google OAuth2 (recommended). Provides secure access to Gmail. #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Client ID | Google OAuth2 Client ID from Google Cloud Console | Yes | `123456789-xxxxxxxxxxxxxxxx.apps.googleusercontent.com` | | Client Secret | Google OAuth2 Client Secret | Yes | `-` | #### OAuth Configuration - **Authorization URL**: `https://accounts.google.com/o/oauth2/v2/auth` - **Token URL**: `https://oauth2.googleapis.com/token` - **Scopes**: `https://www.googleapis.com/auth/gmail.send`, `https://www.googleapis.com/auth/gmail.labels` ### Service Account / Access Token Use a pre-generated access token or service account credentials #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Access Token | A valid Google OAuth2 access token with Gmail scopes | Yes | `-` | ## Available Actions ### Parameters Recipient email address Email subject line Email body content CC recipients (comma-separated) BCC recipients (comma-separated) Whether the body is HTML content (Default: `false`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "thread_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Thread Id" }, "label_ids": { "items": { "type": "string" }, "title": "Label Ids", "type": "array" } }, "required": [ "success" ], "title": "SendMessageOutput", "type": "object" } ``` ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "labels": { "items": { "additionalProperties": true, "type": "object" }, "title": "Labels", "type": "array" }, "total": { "default": 0, "title": "Total", "type": "integer" } }, "required": [ "success" ], "title": "ListLabelsOutput", "type": "object" } ``` ## Limits & Quotas - `send_message` builds a base64url-encoded MIME message locally (no SDK dep). - Sending is limited to 500 messages/day (consumer accounts) or 2000/day (Google Workspace). - 60s timeout for send, 30s for label listing. ## Related integrations ## Links - [Gmail](https://mail.google.com) --- # GoDaddy Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/godaddy Domain registration, availability checking, and management via the GoDaddy API {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/godaddy/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} GoDaddy logo GoDaddy logo ## Overview Add **GoDaddy** to any ModuleX agent or workflow. Domain registration, availability checking, and management via the GoDaddy REST API (`api.godaddy.com/v1`). **Categories**: Cloud Infrastructure · Domains · Infrastructure · Web Hosting · **Auth**: GoDaddy API Key + Secret · **Actions**: 5 ## Authentication ### GoDaddy API Key + Secret Authenticate using a GoDaddy API key and secret pair. The Authorization header is built as 'sso-key <key>:<secret>'. #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | API Key | Your GoDaddy API Key from developer.godaddy.com | Yes | `xxxxxxxxxxxxxxxx` | | API Secret | Your GoDaddy API Secret from developer.godaddy.com | Yes | `xxxxxxxxxxxxxxxxxxxxxxxx` | | API URL | GoDaddy API base URL. Use https://api.godaddy.com for production or https://api.ote-godaddy.com for the test environment | Yes | `https://api.godaddy.com` | ## Available Actions ### Parameters The domain name to check availability for (e.g. example.com) Optimize for time (FAST) or accuracy (FULL). Allowed values: FAST, FULL Whether to include domains available for transfer. If true, check_type is ignored ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "available": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Available" }, "domain": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Domain" }, "definitive": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Definitive" }, "price": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Price" }, "currency": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Currency" }, "period": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Period" } }, "required": [ "success" ], "title": "CheckDomainAvailabilityOutput", "type": "object" } ``` ### Parameters Filter by status. Array of strings: ACTIVE, CANCELLED, etc. Filter by status group. Array of strings: VISIBLE, INACTIVE, RENEWABLE, etc. Maximum number of domains to return Marker domain to use as the offset in results Optional details to include: authCode, contacts, nameServers Only include results modified since this date (ISO 8601 format) ### Response ```json { "$defs": { "DomainSummary": { "additionalProperties": false, "description": "A domain summary object from the list domains endpoint.", "properties": { "domain": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Domain" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "expires": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Expires" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "renewable": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Renewable" } }, "title": "DomainSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "domains": { "items": { "$ref": "#/$defs/DomainSummary" }, "title": "Domains", "type": "array" } }, "required": [ "success" ], "title": "ListDomainsOutput", "type": "object" } ``` ### Response ```json { "$defs": { "TldInfo": { "additionalProperties": false, "description": "A TLD entry from the list TLDs endpoint.", "properties": { "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Type" } }, "title": "TldInfo", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "tlds": { "items": { "$ref": "#/$defs/TldInfo" }, "title": "Tlds", "type": "array" } }, "required": [ "success" ], "title": "ListTldsOptionsOutput", "type": "object" } ``` ### Parameters The domain name to renew (e.g. example.com) Number of years to extend the domain (1-10). Defaults to original purchase period when omitted ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "order_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Order Id" }, "item_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Item Count" }, "total": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Total" }, "currency": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Currency" } }, "required": [ "success" ], "title": "RenewDomainOutput", "type": "object" } ``` ### Parameters Domain name or keywords for which alternative domain names will be suggested Two-letter ISO country code as a hint for target region City name as a hint for target region Sources to query: CC_TLD, EXTENSION, KEYWORD_SPIN, PREMIUM Top-level domains to include in suggestions (e.g. com, net, org) Maximum length of second-level domain Minimum length of second-level domain Maximum number of suggestions to return Maximum time in milliseconds to wait for responses ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "suggestions": { "items": { "additionalProperties": true, "type": "object" }, "title": "Suggestions", "type": "array" } }, "required": [ "success" ], "title": "SuggestDomainsOutput", "type": "object" } ``` ## Limits & Quotas - **Production API**: 60 requests per minute per API key (per GoDaddy developer documentation). - **OTE (test) API**: Lower rate limits; intended for development only. - **Renewal actions** incur actual charges on the account. - **Error model**: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. ## Related integrations ## Links - [GoDaddy](https://www.godaddy.com) --- # Gong Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/gong Revenue intelligence platform for recording, transcribing, and analyzing sales conversations {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/gong/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Gong logo ## Overview Add **Gong** to any ModuleX agent or workflow. Revenue intelligence platform for recording, transcribing, and analyzing sales conversations via the Gong REST API (your per-tenant `https://-.api.gong.io/v2` base URL). **Categories**: Sales · Revenue Intelligence · Conversation Analytics · **Auth**: Gong API Key · **Actions**: 5 ## Authentication ### Gong API Key Authenticate with a Gong Access Key + Access Key Secret (Company Settings -> API), sent as HTTP Basic auth. Sign in to Gong as a technical administrator Go to Company Settings -> Ecosystem -> API (https://app.gong.io/company/api) Click 'Create' to generate an Access Key and Access Key Secret (the Secret is shown only once) Find your API base URL at https://app.gong.io/company/api-authentication (e.g. https://us-12345.api.gong.io) #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Access Key | Gong API Access Key | Yes | `-` | | Access Key Secret | Gong API Access Key Secret (shown only once at creation) | Yes | `-` | | API Base URL | Your Gong API base URL (region/tenant-specific), e.g. https://us-12345.api.gong.io | Yes | `https://us-12345.api.gong.io` | ## Available Actions ### Parameters A call's unique identifier in the PBX or recording system. Used to prevent duplicate uploads. The actual date and time when the call started in ISO-8601 format (e.g., 2018-02-18T02:30:00-07:00 or 2018-02-18T08:00:00Z). Whether the call is Inbound, Outbound, Conference, or Unknown. The Gong internal user ID of the team member who hosted the call. A list of the call's participants as JSON objects. Each party can have: phoneNumber, emailAddress, name, mediaChannelId. The title of the call, available for indexing and search. The purpose of the call. Free text up to 255 characters. The date and time the call was scheduled to begin in ISO-8601 format. The date and time the call was scheduled to end in ISO-8601 format. The actual call duration in seconds. The disposition of the call. Free text up to 255 characters. The URL of the conference call by which users join the meeting. Code identifying the conferencing/telephony system: zoom, clearslide, gotomeeting, ringcentral, outreach, insidesales. The URL from which Gong can download the media file. Must be unique, max 1.5GB. Optional workspace identifier for call placement. Language code for transcription (e.g., en-US, fr-FR). Optional; Gong auto-detects if not set. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "request_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Request Id" }, "call_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Call Id" } }, "required": [ "success" ], "title": "AddNewCallOutput", "type": "object" } ``` ### Parameters Date and time (ISO-8601) from which to list recorded calls. Date and time (ISO-8601) until which to list recorded calls. The ID of the workspace to filter by. List of call ID strings to filter. If not supplied, returns all calls in date range. List of user ID strings. If supplied, returns only calls hosted by these users. Maximum number of results to return. (Default: `600`) Context level: None, Basic (add links), or Extended (include link data). (Default: `None`) Timing for context data: 'Now' or 'TimeOfCall' or both. Only valid when context is Extended. Whether to include parties in the response. (Default: `false`) Fields to include for content: structure, topics, trackers, trackerOccurrences, pointsOfInterest, brief, outline, highlights, callOutcome, keyPoints (boolean values). Fields to include for interaction: speakers, video, personInteractionStats, questions (boolean values). Whether to include public comments in the response. (Default: `false`) Whether to include media in the response. (Default: `false`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "calls": { "items": { "additionalProperties": true, "type": "object" }, "title": "Calls", "type": "array" } }, "required": [ "success" ], "title": "GetExtensiveDataOutput", "type": "object" } ``` ### Parameters Date and time (ISO-8601) from which to list recorded calls. Date and time (ISO-8601) until which to list recorded calls. Pagination cursor returned by a previous call. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "request_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Request Id" }, "cursor": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Cursor" }, "calls": { "items": { "additionalProperties": true, "type": "object" }, "title": "Calls", "type": "array" } }, "required": [ "success" ], "title": "ListCallsOutput", "type": "object" } ``` ### Response ```json { "$defs": { "Workspace": { "additionalProperties": false, "description": "A Gong workspace entry.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" } }, "title": "Workspace", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "workspaces": { "items": { "$ref": "#/$defs/Workspace" }, "title": "Workspaces", "type": "array" } }, "required": [ "success" ], "title": "ListWorkspaceIdOptionsOutput", "type": "object" } ``` ### Parameters Date and time (ISO-8601) from which to filter calls. Date and time (ISO-8601) until which to filter calls. The ID of the workspace to filter by. List of call ID strings to retrieve transcripts for. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "call_transcripts": { "items": { "additionalProperties": true, "type": "object" }, "title": "Call Transcripts", "type": "array" } }, "required": [ "success" ], "title": "RetrieveTranscriptsOfCallsOutput", "type": "object" } ``` ## Limits & Quotas - Gong API rate limits vary by endpoint and plan tier. Consult your Gong admin for specific limits. - The `get_extensive_data` action paginates internally up to `max_results` (default 600). - Error model: non-2xx responses are caught and returned as `success=False` + `error` rather than raising. ## Related integrations ## Links - [Gong](https://www.gong.io) --- # Google Ad Manager Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/google-ad-manager Programmatic advertising platform for managing ad inventory, reporting, and campaign delivery {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/google_ad_manager/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Google Ad Manager logo Google Ad Manager logo ## Overview Add **Google Ad Manager** to any ModuleX agent or workflow. Programmatic advertising platform for managing ad inventory, reporting, and campaign delivery via the Google Ad Manager REST API (`admanager.googleapis.com/v1`). **Categories**: Marketing & Advertising · Advertising · Marketing · **Auth**: OAuth2 · **Actions**: 2 ## Authentication ### OAuth2 Authentication Connect using Google OAuth (recommended) #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Client ID | Google OAuth App Client ID | Yes | `-` | | Client Secret | Google OAuth App Client Secret | Yes | `-` | #### OAuth Configuration - **Authorization URL**: `https://accounts.google.com/o/oauth2/v2/auth` - **Token URL**: `https://oauth2.googleapis.com/token` - **Scopes**: `https://www.googleapis.com/auth/admanager` ## Available Actions ### Parameters The parent resource where this Report will be created. Format: networks/{networkCode} Identifier. The resource name of the report. Format: networks/{networkCode}/reports/{reportId} The visibility of the report. Allowed values: HIDDEN, DRAFT, SAVED The list of dimension identifiers to report on The list of metric identifiers to report on The type of this report. Allowed values: REPORT_TYPE_UNSPECIFIED, HISTORICAL The date range type of this report. Allowed values: fixed, relative The display name of the report The options for a scheduled report (ScheduleOptions schema) The filters for this report. Each element is a JSON object conforming to the Filter schema The time zone for the date range (IANA format). Defaults to publisher time zone ISO 4217 currency code. Defaults to publisher currency code Custom Dimension keys for CUSTOM_DIMENSION_* dimensions Custom field IDs for LINE_ITEM_CUSTOM_FIELD_* dimensions Custom field IDs for ORDER_CUSTOM_FIELD_* dimensions Custom field IDs for CREATIVE_CUSTOM_FIELD_* dimensions Time period column for comparison. Allowed values: TIME_PERIOD_COLUMN_UNSPECIFIED, TIME_PERIOD_COLUMN_DATE, TIME_PERIOD_COLUMN_WEEK, TIME_PERIOD_COLUMN_MONTH, TIME_PERIOD_COLUMN_QUARTER List of flags for this report (Flag schema objects) Default sorts for this report (Sort schema objects) Year of the start date (1-9999). Required when date_range is 'fixed' Month of the start date (1-12). Required when date_range is 'fixed' Day of the start date (1-31). Required when date_range is 'fixed' Year of the end date (1-9999). Required when date_range is 'fixed' Month of the end date (1-12). Required when date_range is 'fixed' Day of the end date (1-31). Required when date_range is 'fixed' Relative date range identifier (e.g. TODAY, YESTERDAY, LAST_7_DAYS). Required when date_range is 'relative' Comparison date range type. Allowed values: fixed, relative Year of comparison start date. Required when comparison_date_range is 'fixed' Month of comparison start date. Required when comparison_date_range is 'fixed' Day of comparison start date. Required when comparison_date_range is 'fixed' Year of comparison end date. Required when comparison_date_range is 'fixed' Month of comparison end date. Required when comparison_date_range is 'fixed' Day of comparison end date. Required when comparison_date_range is 'fixed' Relative comparison date range identifier (e.g. PREVIOUS_PERIOD, SAME_PERIOD_PREVIOUS_YEAR). Required when comparison_date_range is 'relative' ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "CreateReportOutput", "type": "object" } ``` ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "networks": { "items": { "type": "string" }, "title": "Networks", "type": "array" } }, "required": [ "success" ], "title": "ListNetworkOptionsOutput", "type": "object" } ``` ## Limits & Quotas - Google Ad Manager API enforces per-network rate limits; consult your network's quota settings in the Ad Manager UI. - Standard quota: 10,000 requests per day per project (default; can be increased via Google Cloud console). - Error model: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. ## Related integrations ## Links - [Google Ad Manager](https://admanager.google.com) --- # Google Ads Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/google-ads Google Ads API integration: run GAQL reports across Campaigns, Ad Groups, Ads, and Customers; create customer lists; send offline conversions; generate keyword ideas. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/google_ads/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Google Ads logo ## Overview Add **Google Ads** to any ModuleX agent or workflow. Google Ads API integration covering GAQL reports across Campaigns, Ad Groups, Ads, and Customers; Customer Match list management; offline conversion uploads; and keyword idea generation against the REST API at `googleads.googleapis.com`. **Categories**: Marketing & Advertising · Analytics · Business Services · **Auth**: OAuth2 · **Actions**: 10 ## Authentication ### OAuth2 Authentication Connect using Google OAuth2 (recommended). Requires a Google Ads API developer token in addition to the OAuth credentials. #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Client ID | Google OAuth2 Client ID from Cloud Console. | Yes | `123456789-xxxxxxxxxxxxxxxx.apps.googleusercontent.com` | | Client Secret | Google OAuth2 Client Secret. | Yes | `GOCSPX-xxxxxxxxxxxxxxxxxxxx` | | Developer Token | Google Ads API developer-token header value. Apply at https://ads.google.com/aw/apicenter (Manager Account required). | Yes | `xxxxxxxxxxxxxxxxxxxxxx` | #### OAuth Configuration - **Authorization URL**: `https://accounts.google.com/o/oauth2/v2/auth` - **Token URL**: `https://oauth2.googleapis.com/token` - **Scopes**: `https://www.googleapis.com/auth/adwords` ## Available Actions ### Parameters Login customer ID (the Manager Account ID used as login-customer-id header). Digits only, no dashes. ID of the CRM-based customer list to update. Email address of the contact to add. Managed customer (client) ID. Defaults to account_id when omitted. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "offline_user_data_job_resource_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Offline User Data Job Resource Name" }, "operation_resource_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Operation Resource Name" } }, "required": [ "success" ], "title": "AddContactToListByEmailOutput", "type": "object" } ``` ### Parameters Login customer ID (login-customer-id header). List of ad_group fields to select (e.g. 'ad_group.id', 'ad_group.name'). Each element is a string; the 'ad_group.' prefix is auto-added if missing. List of segments (e.g. 'segments.date'). Each element is a string; the 'segments.' prefix is auto-added. List of metric fields (e.g. 'metrics.clicks'). Each element is a string; the 'metrics.' prefix is auto-added. Optional list of ad_group IDs to filter by. Each element is a string (the numeric ID). Date range constant: CUSTOM, TODAY, YESTERDAY, LAST_7_DAYS, LAST_14_DAYS, LAST_30_DAYS, THIS_MONTH, LAST_MONTH, THIS_WEEK_SUN_TODAY, THIS_WEEK_MON_TODAY, LAST_WEEK_SUN_SAT, LAST_WEEK_MON_SUN, LAST_BUSINESS_WEEK. Custom range start (YYYY-MM-DD); required when date_range=CUSTOM. Custom range end (YYYY-MM-DD); required when date_range=CUSTOM. Field name to order results by. Order direction: ASC or DESC. (Default: `ASC`) Maximum number of results to return. Managed customer ID; defaults to account_id when omitted. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "query": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Query" }, "results": { "items": { "additionalProperties": true, "type": "object" }, "title": "Results", "type": "array" }, "field_mask": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Field Mask" }, "request_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Request Id" } }, "required": [ "success" ], "title": "CreateAdGroupReportOutput", "type": "object" } ``` ### Parameters Login customer ID (login-customer-id header). List of ad_group_ad fields to select (strings). List of segments (strings). List of metric fields (strings). Optional list of ad IDs to filter by (strings). Date range constant. See create_ad_group_report for valid values. Custom range start (YYYY-MM-DD); required when date_range=CUSTOM. Custom range end (YYYY-MM-DD); required when date_range=CUSTOM. Field name to order results by. Order direction: ASC or DESC. (Default: `ASC`) Maximum number of results to return. Managed customer ID; defaults to account_id when omitted. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "query": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Query" }, "results": { "items": { "additionalProperties": true, "type": "object" }, "title": "Results", "type": "array" }, "field_mask": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Field Mask" }, "request_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Request Id" } }, "required": [ "success" ], "title": "CreateAdReportOutput", "type": "object" } ``` ### Parameters Login customer ID (login-customer-id header). List of campaign fields to select (strings). List of segments (strings). List of metric fields (strings). Optional list of campaign IDs to filter by (strings). Date range constant. See create_ad_group_report for valid values. Custom range start (YYYY-MM-DD); required when date_range=CUSTOM. Custom range end (YYYY-MM-DD); required when date_range=CUSTOM. Field name to order results by. Order direction: ASC or DESC. (Default: `ASC`) Maximum number of results to return. Managed customer ID; defaults to account_id when omitted. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "query": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Query" }, "results": { "items": { "additionalProperties": true, "type": "object" }, "title": "Results", "type": "array" }, "field_mask": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Field Mask" }, "request_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Request Id" } }, "required": [ "success" ], "title": "CreateCampaignReportOutput", "type": "object" } ``` ### Parameters Login customer ID (login-customer-id header). Name of the user list. Which UserList variant to create. One of: crmBasedUserList, ruleBasedUserList, logicalUserList, basicUserList, lookalikeUserList. Determines which fields list_type_data should populate. Variant-specific payload. Merged under the list_type key. See https://developers.google.com/google-ads/api/reference/rpc/v21/UserList for the per-variant schema. (Default: `{}`) Optional description of the user list. Additional top-level UserList fields to merge into the create payload (e.g. membershipLifeSpan). (Default: `{}`) Managed customer ID; defaults to account_id when omitted. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "resource_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Resource Name" } }, "required": [ "success" ], "title": "CreateCustomerListOutput", "type": "object" } ``` ### Parameters Login customer ID (login-customer-id header). List of customer fields to select (strings). List of segments (strings). List of metric fields (strings). Optional list of customer IDs to filter by (strings). Date range constant. See create_ad_group_report for valid values. Custom range start (YYYY-MM-DD); required when date_range=CUSTOM. Custom range end (YYYY-MM-DD); required when date_range=CUSTOM. Field name to order results by. Order direction: ASC or DESC. (Default: `ASC`) Maximum number of results to return. Managed customer ID; defaults to account_id when omitted. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "query": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Query" }, "results": { "items": { "additionalProperties": true, "type": "object" }, "title": "Results", "type": "array" }, "field_mask": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Field Mask" }, "request_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Request Id" } }, "required": [ "success" ], "title": "CreateCustomerReportOutput", "type": "object" } ``` ### Parameters Login customer ID (login-customer-id header). Resource name to FROM-clause against (e.g. campaign, ad_group, ad_group_ad, customer). List of resource fields to select (strings). The resource prefix is auto-added if missing. List of segments (strings). List of metric fields (strings). Optional list of resource IDs to filter by (strings). Date range constant. See create_ad_group_report for valid values. Custom range start (YYYY-MM-DD); required when date_range=CUSTOM. Custom range end (YYYY-MM-DD); required when date_range=CUSTOM. Field name to order results by. Order direction: ASC or DESC. (Default: `ASC`) Maximum number of results to return. Managed customer ID; defaults to account_id when omitted. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "query": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Query" }, "results": { "items": { "additionalProperties": true, "type": "object" }, "title": "Results", "type": "array" }, "field_mask": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Field Mask" }, "request_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Request Id" } }, "required": [ "success" ], "title": "CreateReportOutput", "type": "object" } ``` ### Parameters Login customer ID (login-customer-id header). Managed customer ID to run the request against (required by the keyword ideas service). Body of the GenerateKeywordIdeasRequest. See https://developers.google.com/google-ads/api/reference/rpc/v22/KeywordPlanIdeaService/GenerateKeywordIdeas for the required structure (e.g. language, geoTargetConstants, keywordSeed). (Default: `{}`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "results": { "items": { "additionalProperties": true, "type": "object" }, "title": "Results", "type": "array" }, "total_size": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Total Size" }, "next_page_token": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Next Page Token" } }, "required": [ "success" ], "title": "GenerateKeywordIdeasOutput", "type": "object" } ``` ### Response ```json { "$defs": { "AccessibleCustomer": { "additionalProperties": false, "description": "One entry returned from ``customers:listAccessibleCustomers``.", "properties": { "resource_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Resource Name" }, "customer_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Customer Id" } }, "title": "AccessibleCustomer", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "customers": { "items": { "$ref": "#/$defs/AccessibleCustomer" }, "title": "Customers", "type": "array" } }, "required": [ "success" ], "title": "ListAccountIdOptionsOutput", "type": "object" } ``` ### Parameters Login customer ID (login-customer-id header). Name of the conversion action. ConversionActionType enum value (e.g. UPLOAD_CLICKS, UPLOAD_CALLS, WEBPAGE). See https://developers.google.com/google-ads/api/reference/rpc/v21/ConversionActionTypeEnum.ConversionActionType. Additional ConversionAction fields to merge into the create payload (e.g. category, status, valueSettings). (Default: `{}`) Managed customer ID; defaults to account_id when omitted. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "resource_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Resource Name" } }, "required": [ "success" ], "title": "SendOfflineConversionOutput", "type": "object" } ``` ## Limits & Quotas - **Daily operations quota** depends on the developer-token access level (Test, Basic, Standard). Standard tokens allow 15,000 operations/day initially. See https://developers.google.com/google-ads/api/docs/access-levels. - **Customer Match lists** typically take 6 to 12 hours to update after `add_contact_to_list_by_email` runs. - **`generate_keyword_ideas`** counts against the same quota and may be rate-limited per minute on Basic-access tokens. - **Error model**: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. The error string includes the API status code and body excerpt. ## Related integrations ## Links - [Google Ads](https://ads.google.com) --- # Google Analytics Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/google-analytics Manage Google Analytics 4 properties, list accounts, configure key events, and run analytics reports against the Google Analytics Admin and Data APIs. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/google_analytics/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Google Analytics logo ## Overview Add **Google Analytics** to any ModuleX agent or workflow. Manage Google Analytics 4 properties, list accounts, configure key events, and run analytics reports against the Google Analytics Admin (`analyticsadmin.googleapis.com`) and Data (`analyticsdata.googleapis.com`) APIs. **Categories**: Analytics & Data · Analytics & Reporting · Marketing · Productivity & Collaboration · **Auth**: OAuth2 · **Actions**: 6 ## Authentication ### OAuth2 Authentication Connect using Google OAuth (recommended) #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Client ID | Google Cloud OAuth 2.0 Client ID | Yes | `1234567890-abcdefghijklmnop.apps.googleusercontent.com` | | Client Secret | Google Cloud OAuth 2.0 Client Secret | Yes | `GOCSPX-xxxxxxxxxxxxxxxxxxxxxxxx` | #### OAuth Configuration - **Authorization URL**: `https://accounts.google.com/o/oauth2/v2/auth` - **Token URL**: `https://oauth2.googleapis.com/token` - **Scopes**: `https://www.googleapis.com/auth/analytics.edit`, `https://www.googleapis.com/auth/analytics.readonly` ## Available Actions ### Parameters Maximum number of accounts to return per page. The service may return fewer than this value. If unspecified, at most 50 accounts are returned (max 200). (Default: `50`) Optional page token from a previous response, used to retrieve the next page of accounts. ### Response ```json { "$defs": { "AccountSummary": { "additionalProperties": false, "description": "A Google Analytics account (admin API).", "properties": { "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "display_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Display Name" }, "account": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Account" } }, "title": "AccountSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "accounts": { "items": { "$ref": "#/$defs/AccountSummary" }, "title": "Accounts", "type": "array" }, "next_page_token": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Next Page Token" } }, "required": [ "success" ], "title": "ListAccountOptionsOutput", "type": "object" } ``` ### Response ```json { "$defs": { "PropertySummary": { "additionalProperties": false, "description": "A Google Analytics property summary (admin API).", "properties": { "property": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Property" }, "display_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Display Name" }, "property_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Property Type" }, "parent": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Parent" } }, "title": "PropertySummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "properties": { "type": "array", "items": { "$ref": "#/$defs/PropertySummary" }, "title": "Properties" } }, "required": [ "success" ], "title": "ListPropertyOptionsOutput", "type": "object" } ``` ### Parameters The parent account resource name, e.g. 'accounts/123456789'. Use list_account_options to discover available accounts. Human-readable display name for the new property. Max 100 UTF-16 code units. Reporting time zone for the property. Must be a valid IANA timezone identifier, e.g. 'America/Los_Angeles', 'Europe/London', 'Asia/Tokyo'. Industry category associated with the property. Valid values include AUTOMOTIVE, FINANCE, HEALTHCARE, TECHNOLOGY, TRAVEL, ARTS_AND_ENTERTAINMENT, GAMES, SHOPPING, JOBS_AND_EDUCATION, and others; pass INDUSTRY_CATEGORY_UNSPECIFIED if unsure. ISO 4217 currency code used in reports involving monetary values, e.g. 'USD', 'EUR', 'JPY'. ### Response ```json { "$defs": { "PropertyResource": { "additionalProperties": false, "description": "A full GA4 property resource (admin API).", "properties": { "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "parent": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Parent" }, "display_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Display Name" }, "industry_category": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Industry Category" }, "time_zone": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Time Zone" }, "currency_code": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Currency Code" }, "property_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Property Type" }, "create_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Create Time" }, "update_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Update Time" } }, "title": "PropertyResource", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "property": { "anyOf": [ { "$ref": "#/$defs/PropertyResource" }, { "type": "null" } ], "default": null }, "raw": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Raw" } }, "required": [ "success" ], "title": "CreateGa4PropertyOutput", "type": "object" } ``` ### Parameters Parent property resource name where the key event will be created. Format: 'properties/123'. Immutable event name for this key event. Examples: 'click', 'purchase', 'sign_up'. Method by which the key event is counted across multiple events within a session. Valid values: ONCE_PER_EVENT, ONCE_PER_SESSION, or COUNTING_METHOD_UNSPECIFIED. ### Response ```json { "$defs": { "KeyEventResource": { "additionalProperties": false, "description": "A GA4 key event resource (admin API).", "properties": { "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "event_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Event Name" }, "counting_method": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Counting Method" }, "create_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Create Time" }, "deletable": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Deletable" }, "custom": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Custom" } }, "title": "KeyEventResource", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "key_event": { "anyOf": [ { "$ref": "#/$defs/KeyEventResource" }, { "type": "null" } ], "default": null }, "raw": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Raw" } }, "required": [ "success" ], "title": "CreateKeyEventOutput", "type": "object" } ``` ### Parameters ID of the Universal Analytics view to query. Found in the legacy Google Analytics Admin UI under View Settings. Start date in YYYY-MM-DD format. End date in YYYY-MM-DD format. List of metric expression strings (each becomes a {expression: <metric>} entry), e.g. ['ga:sessions', 'ga:pageviews']. Optional list of dimension name strings (each becomes a {name: <dimension>} entry), e.g. ['ga:country', 'ga:browser']. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "reports": { "items": { "additionalProperties": true, "type": "object" }, "title": "Reports", "type": "array" }, "raw": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Raw" } }, "required": [ "success" ], "title": "RunReportOutput", "type": "object" } ``` ### Parameters GA4 property identifier. Accepts either the bare numeric ID ('123456789') or the full resource path ('properties/123456789'); only the numeric ID is sent in the URL. Start date in YYYY-MM-DD format. End date in YYYY-MM-DD format. List of metric name strings (each becomes a {name: <metric>} entry), e.g. ['activeUsers', 'screenPageViews']. Optional list of dimension name strings (each becomes a {name: <dimension>} entry), e.g. ['country', 'deviceCategory']. Optional GA4 FilterExpression object restricting which dimension values appear in the report. See the Google Analytics Data API v1 docs for the schema. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "row_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Row Count" }, "dimension_headers": { "items": { "additionalProperties": true, "type": "object" }, "title": "Dimension Headers", "type": "array" }, "metric_headers": { "items": { "additionalProperties": true, "type": "object" }, "title": "Metric Headers", "type": "array" }, "rows": { "items": { "additionalProperties": true, "type": "object" }, "title": "Rows", "type": "array" }, "totals": { "items": { "additionalProperties": true, "type": "object" }, "title": "Totals", "type": "array" }, "raw": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Raw" } }, "required": [ "success" ], "title": "RunReportInGa4Output", "type": "object" } ``` ## Limits & Quotas - **Admin API** — default quota is 1,200 requests per minute per project, with a daily ceiling of 600,000 requests per project. See the [Admin API quotas docs](https://developers.google.com/analytics/devguides/config/admin/v1/quotas) for the per-method breakdown. - **Data API** — quotas are tracked separately per property (core tokens, realtime tokens, server errors). See the [Data API quotas docs](https://developers.google.com/analytics/devguides/reporting/data/v1/quotas) for the multi-token model and per-tier limits (Standard vs. Analytics 360). - **Universal Analytics Reporting API v4** is included for backward compatibility only; Universal Analytics properties stopped processing data on July 1, 2024. `run_report` will return `success=False` with an API error for any non-GA4 view. - **Error model** — non-2xx HTTP responses and timeouts are caught and returned as `success=False` plus a populated `error` string rather than raised. Plan for agent-side retries based on the error string. ## Related integrations ## Links - [Google Analytics](https://analytics.google.com) --- # Google AppSheet Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/google-appsheet Manage rows in Google AppSheet tables via the AppSheet API {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/google_appsheet/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Google AppSheet logo ## Overview Add **Google AppSheet** to any ModuleX agent or workflow. Manage rows in Google AppSheet tables (add, read, update, delete) via the AppSheet REST API (`api.appsheet.com`). **Categories**: Productivity & Collaboration · No Code · Databases · **Auth**: AppSheet API Key · **Actions**: 4 ## Authentication ### AppSheet API Key Authenticate using your AppSheet Application Access Key and App ID Open your app in AppSheet at https://www.appsheet.com Go to Settings > Integrations > IN: from cloud services to your app Enable the API and copy the Application Access Key Copy the App ID from the URL or Settings > App Info #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | App ID | Your AppSheet application ID (found in Settings > App Info or the app URL) | Yes | `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` | | Application Access Key | Your AppSheet Application Access Key from Settings > Integrations | Yes | `V2-xxxxx-xxxxx-xxxxx-xxxxx-xxxxx` | ## Available Actions ### Parameters Name of the table to add a row to JSON object representing the row data to add, with keys matching table column names ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "rows": { "items": { "additionalProperties": true, "type": "object" }, "title": "Rows", "type": "array" } }, "required": [ "success" ], "title": "AddRowOutput", "type": "object" } ``` ### Parameters Name of the table to delete a row from JSON object containing the key field values of the record to delete ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "rows": { "items": { "additionalProperties": true, "type": "object" }, "title": "Rows", "type": "array" } }, "required": [ "success" ], "title": "DeleteRowOutput", "type": "object" } ``` ### Parameters Name of the table to read rows from An expression to filter and format the rows returned, e.g. Filter(TableName, [Column] = "Value") Filter results using key field values, e.g. {"First Name": "John"} ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "rows": { "items": { "additionalProperties": true, "type": "object" }, "title": "Rows", "type": "array" } }, "required": [ "success" ], "title": "GetRowsOutput", "type": "object" } ``` ### Parameters Name of the table containing the row to update JSON object with the key field values of the record to update and any fields to change ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "rows": { "items": { "additionalProperties": true, "type": "object" }, "title": "Rows", "type": "array" } }, "required": [ "success" ], "title": "UpdateRowOutput", "type": "object" } ``` ## Limits & Quotas - AppSheet API has no publicly documented per-minute rate limits, but Google recommends keeping request volume moderate for shared-tenant apps. - Each app must have the API enabled in Settings > Integrations. - Error model: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. ## Related integrations ## Links - [Google AppSheet](https://www.appsheet.com) --- # Google Calendar Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/google-calendar Manage Google Calendar events, calendars, and availability via the Google Calendar v3 REST API. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/google_calendar/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Google Calendar logo ## Overview Add **Google Calendar** to any ModuleX agent or workflow. Manage Google Calendar events, calendars, attendees, recurring series, and free/busy availability via the Google Calendar v3 REST API (`https://www.googleapis.com/calendar/v3`). **Categories**: Scheduling & Events · Productivity & Collaboration · Scheduling · **Auth**: OAuth2 · **Actions**: 16 ## Authentication ### OAuth2 Authentication Connect using Google OAuth (recommended). Grants access to read and manage the user's calendars and events. #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Client ID | Google Cloud OAuth Client ID for the Calendar API. | Yes | `xxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com` | | Client Secret | Google Cloud OAuth Client Secret for the Calendar API. | Yes | `GOCSPX-xxxxxxxxxxxxxxxxxxxxxxxx` | #### OAuth Configuration - **Authorization URL**: `https://accounts.google.com/o/oauth2/v2/auth` - **Token URL**: `https://oauth2.googleapis.com/token` - **Scopes**: `https://www.googleapis.com/auth/calendar`, `https://www.googleapis.com/auth/calendar.events` ## Available Actions ### Parameters Calendar identifier (use 'primary' for the user's primary calendar, or a calendar email/ID). (Default: `primary`) ID of the event to update. List of attendee email addresses to add (existing attendees are preserved). Whether to send notifications about the update. One of: all, externalOnly, none. ### Response ```json { "$defs": { "EventSummary": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "summary": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Summary" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "location": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Location" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "htmlLink": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Htmllink" }, "created": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created" }, "updated": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated" }, "start": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Start" }, "end": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "End" }, "creator": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Creator" }, "organizer": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Organizer" }, "attendees": { "items": { "additionalProperties": true, "type": "object" }, "title": "Attendees", "type": "array" }, "recurrence": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Recurrence" }, "recurringEventId": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Recurringeventid" }, "visibility": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Visibility" }, "colorId": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Colorid" }, "conferenceData": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Conferencedata" }, "hangoutLink": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Hangoutlink" }, "kind": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Kind" }, "etag": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Etag" }, "iCalUID": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Icaluid" }, "sequence": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Sequence" }, "eventType": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Eventtype" } }, "title": "EventSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "event": { "anyOf": [ { "$ref": "#/$defs/EventSummary" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "AddAttendeesToEventOutput", "type": "object" } ``` ### Parameters Calendar identifier (use 'primary' for the user's primary calendar, or a calendar email/ID). (Default: `primary`) Event title. Event start. For all-day events: yyyy-mm-dd. For timed events: RFC3339, e.g. 2025-01-15T10:00:00-05:00. Event end. Same formats as event_start_date. Event location. Event description. List of attendee email addresses. Color ID for the event. See list_color_id_options for valid values. IANA time zone name for the event (e.g. 'America/New_York'). Defaults to the calendar's time zone. Whether to send invitations. One of: all, externalOnly, none. Whether to attach a Google Meet conference link to this event. (Default: `false`) Event visibility. One of: default, public, private, confidential. Recurrence frequency. One of: DAILY, WEEKLY, MONTHLY, YEARLY. Recurrence interval (1 = every period, 2 = every other, ...). Defaults to 1. Two-letter day codes for WEEKLY recurrence (SU,MO,TU,WE,TH,FR,SA). End date for recurrence (yyyy-mm-dd). Mutually exclusive with repeat_times. Number of occurrences. Mutually exclusive with repeat_until. ### Response ```json { "$defs": { "EventSummary": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "summary": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Summary" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "location": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Location" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "htmlLink": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Htmllink" }, "created": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created" }, "updated": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated" }, "start": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Start" }, "end": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "End" }, "creator": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Creator" }, "organizer": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Organizer" }, "attendees": { "items": { "additionalProperties": true, "type": "object" }, "title": "Attendees", "type": "array" }, "recurrence": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Recurrence" }, "recurringEventId": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Recurringeventid" }, "visibility": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Visibility" }, "colorId": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Colorid" }, "conferenceData": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Conferencedata" }, "hangoutLink": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Hangoutlink" }, "kind": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Kind" }, "etag": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Etag" }, "iCalUID": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Icaluid" }, "sequence": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Sequence" }, "eventType": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Eventtype" } }, "title": "EventSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "event": { "anyOf": [ { "$ref": "#/$defs/EventSummary" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "CreateEventOutput", "type": "object" } ``` ### Parameters Calendar identifier (use 'primary' for the user's primary calendar). (Default: `primary`) ID of the event to delete. Whether to send cancellation notifications. One of: all, externalOnly, none. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "eventId": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Eventid" }, "statusCode": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Statuscode" } }, "required": [ "success" ], "title": "DeleteEventOutput", "type": "object" } ``` ### Parameters Calendar identifier (use 'primary' for the user's primary calendar). (Default: `primary`) ### Response ```json { "$defs": { "CalendarSummary": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "summary": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Summary" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "location": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Location" }, "timeZone": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Timezone" }, "kind": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Kind" }, "etag": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Etag" }, "accessRole": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Accessrole" }, "primary": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Primary" }, "selected": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Selected" }, "backgroundColor": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Backgroundcolor" }, "foregroundColor": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Foregroundcolor" }, "colorId": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Colorid" }, "hidden": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Hidden" } }, "title": "CalendarSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "calendar": { "anyOf": [ { "$ref": "#/$defs/CalendarSummary" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "GetCalendarOutput", "type": "object" } ``` ### Response ```json { "$defs": { "CalendarSummary": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "summary": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Summary" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "location": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Location" }, "timeZone": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Timezone" }, "kind": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Kind" }, "etag": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Etag" }, "accessRole": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Accessrole" }, "primary": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Primary" }, "selected": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Selected" }, "backgroundColor": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Backgroundcolor" }, "foregroundColor": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Foregroundcolor" }, "colorId": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Colorid" }, "hidden": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Hidden" } }, "title": "CalendarSummary", "type": "object" }, "SettingItem": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "value": { "anyOf": [ {}, { "type": "null" } ], "default": null, "title": "Value" }, "kind": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Kind" }, "etag": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Etag" } }, "title": "SettingItem", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "primaryCalendar": { "anyOf": [ { "$ref": "#/$defs/CalendarSummary" }, { "type": "null" } ], "default": null }, "calendars": { "items": { "$ref": "#/$defs/CalendarSummary" }, "title": "Calendars", "type": "array" }, "settings": { "items": { "$ref": "#/$defs/SettingItem" }, "title": "Settings", "type": "array" }, "timezone": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Timezone" }, "locale": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Locale" }, "colors": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Colors" } }, "required": [ "success" ], "title": "GetCurrentUserOutput", "type": "object" } ``` ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Date" }, "time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Time" }, "timezone": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Timezone" }, "timezoneOffset": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Timezoneoffset" }, "timestamp": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Timestamp" }, "isoString": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Isostring" }, "rfc3339": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Rfc3339" } }, "required": [ "success" ], "title": "GetDateTimeOutput", "type": "object" } ``` ### Parameters Calendar identifier (use 'primary' for the user's primary calendar). (Default: `primary`) ID of the event to retrieve. ### Response ```json { "$defs": { "EventSummary": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "summary": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Summary" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "location": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Location" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "htmlLink": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Htmllink" }, "created": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created" }, "updated": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated" }, "start": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Start" }, "end": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "End" }, "creator": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Creator" }, "organizer": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Organizer" }, "attendees": { "items": { "additionalProperties": true, "type": "object" }, "title": "Attendees", "type": "array" }, "recurrence": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Recurrence" }, "recurringEventId": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Recurringeventid" }, "visibility": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Visibility" }, "colorId": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Colorid" }, "conferenceData": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Conferencedata" }, "hangoutLink": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Hangoutlink" }, "kind": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Kind" }, "etag": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Etag" }, "iCalUID": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Icaluid" }, "sequence": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Sequence" }, "eventType": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Eventtype" } }, "title": "EventSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "event": { "anyOf": [ { "$ref": "#/$defs/EventSummary" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "GetEventOutput", "type": "object" } ``` ### Response ```json { "$defs": { "CalendarSummary": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "summary": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Summary" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "location": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Location" }, "timeZone": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Timezone" }, "kind": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Kind" }, "etag": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Etag" }, "accessRole": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Accessrole" }, "primary": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Primary" }, "selected": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Selected" }, "backgroundColor": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Backgroundcolor" }, "foregroundColor": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Foregroundcolor" }, "colorId": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Colorid" }, "hidden": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Hidden" } }, "title": "CalendarSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "calendars": { "items": { "$ref": "#/$defs/CalendarSummary" }, "title": "Calendars", "type": "array" } }, "required": [ "success" ], "title": "ListCalendarsOutput", "type": "object" } ``` ### Response ```json { "$defs": { "ColorEntry": { "additionalProperties": false, "properties": { "label": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Label" }, "value": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Value" } }, "title": "ColorEntry", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "options": { "items": { "$ref": "#/$defs/ColorEntry" }, "title": "Options", "type": "array" } }, "required": [ "success" ], "title": "ListColorIdOptionsOutput", "type": "object" } ``` ### Parameters Calendar identifier (use 'primary' for the user's primary calendar). (Default: `primary`) ID of the recurring event. Maximum attendees per instance to include. Maximum number of instances to return across paged calls. Include cancelled instances. Lower bound (RFC3339) for instance start times. Upper bound (RFC3339) for instance start times. IANA time zone used in the response. ### Response ```json { "$defs": { "EventSummary": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "summary": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Summary" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "location": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Location" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "htmlLink": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Htmllink" }, "created": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created" }, "updated": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated" }, "start": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Start" }, "end": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "End" }, "creator": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Creator" }, "organizer": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Organizer" }, "attendees": { "items": { "additionalProperties": true, "type": "object" }, "title": "Attendees", "type": "array" }, "recurrence": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Recurrence" }, "recurringEventId": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Recurringeventid" }, "visibility": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Visibility" }, "colorId": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Colorid" }, "conferenceData": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Conferencedata" }, "hangoutLink": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Hangoutlink" }, "kind": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Kind" }, "etag": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Etag" }, "iCalUID": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Icaluid" }, "sequence": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Sequence" }, "eventType": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Eventtype" } }, "title": "EventSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "instances": { "items": { "$ref": "#/$defs/EventSummary" }, "title": "Instances", "type": "array" } }, "required": [ "success" ], "title": "ListEventInstancesOutput", "type": "object" } ``` ### Parameters Calendar identifier (use 'primary' for the user's primary calendar). (Default: `primary`) Specifies an iCalendar UID to filter by. Maximum attendees per event to include. Maximum number of events to return across paged calls. Sort order. One of: startTime, updated. startTime requires single_events=true. propertyName=value constraint on private extended properties. Free-text search across event fields. propertyName=value constraint on shared extended properties. Include cancelled events. Include hidden invitations. Expand recurring events into individual instances. Upper bound (RFC3339) for event start times. Lower bound (RFC3339) for event start times. IANA time zone used in the response. Lower bound (RFC3339) for event last-modified time. Filter by event type. Values: default, focusTime, outOfOffice, workingLocation. ### Response ```json { "$defs": { "EventSummary": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "summary": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Summary" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "location": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Location" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "htmlLink": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Htmllink" }, "created": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created" }, "updated": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated" }, "start": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Start" }, "end": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "End" }, "creator": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Creator" }, "organizer": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Organizer" }, "attendees": { "items": { "additionalProperties": true, "type": "object" }, "title": "Attendees", "type": "array" }, "recurrence": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Recurrence" }, "recurringEventId": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Recurringeventid" }, "visibility": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Visibility" }, "colorId": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Colorid" }, "conferenceData": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Conferencedata" }, "hangoutLink": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Hangoutlink" }, "kind": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Kind" }, "etag": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Etag" }, "iCalUID": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Icaluid" }, "sequence": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Sequence" }, "eventType": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Eventtype" } }, "title": "EventSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "events": { "items": { "$ref": "#/$defs/EventSummary" }, "title": "Events", "type": "array" } }, "required": [ "success" ], "title": "ListEventsOutput", "type": "object" } ``` ### Parameters List of calendar IDs to query (use 'primary' for the user's primary calendar). (Default: `["primary"]`) Start of the query window (RFC3339). End of the query window (RFC3339). IANA time zone for the response. ### Response ```json { "$defs": { "FreeBusyCalendar": { "additionalProperties": false, "properties": { "busy": { "items": { "$ref": "#/$defs/FreeBusyPeriod" }, "title": "Busy", "type": "array" }, "errors": { "anyOf": [ { "items": { "additionalProperties": true, "type": "object" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Errors" } }, "title": "FreeBusyCalendar", "type": "object" }, "FreeBusyPeriod": { "additionalProperties": false, "properties": { "start": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Start" }, "end": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "End" } }, "title": "FreeBusyPeriod", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "timeMin": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Timemin" }, "timeMax": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Timemax" }, "calendars": { "additionalProperties": { "$ref": "#/$defs/FreeBusyCalendar" }, "title": "Calendars", "type": "object" } }, "required": [ "success" ], "title": "QueryFreeBusyCalendarsOutput", "type": "object" } ``` ### Parameters Calendar identifier (use 'primary' for the user's primary calendar). (Default: `primary`) Natural-language event description, e.g. 'Meet with Sarah at 3pm Friday'. Optional list of attendee email addresses to add after parsing. ### Response ```json { "$defs": { "EventSummary": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "summary": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Summary" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "location": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Location" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "htmlLink": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Htmllink" }, "created": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created" }, "updated": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated" }, "start": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Start" }, "end": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "End" }, "creator": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Creator" }, "organizer": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Organizer" }, "attendees": { "items": { "additionalProperties": true, "type": "object" }, "title": "Attendees", "type": "array" }, "recurrence": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Recurrence" }, "recurringEventId": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Recurringeventid" }, "visibility": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Visibility" }, "colorId": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Colorid" }, "conferenceData": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Conferencedata" }, "hangoutLink": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Hangoutlink" }, "kind": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Kind" }, "etag": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Etag" }, "iCalUID": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Icaluid" }, "sequence": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Sequence" }, "eventType": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Eventtype" } }, "title": "EventSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "event": { "anyOf": [ { "$ref": "#/$defs/EventSummary" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "QuickAddEventOutput", "type": "object" } ``` ### Parameters Calendar identifier (use 'primary' for the user's primary calendar). (Default: `primary`) ID of the event to update. New event title. New start (yyyy-mm-dd for all-day, or RFC3339 for timed). New end (yyyy-mm-dd for all-day, or RFC3339 for timed). New event location. New event description. Replacement list of attendee email addresses. New color ID for the event. IANA time zone (defaults to the event's existing time zone). Whether to send notifications. One of: all, externalOnly, none. Updated recurrence frequency. One of: DAILY, WEEKLY, MONTHLY, YEARLY. Updated recurrence interval. Two-letter day codes for WEEKLY recurrence. New end date for recurrence (yyyy-mm-dd). New number of occurrences. ### Response ```json { "$defs": { "EventSummary": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "summary": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Summary" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "location": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Location" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "htmlLink": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Htmllink" }, "created": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created" }, "updated": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated" }, "start": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Start" }, "end": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "End" }, "creator": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Creator" }, "organizer": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Organizer" }, "attendees": { "items": { "additionalProperties": true, "type": "object" }, "title": "Attendees", "type": "array" }, "recurrence": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Recurrence" }, "recurringEventId": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Recurringeventid" }, "visibility": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Visibility" }, "colorId": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Colorid" }, "conferenceData": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Conferencedata" }, "hangoutLink": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Hangoutlink" }, "kind": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Kind" }, "etag": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Etag" }, "iCalUID": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Icaluid" }, "sequence": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Sequence" }, "eventType": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Eventtype" } }, "title": "EventSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "event": { "anyOf": [ { "$ref": "#/$defs/EventSummary" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "UpdateEventOutput", "type": "object" } ``` ### Parameters Calendar identifier (use 'primary' for the user's primary calendar). (Default: `primary`) ID of the specific recurring-event instance to update. New event title. New start (yyyy-mm-dd for all-day, or RFC3339 for timed). New end (yyyy-mm-dd for all-day, or RFC3339 for timed). New event location. New event description. Replacement list of attendee email addresses. New color ID for the event instance. IANA time zone (defaults to the instance's existing time zone). Whether to send notifications. One of: all, externalOnly, none. ### Response ```json { "$defs": { "EventSummary": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "summary": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Summary" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "location": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Location" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "htmlLink": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Htmllink" }, "created": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created" }, "updated": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated" }, "start": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Start" }, "end": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "End" }, "creator": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Creator" }, "organizer": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Organizer" }, "attendees": { "items": { "additionalProperties": true, "type": "object" }, "title": "Attendees", "type": "array" }, "recurrence": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Recurrence" }, "recurringEventId": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Recurringeventid" }, "visibility": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Visibility" }, "colorId": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Colorid" }, "conferenceData": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Conferencedata" }, "hangoutLink": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Hangoutlink" }, "kind": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Kind" }, "etag": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Etag" }, "iCalUID": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Icaluid" }, "sequence": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Sequence" }, "eventType": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Eventtype" } }, "title": "EventSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "event": { "anyOf": [ { "$ref": "#/$defs/EventSummary" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "UpdateEventInstanceOutput", "type": "object" } ``` ### Parameters Calendar identifier (use 'primary' for the user's primary calendar). (Default: `primary`) ID of the parent recurring event. ID of the instance from which the split begins (this and all following instances are updated). New event title for the split series. New start (yyyy-mm-dd or RFC3339) for the split series. New end (yyyy-mm-dd or RFC3339) for the split series. New location for the split series. New description for the split series. Replacement list of attendee email addresses. New color ID for the split series. IANA time zone for the split series. Whether to send notifications. One of: all, externalOnly, none. Optional new recurrence frequency for the split series. Optional new recurrence interval for the split series. Optional new recurrence end date (yyyy-mm-dd). Optional new occurrence count. ### Response ```json { "$defs": { "EventSummary": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "summary": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Summary" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "location": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Location" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "htmlLink": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Htmllink" }, "created": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created" }, "updated": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated" }, "start": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Start" }, "end": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "End" }, "creator": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Creator" }, "organizer": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Organizer" }, "attendees": { "items": { "additionalProperties": true, "type": "object" }, "title": "Attendees", "type": "array" }, "recurrence": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Recurrence" }, "recurringEventId": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Recurringeventid" }, "visibility": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Visibility" }, "colorId": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Colorid" }, "conferenceData": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Conferencedata" }, "hangoutLink": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Hangoutlink" }, "kind": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Kind" }, "etag": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Etag" }, "iCalUID": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Icaluid" }, "sequence": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Sequence" }, "eventType": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Eventtype" } }, "title": "EventSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "originalEventId": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Originaleventid" }, "newEvent": { "anyOf": [ { "$ref": "#/$defs/EventSummary" }, { "type": "null" } ], "default": null }, "trimmedRecurrence": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Trimmedrecurrence" } }, "required": [ "success" ], "title": "UpdateFollowingInstancesOutput", "type": "object" } ``` ## Limits & Quotas - Per-user default quota: ~1,000,000 queries/day; per-user per-100-seconds quota also applies (see [Google Calendar API quotas](https://developers.google.com/calendar/api/guides/quota)). - Quotas are shared across all Google Calendar API methods for the authenticated user. - `list_events` and `list_event_instances` page through results internally and honor `max_results` as a soft cap across pages. - `update_following_instances` performs four sequential API calls (get original, get instance, delete instance, trim recurrence, create new event); on partial failure the deleted instance is restored automatically when possible. Network or quota errors mid-flow can leave the series in a manually-recoverable state. - Error model: non-2xx responses raise (Pattern A). The runtime surfaces them as failed tool calls; the agent decides whether to retry. ## Related integrations ## Links - [Google Calendar](https://calendar.google.com) --- # Google Cloud Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/google-cloud Google Cloud Platform services including Cloud Storage, BigQuery, Compute Engine, and Cloud Logging {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/google_cloud/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Google Cloud logo ## Overview Add **Google Cloud** to any ModuleX agent or workflow. Google Cloud Platform integration providing Cloud Storage bucket and object management, BigQuery query execution and streaming inserts, BigQuery Data Transfer scheduled queries, Compute Engine instance control, and Cloud Logging via the GCP REST APIs. **Categories**: Cloud Infrastructure · Data & Analytics · Developer Tools & Infrastructure · **Auth**: Service Account Key · **Actions**: 11 ## Authentication ### Service Account Key Authenticate using a GCP service account JSON key file #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Service Account Key JSON | The full JSON content of your GCP service account key file | Yes | `{"type":"service_account","project_id":"...","private_key":"...","client_email":"..."}` | ## Available Actions ### Parameters Globally unique bucket name Bucket location (e.g. US, EU, us-central1). Defaults to US (Default: `US`) Storage class: STANDARD, NEARLINE, COLDLINE, or ARCHIVE (Default: `STANDARD`) ### Response ```json { "$defs": { "BucketMetadata": { "additionalProperties": false, "description": "Metadata for a Google Cloud Storage bucket.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "location": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Location" }, "storage_class": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Storage Class" }, "time_created": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Time Created" }, "updated": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated" }, "project_number": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Project Number" } }, "title": "BucketMetadata", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "bucket": { "anyOf": [ { "$ref": "#/$defs/BucketMetadata" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "CreateBucketOutput", "type": "object" } ``` ### Parameters Name of the bucket ### Response ```json { "$defs": { "BucketMetadata": { "additionalProperties": false, "description": "Metadata for a Google Cloud Storage bucket.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "location": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Location" }, "storage_class": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Storage Class" }, "time_created": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Time Created" }, "updated": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated" }, "project_number": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Project Number" } }, "title": "BucketMetadata", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "bucket": { "anyOf": [ { "$ref": "#/$defs/BucketMetadata" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "GetBucketOutput", "type": "object" } ``` ### Response ```json { "$defs": { "BucketMetadata": { "additionalProperties": false, "description": "Metadata for a Google Cloud Storage bucket.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "location": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Location" }, "storage_class": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Storage Class" }, "time_created": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Time Created" }, "updated": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated" }, "project_number": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Project Number" } }, "title": "BucketMetadata", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "buckets": { "items": { "$ref": "#/$defs/BucketMetadata" }, "title": "Buckets", "type": "array" } }, "required": [ "success" ], "title": "ListBucketsOutput", "type": "object" } ``` ### Parameters Name of the bucket to search Object name prefix to filter by Delimiter for directory-like listing (commonly '/') ### Response ```json { "$defs": { "ObjectMetadata": { "additionalProperties": false, "description": "Metadata for a Google Cloud Storage object.", "properties": { "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "bucket": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Bucket" }, "size": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Size" }, "content_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Content Type" }, "time_created": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Time Created" }, "updated": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated" }, "md5_hash": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Md5 Hash" } }, "title": "ObjectMetadata", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "objects": { "items": { "$ref": "#/$defs/ObjectMetadata" }, "title": "Objects", "type": "array" } }, "required": [ "success" ], "title": "SearchObjectsOutput", "type": "object" } ``` ### Parameters Name of the bucket containing the object Full path/name of the object in the bucket ### Response ```json { "$defs": { "ObjectMetadata": { "additionalProperties": false, "description": "Metadata for a Google Cloud Storage object.", "properties": { "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "bucket": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Bucket" }, "size": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Size" }, "content_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Content Type" }, "time_created": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Time Created" }, "updated": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated" }, "md5_hash": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Md5 Hash" } }, "title": "ObjectMetadata", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "metadata": { "anyOf": [ { "$ref": "#/$defs/ObjectMetadata" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "GetObjectOutput", "type": "object" } ``` ### Parameters Name of the destination bucket Destination path/name for the object in the bucket Text content to upload MIME type of the content (e.g. text/plain, application/json) (Default: `text/plain`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "object_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Object Name" }, "bucket": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Bucket" } }, "required": [ "success" ], "title": "UploadObjectOutput", "type": "object" } ``` ### Parameters Name of the log to write to Log message text Log severity: DEFAULT, DEBUG, INFO, NOTICE, WARNING, ERROR, CRITICAL, ALERT, or EMERGENCY (Default: `DEFAULT`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" } }, "required": [ "success" ], "title": "LoggingWriteLogOutput", "type": "object" } ``` ### Parameters GoogleSQL query to execute Dataset location (e.g. US, EU). Must match dataset location (Default: `US`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "rows": { "items": { "additionalProperties": { "anyOf": [ { "type": "string" }, { "type": "integer" }, { "type": "number" }, { "type": "boolean" }, { "type": "null" } ] }, "type": "object" }, "title": "Rows", "type": "array" }, "total_rows": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Total Rows" } }, "required": [ "success" ], "title": "RunQueryOutput", "type": "object" } ``` ### Parameters BigQuery dataset ID BigQuery table ID Array of row objects to insert, each with column names as keys ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "inserted_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Inserted Count" } }, "required": [ "success" ], "title": "BigqueryInsertRowsOutput", "type": "object" } ``` ### Parameters Destination dataset ID for query results Human-readable name for the scheduled query GoogleSQL query to schedule Geographic location of the dataset (e.g. us, eu) (Default: `us`) Schedule in cron-like format (e.g. 'every 24 hours', '1st monday of month 09:00') Write behavior: WRITE_TRUNCATE or WRITE_APPEND (Default: `WRITE_TRUNCATE`) Destination table name template, can use {run_date} or {run_time} variables (Default: `logs`) ### Response ```json { "$defs": { "TransferConfig": { "additionalProperties": false, "description": "A BigQuery Data Transfer scheduled query config.", "properties": { "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "display_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Display Name" }, "data_source_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Data Source Id" }, "schedule": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Schedule" }, "state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "State" }, "destination_dataset_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Destination Dataset Id" } }, "title": "TransferConfig", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "transfer_config": { "anyOf": [ { "$ref": "#/$defs/TransferConfig" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "CreateScheduledQueryOutput", "type": "object" } ``` ### Parameters Compute Engine zone (e.g. us-central1-a) Name of the VM instance Action to perform: start or stop ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "operation_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Operation Name" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" } }, "required": [ "success" ], "title": "SwitchInstanceBootStatusOutput", "type": "object" } ``` ## Limits & Quotas - **Cloud Storage**: 1,000 bucket creates per project per 2 seconds; 5,000 object mutations per bucket per second. - **BigQuery**: 100 concurrent queries per project; streaming inserts limited to 50,000 rows per request and 10 MB per request. - **Compute Engine**: API rate limits vary by method (typically 20 requests/second per project per region). - **Cloud Logging**: 60 write requests per second per project. - **Error model**: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. ## Related integrations ## Links - [Google Cloud](https://cloud.google.com) --- # Google Contacts Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/google-contacts Manage Google People (Contacts) — create, list, get, update, and delete contacts plus list Google Workspace directory people. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/google_contacts/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Google Contacts logo ## Overview Add **Google Contacts** to any ModuleX agent or workflow. Manage Google Contacts (People API v1) — create, list, get, update, and delete the authenticated user's contacts, plus list Google Workspace directory people. All calls go to `https://people.googleapis.com/v1`. **Categories**: Productivity & Collaboration · CRM · **Auth**: Google OAuth2 · **Actions**: 6 ## Authentication ### Google OAuth2 Connect using Google OAuth (recommended) — Contacts + Directory scopes. #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Client ID | Google Cloud OAuth 2.0 Client ID. | Yes | `xxxxxxxxxxxx-xxxxxxxxxxxxxxxx.apps.googleusercontent.com` | | Client Secret | Google Cloud OAuth 2.0 Client Secret. | Yes | `GOCSPX-xxxxxxxxxxxxxxxxxxxxxxxx` | #### OAuth Configuration - **Authorization URL**: `https://accounts.google.com/o/oauth2/v2/auth` - **Token URL**: `https://oauth2.googleapis.com/token` - **Scopes**: `https://www.googleapis.com/auth/contacts`, `https://www.googleapis.com/auth/directory.readonly` ## Available Actions ### Parameters Contact fields to populate and return — any of: names, emailAddresses, phoneNumbers, addresses, biographies, birthdays, calendarUrls, genders, urls. Contact's first name (used when person_fields includes 'names'). Contact's middle name (used when person_fields includes 'names'). Contact's last name (used when person_fields includes 'names'). Contact's email address (used when person_fields includes 'emailAddresses'). Contact's phone number (used when person_fields includes 'phoneNumbers'). Street address (used when person_fields includes 'addresses'). City (used when person_fields includes 'addresses'). State / region (used when person_fields includes 'addresses'). Postal / zip code (used when person_fields includes 'addresses'). Country (used when person_fields includes 'addresses'). Biography text (used when person_fields includes 'biographies'). Birthday in YYYY-MM-DD format (used when person_fields includes 'birthdays'). Calendar URL (used when person_fields includes 'calendarUrls'). Gender: male, female, or unspecified (used when person_fields includes 'genders'). List of associated URLs (used when person_fields includes 'urls'). Optional extra fields merged into the Person request body. See https://developers.google.com/people/api/rest/v1/people for the full schema. ### Response ```json { "$defs": { "ContactSummary": { "additionalProperties": false, "description": "A Google People `Person` resource, projected to the fields most tools read.", "properties": { "resource_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Resource Name" }, "etag": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Etag" }, "display_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Display Name" }, "given_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Given Name" }, "family_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Family Name" }, "middle_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Middle Name" }, "email_addresses": { "items": { "type": "string" }, "title": "Email Addresses", "type": "array" }, "phone_numbers": { "items": { "type": "string" }, "title": "Phone Numbers", "type": "array" }, "organizations": { "items": { "additionalProperties": true, "type": "object" }, "title": "Organizations", "type": "array" }, "addresses": { "items": { "additionalProperties": true, "type": "object" }, "title": "Addresses", "type": "array" }, "biographies": { "items": { "additionalProperties": true, "type": "object" }, "title": "Biographies", "type": "array" }, "urls": { "items": { "additionalProperties": true, "type": "object" }, "title": "Urls", "type": "array" }, "birthdays": { "items": { "additionalProperties": true, "type": "object" }, "title": "Birthdays", "type": "array" }, "genders": { "items": { "additionalProperties": true, "type": "object" }, "title": "Genders", "type": "array" }, "raw": { "additionalProperties": true, "description": "Untouched upstream Person object — all fields requested via personFields.", "title": "Raw", "type": "object" } }, "title": "ContactSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "contact": { "anyOf": [ { "$ref": "#/$defs/ContactSummary" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "CreateContactOutput", "type": "object" } ``` ### Parameters The People API resource name identifying the contact, e.g. 'people/c123456789'. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "resource_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Resource Name" } }, "required": [ "success" ], "title": "DeleteContactOutput", "type": "object" } ``` ### Parameters The People API resource name identifying the contact, e.g. 'people/c123456789'. Contact fields to return — any of: addresses, ageRanges, biographies, birthdays, calendarUrls, clientData, coverPhotos, emailAddresses, events, externalIds, genders, imClients, interests, locales, locations, memberships, metadata, miscKeywords, names, nicknames, occupations, organizations, phoneNumbers, photos, relations, sipAddresses, skills, urls, userDefined. ### Response ```json { "$defs": { "ContactSummary": { "additionalProperties": false, "description": "A Google People `Person` resource, projected to the fields most tools read.", "properties": { "resource_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Resource Name" }, "etag": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Etag" }, "display_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Display Name" }, "given_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Given Name" }, "family_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Family Name" }, "middle_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Middle Name" }, "email_addresses": { "items": { "type": "string" }, "title": "Email Addresses", "type": "array" }, "phone_numbers": { "items": { "type": "string" }, "title": "Phone Numbers", "type": "array" }, "organizations": { "items": { "additionalProperties": true, "type": "object" }, "title": "Organizations", "type": "array" }, "addresses": { "items": { "additionalProperties": true, "type": "object" }, "title": "Addresses", "type": "array" }, "biographies": { "items": { "additionalProperties": true, "type": "object" }, "title": "Biographies", "type": "array" }, "urls": { "items": { "additionalProperties": true, "type": "object" }, "title": "Urls", "type": "array" }, "birthdays": { "items": { "additionalProperties": true, "type": "object" }, "title": "Birthdays", "type": "array" }, "genders": { "items": { "additionalProperties": true, "type": "object" }, "title": "Genders", "type": "array" }, "raw": { "additionalProperties": true, "description": "Untouched upstream Person object — all fields requested via personFields.", "title": "Raw", "type": "object" } }, "title": "ContactSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "contact": { "anyOf": [ { "$ref": "#/$defs/ContactSummary" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "GetContactOutput", "type": "object" } ``` ### Parameters Contact fields to return on every result — same enum as get_contact's `fields`. Maximum number of pages to fetch (1-500). Prevents runaway pagination. (Default: `50`) ### Response ```json { "$defs": { "ContactSummary": { "additionalProperties": false, "description": "A Google People `Person` resource, projected to the fields most tools read.", "properties": { "resource_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Resource Name" }, "etag": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Etag" }, "display_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Display Name" }, "given_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Given Name" }, "family_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Family Name" }, "middle_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Middle Name" }, "email_addresses": { "items": { "type": "string" }, "title": "Email Addresses", "type": "array" }, "phone_numbers": { "items": { "type": "string" }, "title": "Phone Numbers", "type": "array" }, "organizations": { "items": { "additionalProperties": true, "type": "object" }, "title": "Organizations", "type": "array" }, "addresses": { "items": { "additionalProperties": true, "type": "object" }, "title": "Addresses", "type": "array" }, "biographies": { "items": { "additionalProperties": true, "type": "object" }, "title": "Biographies", "type": "array" }, "urls": { "items": { "additionalProperties": true, "type": "object" }, "title": "Urls", "type": "array" }, "birthdays": { "items": { "additionalProperties": true, "type": "object" }, "title": "Birthdays", "type": "array" }, "genders": { "items": { "additionalProperties": true, "type": "object" }, "title": "Genders", "type": "array" }, "raw": { "additionalProperties": true, "description": "Untouched upstream Person object — all fields requested via personFields.", "title": "Raw", "type": "object" } }, "title": "ContactSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "contacts": { "items": { "$ref": "#/$defs/ContactSummary" }, "title": "Contacts", "type": "array" }, "total": { "default": 0, "title": "Total", "type": "integer" } }, "required": [ "success" ], "title": "ListContactsOutput", "type": "object" } ``` ### Parameters Contact fields to return — same enum as get_contact's `fields`. Directory source to return: 'DIRECTORY_SOURCE_TYPE_DOMAIN_CONTACT' or 'DIRECTORY_SOURCE_TYPE_DOMAIN_PROFILE'. Optional. Additional sources to merge in via verified join keys. Any of: 'DIRECTORY_MERGE_SOURCE_TYPE_UNSPECIFIED', 'DIRECTORY_MERGE_SOURCE_TYPE_CONTACT'. Number of people per response page (1-1000). Defaults to 100. (Default: `100`) Page token returned from a previous response's nextPageToken. Whether the response should include a nextSyncToken for incremental sync. (Default: `false`) Sync token from a previous response — fetch only resources changed since then. ### Response ```json { "$defs": { "ContactSummary": { "additionalProperties": false, "description": "A Google People `Person` resource, projected to the fields most tools read.", "properties": { "resource_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Resource Name" }, "etag": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Etag" }, "display_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Display Name" }, "given_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Given Name" }, "family_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Family Name" }, "middle_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Middle Name" }, "email_addresses": { "items": { "type": "string" }, "title": "Email Addresses", "type": "array" }, "phone_numbers": { "items": { "type": "string" }, "title": "Phone Numbers", "type": "array" }, "organizations": { "items": { "additionalProperties": true, "type": "object" }, "title": "Organizations", "type": "array" }, "addresses": { "items": { "additionalProperties": true, "type": "object" }, "title": "Addresses", "type": "array" }, "biographies": { "items": { "additionalProperties": true, "type": "object" }, "title": "Biographies", "type": "array" }, "urls": { "items": { "additionalProperties": true, "type": "object" }, "title": "Urls", "type": "array" }, "birthdays": { "items": { "additionalProperties": true, "type": "object" }, "title": "Birthdays", "type": "array" }, "genders": { "items": { "additionalProperties": true, "type": "object" }, "title": "Genders", "type": "array" }, "raw": { "additionalProperties": true, "description": "Untouched upstream Person object — all fields requested via personFields.", "title": "Raw", "type": "object" } }, "title": "ContactSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "people": { "items": { "$ref": "#/$defs/ContactSummary" }, "title": "People", "type": "array" }, "next_page_token": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Next Page Token" }, "next_sync_token": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Next Sync Token" }, "total": { "default": 0, "title": "Total", "type": "integer" } }, "required": [ "success" ], "title": "ListDirectoryContactsOutput", "type": "object" } ``` ### Parameters The People API resource name identifying the contact to update. Contact sections to update — any of: names, emailAddresses, phoneNumbers, addresses, biographies, birthdays, calendarUrls, genders, urls. Updated first name (used when update_person_fields includes 'names'). Updated middle name (used when update_person_fields includes 'names'). Updated last name (used when update_person_fields includes 'names'). Updated email (used when update_person_fields includes 'emailAddresses'). Updated phone (used when update_person_fields includes 'phoneNumbers'). Updated street address (used when update_person_fields includes 'addresses'). Updated city. Updated state / region. Updated postal / zip code. Updated country. Updated biography. Updated birthday in YYYY-MM-DD format. Updated calendar URL. Updated gender: male, female, or unspecified. Updated list of associated URLs. Optional extra fields merged into the Person request body. See https://developers.google.com/people/api/rest/v1/people for the full schema. ### Response ```json { "$defs": { "ContactSummary": { "additionalProperties": false, "description": "A Google People `Person` resource, projected to the fields most tools read.", "properties": { "resource_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Resource Name" }, "etag": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Etag" }, "display_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Display Name" }, "given_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Given Name" }, "family_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Family Name" }, "middle_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Middle Name" }, "email_addresses": { "items": { "type": "string" }, "title": "Email Addresses", "type": "array" }, "phone_numbers": { "items": { "type": "string" }, "title": "Phone Numbers", "type": "array" }, "organizations": { "items": { "additionalProperties": true, "type": "object" }, "title": "Organizations", "type": "array" }, "addresses": { "items": { "additionalProperties": true, "type": "object" }, "title": "Addresses", "type": "array" }, "biographies": { "items": { "additionalProperties": true, "type": "object" }, "title": "Biographies", "type": "array" }, "urls": { "items": { "additionalProperties": true, "type": "object" }, "title": "Urls", "type": "array" }, "birthdays": { "items": { "additionalProperties": true, "type": "object" }, "title": "Birthdays", "type": "array" }, "genders": { "items": { "additionalProperties": true, "type": "object" }, "title": "Genders", "type": "array" }, "raw": { "additionalProperties": true, "description": "Untouched upstream Person object — all fields requested via personFields.", "title": "Raw", "type": "object" } }, "title": "ContactSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "contact": { "anyOf": [ { "$ref": "#/$defs/ContactSummary" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "UpdateContactOutput", "type": "object" } ``` ## Limits & Quotas - Google People API default quota: **90 reads/minute** and **60 writes/minute** per user, **1,200 reads/minute** per project. Higher limits available via Google Cloud quota request. - `list_contacts` auto-paginates through `people/me/connections` — beware large contact directories (each page is 100 contacts by default; the implementation fetches every page in sequence). - `list_directory_contacts` returns one page per call; supply `page_token` / `sync_token` from a previous response for pagination or incremental sync. - `update_contact` performs an extra `GET` to refresh the etag before the `PATCH`. Stale-etag updates are rejected by Google with HTTP 400. - **Error model**: non-2xx responses raise `httpx.HTTPStatusError`. The modulex runtime surfaces these as failed tool calls — plan for retries on the agent side if a transient 5xx is possible. ## Related integrations ## Links - [Google Contacts](https://contacts.google.com) --- # Google Docs Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/google-docs Create, read, and edit Google Docs documents via the Google Docs API {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/google_docs/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Google Docs logo ## Overview Add **Google Docs** to any ModuleX agent or workflow. Create, read, and edit Google Docs documents via the Google Docs API (`docs.googleapis.com/v1`). **Categories**: Productivity & Collaboration · Google Workspace · Documents · **Auth**: OAuth2 · **Actions**: 10 ## Authentication ### OAuth2 Authentication Connect using Google OAuth (recommended) #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Client ID | Google OAuth App Client ID | Yes | `xxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com` | | Client Secret | Google OAuth App Client Secret | Yes | `GOCSPX-xxxxxxxxxxxxxxxxxxxxxxxxxx` | #### OAuth Configuration - **Authorization URL**: `https://accounts.google.com/o/oauth2/v2/auth` - **Token URL**: `https://oauth2.googleapis.com/token` - **Scopes**: `https://www.googleapis.com/auth/documents` ## Available Actions ### Parameters The ID of the document to append the image to The URL of the image to insert into the document Whether to append at the beginning (true) or end (false) of the document (Default: `false`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "document_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Document Id" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" } }, "required": [ "success" ], "title": "AppendImageOutput", "type": "object" } ``` ### Parameters The ID of the document to append text to The text to append to the document Whether to append at the beginning (true) or end (false) of the document (Default: `false`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "document_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Document Id" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" } }, "required": [ "success" ], "title": "AppendTextOutput", "type": "object" } ``` ### Parameters Title of the new document Text content to insert into the document ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "document_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Document Id" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" } }, "required": [ "success" ], "title": "CreateDocumentOutput", "type": "object" } ``` ### Parameters The ID of the document to retrieve Whether to populate the tabs field instead of the body field (Default: `false`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "document_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Document Id" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "body": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Body" } }, "required": [ "success" ], "title": "GetDocumentOutput", "type": "object" } ``` ### Parameters The ID of the document List of tab IDs to retrieve content for (element type: string) ### Response ```json { "$defs": { "TabContent": { "additionalProperties": false, "description": "Tab content returned by get_tab_content.", "properties": { "tab_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Tab Id" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "body": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Body" } }, "title": "TabContent", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "tabs": { "items": { "$ref": "#/$defs/TabContent" }, "title": "Tabs", "type": "array" } }, "required": [ "success" ], "title": "GetTabContentOutput", "type": "object" } ``` ### Parameters The ID of the document The index position to insert the page break at (Default: `1`) The tab ID to insert the page break into ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "document_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Document Id" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" } }, "required": [ "success" ], "title": "InsertPageBreakOutput", "type": "object" } ``` ### Parameters The ID of the document The number of rows in the table The number of columns in the table The index position to insert the table at (Default: `1`) The tab ID to insert the table into ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "document_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Document Id" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" } }, "required": [ "success" ], "title": "InsertTableOutput", "type": "object" } ``` ### Parameters The ID of the document The text to insert The index position to insert the text at (Default: `1`) The tab ID to insert the text into ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "document_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Document Id" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" } }, "required": [ "success" ], "title": "InsertTextOutput", "type": "object" } ``` ### Parameters The ID of the document The ID of the image to be replaced (from the document's inlineObjects) The URL of the new image to replace with ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "document_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Document Id" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" } }, "required": [ "success" ], "title": "ReplaceImageOutput", "type": "object" } ``` ### Parameters The ID of the document The text to find and replace The replacement text Whether the search is case-sensitive (Default: `false`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "document_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Document Id" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" } }, "required": [ "success" ], "title": "ReplaceTextOutput", "type": "object" } ``` ## Limits & Quotas - Google Docs API: 300 read requests per minute per user, 60 write requests per minute per user. - Error model: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. ## Related integrations ## Links - [Google Docs](https://docs.google.com) --- # Google Drive Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/google-drive Google Workspace integration: Drive (files/folders), Docs, Sheets, and Slides via the v3 / v1 REST APIs. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/google_drive/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Google Drive logo ## Overview Add **Google Drive** to any ModuleX agent or workflow. Google Workspace integration via the v3 (Drive) / v1 (Docs, Slides) / v4 (Sheets) REST APIs. Pure HTTP, no SDK dep. 16 actions. **Categories**: Cloud Infrastructure · File Storage · Document Management · Productivity · **Auth**: OAuth2, Service Account / Access Token · **Actions**: 16 ## Authentication ### OAuth2 Authentication Connect using Google OAuth2 (recommended). Provides secure access to Drive, Docs, Sheets, and Slides. #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Client ID | Google OAuth2 Client ID from Cloud Console | Yes | `123456789-xxxxxxxxxxxxxxxx.apps.googleusercontent.com` | | Client Secret | Google OAuth2 Client Secret | Yes | `-` | #### OAuth Configuration - **Authorization URL**: `https://accounts.google.com/o/oauth2/v2/auth` - **Token URL**: `https://oauth2.googleapis.com/token` - **Scopes**: `https://www.googleapis.com/auth/drive.file`, `https://www.googleapis.com/auth/documents`, `https://www.googleapis.com/auth/spreadsheets`, `https://www.googleapis.com/auth/presentations` ### Service Account / Access Token Use a pre-generated access token or service account credentials #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Access Token | A valid Google OAuth2 access token | Yes | `-` | ## Available Actions ### Parameters File name (must end with .txt or .md) Text content Parent folder (defaults to root) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "mime_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Mime Type" }, "web_view_link": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Web View Link" } }, "required": [ "success" ], "title": "CreateTextFileOutput", "type": "object" } ``` ### Parameters File ID New content Optional new name ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "mime_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Mime Type" }, "modified_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Modified Time" }, "web_view_link": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Web View Link" } }, "required": [ "success" ], "title": "UpdateTextFileOutput", "type": "object" } ``` ### Parameters Folder name Parent folder ID ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "web_view_link": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Web View Link" } }, "required": [ "success" ], "title": "CreateFolderOutput", "type": "object" } ``` ### Parameters Document name Initial content (Default: ``) Parent folder ID ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "web_view_link": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Web View Link" } }, "required": [ "success" ], "title": "CreateGoogleDocOutput", "type": "object" } ``` ### Parameters Document ID ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "content": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Content" }, "revision_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Revision Id" } }, "required": [ "success" ], "title": "ReadGoogleDocOutput", "type": "object" } ``` ### Parameters Document ID New content ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" } }, "required": [ "success" ], "title": "UpdateGoogleDocOutput", "type": "object" } ``` ### Parameters Document ID Content to append ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" } }, "required": [ "success" ], "title": "AppendToGoogleDocOutput", "type": "object" } ``` ### Parameters Spreadsheet name Initial data (2D array) Parent folder ID ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "web_view_link": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Web View Link" } }, "required": [ "success" ], "title": "CreateGoogleSheetOutput", "type": "object" } ``` ### Parameters Spreadsheet ID A1 notation (e.g. 'Sheet1!A1:D10') (Default: `Sheet1`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "spreadsheet_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Spreadsheet Id" }, "range": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Range" }, "values": { "items": { "items": {}, "type": "array" }, "title": "Values", "type": "array" }, "row_count": { "default": 0, "title": "Row Count", "type": "integer" } }, "required": [ "success" ], "title": "ReadGoogleSheetOutput", "type": "object" } ``` ### Parameters Spreadsheet ID A1 notation 2D array of strings ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "spreadsheet_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Spreadsheet Id" }, "updated_range": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated Range" }, "updated_rows": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Updated Rows" }, "updated_columns": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Updated Columns" }, "updated_cells": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Updated Cells" } }, "required": [ "success" ], "title": "UpdateGoogleSheetOutput", "type": "object" } ``` ### Parameters Spreadsheet ID A1 range RGB color {red, green, blue} (values 0-1) LEFT / CENTER / RIGHT TOP / MIDDLE / BOTTOM ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "spreadsheet_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Spreadsheet Id" }, "range": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Range" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" } }, "required": [ "success" ], "title": "FormatSheetCellsOutput", "type": "object" } ``` ### Parameters Spreadsheet ID A1 range Bold Italic Font size (pt) Text RGB color {red, green, blue} ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "spreadsheet_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Spreadsheet Id" }, "range": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Range" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" } }, "required": [ "success" ], "title": "FormatSheetTextOutput", "type": "object" } ``` ### Parameters Presentation name Parent folder ID ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "web_view_link": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Web View Link" } }, "required": [ "success" ], "title": "CreateGoogleSlidesOutput", "type": "object" } ``` ### Parameters Presentation ID ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "slide_count": { "default": 0, "title": "Slide Count", "type": "integer" }, "slides": { "items": { "additionalProperties": true, "type": "object" }, "title": "Slides", "type": "array" } }, "required": [ "success" ], "title": "ReadGoogleSlidesOutput", "type": "object" } ``` ### Parameters Presentation ID BLANK / TITLE / TITLE_AND_BODY / etc. (Default: `BLANK`) Insert position (0-based) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "presentation_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Presentation Id" }, "slide_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Slide Id" }, "layout": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Layout" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" } }, "required": [ "success" ], "title": "AddSlideOutput", "type": "object" } ``` ### Parameters Presentation ID Slide object ID Placeholder element ID New text ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "presentation_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Presentation Id" }, "slide_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Slide Id" }, "placeholder_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Placeholder Id" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" } }, "required": [ "success" ], "title": "UpdateSlideContentOutput", "type": "object" } ``` ## Related integrations ## Links - [Google Drive](https://drive.google.com) --- # Google Forms Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/google-forms Create, update, and read Google Forms and their responses via the Google Forms API. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/google_forms/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Google Forms logo ## Overview Add **Google Forms** to any ModuleX agent or workflow. Create, update, and read Google Forms and their responses via the Google Forms REST API (`forms.googleapis.com/v1`). **Categories**: Productivity & Collaboration · Forms & Surveys · **Auth**: OAuth2 · **Actions**: 6 ## Authentication ### OAuth2 Authentication Connect using Google OAuth (recommended) #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Client ID | Google OAuth App Client ID from the Google Cloud Console. | Yes | `xxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com` | | Client Secret | Google OAuth App Client Secret from the Google Cloud Console. | Yes | `GOCSPX-xxxxxxxxxxxxxxxxxxxxxxxx` | #### OAuth Configuration - **Authorization URL**: `https://accounts.google.com/o/oauth2/v2/auth` - **Token URL**: `https://oauth2.googleapis.com/token` - **Scopes**: `https://www.googleapis.com/auth/forms.body`, `https://www.googleapis.com/auth/forms.responses.readonly` ## Available Actions ### Parameters The title of the form which is visible to responders. The title of the document which is visible in Google Drive. Optional. ### Response ```json { "$defs": { "FormInfo": { "additionalProperties": false, "description": "The ``info`` subobject embedded in a Google Form resource.", "properties": { "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "documentTitle": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Documenttitle" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" } }, "title": "FormInfo", "type": "object" }, "FormSummary": { "additionalProperties": false, "description": "Slim view of a Google Form resource returned by Get/Create.", "properties": { "formId": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Formid" }, "responderUri": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Responderuri" }, "revisionId": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Revisionid" }, "info": { "anyOf": [ { "$ref": "#/$defs/FormInfo" }, { "type": "null" } ], "default": null }, "settings": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Settings" }, "items": { "items": { "additionalProperties": true, "type": "object" }, "title": "Items", "type": "array" } }, "title": "FormSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "form": { "anyOf": [ { "$ref": "#/$defs/FormSummary" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "CreateFormOutput", "type": "object" } ``` ### Parameters Identifier of the Google Form to update. Title of the question. Description of the question. The 0-based index where the item is inserted in the form. Must be in the range [0..N), where N is the current number of items. If true, the question is a paragraph (multi-line) question. If false, it is a short text question. ### Response ```json { "$defs": { "FormInfo": { "additionalProperties": false, "description": "The ``info`` subobject embedded in a Google Form resource.", "properties": { "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "documentTitle": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Documenttitle" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" } }, "title": "FormInfo", "type": "object" }, "FormSummary": { "additionalProperties": false, "description": "Slim view of a Google Form resource returned by Get/Create.", "properties": { "formId": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Formid" }, "responderUri": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Responderuri" }, "revisionId": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Revisionid" }, "info": { "anyOf": [ { "$ref": "#/$defs/FormInfo" }, { "type": "null" } ], "default": null }, "settings": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Settings" }, "items": { "items": { "additionalProperties": true, "type": "object" }, "title": "Items", "type": "array" } }, "title": "FormSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "formId": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Formid" }, "replies": { "items": { "additionalProperties": true, "type": "object" }, "title": "Replies", "type": "array" }, "form": { "anyOf": [ { "$ref": "#/$defs/FormSummary" }, { "type": "null" } ], "default": null }, "writeControl": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Writecontrol" } }, "required": [ "success" ], "title": "CreateTextQuestionOutput", "type": "object" } ``` ### Parameters Identifier of the Google Form to retrieve. ### Response ```json { "$defs": { "FormInfo": { "additionalProperties": false, "description": "The ``info`` subobject embedded in a Google Form resource.", "properties": { "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "documentTitle": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Documenttitle" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" } }, "title": "FormInfo", "type": "object" }, "FormSummary": { "additionalProperties": false, "description": "Slim view of a Google Form resource returned by Get/Create.", "properties": { "formId": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Formid" }, "responderUri": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Responderuri" }, "revisionId": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Revisionid" }, "info": { "anyOf": [ { "$ref": "#/$defs/FormInfo" }, { "type": "null" } ], "default": null }, "settings": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Settings" }, "items": { "items": { "additionalProperties": true, "type": "object" }, "title": "Items", "type": "array" } }, "title": "FormSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "form": { "anyOf": [ { "$ref": "#/$defs/FormSummary" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "GetFormOutput", "type": "object" } ``` ### Parameters Identifier of the Google Form the response belongs to. The response ID within the form to retrieve. ### Response ```json { "$defs": { "FormResponseSummary": { "additionalProperties": false, "description": "A single response to a Google Form.", "properties": { "responseId": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Responseid" }, "formId": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Formid" }, "createTime": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Createtime" }, "lastSubmittedTime": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Lastsubmittedtime" }, "respondentEmail": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Respondentemail" }, "answers": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Answers" }, "totalScore": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Totalscore" } }, "title": "FormResponseSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "response": { "anyOf": [ { "$ref": "#/$defs/FormResponseSummary" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "GetFormResponseOutput", "type": "object" } ``` ### Parameters Identifier of the Google Form whose responses are listed. ### Response ```json { "$defs": { "FormResponseSummary": { "additionalProperties": false, "description": "A single response to a Google Form.", "properties": { "responseId": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Responseid" }, "formId": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Formid" }, "createTime": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Createtime" }, "lastSubmittedTime": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Lastsubmittedtime" }, "respondentEmail": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Respondentemail" }, "answers": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Answers" }, "totalScore": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Totalscore" } }, "title": "FormResponseSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "responses": { "items": { "$ref": "#/$defs/FormResponseSummary" }, "title": "Responses", "type": "array" }, "nextPageToken": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Nextpagetoken" } }, "required": [ "success" ], "title": "ListFormResponsesOutput", "type": "object" } ``` ### Parameters Identifier of the Google Form to update. The new title of the form which is visible to responders. ### Response ```json { "$defs": { "FormInfo": { "additionalProperties": false, "description": "The ``info`` subobject embedded in a Google Form resource.", "properties": { "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "documentTitle": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Documenttitle" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" } }, "title": "FormInfo", "type": "object" }, "FormSummary": { "additionalProperties": false, "description": "Slim view of a Google Form resource returned by Get/Create.", "properties": { "formId": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Formid" }, "responderUri": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Responderuri" }, "revisionId": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Revisionid" }, "info": { "anyOf": [ { "$ref": "#/$defs/FormInfo" }, { "type": "null" } ], "default": null }, "settings": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Settings" }, "items": { "items": { "additionalProperties": true, "type": "object" }, "title": "Items", "type": "array" } }, "title": "FormSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "formId": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Formid" }, "replies": { "items": { "additionalProperties": true, "type": "object" }, "title": "Replies", "type": "array" }, "form": { "anyOf": [ { "$ref": "#/$defs/FormSummary" }, { "type": "null" } ], "default": null }, "writeControl": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Writecontrol" } }, "required": [ "success" ], "title": "UpdateFormTitleOutput", "type": "object" } ``` ## Limits & Quotas - The Google Forms API enforces per-project quotas administered through Google Cloud. Default quota is **300 read requests per minute per project** and **60 write requests per minute per project** (subject to change — see https://developers.google.com/forms/api/limits). - Per-user quotas may apply for OAuth client credentials with many concurrent users. - A `400 Bad Request` is returned when `index` for `create_text_question` is outside `[0..N)` where N is the current item count. - **Error model:** non-2xx responses from the API raise `httpx.HTTPStatusError`, which the ModuleX runtime surfaces as a failed tool call with the original API error text included. ## Related integrations ## Links - [Google Forms](https://forms.google.com) --- # Google Maps Platform Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/google-maps-platform Search for places and retrieve place details using the Google Places API (New) {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/google_maps_platform/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Google Maps Platform logo ## Overview Add **Google Maps Platform** to any ModuleX agent or workflow. Search for places and retrieve place details using the Google Places API (New) (`places.googleapis.com`). **Categories**: Developer Tools & Infrastructure · Geolocation · Maps & Places · **Auth**: Google Maps API Key · **Actions**: 2 ## Authentication ### Google Maps API Key Authenticate using your Google Cloud API key with Places API enabled Go to https://console.cloud.google.com/apis/credentials Create or select a project Enable the Places API (New) for your project Create an API key or use an existing one Paste the API key below #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Google Maps API Key | Your Google Cloud API key with Places API (New) enabled | Yes | `AIzaSyXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX` | ## Available Actions ### Parameters The text string on which to search, for example: 'restaurant', '123 Main Street', or 'best place to visit in San Francisco' Restricts results to places matching the specified type from Table A (e.g. 'restaurant', 'cafe', 'hotel') If true, include businesses that visit or deliver to customers directly but don't have a physical location BCP-47 language code for results (e.g. 'en', 'fr', 'zh-CN') Area to bias search results toward. JSON object with circle or rectangle specification Area to restrict search results to. Results outside this area are not returned Parameters for identifying available EV charging connectors and charging rates Minimum average user rating (0.0 to 5.0 inclusive, in increments of 0.5) If true, return only places that are currently open for business Restrict to places at certain price levels. Valid values: PRICE_LEVEL_FREE, PRICE_LEVEL_INEXPENSIVE, PRICE_LEVEL_MODERATE, PRICE_LEVEL_EXPENSIVE, PRICE_LEVEL_VERY_EXPENSIVE How results are ranked: RELEVANCE (by search relevance) or DISTANCE (by distance) Two-character CLDR region code to format and bias results When true and included_type is set, only return places matching the specified type If true, return a reduced set of fields per place (id, name, type, status, rating, address, phone, website, hours, location) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "places": { "items": { "additionalProperties": true, "type": "object" }, "title": "Places", "type": "array" } }, "required": [ "success" ], "title": "SearchPlacesOutput", "type": "object" } ``` ### Parameters A textual identifier that uniquely identifies a place, returned from the search_places action If true, return a reduced set of fields (id, name, type, status, rating, address, phone, website, hours, location) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "GetPlaceDetailsOutput", "type": "object" } ``` ## Limits & Quotas - **Text Search**: $32.00 per 1,000 requests (standard SKU pricing). - **Place Details**: $17.00 per 1,000 requests (standard SKU pricing). - **Default quota**: 6,000 QPM (queries per minute) per project; can be increased via Google Cloud Console. - **Error model**: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. ## Related integrations ## Links - [Google Maps Platform](https://developers.google.com/maps) --- # Google Meet Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/google-meet Schedule Google Meet video conferences via Google Calendar events. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/google_meet/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Google Meet logo Google Meet logo ## Overview Add **Google Meet** to any ModuleX agent or workflow. Schedule Google Meet video conferences by creating Google Calendar events with an attached Meet link, via the Google Calendar REST API (`https://www.googleapis.com/calendar/v3`). **Categories**: Communication · Productivity & Collaboration · **Auth**: OAuth2 · **Actions**: 2 ## Authentication ### OAuth2 Authentication Connect using Google OAuth (recommended) #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Client ID | Google Cloud OAuth 2.0 Client ID | Yes | `xxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com` | | Client Secret | Google Cloud OAuth 2.0 Client Secret | Yes | `GOCSPX-xxxxxxxxxxxxxxxxxxxxxxxx` | #### OAuth Configuration - **Authorization URL**: `https://accounts.google.com/o/oauth2/v2/auth` - **Token URL**: `https://oauth2.googleapis.com/token` - **Scopes**: `https://www.googleapis.com/auth/calendar`, `https://www.googleapis.com/auth/calendar.events` ## Available Actions ### Parameters For all-day events use yyyy-mm-dd. For timed events use RFC3339 (yyyy-mm-ddThh:mm:ss+01:00). A time zone offset is required unless time_zone is set. For all-day events use yyyy-mm-dd. For timed events use RFC3339 (yyyy-mm-ddThh:mm:ss+01:00). A time zone offset is required unless time_zone is set. Calendar to create the event on. Defaults to 'primary' (the authenticated user's primary calendar). (Default: `primary`) Title of the event (e.g. 'Weekly sync'). Physical or virtual location of the event. Long-form description of the event. List of attendee email addresses to invite. RRULE strings describing recurrence (e.g. 'RRULE:FREQ=DAILY;INTERVAL=2'). IANA time zone name used for start/end (e.g. 'America/Los_Angeles'). Defaults to the calendar's time zone. Whether to notify attendees: 'all', 'externalOnly', or 'none'. Whether to send notifications about the event update. Color ID for the event. Use list_color_id_options to discover valid values. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "event_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Event Id" }, "html_link": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Html Link" }, "hangout_link": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Hangout Link" }, "meet_link": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Meet Link" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "summary": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Summary" }, "start": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Start" }, "end": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "End" }, "attendees": { "items": { "additionalProperties": true, "type": "object" }, "title": "Attendees", "type": "array" }, "conference_data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Conference Data" }, "event": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Event" } }, "required": [ "success" ], "title": "ScheduleMeetingOutput", "type": "object" } ``` ### Response ```json { "$defs": { "ColorIdOption": { "additionalProperties": false, "description": "One color option exposed by the Google Calendar /colors endpoint.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "background": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Background" }, "foreground": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Foreground" } }, "title": "ColorIdOption", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "options": { "items": { "$ref": "#/$defs/ColorIdOption" }, "title": "Options", "type": "array" }, "count": { "default": 0, "title": "Count", "type": "integer" } }, "required": [ "success" ], "title": "ListColorIdOptionsOutput", "type": "object" } ``` ## Limits & Quotas - Google Calendar API default quota: **1,000,000 queries/day** per project, with per-user limits of **600 queries/minute**. See the [Google Calendar API quotas page](https://developers.google.com/calendar/api/guides/quota) for details. - `schedule_meeting` sends `conferenceDataVersion=1` so Google provisions a Meet link in the same call (counts as a single event-insert quota unit). - **Error model**: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. Plan for retries on the agent side based on the error string. 401/403 typically means the OAuth token has expired or lacks the calendar scope. ## Related integrations ## Links - [Google Meet](https://meet.google.com) --- # Google Merchant Center Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/google-merchant-center Manage product listings in Google Merchant Center via the Shopping Content API {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/google_merchant_center/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Google Merchant Center logo ## Overview Add **Google Merchant Center** to any ModuleX agent or workflow. Manage product listings in Google Merchant Center via the Shopping Content API (`shoppingcontent.googleapis.com/content/v2.1`). **Categories**: E-Commerce · Marketing · Google · **Auth**: OAuth2 · **Actions**: 2 ## Authentication ### OAuth2 Authentication Connect using Google OAuth (recommended) #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Client ID | Google OAuth App Client ID | Yes | `-` | | Client Secret | Google OAuth App Client Secret | Yes | `-` | | Merchant ID | Your Google Merchant Center account ID (numeric) | Yes | `123456789` | #### OAuth Configuration - **Authorization URL**: `https://accounts.google.com/o/oauth2/v2/auth` - **Token URL**: `https://oauth2.googleapis.com/token` - **Scopes**: `https://www.googleapis.com/auth/content` ## Available Actions ### Parameters A unique identifier for the item The two-letter ISO 639-1 language code for the item (e.g. 'en', 'fr', 'de') The CLDR territory code for the item's country of sale (e.g. 'US', 'GB', 'DE') The item's channel: 'local' or 'online' Your product's name (max 150 characters) Your product's description (max 5000 characters) Additional product attributes as a JSON object (e.g. 'link', 'imageLink', 'price', 'availability') ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "CreateProductOutput", "type": "object" } ``` ### Parameters The ID of the product to update (e.g. 'online:en:US:offer123') Product attributes to update as a JSON object with attribute names as keys List of product attribute names to update. Attributes in this list without a value in updated_values will be deleted ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "UpdateProductOutput", "type": "object" } ``` ## Limits & Quotas - **Rate limits**: Google Shopping Content API has per-project and per-user quotas managed via the Google Cloud Console. Default is approximately 7,000 requests per 100 seconds per project. - **Error model**: Non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. ## Related integrations ## Links - [Google Merchant Center](https://merchants.google.com) --- # Google My Business Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/google-my-business Manage Google Business Profile posts, reviews, and replies {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/google_my_business/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Google My Business logo ## Overview Add **Google My Business** to any ModuleX agent or workflow. Manage Google Business Profile posts, reviews, and replies via the Google My Business API (`mybusiness.googleapis.com/v4`). **Categories**: Marketing & Advertising · Marketing · Local Business · Reviews · **Auth**: OAuth2 · **Actions**: 6 ## Authentication ### OAuth2 Authentication Connect using Google OAuth (recommended) #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Client ID | Google OAuth App Client ID | Yes | `123456789-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com` | | Client Secret | Google OAuth App Client Secret | Yes | `GOCSPX-xxxxxxxxxxxxxxxxxxxxxxxx` | #### OAuth Configuration - **Authorization URL**: `https://accounts.google.com/o/oauth2/v2/auth` - **Token URL**: `https://oauth2.googleapis.com/token` - **Scopes**: `https://www.googleapis.com/auth/business.manage` ## Available Actions ### Parameters Account name/ID for the Google Business Profile account Location name/ID whose local posts will be created Topic type of the local post: STANDARD, EVENT, OFFER, or ALERT Language of the local post (e.g. en-US) Description/body of the local post Action performed when user clicks through the post. Object with actionType and url fields Event information. Required for topic types EVENT and OFFER. Object with title, schedule (startDate, startTime, endDate, endTime) Media associated with the post. Array of objects with sourceUrl (publicly accessible URL) fields Format of the media items: PHOTO or VIDEO Type of alert for ALERT topic type: ALERT_TYPE_UNSPECIFIED or COVID_19 Additional data for offer posts (only for OFFER topic type). Object with couponCode, redeemOnlineUrl, termsConditions ### Response ```json { "$defs": { "LocalPost": { "additionalProperties": false, "description": "A Google My Business local post.", "properties": { "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "summary": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Summary" }, "topic_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Topic Type" }, "state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "State" }, "create_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Create Time" }, "update_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Update Time" }, "search_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Search Url" }, "event": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Event" }, "offer": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Offer" }, "call_to_action": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Call To Action" } }, "title": "LocalPost", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "post": { "anyOf": [ { "$ref": "#/$defs/LocalPost" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "CreatePostOutput", "type": "object" } ``` ### Parameters Account name/ID for the Google Business Profile account Location name/ID of the review Review name/ID to reply to Body of the reply as plain text (max 4096 bytes) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "comment": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Comment" }, "update_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Update Time" } }, "required": [ "success" ], "title": "CreateUpdateReplyToReviewOutput", "type": "object" } ``` ### Parameters Account name/ID for the Google Business Profile account List of location name/ID strings to get reviews from Number of reviews to return per location (max 50) (Default: `50`) How to order reviews: 'createTime desc', 'createTime asc', 'updateTime desc', or 'updateTime asc' If true, only return reviews that have textual content (Default: `false`) ### Response ```json { "$defs": { "LocationReview": { "additionalProperties": false, "description": "A review associated with a location in a batch response.", "properties": { "location_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Location Name" }, "reviews": { "items": { "$ref": "#/$defs/Review" }, "title": "Reviews", "type": "array" } }, "title": "LocationReview", "type": "object" }, "Review": { "additionalProperties": false, "description": "A Google My Business review.", "properties": { "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "review_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Review Id" }, "reviewer_display_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Reviewer Display Name" }, "star_rating": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Star Rating" }, "comment": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Comment" }, "create_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Create Time" }, "update_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Update Time" }, "review_reply": { "anyOf": [ { "$ref": "#/$defs/ReviewReply" }, { "type": "null" } ], "default": null } }, "title": "Review", "type": "object" }, "ReviewReply": { "additionalProperties": false, "description": "A reply to a review.", "properties": { "comment": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Comment" }, "update_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Update Time" } }, "title": "ReviewReply", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "location_reviews": { "items": { "$ref": "#/$defs/LocationReview" }, "title": "Location Reviews", "type": "array" } }, "required": [ "success" ], "title": "GetReviewsMultipleLocationsOutput", "type": "object" } ``` ### Parameters Account name/ID for the Google Business Profile account Location name/ID of the review Review name/ID to retrieve ### Response ```json { "$defs": { "Review": { "additionalProperties": false, "description": "A Google My Business review.", "properties": { "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "review_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Review Id" }, "reviewer_display_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Reviewer Display Name" }, "star_rating": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Star Rating" }, "comment": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Comment" }, "create_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Create Time" }, "update_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Update Time" }, "review_reply": { "anyOf": [ { "$ref": "#/$defs/ReviewReply" }, { "type": "null" } ], "default": null } }, "title": "Review", "type": "object" }, "ReviewReply": { "additionalProperties": false, "description": "A reply to a review.", "properties": { "comment": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Comment" }, "update_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Update Time" } }, "title": "ReviewReply", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "review": { "anyOf": [ { "$ref": "#/$defs/Review" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "GetSpecificReviewOutput", "type": "object" } ``` ### Parameters Account name/ID for the Google Business Profile account Location name/ID whose reviews will be listed ### Response ```json { "$defs": { "Review": { "additionalProperties": false, "description": "A Google My Business review.", "properties": { "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "review_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Review Id" }, "reviewer_display_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Reviewer Display Name" }, "star_rating": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Star Rating" }, "comment": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Comment" }, "create_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Create Time" }, "update_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Update Time" }, "review_reply": { "anyOf": [ { "$ref": "#/$defs/ReviewReply" }, { "type": "null" } ], "default": null } }, "title": "Review", "type": "object" }, "ReviewReply": { "additionalProperties": false, "description": "A reply to a review.", "properties": { "comment": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Comment" }, "update_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Update Time" } }, "title": "ReviewReply", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "reviews": { "items": { "$ref": "#/$defs/Review" }, "title": "Reviews", "type": "array" }, "next_page_token": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Next Page Token" } }, "required": [ "success" ], "title": "ListAllReviewsOutput", "type": "object" } ``` ### Parameters Account name/ID for the Google Business Profile account Location name/ID whose local posts will be listed Max number of posts to retrieve (each request retrieves up to 100, maximum 1000) (Default: `100`) ### Response ```json { "$defs": { "LocalPost": { "additionalProperties": false, "description": "A Google My Business local post.", "properties": { "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "summary": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Summary" }, "topic_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Topic Type" }, "state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "State" }, "create_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Create Time" }, "update_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Update Time" }, "search_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Search Url" }, "event": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Event" }, "offer": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Offer" }, "call_to_action": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Call To Action" } }, "title": "LocalPost", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "posts": { "items": { "$ref": "#/$defs/LocalPost" }, "title": "Posts", "type": "array" } }, "required": [ "success" ], "title": "ListPostsOutput", "type": "object" } ``` ## Limits & Quotas - Google My Business API is subject to Google Cloud project quotas (typically 60 requests/minute per project by default). - Some endpoints may return 429 status if quota is exceeded. - Error model: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. ## Related integrations ## Links - [Google My Business](https://business.google.com) --- # Google Search Console Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/google-search-console Access Google Search Console data for search analytics and URL indexing {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/google_search_console/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Google Search Console logo ## Overview Add **Google Search Console** to any ModuleX agent or workflow. Access Google Search Console search analytics and URL indexing via the Search Console API (`searchconsole.googleapis.com`) and the Indexing API (`indexing.googleapis.com`). **Categories**: Marketing & Advertising · SEO · Marketing · Developer Tools & Infrastructure · **Auth**: OAuth2 · **Actions**: 2 ## Authentication ### OAuth2 Authentication Connect using Google OAuth (recommended) #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Client ID | Google OAuth App Client ID | Yes | `-` | | Client Secret | Google OAuth App Client Secret | Yes | `-` | #### OAuth Configuration - **Authorization URL**: `https://accounts.google.com/o/oauth2/v2/auth` - **Token URL**: `https://oauth2.googleapis.com/token` - **Scopes**: `https://www.googleapis.com/auth/webmasters.readonly`, `https://www.googleapis.com/auth/indexing` ## Available Actions ### Parameters The site URL as registered in Search Console (e.g. 'sc-domain:example.com' or 'https://example.com/') Start date in YYYY-MM-DD format End date in YYYY-MM-DD format Dimensions to group results by. Allowed values: country, device, page, query, searchAppearance, date Type of search. Allowed values: web, image, video, news, googleNews, discover (Default: `web`) Aggregation type. Allowed values: auto, byPage Maximum number of rows to return (Default: `10`) Start row for pagination (zero-based) Filter results to a specific subdomain when using a domain property (e.g. 'https://subdomain.example.com') Dimension to filter by when subdomain_filter is used. Allowed values: country, device, page, query (Default: `page`) Filter operator. Allowed values: contains, equals, notContains, notEquals, includingRegex, excludingRegex (Default: `contains`) Custom dimension filter groups following the Search Console API structure (JSON object) Data state to use. Allowed values: all, final (Default: `final`) ### Response ```json { "$defs": { "SearchAnalyticsRow": { "additionalProperties": false, "description": "A single row from the Search Analytics response.", "properties": { "keys": { "items": { "type": "string" }, "title": "Keys", "type": "array" }, "clicks": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Clicks" }, "impressions": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Impressions" }, "ctr": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Ctr" }, "position": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Position" } }, "title": "SearchAnalyticsRow", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "rows": { "items": { "$ref": "#/$defs/SearchAnalyticsRow" }, "title": "Rows", "type": "array" }, "response_aggregation_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Response Aggregation Type" } }, "required": [ "success" ], "title": "RetrieveSitePerformanceDataOutput", "type": "object" } ``` ### Parameters The URL to submit for indexing (must be a canonical URL verified in Google Search Console) Type of notification. Allowed values: URL_UPDATED (content updated), URL_DELETED (page removed) (Default: `URL_UPDATED`) ### Response ```json { "$defs": { "UrlNotificationMetadata": { "additionalProperties": false, "description": "Metadata returned after submitting a URL for indexing.", "properties": { "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" }, "latest_update": { "anyOf": [ { "additionalProperties": { "type": "string" }, "type": "object" }, { "type": "null" } ], "default": null, "title": "Latest Update" }, "latest_remove": { "anyOf": [ { "additionalProperties": { "type": "string" }, "type": "object" }, { "type": "null" } ], "default": null, "title": "Latest Remove" } }, "title": "UrlNotificationMetadata", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "url_notification_metadata": { "anyOf": [ { "$ref": "#/$defs/UrlNotificationMetadata" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "SubmitUrlForIndexingOutput", "type": "object" } ``` ## Limits & Quotas - **Search Analytics API**: 1,200 queries per minute per project (default quota). - **Indexing API**: 200 publish requests per day per site property (standard quota). - **Error model**: non-2xx responses are caught and returned as `success=False` + `error` rather than raising. ## Related integrations ## Links - [Google Search Console](https://search.google.com/search-console) --- # Google Sheets Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/google-sheets Read, write, and manage Google Sheets spreadsheets and worksheets. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/google_sheets/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Google Sheets logo ## Overview Add **Google Sheets** to any ModuleX agent or workflow. Read, write, and manage Google Sheets spreadsheets and worksheets via the Google Sheets API v4 (`https://sheets.googleapis.com/v4`). **Categories**: Productivity & Collaboration · Data & Analytics · **Auth**: OAuth2 · **Actions**: 13 ## Authentication ### OAuth2 Authentication Connect using Google OAuth (recommended) #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Client ID | Google OAuth Client ID | Yes | `xxxxxxxxxxxx.apps.googleusercontent.com` | | Client Secret | Google OAuth Client Secret | Yes | `GOCSPX-xxxxxxxxxxxxxxxxxxxx` | #### OAuth Configuration - **Authorization URL**: `https://accounts.google.com/o/oauth2/v2/auth` - **Token URL**: `https://oauth2.googleapis.com/token` - **Scopes**: `https://www.googleapis.com/auth/spreadsheets`, `https://www.googleapis.com/auth/drive.file` ## Available Actions ### Parameters The title of the new spreadsheet. Name for the first worksheet. Default: 'Sheet1'. Column headers to add as row 1. Each item is a string. Example: ["Name", "Email", "Score"]. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "spreadsheet_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Spreadsheet Id" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" }, "worksheet_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Worksheet Name" } }, "required": [ "success" ], "title": "NewSpreadsheetOutput", "type": "object" } ``` ### Parameters The spreadsheet ID from the Google Sheets URL. ### Response ```json { "$defs": { "WorksheetInfo": { "additionalProperties": false, "description": "A worksheet summary plus its row-1 headers, used by get_spreadsheet_info.", "properties": { "sheet_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Sheet Name" }, "sheet_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Sheet Id" }, "row_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Row Count" }, "headers": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Headers" } }, "title": "WorksheetInfo", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "spreadsheet_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Spreadsheet Id" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "worksheets": { "anyOf": [ { "items": { "$ref": "#/$defs/WorksheetInfo" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Worksheets" } }, "required": [ "success" ], "title": "GetSpreadsheetInfoOutput", "type": "object" } ``` ### Parameters The spreadsheet ID from the Google Sheets URL. ### Response ```json { "$defs": { "WorksheetSummary": { "additionalProperties": false, "description": "A worksheet (tab) summary as returned by list/info actions.", "properties": { "sheet_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Sheet Id" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "index": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Index" }, "sheet_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Sheet Type" }, "row_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Row Count" }, "column_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Column Count" } }, "title": "WorksheetSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "worksheets": { "anyOf": [ { "items": { "$ref": "#/$defs/WorksheetSummary" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Worksheets" }, "count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Count" } }, "required": [ "success" ], "title": "ListWorksheetsOutput", "type": "object" } ``` ### Parameters The spreadsheet ID from the Google Sheets URL. The name of the new worksheet (tab). Column headers to add as row 1. Each item is a string. Example: ["Name", "Email", "Score"]. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "sheet_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Sheet Id" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "index": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Index" } }, "required": [ "success" ], "title": "AddWorksheetOutput", "type": "object" } ``` ### Parameters The spreadsheet ID from the Google Sheets URL. The numeric worksheet (sheet) ID to delete. Use get_spreadsheet_info to discover sheet IDs. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "spreadsheet_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Spreadsheet Id" }, "deleted_sheet_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Deleted Sheet Id" } }, "required": [ "success" ], "title": "DeleteWorksheetOutput", "type": "object" } ``` ### Parameters The spreadsheet ID from the Google Sheets URL. The worksheet (tab) name. Optional A1 notation range within the sheet (e.g. 'A1:D10', 'A:F'). Omit to read all data. Whether row 1 contains column headers. If true, returns rows as objects with header keys. (Default: `true`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "headers": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Headers" }, "rows": { "anyOf": [ { "items": {}, "type": "array" }, { "type": "null" } ], "default": null, "title": "Rows" }, "row_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Row Count" } }, "required": [ "success" ], "title": "ReadRowsOutput", "type": "object" } ``` ### Parameters The spreadsheet ID from the Google Sheets URL. The worksheet (tab) name. A1 notation range within the sheet (e.g. 'A1:E5'). Omit to read the entire sheet. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "range": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Range" }, "values": { "anyOf": [ { "items": { "items": {}, "type": "array" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Values" }, "row_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Row Count" } }, "required": [ "success" ], "title": "GetValuesInRangeOutput", "type": "object" } ``` ### Parameters The spreadsheet ID from the Google Sheets URL. The worksheet (tab) name. Column header name (e.g. 'Name') or column letter (e.g. 'A', 'B'). The value to search for. How to match. Valid values: 'exact' (case-insensitive exact match), 'contains' (substring), 'starts_with'. (Default: `contains`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "matches": { "anyOf": [ { "items": { "additionalProperties": true, "type": "object" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Matches" }, "match_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Match Count" } }, "required": [ "success" ], "title": "FindRowsOutput", "type": "object" } ``` ### Parameters The spreadsheet ID from the Google Sheets URL. The worksheet (tab) name. Array of rows to append. Each row is either an array of positional cell values or an object whose keys match the worksheet's column headers. Whether row 1 contains column headers. Required when passing rows as objects. (Default: `true`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "rows_added": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Rows Added" }, "updated_range": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated Range" }, "updated_rows": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Updated Rows" }, "updated_columns": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Updated Columns" }, "updated_cells": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Updated Cells" } }, "required": [ "success" ], "title": "AddRowsOutput", "type": "object" } ``` ### Parameters The spreadsheet ID from the Google Sheets URL. The worksheet (tab) name. 1-indexed row number to update. Array of cell values for the row, in column order. Numbers, strings, and booleans are all accepted. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "updated_range": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated Range" }, "updated_rows": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Updated Rows" }, "updated_columns": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Updated Columns" }, "updated_cells": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Updated Cells" } }, "required": [ "success" ], "title": "UpdateRowOutput", "type": "object" } ``` ### Parameters The spreadsheet ID from the Google Sheets URL. The worksheet (tab) name. A1 notation for the cell (e.g. 'B5'). The new cell value. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "updated_range": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated Range" }, "updated_cells": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Updated Cells" } }, "required": [ "success" ], "title": "UpdateCellOutput", "type": "object" } ``` ### Parameters The spreadsheet ID from the Google Sheets URL. The worksheet (tab) name. 1-indexed row number of the first row to clear (inclusive). 1-indexed row number of the last row to clear (inclusive). Omit to clear only the start row. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "spreadsheet_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Spreadsheet Id" }, "cleared_range": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Cleared Range" } }, "required": [ "success" ], "title": "ClearRowsOutput", "type": "object" } ``` ### Parameters The spreadsheet ID from the Google Sheets URL. The numeric worksheet (sheet) ID. Use get_spreadsheet_info to discover sheet IDs. 1-indexed row number of the first row to delete (inclusive). 1-indexed row number of the last row to delete (inclusive). Omit to delete only the start row. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "spreadsheet_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Spreadsheet Id" }, "deleted_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Deleted Count" } }, "required": [ "success" ], "title": "DeleteRowsOutput", "type": "object" } ``` ## Limits & Quotas - Google Sheets API default quota: 300 read requests / minute / project and 300 write requests / minute / project (with a 60 / minute / user cap for both). - Cell-value writes use `valueInputOption=USER_ENTERED`, so values are parsed as if a user typed them in the UI (numbers/dates/booleans get coerced). - `add_rows` uses `insertDataOption=INSERT_ROWS`, so it never overwrites existing data — new rows are inserted after the last row containing data. - Non-2xx responses are caught and returned as `success=False` + `error` with the API `error.message` (when present). - Timeouts surface as `success=False` with `error="Request timed out."`. ## Related integrations ## Links - [Google Sheets](https://www.google.com/sheets/about/) --- # Google Slides Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/google-slides Create and edit Google Slides presentations — manage slides, shapes, images, tables, and text via the Google Slides REST API. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/google_slides/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Google Slides logo ## Overview Add **Google Slides** to any ModuleX agent or workflow. Create and edit Google Slides presentations from agents — manage slides, shapes, images, tables, and text via the Google Slides REST API (`slides.googleapis.com/v1`). **Categories**: Productivity & Collaboration · Document Management · **Auth**: OAuth2 · **Actions**: 15 ## Authentication ### OAuth2 Authentication Connect using Google OAuth (recommended) #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Client ID | Google Cloud OAuth 2.0 Client ID | Yes | `1234567890-abcdefghijklmnop.apps.googleusercontent.com` | | Client Secret | Google Cloud OAuth 2.0 Client Secret | Yes | `GOCSPX-xxxxxxxxxxxxxxxxxxxxxxxx` | #### OAuth Configuration - **Authorization URL**: `https://accounts.google.com/o/oauth2/v2/auth` - **Token URL**: `https://oauth2.googleapis.com/token` - **Scopes**: `https://www.googleapis.com/auth/presentations` ## Available Actions ### Parameters ID of the target Google Slides presentation. Object ID of the slide (page) on which to place the image. Publicly accessible URL of the image to insert; must be reachable by Google's servers. Height of the image in points (1 pt = 1/72 inch). Width of the image in points (1 pt = 1/72 inch). Horizontal scale factor for the image. (Default: `1`) Vertical scale factor for the image. (Default: `1`) Horizontal translation (offset) of the image in points. (Default: `0`) Vertical translation (offset) of the image in points. (Default: `0`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "presentation_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Presentation Id" }, "image_object_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Image Object Id" }, "replies": { "items": { "additionalProperties": true, "type": "object" }, "title": "Replies", "type": "array" }, "write_control": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Write Control" } }, "required": [ "success" ], "title": "CreateImageOutput", "type": "object" } ``` ### Parameters ID of the target Google Slides presentation. Object ID of the slide on which to place the shape. Shape type. Common values: 'TEXT_BOX', 'RECTANGLE', 'ELLIPSE', 'ROUND_RECTANGLE', 'ARROW_EAST', 'RIGHT_ARROW', 'STAR_5', 'CLOUD'. See Google Slides API Type enum for the full list. Height of the shape in points (1 pt = 1/72 inch). Width of the shape in points (1 pt = 1/72 inch). Horizontal scale factor for the shape. (Default: `1`) Vertical scale factor for the shape. (Default: `1`) Horizontal translation (offset) of the shape in points. (Default: `0`) Vertical translation (offset) of the shape in points. (Default: `0`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "presentation_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Presentation Id" }, "shape_object_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Shape Object Id" }, "replies": { "items": { "additionalProperties": true, "type": "object" }, "title": "Replies", "type": "array" }, "write_control": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Write Control" } }, "required": [ "success" ], "title": "CreatePageElementOutput", "type": "object" } ``` ### Parameters Title of the new presentation. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "presentation_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Presentation Id" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "revision_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Revision Id" }, "raw": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Raw" } }, "required": [ "success" ], "title": "CreatePresentationOutput", "type": "object" } ``` ### Parameters ID of the target Google Slides presentation. Object ID of the slide layout to apply (from presentation.layouts[*].objectId). Optional 0-based index where the slide is inserted. Omit to append at the end. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "presentation_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Presentation Id" }, "slide_object_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Slide Object Id" }, "replies": { "items": { "additionalProperties": true, "type": "object" }, "title": "Replies", "type": "array" }, "write_control": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Write Control" } }, "required": [ "success" ], "title": "CreateSlideOutput", "type": "object" } ``` ### Parameters ID of the target Google Slides presentation. Object ID of the slide on which to place the table. Number of rows in the table. Number of columns in the table. Height of the table in points (1 pt = 1/72 inch). Width of the table in points (1 pt = 1/72 inch). Optional horizontal translation (offset) of the table in points. Optional vertical translation (offset) of the table in points. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "presentation_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Presentation Id" }, "table_object_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Table Object Id" }, "replies": { "items": { "additionalProperties": true, "type": "object" }, "title": "Replies", "type": "array" }, "write_control": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Write Control" } }, "required": [ "success" ], "title": "CreateTableOutput", "type": "object" } ``` ### Parameters ID of the target Google Slides presentation. Object ID of the page element to delete. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "presentation_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Presentation Id" }, "deleted_object_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Deleted Object Id" }, "replies": { "items": { "additionalProperties": true, "type": "object" }, "title": "Replies", "type": "array" }, "write_control": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Write Control" } }, "required": [ "success" ], "title": "DeletePageElementOutput", "type": "object" } ``` ### Parameters ID of the target Google Slides presentation. Object ID of the slide to delete. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "presentation_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Presentation Id" }, "deleted_slide_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Deleted Slide Id" }, "replies": { "items": { "additionalProperties": true, "type": "object" }, "title": "Replies", "type": "array" }, "write_control": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Write Control" } }, "required": [ "success" ], "title": "DeleteSlideOutput", "type": "object" } ``` ### Parameters ID of the target Google Slides presentation. Object ID of the table to modify. 0-based index of the column inside the table to delete. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "presentation_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Presentation Id" }, "replies": { "items": { "additionalProperties": true, "type": "object" }, "title": "Replies", "type": "array" }, "write_control": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Write Control" } }, "required": [ "success" ], "title": "DeleteTableColumnOutput", "type": "object" } ``` ### Parameters ID of the target Google Slides presentation. Object ID of the table to modify. 0-based index of the row inside the table to delete. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "presentation_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Presentation Id" }, "replies": { "items": { "additionalProperties": true, "type": "object" }, "title": "Replies", "type": "array" }, "write_control": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Write Control" } }, "required": [ "success" ], "title": "DeleteTableRowOutput", "type": "object" } ``` ### Parameters ID of the Google Slides presentation to retrieve. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "presentation_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Presentation Id" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "locale": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Locale" }, "revision_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Revision Id" }, "page_size": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Page Size" }, "slides": { "items": { "additionalProperties": true, "type": "object" }, "title": "Slides", "type": "array" }, "layouts": { "items": { "additionalProperties": true, "type": "object" }, "title": "Layouts", "type": "array" }, "masters": { "items": { "additionalProperties": true, "type": "object" }, "title": "Masters", "type": "array" }, "notes_master": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Notes Master" }, "raw": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Raw" } }, "required": [ "success" ], "title": "FindPresentationOutput", "type": "object" } ``` ### Parameters ID of the target Google Slides presentation. Object ID of the table to modify. Optional 0-based column index of the reference cell. Whether the new columns are inserted to the right of the reference cell. Number of columns to insert (max 20 per request). (Default: `1`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "presentation_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Presentation Id" }, "replies": { "items": { "additionalProperties": true, "type": "object" }, "title": "Replies", "type": "array" }, "write_control": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Write Control" } }, "required": [ "success" ], "title": "InsertTableColumnsOutput", "type": "object" } ``` ### Parameters ID of the target Google Slides presentation. Object ID of the table to modify. Optional 0-based row index of the reference cell. Whether the new rows are inserted below the reference cell. Number of rows to insert (max 20 per request). (Default: `1`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "presentation_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Presentation Id" }, "replies": { "items": { "additionalProperties": true, "type": "object" }, "title": "Replies", "type": "array" }, "write_control": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Write Control" } }, "required": [ "success" ], "title": "InsertTableRowsOutput", "type": "object" } ``` ### Parameters ID of the target Google Slides presentation. Object ID of the target shape (must contain a text frame, e.g. a TEXT_BOX). Text content to insert into the shape. Optional 0-based character index at which to insert the text. Omit to append at the end. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "presentation_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Presentation Id" }, "replies": { "items": { "additionalProperties": true, "type": "object" }, "title": "Replies", "type": "array" }, "write_control": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Write Control" } }, "required": [ "success" ], "title": "InsertTextOutput", "type": "object" } ``` ### Parameters ID of the target Google Slides presentation. Object ID of the target table. Text content to insert into the table cell. Optional 0-based row index of the target cell. Optional 0-based column index of the target cell. Optional 0-based character index within the cell at which to insert the text. Omit to append at the end. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "presentation_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Presentation Id" }, "replies": { "items": { "additionalProperties": true, "type": "object" }, "title": "Replies", "type": "array" }, "write_control": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Write Control" } }, "required": [ "success" ], "title": "InsertTextIntoTableOutput", "type": "object" } ``` ### Parameters ID of the target Google Slides presentation. Text snippet to search for. Replacement text inserted for every match found. Optional list of slide page object IDs (strings). When supplied, only page elements on these pages are searched. Whether the text match is case-sensitive. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "presentation_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Presentation Id" }, "total_occurrences_changed": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Total Occurrences Changed" }, "replies": { "items": { "additionalProperties": true, "type": "object" }, "title": "Replies", "type": "array" }, "write_control": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Write Control" } }, "required": [ "success" ], "title": "ReplaceAllTextOutput", "type": "object" } ``` ## Limits & Quotas - **Per-project quotas**: Google Slides API defaults to a few hundred read or write requests per 100 seconds per project; per-user limits also apply (see the [Slides API quotas page](https://developers.google.com/workspace/slides/api/limits)). Quotas are managed in the Google Cloud Console (APIs & Services -> Quotas). - **batchUpdate body**: Slides `batchUpdate` requests should stay under ~10 MB; for very large updates, batch the work into multiple calls. - **Insert table rows/columns**: capped at 20 per single request, per Google's documented `InsertTableRowsRequest` / `InsertTableColumnsRequest` limits. - **Error model**: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. Plan for retries on the agent side based on the error string. OAuth tokens that have expired surface as HTTP 401 in the `error` field. ## Related integrations ## Links - [Google Slides](https://slides.google.com) --- # Google Tag Manager Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/google-tag-manager Manage tags, variables, and workspaces in Google Tag Manager containers {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/google_tag_manager/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Google Tag Manager logo ## Overview Add **Google Tag Manager** to any ModuleX agent or workflow. Manage tags, variables, and workspaces in Google Tag Manager containers via the Tag Manager API v2 (`www.googleapis.com/tagmanager/v2`). **Categories**: Analytics & Data · Analytics · Marketing · **Auth**: OAuth2 · **Actions**: 6 ## Authentication ### OAuth2 Authentication Connect using Google OAuth (recommended) #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Client ID | Google OAuth App Client ID | Yes | `-` | | Client Secret | Google OAuth App Client Secret | Yes | `-` | #### OAuth Configuration - **Authorization URL**: `https://accounts.google.com/o/oauth2/v2/auth` - **Token URL**: `https://oauth2.googleapis.com/token` - **Scopes**: `https://www.googleapis.com/auth/tagmanager.edit.containers`, `https://www.googleapis.com/auth/tagmanager.readonly` ## Available Actions ### Parameters The Google Tag Manager account ID The container ID The workspace ID The name of the tag The type of the tag (see Tag Dictionary Reference) JSON string representing the list of parameters for the tag Whether the tag should only fire in the live environment Any notes or comments about the tag JSON string representing consent settings for the tag JSON string representing monitoring metadata for the tag ### Response ```json { "$defs": { "TagResource": { "additionalProperties": false, "description": "A Google Tag Manager tag resource.", "properties": { "tag_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Tag Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Type" }, "live_only": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Live Only" }, "notes": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Notes" }, "parameter": { "items": { "additionalProperties": true, "type": "object" }, "title": "Parameter", "type": "array" }, "fingerprint": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Fingerprint" }, "tag_manager_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Tag Manager Url" }, "path": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Path" }, "account_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Account Id" }, "container_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Container Id" }, "workspace_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Workspace Id" } }, "title": "TagResource", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "tag": { "anyOf": [ { "$ref": "#/$defs/TagResource" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "CreateTagOutput", "type": "object" } ``` ### Parameters The Google Tag Manager account ID The container ID The workspace ID The tag ID ### Response ```json { "$defs": { "TagResource": { "additionalProperties": false, "description": "A Google Tag Manager tag resource.", "properties": { "tag_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Tag Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Type" }, "live_only": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Live Only" }, "notes": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Notes" }, "parameter": { "items": { "additionalProperties": true, "type": "object" }, "title": "Parameter", "type": "array" }, "fingerprint": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Fingerprint" }, "tag_manager_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Tag Manager Url" }, "path": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Path" }, "account_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Account Id" }, "container_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Container Id" }, "workspace_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Workspace Id" } }, "title": "TagResource", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "tag": { "anyOf": [ { "$ref": "#/$defs/TagResource" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "GetTagOutput", "type": "object" } ``` ### Parameters The Google Tag Manager account ID The container ID The workspace ID ### Response ```json { "$defs": { "TagResource": { "additionalProperties": false, "description": "A Google Tag Manager tag resource.", "properties": { "tag_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Tag Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Type" }, "live_only": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Live Only" }, "notes": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Notes" }, "parameter": { "items": { "additionalProperties": true, "type": "object" }, "title": "Parameter", "type": "array" }, "fingerprint": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Fingerprint" }, "tag_manager_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Tag Manager Url" }, "path": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Path" }, "account_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Account Id" }, "container_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Container Id" }, "workspace_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Workspace Id" } }, "title": "TagResource", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "tags": { "items": { "$ref": "#/$defs/TagResource" }, "title": "Tags", "type": "array" } }, "required": [ "success" ], "title": "GetTagsOutput", "type": "object" } ``` ### Response ```json { "$defs": { "AccountItem": { "additionalProperties": false, "description": "A Google Tag Manager account.", "properties": { "account_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Account Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "share_data": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Share Data" }, "fingerprint": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Fingerprint" }, "path": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Path" }, "tag_manager_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Tag Manager Url" } }, "title": "AccountItem", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "accounts": { "items": { "$ref": "#/$defs/AccountItem" }, "title": "Accounts", "type": "array" } }, "required": [ "success" ], "title": "ListAccountIdOptionsOutput", "type": "object" } ``` ### Parameters The Google Tag Manager account ID The container ID The workspace ID The tag ID The type of the tag (see Tag Dictionary Reference) JSON string representing the list of parameters for the tag The name of the tag Whether the tag should only fire in the live environment Any notes or comments about the tag JSON string representing consent settings for the tag JSON string representing monitoring metadata for the tag ### Response ```json { "$defs": { "TagResource": { "additionalProperties": false, "description": "A Google Tag Manager tag resource.", "properties": { "tag_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Tag Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Type" }, "live_only": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Live Only" }, "notes": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Notes" }, "parameter": { "items": { "additionalProperties": true, "type": "object" }, "title": "Parameter", "type": "array" }, "fingerprint": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Fingerprint" }, "tag_manager_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Tag Manager Url" }, "path": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Path" }, "account_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Account Id" }, "container_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Container Id" }, "workspace_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Workspace Id" } }, "title": "TagResource", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "tag": { "anyOf": [ { "$ref": "#/$defs/TagResource" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "UpdateTagOutput", "type": "object" } ``` ### Parameters The Google Tag Manager account ID The container ID The workspace ID The variable ID The name of the variable The type of the variable (e.g. 'jsm') JSON string representing the list of parameters for the variable JSON string representing the formatValue object for the variable ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "UpdateVariableOutput", "type": "object" } ``` ## Limits & Quotas - Google Tag Manager API uses per-project quotas managed via the Google Cloud Console. - Default quota: 10,000 requests per day per project (varies by endpoint). - Rate limit: approximately 5 requests per second per user. - Error model: non-2xx responses raise `httpx.HTTPStatusError` (Pattern A). ## Related integrations ## Links - [Google Tag Manager](https://tagmanager.google.com) --- # Google Tasks Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/google-tasks Manage tasks and task lists using the Google Tasks API {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/google_tasks/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Google Tasks logo ## Overview Add **Google Tasks** to any ModuleX agent or workflow. Manage tasks and task lists using the Google Tasks REST API (`tasks.googleapis.com/tasks/v1`). **Categories**: Productivity & Collaboration · Task Management · **Auth**: OAuth2 · **Actions**: 8 ## Authentication ### OAuth2 Authentication Connect using Google OAuth (recommended) #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Client ID | Google OAuth App Client ID | Yes | `xxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com` | | Client Secret | Google OAuth App Client Secret | Yes | `GOCSPX-xxxxxxxxxxxxxxxxxxxxxxxxxxxx` | #### OAuth Configuration - **Authorization URL**: `https://accounts.google.com/o/oauth2/v2/auth` - **Token URL**: `https://oauth2.googleapis.com/token` - **Scopes**: `https://www.googleapis.com/auth/tasks` ## Available Actions ### Parameters The ID of the task list The title of the task The description of the task Mark as true if your task is already completed Due date of the task as an RFC 3339 timestamp (date portion only) ### Response ```json { "$defs": { "TaskItem": { "additionalProperties": false, "description": "A Google Tasks task resource.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "notes": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Notes" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "due": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Due" }, "updated": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated" }, "self_link": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Self Link" }, "etag": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Etag" }, "kind": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Kind" } }, "title": "TaskItem", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "task": { "anyOf": [ { "$ref": "#/$defs/TaskItem" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "CreateTaskOutput", "type": "object" } ``` ### Parameters The title of the task list ### Response ```json { "$defs": { "TaskListItem": { "additionalProperties": false, "description": "A Google Tasks task list resource.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "updated": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated" }, "self_link": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Self Link" }, "etag": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Etag" }, "kind": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Kind" } }, "title": "TaskListItem", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "task_list": { "anyOf": [ { "$ref": "#/$defs/TaskListItem" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "CreateTaskListOutput", "type": "object" } ``` ### Parameters The ID of the task list The ID of the task ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" } }, "required": [ "success" ], "title": "DeleteTaskOutput", "type": "object" } ``` ### Parameters The ID of the task list ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" } }, "required": [ "success" ], "title": "DeleteTaskListOutput", "type": "object" } ``` ### Parameters The ID of the task list Maximum number of tasks to be fetched (Default: `20`) Whether completed tasks are returned in the result Whether deleted tasks are returned in the result ### Response ```json { "$defs": { "TaskItem": { "additionalProperties": false, "description": "A Google Tasks task resource.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "notes": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Notes" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "due": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Due" }, "updated": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated" }, "self_link": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Self Link" }, "etag": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Etag" }, "kind": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Kind" } }, "title": "TaskItem", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "tasks": { "items": { "$ref": "#/$defs/TaskItem" }, "title": "Tasks", "type": "array" } }, "required": [ "success" ], "title": "ListTasksOutput", "type": "object" } ``` ### Parameters Maximum number of task lists to be fetched (Default: `20`) ### Response ```json { "$defs": { "TaskListItem": { "additionalProperties": false, "description": "A Google Tasks task list resource.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "updated": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated" }, "self_link": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Self Link" }, "etag": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Etag" }, "kind": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Kind" } }, "title": "TaskListItem", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "task_lists": { "items": { "$ref": "#/$defs/TaskListItem" }, "title": "Task Lists", "type": "array" } }, "required": [ "success" ], "title": "ListTaskListsOutput", "type": "object" } ``` ### Parameters The ID of the task list The ID of the task The title of the task The description of the task Mark as true if your task is already completed Due date of the task as an RFC 3339 timestamp (date portion only) ### Response ```json { "$defs": { "TaskItem": { "additionalProperties": false, "description": "A Google Tasks task resource.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "notes": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Notes" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "due": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Due" }, "updated": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated" }, "self_link": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Self Link" }, "etag": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Etag" }, "kind": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Kind" } }, "title": "TaskItem", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "task": { "anyOf": [ { "$ref": "#/$defs/TaskItem" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "UpdateTaskOutput", "type": "object" } ``` ### Parameters The ID of the task list The title of the task list ### Response ```json { "$defs": { "TaskListItem": { "additionalProperties": false, "description": "A Google Tasks task list resource.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "updated": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated" }, "self_link": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Self Link" }, "etag": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Etag" }, "kind": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Kind" } }, "title": "TaskListItem", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "task_list": { "anyOf": [ { "$ref": "#/$defs/TaskListItem" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "UpdateTaskListOutput", "type": "object" } ``` ## Limits & Quotas - Default quota: 50,000 queries per day per project (Google Cloud project-level). - Per-user rate limit: ~500 requests per 100 seconds. - Error model: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. ## Related integrations ## Links - [Google Tasks](https://tasks.google.com) --- # Google Workspace Admin Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/google-workspace Retrieve admin audit activity reports from Google Workspace via the Admin SDK Reports API {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/google_workspace/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Google Workspace Admin logo Google Workspace Admin logo ## Overview Add **Google Workspace Admin** to any ModuleX agent or workflow. Retrieve admin audit activity reports from Google Workspace via the Admin SDK Reports API (`admin.googleapis.com`). **Categories**: Productivity & Collaboration · Admin · Enterprise · **Auth**: OAuth2 · **Actions**: 4 ## Authentication ### OAuth2 Authentication Connect using Google OAuth (recommended) #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Client ID | Google OAuth App Client ID | Yes | `xxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com` | | Client Secret | Google OAuth App Client Secret | Yes | `GOCSPX-xxxxxxxxxxxxxxxxxxxxxxxxxxxx` | #### OAuth Configuration - **Authorization URL**: `https://accounts.google.com/o/oauth2/v2/auth` - **Token URL**: `https://oauth2.googleapis.com/token` - **Scopes**: `https://www.googleapis.com/auth/admin.reports.audit.readonly` ## Available Actions ### Parameters Application name to retrieve events for. Valid values: access_transparency, admin, calendar, chat, drive, gcp, gplus, groups, groups_enterprise, jamboard, login, meet, mobile, rules, saml, token, user_accounts, context_aware_access, chrome, data_studio, keep Profile ID or email of the administrator to filter activities for Name of the event to filter by End of the time range in RFC 3339 format (e.g. 2010-10-28T10:26:35.000Z) Beginning of the time range in RFC 3339 format (e.g. 2010-10-28T10:26:35.000Z) Maximum number of activity records to return per page (default 1000) Comma-separated event parameter filters (e.g. parameter_name relational_operator value) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "activities": { "items": { "additionalProperties": true, "type": "object" }, "title": "Activities", "type": "array" } }, "required": [ "success" ], "title": "ListActivitiesByAdminOutput", "type": "object" } ``` ### Parameters Application name to retrieve events for. Valid values: access_transparency, admin, calendar, chat, drive, gcp, gplus, groups, groups_enterprise, jamboard, login, meet, mobile, rules, saml, token, user_accounts, context_aware_access, chrome, data_studio, keep Name of the event to filter by Profile ID or email of the administrator to filter activities for End of the time range in RFC 3339 format (e.g. 2010-10-28T10:26:35.000Z) Beginning of the time range in RFC 3339 format (e.g. 2010-10-28T10:26:35.000Z) Maximum number of activity records to return per page (default 1000) Comma-separated event parameter filters (e.g. parameter_name relational_operator value) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "activities": { "items": { "additionalProperties": true, "type": "object" }, "title": "Activities", "type": "array" } }, "required": [ "success" ], "title": "ListActivitiesByEventAndAdminOutput", "type": "object" } ``` ### Parameters Application name to retrieve events for. Valid values: access_transparency, admin, calendar, chat, drive, gcp, gplus, groups, groups_enterprise, jamboard, login, meet, mobile, rules, saml, token, user_accounts, context_aware_access, chrome, data_studio, keep Name of the event to filter by End of the time range in RFC 3339 format (e.g. 2010-10-28T10:26:35.000Z) Beginning of the time range in RFC 3339 format (e.g. 2010-10-28T10:26:35.000Z) Maximum number of activity records to return per page (default 1000) Comma-separated event parameter filters (e.g. parameter_name relational_operator value) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "activities": { "items": { "additionalProperties": true, "type": "object" }, "title": "Activities", "type": "array" } }, "required": [ "success" ], "title": "ListActivitiesByEventNameOutput", "type": "object" } ``` ### Parameters Application name to retrieve events for. Valid values: access_transparency, admin, calendar, chat, drive, gcp, gplus, groups, groups_enterprise, jamboard, login, meet, mobile, rules, saml, token, user_accounts, context_aware_access, chrome, data_studio, keep End of the time range in RFC 3339 format (e.g. 2010-10-28T10:26:35.000Z) Beginning of the time range in RFC 3339 format (e.g. 2010-10-28T10:26:35.000Z) Maximum number of activity records to return per page (default 1000) Comma-separated event parameter filters (e.g. parameter_name relational_operator value) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "activities": { "items": { "additionalProperties": true, "type": "object" }, "title": "Activities", "type": "array" } }, "required": [ "success" ], "title": "ListAllActivitiesOutput", "type": "object" } ``` ## Limits & Quotas - **Admin SDK Reports API quota**: 2,400 queries per minute per Google Workspace domain (default). - **Per-user limit**: 480 queries per minute per user. - **Error model**: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. Plan for retries on the agent side based on the error string. ## Related integrations ## Links - [Google Workspace Admin](https://workspace.google.com) --- # Grafana Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/grafana Manage Grafana dashboards, alert rules, annotations, contact points, data sources, and folders, and monitor instance and data source health via the Grafana HTTP API. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/grafana/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Grafana logo ## Overview Add **Grafana** to any ModuleX agent or workflow. Manage Grafana dashboards, alert rules, annotations, contact points, data sources, and folders, and monitor instance and data source health against the Grafana HTTP API of your own instance. **Categories**: Monitoring & Observability · Monitoring · Data Analytics · **Auth**: Service Account Token · **Actions**: 25 ## Authentication ### Service Account Token Authenticate with a Grafana service account token against your instance In Grafana, open Administration -> Users and access -> Service accounts Create a service account with the roles your workflow needs (e.g. Editor or Admin) Add a service account token and copy the generated 'glsa_...' value Paste the token below and set your Grafana instance URL (e.g. https://your-grafana.com) #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Service Account Token | Your Grafana service account token (starts with glsa_) | Yes | `glsa_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx_xxxxxxxx` | | Grafana URL | Your Grafana instance base URL, e.g. https://your-grafana.com | Yes | `https://your-grafana.com` | ## Available Actions ### Parameters Organization ID for multi-org Grafana instances (e.g. 1, 2). Sent as the X-Grafana-Org-Id header when set. Search query to filter dashboards by title Filter by tag (comma-separated for multiple tags) Filter by folder UIDs (comma-separated) Filter by dashboard UIDs (comma-separated) Only return starred dashboards Maximum number of dashboards to return Page number for pagination (1-based) ### Response ```json { "$defs": { "DashboardSearchResult": { "additionalProperties": false, "description": "A single dashboard search-result row from ``/api/search``.", "properties": { "id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Id" }, "uid": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Uid" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "uri": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Uri" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" }, "type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Type" }, "tags": { "items": { "type": "string" }, "title": "Tags", "type": "array" }, "is_starred": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Starred" }, "folder_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Folder Id" }, "folder_uid": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Folder Uid" }, "folder_title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Folder Title" }, "folder_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Folder Url" } }, "title": "DashboardSearchResult", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "dashboards": { "items": { "$ref": "#/$defs/DashboardSearchResult" }, "title": "Dashboards", "type": "array" } }, "required": [ "success" ], "title": "ListDashboardsOutput", "type": "object" } ``` ### Parameters The UID of the dashboard to retrieve Organization ID for multi-org Grafana instances (e.g. 1, 2). Sent as the X-Grafana-Org-Id header when set. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "dashboard": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Dashboard" }, "meta": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Meta" } }, "required": [ "success" ], "title": "GetDashboardOutput", "type": "object" } ``` ### Parameters The title of the new dashboard UID of the folder to create the dashboard in Comma-separated list of tags Dashboard timezone (e.g. browser, utc) Auto-refresh interval (e.g. 5s, 1m, 5m) JSON array of panel configurations Overwrite an existing dashboard with the same title Commit message for the dashboard version Organization ID for multi-org Grafana instances (e.g. 1, 2). Sent as the X-Grafana-Org-Id header when set. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Id" }, "uid": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Uid" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "version": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Version" }, "slug": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Slug" } }, "required": [ "success" ], "title": "CreateDashboardOutput", "type": "object" } ``` ### Parameters The UID of the dashboard to update New title for the dashboard New folder UID to move the dashboard to Comma-separated list of new tags Dashboard timezone (e.g. browser, utc) Auto-refresh interval (e.g. 5s, 1m, 5m) JSON array of panel configurations Overwrite even on a version conflict Commit message for this version Organization ID for multi-org Grafana instances (e.g. 1, 2). Sent as the X-Grafana-Org-Id header when set. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Id" }, "uid": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Uid" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "version": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Version" }, "slug": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Slug" } }, "required": [ "success" ], "title": "UpdateDashboardOutput", "type": "object" } ``` ### Parameters The UID of the dashboard to delete Organization ID for multi-org Grafana instances (e.g. 1, 2). Sent as the X-Grafana-Org-Id header when set. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" }, "id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Id" } }, "required": [ "success" ], "title": "DeleteDashboardOutput", "type": "object" } ``` ### Parameters Organization ID for multi-org Grafana instances (e.g. 1, 2). Sent as the X-Grafana-Org-Id header when set. ### Response ```json { "$defs": { "AlertRule": { "additionalProperties": false, "description": "A provisioned alert rule (shared by list/get/create/update).", "properties": { "id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Id" }, "uid": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Uid" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "condition": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Condition" }, "data": { "items": {}, "title": "Data", "type": "array" }, "updated": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated" }, "no_data_state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "No Data State" }, "exec_err_state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Exec Err State" }, "for_": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "For" }, "keep_firing_for": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Keep Firing For" }, "missing_series_evals_to_resolve": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Missing Series Evals To Resolve" }, "annotations": { "additionalProperties": true, "title": "Annotations", "type": "object" }, "labels": { "additionalProperties": true, "title": "Labels", "type": "object" }, "is_paused": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Paused" }, "folder_uid": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Folder Uid" }, "rule_group": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Rule Group" }, "org_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Org Id" }, "provenance": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Provenance" }, "notification_settings": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Notification Settings" }, "record": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Record" } }, "title": "AlertRule", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "rules": { "items": { "$ref": "#/$defs/AlertRule" }, "title": "Rules", "type": "array" } }, "required": [ "success" ], "title": "ListAlertRulesOutput", "type": "object" } ``` ### Parameters The UID of the alert rule to retrieve Organization ID for multi-org Grafana instances (e.g. 1, 2). Sent as the X-Grafana-Org-Id header when set. ### Response ```json { "$defs": { "AlertRule": { "additionalProperties": false, "description": "A provisioned alert rule (shared by list/get/create/update).", "properties": { "id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Id" }, "uid": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Uid" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "condition": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Condition" }, "data": { "items": {}, "title": "Data", "type": "array" }, "updated": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated" }, "no_data_state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "No Data State" }, "exec_err_state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Exec Err State" }, "for_": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "For" }, "keep_firing_for": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Keep Firing For" }, "missing_series_evals_to_resolve": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Missing Series Evals To Resolve" }, "annotations": { "additionalProperties": true, "title": "Annotations", "type": "object" }, "labels": { "additionalProperties": true, "title": "Labels", "type": "object" }, "is_paused": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Paused" }, "folder_uid": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Folder Uid" }, "rule_group": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Rule Group" }, "org_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Org Id" }, "provenance": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Provenance" }, "notification_settings": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Notification Settings" }, "record": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Record" } }, "title": "AlertRule", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "rule": { "anyOf": [ { "$ref": "#/$defs/AlertRule" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "GetAlertRuleOutput", "type": "object" } ``` ### Parameters The title of the alert rule UID of the folder to create the alert in The name of the rule group JSON array of query/expression data objects RefId of the query/expression used as the alert condition (omit for recording rules) Duration to wait before firing (e.g. 5m, 1h) State when no data is returned (NoData, Alerting, OK) State on execution error (Error, Alerting, OK) JSON object of annotations JSON object of labels Optional custom UID for the alert rule Whether the rule is paused on creation Duration to keep firing after the condition stops Missing series evaluations before resolving JSON object of per-rule notification settings JSON object configuring this as a recording rule Set the X-Disable-Provenance header so the rule stays UI-editable Organization ID for multi-org Grafana instances (e.g. 1, 2). Sent as the X-Grafana-Org-Id header when set. ### Response ```json { "$defs": { "AlertRule": { "additionalProperties": false, "description": "A provisioned alert rule (shared by list/get/create/update).", "properties": { "id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Id" }, "uid": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Uid" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "condition": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Condition" }, "data": { "items": {}, "title": "Data", "type": "array" }, "updated": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated" }, "no_data_state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "No Data State" }, "exec_err_state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Exec Err State" }, "for_": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "For" }, "keep_firing_for": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Keep Firing For" }, "missing_series_evals_to_resolve": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Missing Series Evals To Resolve" }, "annotations": { "additionalProperties": true, "title": "Annotations", "type": "object" }, "labels": { "additionalProperties": true, "title": "Labels", "type": "object" }, "is_paused": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Paused" }, "folder_uid": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Folder Uid" }, "rule_group": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Rule Group" }, "org_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Org Id" }, "provenance": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Provenance" }, "notification_settings": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Notification Settings" }, "record": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Record" } }, "title": "AlertRule", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "rule": { "anyOf": [ { "$ref": "#/$defs/AlertRule" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "CreateAlertRuleOutput", "type": "object" } ``` ### Parameters The UID of the alert rule to update New title for the alert rule New folder UID for the rule New rule group name New condition refId New JSON array of query/expression data objects Duration to wait before firing (e.g. 5m, 1h) State when no data is returned (NoData, Alerting, OK) State on execution error (Error, Alerting, OK) JSON object of annotations JSON object of labels Whether the rule is paused Duration to keep firing after the condition stops Missing series evaluations before resolving JSON object of per-rule notification settings JSON object configuring this as a recording rule Set the X-Disable-Provenance header so the rule stays UI-editable Organization ID for multi-org Grafana instances (e.g. 1, 2). Sent as the X-Grafana-Org-Id header when set. ### Response ```json { "$defs": { "AlertRule": { "additionalProperties": false, "description": "A provisioned alert rule (shared by list/get/create/update).", "properties": { "id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Id" }, "uid": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Uid" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "condition": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Condition" }, "data": { "items": {}, "title": "Data", "type": "array" }, "updated": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated" }, "no_data_state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "No Data State" }, "exec_err_state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Exec Err State" }, "for_": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "For" }, "keep_firing_for": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Keep Firing For" }, "missing_series_evals_to_resolve": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Missing Series Evals To Resolve" }, "annotations": { "additionalProperties": true, "title": "Annotations", "type": "object" }, "labels": { "additionalProperties": true, "title": "Labels", "type": "object" }, "is_paused": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Paused" }, "folder_uid": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Folder Uid" }, "rule_group": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Rule Group" }, "org_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Org Id" }, "provenance": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Provenance" }, "notification_settings": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Notification Settings" }, "record": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Record" } }, "title": "AlertRule", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "rule": { "anyOf": [ { "$ref": "#/$defs/AlertRule" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "UpdateAlertRuleOutput", "type": "object" } ``` ### Parameters The UID of the alert rule to delete Organization ID for multi-org Grafana instances (e.g. 1, 2). Sent as the X-Grafana-Org-Id header when set. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" } }, "required": [ "success" ], "title": "DeleteAlertRuleOutput", "type": "object" } ``` ### Parameters Filter contact points by exact name match Organization ID for multi-org Grafana instances (e.g. 1, 2). Sent as the X-Grafana-Org-Id header when set. ### Response ```json { "$defs": { "ContactPoint": { "additionalProperties": false, "description": "A single notification contact point.", "properties": { "uid": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Uid" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Type" }, "settings": { "additionalProperties": true, "title": "Settings", "type": "object" }, "disable_resolve_message": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Disable Resolve Message" }, "provenance": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Provenance" } }, "title": "ContactPoint", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "contact_points": { "items": { "$ref": "#/$defs/ContactPoint" }, "title": "Contact Points", "type": "array" } }, "required": [ "success" ], "title": "ListContactPointsOutput", "type": "object" } ``` ### Parameters Name of the contact point Receiver type (e.g. slack, email, pagerduty, webhook, teams, opsgenie, discord) JSON object of type-specific receiver settings Do not send a notification when the alert resolves Set the X-Disable-Provenance header so the contact point stays UI-editable Organization ID for multi-org Grafana instances (e.g. 1, 2). Sent as the X-Grafana-Org-Id header when set. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "uid": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Uid" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Type" }, "settings": { "additionalProperties": true, "title": "Settings", "type": "object" }, "disable_resolve_message": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Disable Resolve Message" }, "provenance": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Provenance" } }, "required": [ "success" ], "title": "CreateContactPointOutput", "type": "object" } ``` ### Parameters The text content of the annotation Comma-separated list of tags UID of the dashboard to annotate; omit for a global annotation ID of the panel to attach the annotation to Start time in epoch milliseconds (defaults to now) End time in epoch milliseconds for range annotations Organization ID for multi-org Grafana instances (e.g. 1, 2). Sent as the X-Grafana-Org-Id header when set. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Id" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" } }, "required": [ "success" ], "title": "CreateAnnotationOutput", "type": "object" } ``` ### Parameters Start time in epoch milliseconds End time in epoch milliseconds Dashboard UID to query annotations from Legacy numeric dashboard ID filter Filter by panel ID Filter by alert ID Filter by ID of the creating user Comma-separated list of tags to filter by Filter by type (alert or annotation) Maximum number of annotations to return Organization ID for multi-org Grafana instances (e.g. 1, 2). Sent as the X-Grafana-Org-Id header when set. ### Response ```json { "$defs": { "Annotation": { "additionalProperties": false, "description": "A single annotation row from ``/api/annotations``.", "properties": { "id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Id" }, "alert_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Alert Id" }, "dashboard_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Dashboard Id" }, "dashboard_uid": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Dashboard Uid" }, "panel_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Panel Id" }, "user_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "User Id" }, "user_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "User Name" }, "new_state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "New State" }, "prev_state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Prev State" }, "time": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Time" }, "time_end": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Time End" }, "text": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Text" }, "metric": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Metric" }, "tags": { "items": { "type": "string" }, "title": "Tags", "type": "array" }, "data": { "additionalProperties": true, "title": "Data", "type": "object" } }, "title": "Annotation", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "annotations": { "items": { "$ref": "#/$defs/Annotation" }, "title": "Annotations", "type": "array" } }, "required": [ "success" ], "title": "ListAnnotationsOutput", "type": "object" } ``` ### Parameters The ID of the annotation to update New text content for the annotation Comma-separated list of new tags New start time in epoch milliseconds New end time in epoch milliseconds Organization ID for multi-org Grafana instances (e.g. 1, 2). Sent as the X-Grafana-Org-Id header when set. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Id" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" } }, "required": [ "success" ], "title": "UpdateAnnotationOutput", "type": "object" } ``` ### Parameters The ID of the annotation to delete Organization ID for multi-org Grafana instances (e.g. 1, 2). Sent as the X-Grafana-Org-Id header when set. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" } }, "required": [ "success" ], "title": "DeleteAnnotationOutput", "type": "object" } ``` ### Parameters Organization ID for multi-org Grafana instances (e.g. 1, 2). Sent as the X-Grafana-Org-Id header when set. ### Response ```json { "$defs": { "DataSource": { "additionalProperties": false, "description": "A single data source from ``/api/datasources``.", "properties": { "id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Id" }, "uid": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Uid" }, "org_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Org Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Type" }, "type_logo_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Type Logo Url" }, "access": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Access" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" }, "user": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "User" }, "database": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Database" }, "basic_auth": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Basic Auth" }, "basic_auth_user": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Basic Auth User" }, "with_credentials": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "With Credentials" }, "is_default": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Default" }, "json_data": { "additionalProperties": true, "title": "Json Data", "type": "object" }, "secure_json_fields": { "additionalProperties": true, "title": "Secure Json Fields", "type": "object" }, "version": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Version" }, "read_only": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Read Only" } }, "title": "DataSource", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data_sources": { "items": { "$ref": "#/$defs/DataSource" }, "title": "Data Sources", "type": "array" } }, "required": [ "success" ], "title": "ListDataSourcesOutput", "type": "object" } ``` ### Parameters The ID or UID of the data source to retrieve Organization ID for multi-org Grafana instances (e.g. 1, 2). Sent as the X-Grafana-Org-Id header when set. ### Response ```json { "$defs": { "DataSource": { "additionalProperties": false, "description": "A single data source from ``/api/datasources``.", "properties": { "id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Id" }, "uid": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Uid" }, "org_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Org Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Type" }, "type_logo_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Type Logo Url" }, "access": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Access" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" }, "user": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "User" }, "database": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Database" }, "basic_auth": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Basic Auth" }, "basic_auth_user": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Basic Auth User" }, "with_credentials": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "With Credentials" }, "is_default": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Default" }, "json_data": { "additionalProperties": true, "title": "Json Data", "type": "object" }, "secure_json_fields": { "additionalProperties": true, "title": "Secure Json Fields", "type": "object" }, "version": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Version" }, "read_only": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Read Only" } }, "title": "DataSource", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data_source": { "anyOf": [ { "$ref": "#/$defs/DataSource" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "GetDataSourceOutput", "type": "object" } ``` ### Parameters The UID of the data source to health-check Organization ID for multi-org Grafana instances (e.g. 1, 2). Sent as the X-Grafana-Org-Id header when set. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" } }, "required": [ "success" ], "title": "CheckDataSourceHealthOutput", "type": "object" } ``` ### Parameters Maximum number of folders to return Page number for pagination List children of this folder UID Organization ID for multi-org Grafana instances (e.g. 1, 2). Sent as the X-Grafana-Org-Id header when set. ### Response ```json { "$defs": { "Folder": { "additionalProperties": false, "description": "A single folder from ``/api/folders``.", "properties": { "id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Id" }, "uid": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Uid" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" }, "parent_uid": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Parent Uid" }, "parents": { "items": { "$ref": "#/$defs/FolderParent" }, "title": "Parents", "type": "array" }, "has_acl": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Has Acl" }, "can_save": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Can Save" }, "can_edit": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Can Edit" }, "can_admin": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Can Admin" }, "created_by": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created By" }, "created": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created" }, "updated_by": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated By" }, "updated": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated" }, "version": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Version" } }, "title": "Folder", "type": "object" }, "FolderParent": { "additionalProperties": false, "description": "An ancestor folder entry (nested folders only).", "properties": { "uid": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Uid" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" } }, "title": "FolderParent", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "folders": { "items": { "$ref": "#/$defs/Folder" }, "title": "Folders", "type": "array" } }, "required": [ "success" ], "title": "ListFoldersOutput", "type": "object" } ``` ### Parameters The title of the new folder Optional UID for the folder Parent folder UID for nested folders Organization ID for multi-org Grafana instances (e.g. 1, 2). Sent as the X-Grafana-Org-Id header when set. ### Response ```json { "$defs": { "Folder": { "additionalProperties": false, "description": "A single folder from ``/api/folders``.", "properties": { "id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Id" }, "uid": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Uid" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" }, "parent_uid": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Parent Uid" }, "parents": { "items": { "$ref": "#/$defs/FolderParent" }, "title": "Parents", "type": "array" }, "has_acl": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Has Acl" }, "can_save": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Can Save" }, "can_edit": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Can Edit" }, "can_admin": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Can Admin" }, "created_by": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created By" }, "created": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created" }, "updated_by": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated By" }, "updated": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated" }, "version": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Version" } }, "title": "Folder", "type": "object" }, "FolderParent": { "additionalProperties": false, "description": "An ancestor folder entry (nested folders only).", "properties": { "uid": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Uid" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" } }, "title": "FolderParent", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "folder": { "anyOf": [ { "$ref": "#/$defs/Folder" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "CreateFolderOutput", "type": "object" } ``` ### Parameters The UID of the folder to retrieve Organization ID for multi-org Grafana instances (e.g. 1, 2). Sent as the X-Grafana-Org-Id header when set. ### Response ```json { "$defs": { "Folder": { "additionalProperties": false, "description": "A single folder from ``/api/folders``.", "properties": { "id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Id" }, "uid": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Uid" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" }, "parent_uid": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Parent Uid" }, "parents": { "items": { "$ref": "#/$defs/FolderParent" }, "title": "Parents", "type": "array" }, "has_acl": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Has Acl" }, "can_save": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Can Save" }, "can_edit": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Can Edit" }, "can_admin": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Can Admin" }, "created_by": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created By" }, "created": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created" }, "updated_by": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated By" }, "updated": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated" }, "version": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Version" } }, "title": "Folder", "type": "object" }, "FolderParent": { "additionalProperties": false, "description": "An ancestor folder entry (nested folders only).", "properties": { "uid": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Uid" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" } }, "title": "FolderParent", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "folder": { "anyOf": [ { "$ref": "#/$defs/Folder" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "GetFolderOutput", "type": "object" } ``` ### Parameters The UID of the folder to update New title for the folder Organization ID for multi-org Grafana instances (e.g. 1, 2). Sent as the X-Grafana-Org-Id header when set. ### Response ```json { "$defs": { "Folder": { "additionalProperties": false, "description": "A single folder from ``/api/folders``.", "properties": { "id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Id" }, "uid": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Uid" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" }, "parent_uid": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Parent Uid" }, "parents": { "items": { "$ref": "#/$defs/FolderParent" }, "title": "Parents", "type": "array" }, "has_acl": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Has Acl" }, "can_save": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Can Save" }, "can_edit": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Can Edit" }, "can_admin": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Can Admin" }, "created_by": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created By" }, "created": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created" }, "updated_by": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated By" }, "updated": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated" }, "version": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Version" } }, "title": "Folder", "type": "object" }, "FolderParent": { "additionalProperties": false, "description": "An ancestor folder entry (nested folders only).", "properties": { "uid": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Uid" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" } }, "title": "FolderParent", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "folder": { "anyOf": [ { "$ref": "#/$defs/Folder" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "UpdateFolderOutput", "type": "object" } ``` ### Parameters The UID of the folder to delete Delete any alert rules stored in the folder along with it Organization ID for multi-org Grafana instances (e.g. 1, 2). Sent as the X-Grafana-Org-Id header when set. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "uid": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Uid" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" } }, "required": [ "success" ], "title": "DeleteFolderOutput", "type": "object" } ``` ### Parameters Organization ID for multi-org Grafana instances (e.g. 1, 2). Sent as the X-Grafana-Org-Id header when set. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "commit": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Commit" }, "database": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Database" }, "version": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Version" } }, "required": [ "success" ], "title": "GetHealthOutput", "type": "object" } ``` ## Limits & Quotas - **Rate limits** are configured per Grafana instance; self-hosted instances are unbounded by default, while Grafana Cloud applies tier limits. - **Update semantics**: `update_dashboard`, `update_alert_rule`, and `update_folder` first `GET` the current resource, merge your changes, and write the full body back (Grafana's update endpoints require the complete resource and a version for conflict detection). - **Error model**: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. Plan for retries on the agent side based on the error string. ## Related integrations ## Links - [Grafana](https://grafana.com) --- # Grain Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/grain Access Grain meeting recordings, transcripts, highlights, and AI-generated summaries. List and retrieve recordings with flexible filters, fetch full transcripts, browse teams, meeting types, and views, and manage webhook subscriptions for recording events. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/grain/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Grain logo ## Overview Add **Grain** to any ModuleX agent or workflow. Access Grain meeting recordings, transcripts, highlights, and AI-generated summaries through the Grain public REST API (`api.grain.com/_/public-api`). List and retrieve recordings, fetch full transcripts, browse teams, meeting types, and views, and manage webhook subscriptions for recording events. **Categories**: Productivity & Collaboration · Meeting · Note Taking · **Auth**: API Key · **Actions**: 9 ## Authentication ### API Key Authentication Authenticate using your Grain personal access token Log in to your Grain account at https://grain.com Open Settings and navigate to the API / Integrations section Create a Personal Access Token (developer access may require approval) Paste the access token below #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Grain API Key | Your Grain personal access token | Yes | `-` | ## Available Actions ### Parameters Pagination cursor for next page (returned from previous response) Only recordings before this ISO8601 timestamp (e.g., "2024-01-15T00:00:00Z") Only recordings after this ISO8601 timestamp (e.g., "2024-01-01T00:00:00Z") Filter: "internal" or "external" Search term to filter by recording title (e.g., "weekly standup") Filter by team UUID Filter by meeting type UUID Include highlights/clips in response (Default: `false`) Include participant list in response (Default: `false`) Include AI-generated summary (Default: `false`) ### Response ```json { "$defs": { "Recording": { "additionalProperties": false, "description": "A meeting recording object.\n\nExtra include-blocks (highlights, participants, ai_summary,\ncalendar_event, hubspot, etc.) are returned verbatim as\nJSON-serializable values.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "start_datetime": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Start Datetime" }, "end_datetime": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "End Datetime" }, "duration_ms": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Duration Ms" }, "media_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Media Type" }, "source": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Source" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" }, "thumbnail_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Thumbnail Url" }, "tags": { "items": { "type": "string" }, "title": "Tags", "type": "array" }, "teams": { "items": { "additionalProperties": true, "type": "object" }, "title": "Teams", "type": "array" }, "meeting_type": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Meeting Type" }, "highlights": { "anyOf": [ { "items": { "additionalProperties": true, "type": "object" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Highlights" }, "participants": { "anyOf": [ { "items": { "additionalProperties": true, "type": "object" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Participants" }, "ai_summary": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Ai Summary" }, "calendar_event": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Calendar Event" }, "hubspot": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Hubspot" }, "private_notes": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Private Notes" }, "ai_template_sections": { "anyOf": [ { "items": { "additionalProperties": true, "type": "object" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Ai Template Sections" } }, "title": "Recording", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "recordings": { "items": { "$ref": "#/$defs/Recording" }, "title": "Recordings", "type": "array" }, "cursor": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Cursor" } }, "required": [ "success" ], "title": "ListRecordingsOutput", "type": "object" } ``` ### Parameters The recording UUID Include highlights/clips (Default: `false`) Include participant list (Default: `false`) Include AI summary (Default: `false`) Include calendar event data (Default: `false`) Include HubSpot associations (Default: `false`) ### Response ```json { "$defs": { "Recording": { "additionalProperties": false, "description": "A meeting recording object.\n\nExtra include-blocks (highlights, participants, ai_summary,\ncalendar_event, hubspot, etc.) are returned verbatim as\nJSON-serializable values.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "start_datetime": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Start Datetime" }, "end_datetime": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "End Datetime" }, "duration_ms": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Duration Ms" }, "media_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Media Type" }, "source": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Source" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" }, "thumbnail_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Thumbnail Url" }, "tags": { "items": { "type": "string" }, "title": "Tags", "type": "array" }, "teams": { "items": { "additionalProperties": true, "type": "object" }, "title": "Teams", "type": "array" }, "meeting_type": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Meeting Type" }, "highlights": { "anyOf": [ { "items": { "additionalProperties": true, "type": "object" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Highlights" }, "participants": { "anyOf": [ { "items": { "additionalProperties": true, "type": "object" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Participants" }, "ai_summary": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Ai Summary" }, "calendar_event": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Calendar Event" }, "hubspot": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Hubspot" }, "private_notes": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Private Notes" }, "ai_template_sections": { "anyOf": [ { "items": { "additionalProperties": true, "type": "object" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Ai Template Sections" } }, "title": "Recording", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "recording": { "anyOf": [ { "$ref": "#/$defs/Recording" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "GetRecordingOutput", "type": "object" } ``` ### Parameters The recording UUID ### Response ```json { "$defs": { "TranscriptSection": { "additionalProperties": false, "description": "A single transcript section (speaker turn).", "properties": { "participant_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Participant Id" }, "speaker": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Speaker" }, "start": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Start" }, "end": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "End" }, "text": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Text" } }, "title": "TranscriptSection", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "transcript": { "items": { "$ref": "#/$defs/TranscriptSection" }, "title": "Transcript", "type": "array" } }, "required": [ "success" ], "title": "GetTranscriptOutput", "type": "object" } ``` ### Parameters Optional view type filter: recordings, highlights, or stories ### Response ```json { "$defs": { "View": { "additionalProperties": false, "description": "A Grain view object.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Type" } }, "title": "View", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "views": { "items": { "$ref": "#/$defs/View" }, "title": "Views", "type": "array" } }, "required": [ "success" ], "title": "ListViewsOutput", "type": "object" } ``` ### Response ```json { "$defs": { "Team": { "additionalProperties": false, "description": "A team object.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" } }, "title": "Team", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "teams": { "items": { "$ref": "#/$defs/Team" }, "title": "Teams", "type": "array" } }, "required": [ "success" ], "title": "ListTeamsOutput", "type": "object" } ``` ### Response ```json { "$defs": { "MeetingType": { "additionalProperties": false, "description": "A meeting type object.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "scope": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Scope" } }, "title": "MeetingType", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "meeting_types": { "items": { "$ref": "#/$defs/MeetingType" }, "title": "Meeting Types", "type": "array" } }, "required": [ "success" ], "title": "ListMeetingTypesOutput", "type": "object" } ``` ### Parameters Webhook endpoint URL (e.g., "https://example.com/webhooks/grain") Grain view ID for the webhook subscription Optional list of actions to subscribe to: added, updated, removed ### Response ```json { "$defs": { "Hook": { "additionalProperties": false, "description": "A webhook subscription object.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "enabled": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Enabled" }, "version": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Version" }, "hook_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Hook Url" }, "view_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "View Id" }, "actions": { "items": { "type": "string" }, "title": "Actions", "type": "array" }, "inserted_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Inserted At" } }, "title": "Hook", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "hook": { "anyOf": [ { "$ref": "#/$defs/Hook" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "CreateHookOutput", "type": "object" } ``` ### Response ```json { "$defs": { "Hook": { "additionalProperties": false, "description": "A webhook subscription object.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "enabled": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Enabled" }, "version": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Version" }, "hook_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Hook Url" }, "view_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "View Id" }, "actions": { "items": { "type": "string" }, "title": "Actions", "type": "array" }, "inserted_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Inserted At" } }, "title": "Hook", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "hooks": { "items": { "$ref": "#/$defs/Hook" }, "title": "Hooks", "type": "array" } }, "required": [ "success" ], "title": "ListHooksOutput", "type": "object" } ``` ### Parameters The hook UUID to delete ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" } }, "required": [ "success" ], "title": "DeleteHookOutput", "type": "object" } ``` ## Limits & Quotas - The Grain public API requires the `Public-Api-Version` header (currently `2025-10-31`) on the recording, transcript, team, and meeting-type endpoints. - Developer access to the public API is in beta and may be limited to approved partners. - **Error model**: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. Plan for retries on the agent side based on the error string. ## Related integrations ## Links - [Grain](https://grain.com) --- # Granola Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/granola Retrieve meeting notes, summaries, attendees, calendar details, and transcripts from Granola. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/granola/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Granola logo ## Overview Add **Granola** to any ModuleX agent or workflow. Retrieve meeting notes, summaries, attendees, calendar event details, and transcripts from Granola through its REST API (`public-api.granola.ai`). **Categories**: Productivity & Collaboration · Meeting · Note Taking · **Auth**: API Key · **Actions**: 3 ## Authentication ### API Key Authentication Authenticate using your Granola API key Open Granola and go to Settings (a Business plan is required) Navigate to the API / Developer section Create a new API key and copy it Paste the API key below #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Granola API Key | Your Granola API key | Yes | `grn_xxxxxxxxxxxxxxxxxxxxxxxx` | ## Available Actions ### Parameters Return notes created before this date (ISO 8601) Return notes created after this date (ISO 8601) Return notes updated after this date (ISO 8601) Return notes in this folder and its child folders (e.g., fol_4y6LduVdwSKC27) Pagination cursor from a previous response Number of notes per page (1-30, default 10) ### Response ```json { "$defs": { "NoteSummary": { "additionalProperties": false, "description": "A single note row in ``list_notes``.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "owner_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Owner Name" }, "owner_email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Owner Email" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "updated_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated At" } }, "title": "NoteSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "notes": { "items": { "$ref": "#/$defs/NoteSummary" }, "title": "Notes", "type": "array" }, "has_more": { "default": false, "title": "Has More", "type": "boolean" }, "cursor": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Cursor" } }, "required": [ "success" ], "title": "ListNotesOutput", "type": "object" } ``` ### Parameters The note ID (e.g., not_1d3tmYTlCICgjy) Whether to include the meeting transcript (Default: `false`) ### Response ```json { "$defs": { "Attendee": { "additionalProperties": false, "description": "A meeting attendee in ``get_note``.", "properties": { "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email" } }, "title": "Attendee", "type": "object" }, "FolderRef": { "additionalProperties": false, "description": "A folder reference.\n\nUsed both for a note's folder memberships in ``get_note`` (id, name)\nand for the workspace folder listing in ``list_folders``\n(id, name, parent_folder_id).", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "parent_folder_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Parent Folder Id" } }, "title": "FolderRef", "type": "object" }, "TranscriptSegment": { "additionalProperties": false, "description": "A single transcript entry in ``get_note``.", "properties": { "speaker": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Speaker" }, "speaker_label": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Speaker Label" }, "text": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Text" }, "start_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Start Time" }, "end_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "End Time" } }, "title": "TranscriptSegment", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "owner_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Owner Name" }, "owner_email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Owner Email" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "updated_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated At" }, "web_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Web Url" }, "summary_text": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Summary Text" }, "summary_markdown": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Summary Markdown" }, "attendees": { "items": { "$ref": "#/$defs/Attendee" }, "title": "Attendees", "type": "array" }, "folders": { "items": { "$ref": "#/$defs/FolderRef" }, "title": "Folders", "type": "array" }, "calendar_event_title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Calendar Event Title" }, "calendar_organiser": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Calendar Organiser" }, "calendar_event_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Calendar Event Id" }, "scheduled_start_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Scheduled Start Time" }, "scheduled_end_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Scheduled End Time" }, "invitees": { "items": { "type": "string" }, "title": "Invitees", "type": "array" }, "transcript": { "anyOf": [ { "items": { "$ref": "#/$defs/TranscriptSegment" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Transcript" } }, "required": [ "success" ], "title": "GetNoteOutput", "type": "object" } ``` ### Parameters Pagination cursor from a previous response Number of folders per page (1-30, default 10) ### Response ```json { "$defs": { "FolderRef": { "additionalProperties": false, "description": "A folder reference.\n\nUsed both for a note's folder memberships in ``get_note`` (id, name)\nand for the workspace folder listing in ``list_folders``\n(id, name, parent_folder_id).", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "parent_folder_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Parent Folder Id" } }, "title": "FolderRef", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "folders": { "items": { "$ref": "#/$defs/FolderRef" }, "title": "Folders", "type": "array" }, "has_more": { "default": false, "title": "Has More", "type": "boolean" }, "cursor": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Cursor" } }, "required": [ "success" ], "title": "ListFoldersOutput", "type": "object" } ``` ## Limits & Quotas - The API only returns notes that have a generated AI summary and transcript; notes still processing or never summarized are omitted from list responses and return `404` on direct access. - `page_size` accepts `1-30` (default `10`) for `list_notes` and `list_folders`. - Rate limits are applied per user or workspace depending on the key's access scope; exceeding them returns `429 Too Many Requests`. - **Error model**: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. Plan for retries on the agent side based on the error string. ## Related integrations ## Links - [Granola](https://granola.ai) --- # Greptile Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/greptile Query and search codebases using natural language with Greptile. Get AI-generated answers about your code, find relevant files, and understand complex codebases. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/greptile/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Greptile logo ## Overview Add **Greptile** to any ModuleX agent or workflow. AI-powered codebase search and Q&A against the Greptile v2 REST API (`api.greptile.com`). Query and search codebases using natural language, get AI-generated answers about your code with cited file references, index repositories, and check indexing status. **Categories**: Developer Tools & Infrastructure · Version Control · Knowledge Base · **Auth**: API Key · **Actions**: 4 ## Authentication ### API Key Authentication Authenticate using your Greptile API key plus a GitHub Personal Access Token so Greptile can access your repositories Go to https://app.greptile.com and sign up or log in Open Settings and create a new API key Paste the Greptile API key below Create a GitHub Personal Access Token with 'repo' read access at https://github.com/settings/tokens and paste it as the GitHub token #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Greptile API Key | Your Greptile API key from app.greptile.com | Yes | `-` | | GitHub Token | A GitHub Personal Access Token with 'repo' read access, used by Greptile to clone and index your repositories | Yes | `-` | ## Available Actions ### Parameters Natural language question about the codebase Comma-separated list of repositories. Format: 'github:branch:owner/repo' or just 'owner/repo' (defaults to github:main). Example: 'facebook/react' or 'github:main:facebook/react,github:main:facebook/relay' Optional session ID for conversation continuity across multiple queries Enable genius mode for more thorough analysis (slower but more accurate) ### Response ```json { "$defs": { "QuerySource": { "additionalProperties": false, "description": "A single code reference returned by ``query`` and ``search``.", "properties": { "repository": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Repository" }, "remote": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Remote" }, "branch": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Branch" }, "filepath": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Filepath" }, "linestart": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Linestart" }, "lineend": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Lineend" }, "summary": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Summary" }, "distance": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Distance" } }, "title": "QuerySource", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" }, "sources": { "items": { "$ref": "#/$defs/QuerySource" }, "title": "Sources", "type": "array" } }, "required": [ "success" ], "title": "QueryOutput", "type": "object" } ``` ### Parameters Natural language search query to find relevant code Comma-separated list of repositories. Format: 'github:branch:owner/repo' or just 'owner/repo' (defaults to github:main). Example: 'facebook/react' or 'github:main:facebook/react,github:main:facebook/relay' Optional session ID for conversation continuity across multiple searches Enable genius mode for more thorough search (slower but more accurate) ### Response ```json { "$defs": { "QuerySource": { "additionalProperties": false, "description": "A single code reference returned by ``query`` and ``search``.", "properties": { "repository": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Repository" }, "remote": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Remote" }, "branch": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Branch" }, "filepath": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Filepath" }, "linestart": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Linestart" }, "lineend": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Lineend" }, "summary": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Summary" }, "distance": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Distance" } }, "title": "QuerySource", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "sources": { "items": { "$ref": "#/$defs/QuerySource" }, "title": "Sources", "type": "array" } }, "required": [ "success" ], "title": "SearchOutput", "type": "object" } ``` ### Parameters Git remote type: 'github' or 'gitlab' (Default: `github`) Repository in owner/repo format. Example: 'facebook/react' Branch to index (e.g. 'main' or 'master') Force re-indexing even if already indexed Send email notification when indexing completes ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "repository_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Repository Id" }, "status_endpoint": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status Endpoint" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" } }, "required": [ "success" ], "title": "IndexRepositoryOutput", "type": "object" } ``` ### Parameters Git remote type: 'github' or 'gitlab' (Default: `github`) Repository in owner/repo format. Example: 'facebook/react' Branch name (e.g. 'main' or 'master') ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "repository": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Repository" }, "remote": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Remote" }, "branch": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Branch" }, "private": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Private" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "files_processed": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Files Processed" }, "num_files": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Num Files" }, "sample_questions": { "items": { "type": "string" }, "title": "Sample Questions", "type": "array" }, "sha": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Sha" }, "raw": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Raw" } }, "required": [ "success" ], "title": "StatusOutput", "type": "object" } ``` ## Limits & Quotas - Indexing is asynchronous: small repositories take 3–5 minutes, larger ones can take over an hour. Use `index_repo` to start indexing, then poll `status` until it reports `completed`. - **Error model**: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. Plan retries on the agent side based on the error string. ## Related integrations ## Links - [Greptile](https://www.greptile.com) --- # Hacker News Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/hackernews Access Hacker News content including top stories, new posts, Ask HN, Show HN, job postings, and search functionality for stories and comments. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/hackernews/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Hacker News logo ## Overview Add **Hacker News** to any ModuleX agent or workflow. Read-only access to the official Hacker News JSON API (`hacker-news.firebaseio.com/v0`) plus keyword search backed by `hnrss.org` RSS feeds. **Categories**: Social Media · News · Social · Technology · Community · **Auth**: ModuleX Managed Key · **Actions**: 10 ## Authentication ### ModuleX Managed Key Use ModuleX's managed access (Hacker News API is public, no authentication required) ## Available Actions ### Parameters Keyword to search for in story titles. Leave empty to get all new stories. (Default: ``) Maximum number of stories to return (1-50) (Default: `10`) Minimum points threshold for stories Minimum comments threshold for stories ### Response ```json { "$defs": { "HNRSSItem": { "additionalProperties": false, "properties": { "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "link": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Link" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "pub_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Pub Date" }, "guid": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Guid" }, "author": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Author" }, "comments_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Comments Url" } }, "title": "HNRSSItem", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "stories": { "items": { "$ref": "#/$defs/HNRSSItem" }, "title": "Stories", "type": "array" }, "comments": { "items": { "$ref": "#/$defs/HNRSSItem" }, "title": "Comments", "type": "array" }, "count": { "default": 0, "title": "Count", "type": "integer" }, "keyword": { "default": "", "title": "Keyword", "type": "string" }, "source": { "default": "hnrss.org", "title": "Source", "type": "string" } }, "required": [ "success" ], "title": "SearchOutput", "type": "object" } ``` ### Parameters Keyword to search for in comments. Leave empty to get all new comments. (Default: ``) Maximum number of comments to return (1-50) (Default: `10`) ### Response ```json { "$defs": { "HNRSSItem": { "additionalProperties": false, "properties": { "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "link": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Link" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "pub_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Pub Date" }, "guid": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Guid" }, "author": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Author" }, "comments_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Comments Url" } }, "title": "HNRSSItem", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "stories": { "items": { "$ref": "#/$defs/HNRSSItem" }, "title": "Stories", "type": "array" }, "comments": { "items": { "$ref": "#/$defs/HNRSSItem" }, "title": "Comments", "type": "array" }, "count": { "default": 0, "title": "Count", "type": "integer" }, "keyword": { "default": "", "title": "Keyword", "type": "string" }, "source": { "default": "hnrss.org", "title": "Source", "type": "string" } }, "required": [ "success" ], "title": "SearchOutput", "type": "object" } ``` ### Parameters Number of stories to return (1-100) (Default: `10`) Whether to fetch full story details or just IDs (Default: `true`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "stories": { "items": { "additionalProperties": true, "type": "object" }, "title": "Stories", "type": "array" }, "story_ids": { "items": { "type": "integer" }, "title": "Story Ids", "type": "array" }, "count": { "default": 0, "title": "Count", "type": "integer" }, "type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Type" } }, "required": [ "success" ], "title": "GetStoriesOutput", "type": "object" } ``` ### Parameters Number of stories to return (1-100) (Default: `10`) Whether to fetch full story details or just IDs (Default: `true`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "stories": { "items": { "additionalProperties": true, "type": "object" }, "title": "Stories", "type": "array" }, "story_ids": { "items": { "type": "integer" }, "title": "Story Ids", "type": "array" }, "count": { "default": 0, "title": "Count", "type": "integer" }, "type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Type" } }, "required": [ "success" ], "title": "GetStoriesOutput", "type": "object" } ``` ### Parameters Number of stories to return (1-100) (Default: `10`) Whether to fetch full story details or just IDs (Default: `true`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "stories": { "items": { "additionalProperties": true, "type": "object" }, "title": "Stories", "type": "array" }, "story_ids": { "items": { "type": "integer" }, "title": "Story Ids", "type": "array" }, "count": { "default": 0, "title": "Count", "type": "integer" }, "type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Type" } }, "required": [ "success" ], "title": "GetStoriesOutput", "type": "object" } ``` ### Parameters Number of stories to return (1-100) (Default: `10`) Whether to fetch full story details or just IDs (Default: `true`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "stories": { "items": { "additionalProperties": true, "type": "object" }, "title": "Stories", "type": "array" }, "story_ids": { "items": { "type": "integer" }, "title": "Story Ids", "type": "array" }, "count": { "default": 0, "title": "Count", "type": "integer" }, "type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Type" } }, "required": [ "success" ], "title": "GetStoriesOutput", "type": "object" } ``` ### Parameters Number of stories to return (1-100) (Default: `10`) Whether to fetch full story details or just IDs (Default: `true`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "stories": { "items": { "additionalProperties": true, "type": "object" }, "title": "Stories", "type": "array" }, "story_ids": { "items": { "type": "integer" }, "title": "Story Ids", "type": "array" }, "count": { "default": 0, "title": "Count", "type": "integer" }, "type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Type" } }, "required": [ "success" ], "title": "GetStoriesOutput", "type": "object" } ``` ### Parameters Number of stories to return (1-100) (Default: `10`) Whether to fetch full story details or just IDs (Default: `true`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "stories": { "items": { "additionalProperties": true, "type": "object" }, "title": "Stories", "type": "array" }, "story_ids": { "items": { "type": "integer" }, "title": "Story Ids", "type": "array" }, "count": { "default": 0, "title": "Count", "type": "integer" }, "type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Type" } }, "required": [ "success" ], "title": "GetStoriesOutput", "type": "object" } ``` ### Parameters The ID of the item to fetch ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "item": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Item" }, "item_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Item Id" } }, "required": [ "success" ], "title": "GetItemOutput", "type": "object" } ``` ### Parameters The HN username to look up ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "user": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "User" }, "username": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Username" } }, "required": [ "success" ], "title": "GetUserOutput", "type": "object" } ``` ## Limits & Quotas - `fetch_details=False` returns only the ID list (one HTTP call instead of N+1). `fetch_details=True` issues one detail call per item, capped at `limit`. - `max_results` is capped at 50 on the RSS endpoints (hnrss.org cap). - Failures and parse errors surface as `success=False` with `error`. ## Related integrations ## Links - [Hacker News](https://news.ycombinator.com) --- # Heroku Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/heroku Cloud platform for building, deploying, and managing applications {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/heroku/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Heroku logo ## Overview Add **Heroku** to any ModuleX agent or workflow. Cloud platform for building, deploying, and managing applications via the Heroku Platform API (`api.heroku.com`). **Categories**: Developer Tools & Infrastructure · Cloud Infrastructure · Paas · **Auth**: OAuth2 · **Actions**: 1 ## Authentication ### OAuth2 Authentication Connect using Heroku OAuth (recommended) #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Client ID | Heroku OAuth App Client ID | Yes | `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` | | Client Secret | Heroku OAuth App Client Secret | Yes | `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` | #### OAuth Configuration - **Authorization URL**: `https://id.heroku.com/oauth/authorize` - **Token URL**: `https://id.heroku.com/oauth/token` - **Scopes**: `global` ## Available Actions ### Response ```json { "$defs": { "AppSummary": { "additionalProperties": false, "description": "A Heroku app summary.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "web_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Web Url" }, "region": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Region" }, "stack": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Stack" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "updated_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated At" } }, "title": "AppSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "apps": { "items": { "$ref": "#/$defs/AppSummary" }, "title": "Apps", "type": "array" } }, "required": [ "success" ], "title": "ListAppsOutput", "type": "object" } ``` ## Limits & Quotas - Heroku Platform API rate limit: 4,500 requests per hour per OAuth token. - Rate limit headers: `RateLimit-Remaining` and `RateLimit-Reset` are returned on every response. - Error model: non-2xx responses raise (Pattern A). The caller receives an `httpx.HTTPStatusError` surfaced by the modulex runtime. ## Related integrations ## Links - [Heroku](https://www.heroku.com) --- # HeyGen Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/heygen AI video generation platform for creating talking avatar videos {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/heygen/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} HeyGen logo ## Overview Add **HeyGen** to any ModuleX agent or workflow. AI video generation platform for creating talking avatar videos via the HeyGen REST API (`api.heygen.com`). **Categories**: AI & Machine Learning · AI · Video · Content Creation · **Auth**: API Key · **Actions**: 5 ## Authentication ### API Key Authentication Authenticate using your HeyGen API key Go to https://app.heygen.com and sign in Navigate to Settings > API Copy your API key Paste the API key below #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | HeyGen API Key | Your HeyGen API key from app.heygen.com/settings | Yes | `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | ## Available Actions ### Parameters Identifier of the talking photo to use The text that the character will speak Identifier of the voice to use Title of the video Set to true to use test mode (no credits charged, watermark added) Set to true to create video with captions Talking photo scale, value between 0 and 2.0 (default 1.0) Talking photo crop style: square, circle Talking photo talking style: stable, expressive Talking photo expression style: default, happy Whether to enhance the photo image Whether to apply matting to the photo ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "video_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Video Id" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" } }, "required": [ "success" ], "title": "CreateTalkingPhotoOutput", "type": "object" } ``` ### Parameters Identifier of the template to use Title of the video Set to true to use test mode (no credits charged, watermark added) Set to true to create video with captions Template variable overrides as a JSON object where keys are variable names and values are objects with variable properties ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "video_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Video Id" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" } }, "required": [ "success" ], "title": "CreateVideoFromTemplateOutput", "type": "object" } ``` ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "events": { "items": { "type": "string" }, "title": "Events", "type": "array" } }, "required": [ "success" ], "title": "ListCustomEventsOptionsOutput", "type": "object" } ``` ### Response ```json { "$defs": { "VoiceInfo": { "additionalProperties": false, "description": "A voice entry returned by the list voices endpoint.", "properties": { "voice_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Voice Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" } }, "title": "VoiceInfo", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "voices": { "items": { "$ref": "#/$defs/VoiceInfo" }, "title": "Voices", "type": "array" } }, "required": [ "success" ], "title": "ListVoiceIdOptionsOutput", "type": "object" } ``` ### Parameters Identifier of the HeyGen video to retrieve ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "video_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Video Id" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "video_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Video Url" }, "thumbnail_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Thumbnail Url" }, "duration": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Duration" }, "caption_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Caption Url" } }, "required": [ "success" ], "title": "RetrieveVideoLinkOutput", "type": "object" } ``` ## Limits & Quotas - Rate limits depend on your HeyGen plan tier (Starter, Business, Enterprise). - Video generation consumes credits; use `test=true` to avoid credit charges during development. - No documented per-minute rate limit; excessive requests may trigger throttling. - Error model: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. ## Related integrations ## Links - [HeyGen](https://www.heygen.com) --- # Hootsuite Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/hootsuite Social media management platform for scheduling posts and managing profiles {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/hootsuite/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Hootsuite logo ## Overview Add **Hootsuite** to any ModuleX agent or workflow. Social media management platform for scheduling posts and managing profiles via the Hootsuite REST API (`platform.hootsuite.com/v1`). **Categories**: Social Media · Marketing · **Auth**: OAuth2 · **Actions**: 4 ## Authentication ### OAuth2 Authentication Connect using Hootsuite OAuth (recommended) #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Client ID | Hootsuite OAuth App Client ID | Yes | `-` | | Client Secret | Hootsuite OAuth App Client Secret | Yes | `-` | #### OAuth Configuration - **Authorization URL**: `https://platform.hootsuite.com/oauth2/auth` - **Token URL**: `https://platform.hootsuite.com/oauth2/token` - **Scopes**: `offline` ## Available Actions ### Parameters The size of the file in bytes The MIME type of the file (e.g. image/png, image/jpeg, video/mp4) A publicly-accessible URL to the file to upload ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "file_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "File Id" } }, "required": [ "success" ], "title": "CreateMediaUploadJobOutput", "type": "object" } ``` ### Parameters The ID of the media file to check status for ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "State" }, "download_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Download Url" }, "thumbnail_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Thumbnail Url" } }, "required": [ "success" ], "title": "GetMediaUploadStatusOutput", "type": "object" } ``` ### Response ```json { "$defs": { "SocialProfile": { "additionalProperties": false, "description": "A Hootsuite social profile.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Type" }, "social_network_username": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Social Network Username" }, "social_network_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Social Network Id" } }, "title": "SocialProfile", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "profiles": { "items": { "$ref": "#/$defs/SocialProfile" }, "title": "Profiles", "type": "array" } }, "required": [ "success" ], "title": "ListSocialProfilesOutput", "type": "object" } ``` ### Parameters The message text to publish Array of social profile ID strings to post to (use list_social_profiles to discover IDs) ISO-8601 UTC time to send the message (must end with 'Z'). If omitted, sends immediately. Array of ow.ly media URLs to attach to the message Array of media IDs from create_media_upload_job to attach Array of Hootsuite message tags to apply (case sensitive) Array of webhook URLs to call when message state changes Targeting object for Facebook Page and LinkedIn Company posts. See Hootsuite API docs for structure. Privacy settings object with optional facebook.visibility and linkedIn.visibility arrays Latitude in decimal degrees (-90 to 90) Longitude in decimal degrees (-180 to 180) Whether to send email notification when the message is published ### Response ```json { "$defs": { "ScheduledMessage": { "additionalProperties": false, "description": "A scheduled message returned by the Hootsuite API.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "State" }, "text": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Text" }, "scheduled_send_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Scheduled Send Time" } }, "title": "ScheduledMessage", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "messages": { "items": { "$ref": "#/$defs/ScheduledMessage" }, "title": "Messages", "type": "array" } }, "required": [ "success" ], "title": "ScheduleMessageOutput", "type": "object" } ``` ## Limits & Quotas - Hootsuite API rate limits vary by plan tier; consult your account's developer dashboard for current limits. - Free/Professional plans have lower API access thresholds than Enterprise plans. - Error model: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. ## Related integrations ## Links - [Hootsuite](https://hootsuite.com) --- # HubSpot Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/hubspot HubSpot CRM integration: contacts, companies, deals, tickets, and engagement (note/task/meeting) management. Uses the hubspot-api-client SDK. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/hubspot/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} HubSpot logo ## Overview Add **HubSpot** to any ModuleX agent or workflow. HubSpot CRM integration via the **synchronous** `hubspot-api-client` SDK. 26 actions across contacts, companies, deals, tickets, plus engagement (note/task/meeting) and property management. **Categories**: CRM · Sales · Marketing · **Auth**: OAuth2, Private App Access Token · **Actions**: 26 ## Authentication ### OAuth2 Authentication Connect using HubSpot OAuth (recommended for most use cases) #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Client ID | HubSpot OAuth App Client ID | Yes | `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` | | Client Secret | HubSpot OAuth App Client Secret | Yes | `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` | #### OAuth Configuration - **Authorization URL**: `https://app.hubspot.com/oauth/authorize` - **Token URL**: `https://api.hubapi.com/oauth/v1/token` - **Scopes**: `crm.objects.contacts.read`, `crm.objects.contacts.write`, `crm.objects.companies.read`, `crm.objects.companies.write`, `crm.objects.deals.read`, `crm.objects.deals.write`, `tickets`, `crm.schemas.contacts.read`, `crm.schemas.companies.read`, `crm.schemas.deals.read` ### Private App Access Token Use a HubSpot Private App access token for authentication #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Access Token | HubSpot Private App access token | Yes | `pat-eu1-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` | ## Available Actions ### Parameters Maximum results to return (Default: `10`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "contacts": { "items": { "additionalProperties": true, "type": "object" }, "title": "Contacts", "type": "array" }, "total": { "default": 0, "title": "Total", "type": "integer" } }, "required": [ "success" ], "title": "GetRecentContactsOutput", "type": "object" } ``` ### Parameters HubSpot contact ID Properties to retrieve (None = default property set) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "GetContactByIdOutput", "type": "object" } ``` ### Parameters Contact email address First name Last name Phone number Company Website URL Additional properties to set ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "CreateContactOutput", "type": "object" } ``` ### Parameters HubSpot contact ID Properties to update ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "UpdateContactOutput", "type": "object" } ``` ### Parameters Search query string Maximum results to return (Default: `10`) Properties to retrieve (None = default property set) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "contacts": { "items": { "additionalProperties": true, "type": "object" }, "title": "Contacts", "type": "array" }, "total": { "default": 0, "title": "Total", "type": "integer" } }, "required": [ "success" ], "title": "SearchContactsOutput", "type": "object" } ``` ### Parameters Maximum results to return (Default: `10`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "companies": { "items": { "additionalProperties": true, "type": "object" }, "title": "Companies", "type": "array" }, "total": { "default": 0, "title": "Total", "type": "integer" } }, "required": [ "success" ], "title": "GetRecentCompaniesOutput", "type": "object" } ``` ### Parameters HubSpot company ID Properties to retrieve (None = default property set) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "GetCompanyByIdOutput", "type": "object" } ``` ### Parameters Company name Primary domain Industry Phone number Website URL Additional properties ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "CreateCompanyOutput", "type": "object" } ``` ### Parameters HubSpot company ID Properties to update ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "UpdateCompanyOutput", "type": "object" } ``` ### Parameters HubSpot company ID ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "activities": { "items": { "additionalProperties": true, "type": "object" }, "title": "Activities", "type": "array" }, "total": { "default": 0, "title": "Total", "type": "integer" } }, "required": [ "success" ], "title": "GetCompanyActivityOutput", "type": "object" } ``` ### Parameters Search query string Maximum results to return (Default: `10`) Properties to retrieve (None = default property set) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "companies": { "items": { "additionalProperties": true, "type": "object" }, "title": "Companies", "type": "array" }, "total": { "default": 0, "title": "Total", "type": "integer" } }, "required": [ "success" ], "title": "SearchCompaniesOutput", "type": "object" } ``` ### Parameters Maximum results to return (Default: `10`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "deals": { "items": { "additionalProperties": true, "type": "object" }, "title": "Deals", "type": "array" }, "total": { "default": 0, "title": "Total", "type": "integer" } }, "required": [ "success" ], "title": "GetRecentDealsOutput", "type": "object" } ``` ### Parameters HubSpot deal ID Properties to retrieve (None = default property set) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "GetDealByIdOutput", "type": "object" } ``` ### Parameters Deal name Pipeline ID Stage ID Deal amount Expected close date (ISO 8601) Additional properties ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "CreateDealOutput", "type": "object" } ``` ### Parameters HubSpot deal ID Properties to update ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "UpdateDealOutput", "type": "object" } ``` ### Parameters Search query string Maximum results to return (Default: `10`) Properties to retrieve (None = default property set) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "deals": { "items": { "additionalProperties": true, "type": "object" }, "title": "Deals", "type": "array" }, "total": { "default": 0, "title": "Total", "type": "integer" } }, "required": [ "success" ], "title": "SearchDealsOutput", "type": "object" } ``` ### Parameters Maximum results to return (Default: `10`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "tickets": { "items": { "additionalProperties": true, "type": "object" }, "title": "Tickets", "type": "array" }, "total": { "default": 0, "title": "Total", "type": "integer" } }, "required": [ "success" ], "title": "GetRecentTicketsOutput", "type": "object" } ``` ### Parameters HubSpot ticket ID Properties to retrieve (None = default property set) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "GetTicketByIdOutput", "type": "object" } ``` ### Parameters Ticket subject Ticket content Pipeline ID Pipeline stage ID LOW / MEDIUM / HIGH Additional properties ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "CreateTicketOutput", "type": "object" } ``` ### Parameters HubSpot ticket ID Properties to update ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "UpdateTicketOutput", "type": "object" } ``` ### Parameters Search query string Maximum results to return (Default: `10`) Properties to retrieve (None = default property set) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "tickets": { "items": { "additionalProperties": true, "type": "object" }, "title": "Tickets", "type": "array" }, "total": { "default": 0, "title": "Total", "type": "integer" } }, "required": [ "success" ], "title": "SearchTicketsOutput", "type": "object" } ``` ### Parameters Note body content Contact IDs to associate Company IDs to associate Deal IDs to associate ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "CreateNoteOutput", "type": "object" } ``` ### Parameters Task subject Task body Due date (ISO 8601) Priority (NONE/LOW/MEDIUM/HIGH) (Default: `NONE`) Status (NOT_STARTED/IN_PROGRESS/COMPLETED/etc.) (Default: `NOT_STARTED`) Contact IDs to associate Company IDs to associate Deal IDs to associate ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "CreateTaskOutput", "type": "object" } ``` ### Parameters Meeting title Start time (ISO 8601 or epoch ms) End time Meeting body Contact IDs to associate Company IDs to associate Deal IDs to associate ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "CreateMeetingOutput", "type": "object" } ``` ### Parameters One of contacts/companies/deals/tickets Property name to retrieve ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "GetPropertyOutput", "type": "object" } ``` ### Parameters One of contacts/companies/deals/tickets ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "properties": { "type": "array", "items": { "additionalProperties": true, "type": "object" }, "title": "Properties" }, "total": { "default": 0, "title": "Total", "type": "integer" } }, "required": [ "success" ], "title": "ListPropertiesOutput", "type": "object" } ``` ## Related integrations ## Links - [HubSpot](https://www.hubspot.com) --- # Hunter Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/hunter Find and verify professional email addresses using the Hunter.io API {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/hunter/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Hunter logo ## Overview Add **Hunter** to any ModuleX agent or workflow. Find and verify professional email addresses, search domains for contacts, and manage leads using the Hunter.io API (`api.hunter.io/v2`). **Categories**: Sales · Marketing & Sales · Lead Generation · Email · **Auth**: API Key · **Actions**: 13 ## Authentication ### API Key Authentication Authenticate using your Hunter API key Go to https://hunter.io and sign in Navigate to your account API settings at https://hunter.io/api-keys Copy your API key Paste the API key below #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Hunter API Key | Your Hunter API key from hunter.io/api-keys | Yes | `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | ## Available Actions ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email" }, "first_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "First Name" }, "last_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Name" }, "plan_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Plan Name" }, "plan_level": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Plan Level" }, "reset_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Reset Date" }, "team_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Team Id" }, "calls_used": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Calls Used" }, "calls_available": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Calls Available" } }, "required": [ "success" ], "title": "AccountInformationOutput", "type": "object" } ``` ### Parameters The email address you want to find information about ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "CombinedEnrichmentOutput", "type": "object" } ``` ### Parameters The email address of the lead The first name of the lead The last name of the lead The job title of the lead The name of the company the lead is working in The sector of the company. Allowed values: Animal, Art & Entertainment, Automotive, Beauty & Fitness, Books & Literature, Education & Career, Finance, Food & Drink, Game, Health, Hobby & Leisure, Home & Garden, Industry, Internet & Telecom, Law & Government, Manufacturing, News, Real Estate, Science, Retail, Sport, Technology, Travel The size of the company the lead is working in Estimation of the probability the email address returned is correct, between 0 and 100 The domain name of the company The country of the lead (ISO 3166-1 alpha-2 standard) The address of the public profile on LinkedIn The phone number of the lead The Twitter handle of the lead Some personal notes about the lead The source where the lead has been found The identifier of the list the lead belongs to. If not specified, the lead is saved in the last list created ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Id" }, "email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email" }, "first_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "First Name" }, "last_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Name" } }, "required": [ "success" ], "title": "CreateLeadOutput", "type": "object" } ``` ### Parameters The unique identifier of the lead ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" } }, "required": [ "success" ], "title": "DeleteLeadOutput", "type": "object" } ``` ### Parameters Domain name from which you want to find the email addresses. For example, 'stripe.com'. Either domain or company must be provided The company name from which you want to find the email addresses. For example, 'stripe'. Either domain or company must be provided Specifies the max number of email addresses to return (Default: `100`) Get only personal or generic email addresses. Allowed values: personal, generic Get only email addresses for people with the selected seniority level(s). Comma-separated values: junior, senior, executive Get only email addresses for people working in the selected department(s). Comma-separated values: executive, it, finance, management, sales, legal, support, hr, marketing, communication, education, design, health, operations ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "domain": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Domain" }, "disposable": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Disposable" }, "webmail": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Webmail" }, "accept_all": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Accept All" }, "pattern": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Pattern" }, "organization": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Organization" }, "emails": { "items": { "additionalProperties": true, "type": "object" }, "title": "Emails", "type": "array" }, "total_results": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Total Results" } }, "required": [ "success" ], "title": "DomainSearchOutput", "type": "object" } ``` ### Parameters Domain name from which you want to find the email addresses. For example, 'stripe.com'. Either domain or company must be provided The company name from which you want to find the email addresses. For example, 'stripe'. Either domain or company must be provided Get only personal or generic email addresses. Allowed values: personal, generic ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "total": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Total" }, "personal_emails": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Personal Emails" }, "generic_emails": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Generic Emails" }, "department": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Department" } }, "required": [ "success" ], "title": "EmailCountOutput", "type": "object" } ``` ### Parameters Domain name from which you want to find the email addresses. For example, 'stripe.com'. Either domain or company must be provided The company name from which you want to find the email addresses. For example, 'stripe'. Either domain or company must be provided The person's first name The person's last name ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email" }, "first_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "First Name" }, "last_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Name" }, "score": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Score" }, "domain": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Domain" }, "accept_all": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Accept All" }, "position": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Position" }, "twitter": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Twitter" }, "linkedin_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Linkedin Url" }, "phone_number": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Phone Number" }, "company": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Company" }, "sources": { "items": { "additionalProperties": true, "type": "object" }, "title": "Sources", "type": "array" } }, "required": [ "success" ], "title": "EmailFinderOutput", "type": "object" } ``` ### Parameters The email address you want to verify ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "result": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Result" }, "score": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Score" }, "email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email" }, "regexp": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Regexp" }, "gibberish": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Gibberish" }, "disposable": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Disposable" }, "webmail": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Webmail" }, "mx_records": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Mx Records" }, "smtp_server": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Smtp Server" }, "smtp_check": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Smtp Check" }, "accept_all": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Accept All" }, "block": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Block" }, "sources": { "items": { "additionalProperties": true, "type": "object" }, "title": "Sources", "type": "array" } }, "required": [ "success" ], "title": "EmailVerifierOutput", "type": "object" } ``` ### Parameters The unique identifier of the lead ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Id" }, "email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email" }, "first_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "First Name" }, "last_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Name" }, "position": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Position" }, "company": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Company" } }, "required": [ "success" ], "title": "GetLeadOutput", "type": "object" } ``` ### Parameters Identifier of the leads list to retrieve A limit on the number of leads to be returned. Limit can range between 1 and 100 (Default: `100`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "leads": { "items": { "additionalProperties": true, "type": "object" }, "title": "Leads", "type": "array" } }, "required": [ "success" ], "title": "GetLeadsListOutput", "type": "object" } ``` ### Parameters Only returns the leads belonging to this list Filter leads by email Filter leads by first name Filter leads by last name Filter leads by position Filter leads by company Filter leads by industry Filter leads by website Filter leads by country code (ISO 3166-1 alpha-2) Filter leads by company size Filter leads by source Filter leads by Twitter handle Filter leads by LinkedIn URL Filter leads by phone number Filter by synchronization status. Allowed values: pending, error, success Filter by sending status. Comma-separated values: clicked, opened, sent, pending, error, bounced, unsubscribed, replied Filter by verification status. Comma-separated values: accept_all, disposable, invalid, unknown, valid, webmail, pending Filter by last activity. Allowed values: * (any), ~ (unset) Filter by last contact date. Allowed values: * (any), ~ (unset) Only returns leads with First Name, Last Name, or Email matching the query A limit on the number of leads to be returned. Limit can range between 1 and 1000 (Default: `100`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "leads": { "items": { "additionalProperties": true, "type": "object" }, "title": "Leads", "type": "array" }, "total": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Total" } }, "required": [ "success" ], "title": "ListLeadsOutput", "type": "object" } ``` ### Parameters A limit on the number of lists to be returned. Limit can range between 1 and 100 (Default: `100`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "leads_lists": { "items": { "additionalProperties": true, "type": "object" }, "title": "Leads Lists", "type": "array" } }, "required": [ "success" ], "title": "ListLeadsListsOutput", "type": "object" } ``` ### Parameters The unique identifier of the lead The email address of the lead The person's first name The person's last name The person's position in the company The company name The website URL of the company The person's phone number ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" } }, "required": [ "success" ], "title": "UpdateLeadOutput", "type": "object" } ``` ## Limits & Quotas - **Free plan**: 25 searches and 50 verifications per month. - **Paid plans**: Limits scale with plan tier (up to 30,000+ requests/month on Enterprise). - **Rate limit**: 10 requests/second across all endpoints. - **Error model**: Non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. ## Related integrations ## Links - [Hunter](https://hunter.io) --- # Icypeas Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/icypeas Find a professional email address from a name and company domain, or verify whether an existing email is valid and deliverable. Results are returned asynchronously via polling. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/icypeas/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Icypeas logo ## Overview Add **Icypeas** to any ModuleX agent or workflow. Find and verify professional email addresses against the Icypeas REST API (`app.icypeas.com`). Resolve a likely professional email from a person's name and company domain, or check whether an existing address is valid and deliverable. Both operations run asynchronously: a job is submitted, then the result is fetched by polling. **Categories**: Sales · Enrichment · Sales Engagement · **Auth**: API Key · **Actions**: 2 ## Authentication ### API Key Authentication Authenticate using your Icypeas API key Go to https://app.icypeas.com and sign up or log in Open the API settings / API access section in your account Create a new API key or copy your existing one Paste the API key below #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Icypeas API Key | Your Icypeas API key from app.icypeas.com | Yes | `-` | ## Available Actions ### Parameters Target company domain (e.g. stripe.com) or company name (e.g. Stripe) Target person's first name Target person's last name ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "search_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Search Id" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email" }, "firstname": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Firstname" }, "lastname": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Lastname" }, "item": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Item" } }, "required": [ "success" ], "title": "FindEmailOutput", "type": "object" } ``` ### Parameters Email address to verify (e.g. john@stripe.com) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "search_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Search Id" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email" }, "valid": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Valid" }, "item": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Item" } }, "required": [ "success" ], "title": "VerifyEmailOutput", "type": "object" } ``` ## Limits & Quotas - **Async polling**: each call submits a job, then polls the read endpoint roughly every 3 seconds for up to 120 seconds. If the job does not reach a terminal status in that window, the call returns `success=False` with an explanatory error. - **Terminal statuses**: `FOUND`, `DEBITED`, `NOT_FOUND`, `DEBITED_NOT_FOUND`, `BAD_INPUT`, `INSUFFICIENT_FUNDS`, `ABORTED`. A clean no-match (`NOT_FOUND`) is still a successful run — `email` is simply `null` and, for verification, `valid` is `false`. - **Rate limits**: approximately 60 requests/minute. - **Pricing** (approx., per vendor pricing page): email finding ~1 credit per found email; email verification ~0.1 credit per check. - **Error model**: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. Plan for retries on the agent side based on the error string. ## Related integrations ## Links - [Icypeas](https://www.icypeas.com) --- # incident.io Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/incidentio Manage incidents, actions, follow-ups, workflows, schedules, escalations, custom fields, and more with incident.io — the incident management and on-call response platform. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/incidentio/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} incident.io logo ## Overview Add **incident.io** to any ModuleX agent or workflow. Incident management and on-call response against the incident.io REST API (`api.incident.io`). Manage incidents, actions, follow-ups, workflows, schedules, escalations, escalation paths, custom fields, incident roles, and reference data. **Categories**: Monitoring & Observability · Incident Management · On Call · **Auth**: API Key · **Actions**: 46 ## Authentication ### API Key Authentication Authenticate using your incident.io API key Log in to your incident.io dashboard Go to Settings -> API keys Create a new API key and select the permissions it should have Copy the key (it is shown only once) and paste it below #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | incident.io API Key | Your incident.io API key from Settings -> API keys | Yes | `-` | ## Available Actions ### Parameters Number of results to return per page (e.g., 10, 25, 50) Pagination cursor to fetch the next page of results Sort order: 'created_at_newest_first' or 'created_at_oldest_first' How to combine filters: 'all' or 'any' ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "incidents": { "items": { "additionalProperties": true, "type": "object" }, "title": "Incidents", "type": "array" }, "pagination_meta": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Pagination Meta" } }, "required": [ "success" ], "title": "IncidentsListOutput", "type": "object" } ``` ### Parameters Unique identifier to prevent duplicate creation (e.g., a UUID) ID of the severity level Visibility of the incident: 'public' or 'private' Name of the incident Brief summary of the incident ID of the incident type ID of the initial incident status ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "incident": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Incident" } }, "required": [ "success" ], "title": "IncidentsCreateOutput", "type": "object" } ``` ### Parameters ID of the incident to retrieve ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "incident": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Incident" } }, "required": [ "success" ], "title": "IncidentsShowOutput", "type": "object" } ``` ### Parameters ID of the incident to update Whether to notify the incident channel about this update Updated name of the incident Updated summary of the incident Updated severity ID Updated status ID Updated incident type ID ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "incident": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Incident" } }, "required": [ "success" ], "title": "IncidentsUpdateOutput", "type": "object" } ``` ### Parameters Filter actions by incident ID Filter by incident mode: standard, retrospective, test, tutorial, or stream ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "actions": { "items": { "additionalProperties": true, "type": "object" }, "title": "Actions", "type": "array" } }, "required": [ "success" ], "title": "ActionsListOutput", "type": "object" } ``` ### Parameters Action ID ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "action": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Action" } }, "required": [ "success" ], "title": "ActionsShowOutput", "type": "object" } ``` ### Parameters Filter follow-ups by incident ID Filter by incident mode: standard, retrospective, test, tutorial, or stream ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "follow_ups": { "items": { "additionalProperties": true, "type": "object" }, "title": "Follow Ups", "type": "array" } }, "required": [ "success" ], "title": "FollowUpsListOutput", "type": "object" } ``` ### Parameters Follow-up ID ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "follow_up": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Follow Up" } }, "required": [ "success" ], "title": "FollowUpsShowOutput", "type": "object" } ``` ### Parameters Number of results to return per page (e.g., 10, 25, 50) Pagination cursor to fetch the next page of results Filter users by email address Filter users by Slack user ID ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "users": { "items": { "additionalProperties": true, "type": "object" }, "title": "Users", "type": "array" }, "pagination_meta": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Pagination Meta" } }, "required": [ "success" ], "title": "UsersListOutput", "type": "object" } ``` ### Parameters The unique identifier of the user to retrieve ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "user": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "User" } }, "required": [ "success" ], "title": "UsersShowOutput", "type": "object" } ``` ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "workflows": { "items": { "additionalProperties": true, "type": "object" }, "title": "Workflows", "type": "array" } }, "required": [ "success" ], "title": "WorkflowsListOutput", "type": "object" } ``` ### Parameters Name of the workflow Folder to organize the workflow in State of the workflow: active, draft, or disabled (Default: `draft`) Trigger type for the workflow (Default: `incident.updated`) Array of workflow steps (defaults to []) Array of condition groups controlling when the workflow runs When to run: newly_created, newly_created_and_active, active, or all (Default: `newly_created`) Array of incident modes to run on (defaults to ['standard']) Whether to include private incidents (Default: `true`) Whether to continue executing subsequent steps if one fails (Default: `false`) Array of fields making the workflow run once per combination Array of workflow expressions for advanced logic Delay configuration object ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "workflow": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Workflow" }, "management_meta": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Management Meta" } }, "required": [ "success" ], "title": "WorkflowsCreateOutput", "type": "object" } ``` ### Parameters The ID of the workflow to retrieve Skip workflow step upgrades when existing step parameters changed ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "workflow": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Workflow" }, "management_meta": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Management Meta" } }, "required": [ "success" ], "title": "WorkflowsShowOutput", "type": "object" } ``` ### Parameters The ID of the workflow to update New name for the workflow Complete array of workflow steps Complete array of workflow condition groups When to run: newly_created, newly_created_and_active, active, or all Complete array of incident modes to run on Whether to include private incidents Whether to continue executing subsequent steps if one fails Complete array of fields that make the workflow run once Complete array of workflow expressions New state: active, draft, or disabled New folder for the workflow Delay configuration object ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "workflow": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Workflow" }, "management_meta": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Management Meta" } }, "required": [ "success" ], "title": "WorkflowsUpdateOutput", "type": "object" } ``` ### Parameters The ID of the workflow to delete ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" } }, "required": [ "success" ], "title": "WorkflowsDeleteOutput", "type": "object" } ``` ### Parameters Number of results to return per page (e.g., 10, 25, 50) Pagination cursor to fetch the next page of results ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "schedules": { "items": { "additionalProperties": true, "type": "object" }, "title": "Schedules", "type": "array" }, "pagination_meta": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Pagination Meta" } }, "required": [ "success" ], "title": "SchedulesListOutput", "type": "object" } ``` ### Parameters Name of the schedule Timezone for the schedule (e.g., America/New_York) Schedule configuration object with rotations ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "schedule": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Schedule" } }, "required": [ "success" ], "title": "SchedulesCreateOutput", "type": "object" } ``` ### Parameters The ID of the schedule ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "schedule": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Schedule" } }, "required": [ "success" ], "title": "SchedulesShowOutput", "type": "object" } ``` ### Parameters The ID of the schedule to update New name for the schedule New timezone for the schedule Schedule configuration object with rotations ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "schedule": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Schedule" } }, "required": [ "success" ], "title": "SchedulesUpdateOutput", "type": "object" } ``` ### Parameters The ID of the schedule to delete ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" } }, "required": [ "success" ], "title": "SchedulesDeleteOutput", "type": "object" } ``` ### Parameters Number of results to return per page (e.g., 10, 25, 50) Pagination cursor to fetch the next page of results ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "escalations": { "items": { "additionalProperties": true, "type": "object" }, "title": "Escalations", "type": "array" }, "pagination_meta": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Pagination Meta" } }, "required": [ "success" ], "title": "EscalationsListOutput", "type": "object" } ``` ### Parameters Unique identifier to prevent duplicate escalation creation Title of the escalation ID of the escalation path (required if user_ids not provided) Comma-separated user IDs to notify (required if no escalation_path_id) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "escalation": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Escalation" } }, "required": [ "success" ], "title": "EscalationsCreateOutput", "type": "object" } ``` ### Parameters The ID of the escalation policy ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "escalation": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Escalation" } }, "required": [ "success" ], "title": "EscalationsShowOutput", "type": "object" } ``` ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "custom_fields": { "items": { "additionalProperties": true, "type": "object" }, "title": "Custom Fields", "type": "array" } }, "required": [ "success" ], "title": "CustomFieldsListOutput", "type": "object" } ``` ### Parameters Name of the custom field Description of the custom field Field type: text, single_select, multi_select, numeric, link, etc. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "custom_field": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Custom Field" } }, "required": [ "success" ], "title": "CustomFieldsCreateOutput", "type": "object" } ``` ### Parameters Custom field ID ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "custom_field": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Custom Field" } }, "required": [ "success" ], "title": "CustomFieldsShowOutput", "type": "object" } ``` ### Parameters Custom field ID New name for the custom field New description for the custom field ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "custom_field": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Custom Field" } }, "required": [ "success" ], "title": "CustomFieldsUpdateOutput", "type": "object" } ``` ### Parameters Custom field ID ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" } }, "required": [ "success" ], "title": "CustomFieldsDeleteOutput", "type": "object" } ``` ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "severities": { "items": { "additionalProperties": true, "type": "object" }, "title": "Severities", "type": "array" } }, "required": [ "success" ], "title": "SeveritiesListOutput", "type": "object" } ``` ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "incident_statuses": { "items": { "additionalProperties": true, "type": "object" }, "title": "Incident Statuses", "type": "array" } }, "required": [ "success" ], "title": "IncidentStatusesListOutput", "type": "object" } ``` ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "incident_types": { "items": { "additionalProperties": true, "type": "object" }, "title": "Incident Types", "type": "array" } }, "required": [ "success" ], "title": "IncidentTypesListOutput", "type": "object" } ``` ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "incident_roles": { "items": { "additionalProperties": true, "type": "object" }, "title": "Incident Roles", "type": "array" } }, "required": [ "success" ], "title": "IncidentRolesListOutput", "type": "object" } ``` ### Parameters Name of the incident role Description of the incident role Instructions for the incident role Short form abbreviation for the role ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "incident_role": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Incident Role" } }, "required": [ "success" ], "title": "IncidentRolesCreateOutput", "type": "object" } ``` ### Parameters The ID of the incident role ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "incident_role": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Incident Role" } }, "required": [ "success" ], "title": "IncidentRolesShowOutput", "type": "object" } ``` ### Parameters The ID of the incident role to update Name of the incident role Description of the incident role Instructions for the incident role Short form abbreviation for the role ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "incident_role": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Incident Role" } }, "required": [ "success" ], "title": "IncidentRolesUpdateOutput", "type": "object" } ``` ### Parameters The ID of the incident role to delete ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" } }, "required": [ "success" ], "title": "IncidentRolesDeleteOutput", "type": "object" } ``` ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "incident_timestamps": { "items": { "additionalProperties": true, "type": "object" }, "title": "Incident Timestamps", "type": "array" } }, "required": [ "success" ], "title": "IncidentTimestampsListOutput", "type": "object" } ``` ### Parameters The ID of the incident timestamp ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "incident_timestamp": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Incident Timestamp" } }, "required": [ "success" ], "title": "IncidentTimestampsShowOutput", "type": "object" } ``` ### Parameters The ID of the incident to get updates for Number of results to return per page (e.g., 10, 25, 50) Pagination cursor to fetch the next page of results ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "incident_updates": { "items": { "additionalProperties": true, "type": "object" }, "title": "Incident Updates", "type": "array" }, "pagination_meta": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Pagination Meta" } }, "required": [ "success" ], "title": "IncidentUpdatesListOutput", "type": "object" } ``` ### Parameters The ID of the schedule to get entries for Start of the filter window in ISO 8601 format End of the filter window in ISO 8601 format ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "schedule_entries": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Schedule Entries" }, "pagination_meta": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Pagination Meta" } }, "required": [ "success" ], "title": "ScheduleEntriesListOutput", "type": "object" } ``` ### Parameters The ID of the rotation to override The ID of the layer this override applies to The ID of the schedule When the override starts in ISO 8601 format When the override ends in ISO 8601 format The ID of the user to assign The email of the user to assign The Slack ID of the user to assign ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "override": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Override" } }, "required": [ "success" ], "title": "ScheduleOverridesCreateOutput", "type": "object" } ``` ### Parameters Number of results to return per page (e.g., 10, 25, 50) Pagination cursor to fetch the next page of results ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "escalation_paths": { "items": { "additionalProperties": true, "type": "object" }, "title": "Escalation Paths", "type": "array" }, "pagination_meta": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Pagination Meta" } }, "required": [ "success" ], "title": "EscalationPathsListOutput", "type": "object" } ``` ### Parameters Name of the escalation path Array of escalation levels. Each level has targets (array of {id, type, schedule_id?, user_id?, urgency}) and time_to_ack_seconds Optional working hours config: array of {weekday, start_time, end_time} ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "escalation_path": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Escalation Path" } }, "required": [ "success" ], "title": "EscalationPathsCreateOutput", "type": "object" } ``` ### Parameters The ID of the escalation path ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "escalation_path": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Escalation Path" } }, "required": [ "success" ], "title": "EscalationPathsShowOutput", "type": "object" } ``` ### Parameters The ID of the escalation path to update New name for the escalation path New escalation path: array of levels with targets and time_to_ack_seconds New working hours config: array of {weekday, start_time, end_time} ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "escalation_path": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Escalation Path" } }, "required": [ "success" ], "title": "EscalationPathsUpdateOutput", "type": "object" } ``` ### Parameters The ID of the escalation path to delete ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" } }, "required": [ "success" ], "title": "EscalationPathsDeleteOutput", "type": "object" } ``` ## Limits & Quotas - **Rate limit**: 1200 requests/minute per API key (default). - **Endpoints**: most resources live under `/v2`; severities, incident statuses, and incident types are served under `/v1`. - **Pagination**: list endpoints accept `page_size` plus an `after` cursor; the tools expose both as optional parameters and do not auto-loop — pass the returned cursor to fetch the next page. - **Error model**: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. Plan retries on the agent side based on the error string. ## Related integrations ## Links - [incident.io](https://incident.io) --- # Insightly Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/insightly CRM and project management platform for managing contacts, tasks, and sales pipelines {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/insightly/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Insightly logo ## Overview Add **Insightly** to any ModuleX agent or workflow. CRM and project management platform for managing contacts, tasks, and sales pipelines via the Insightly REST API (`api.{pod}.insightly.com/v3.1`). **Categories**: CRM · Sales · Productivity & Collaboration · **Auth**: Insightly API Key · **Actions**: 2 ## Authentication ### Insightly API Key Authenticate using your Insightly API key and pod identifier Log in to your Insightly account Go to User Settings > API Copy your API key Find your pod identifier from your Insightly URL (e.g. na1, au1) #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Pod | Your Insightly pod/region identifier (e.g. na1, au1) found in your Insightly URL | Yes | `na1` | | API Key | Your Insightly API key from User Settings > API | Yes | `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` | ## Available Actions ### Parameters The first name of the contact The last name of the contact The email address of the contact The title of the contact The phone number of the contact The street address of the contact The city of the contact The state of the contact The zip code/postcode of the contact The country of the contact ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "contact_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Contact Id" }, "first_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "First Name" }, "last_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Name" }, "email_address": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email Address" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "phone": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Phone" } }, "required": [ "success" ], "title": "CreateContactOutput", "type": "object" } ``` ### Parameters The title of the task The status of the task. Allowed values: Not Started, In Progress, Completed, Deferred, Waiting The due date of the task in YYYY-MM-DD format (e.g. 2023-08-20) Identifier of a task category ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "task_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Task Id" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "due_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Due Date" }, "category_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Category Id" } }, "required": [ "success" ], "title": "CreateTaskOutput", "type": "object" } ``` ## Limits & Quotas - **Rate limits**: Insightly enforces per-plan rate limits (typically 10 requests/second for Professional plans, higher for Enterprise). - **Pricing**: API access requires a paid Insightly plan (Professional or higher). - **Error model**: non-2xx responses are caught and returned as `success=False` + `error` rather than raising. ## Related integrations ## Links - [Insightly](https://www.insightly.com) --- # Instacart Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/instacart Create shareable recipe pages, shopping lists, and find nearby retailers on Instacart. No authentication required. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/instacart/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Instacart logo ## Overview Add **Instacart** to any ModuleX agent or workflow. Public Instacart actions: build shareable recipe + shopping-list pages and look up retailers by postal code. No API key required. **Categories**: E-Commerce · Business Services · Grocery · Retail · Shopping · **Auth**: ModuleX Managed · **Actions**: 3 ## Authentication ### ModuleX Managed This integration uses Instacart's public APIs. No API key is required for requests. ## Available Actions ### Parameters The title of the recipe List of ingredients for the recipe Recipe instructions/directions Number of servings URL to recipe image Original recipe source URL Partner ID for affiliate tracking ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "shareable_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Shareable Url" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "ingredient_count": { "default": 0, "title": "Ingredient Count", "type": "integer" }, "ingredients": { "items": { "type": "string" }, "title": "Ingredients", "type": "array" } }, "required": [ "success" ], "title": "CreateRecipePageOutput", "type": "object" } ``` ### Parameters The title of the shopping list List of items to add to the shopping list Partner ID for affiliate tracking ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "shareable_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Shareable Url" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "item_count": { "default": 0, "title": "Item Count", "type": "integer" }, "items": { "items": { "type": "string" }, "title": "Items", "type": "array" } }, "required": [ "success" ], "title": "CreateShoppingListPageOutput", "type": "object" } ``` ### Parameters Postal/ZIP code to search near Country code (e.g., 'US', 'CA') (Default: `US`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "postal_code": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Postal Code" }, "country_code": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Country Code" }, "retailer_count": { "default": 0, "title": "Retailer Count", "type": "integer" }, "retailers": { "items": { "additionalProperties": true, "type": "object" }, "title": "Retailers", "type": "array" }, "store_finder_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Store Finder Url" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" } }, "required": [ "success" ], "title": "GetNearbyRetailersOutput", "type": "object" } ``` ## Limits & Quotas - Recipe/list pages are pure URL builders (no API hit). - Retailer lookup goes to Instacart's public `/v3/retailers` endpoint; on timeout or non-200 the tool returns a store-finder URL as a usable fallback (still `success=True`). - Country supported: `US`, `CA`. ## Related integrations ## Links - [Instacart](https://www.instacart.com) --- # Instantly Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/instantly Integrate the Instantly V2 cold-email outreach API. Create and list leads, manage lead interest status, delete leads in bulk, list and create campaigns, reply to emails, and manage lead lists. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/instantly/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Instantly logo ## Overview Add **Instantly** to any ModuleX agent or workflow. Cold-email outreach automation against the Instantly V2 REST API (`api.instantly.ai`). Manage leads, lead lists, campaigns, and Unibox emails. **Categories**: Marketing & Advertising · Email · Sales Engagement · **Auth**: API Key · **Actions**: 13 ## Authentication ### API Key Authentication Authenticate using your Instantly V2 API key Sign in at https://app.instantly.ai Open Settings and navigate to the 'API Keys' (Integrations) section Create a new V2 API key with the required scopes Copy the key and paste it below #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Instantly API Key | Your Instantly V2 API key with the required scopes | Yes | `-` | ## Available Actions ### Parameters Search by first name, last name, or email Lead filter value, such as FILTER_VAL_CONTACTED or FILTER_VAL_ACTIVE Campaign ID to filter leads Lead list ID to filter leads Whether the lead is in a campaign Whether the lead is in a list Lead IDs to include Lead IDs to exclude Lead email addresses to include Organization user IDs to filter leads Smart view ID to filter leads Whether the lead is a website visitor Whether to return distinct contacts Enrichment status filter Email security gateway code filter Number of leads to return, from 1 to 100 Forward pagination cursor from next_starting_after ### Response ```json { "$defs": { "LeadDetail": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "timestamp_created": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Timestamp Created" }, "timestamp_updated": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Timestamp Updated" }, "organization": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Organization" }, "campaign": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Campaign" }, "status": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Status" }, "email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email" }, "personalization": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Personalization" }, "website": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Website" }, "last_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Name" }, "first_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "First Name" }, "company_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Company Name" }, "job_title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Job Title" }, "phone": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Phone" }, "email_open_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Email Open Count" }, "email_reply_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Email Reply Count" }, "email_click_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Email Click Count" }, "company_domain": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Company Domain" }, "payload": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Payload" }, "lt_interest_status": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Lt Interest Status" } }, "title": "LeadDetail", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "leads": { "items": { "$ref": "#/$defs/LeadDetail" }, "title": "Leads", "type": "array" }, "count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Count" }, "next_starting_after": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Next Starting After" } }, "required": [ "success" ], "title": "ListLeadsOutput", "type": "object" } ``` ### Parameters Lead ID ### Response ```json { "$defs": { "LeadDetail": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "timestamp_created": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Timestamp Created" }, "timestamp_updated": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Timestamp Updated" }, "organization": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Organization" }, "campaign": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Campaign" }, "status": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Status" }, "email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email" }, "personalization": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Personalization" }, "website": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Website" }, "last_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Name" }, "first_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "First Name" }, "company_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Company Name" }, "job_title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Job Title" }, "phone": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Phone" }, "email_open_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Email Open Count" }, "email_reply_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Email Reply Count" }, "email_click_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Email Click Count" }, "company_domain": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Company Domain" }, "payload": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Payload" }, "lt_interest_status": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Lt Interest Status" } }, "title": "LeadDetail", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "lead": { "anyOf": [ { "$ref": "#/$defs/LeadDetail" }, { "type": "null" } ], "default": null }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "email_address": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email Address" }, "first_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "First Name" }, "last_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Name" }, "campaign": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Campaign" }, "status": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Status" } }, "required": [ "success" ], "title": "GetLeadOutput", "type": "object" } ``` ### Parameters Campaign ID associated with the lead Lead list ID associated with the lead Lead email address. Required when adding to a campaign. Lead first name Lead last name Lead company name Lead job title Lead phone number Lead website Lead personalization text Lead interest status value Potential value of the lead Organization user ID assigned to the lead Skip if the lead already exists in the workspace Skip if the lead already exists in the campaign Skip if the lead already exists in the list Blocklist ID to check for the lead Whether to verify leads imported from Lead Finder Whether to verify leads on import Custom variable object with string, number, boolean, or null values ### Response ```json { "$defs": { "LeadDetail": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "timestamp_created": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Timestamp Created" }, "timestamp_updated": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Timestamp Updated" }, "organization": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Organization" }, "campaign": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Campaign" }, "status": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Status" }, "email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email" }, "personalization": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Personalization" }, "website": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Website" }, "last_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Name" }, "first_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "First Name" }, "company_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Company Name" }, "job_title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Job Title" }, "phone": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Phone" }, "email_open_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Email Open Count" }, "email_reply_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Email Reply Count" }, "email_click_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Email Click Count" }, "company_domain": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Company Domain" }, "payload": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Payload" }, "lt_interest_status": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Lt Interest Status" } }, "title": "LeadDetail", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "lead": { "anyOf": [ { "$ref": "#/$defs/LeadDetail" }, { "type": "null" } ], "default": null }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "email_address": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email Address" }, "first_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "First Name" }, "last_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Name" }, "campaign": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Campaign" }, "status": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Status" } }, "required": [ "success" ], "title": "CreateLeadOutput", "type": "object" } ``` ### Parameters Campaign ID to delete leads from. Required if list_id is not provided. Lead list ID to delete leads from. Required if campaign_id is not provided. Optional lead status filter Specific lead IDs to delete Maximum number of matching leads to delete, up to 10000 ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Count" } }, "required": [ "success" ], "title": "DeleteLeadsOutput", "type": "object" } ``` ### Parameters Lead email address Interest status value. Pass null to reset to Lead. Campaign ID for the lead Lead list ID for the lead AI interest value to set for the lead Whether to disable auto interest ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" } }, "required": [ "success" ], "title": "UpdateLeadInterestStatusOutput", "type": "object" } ``` ### Parameters Number of campaigns to return, from 1 to 100 Pagination cursor from next_starting_after Search by campaign name Comma-separated campaign tag IDs AI Sales Agent ID filter Campaign status enum value ### Response ```json { "$defs": { "CampaignDetail": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "pl_value": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Pl Value" }, "status": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Status" }, "is_evergreen": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Evergreen" }, "timestamp_created": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Timestamp Created" }, "timestamp_updated": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Timestamp Updated" }, "email_gap": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Email Gap" }, "daily_limit": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Daily Limit" }, "daily_max_leads": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Daily Max Leads" }, "open_tracking": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Open Tracking" }, "stop_on_reply": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Stop On Reply" }, "sequences": { "items": {}, "title": "Sequences", "type": "array" }, "campaign_schedule": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Campaign Schedule" } }, "title": "CampaignDetail", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "campaigns": { "items": { "$ref": "#/$defs/CampaignDetail" }, "title": "Campaigns", "type": "array" }, "count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Count" }, "next_starting_after": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Next Starting After" } }, "required": [ "success" ], "title": "ListCampaignsOutput", "type": "object" } ``` ### Parameters Campaign name Campaign schedule object with schedules array Campaign sequence definitions Sending email accounts Daily sending limit Daily maximum new leads to contact Whether to track opens Whether to stop the campaign on reply Whether to track links Whether the campaign is text only Gap between emails in minutes Value of every positive lead ### Response ```json { "$defs": { "CampaignDetail": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "pl_value": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Pl Value" }, "status": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Status" }, "is_evergreen": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Evergreen" }, "timestamp_created": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Timestamp Created" }, "timestamp_updated": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Timestamp Updated" }, "email_gap": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Email Gap" }, "daily_limit": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Daily Limit" }, "daily_max_leads": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Daily Max Leads" }, "open_tracking": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Open Tracking" }, "stop_on_reply": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Stop On Reply" }, "sequences": { "items": {}, "title": "Sequences", "type": "array" }, "campaign_schedule": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Campaign Schedule" } }, "title": "CampaignDetail", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "campaign": { "anyOf": [ { "$ref": "#/$defs/CampaignDetail" }, { "type": "null" } ], "default": null }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "status": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Status" } }, "required": [ "success" ], "title": "CreateCampaignOutput", "type": "object" } ``` ### Parameters Campaign ID Campaign name Campaign schedule object with schedules array Campaign sequence definitions Sending email accounts Daily sending limit Daily maximum new leads to contact Whether to track opens Whether to stop the campaign on reply Whether to track links Whether the campaign is text only Gap between emails in minutes Value of every positive lead ### Response ```json { "$defs": { "CampaignDetail": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "pl_value": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Pl Value" }, "status": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Status" }, "is_evergreen": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Evergreen" }, "timestamp_created": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Timestamp Created" }, "timestamp_updated": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Timestamp Updated" }, "email_gap": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Email Gap" }, "daily_limit": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Daily Limit" }, "daily_max_leads": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Daily Max Leads" }, "open_tracking": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Open Tracking" }, "stop_on_reply": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Stop On Reply" }, "sequences": { "items": {}, "title": "Sequences", "type": "array" }, "campaign_schedule": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Campaign Schedule" } }, "title": "CampaignDetail", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "campaign": { "anyOf": [ { "$ref": "#/$defs/CampaignDetail" }, { "type": "null" } ], "default": null }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "status": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Status" } }, "required": [ "success" ], "title": "PatchCampaignOutput", "type": "object" } ``` ### Parameters Campaign ID ### Response ```json { "$defs": { "CampaignDetail": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "pl_value": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Pl Value" }, "status": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Status" }, "is_evergreen": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Evergreen" }, "timestamp_created": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Timestamp Created" }, "timestamp_updated": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Timestamp Updated" }, "email_gap": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Email Gap" }, "daily_limit": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Daily Limit" }, "daily_max_leads": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Daily Max Leads" }, "open_tracking": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Open Tracking" }, "stop_on_reply": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Stop On Reply" }, "sequences": { "items": {}, "title": "Sequences", "type": "array" }, "campaign_schedule": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Campaign Schedule" } }, "title": "CampaignDetail", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "campaign": { "anyOf": [ { "$ref": "#/$defs/CampaignDetail" }, { "type": "null" } ], "default": null }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "status": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Status" } }, "required": [ "success" ], "title": "ActivateCampaignOutput", "type": "object" } ``` ### Parameters Number of emails to return, from 1 to 100 Pagination cursor from next_starting_after Search query, email address, or thread:<thread-id> Campaign ID filter Lead list ID filter Email interest status filter Sending email account filter Lead email address filter Unread status filter ### Response ```json { "$defs": { "EmailBody": { "additionalProperties": false, "properties": { "text": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Text" }, "html": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Html" } }, "title": "EmailBody", "type": "object" }, "EmailDetail": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "timestamp_created": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Timestamp Created" }, "timestamp_email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Timestamp Email" }, "message_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message Id" }, "subject": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Subject" }, "from_address_email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "From Address Email" }, "to_address_email_list": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "To Address Email List" }, "cc_address_email_list": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Cc Address Email List" }, "bcc_address_email_list": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Bcc Address Email List" }, "reply_to": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Reply To" }, "body": { "anyOf": [ { "$ref": "#/$defs/EmailBody" }, { "type": "null" } ], "default": null }, "organization_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Organization Id" }, "campaign_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Campaign Id" }, "subsequence_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Subsequence Id" }, "list_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "List Id" }, "lead": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Lead" }, "lead_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Lead Id" }, "eaccount": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Eaccount" }, "ue_type": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Ue Type" }, "is_unread": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Is Unread" }, "is_auto_reply": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Is Auto Reply" }, "i_status": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "I Status" }, "thread_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Thread Id" }, "content_preview": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Content Preview" } }, "title": "EmailDetail", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "emails": { "items": { "$ref": "#/$defs/EmailDetail" }, "title": "Emails", "type": "array" }, "count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Count" }, "next_starting_after": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Next Starting After" } }, "required": [ "success" ], "title": "ListEmailsOutput", "type": "object" } ``` ### Parameters Connected email account used to send the reply Email ID to reply to Reply subject Reply body as plain text Reply body as HTML Comma-separated CC email addresses Comma-separated BCC email addresses ### Response ```json { "$defs": { "EmailBody": { "additionalProperties": false, "properties": { "text": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Text" }, "html": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Html" } }, "title": "EmailBody", "type": "object" }, "EmailDetail": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "timestamp_created": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Timestamp Created" }, "timestamp_email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Timestamp Email" }, "message_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message Id" }, "subject": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Subject" }, "from_address_email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "From Address Email" }, "to_address_email_list": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "To Address Email List" }, "cc_address_email_list": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Cc Address Email List" }, "bcc_address_email_list": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Bcc Address Email List" }, "reply_to": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Reply To" }, "body": { "anyOf": [ { "$ref": "#/$defs/EmailBody" }, { "type": "null" } ], "default": null }, "organization_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Organization Id" }, "campaign_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Campaign Id" }, "subsequence_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Subsequence Id" }, "list_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "List Id" }, "lead": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Lead" }, "lead_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Lead Id" }, "eaccount": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Eaccount" }, "ue_type": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Ue Type" }, "is_unread": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Is Unread" }, "is_auto_reply": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Is Auto Reply" }, "i_status": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "I Status" }, "thread_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Thread Id" }, "content_preview": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Content Preview" } }, "title": "EmailDetail", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "email": { "anyOf": [ { "$ref": "#/$defs/EmailDetail" }, { "type": "null" } ], "default": null }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "subject": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Subject" }, "thread_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Thread Id" } }, "required": [ "success" ], "title": "ReplyToEmailOutput", "type": "object" } ``` ### Parameters Number of lead lists to return, from 1 to 100 Starting-after cursor from next_starting_after Filter by enrichment task setting Search query to filter lead lists by name ### Response ```json { "$defs": { "LeadListDetail": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "organization_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Organization Id" }, "has_enrichment_task": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Has Enrichment Task" }, "owned_by": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Owned By" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "timestamp_created": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Timestamp Created" } }, "title": "LeadListDetail", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "lead_lists": { "items": { "$ref": "#/$defs/LeadListDetail" }, "title": "Lead Lists", "type": "array" }, "count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Count" }, "next_starting_after": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Next Starting After" } }, "required": [ "success" ], "title": "ListLeadListsOutput", "type": "object" } ``` ### Parameters Lead list name Whether this list runs enrichment for every added lead User ID of the lead list owner ### Response ```json { "$defs": { "LeadListDetail": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "organization_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Organization Id" }, "has_enrichment_task": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Has Enrichment Task" }, "owned_by": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Owned By" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "timestamp_created": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Timestamp Created" } }, "title": "LeadListDetail", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "lead_list": { "anyOf": [ { "$ref": "#/$defs/LeadListDetail" }, { "type": "null" } ], "default": null }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" } }, "required": [ "success" ], "title": "CreateLeadListOutput", "type": "object" } ``` ## Limits & Quotas - List endpoints accept `limit` (1–100) and forward-paginate via the `next_starting_after` cursor returned in each response — pass it back as `starting_after` for the next page. - `update_lead_interest_status` submits an asynchronous background job; the response carries a submission `message`, not the updated lead. - **Error model**: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. Plan agent retries based on the error string. ## Related integrations ## Links - [Instantly](https://instantly.ai) --- # Instructure Canvas LMS Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/instructure-canvas Learning management system for course, assignment, and user management via the Canvas REST API. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/instructure_canvas/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Instructure Canvas LMS logo ## Overview Add **Instructure Canvas LMS** to any ModuleX agent or workflow. Learning management system integration for [Instructure Canvas](https://www.instructure.com/canvas) — course, assignment, and user management via the Canvas REST API (`https://{your-domain}/api/v1`). **Categories**: Education · Learning Management · **Auth**: Canvas OAuth Token + Domain · **Actions**: 5 ## Authentication ### Canvas OAuth Token + Domain Authenticate using a Canvas access token and your instance domain. Canvas LMS is self-hosted, so both the domain and token are required. Log into your Canvas instance. Go to Account > Settings > Approved Integrations (or generate a new access token). Copy your access token and note your Canvas domain (e.g. myschool.instructure.com). #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Canvas Domain | Your Canvas instance domain (e.g. myschool.instructure.com) | Yes | `myschool.instructure.com` | | Access Token | Your Canvas API access token | Yes | `7~xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | ## Available Actions ### Response ```json { "$defs": { "AccountOption": { "additionalProperties": false, "description": "A Canvas account option.", "properties": { "id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" } }, "title": "AccountOption", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "accounts": { "items": { "$ref": "#/$defs/AccountOption" }, "title": "Accounts", "type": "array" } }, "required": [ "success" ], "title": "ListAccountsOutput", "type": "object" } ``` ### Parameters The ID of the user whose assignments to list. The ID of the course to list assignments from. ### Response ```json { "$defs": { "AssignmentSummary": { "additionalProperties": false, "description": "A Canvas assignment.", "properties": { "id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "due_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Due At" }, "points_possible": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Points Possible" }, "grading_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Grading Type" }, "submission_types": { "items": { "type": "string" }, "title": "Submission Types", "type": "array" }, "course_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Course Id" }, "allowed_attempts": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Allowed Attempts" }, "omit_from_final_grade": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Omit From Final Grade" } }, "title": "AssignmentSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "assignments": { "items": { "$ref": "#/$defs/AssignmentSummary" }, "title": "Assignments", "type": "array" } }, "required": [ "success" ], "title": "ListAssignmentsOutput", "type": "object" } ``` ### Parameters The ID of the user whose courses to list. ### Response ```json { "$defs": { "CourseSummary": { "additionalProperties": false, "description": "A Canvas course.", "properties": { "id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "course_code": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Course Code" }, "workflow_state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Workflow State" }, "enrollment_term_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Enrollment Term Id" } }, "title": "CourseSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "courses": { "items": { "$ref": "#/$defs/CourseSummary" }, "title": "Courses", "type": "array" } }, "required": [ "success" ], "title": "ListCoursesOutput", "type": "object" } ``` ### Parameters The ID of the course to search within. The search query string. ### Response ```json { "$defs": { "SearchResultItem": { "additionalProperties": false, "description": "A search result from Canvas smart search.", "properties": { "content_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Content Id" }, "content_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Content Type" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "body": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Body" }, "html_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Html Url" }, "distance": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Distance" }, "readable_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Readable Type" }, "relevance": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Relevance" } }, "title": "SearchResultItem", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "results": { "items": { "$ref": "#/$defs/SearchResultItem" }, "title": "Results", "type": "array" } }, "required": [ "success" ], "title": "SearchCourseContentOutput", "type": "object" } ``` ### Parameters The ID of the course containing the assignment. The ID of the assignment to update. The new name of the assignment. The new description of the assignment (supports HTML). Submission type: online_quiz, none, on_paper, discussion_topic, external_tool, online_upload, online_text_entry, online_url, media_recording, student_annotation. Whether to notify students of the update. Maximum points possible on the assignment. Grading strategy: pass_fail, percent, letter_grade, gpa_scale, points, not_graded. Due date/time in ISO 8601 format (e.g. 2014-10-21T18:48:00Z). Whether to omit this assignment from the student's final grade. Number of submission attempts allowed (-1 for unlimited). ### Response ```json { "$defs": { "AssignmentSummary": { "additionalProperties": false, "description": "A Canvas assignment.", "properties": { "id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "due_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Due At" }, "points_possible": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Points Possible" }, "grading_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Grading Type" }, "submission_types": { "items": { "type": "string" }, "title": "Submission Types", "type": "array" }, "course_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Course Id" }, "allowed_attempts": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Allowed Attempts" }, "omit_from_final_grade": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Omit From Final Grade" } }, "title": "AssignmentSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "assignment": { "anyOf": [ { "$ref": "#/$defs/AssignmentSummary" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "UpdateAssignmentOutput", "type": "object" } ``` ## Limits & Quotas - **Rate limits**: Canvas enforces per-user rate limits (typically 700 requests per 10 minutes for the default configuration, varies by institution). - **Pagination**: List endpoints may return paginated results; current implementation fetches the first page. - **Error model**: Non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. ## Links - [Instructure Canvas LMS](https://www.instructure.com/canvas) --- # Intercom Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/intercom Customer communication platform for support, engagement, and marketing automation. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/intercom/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Intercom logo Intercom logo ## Overview Add **Intercom** to any ModuleX agent or workflow. Customer-communication integration for Intercom: contacts (CRUD + search), notes, tags, admins, conversations (CRUD + search), and inbound/outbound messaging. All against `api.intercom.io` with the `Intercom-Version: 2.12` header on every request. **Categories**: Customer Support · CRM & Customer · Communication · Automation · Development · **Auth**: Access Token · **Actions**: 13 ## Authentication ### Access Token Use your Intercom Access Token for direct authentication #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Access Token | Your Intercom Access Token | Yes | `dG9rOjxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | | API Base URL | Intercom API base URL (optional — leave empty for https://api.intercom.io US default). Set to https://api.eu.intercom.io for EU workspaces or https://api.au.intercom.io for Australia. | No | `https://api.intercom.io` | ## Available Actions ### Parameters The unique identifier for the contact ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "GetContactOutput", "type": "object" } ``` ### Parameters Field to search by (email, name, phone, role, external_id, etc.) (Default: `email`) Query operator (=, !=, >, <, ~, !~, IN, NIN) (Default: `=`) Value to search for Results per page (max 150) (Default: `50`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "contacts": { "items": { "additionalProperties": true, "type": "object" }, "title": "Contacts", "type": "array" }, "total_count": { "default": 0, "title": "Total Count", "type": "integer" }, "pages": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Pages" } }, "required": [ "success" ], "title": "SearchContactsOutput", "type": "object" } ``` ### Parameters The contact's email address Role of the contact ('user' or 'lead') A unique identifier from your system The contact's phone number The contact's name URL of the avatar image Whether the contact is unsubscribed from emails Custom attributes for the contact ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "action_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Action Type" }, "contact": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Contact" } }, "required": [ "success" ], "title": "UpsertContactOutput", "type": "object" } ``` ### Parameters The contact's unique Intercom identifier The note content (supports HTML) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "CreateNoteOutput", "type": "object" } ``` ### Parameters The contact's unique Intercom identifier The tag's unique identifier ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "AddTagToContactOutput", "type": "object" } ``` ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "ListTagsOutput", "type": "object" } ``` ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "ListAdminsOutput", "type": "object" } ``` ### Parameters The conversation's unique identifier ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "GetConversationOutput", "type": "object" } ``` ### Parameters Conversations per page (max 150) (Default: `20`) Pagination cursor from previous response ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "conversations": { "items": { "additionalProperties": true, "type": "object" }, "title": "Conversations", "type": "array" }, "pages": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Pages" } }, "required": [ "success" ], "title": "ListConversationsOutput", "type": "object" } ``` ### Parameters Field to search by (created_at, state, source.type, etc.) (Default: `created_at`) Query operator (=, !=, >, <, IN, NIN) (Default: `>`) Value to search for (timestamp for date fields) Results per page (max 150) (Default: `20`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "conversations": { "items": { "additionalProperties": true, "type": "object" }, "title": "Conversations", "type": "array" }, "total_count": { "default": 0, "title": "Total Count", "type": "integer" }, "pages": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Pages" } }, "required": [ "success" ], "title": "SearchConversationsOutput", "type": "object" } ``` ### Parameters The contact's unique Intercom identifier The message content ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "SendIncomingMessageOutput", "type": "object" } ``` ### Parameters The admin's unique identifier The contact's unique identifier Recipient type ('user' or 'lead') (Default: `user`) Message type ('in_app' or 'email') (Default: `in_app`) Subject/title of the message Message content (HTML and plaintext supported) Template style ('plain' or 'personal') (Default: `personal`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "SendMessageToContactOutput", "type": "object" } ``` ### Parameters The conversation's unique identifier Type of reply ('user' or 'admin') Message type ('comment' or 'note', note only for admin) (Default: `comment`) The reply content Admin ID (required if reply_type is 'admin') Intercom user ID (for user reply) List of image URLs to attach (max 10) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "ReplyToConversationOutput", "type": "object" } ``` ## Limits & Quotas - `per_page` is clamped at 150 (Intercom's max). - `reply_to_conversation` allows up to 10 attachment URLs (Intercom's max). - Failures (non-2xx, exceptions) surface as `success=False` + `error`. ## Related integrations ## Links - [Intercom](https://www.intercom.com) --- # Jina AI Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/jina-ai AI-powered search foundation with embeddings, reranking, web reading, search, deep research, text segmentation, and zero-shot classification capabilities. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/jina_ai/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Jina AI logo Jina AI logo ## Overview Add **Jina AI** to any ModuleX agent or workflow. AI-powered search foundation tools against Jina AI's REST surfaces: embeddings, rerank, web reader, web search, deep search, text segmentation, and zero-shot classification. Each capability lives on a different subdomain — the tool code routes to the correct one per action. **Categories**: Web Search & Scraping · Ai · Embeddings · Nlp · Research · **Auth**: API Key, ModuleX Managed Key · **Actions**: 7 ## Authentication ### API Key Authentication Authenticate using your Jina AI API key. Get your free API key at https://jina.ai/?sui=apikey #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Jina AI API Key | Your Jina AI API key for authentication. Get your free key at https://jina.ai/?sui=apikey | Yes | `jina_xxxxxxxxxxxxxxxxxxxxxxxxxxxx` | ### ModuleX Managed Key Use ModuleX's managed API keys with usage tracked against your weekly credit limit ## Available Actions ### Parameters Array of input strings or objects with 'text', 'image', or 'pdf' keys Embedding model to use (Default: `jina-embeddings-v3`) Task optimization type Truncate embeddings to specified size Enable late chunking mode (Default: `false`) Auto-truncate content beyond max length (Default: `false`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "GenerateEmbeddingsOutput", "type": "object" } ``` ### Parameters Search query to rank documents against Documents to rerank (strings or objects) Reranker model to use (Default: `jina-reranker-v2-base-multilingual`) Number of top results to return Include document text in response (Default: `true`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "RerankDocumentsOutput", "type": "object" } ``` ### Parameters The URL to read and parse Output format (Default: `markdown`) CSS selectors to focus on specific elements CSS selectors to exclude from page CSS selectors to wait for before returning Gather all links at the end of response (Default: `false`) Gather all images at the end of response (Default: `false`) Maximum time in seconds to wait for webpage to load Bypass cache for fresh retrieval (Default: `false`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" }, "content": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Content" }, "links": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Links" }, "images": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Images" }, "usage": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Usage" } }, "required": [ "success" ], "title": "ReadWebpageOutput", "type": "object" } ``` ### Parameters The search query to execute Limit search to specific domain (e.g. 'github.com') Output format (Default: `markdown`) Maximum number of results to return Two-letter country code for localized results Two-letter language code for search Include links summary (Default: `false`) Include images summary (Default: `false`) Bypass cache for real-time data (Default: `false`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" }, "results": { "items": { "additionalProperties": true, "type": "object" }, "title": "Results", "type": "array" }, "count": { "default": 0, "title": "Count", "type": "integer" } }, "required": [ "success" ], "title": "WebSearchOutput", "type": "object" } ``` ### Parameters Research query or question to investigate Reasoning effort level (low, medium, high) (Default: `medium`) Maximum tokens for the process (overrides reasoning_effort) Maximum retries for problem solving Force deeper search even for simple queries (Default: `false`) Maximum URLs in final answer Domains to prioritize for content retrieval Domains to exclude from content retrieval Only retrieve content from these domains ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "DeepSearchOutput", "type": "object" } ``` ### Parameters Text content to segment Tokenizer to use (Default: `cl100k_base`) Include tokens and IDs in response (Default: `false`) Segment into semantic chunks (Default: `true`) Maximum characters per chunk (Default: `1000`) Return only first N tokens (exclusive with tail) Return only last N tokens (exclusive with head) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" }, "num_tokens": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Num Tokens" }, "num_chunks": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Num Chunks" }, "chunks": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Chunks" }, "chunk_positions": { "anyOf": [ { "items": { "items": { "type": "integer" }, "type": "array" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Chunk Positions" }, "tokens": { "anyOf": [ { "items": {}, "type": "array" }, { "type": "null" } ], "default": null, "title": "Tokens" }, "tokenizer": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Tokenizer" }, "usage": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Usage" } }, "required": [ "success" ], "title": "SegmentTextOutput", "type": "object" } ``` ### Parameters Array of texts or image objects to classify List of classification categories Classification model (Default: `jina-embeddings-v3`) Existing classifier ID to reuse ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" }, "classifications": { "items": { "additionalProperties": true, "type": "object" }, "title": "Classifications", "type": "array" }, "usage": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Usage" } }, "required": [ "success" ], "title": "ClassifyOutput", "type": "object" } ``` ## Limits & Quotas - Per-endpoint subdomains: `api.jina.ai/v1/embeddings`, `api.jina.ai/v1/rerank`, `r.jina.ai/`, `s.jina.ai/`, `deepsearch.jina.ai/v1/chat/completions`, `segment.jina.ai/`, `api.jina.ai/v1/classify`. - Reader and Search expect their configuration via `X-*` request headers (return format, target selectors, cache bypass, etc.) — preserved verbatim from the legacy implementation. - Deep search timeout is 300s (the API genuinely takes that long for high-effort queries); reader/search use 120s; everything else uses 60s. - Standard plan rate limits (from the legacy JSON's `rate_limits` block): embeddings/rerank 500 RPM, reader 200 RPM, search 40 RPM, classify 20 RPM, segment 200 RPM. Premium is 4-10× higher. ## Related integrations ## Links - [Jina AI](https://jina.ai) --- # Jira Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/jira Atlassian Jira Cloud project tracking and issue management platform {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/jira/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Jira logo ## Overview Add **Jira** to any ModuleX agent or workflow. Atlassian Jira Cloud project tracking and issue management integration against the Jira REST API v3 (`api.atlassian.com/ex/jira/{cloudId}/rest/api/3`) and Jira Agile API (`api.atlassian.com/ex/jira/{cloudId}/rest/agile/1.0`). **Categories**: Project & Task Management · Project Management · Developer Tools & Infrastructure · Productivity & Collaboration · **Auth**: OAuth2 · **Actions**: 38 ## Authentication ### OAuth2 Authentication Connect using Atlassian OAuth2 (recommended) #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Client ID | Atlassian OAuth App Client ID | Yes | `-` | | Client Secret | Atlassian OAuth App Client Secret | Yes | `-` | #### OAuth Configuration - **Authorization URL**: `https://auth.atlassian.com/authorize` - **Token URL**: `https://auth.atlassian.com/oauth/token` - **Scopes**: `read:jira-work`, `write:jira-work`, `read:jira-user`, `manage:jira-project`, `manage:jira-configuration`, `offline_access` ## Available Actions ### Parameters The cloud ID of the Jira site The ID or key of the issue A file URL to attach to the issue ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "filename": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Filename" }, "mime_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Mime Type" }, "size": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Size" }, "self_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Self Url" } }, "required": [ "success" ], "title": "AddAttachmentToIssueOutput", "type": "object" } ``` ### Parameters The cloud ID of the Jira site The ID or key of the issue The comment text in plain text (converted to Atlassian Document Format) The comment text in Atlassian Document Format (JSON object). Overrides comment if both provided The group or role to which this comment is visible A JSON array of comment properties Extra properties of any type to include Use 'renderedBody' to get comment body rendered in HTML ### Response ```json { "$defs": { "CommentSummary": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "self_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Self Url" }, "body": { "anyOf": [ {}, { "type": "null" } ], "default": null, "title": "Body" }, "author": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Author" }, "created": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created" }, "updated": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated" } }, "title": "CommentSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "comment": { "anyOf": [ { "$ref": "#/$defs/CommentSummary" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "AddCommentToIssueOutput", "type": "object" } ``` ### Parameters The cloud ID of the Jira site The ID or key of the issue An array of file URLs to attach to the issue. Each element is a string URL ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "attachments": { "items": { "additionalProperties": true, "type": "object" }, "title": "Attachments", "type": "array" } }, "required": [ "success" ], "title": "AddMultipleAttachmentsToIssueOutput", "type": "object" } ``` ### Parameters The cloud ID of the Jira site The ID or key of the issue The account ID of the user to add as watcher ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" } }, "required": [ "success" ], "title": "AddWatcherToIssueOutput", "type": "object" } ``` ### Parameters The cloud ID of the Jira site The ID or key of the issue The account ID of the user to assign ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" } }, "required": [ "success" ], "title": "AssignIssueOutput", "type": "object" } ``` ### Parameters The cloud ID of the Jira site A list of issue IDs to check against the JQL queries. Elements are integers A list of JQL query strings to check the issues against ### Response ```json { "$defs": { "JqlMatchResult": { "additionalProperties": false, "properties": { "issue_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Issue Id" }, "matched_jqls": { "items": { "type": "string" }, "title": "Matched Jqls", "type": "array" } }, "title": "JqlMatchResult", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "matches": { "items": { "$ref": "#/$defs/JqlMatchResult" }, "title": "Matches", "type": "array" } }, "required": [ "success" ], "title": "CheckIssuesAgainstJqlOutput", "type": "object" } ``` ### Parameters The cloud ID of the Jira site The JQL query to count issues. Must be bounded (e.g. project = ABC) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Count" } }, "required": [ "success" ], "title": "CountIssuesUsingJqlOutput", "type": "object" } ``` ### Parameters The cloud ID of the Jira site The ID of the custom field The ID of the field context ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "options": { "items": { "additionalProperties": true, "type": "object" }, "title": "Options", "type": "array" } }, "required": [ "success" ], "title": "CreateCustomFieldOptionsContextOutput", "type": "object" } ``` ### Parameters The cloud ID of the Jira site The ID of the board the sprint will be created on The name of the sprint The goal of the sprint The start date in ISO 8601 format (e.g. 2024-01-01T00:00:00.000Z) The end date in ISO 8601 format (e.g. 2024-01-15T00:00:00.000Z) ### Response ```json { "$defs": { "SprintSummary": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "State" }, "start_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Start Date" }, "end_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "End Date" }, "complete_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Complete Date" }, "goal": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Goal" }, "self_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Self Url" } }, "title": "SprintSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "sprint": { "anyOf": [ { "$ref": "#/$defs/SprintSummary" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "CreateFutureSprintOutput", "type": "object" } ``` ### Parameters The cloud ID of the Jira site The project ID The issue type ID Whether the project is added to the user's Recently viewed project list Additional issue history details Details of issue properties to add or update (JSON array) A map of field name to operations list for issue screen fields Extra fields to include in the issue body (e.g. summary, description, labels, priority) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "key": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Key" }, "self_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Self Url" } }, "required": [ "success" ], "title": "CreateIssueOutput", "type": "object" } ``` ### Parameters The cloud ID of the Jira site The project ID The unique name of the version. Max 255 characters The description of the version Indicates that the version is archived The start date (yyyy-mm-dd) The release date (yyyy-mm-dd) Expand options: operations, issuesstatus ### Response ```json { "$defs": { "VersionSummary": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "archived": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Archived" }, "released": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Released" }, "start_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Start Date" }, "release_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Release Date" }, "self_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Self Url" } }, "title": "VersionSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "version": { "anyOf": [ { "$ref": "#/$defs/VersionSummary" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "CreateVersionOutput", "type": "object" } ``` ### Parameters The cloud ID of the Jira site The project ID or key Whether this project is placed in the Jira recycle bin for recovery ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" } }, "required": [ "success" ], "title": "DeleteProjectOutput", "type": "object" } ``` ### Parameters The cloud ID of the Jira site Returns the user's most recently accessed projects (max 20) Issue properties to include (JSON array) Expand options: description, issueTypes, lead, projectKeys ### Response ```json { "$defs": { "ProjectSummary": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "key": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Key" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "project_type_key": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Project Type Key" }, "self_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Self Url" } }, "title": "ProjectSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "projects": { "items": { "$ref": "#/$defs/ProjectSummary" }, "title": "Projects", "type": "array" }, "total": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Total" } }, "required": [ "success" ], "title": "GetAllProjectsOutput", "type": "object" } ``` ### Parameters The cloud ID of the Jira site The ID of the board ### Response ```json { "$defs": { "BoardSummary": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "board_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Board Type" }, "self_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Self Url" } }, "title": "BoardSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "board": { "anyOf": [ { "$ref": "#/$defs/BoardSummary" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "GetBoardOutput", "type": "object" } ``` ### Response ```json { "$defs": { "CloudSite": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "scopes": { "items": { "type": "string" }, "title": "Scopes", "type": "array" }, "avatar_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Avatar Url" } }, "title": "CloudSite", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "sites": { "items": { "$ref": "#/$defs/CloudSite" }, "title": "Sites", "type": "array" } }, "required": [ "success" ], "title": "GetCloudIdOutput", "type": "object" } ``` ### Parameters The cloud ID of the Jira site ### Response ```json { "$defs": { "UserSummary": { "additionalProperties": false, "properties": { "account_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Account Id" }, "display_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Display Name" }, "email_address": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email Address" }, "active": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Active" }, "avatar_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Avatar Url" }, "account_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Account Type" } }, "title": "UserSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "user": { "anyOf": [ { "$ref": "#/$defs/UserSummary" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "GetCurrentUserOutput", "type": "object" } ``` ### Parameters The cloud ID of the Jira site The ID or key of the issue Comma-separated list of fields to return. *all or *navigable for all or navigable fields Whether fields are referenced by keys rather than IDs Issue properties to include (comma-separated or *all) Whether project is added to Recently viewed Expand options: renderedFields, names, schema, transitions, editmeta, changelog, versionedRepresentations ### Response ```json { "$defs": { "IssueSummary": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "key": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Key" }, "self_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Self Url" }, "summary": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Summary" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "issue_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Issue Type" }, "priority": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Priority" }, "assignee": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Assignee" }, "reporter": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Reporter" }, "created": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created" }, "updated": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated" }, "fields": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Fields" } }, "title": "IssueSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "issue": { "anyOf": [ { "$ref": "#/$defs/IssueSummary" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "GetIssueOutput", "type": "object" } ``` ### Parameters The cloud ID of the Jira site A string to match against issue text fields A JQL query defining a list of issues to search within The key of an issue to exclude from results The ID of a project that results must belong to Whether to include subtasks in suggestions Whether to include the parent issue when currentIssueKey is a subtask ### Response ```json { "$defs": { "IssueSummary": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "key": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Key" }, "self_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Self Url" }, "summary": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Summary" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "issue_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Issue Type" }, "priority": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Priority" }, "assignee": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Assignee" }, "reporter": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Reporter" }, "created": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created" }, "updated": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated" }, "fields": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Fields" } }, "title": "IssueSummary", "type": "object" }, "SuggestionSection": { "additionalProperties": false, "properties": { "label": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Label" }, "issues": { "items": { "$ref": "#/$defs/IssueSummary" }, "title": "Issues", "type": "array" } }, "title": "SuggestionSection", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "sections": { "items": { "$ref": "#/$defs/SuggestionSection" }, "title": "Sections", "type": "array" } }, "required": [ "success" ], "title": "GetIssuePickerSuggestionsOutput", "type": "object" } ``` ### Parameters The cloud ID of the Jira site The project ID to filter issue types ### Response ```json { "$defs": { "IssueTypeSummary": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "subtask": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Subtask" }, "self_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Self Url" } }, "title": "IssueTypeSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "issue_types": { "items": { "$ref": "#/$defs/IssueTypeSummary" }, "title": "Issue Types", "type": "array" } }, "required": [ "success" ], "title": "GetIssueTypesOutput", "type": "object" } ``` ### Parameters The cloud ID of the Jira site The ID of the sprint ### Response ```json { "$defs": { "SprintSummary": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "State" }, "start_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Start Date" }, "end_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "End Date" }, "complete_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Complete Date" }, "goal": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Goal" }, "self_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Self Url" } }, "title": "SprintSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "sprint": { "anyOf": [ { "$ref": "#/$defs/SprintSummary" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "GetSprintOutput", "type": "object" } ``` ### Parameters The cloud ID of the Jira site The ID of the long-running async task ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "progress": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Progress" }, "result": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Result" }, "task_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Task Id" } }, "required": [ "success" ], "title": "GetTaskOutput", "type": "object" } ``` ### Parameters The cloud ID of the Jira site The ID or key of the issue The ID of a specific transition to retrieve Whether transitions with Hide From User Condition are included Whether details of failing-condition transitions are included Whether transitions are sorted by ops-bar sequence then category Expand options: transitions.fields ### Response ```json { "$defs": { "TransitionSummary": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "to_status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "To Status" }, "has_screen": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Has Screen" } }, "title": "TransitionSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "transitions": { "items": { "$ref": "#/$defs/TransitionSummary" }, "title": "Transitions", "type": "array" } }, "required": [ "success" ], "title": "GetTransitionsOutput", "type": "object" } ``` ### Parameters The cloud ID of the Jira site The account ID of the user Expand options: groups, applicationRoles ### Response ```json { "$defs": { "UserSummary": { "additionalProperties": false, "properties": { "account_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Account Id" }, "display_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Display Name" }, "email_address": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email Address" }, "active": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Active" }, "avatar_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Avatar Url" }, "account_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Account Type" } }, "title": "UserSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "user": { "anyOf": [ { "$ref": "#/$defs/UserSummary" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "GetUserOutput", "type": "object" } ``` ### Parameters The cloud ID of the Jira site Filter for a name or term ### Response ```json { "$defs": { "UserSummary": { "additionalProperties": false, "properties": { "account_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Account Id" }, "display_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Display Name" }, "email_address": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email Address" }, "active": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Active" }, "avatar_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Avatar Url" }, "account_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Account Type" } }, "title": "UserSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "users": { "items": { "$ref": "#/$defs/UserSummary" }, "title": "Users", "type": "array" } }, "required": [ "success" ], "title": "GetUsersOutput", "type": "object" } ``` ### Parameters The cloud ID of the Jira site The ID of the board The starting index of the returned issues. Base index: 0 The maximum number of issues to return Filters results using a JQL query Comma-separated list of fields to return per issue Expand options for additional information ### Response ```json { "$defs": { "IssueSummary": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "key": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Key" }, "self_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Self Url" }, "summary": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Summary" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "issue_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Issue Type" }, "priority": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Priority" }, "assignee": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Assignee" }, "reporter": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Reporter" }, "created": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created" }, "updated": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated" }, "fields": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Fields" } }, "title": "IssueSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "issues": { "items": { "$ref": "#/$defs/IssueSummary" }, "title": "Issues", "type": "array" }, "total": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Total" }, "start_at": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Start At" }, "max_results": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Max Results" } }, "required": [ "success" ], "title": "ListBoardIssuesOutput", "type": "object" } ``` ### Parameters The cloud ID of the Jira site The starting index of the returned boards. Base index: 0 The maximum number of boards to return Filters by board type: scrum, kanban, simple Filters results to boards matching this name Filters results to boards relevant to a project ### Response ```json { "$defs": { "BoardSummary": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "board_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Board Type" }, "self_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Self Url" } }, "title": "BoardSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "boards": { "items": { "$ref": "#/$defs/BoardSummary" }, "title": "Boards", "type": "array" }, "total": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Total" }, "start_at": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Start At" }, "max_results": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Max Results" } }, "required": [ "success" ], "title": "ListBoardsOutput", "type": "object" } ``` ### Parameters The cloud ID of the Jira site The ID of the board The ID of the epic The starting index of the returned issues. Base index: 0 The maximum number of issues to return Filters results using a JQL query Comma-separated list of fields to return per issue Expand options for additional information ### Response ```json { "$defs": { "IssueSummary": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "key": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Key" }, "self_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Self Url" }, "summary": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Summary" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "issue_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Issue Type" }, "priority": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Priority" }, "assignee": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Assignee" }, "reporter": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Reporter" }, "created": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created" }, "updated": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated" }, "fields": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Fields" } }, "title": "IssueSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "issues": { "items": { "$ref": "#/$defs/IssueSummary" }, "title": "Issues", "type": "array" }, "total": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Total" }, "start_at": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Start At" }, "max_results": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Max Results" } }, "required": [ "success" ], "title": "ListEpicIssuesOutput", "type": "object" } ``` ### Parameters The cloud ID of the Jira site The ID of the board The starting index of the returned epics. Base index: 0 The maximum number of epics to return Filter to epics that are done or not done ### Response ```json { "$defs": { "EpicSummary": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Id" }, "key": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Key" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "summary": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Summary" }, "done": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Done" }, "self_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Self Url" } }, "title": "EpicSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "epics": { "items": { "$ref": "#/$defs/EpicSummary" }, "title": "Epics", "type": "array" }, "total": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Total" }, "start_at": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Start At" }, "max_results": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Max Results" } }, "required": [ "success" ], "title": "ListEpicsOutput", "type": "object" } ``` ### Parameters The cloud ID of the Jira site The ID or key of the issue Order results by a field. Valid: created, +created, -created Expand options: renderedBody ### Response ```json { "$defs": { "CommentSummary": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "self_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Self Url" }, "body": { "anyOf": [ {}, { "type": "null" } ], "default": null, "title": "Body" }, "author": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Author" }, "created": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created" }, "updated": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated" } }, "title": "CommentSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "comments": { "items": { "$ref": "#/$defs/CommentSummary" }, "title": "Comments", "type": "array" }, "total": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Total" }, "start_at": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Start At" }, "max_results": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Max Results" } }, "required": [ "success" ], "title": "ListIssueCommentsOutput", "type": "object" } ``` ### Parameters The cloud ID of the Jira site ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "labels": { "items": { "type": "string" }, "title": "Labels", "type": "array" }, "total": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Total" } }, "required": [ "success" ], "title": "ListLabelsOptionsOutput", "type": "object" } ``` ### Parameters The cloud ID of the Jira site The ID of the sprint The starting index of the returned issues. Base index: 0 The maximum number of issues to return Filters results using a JQL query Comma-separated list of fields to return per issue Expand options for additional information ### Response ```json { "$defs": { "IssueSummary": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "key": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Key" }, "self_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Self Url" }, "summary": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Summary" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "issue_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Issue Type" }, "priority": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Priority" }, "assignee": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Assignee" }, "reporter": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Reporter" }, "created": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created" }, "updated": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated" }, "fields": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Fields" } }, "title": "IssueSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "issues": { "items": { "$ref": "#/$defs/IssueSummary" }, "title": "Issues", "type": "array" }, "total": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Total" }, "start_at": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Start At" }, "max_results": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Max Results" } }, "required": [ "success" ], "title": "ListSprintIssuesOutput", "type": "object" } ``` ### Parameters The cloud ID of the Jira site The ID of the board The starting index of the returned sprints. Base index: 0 The maximum number of sprints to return Filter by sprint states: future, active, closed (comma-separated) ### Response ```json { "$defs": { "SprintSummary": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "State" }, "start_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Start Date" }, "end_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "End Date" }, "complete_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Complete Date" }, "goal": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Goal" }, "self_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Self Url" } }, "title": "SprintSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "sprints": { "items": { "$ref": "#/$defs/SprintSummary" }, "title": "Sprints", "type": "array" }, "total": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Total" }, "start_at": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Start At" }, "max_results": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Max Results" } }, "required": [ "success" ], "title": "ListSprintsOutput", "type": "object" } ``` ### Parameters The cloud ID of the Jira site The ID of the sprint The IDs or keys of the issues to move to the sprint. Each element is a string ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" } }, "required": [ "success" ], "title": "MoveIssuesToSprintOutput", "type": "object" } ``` ### Parameters The cloud ID of the Jira site The JQL query to search for issues Maximum number of issues to return (default 50, max 5000) Comma-separated list of fields to return Expand options: renderedFields, names, schema, transitions, editmeta, changelog, versionedRepresentations Comma-separated list of issue properties to include (max 5) Reference fields by key rather than ID Fail early if not all field data can be retrieved ### Response ```json { "$defs": { "IssueSummary": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "key": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Key" }, "self_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Self Url" }, "summary": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Summary" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "issue_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Issue Type" }, "priority": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Priority" }, "assignee": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Assignee" }, "reporter": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Reporter" }, "created": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created" }, "updated": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated" }, "fields": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Fields" } }, "title": "IssueSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "issues": { "items": { "$ref": "#/$defs/IssueSummary" }, "title": "Issues", "type": "array" }, "total": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Total" }, "start_at": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Start At" }, "max_results": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Max Results" } }, "required": [ "success" ], "title": "SearchIssuesWithJqlOutput", "type": "object" } ``` ### Parameters The cloud ID of the Jira site The JQL query to search for issues Maximum number of items per page Token for pagination from a previous response Fields to return per issue. Elements are strings. Example: ['summary', 'status'] Expand options: renderedFields, names, schema, transitions, editmeta, changelog, versionedRepresentations Issue property keys to include (max 5). Elements are strings Reference fields by key rather than ID Issue IDs/keys for read-after-write consistency. Elements are strings ### Response ```json { "$defs": { "IssueSummary": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "key": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Key" }, "self_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Self Url" }, "summary": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Summary" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "issue_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Issue Type" }, "priority": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Priority" }, "assignee": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Assignee" }, "reporter": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Reporter" }, "created": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created" }, "updated": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated" }, "fields": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Fields" } }, "title": "IssueSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "issues": { "items": { "$ref": "#/$defs/IssueSummary" }, "title": "Issues", "type": "array" }, "total": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Total" }, "next_page_token": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Next Page Token" } }, "required": [ "success" ], "title": "SearchIssuesWithJqlPostOutput", "type": "object" } ``` ### Parameters The cloud ID of the Jira site The ID or key of the issue The transition ID to perform List of issue screen fields to update during transition Operations to perform on issue screen fields Additional issue history details Issue properties to add or update (JSON) Extra properties of any type ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" } }, "required": [ "success" ], "title": "TransitionIssueOutput", "type": "object" } ``` ### Parameters The cloud ID of the Jira site The ID or key of the issue The ID of the comment to update The comment text in Atlassian Document Format (JSON). Overrides comment if both provided The comment text (plain text) The group or role to which this comment is visible Comment properties (JSON array) Extra properties of any type Whether users are notified when comment is updated Expand options: renderedBody ### Response ```json { "$defs": { "CommentSummary": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "self_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Self Url" }, "body": { "anyOf": [ {}, { "type": "null" } ], "default": null, "title": "Body" }, "author": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Author" }, "created": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created" }, "updated": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated" } }, "title": "CommentSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "comment": { "anyOf": [ { "$ref": "#/$defs/CommentSummary" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "UpdateCommentOutput", "type": "object" } ``` ### Parameters The cloud ID of the Jira site The project ID The ID or key of the issue The issue type ID Whether a notification email is sent to watchers Override screen security to enable hidden fields Override editable flag to enable uneditable fields The ID of the transition to undertake Whether the transition is looped Additional issue history details Issue properties to add or update (JSON array) A map of field name to operations list Extra fields to update (e.g. summary, description) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" } }, "required": [ "success" ], "title": "UpdateIssueOutput", "type": "object" } ``` ## Limits & Quotas - **Standard rate limit**: Jira Cloud REST API allows approximately 100 requests per 10 seconds per user per app. - **Concurrent requests**: Maximum 10 concurrent requests per user per app. - **Search (JQL)**: Maximum 5000 results per search request. - **Attachments**: Maximum 10 MB per file (default; configurable by site admin). - **Error model**: non-2xx responses raise `httpx.HTTPStatusError` (Pattern A). The error includes the status code and response body for debugging. ## Related integrations ## Links - [Jira](https://www.atlassian.com/software/jira) --- # Kalshi Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/kalshi Access Kalshi prediction markets and trade event contracts: retrieve markets, events, series, orderbooks, trades, candlesticks, account balance, positions, orders, fills, settlements, exchange status, and place, cancel, or amend orders. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/kalshi/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Kalshi logo ## Overview Add **Kalshi** to any ModuleX agent or workflow. Access Kalshi prediction markets and trade event contracts against the Kalshi Trade API v2 (`api.elections.kalshi.com/trade-api/v2`). Retrieve markets, events, series, orderbooks, trades, and candlesticks; manage your balance, positions, orders, fills, and settlements; check exchange status; and place, cancel, or amend orders. **Categories**: Finance & Payments · Prediction Markets · Data Analytics · **Auth**: API Key · **Actions**: 22 ## Authentication ### API Key Authentication Authenticate with a Kalshi API key id and its RSA private key. Each request is signed with the private key (RSA-PSS over SHA-256); the signing material never leaves the runtime. Sign in at https://kalshi.com and open Account → API Keys. Create a new API key; download the RSA private key (PEM) and copy the Key ID. Paste the Key ID into KALSHI_KEY_ID below. Paste the full PEM private key into KALSHI_PRIVATE_KEY below. #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | API Key ID | Your Kalshi API Key ID from Account → API Keys | Yes | `-` | | RSA Private Key (PEM) | Your Kalshi RSA private key in PEM format | Yes | `-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----` | ## Available Actions ### Parameters Filter by market status: "unopened", "open", "closed", "settled" Filter by series ticker Filter by event ticker Min created timestamp (Unix seconds) Max created timestamp (Unix seconds) Min updated timestamp (Unix seconds) Min close timestamp (Unix seconds) Max close timestamp (Unix seconds) Min settled timestamp (Unix seconds) Max settled timestamp (Unix seconds) Comma-separated list of market tickers Multivariate event filter: "only" or "exclude" Number of results to return (1-1000) Pagination cursor from a previous response ### Response ```json { "$defs": { "Market": { "additionalProperties": false, "description": "A single market row (Trade API v2 shape).", "properties": { "ticker": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Ticker" }, "event_ticker": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Event Ticker" }, "market_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Market Type" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "subtitle": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Subtitle" }, "yes_sub_title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Yes Sub Title" }, "no_sub_title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "No Sub Title" }, "open_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Open Time" }, "close_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Close Time" }, "expected_expiration_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Expected Expiration Time" }, "expiration_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Expiration Time" }, "latest_expiration_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Latest Expiration Time" }, "settlement_timer_seconds": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Settlement Timer Seconds" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "response_price_units": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Response Price Units" }, "notional_value": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Notional Value" }, "tick_size": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Tick Size" }, "yes_bid": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Yes Bid" }, "yes_ask": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Yes Ask" }, "no_bid": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "No Bid" }, "no_ask": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "No Ask" }, "last_price": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Last Price" }, "previous_yes_bid": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Previous Yes Bid" }, "previous_yes_ask": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Previous Yes Ask" }, "previous_price": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Previous Price" }, "volume": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Volume" }, "volume_24h": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Volume 24H" }, "liquidity": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Liquidity" }, "open_interest": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Open Interest" }, "result": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Result" }, "cap_strike": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Cap Strike" }, "floor_strike": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Floor Strike" }, "can_close_early": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Can Close Early" }, "expiration_value": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Expiration Value" }, "category": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Category" }, "risk_limit_cents": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Risk Limit Cents" }, "strike_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Strike Type" }, "rules_primary": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Rules Primary" }, "rules_secondary": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Rules Secondary" }, "settlement_source_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Settlement Source Url" }, "custom_strike": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Custom Strike" }, "underlying": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Underlying" }, "settlement_value": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Settlement Value" }, "cfd_contract_size": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Cfd Contract Size" }, "yes_fee_fp": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Yes Fee Fp" }, "no_fee_fp": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "No Fee Fp" }, "last_price_fp": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Last Price Fp" }, "yes_bid_fp": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Yes Bid Fp" }, "yes_ask_fp": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Yes Ask Fp" }, "no_bid_fp": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "No Bid Fp" }, "no_ask_fp": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "No Ask Fp" } }, "title": "Market", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "markets": { "items": { "$ref": "#/$defs/Market" }, "title": "Markets", "type": "array" }, "cursor": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Cursor" } }, "required": [ "success" ], "title": "GetMarketsOutput", "type": "object" } ``` ### Parameters Market ticker identifier (e.g., 'KXBTC-24DEC31') ### Response ```json { "$defs": { "Market": { "additionalProperties": false, "description": "A single market row (Trade API v2 shape).", "properties": { "ticker": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Ticker" }, "event_ticker": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Event Ticker" }, "market_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Market Type" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "subtitle": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Subtitle" }, "yes_sub_title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Yes Sub Title" }, "no_sub_title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "No Sub Title" }, "open_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Open Time" }, "close_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Close Time" }, "expected_expiration_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Expected Expiration Time" }, "expiration_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Expiration Time" }, "latest_expiration_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Latest Expiration Time" }, "settlement_timer_seconds": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Settlement Timer Seconds" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "response_price_units": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Response Price Units" }, "notional_value": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Notional Value" }, "tick_size": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Tick Size" }, "yes_bid": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Yes Bid" }, "yes_ask": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Yes Ask" }, "no_bid": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "No Bid" }, "no_ask": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "No Ask" }, "last_price": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Last Price" }, "previous_yes_bid": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Previous Yes Bid" }, "previous_yes_ask": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Previous Yes Ask" }, "previous_price": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Previous Price" }, "volume": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Volume" }, "volume_24h": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Volume 24H" }, "liquidity": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Liquidity" }, "open_interest": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Open Interest" }, "result": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Result" }, "cap_strike": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Cap Strike" }, "floor_strike": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Floor Strike" }, "can_close_early": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Can Close Early" }, "expiration_value": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Expiration Value" }, "category": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Category" }, "risk_limit_cents": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Risk Limit Cents" }, "strike_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Strike Type" }, "rules_primary": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Rules Primary" }, "rules_secondary": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Rules Secondary" }, "settlement_source_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Settlement Source Url" }, "custom_strike": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Custom Strike" }, "underlying": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Underlying" }, "settlement_value": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Settlement Value" }, "cfd_contract_size": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Cfd Contract Size" }, "yes_fee_fp": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Yes Fee Fp" }, "no_fee_fp": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "No Fee Fp" }, "last_price_fp": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Last Price Fp" }, "yes_bid_fp": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Yes Bid Fp" }, "yes_ask_fp": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Yes Ask Fp" }, "no_bid_fp": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "No Bid Fp" }, "no_ask_fp": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "No Ask Fp" } }, "title": "Market", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "market": { "anyOf": [ { "$ref": "#/$defs/Market" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "GetMarketOutput", "type": "object" } ``` ### Parameters Filter by event status: "open", "closed", "settled" Filter by series ticker Include nested markets: "true" or "false" Include milestones: "true" or "false" Min close timestamp (Unix seconds) Number of results to return (1-200) Pagination cursor from a previous response ### Response ```json { "$defs": { "Event": { "additionalProperties": false, "description": "An event row (Trade API v2 shape).", "properties": { "event_ticker": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Event Ticker" }, "series_ticker": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Series Ticker" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "sub_title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Sub Title" }, "mutually_exclusive": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Mutually Exclusive" }, "category": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Category" }, "collateral_return_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Collateral Return Type" }, "strike_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Strike Date" }, "strike_period": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Strike Period" }, "available_on_brokers": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Available On Brokers" }, "product_metadata": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Product Metadata" }, "markets": { "items": { "additionalProperties": true, "type": "object" }, "title": "Markets", "type": "array" } }, "title": "Event", "type": "object" }, "Milestone": { "additionalProperties": false, "description": "An event milestone (returned by get_events when requested).", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "category": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Category" }, "type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Type" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "start_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Start Date" }, "end_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "End Date" }, "notification_message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Notification Message" }, "primary_event_tickers": { "items": { "type": "string" }, "title": "Primary Event Tickers", "type": "array" }, "related_event_tickers": { "items": { "type": "string" }, "title": "Related Event Tickers", "type": "array" }, "last_updated_ts": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Updated Ts" } }, "title": "Milestone", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "events": { "items": { "$ref": "#/$defs/Event" }, "title": "Events", "type": "array" }, "milestones": { "items": { "$ref": "#/$defs/Milestone" }, "title": "Milestones", "type": "array" }, "cursor": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Cursor" } }, "required": [ "success" ], "title": "GetEventsOutput", "type": "object" } ``` ### Parameters Event ticker identifier Include nested markets: "true" or "false" ### Response ```json { "$defs": { "Event": { "additionalProperties": false, "description": "An event row (Trade API v2 shape).", "properties": { "event_ticker": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Event Ticker" }, "series_ticker": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Series Ticker" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "sub_title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Sub Title" }, "mutually_exclusive": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Mutually Exclusive" }, "category": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Category" }, "collateral_return_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Collateral Return Type" }, "strike_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Strike Date" }, "strike_period": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Strike Period" }, "available_on_brokers": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Available On Brokers" }, "product_metadata": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Product Metadata" }, "markets": { "items": { "additionalProperties": true, "type": "object" }, "title": "Markets", "type": "array" } }, "title": "Event", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "event": { "anyOf": [ { "$ref": "#/$defs/Event" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "GetEventOutput", "type": "object" } ``` ### Parameters Kalshi API Key ID (provided by the credential system) Kalshi RSA private key in PEM format (provided by the credential system) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "balance": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Balance" }, "portfolio_value": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Portfolio Value" }, "updated_ts": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Updated Ts" } }, "required": [ "success" ], "title": "GetBalanceOutput", "type": "object" } ``` ### Parameters Kalshi API Key ID (provided by the credential system) Kalshi RSA private key in PEM format (provided by the credential system) Filter by market ticker Filter by event ticker (max 10, comma-separated) Comma-separated non-zero fields: position, total_traded Subaccount identifier Number of results to return (1-1000) Pagination cursor from a previous response ### Response ```json { "$defs": { "EventPosition": { "additionalProperties": false, "properties": { "event_ticker": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Event Ticker" }, "event_exposure": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Event Exposure" }, "realized_pnl": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Realized Pnl" }, "total_cost": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Total Cost" } }, "title": "EventPosition", "type": "object" }, "MarketPosition": { "additionalProperties": false, "properties": { "ticker": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Ticker" }, "event_ticker": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Event Ticker" }, "event_title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Event Title" }, "market_title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Market Title" }, "position": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Position" }, "market_exposure": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Market Exposure" }, "realized_pnl": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Realized Pnl" }, "total_traded": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Total Traded" }, "resting_orders_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Resting Orders Count" }, "fees_paid": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Fees Paid" } }, "title": "MarketPosition", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "market_positions": { "items": { "$ref": "#/$defs/MarketPosition" }, "title": "Market Positions", "type": "array" }, "event_positions": { "items": { "$ref": "#/$defs/EventPosition" }, "title": "Event Positions", "type": "array" }, "cursor": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Cursor" } }, "required": [ "success" ], "title": "GetPositionsOutput", "type": "object" } ``` ### Parameters Kalshi API Key ID (provided by the credential system) Kalshi RSA private key in PEM format (provided by the credential system) Filter by market ticker Filter by event ticker (max 10, comma-separated) Filter by status: "resting", "canceled", "executed" Minimum timestamp filter (Unix timestamp) Maximum timestamp filter (Unix timestamp) Subaccount identifier Number of results to return (1-1000) Pagination cursor from a previous response ### Response ```json { "$defs": { "Order": { "additionalProperties": false, "description": "An order row (Trade API v2 portfolio order shape).", "properties": { "order_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Order Id" }, "user_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "User Id" }, "client_order_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Client Order Id" }, "ticker": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Ticker" }, "side": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Side" }, "action": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Action" }, "type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Type" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "yes_price": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Yes Price" }, "no_price": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "No Price" }, "yes_price_dollars": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Yes Price Dollars" }, "no_price_dollars": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "No Price Dollars" }, "fill_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Fill Count" }, "fill_count_fp": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Fill Count Fp" }, "remaining_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Remaining Count" }, "remaining_count_fp": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Remaining Count Fp" }, "initial_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Initial Count" }, "initial_count_fp": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Initial Count Fp" }, "taker_fees": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Taker Fees" }, "maker_fees": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Maker Fees" }, "taker_fees_dollars": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Taker Fees Dollars" }, "maker_fees_dollars": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Maker Fees Dollars" }, "taker_fill_cost": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Taker Fill Cost" }, "maker_fill_cost": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Maker Fill Cost" }, "taker_fill_cost_dollars": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Taker Fill Cost Dollars" }, "maker_fill_cost_dollars": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Maker Fill Cost Dollars" }, "queue_position": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Queue Position" }, "expiration_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Expiration Time" }, "created_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created Time" }, "last_update_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Update Time" }, "self_trade_prevention_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Self Trade Prevention Type" }, "order_group_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Order Group Id" }, "cancel_order_on_pause": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Cancel Order On Pause" } }, "title": "Order", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "orders": { "items": { "$ref": "#/$defs/Order" }, "title": "Orders", "type": "array" }, "cursor": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Cursor" } }, "required": [ "success" ], "title": "GetOrdersOutput", "type": "object" } ``` ### Parameters Kalshi API Key ID (provided by the credential system) Kalshi RSA private key in PEM format (provided by the credential system) Order ID to retrieve ### Response ```json { "$defs": { "Order": { "additionalProperties": false, "description": "An order row (Trade API v2 portfolio order shape).", "properties": { "order_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Order Id" }, "user_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "User Id" }, "client_order_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Client Order Id" }, "ticker": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Ticker" }, "side": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Side" }, "action": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Action" }, "type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Type" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "yes_price": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Yes Price" }, "no_price": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "No Price" }, "yes_price_dollars": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Yes Price Dollars" }, "no_price_dollars": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "No Price Dollars" }, "fill_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Fill Count" }, "fill_count_fp": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Fill Count Fp" }, "remaining_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Remaining Count" }, "remaining_count_fp": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Remaining Count Fp" }, "initial_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Initial Count" }, "initial_count_fp": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Initial Count Fp" }, "taker_fees": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Taker Fees" }, "maker_fees": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Maker Fees" }, "taker_fees_dollars": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Taker Fees Dollars" }, "maker_fees_dollars": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Maker Fees Dollars" }, "taker_fill_cost": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Taker Fill Cost" }, "maker_fill_cost": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Maker Fill Cost" }, "taker_fill_cost_dollars": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Taker Fill Cost Dollars" }, "maker_fill_cost_dollars": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Maker Fill Cost Dollars" }, "queue_position": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Queue Position" }, "expiration_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Expiration Time" }, "created_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created Time" }, "last_update_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Update Time" }, "self_trade_prevention_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Self Trade Prevention Type" }, "order_group_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Order Group Id" }, "cancel_order_on_pause": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Cancel Order On Pause" } }, "title": "Order", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "order": { "anyOf": [ { "$ref": "#/$defs/Order" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "GetOrderOutput", "type": "object" } ``` ### Parameters Market ticker identifier Number of price levels to return ### Response ```json { "$defs": { "Orderbook": { "additionalProperties": false, "properties": { "yes": { "items": { "items": {}, "type": "array" }, "title": "Yes", "type": "array" }, "no": { "items": { "items": {}, "type": "array" }, "title": "No", "type": "array" }, "yes_dollars": { "items": { "items": {}, "type": "array" }, "title": "Yes Dollars", "type": "array" }, "no_dollars": { "items": { "items": {}, "type": "array" }, "title": "No Dollars", "type": "array" } }, "title": "Orderbook", "type": "object" }, "OrderbookFp": { "additionalProperties": false, "properties": { "yes_dollars": { "items": { "items": {}, "type": "array" }, "title": "Yes Dollars", "type": "array" }, "no_dollars": { "items": { "items": {}, "type": "array" }, "title": "No Dollars", "type": "array" } }, "title": "OrderbookFp", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "orderbook": { "anyOf": [ { "$ref": "#/$defs/Orderbook" }, { "type": "null" } ], "default": null }, "orderbook_fp": { "anyOf": [ { "$ref": "#/$defs/OrderbookFp" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "GetOrderbookOutput", "type": "object" } ``` ### Parameters Filter by market ticker Minimum timestamp (Unix seconds) Maximum timestamp (Unix seconds) Number of results to return (1-1000) Pagination cursor from a previous response ### Response ```json { "$defs": { "Trade": { "additionalProperties": false, "properties": { "trade_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Trade Id" }, "ticker": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Ticker" }, "yes_price": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Yes Price" }, "no_price": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "No Price" }, "count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Count" }, "count_fp": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Count Fp" }, "created_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created Time" }, "taker_side": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Taker Side" } }, "title": "Trade", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "trades": { "items": { "$ref": "#/$defs/Trade" }, "title": "Trades", "type": "array" }, "cursor": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Cursor" } }, "required": [ "success" ], "title": "GetTradesOutput", "type": "object" } ``` ### Parameters Series ticker identifier Market ticker identifier Start timestamp (Unix seconds) End timestamp (Unix seconds) Period interval: 1 (minute), 60 (hour), 1440 (day) ### Response ```json { "$defs": { "BidAskDistribution": { "additionalProperties": false, "properties": { "open": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Open" }, "open_dollars": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Open Dollars" }, "low": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Low" }, "low_dollars": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Low Dollars" }, "high": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "High" }, "high_dollars": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "High Dollars" }, "close": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Close" }, "close_dollars": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Close Dollars" } }, "title": "BidAskDistribution", "type": "object" }, "MarketCandlestick": { "additionalProperties": false, "properties": { "end_period_ts": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "End Period Ts" }, "yes_bid": { "anyOf": [ { "$ref": "#/$defs/BidAskDistribution" }, { "type": "null" } ], "default": null }, "yes_ask": { "anyOf": [ { "$ref": "#/$defs/BidAskDistribution" }, { "type": "null" } ], "default": null }, "price": { "anyOf": [ { "$ref": "#/$defs/PriceDistribution" }, { "type": "null" } ], "default": null }, "volume": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Volume" }, "volume_fp": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Volume Fp" }, "open_interest": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Open Interest" }, "open_interest_fp": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Open Interest Fp" } }, "title": "MarketCandlestick", "type": "object" }, "PriceDistribution": { "additionalProperties": false, "properties": { "open": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Open" }, "open_dollars": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Open Dollars" }, "low": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Low" }, "low_dollars": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Low Dollars" }, "high": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "High" }, "high_dollars": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "High Dollars" }, "close": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Close" }, "close_dollars": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Close Dollars" }, "mean": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Mean" }, "mean_dollars": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Mean Dollars" }, "previous": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Previous" }, "previous_dollars": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Previous Dollars" }, "min": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Min" }, "min_dollars": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Min Dollars" }, "max": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Max" }, "max_dollars": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Max Dollars" } }, "title": "PriceDistribution", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "ticker": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Ticker" }, "candlesticks": { "items": { "$ref": "#/$defs/MarketCandlestick" }, "title": "Candlesticks", "type": "array" } }, "required": [ "success" ], "title": "GetCandlesticksOutput", "type": "object" } ``` ### Parameters Series ticker identifier Event ticker identifier Start timestamp (Unix seconds) End timestamp (Unix seconds) Period interval: 1 (minute), 60 (hour), 1440 (day) ### Response ```json { "$defs": { "BidAskDistribution": { "additionalProperties": false, "properties": { "open": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Open" }, "open_dollars": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Open Dollars" }, "low": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Low" }, "low_dollars": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Low Dollars" }, "high": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "High" }, "high_dollars": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "High Dollars" }, "close": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Close" }, "close_dollars": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Close Dollars" } }, "title": "BidAskDistribution", "type": "object" }, "EventCandlestick": { "additionalProperties": false, "properties": { "end_period_ts": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "End Period Ts" }, "yes_bid": { "anyOf": [ { "$ref": "#/$defs/BidAskDistribution" }, { "type": "null" } ], "default": null }, "yes_ask": { "anyOf": [ { "$ref": "#/$defs/BidAskDistribution" }, { "type": "null" } ], "default": null }, "price": { "anyOf": [ { "$ref": "#/$defs/PriceDistribution" }, { "type": "null" } ], "default": null }, "volume_fp": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Volume Fp" }, "open_interest_fp": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Open Interest Fp" } }, "title": "EventCandlestick", "type": "object" }, "PriceDistribution": { "additionalProperties": false, "properties": { "open": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Open" }, "open_dollars": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Open Dollars" }, "low": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Low" }, "low_dollars": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Low Dollars" }, "high": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "High" }, "high_dollars": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "High Dollars" }, "close": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Close" }, "close_dollars": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Close Dollars" }, "mean": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Mean" }, "mean_dollars": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Mean Dollars" }, "previous": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Previous" }, "previous_dollars": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Previous Dollars" }, "min": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Min" }, "min_dollars": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Min Dollars" }, "max": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Max" }, "max_dollars": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Max Dollars" } }, "title": "PriceDistribution", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "market_tickers": { "items": { "type": "string" }, "title": "Market Tickers", "type": "array" }, "adjusted_end_ts": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Adjusted End Ts" }, "market_candlesticks": { "items": { "$ref": "#/$defs/EventCandlestick" }, "title": "Market Candlesticks", "type": "array" } }, "required": [ "success" ], "title": "GetEventCandlesticksOutput", "type": "object" } ``` ### Parameters Kalshi API Key ID (provided by the credential system) Kalshi RSA private key in PEM format (provided by the credential system) Filter by market ticker Filter by order ID Minimum timestamp (Unix seconds) Maximum timestamp (Unix seconds) Subaccount identifier Number of results to return (1-1000) Pagination cursor from a previous response ### Response ```json { "$defs": { "Fill": { "additionalProperties": false, "properties": { "fill_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Fill Id" }, "trade_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Trade Id" }, "order_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Order Id" }, "client_order_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Client Order Id" }, "ticker": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Ticker" }, "market_ticker": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Market Ticker" }, "side": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Side" }, "action": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Action" }, "count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Count" }, "count_fp": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Count Fp" }, "price": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Price" }, "yes_price": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Yes Price" }, "no_price": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "No Price" }, "yes_price_fixed": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Yes Price Fixed" }, "no_price_fixed": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "No Price Fixed" }, "is_taker": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Taker" }, "created_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created Time" }, "ts": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Ts" } }, "title": "Fill", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "fills": { "items": { "$ref": "#/$defs/Fill" }, "title": "Fills", "type": "array" }, "cursor": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Cursor" } }, "required": [ "success" ], "title": "GetFillsOutput", "type": "object" } ``` ### Parameters Kalshi API Key ID (provided by the credential system) Kalshi RSA private key in PEM format (provided by the credential system) Filter by market ticker Filter by event ticker Min settled timestamp (Unix seconds) Max settled timestamp (Unix seconds) Subaccount number (0 primary, 1-63) Number of results to return (1-1000) Pagination cursor from a previous response ### Response ```json { "$defs": { "Settlement": { "additionalProperties": false, "properties": { "ticker": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Ticker" }, "event_ticker": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Event Ticker" }, "market_result": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Market Result" }, "yes_count_fp": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Yes Count Fp" }, "yes_total_cost_dollars": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Yes Total Cost Dollars" }, "no_count_fp": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "No Count Fp" }, "no_total_cost_dollars": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "No Total Cost Dollars" }, "revenue": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Revenue" }, "settled_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Settled Time" }, "fee_cost": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Fee Cost" }, "value": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Value" } }, "title": "Settlement", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "settlements": { "items": { "$ref": "#/$defs/Settlement" }, "title": "Settlements", "type": "array" }, "cursor": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Cursor" } }, "required": [ "success" ], "title": "GetSettlementsOutput", "type": "object" } ``` ### Parameters Series ticker identifier Include volume data: "true" or "false" ### Response ```json { "$defs": { "Series": { "additionalProperties": false, "properties": { "ticker": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Ticker" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "frequency": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Frequency" }, "category": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Category" }, "tags": { "items": { "type": "string" }, "title": "Tags", "type": "array" }, "settlement_sources": { "items": { "$ref": "#/$defs/SettlementSource" }, "title": "Settlement Sources", "type": "array" }, "contract_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Contract Url" }, "contract_terms_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Contract Terms Url" }, "fee_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Fee Type" }, "fee_multiplier": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Fee Multiplier" }, "additional_prohibitions": { "items": { "type": "string" }, "title": "Additional Prohibitions", "type": "array" }, "product_metadata": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Product Metadata" }, "volume": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Volume" }, "volume_fp": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Volume Fp" }, "last_updated_ts": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Updated Ts" } }, "title": "Series", "type": "object" }, "SettlementSource": { "additionalProperties": false, "properties": { "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" } }, "title": "SettlementSource", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "series": { "anyOf": [ { "$ref": "#/$defs/Series" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "GetSeriesByTickerOutput", "type": "object" } ``` ### Parameters Filter by category Filter by comma-separated tags Include product metadata: "true" or "false" Include volume data: "true" or "false" Min updated timestamp (Unix seconds) ### Response ```json { "$defs": { "Series": { "additionalProperties": false, "properties": { "ticker": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Ticker" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "frequency": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Frequency" }, "category": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Category" }, "tags": { "items": { "type": "string" }, "title": "Tags", "type": "array" }, "settlement_sources": { "items": { "$ref": "#/$defs/SettlementSource" }, "title": "Settlement Sources", "type": "array" }, "contract_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Contract Url" }, "contract_terms_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Contract Terms Url" }, "fee_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Fee Type" }, "fee_multiplier": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Fee Multiplier" }, "additional_prohibitions": { "items": { "type": "string" }, "title": "Additional Prohibitions", "type": "array" }, "product_metadata": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Product Metadata" }, "volume": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Volume" }, "volume_fp": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Volume Fp" }, "last_updated_ts": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Updated Ts" } }, "title": "Series", "type": "object" }, "SettlementSource": { "additionalProperties": false, "properties": { "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" } }, "title": "SettlementSource", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "series": { "items": { "$ref": "#/$defs/Series" }, "title": "Series", "type": "array" } }, "required": [ "success" ], "title": "GetSeriesListOutput", "type": "object" } ``` ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "exchange_active": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Exchange Active" }, "trading_active": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Trading Active" }, "exchange_estimated_resume_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Exchange Estimated Resume Time" } }, "required": [ "success" ], "title": "GetExchangeStatusOutput", "type": "object" } ``` ### Response ```json { "$defs": { "ExchangeSchedule": { "additionalProperties": false, "properties": { "standard_hours": { "items": { "additionalProperties": true, "type": "object" }, "title": "Standard Hours", "type": "array" }, "maintenance_windows": { "items": { "additionalProperties": true, "type": "object" }, "title": "Maintenance Windows", "type": "array" } }, "title": "ExchangeSchedule", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "schedule": { "anyOf": [ { "$ref": "#/$defs/ExchangeSchedule" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "GetExchangeScheduleOutput", "type": "object" } ``` ### Response ```json { "$defs": { "Announcement": { "additionalProperties": false, "properties": { "type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Type" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" }, "delivery_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Delivery Time" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" } }, "title": "Announcement", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "announcements": { "items": { "$ref": "#/$defs/Announcement" }, "title": "Announcements", "type": "array" } }, "required": [ "success" ], "title": "GetExchangeAnnouncementsOutput", "type": "object" } ``` ### Parameters Kalshi API Key ID (provided by the credential system) Kalshi RSA private key in PEM format (provided by the credential system) Market ticker identifier Order side: "yes" or "no" Action type: "buy" or "sell" Number of contracts to trade (provide count or count_fp) Order type: "limit" or "market" (default "limit") Yes price in cents (1-99) No price in cents (1-99) Yes price in dollars (e.g., '0.56') No price in dollars (e.g., '0.56') Custom order identifier Unix timestamp for order expiration TIF: fill_or_kill / good_till_canceled / immediate_or_cancel Maximum cost in cents (auto-enables fill_or_kill) Maker-only order Position reduction only Self-trade prevention: 'taker_at_cross' or 'maker' Associated order group ID Count in fixed-point for fractional contracts Cancel order on market pause Subaccount to use for the order ### Response ```json { "$defs": { "Order": { "additionalProperties": false, "description": "An order row (Trade API v2 portfolio order shape).", "properties": { "order_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Order Id" }, "user_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "User Id" }, "client_order_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Client Order Id" }, "ticker": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Ticker" }, "side": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Side" }, "action": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Action" }, "type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Type" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "yes_price": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Yes Price" }, "no_price": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "No Price" }, "yes_price_dollars": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Yes Price Dollars" }, "no_price_dollars": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "No Price Dollars" }, "fill_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Fill Count" }, "fill_count_fp": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Fill Count Fp" }, "remaining_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Remaining Count" }, "remaining_count_fp": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Remaining Count Fp" }, "initial_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Initial Count" }, "initial_count_fp": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Initial Count Fp" }, "taker_fees": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Taker Fees" }, "maker_fees": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Maker Fees" }, "taker_fees_dollars": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Taker Fees Dollars" }, "maker_fees_dollars": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Maker Fees Dollars" }, "taker_fill_cost": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Taker Fill Cost" }, "maker_fill_cost": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Maker Fill Cost" }, "taker_fill_cost_dollars": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Taker Fill Cost Dollars" }, "maker_fill_cost_dollars": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Maker Fill Cost Dollars" }, "queue_position": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Queue Position" }, "expiration_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Expiration Time" }, "created_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created Time" }, "last_update_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Update Time" }, "self_trade_prevention_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Self Trade Prevention Type" }, "order_group_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Order Group Id" }, "cancel_order_on_pause": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Cancel Order On Pause" } }, "title": "Order", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "order": { "anyOf": [ { "$ref": "#/$defs/Order" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "CreateOrderOutput", "type": "object" } ``` ### Parameters Kalshi API Key ID (provided by the credential system) Kalshi RSA private key in PEM format (provided by the credential system) Order ID to cancel ### Response ```json { "$defs": { "Order": { "additionalProperties": false, "description": "An order row (Trade API v2 portfolio order shape).", "properties": { "order_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Order Id" }, "user_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "User Id" }, "client_order_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Client Order Id" }, "ticker": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Ticker" }, "side": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Side" }, "action": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Action" }, "type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Type" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "yes_price": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Yes Price" }, "no_price": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "No Price" }, "yes_price_dollars": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Yes Price Dollars" }, "no_price_dollars": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "No Price Dollars" }, "fill_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Fill Count" }, "fill_count_fp": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Fill Count Fp" }, "remaining_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Remaining Count" }, "remaining_count_fp": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Remaining Count Fp" }, "initial_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Initial Count" }, "initial_count_fp": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Initial Count Fp" }, "taker_fees": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Taker Fees" }, "maker_fees": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Maker Fees" }, "taker_fees_dollars": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Taker Fees Dollars" }, "maker_fees_dollars": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Maker Fees Dollars" }, "taker_fill_cost": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Taker Fill Cost" }, "maker_fill_cost": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Maker Fill Cost" }, "taker_fill_cost_dollars": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Taker Fill Cost Dollars" }, "maker_fill_cost_dollars": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Maker Fill Cost Dollars" }, "queue_position": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Queue Position" }, "expiration_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Expiration Time" }, "created_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created Time" }, "last_update_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Update Time" }, "self_trade_prevention_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Self Trade Prevention Type" }, "order_group_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Order Group Id" }, "cancel_order_on_pause": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Cancel Order On Pause" } }, "title": "Order", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "order": { "anyOf": [ { "$ref": "#/$defs/Order" }, { "type": "null" } ], "default": null }, "reduced_by": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Reduced By" }, "reduced_by_fp": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Reduced By Fp" } }, "required": [ "success" ], "title": "CancelOrderOutput", "type": "object" } ``` ### Parameters Kalshi API Key ID (provided by the credential system) Kalshi RSA private key in PEM format (provided by the credential system) Order ID to amend Market ticker identifier Order side: "yes" or "no" Action type: "buy" or "sell" Original client-specified order ID New client-specified order ID Updated quantity for the order Updated yes price in cents (1-99) Updated no price in cents (1-99) Updated yes price in dollars Updated no price in dollars Count in fixed-point for fractional contracts ### Response ```json { "$defs": { "AmendedOrder": { "additionalProperties": false, "description": "The order shape returned by the amend endpoint (old_order/order).", "properties": { "order_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Order Id" }, "user_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "User Id" }, "ticker": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Ticker" }, "event_ticker": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Event Ticker" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "side": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Side" }, "type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Type" }, "yes_price": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Yes Price" }, "no_price": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "No Price" }, "action": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Action" }, "count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Count" }, "remaining_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Remaining Count" }, "created_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created Time" }, "expiration_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Expiration Time" }, "order_group_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Order Group Id" }, "client_order_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Client Order Id" }, "place_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Place Count" }, "decrease_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Decrease Count" }, "queue_position": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Queue Position" }, "maker_fill_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Maker Fill Count" }, "taker_fill_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Taker Fill Count" }, "maker_fees": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Maker Fees" }, "taker_fees": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Taker Fees" }, "last_update_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Update Time" }, "take_profit_order_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Take Profit Order Id" }, "stop_loss_order_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Stop Loss Order Id" }, "amend_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Amend Count" }, "amend_taker_fill_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Amend Taker Fill Count" } }, "title": "AmendedOrder", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "old_order": { "anyOf": [ { "$ref": "#/$defs/AmendedOrder" }, { "type": "null" } ], "default": null }, "order": { "anyOf": [ { "$ref": "#/$defs/AmendedOrder" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "AmendOrderOutput", "type": "object" } ``` ## Limits & Quotas - **Base URL**: `https://api.elections.kalshi.com/trade-api/v2`. - **Pricing**: prices and counts are returned in cents (and as fixed-point `_fp` / dollar string variants where the API provides them). - **Rate limits**: Kalshi enforces per-tier request rate limits; consult the official docs for current values. Authenticated portfolio and trading calls are signed per request, so clock skew on the host can cause signature rejections — keep the system clock in sync. - **Error model**: non-2xx responses, timeouts, invalid private keys, and unexpected exceptions are caught and returned as `success=False` + `error` rather than raising. On failure the data fields stay at their defaults. ## Related integrations ## Links - [Kalshi](https://kalshi.com) --- # Klaviyo Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/klaviyo Email marketing and automation platform for personalized customer experiences. Manage lists, profiles, and subscriptions for targeted email campaigns. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/klaviyo/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Klaviyo logo Klaviyo logo ## Overview Add **Klaviyo** to any ModuleX agent or workflow. Email-marketing list and profile management against the Klaviyo REST API (`a.klaviyo.com/api`), pinned to revision `2024-10-15`. **Categories**: Marketing & Advertising · Marketing & Email · Email · Automation · Development · **Auth**: Private API Key · **Actions**: 5 ## Authentication ### Private API Key Authenticate using your Klaviyo Private API Key. Create one in Settings > API Keys #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Klaviyo Private API Key | Your Klaviyo Private API Key for API authentication | Yes | `pk_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX` | ## Available Actions ### Parameters Field to sort by: 'created', 'id', 'name', or 'updated' (Default: `created`) Sort direction: 'asc' or 'desc' (Default: `desc`) Maximum number of lists to return (Default: `100`) ### Response ```json { "$defs": { "KlaviyoList": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "created": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created" }, "updated": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated" }, "opt_in_process": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Opt In Process" } }, "title": "KlaviyoList", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "lists": { "items": { "$ref": "#/$defs/KlaviyoList" }, "title": "Lists", "type": "array" }, "count": { "default": 0, "title": "Count", "type": "integer" } }, "required": [ "success" ], "title": "GetListsOutput", "type": "object" } ``` ### Parameters The ID of the Klaviyo list to retrieve ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "created": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created" }, "updated": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated" }, "opt_in_process": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Opt In Process" } }, "required": [ "success" ], "title": "GetListOutput", "type": "object" } ``` ### Parameters The name for the new list ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "created": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created" }, "updated": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated" }, "opt_in_process": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Opt In Process" } }, "required": [ "success" ], "title": "CreateListOutput", "type": "object" } ``` ### Parameters Maximum number of profiles to return (Default: `100`) Field to sort by: 'created', 'updated', 'email' (Default: `created`) Sort direction: 'asc' or 'desc' (Default: `desc`) ### Response ```json { "$defs": { "KlaviyoProfile": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email" }, "first_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "First Name" }, "last_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Name" }, "phone_number": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Phone Number" }, "created": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created" }, "updated": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated" } }, "title": "KlaviyoProfile", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "profiles": { "items": { "$ref": "#/$defs/KlaviyoProfile" }, "title": "Profiles", "type": "array" }, "count": { "default": 0, "title": "Count", "type": "integer" } }, "required": [ "success" ], "title": "GetProfilesOutput", "type": "object" } ``` ### Parameters The ID of the list to add members to List of profile IDs to add to the list ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "list_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "List Id" }, "profiles_added": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Profiles Added" }, "profile_ids": { "items": { "type": "string" }, "title": "Profile Ids", "type": "array" }, "extra": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Extra" } }, "required": [ "success" ], "title": "AddMembersToListOutput", "type": "object" } ``` ## Limits & Quotas - Pagination follows JSON:API cursor links — both `get_lists` and `get_profiles` page through results until `max_results` is reached or `links.next` is exhausted. - Failures (any non-2xx, plus exceptions) surface as `success=False` with an `error` string. Empty/blank API keys short-circuit before the HTTP call. ## Related integrations ## Links - [Klaviyo](https://www.klaviyo.com) --- # Lemlist Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/lemlist Integrate Lemlist into your workflow. Retrieve campaign activities and replies, get lead information, and send emails through the Lemlist inbox. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/lemlist/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Lemlist logo ## Overview Add **Lemlist** to any ModuleX agent or workflow. Manage sales-engagement outreach against the Lemlist REST API (`api.lemlist.com`): retrieve campaign activities and replies, look up lead details, and send emails through the Lemlist inbox. **Categories**: Sales · Email Marketing · Sales Engagement · **Auth**: API Key · **Actions**: 3 ## Authentication ### API Key Authentication Authenticate using your Lemlist API key Sign in to Lemlist and open Settings -> Team Settings -> Integrations Generate an API key (it is shown only once — copy it immediately) Paste the API key below #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Lemlist API Key | Your Lemlist API key from Team Settings -> Integrations | Yes | `-` | ## Available Actions ### Parameters Filter by activity type (e.g., emailOpened, emailClicked, emailReplied, paused) Filter by campaign ID (e.g., "cam_abc123def456") Filter by lead ID (e.g., "lea_abc123def456") Filter for first activity only Number of results per request (max 100, default 100) Number of records to skip for pagination (e.g., 0, 100, 200) ### Response ```json { "$defs": { "ActivityItem": { "additionalProperties": false, "description": "A single activity row in ``get_activities``.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Type" }, "lead_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Lead Id" }, "campaign_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Campaign Id" }, "sequence_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Sequence Id" }, "step_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Step Id" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" } }, "title": "ActivityItem", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "activities": { "items": { "$ref": "#/$defs/ActivityItem" }, "title": "Activities", "type": "array" }, "count": { "default": 0, "title": "Count", "type": "integer" } }, "required": [ "success" ], "title": "GetActivitiesOutput", "type": "object" } ``` ### Parameters Lead email address (e.g., "john@example.com") or lead ID (e.g., "lea_abc123def456") ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email" }, "first_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "First Name" }, "last_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Name" }, "company_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Company Name" }, "job_title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Job Title" }, "company_domain": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Company Domain" }, "is_paused": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Paused" }, "campaign_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Campaign Id" }, "contact_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Contact Id" }, "email_status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email Status" } }, "required": [ "success" ], "title": "GetLeadOutput", "type": "object" } ``` ### Parameters Identifier for the user sending the message (e.g., "usr_abc123def456") Email address of the sender (e.g., "sales@company.com") Mailbox identifier for the sender (e.g., "mbx_abc123def456") Recipient contact identifier (e.g., "con_abc123def456") Associated lead identifier (e.g., "lea_abc123def456") Email subject line Email message body in HTML format Array of CC email addresses ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "ok": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Ok" } }, "required": [ "success" ], "title": "SendEmailOutput", "type": "object" } ``` ## Limits & Quotas - **Pagination**: `get_activities` accepts `limit` (max 100, default 100) and `offset` for paging through campaign activity. - **Send eligibility**: `send_email` requires a configured Lemlist sender user, mailbox, and an existing contact/lead — the values come from your Lemlist workspace. - **Error model**: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. Plan for retries on the agent side based on the error string. ## Related integrations ## Links - [Lemlist](https://www.lemlist.com) --- # Lemon Squeezy Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/lemon-squeezy Digital products and subscriptions platform for selling software, SaaS, and digital goods with built-in payment processing. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/lemon_squeezy/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Lemon Squeezy logo ## Overview Add **Lemon Squeezy** to any ModuleX agent or workflow. Read-only access to the Lemon Squeezy v1 REST API (`api.lemonsqueezy.com/v1`): customers, orders, products, subscriptions, and stores. **Categories**: Finance & Payments · Business Services · Payments · Subscriptions · Digital Products · **Auth**: API Key · **Actions**: 10 ## Authentication ### API Key Authentication Authenticate using your Lemon Squeezy API key #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Lemon Squeezy API Key | Your Lemon Squeezy API key for authentication | Yes | `eyJ...` | ## Available Actions ### Parameters Page number for pagination (Default: `1`) Number of results per page (max 100) (Default: `10`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "default": null, "title": "Data" }, "meta": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Meta" } }, "required": [ "success" ], "title": "ListCustomersOutput", "type": "object" } ``` ### Parameters The ID of the customer to retrieve ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "default": null, "title": "Data" }, "meta": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Meta" } }, "required": [ "success" ], "title": "RetrieveCustomerOutput", "type": "object" } ``` ### Parameters Page number for pagination (Default: `1`) Number of results per page (max 100) (Default: `10`) Filter orders by store ID Filter orders by user email ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "default": null, "title": "Data" }, "meta": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Meta" } }, "required": [ "success" ], "title": "ListOrdersOutput", "type": "object" } ``` ### Parameters The ID of the order to retrieve ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "default": null, "title": "Data" }, "meta": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Meta" } }, "required": [ "success" ], "title": "RetrieveOrderOutput", "type": "object" } ``` ### Parameters Page number for pagination (Default: `1`) Number of results per page (max 100) (Default: `10`) Filter products by store ID ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "default": null, "title": "Data" }, "meta": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Meta" } }, "required": [ "success" ], "title": "ListProductsOutput", "type": "object" } ``` ### Parameters The ID of the product to retrieve ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "default": null, "title": "Data" }, "meta": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Meta" } }, "required": [ "success" ], "title": "RetrieveProductOutput", "type": "object" } ``` ### Parameters Page number for pagination (Default: `1`) Number of results per page (max 100) (Default: `10`) Filter subscriptions by store ID Filter by subscription status: on_trial, active, paused, past_due, unpaid, cancelled, expired ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "default": null, "title": "Data" }, "meta": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Meta" } }, "required": [ "success" ], "title": "ListSubscriptionsOutput", "type": "object" } ``` ### Parameters The ID of the subscription to retrieve ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "default": null, "title": "Data" }, "meta": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Meta" } }, "required": [ "success" ], "title": "RetrieveSubscriptionOutput", "type": "object" } ``` ### Parameters Page number for pagination (Default: `1`) Number of results per page (max 100) (Default: `10`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "default": null, "title": "Data" }, "meta": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Meta" } }, "required": [ "success" ], "title": "ListStoresOutput", "type": "object" } ``` ### Parameters The ID of the store to retrieve ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "default": null, "title": "Data" }, "meta": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Meta" } }, "required": [ "success" ], "title": "RetrieveStoreOutput", "type": "object" } ``` ## Limits & Quotas - Pagination via JSON:API `page[number]` / `page[size]`; `per_page` is clamped to 100. - List endpoints return both `data` (array of resources) and `meta` (pagination block); retrievals return just `data`. - 404 retrievals return `success=False` with a "not found" message; other non-200 responses surface the body verbatim. ## Related integrations ## Links - [Lemon Squeezy](https://lemonsqueezy.com) --- # Linear Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/linear Modern project management and issue tracking tool for software teams. Streamline workflows, track bugs, and coordinate tasks with powerful search, filtering, and automation capabilities. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/linear/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Linear logo Linear logo ## Overview Add **Linear** to any ModuleX agent or workflow. Project-management and issue-tracking integration for Linear via the **GraphQL API** at `api.linear.app/graphql`. Covers team discovery, issue CRUD + search, and project list/create. **Categories**: Project & Task Management · Task Management · Collaboration · **Auth**: OAuth2, API Key · **Actions**: 7 ## Authentication ### OAuth2 Authentication Connect using Linear OAuth (recommended for most use cases) #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Client ID | Linear OAuth application Client ID | Yes | `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | | Client Secret | Linear OAuth application Client Secret | Yes | `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | #### OAuth Configuration - **Authorization URL**: `https://linear.app/oauth/authorize` - **Token URL**: `https://api.linear.app/oauth/token` - **Scopes**: `read`, `write` ### API Key Authentication Authenticate using your Linear API key. Generate one from Linear Settings > API > Personal API keys. #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Linear API Key | Your Linear personal API key for authentication | Yes | `lin_api_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | ## Available Actions ### Parameters Maximum number of teams to return (Default: `50`) Cursor for pagination ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "count": { "default": 0, "title": "Count", "type": "integer" }, "page_info": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Page Info" }, "teams": { "items": { "additionalProperties": true, "type": "object" }, "title": "Teams", "type": "array" } }, "required": [ "success" ], "title": "GetTeamsOutput", "type": "object" } ``` ### Parameters The ID of the issue to retrieve ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "issue": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Issue" } }, "required": [ "success" ], "title": "GetIssueOutput", "type": "object" } ``` ### Parameters Filter by team ID Filter by project ID Filter by assignee user ID Filter by workflow state ID Substring to match in issue titles Filter by label names Include archived issues (Default: `false`) Field to order by ('createdAt' or 'updatedAt') (Default: `updatedAt`) Maximum number of issues to return (Default: `50`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "count": { "default": 0, "title": "Count", "type": "integer" }, "page_info": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Page Info" }, "issues": { "items": { "additionalProperties": true, "type": "object" }, "title": "Issues", "type": "array" } }, "required": [ "success" ], "title": "SearchIssuesOutput", "type": "object" } ``` ### Parameters The team's UUID (the 'id' field returned by get_teams) — NOT the short team key like 'ENG'. Call get_teams first to resolve it. The title of the issue Markdown description UUID of the user to assign the issue to UUID of the project to add the issue to UUID of the workflow state Label UUIDs to attach Priority level: 0=No priority, 1=Urgent, 2=High, 3=Normal, 4=Low ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "issue": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Issue" } }, "required": [ "success" ], "title": "CreateIssueOutput", "type": "object" } ``` ### Parameters The ID of the issue to update New title New markdown description New assignee user ID Move to a different team Move to a different project Change workflow state Replace labels with these IDs New priority level (0-4) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "issue": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Issue" } }, "required": [ "success" ], "title": "UpdateIssueOutput", "type": "object" } ``` ### Parameters Filter by team ID Field to order by ('createdAt' or 'updatedAt') (Default: `updatedAt`) Maximum number of projects to return (Default: `50`) Cursor for pagination ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "count": { "default": 0, "title": "Count", "type": "integer" }, "page_info": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Page Info" }, "projects": { "items": { "additionalProperties": true, "type": "object" }, "title": "Projects", "type": "array" } }, "required": [ "success" ], "title": "ListProjectsOutput", "type": "object" } ``` ### Parameters The team's UUID (the 'id' field returned by get_teams) — NOT the short team key like 'ENG'. Call get_teams first to resolve it. The name of the project The description of the project The project-status ID Priority level (0-4) IDs of users to add as members Start date (YYYY-MM-DD) Target date (YYYY-MM-DD) Label IDs to attach ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "project": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Project" } }, "required": [ "success" ], "title": "CreateProjectOutput", "type": "object" } ``` ## Limits & Quotas - All tools share a single GraphQL endpoint; failures (HTTP non-200, GraphQL `errors`, exceptions) surface as `success=False` + `error`. - The `search_issues` and `list_projects` actions interpolate filter values into the GraphQL string verbatim (matching legacy). Inputs are scoped to internal IDs / label names — not user prose — and treated as opaque tokens. - Output objects keep their nested GraphQL shape unchanged (state, team, assignee, creator, labels.nodes for issues; lead, status for projects). ## Related integrations ## Links - [Linear](https://linear.app) --- # LinkedIn Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/linkedin LinkedIn social networking platform for professional connections, posts, and organization management {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/linkedin/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} LinkedIn logo ## Overview Add **LinkedIn** to any ModuleX agent or workflow. Professional social networking platform for managing posts, comments, likes, profiles, organizations, and ad accounts via the LinkedIn REST API (`api.linkedin.com/rest`). **Categories**: Social Media · Marketing · Communication · **Auth**: OAuth2 · **Actions**: 18 ## Authentication ### OAuth2 Authentication Connect using LinkedIn OAuth (recommended) #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Client ID | LinkedIn OAuth App Client ID | Yes | `-` | | Client Secret | LinkedIn OAuth App Client Secret | Yes | `-` | #### OAuth Configuration - **Authorization URL**: `https://www.linkedin.com/oauth/v2/authorization` - **Token URL**: `https://www.linkedin.com/oauth/v2/accessToken` - **Scopes**: `openid`, `profile`, `email`, `w_member_social` ## Available Actions ### Parameters The share or user generated content post URN where the comment will be made Entity authoring the comment, must be a person or organization URN (e.g. urn:li:person:ABC123) Text of the comment Optional media content entities as a JSON object URN of the parent comment for nested comments (not available for first-level comments) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "CreateCommentOutput", "type": "object" } ``` ### Parameters ID of the organization that will author the post URL of the image to upload and post Text to be posted on the LinkedIn timeline ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "post_urn": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Post Urn" } }, "required": [ "success" ], "title": "CreateImagePostOrganizationOutput", "type": "object" } ``` ### Parameters URL of the image to upload and post Text to be posted on the LinkedIn timeline Visibility restrictions on content. Valid values: CONNECTIONS, PUBLIC, LOGGED_IN ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "post_urn": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Post Urn" } }, "required": [ "success" ], "title": "CreateImagePostUserOutput", "type": "object" } ``` ### Parameters The top-level share URN or user generated content URN where the like will be performed Entity performing the like, must be a person or organization URN URN of the entity to which the like belongs, should be a sub-entity of the top-level share ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" } }, "required": [ "success" ], "title": "CreateLikeOnShareOutput", "type": "object" } ``` ### Parameters ID of the organization that will author the post Text to be posted on the LinkedIn timeline URL of an article to share with the post ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "CreateTextPostOrganizationOutput", "type": "object" } ``` ### Parameters Visibility restrictions on content. Valid values: CONNECTIONS, PUBLIC, LOGGED_IN Text to be posted on the LinkedIn timeline URL of an article to share with the post ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "CreateTextPostUserOutput", "type": "object" } ``` ### Parameters URN of the post to delete (e.g. urn:li:ugcPost:123 or urn:li:share:123) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" } }, "required": [ "success" ], "title": "DeletePostOutput", "type": "object" } ``` ### Parameters ID of the ad account to fetch ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "FetchAdAccountOutput", "type": "object" } ``` ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "GetCurrentMemberProfileOutput", "type": "object" } ``` ### Parameters Identifier of the person to retrieve ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "GetMemberProfileOutput", "type": "object" } ``` ### Parameters List of person ID strings to retrieve ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "results": { "items": { "additionalProperties": true, "type": "object" }, "title": "Results", "type": "array" } }, "required": [ "success" ], "title": "GetMultipleMemberProfilesOutput", "type": "object" } ``` ### Parameters Limit results to a specific role. Valid values: ADMINISTRATOR, DIRECT_SPONSORED_CONTENT_POSTER, RECRUITING_POSTER, LEAD_CAPTURE_ADMINISTRATOR, LEAD_GEN_FORMS_MANAGER, ANALYST, CURATOR, CONTENT_ADMINISTRATOR Limit results to a specific role state. Valid values: APPROVED, REJECTED, REQUESTED, REVOKED ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "GetOrgMemberAccessOutput", "type": "object" } ``` ### Parameters ID of the organization for which the access control information is being retrieved Maximum number of results to return (Default: `50`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "elements": { "items": { "additionalProperties": true, "type": "object" }, "title": "Elements", "type": "array" } }, "required": [ "success" ], "title": "GetOrganizationAccessControlOutput", "type": "object" } ``` ### Parameters ID of the organization for which administrators are being retrieved Maximum number of pages to fetch (1-500) (Default: `50`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "elements": { "items": { "additionalProperties": true, "type": "object" }, "title": "Elements", "type": "array" } }, "required": [ "success" ], "title": "GetOrganizationAdministratorsOutput", "type": "object" } ``` ### Parameters Whether to include the original image data in the response (requires special permissions) (Default: `false`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "GetProfilePictureFieldsOutput", "type": "object" } ``` ### Parameters Parent comment URN to retrieve nested comments for. A composite URN using comment ID and activity URN Maximum number of results to return (Default: `50`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "elements": { "items": { "additionalProperties": true, "type": "object" }, "title": "Elements", "type": "array" } }, "required": [ "success" ], "title": "RetrieveCommentsOnCommentsOutput", "type": "object" } ``` ### Parameters URN of the entity to retrieve comments on Maximum number of results to return (Default: `50`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "elements": { "items": { "additionalProperties": true, "type": "object" }, "title": "Elements", "type": "array" } }, "required": [ "success" ], "title": "RetrieveCommentsSharesOutput", "type": "object" } ``` ### Parameters Field to search by. Valid values: vanityName, emailDomain Keyword to search for Maximum number of results to return (Default: `50`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "elements": { "items": { "additionalProperties": true, "type": "object" }, "title": "Elements", "type": "array" } }, "required": [ "success" ], "title": "SearchOrganizationOutput", "type": "object" } ``` ## Limits & Quotas - LinkedIn API rate limits vary by product and endpoint. Generally 100 requests per day for most marketing/community management endpoints per OAuth app. - Organization-level posting endpoints are subject to daily limits per organization. - The `LinkedIn-Version` header is hardcoded to `202509`; LinkedIn periodically deprecates older API versions. - Non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. ## Related integrations ## Links - [LinkedIn](https://www.linkedin.com) --- # Linkup Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/linkup Search the web with Linkup — retrieve up-to-date information with source attribution, returning either an AI-generated sourced answer or raw ranked search results. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/linkup/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Linkup logo ## Overview Add **Linkup** to any ModuleX agent or workflow. Web search for AI agents — retrieve up-to-date information from across the web with source attribution, via the Linkup REST API (`api.linkup.so/v1`). **Categories**: Web Search & Scraping · Data · Search · **Auth**: API Key · **Actions**: 1 ## Authentication ### API Key Authentication Authenticate using your Linkup API key Go to https://app.linkup.so and sign up or log in Open the API Keys section of your dashboard Create a new API key or copy your existing one Paste the API key below #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Linkup API Key | Your Linkup API key from app.linkup.so | Yes | `-` | ## Available Actions ### Parameters The search query (e.g., "latest AI research papers 2024") Search depth: "standard" for quick results, "deep" for comprehensive search (also supports "fast") (Default: `standard`) Output format: "sourcedAnswer" for an AI-generated answer with citations, "searchResults" for raw results, or "structured" (Default: `sourcedAnswer`) Whether to include images in search results Add inline citations to answers (only applies when output_type is "sourcedAnswer") Include sources in the response Start date for filtering results (YYYY-MM-DD format) End date for filtering results (YYYY-MM-DD format) Comma-separated list of domain names to restrict search results to Comma-separated list of domain names to exclude from search results ### Response ```json { "$defs": { "SearchResultItem": { "additionalProperties": false, "description": "A single result row in a ``searchResults`` response.", "properties": { "type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Type" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" }, "content": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Content" } }, "title": "SearchResultItem", "type": "object" }, "SearchSource": { "additionalProperties": false, "description": "A citation in a ``sourcedAnswer`` response's ``sources`` array.", "properties": { "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" }, "snippet": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Snippet" } }, "title": "SearchSource", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "answer": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Answer" }, "sources": { "items": { "$ref": "#/$defs/SearchSource" }, "title": "Sources", "type": "array" }, "results": { "items": { "$ref": "#/$defs/SearchResultItem" }, "title": "Results", "type": "array" }, "structured_data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Structured Data" } }, "required": [ "success" ], "title": "SearchOutput", "type": "object" } ``` ## Limits & Quotas - **Rate limit**: ~60 requests/min. - **Depth tiers**: `standard` queries are cheaper and faster; `deep` queries cost more and take longer. - **Error model**: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. Plan for retries on the agent side based on the error string. ## Related integrations ## Links - [Linkup](https://www.linkup.so) --- # Livestorm Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/livestorm Video engagement platform for webinars and virtual events {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/livestorm/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Livestorm logo Livestorm logo ## Overview Add **Livestorm** to any ModuleX agent or workflow. Video engagement platform for webinars and virtual events via the Livestorm REST API (`api.livestorm.co/v1`). **Categories**: Scheduling & Events · Marketing · Webinars & Events · **Auth**: API Token · **Actions**: 7 ## Authentication ### API Token Authenticate with a Livestorm private API token (Account Settings -> Integrations -> Public API; account owner/admin, API access must be enabled) Sign in to Livestorm as the workspace owner or an admin Open Account Settings -> Integrations and scroll to the 'Public API' card Generate an API token and copy it (shown only once) Paste the token into ModuleX (if the Public API card is missing, contact support@livestorm.co to enable API access) #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Livestorm API Token | Livestorm private API token, sent as a plain Authorization header (no 'Bearer' prefix) | Yes | `-` | ## Available Actions ### Parameters The ID of the user who owns the event The title of the event The slug of the event The status of the event: draft, published The HTML description of the event Whether the event is recorded Whether the chat is enabled Whether everyone can speak Whether the detailed registration page is enabled Whether the light registration page is enabled Whether the recording is public Whether the event is shown in the company page Whether the polls are enabled Whether the questions are enabled ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "CreateEventOutput", "type": "object" } ``` ### Parameters The ID of the event ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "GetEventOutput", "type": "object" } ``` ### Parameters The ID of the event Filter by role: participant, team_member ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "items": { "additionalProperties": true, "type": "object" }, "title": "Data", "type": "array" } }, "required": [ "success" ], "title": "ListAttendeesFromEventOutput", "type": "object" } ``` ### Parameters Filter events by title ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "items": { "additionalProperties": true, "type": "object" }, "title": "Data", "type": "array" } }, "required": [ "success" ], "title": "ListEventsOutput", "type": "object" } ``` ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "items": { "additionalProperties": true, "type": "object" }, "title": "Data", "type": "array" } }, "required": [ "success" ], "title": "ListSessionsOutput", "type": "object" } ``` ### Parameters The ID of the session The referrer of the person registering The UTM source The UTM medium The UTM campaign The UTM term The UTM content Registration fields as key-value pairs where key is the field ID and value is the field value ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "RegisterSomeoneForSessionOutput", "type": "object" } ``` ### Parameters The ID of the event The ID of the user who owns the event The title of the event The slug of the event The status of the event: draft, published The HTML description of the event Whether the event is recorded Whether the chat is enabled Whether everyone can speak Whether the detailed registration page is enabled Whether the light registration page is enabled Whether the recording is public Whether the event is shown in the company page Whether the polls are enabled Whether the questions are enabled ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "UpdateEventOutput", "type": "object" } ``` ## Limits & Quotas - No publicly documented rate limits for the Livestorm API. - Pagination is applied automatically for list endpoints (page-based). - Error model: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. ## Related integrations ## Links - [Livestorm](https://livestorm.co) --- # Loops Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/loops Integrate Loops into the workflow. Create and manage contacts, send transactional emails, and trigger event-based automations. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/loops/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Loops logo ## Overview Add **Loops** to any ModuleX agent or workflow. Manage contacts and send emails with Loops. Create and manage contacts, send transactional emails, and trigger event-based automations against the Loops REST API (`app.loops.so/api/v1`). **Categories**: Marketing & Advertising · Email Marketing · Automation · **Auth**: API Key · **Actions**: 10 ## Authentication ### API Key Authentication Authenticate using your Loops API key Log in to your Loops account at https://app.loops.so Go to Settings > API Create a new API key or copy your existing one Paste the API key below #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Loops API Key | Your Loops API key from Settings > API | Yes | `-` | ## Available Actions ### Parameters The email address for the new contact The contact first name The contact last name Custom source value replacing the default "API" Whether the contact receives campaign emails (defaults to true) Group to segment the contact into (one group per contact) Unique user identifier from your application Mailing list IDs mapped to boolean values (true to subscribe, false to unsubscribe) Custom contact properties as key-value pairs (string, number, boolean, or date values) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" } }, "required": [ "success" ], "title": "CreateContactOutput", "type": "object" } ``` ### Parameters The contact email address (at least one of email or user_id is required) The contact userId (at least one of email or user_id is required) The contact first name The contact last name Custom source value replacing the default "API" Whether the contact receives campaign emails (sending true re-subscribes unsubscribed contacts) Group to segment the contact into (one group per contact) Mailing list IDs mapped to boolean values (true to subscribe, false to unsubscribe) Custom contact properties as key-value pairs (send null to reset a property) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" } }, "required": [ "success" ], "title": "UpdateContactOutput", "type": "object" } ``` ### Parameters The contact email address to search for (at least one of email or user_id is required) The contact userId to search for (at least one of email or user_id is required) ### Response ```json { "$defs": { "Contact": { "additionalProperties": false, "description": "A single contact row in ``find_contact``.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email" }, "first_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "First Name" }, "last_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Name" }, "source": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Source" }, "subscribed": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Subscribed" }, "user_group": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "User Group" }, "user_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "User Id" }, "mailing_lists": { "additionalProperties": true, "title": "Mailing Lists", "type": "object" }, "opt_in_status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Opt In Status" } }, "title": "Contact", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "contacts": { "items": { "$ref": "#/$defs/Contact" }, "title": "Contacts", "type": "array" } }, "required": [ "success" ], "title": "FindContactOutput", "type": "object" } ``` ### Parameters The email address of the contact to delete (at least one of email or user_id is required) The userId of the contact to delete (at least one of email or user_id is required) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" } }, "required": [ "success" ], "title": "DeleteContactOutput", "type": "object" } ``` ### Parameters The email address of the recipient The ID of the transactional email template to send Template data variables as key-value pairs (string or number values) Whether to create the recipient as a contact if they do not already exist (default: false) Array of file attachments. Each object must have filename (string), contentType (MIME type string), and data (base64-encoded string). ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" } }, "required": [ "success" ], "title": "SendTransactionalEmailOutput", "type": "object" } ``` ### Parameters The name of the event to trigger The email address of the contact (at least one of email or user_id is required) The userId of the contact (at least one of email or user_id is required) Event data as key-value pairs (string, number, boolean, or date values) Mailing list IDs mapped to boolean values (true to subscribe, false to unsubscribe) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" } }, "required": [ "success" ], "title": "SendEventOutput", "type": "object" } ``` ### Response ```json { "$defs": { "MailingList": { "additionalProperties": false, "description": "A single mailing list row in ``list_mailing_lists``.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "is_public": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Public" } }, "title": "MailingList", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "mailing_lists": { "items": { "$ref": "#/$defs/MailingList" }, "title": "Mailing Lists", "type": "array" } }, "required": [ "success" ], "title": "ListMailingListsOutput", "type": "object" } ``` ### Parameters Number of results per page (10-50, default: 20) Pagination cursor from a previous response to fetch the next page ### Response ```json { "$defs": { "Pagination": { "additionalProperties": false, "description": "Pagination block returned by ``list_transactional_emails``.", "properties": { "total_results": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Total Results" }, "returned_results": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Returned Results" }, "per_page": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Per Page" }, "total_pages": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Total Pages" }, "next_cursor": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Next Cursor" }, "next_page": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Next Page" } }, "title": "Pagination", "type": "object" }, "TransactionalEmail": { "additionalProperties": false, "description": "A single template row in ``list_transactional_emails``.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "last_updated": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Updated" }, "data_variables": { "items": { "type": "string" }, "title": "Data Variables", "type": "array" } }, "title": "TransactionalEmail", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "transactional_emails": { "items": { "$ref": "#/$defs/TransactionalEmail" }, "title": "Transactional Emails", "type": "array" }, "pagination": { "anyOf": [ { "$ref": "#/$defs/Pagination" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "ListTransactionalEmailsOutput", "type": "object" } ``` ### Parameters The property name in camelCase format (e.g., "favoriteColor") The property data type (e.g., "string", "number", "boolean", "date") ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" } }, "required": [ "success" ], "title": "CreateContactPropertyOutput", "type": "object" } ``` ### Parameters Filter type: "all" for all properties (default) or "custom" for custom properties only ### Response ```json { "$defs": { "ContactProperty": { "additionalProperties": false, "description": "A single property row in ``list_contact_properties``.", "properties": { "key": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Key" }, "label": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Label" }, "type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Type" } }, "title": "ContactProperty", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "properties": { "type": "array", "items": { "$ref": "#/$defs/ContactProperty" }, "title": "Properties" } }, "required": [ "success" ], "title": "ListContactPropertiesOutput", "type": "object" } ``` ## Limits & Quotas - Loops applies per-account API rate limits; check your plan in the Loops dashboard for current values. - `list_transactional_emails` accepts `per_page` (10-50, default 20) and a `cursor` for pagination; the response carries a `pagination` block with `next_cursor`/`next_page` for fetching subsequent pages. - **Error model**: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. Mutating endpoints also return HTTP 200 with `{"success": false, "message": ...}` for business-logic failures, which is surfaced as `error`. ## Related integrations ## Links - [Loops](https://loops.so) --- # Luma Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/luma Event management platform for creating, managing, and tracking events and guests {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/luma/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Luma logo Luma logo ## Overview Add **Luma** to any ModuleX agent or workflow. Event management platform for creating, managing, and tracking events and guests via the Luma REST API (`public-api.luma.com/v1`). **Categories**: Scheduling & Events · Events · Productivity & Collaboration · Marketing · **Auth**: API Key · **Actions**: 8 ## Authentication ### API Key Authentication Authenticate using your Luma API key Go to https://lu.ma and sign in to your account Navigate to your calendar settings or developer section Generate or copy your API key Paste the API key below #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Luma API Key | Your Luma API key | Yes | `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` | ## Available Actions ### Parameters The event name The event start time as an ISO 8601 datetime, for example 2026-05-15T18:00:00Z The IANA timezone for the event, for example America/New_York The event end time as an ISO 8601 datetime Markdown description for the event Event visibility: public, members-only, or private Custom event URL slug Online meeting URL for a virtual event Cover image URL uploaded to the Luma CDN Maximum number of registrations before the event is sold out Whether guests can register for multiple tickets Whether approved guests can see who else is attending Whether to disable default event reminders How to collect guest names: full-name or first-last Phone number collection: optional or required A hex color like #bb2dc7 for the event theme JSON object with latitude and longitude for the event location JSON object with address details (type, place_id, description) JSON array of registration question objects with label and required fields JSON object with subject and message for the post-event feedback email ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "event": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Event" } }, "required": [ "success" ], "title": "CreateEventOutput", "type": "object" } ``` ### Parameters The Luma event ID (usually starts with evt-) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "event": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Event" } }, "required": [ "success" ], "title": "GetEventOutput", "type": "object" } ``` ### Parameters Return events starting after this ISO 8601 datetime Return events starting before this ISO 8601 datetime The next_cursor value from a previous list response Number of items to request per page (Default: `50`) Calendar submission status: approved or pending Column to sort by (currently only start_at is supported) Sort order: asc, desc, asc nulls last, or desc nulls last ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "events": { "items": { "additionalProperties": true, "type": "object" }, "title": "Events", "type": "array" }, "has_more": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Has More" }, "next_cursor": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Next Cursor" } }, "required": [ "success" ], "title": "ListEventsOutput", "type": "object" } ``` ### Parameters The Luma event ID (usually starts with evt-) Guest ID (gst-...), ticket key, guest key (g-...), or email address ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "guest": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Guest" } }, "required": [ "success" ], "title": "GetGuestOutput", "type": "object" } ``` ### Parameters The Luma event ID (usually starts with evt-) Filter by status: approved, session, pending_approval, invited, declined, or waitlist The next_cursor value from a previous list response Number of items to request per page (Default: `50`) Guest field to sort by: name, email, created_at, registered_at, or checked_in_at Sort order: asc, desc, asc nulls last, or desc nulls last ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "guests": { "items": { "additionalProperties": true, "type": "object" }, "title": "Guests", "type": "array" }, "has_more": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Has More" }, "next_cursor": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Next Cursor" } }, "required": [ "success" ], "title": "GetGuestsOutput", "type": "object" } ``` ### Parameters The Luma event ID (usually starts with evt-) JSON array of guests, each with at least an email field JSON object assigning one ticket type to each guest (mutually exclusive with tickets_json) JSON array assigning multiple tickets to each guest (mutually exclusive with ticket_json) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "guests": { "items": { "additionalProperties": true, "type": "object" }, "title": "Guests", "type": "array" } }, "required": [ "success" ], "title": "AddGuestsOutput", "type": "object" } ``` ### Parameters The Luma event ID (usually starts with evt-) Whether to include hidden ticket types (Default: `false`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "ticket_types": { "items": { "additionalProperties": true, "type": "object" }, "title": "Ticket Types", "type": "array" } }, "required": [ "success" ], "title": "ListTicketTypesOutput", "type": "object" } ``` ### Parameters The Luma event ID (usually starts with evt-) JSON array of guests to invite, each with at least an email field Optional invite message (max 200 characters) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" } }, "required": [ "success" ], "title": "SendInvitesOutput", "type": "object" } ``` ## Limits & Quotas - No documented public rate limits; Luma enforces server-side pagination maximums. - Error model: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. ## Related integrations ## Links - [Luma](https://luma.com) --- # Mailchimp Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/mailchimp Mailchimp marketing platform: list/audience CRUD, subscriber upsert, campaign lifecycle, tags, notes, and segments. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/mailchimp/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Mailchimp logo ## Overview Add **Mailchimp** to any ModuleX agent or workflow. Mailchimp marketing platform integration via the v3.0 REST API. Pure HTTP. 19 actions across lists, subscribers, campaigns, tags, notes, and segments. **Categories**: Marketing & Advertising · Marketing & Email · Email Marketing · CRM · **Auth**: OAuth2, API Key · **Actions**: 19 ## Authentication ### OAuth2 Authentication Connect using Mailchimp OAuth2 (recommended — avoids datacenter discovery and Basic Auth headaches). #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Client ID | Mailchimp OAuth App Client ID | Yes | `-` | | Client Secret | Mailchimp OAuth App Client Secret | Yes | `-` | #### OAuth Configuration - **Authorization URL**: `https://login.mailchimp.com/oauth2/authorize` - **Token URL**: `https://login.mailchimp.com/oauth2/token` ### API Key Authentication Authenticate using your Mailchimp API key. The key includes the datacenter suffix (e.g. xxx-us10). #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Mailchimp API Key | Your Mailchimp API key (with datacenter suffix) | Yes | `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-us10` | | Datacenter | Datacenter portion of your API key (suffix after the dash, e.g. 'us10', 'us20', 'eu1'). Mailchimp routes API requests to the datacenter where your account lives. | Yes | `us10` | ## Available Actions ### Parameters Results (max 1000) (Default: `10`) Results to skip (Default: `0`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "lists": { "items": { "additionalProperties": true, "type": "object" }, "title": "Lists", "type": "array" }, "total_items": { "default": 0, "title": "Total Items", "type": "integer" } }, "required": [ "success" ], "title": "GetListsOutput", "type": "object" } ``` ### Parameters The unique list/audience ID ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "permission_reminder": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Permission Reminder" }, "contact": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Contact" }, "campaign_defaults": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Campaign Defaults" }, "stats": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Stats" }, "date_created": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Date Created" } }, "required": [ "success" ], "title": "GetListOutput", "type": "object" } ``` ### Parameters List name Company name Street address City State/province Postal code ISO3166 country code (e.g. 'US') Permission reminder text Default from name Default from email Default subject line Default language (Default: `en`) Allow subscribers to choose HTML or text (Default: `false`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "date_created": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Date Created" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" } }, "required": [ "success" ], "title": "CreateListOutput", "type": "object" } ``` ### Parameters The unique list/audience ID ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" }, "list_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "List Id" } }, "required": [ "success" ], "title": "DeleteListOutput", "type": "object" } ``` ### Parameters The unique list/audience ID Results (max 1000) (Default: `10`) Results to skip (Default: `0`) subscribed/unsubscribed/cleaned/pending/transactional/archived ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "members": { "items": { "additionalProperties": true, "type": "object" }, "title": "Members", "type": "array" }, "total_items": { "default": 0, "title": "Total Items", "type": "integer" } }, "required": [ "success" ], "title": "GetListMembersOutput", "type": "object" } ``` ### Parameters The unique list/audience ID Subscriber email address ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "email_address": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email Address" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "merge_fields": { "additionalProperties": true, "title": "Merge Fields", "type": "object" }, "tags": { "items": { "type": "string" }, "title": "Tags", "type": "array" }, "vip": { "default": false, "title": "Vip", "type": "boolean" }, "language": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Language" }, "timestamp_signup": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Timestamp Signup" }, "last_changed": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Changed" } }, "required": [ "success" ], "title": "GetSubscriberOutput", "type": "object" } ``` ### Parameters The unique list/audience ID Subscriber email address Status if new (default 'subscribed') (Default: `subscribed`) Current status 'html' or 'text' Merge fields (e.g. {'FNAME': 'John'}) Subscriber language VIP status Tags to add (active) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "email_address": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email Address" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "merge_fields": { "additionalProperties": true, "title": "Merge Fields", "type": "object" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" } }, "required": [ "success" ], "title": "AddOrUpdateSubscriberOutput", "type": "object" } ``` ### Parameters The unique list/audience ID Subscriber email address ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" }, "email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email" }, "list_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "List Id" } }, "required": [ "success" ], "title": "DeleteSubscriberOutput", "type": "object" } ``` ### Parameters Results (max 1000) (Default: `10`) Results to skip (Default: `0`) regular/plaintext/absplit/rss/variate save/paused/schedule/sending/sent ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "campaigns": { "items": { "additionalProperties": true, "type": "object" }, "title": "Campaigns", "type": "array" }, "total_items": { "default": 0, "title": "Total Items", "type": "integer" } }, "required": [ "success" ], "title": "GetCampaignsOutput", "type": "object" } ``` ### Parameters Campaign ID ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "GetCampaignOutput", "type": "object" } ``` ### Parameters Campaign type (Default: `regular`) The unique list/audience ID Subject line From name Reply-to email Campaign title Preview text ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Type" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" } }, "required": [ "success" ], "title": "CreateCampaignOutput", "type": "object" } ``` ### Parameters Campaign ID ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" }, "campaign_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Campaign Id" } }, "required": [ "success" ], "title": "DeleteCampaignOutput", "type": "object" } ``` ### Parameters Campaign ID ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" }, "campaign_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Campaign Id" } }, "required": [ "success" ], "title": "SendCampaignOutput", "type": "object" } ``` ### Parameters Campaign ID ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "GetCampaignReportOutput", "type": "object" } ``` ### Parameters The unique list/audience ID Subscriber email address ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "tags": { "items": { "additionalProperties": true, "type": "object" }, "title": "Tags", "type": "array" }, "total_items": { "default": 0, "title": "Total Items", "type": "integer" } }, "required": [ "success" ], "title": "GetMemberTagsOutput", "type": "object" } ``` ### Parameters The unique list/audience ID Subscriber email address List of {name, status} where status is 'active' or 'inactive' ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" }, "email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email" } }, "required": [ "success" ], "title": "UpdateMemberTagsOutput", "type": "object" } ``` ### Parameters The unique list/audience ID Subscriber email address Note content ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Id" }, "note": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Note" }, "email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" } }, "required": [ "success" ], "title": "AddNoteToSubscriberOutput", "type": "object" } ``` ### Parameters The unique list/audience ID Results (max 1000) (Default: `10`) Results to skip (Default: `0`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "segments": { "items": { "additionalProperties": true, "type": "object" }, "title": "Segments", "type": "array" }, "total_items": { "default": 0, "title": "Total Items", "type": "integer" } }, "required": [ "success" ], "title": "GetSegmentsOutput", "type": "object" } ``` ### Parameters The unique list/audience ID Segment ID Subscriber email address ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" }, "email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email" }, "segment_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Segment Id" } }, "required": [ "success" ], "title": "AddMemberToSegmentOutput", "type": "object" } ``` ## Related integrations ## Links - [Mailchimp](https://mailchimp.com) --- # Mailgun Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/mailgun Transactional email API for sending, receiving, and tracking email {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/mailgun/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Mailgun logo ## Overview Add **Mailgun** to any ModuleX agent or workflow. Transactional email API integration for sending, receiving, and tracking email via the Mailgun REST API (`api.mailgun.net`). **Categories**: Communication · Email · **Auth**: Mailgun API Key · **Actions**: 9 ## Authentication ### Mailgun API Key Authenticate using your Mailgun API key and region Log in to https://app.mailgun.com Go to Settings > API Security Copy your Private API key Set MAILGUN_REGION to 'EU' if your account is on the EU infrastructure, otherwise leave as 'US' #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Mailgun API Key | Private API key from Mailgun dashboard (Settings > API Security) | Yes | `key-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | | Mailgun Region | API region: 'US' (default) or 'EU' | No | `US` | ## Available Actions ### Parameters Mailgun domain name to send from Sender display name Sender email address Recipient email address(es) as a list of strings Email subject line Plain text message body HTML message body Reply-to email address Enable Mailgun test mode (message accepted but not delivered) (Default: `true`) Enable or disable DKIM signatures on the message (Default: `true`) Enable or disable tracking on the message (Default: `true`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" } }, "required": [ "success" ], "title": "SendEmailOutput", "type": "object" } ``` ### Parameters Email address to verify ### Response ```json { "$defs": { "EmailVerificationResult": { "additionalProperties": false, "properties": { "address": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Address" }, "did_you_mean": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Did You Mean" }, "is_disposable_address": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Disposable Address" }, "is_role_address": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Role Address" }, "reason": { "items": { "type": "string" }, "title": "Reason", "type": "array" }, "result": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Result" }, "risk": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Risk" } }, "title": "EmailVerificationResult", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "verification": { "anyOf": [ { "$ref": "#/$defs/EmailVerificationResult" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "VerifyEmailOutput", "type": "object" } ``` ### Parameters Mailing list address (e.g. list@yourdomain.com) Email address of the member to add Display name of the member Extra arbitrary member data as a JSON object Subscription status: 'yes' (default) or 'no' for unsubscribed (Default: `yes`) If 'yes', update existing member; if 'no', error on duplicate. Values: yes, no (Default: `no`) ### Response ```json { "$defs": { "MailinglistMember": { "additionalProperties": false, "properties": { "address": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Address" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "subscribed": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Subscribed" }, "vars": { "anyOf": [ { "additionalProperties": { "anyOf": [ { "type": "string" }, { "type": "integer" }, { "type": "number" }, { "type": "boolean" }, { "type": "null" } ] }, "type": "object" }, { "type": "null" } ], "default": null, "title": "Vars" } }, "title": "MailinglistMember", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "member": { "anyOf": [ { "$ref": "#/$defs/MailinglistMember" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "CreateMailinglistMemberOutput", "type": "object" } ``` ### Parameters Route priority (lower numbers are evaluated first) Human-readable description of the route Mailgun route filter expression, e.g. match_recipient('.*@example.com') List of route action strings, e.g. ['forward("dest@example.com")', 'stop()'] ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "route_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Route Id" }, "route_message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Route Message" } }, "required": [ "success" ], "title": "CreateRouteOutput", "type": "object" } ``` ### Parameters Mailing list address (e.g. list@yourdomain.com) Email address of the member to remove ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "member_address": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Member Address" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" } }, "required": [ "success" ], "title": "DeleteMailinglistMemberOutput", "type": "object" } ``` ### Parameters Filter by domain state: active, unverified, disabled (Default: `active`) ### Response ```json { "$defs": { "DomainSummary": { "additionalProperties": false, "properties": { "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "State" }, "type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Type" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "smtp_login": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Smtp Login" }, "web_prefix": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Web Prefix" } }, "title": "DomainSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "domains": { "items": { "$ref": "#/$defs/DomainSummary" }, "title": "Domains", "type": "array" }, "total_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Total Count" } }, "required": [ "success" ], "title": "ListDomainsOutput", "type": "object" } ``` ### Parameters Mailing list address (e.g. list@yourdomain.com) Filter by subscription: 'true' for subscribed only, 'false' for unsubscribed only, or omit for all ### Response ```json { "$defs": { "MailinglistMember": { "additionalProperties": false, "properties": { "address": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Address" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "subscribed": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Subscribed" }, "vars": { "anyOf": [ { "additionalProperties": { "anyOf": [ { "type": "string" }, { "type": "integer" }, { "type": "number" }, { "type": "boolean" }, { "type": "null" } ] }, "type": "object" }, { "type": "null" } ], "default": null, "title": "Vars" } }, "title": "MailinglistMember", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "members": { "items": { "$ref": "#/$defs/MailinglistMember" }, "title": "Members", "type": "array" }, "total_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Total Count" } }, "required": [ "success" ], "title": "ListMailinglistMembersOutput", "type": "object" } ``` ### Parameters Mailing list address (e.g. list@yourdomain.com) Email address of the member to retrieve ### Response ```json { "$defs": { "MailinglistMember": { "additionalProperties": false, "properties": { "address": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Address" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "subscribed": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Subscribed" }, "vars": { "anyOf": [ { "additionalProperties": { "anyOf": [ { "type": "string" }, { "type": "integer" }, { "type": "number" }, { "type": "boolean" }, { "type": "null" } ] }, "type": "object" }, { "type": "null" } ], "default": null, "title": "Vars" } }, "title": "MailinglistMember", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "member": { "anyOf": [ { "$ref": "#/$defs/MailinglistMember" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "RetrieveMailinglistMemberOutput", "type": "object" } ``` ### Parameters Mailgun domain name Email address to suppress Suppression list category: bounces, unsubscribes, or complaints Bounce error code (only for 'bounces' category) (Default: `550`) Bounce error message (only for 'bounces' category) Tag to unsubscribe from (only for 'unsubscribes' category). Use '*' for all. (Default: `*`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" } }, "required": [ "success" ], "title": "SuppressEmailOutput", "type": "object" } ``` ## Limits & Quotas - **Free tier**: 100 emails/day for the first 30 days (sandbox domain). - **Paid plans**: rate limits vary by plan; see https://www.mailgun.com/pricing/. - **Email validation**: billed separately per validation request. - **Error model**: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. ## Related integrations ## Links - [Mailgun](https://www.mailgun.com) --- # Mem0 Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/mem0 Persistent agent memory management — add, search, and retrieve long-term memories so AI agents can recall user context, preferences, and prior conversations across sessions. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/mem0/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Mem0 logo ## Overview Add **Mem0** to any ModuleX agent or workflow. Persistent agent memory management against the Mem0 REST API (`api.mem0.ai`) — add, search, and retrieve long-term memories so AI agents can recall user context, preferences, and prior conversations across sessions. **Categories**: AI & Machine Learning · Knowledge Base · Agentic · **Auth**: API Key · **Actions**: 3 ## Authentication ### API Key Authentication Authenticate using your Mem0 API key Go to https://app.mem0.ai and sign up or log in Open the dashboard and navigate to the 'API Keys' section Create a new API key or copy your existing one Paste the API key below #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Mem0 API Key | Your Mem0 API key from app.mem0.ai | Yes | `-` | ## Available Actions ### Parameters User ID associated with the memory (e.g., "user_123", "alice@example.com") Array of message objects with role and content (e.g., [{"role": "user", "content": "Hello"}]) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "event_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Event Id" } }, "required": [ "success" ], "title": "AddMemoriesOutput", "type": "object" } ``` ### Parameters User ID to search memories for (e.g., "user_123", "alice@example.com") Search query to find relevant memories (e.g., "What are my favorite foods?") Maximum number of results to return (e.g., 10, 50, 100) (Default: `10`) ### Response ```json { "$defs": { "SearchResultRecord": { "additionalProperties": false, "description": "A single ranked result returned by ``search_memories``.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "memory": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Memory" }, "user_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "User Id" }, "agent_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Agent Id" }, "app_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "App Id" }, "run_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Run Id" }, "hash": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Hash" }, "metadata": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Metadata" }, "categories": { "items": { "type": "string" }, "title": "Categories", "type": "array" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "updated_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated At" }, "score": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Score" } }, "title": "SearchResultRecord", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "search_results": { "items": { "$ref": "#/$defs/SearchResultRecord" }, "title": "Search Results", "type": "array" }, "ids": { "items": { "type": "string" }, "title": "Ids", "type": "array" } }, "required": [ "success" ], "title": "SearchMemoriesOutput", "type": "object" } ``` ### Parameters User ID to retrieve memories for (e.g., "user_123", "alice@example.com") Specific memory ID to retrieve (e.g., "mem_abc123") Start date for filtering by created_at (e.g., "2024-01-15") End date for filtering by created_at (e.g., "2024-12-31") Maximum number of results to return (e.g., 10, 50, 100) (Default: `10`) Page number to retrieve for paginated list results (Default: `1`) ### Response ```json { "$defs": { "MemoryRecord": { "additionalProperties": false, "description": "A single memory object returned by ``get_memories``.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "memory": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Memory" }, "user_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "User Id" }, "agent_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Agent Id" }, "app_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "App Id" }, "run_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Run Id" }, "hash": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Hash" }, "metadata": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Metadata" }, "categories": { "items": { "type": "string" }, "title": "Categories", "type": "array" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "updated_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated At" } }, "title": "MemoryRecord", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "memories": { "items": { "$ref": "#/$defs/MemoryRecord" }, "title": "Memories", "type": "array" }, "ids": { "items": { "type": "string" }, "title": "Ids", "type": "array" }, "count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Count" }, "next": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Next" }, "previous": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Previous" } }, "required": [ "success" ], "title": "GetMemoriesOutput", "type": "object" } ``` ## Limits & Quotas - Rate limits and quotas depend on your Mem0 plan; see https://docs.mem0.ai for current tiers. - `add_memories` is processed asynchronously — the response returns a `status` (initially `PENDING`) and an `event_id` for polling progress, not the stored memory records themselves. - **Error model**: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. Plan for retries on the agent side based on the error string. ## Related integrations ## Links - [Mem0](https://mem0.ai) --- # Mercury Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/mercury Business banking platform for startups and scaling companies {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/mercury/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Mercury logo ## Overview Add **Mercury** to any ModuleX agent or workflow. Business banking platform for startups and scaling companies. Integrates with the Mercury REST API (`backend.mercury.com/api/v1`). **Categories**: Finance · Banking · **Auth**: API Token · **Actions**: 1 ## Authentication ### API Token Use your Mercury API token to authenticate Log in to your Mercury dashboard at https://app.mercury.com Navigate to Settings -> API Tokens Generate a new API token and copy it #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | API Token | Your Mercury API token from the dashboard | Yes | `xxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | ## Available Actions ### Parameters The unique ID of the Mercury account to retrieve ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "account_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Account Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "account_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Account Type" }, "current_balance": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Current Balance" }, "available_balance": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Available Balance" }, "routing_number": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Routing Number" }, "account_number": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Account Number" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" } }, "required": [ "success" ], "title": "GetAccountInfoOutput", "type": "object" } ``` ## Limits & Quotas - No publicly documented rate limits for the Mercury API. - Access is limited to accounts with API access enabled by Mercury support. - Error model: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. ## Related integrations ## Links - [Mercury](https://mercury.com) --- # Microsoft 365 People Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/microsoft-365-people Manage contacts and contact folders via the Microsoft Graph API {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/microsoft_365_people/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Microsoft 365 People logo ## Overview Add **Microsoft 365 People** to any ModuleX agent or workflow. Manage contacts and contact folders via the Microsoft Graph API (`graph.microsoft.com/v1.0`). **Categories**: Productivity & Collaboration · Contacts · **Auth**: OAuth2 · **Actions**: 3 ## Authentication ### OAuth2 Authentication Connect using Microsoft OAuth2 (recommended) #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Client ID | Microsoft Azure AD OAuth App Client ID | Yes | `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` | | Client Secret | Microsoft Azure AD OAuth App Client Secret | Yes | `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | #### OAuth Configuration - **Authorization URL**: `https://login.microsoftonline.com/common/oauth2/v2.0/authorize` - **Token URL**: `https://login.microsoftonline.com/common/oauth2/v2.0/token` - **Scopes**: `Contacts.ReadWrite`, `User.Read`, `offline_access` ## Available Actions ### Parameters Email address of the contact First name of the contact Last name of the contact ID of the contact folder to create the contact in (defaults to the root contacts folder) Mobile phone number of the contact List of home phone numbers for the contact (each element is a string) Street address of the contact City of the contact State of the contact Postal code of the contact Country or region of the contact ### Response ```json { "$defs": { "ContactOutput": { "additionalProperties": false, "description": "A Microsoft 365 contact resource.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "display_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Display Name" }, "given_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Given Name" }, "surname": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Surname" }, "email_addresses": { "items": { "$ref": "#/$defs/EmailAddress" }, "title": "Email Addresses", "type": "array" }, "mobile_phone": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Mobile Phone" }, "home_phones": { "items": { "type": "string" }, "title": "Home Phones", "type": "array" }, "home_address": { "anyOf": [ { "$ref": "#/$defs/HomeAddress" }, { "type": "null" } ], "default": null }, "created_date_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created Date Time" }, "last_modified_date_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Modified Date Time" } }, "title": "ContactOutput", "type": "object" }, "EmailAddress": { "additionalProperties": false, "description": "An email address entry on a contact.", "properties": { "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "address": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Address" } }, "title": "EmailAddress", "type": "object" }, "HomeAddress": { "additionalProperties": false, "description": "A physical address on a contact.", "properties": { "street": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Street" }, "city": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "City" }, "state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "State" }, "postal_code": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Postal Code" }, "country_or_region": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Country Or Region" } }, "title": "HomeAddress", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "contact": { "anyOf": [ { "$ref": "#/$defs/ContactOutput" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "CreateContactOutput", "type": "object" } ``` ### Parameters The display name of the new contact folder ### Response ```json { "$defs": { "ContactFolderOutput": { "additionalProperties": false, "description": "A Microsoft 365 contact folder resource.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "display_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Display Name" }, "parent_folder_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Parent Folder Id" } }, "title": "ContactFolderOutput", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "folder": { "anyOf": [ { "$ref": "#/$defs/ContactFolderOutput" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "CreateContactFolderOutput", "type": "object" } ``` ### Parameters ID of the contact to update ID of the contact folder containing the contact (defaults to the root contacts folder) New email address for the contact New first name for the contact New last name for the contact New mobile phone number for the contact New list of home phone numbers for the contact (each element is a string) New street address for the contact New city for the contact New state for the contact New postal code for the contact New country or region for the contact ### Response ```json { "$defs": { "ContactOutput": { "additionalProperties": false, "description": "A Microsoft 365 contact resource.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "display_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Display Name" }, "given_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Given Name" }, "surname": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Surname" }, "email_addresses": { "items": { "$ref": "#/$defs/EmailAddress" }, "title": "Email Addresses", "type": "array" }, "mobile_phone": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Mobile Phone" }, "home_phones": { "items": { "type": "string" }, "title": "Home Phones", "type": "array" }, "home_address": { "anyOf": [ { "$ref": "#/$defs/HomeAddress" }, { "type": "null" } ], "default": null }, "created_date_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created Date Time" }, "last_modified_date_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Modified Date Time" } }, "title": "ContactOutput", "type": "object" }, "EmailAddress": { "additionalProperties": false, "description": "An email address entry on a contact.", "properties": { "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "address": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Address" } }, "title": "EmailAddress", "type": "object" }, "HomeAddress": { "additionalProperties": false, "description": "A physical address on a contact.", "properties": { "street": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Street" }, "city": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "City" }, "state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "State" }, "postal_code": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Postal Code" }, "country_or_region": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Country Or Region" } }, "title": "HomeAddress", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "contact": { "anyOf": [ { "$ref": "#/$defs/ContactOutput" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "UpdateContactOutput", "type": "object" } ``` ## Limits & Quotas - Microsoft Graph API throttling: 10,000 requests per 10 minutes per app per tenant. - Per-mailbox limits: 4 concurrent connections. - Error model: non-2xx responses raise `httpx.HTTPStatusError` (Pattern A). ## Related integrations ## Links - [Microsoft 365 People](https://www.microsoft.com/en-us/microsoft-365) --- # Microsoft Bookings Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/microsoft-bookings Create and manage Microsoft Bookings businesses, services, staff members, customers, and appointments via the Microsoft Graph API. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/microsoft_bookings/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Microsoft Bookings logo ## Overview Add **Microsoft Bookings** to any ModuleX agent or workflow. Create and manage Microsoft Bookings businesses, services, staff members, customers, and appointments through the Microsoft Graph API (`graph.microsoft.com/v1.0`). **Categories**: Scheduling & Events · Productivity & Collaboration · Scheduling · Business Services · **Auth**: OAuth2 · **Actions**: 10 ## Authentication ### OAuth2 Authentication Connect using a Microsoft (Azure AD / Entra ID) OAuth app with Bookings scopes. #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Client ID | Application (client) ID of the Azure AD app registration. | Yes | `00000000-0000-0000-0000-000000000000` | | Client Secret | Client secret value from the Azure AD app registration. | Yes | `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | #### OAuth Configuration - **Authorization URL**: `https://login.microsoftonline.com/common/oauth2/v2.0/authorize` - **Token URL**: `https://login.microsoftonline.com/common/oauth2/v2.0/token` - **Scopes**: `offline_access`, `Bookings.Read.All`, `BookingsAppointment.ReadWrite.All`, `Bookings.ReadWrite.All`, `Bookings.Manage.All` ## Available Actions ### Parameters ID of the Microsoft Bookings business that owns the appointment. ID of the appointment to cancel. Message sent to the customer and staff members explaining the cancellation. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" } }, "required": [ "success" ], "title": "CancelAppointmentOutput", "type": "object" } ``` ### Parameters ID of the Microsoft Bookings business that will own the appointment. ID of the booking service the customer is booking. ID of the existing customer the appointment is being booked for. Appointment start time in ISO 8601 format (e.g. 2024-05-01T12:00:00). Appointment end time in ISO 8601 format (e.g. 2024-05-01T13:00:00). IANA time zone for the appointment (e.g. UTC, America/Chicago). Display name to record for the customer on this appointment. SMTP email address for the customer on this appointment. IANA time zone for the customer (falls back to time_zone if omitted). Phone number to record for the customer on this appointment. Notes from the customer associated with this appointment. True indicates that the appointment will be held online. List of staff member IDs assigned to this appointment. If True, send SMS notifications to the customer for the appointment. Regular monetary price for the appointment. Pricing structure: undefined, fixedPrice, startingAt, hourly, free, priceVaries, callUs, notSet. Appointment length in ISO 8601 duration (e.g. PT1H, PT30M). Maximum number of customers allowed in this appointment. Time to reserve before the appointment in ISO 8601 duration (e.g. PT15M). Time to reserve after the appointment in ISO 8601 duration (e.g. PT15M). Notes from the staff member about this appointment. Additional information sent to the customer when the appointment is confirmed. True allows the customer to manage bookings created by the staff. If True, the customer will not receive an email confirmation for this appointment. ### Response ```json { "$defs": { "AppointmentSummary": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "self_service_appointment_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Self Service Appointment Id" }, "service_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Service Id" }, "service_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Service Name" }, "customer_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Customer Name" }, "customer_email_address": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Customer Email Address" }, "customer_time_zone": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Customer Time Zone" }, "start_date_time": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Start Date Time" }, "end_date_time": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "End Date Time" }, "duration": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Duration" }, "price": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Price" }, "price_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Price Type" }, "is_location_online": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Location Online" }, "join_web_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Join Web Url" }, "sms_notifications_enabled": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Sms Notifications Enabled" }, "staff_member_ids": { "items": { "type": "string" }, "title": "Staff Member Ids", "type": "array" }, "customers": { "items": { "additionalProperties": true, "type": "object" }, "title": "Customers", "type": "array" } }, "title": "AppointmentSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "appointment": { "anyOf": [ { "$ref": "#/$defs/AppointmentSummary" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "CreateAppointmentOutput", "type": "object" } ``` ### Parameters Customer-facing name of the booking business. Email address for the business. Telephone number for the business. URL of the business web site. Street address of the business. City of the business. State of the business. Postal code of the business. Country or region of the business. ISO currency code the business operates in (e.g. USD). Free-form description of the type of business. ### Response ```json { "$defs": { "BusinessSummary": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "display_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Display Name" }, "email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email" }, "phone": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Phone" }, "web_site_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Web Site Url" }, "default_currency_iso": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Default Currency Iso" }, "business_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Business Type" }, "address": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Address" }, "is_published": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Published" }, "public_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Public Url" } }, "title": "BusinessSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "business": { "anyOf": [ { "$ref": "#/$defs/BusinessSummary" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "CreateBusinessOutput", "type": "object" } ``` ### Parameters ID of the Microsoft Bookings business that will own the customer. Customer's full name. Customer's email address. Customer's phone number. Type of phone number: home, business, mobile. (Default: `home`) Street address for the customer. City for the customer. State for the customer. Postal code for the customer. Country or region for the customer. ### Response ```json { "$defs": { "CustomerSummary": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "display_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Display Name" }, "email_address": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email Address" }, "phones": { "items": { "additionalProperties": true, "type": "object" }, "title": "Phones", "type": "array" }, "addresses": { "items": { "additionalProperties": true, "type": "object" }, "title": "Addresses", "type": "array" } }, "title": "CustomerSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "customer": { "anyOf": [ { "$ref": "#/$defs/CustomerSummary" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "CreateCustomerOutput", "type": "object" } ``` ### Parameters ID of the Microsoft Bookings business that will own the service. Customer-facing name of the service. Default service length in ISO 8601 duration (e.g. PT1H30M). Free-form description for the service. Default monetary price for the service. Default pricing structure: undefined, fixedPrice, startingAt, hourly, free, priceVaries, callUs, notSet. True indicates the service will be held online. List of staff member IDs allowed to provide this service. True hides this service from the customer-facing booking page. Additional notes about this service (staff-facing). True enables SMS notifications to customers for this service. ### Response ```json { "$defs": { "ServiceSummary": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "display_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Display Name" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "default_duration": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Default Duration" }, "default_price": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Default Price" }, "default_price_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Default Price Type" }, "is_location_online": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Location Online" }, "is_hidden_from_customers": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Hidden From Customers" }, "notes": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Notes" }, "staff_member_ids": { "items": { "type": "string" }, "title": "Staff Member Ids", "type": "array" }, "sms_notifications_enabled": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Sms Notifications Enabled" } }, "title": "ServiceSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "service": { "anyOf": [ { "$ref": "#/$defs/ServiceSummary" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "CreateServiceOutput", "type": "object" } ``` ### Parameters ID of the Microsoft Bookings business that will own the staff member. Staff member's name as shown to customers. Staff member's email address. Staff member's role: guest, administrator, viewer, externalGuest, scheduler, teamMember. IANA time zone for the staff member (e.g. America/Chicago). True means the staff member follows the business' default hours; False uses workingHours. True to send email notifications to this staff member. ### Response ```json { "$defs": { "StaffMemberSummary": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "display_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Display Name" }, "email_address": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email Address" }, "role": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Role" }, "time_zone": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Time Zone" }, "use_business_hours": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Use Business Hours" }, "is_email_notification_enabled": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Email Notification Enabled" }, "availability_is_affected_by_personal_calendar": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Availability Is Affected By Personal Calendar" } }, "title": "StaffMemberSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "staff_member": { "anyOf": [ { "$ref": "#/$defs/StaffMemberSummary" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "CreateStaffMemberOutput", "type": "object" } ``` ### Parameters ID of the Microsoft Bookings business to query. Calendar view start in ISO 8601 (e.g. 2024-05-01T00:00:00Z). Calendar view end in ISO 8601 (e.g. 2024-05-31T23:59:59Z). ### Response ```json { "$defs": { "AppointmentSummary": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "self_service_appointment_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Self Service Appointment Id" }, "service_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Service Id" }, "service_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Service Name" }, "customer_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Customer Name" }, "customer_email_address": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Customer Email Address" }, "customer_time_zone": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Customer Time Zone" }, "start_date_time": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Start Date Time" }, "end_date_time": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "End Date Time" }, "duration": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Duration" }, "price": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Price" }, "price_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Price Type" }, "is_location_online": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Location Online" }, "join_web_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Join Web Url" }, "sms_notifications_enabled": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Sms Notifications Enabled" }, "staff_member_ids": { "items": { "type": "string" }, "title": "Staff Member Ids", "type": "array" }, "customers": { "items": { "additionalProperties": true, "type": "object" }, "title": "Customers", "type": "array" } }, "title": "AppointmentSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "appointments": { "items": { "$ref": "#/$defs/AppointmentSummary" }, "title": "Appointments", "type": "array" }, "total": { "default": 0, "title": "Total", "type": "integer" } }, "required": [ "success" ], "title": "ListAppointmentsOutput", "type": "object" } ``` ### Response ```json { "$defs": { "BusinessSummary": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "display_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Display Name" }, "email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email" }, "phone": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Phone" }, "web_site_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Web Site Url" }, "default_currency_iso": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Default Currency Iso" }, "business_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Business Type" }, "address": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Address" }, "is_published": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Published" }, "public_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Public Url" } }, "title": "BusinessSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "businesses": { "items": { "$ref": "#/$defs/BusinessSummary" }, "title": "Businesses", "type": "array" }, "total": { "default": 0, "title": "Total", "type": "integer" } }, "required": [ "success" ], "title": "ListBusinessesOutput", "type": "object" } ``` ### Parameters ID of the Microsoft Bookings business to query. ### Response ```json { "$defs": { "ServiceSummary": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "display_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Display Name" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "default_duration": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Default Duration" }, "default_price": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Default Price" }, "default_price_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Default Price Type" }, "is_location_online": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Location Online" }, "is_hidden_from_customers": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Hidden From Customers" }, "notes": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Notes" }, "staff_member_ids": { "items": { "type": "string" }, "title": "Staff Member Ids", "type": "array" }, "sms_notifications_enabled": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Sms Notifications Enabled" } }, "title": "ServiceSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "services": { "items": { "$ref": "#/$defs/ServiceSummary" }, "title": "Services", "type": "array" }, "total": { "default": 0, "title": "Total", "type": "integer" } }, "required": [ "success" ], "title": "ListServicesOutput", "type": "object" } ``` ### Parameters ID of the Microsoft Bookings business to query. ### Response ```json { "$defs": { "StaffMemberSummary": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "display_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Display Name" }, "email_address": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email Address" }, "role": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Role" }, "time_zone": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Time Zone" }, "use_business_hours": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Use Business Hours" }, "is_email_notification_enabled": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Email Notification Enabled" }, "availability_is_affected_by_personal_calendar": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Availability Is Affected By Personal Calendar" } }, "title": "StaffMemberSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "staff_members": { "items": { "$ref": "#/$defs/StaffMemberSummary" }, "title": "Staff Members", "type": "array" }, "total": { "default": 0, "title": "Total", "type": "integer" } }, "required": [ "success" ], "title": "ListStaffMembersOutput", "type": "object" } ``` ## Limits & Quotas - Microsoft Graph enforces a per-app and per-user throttling envelope; Bookings endpoints typically allow a few thousand requests per 10-minute window per app+tenant. See the [Microsoft Graph throttling guidance](https://learn.microsoft.com/en-us/graph/throttling) for current limits. - Creating appointments requires that the referenced `customer_id` already exists in the business; the tool will surface the underlying Graph error if it does not. - **Error model**: non-2xx Graph responses and timeouts are caught and returned as `success=False` + `error` rather than raising. Plan for retries on the agent side based on the error string. - Dynamic ID dropdowns (business/staff/service/customer/appointment pickers) are not part of this integration; the LLM must supply IDs directly. Use the `list_*` tools to enumerate available IDs first. ## Related integrations ## Links - [Microsoft Bookings](https://www.microsoft.com/en-us/microsoft-365/business/scheduling-and-booking-app) --- # Microsoft Dynamics 365 Sales Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/microsoft-dynamics-365-sales CRM platform for managing accounts, contacts, appointments, and custom entities via the Dynamics 365 Web API {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/microsoft_dynamics_365_sales/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Microsoft Dynamics 365 Sales logo ## Overview Add **Microsoft Dynamics 365 Sales** to any ModuleX agent or workflow. CRM platform integration for managing accounts, contacts, appointments, and custom entities via the Dynamics 365 Web API (`https://{org}.crm.dynamics.com/api/data/v9.2`). **Categories**: CRM · Sales · Productivity & Collaboration · **Auth**: OAuth2 · **Actions**: 11 ## Authentication ### OAuth2 Authentication Connect using Microsoft OAuth2 (recommended) #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Client ID | Microsoft Entra (Azure AD) OAuth App Client ID | Yes | `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` | | Client Secret | Microsoft Entra (Azure AD) OAuth App Client Secret | Yes | `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` | | Dynamics 365 Instance URL | Your Dynamics 365 org URL hostname (e.g. org12345.crm.dynamics.com) | Yes | `org12345.crm.dynamics.com` | #### OAuth Configuration - **Authorization URL**: `https://login.microsoftonline.com/common/oauth2/v2.0/authorize` - **Token URL**: `https://login.microsoftonline.com/common/oauth2/v2.0/token` - **Scopes**: `https://dynamics.microsoft.com/user_impersonation`, `offline_access` ## Available Actions ### Parameters Title of the appointment Start date/time in ISO 8601 format (e.g. 2026-02-20T12:00:00Z) End date/time in ISO 8601 format Account ID the appointment is regarding Email address of the Dynamics system user to add as attendee Optional category value (numeric). Use list_appointment_categories to discover valid values ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "appointment_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Appointment Id" }, "deep_link": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Deep Link" }, "appointment": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Appointment" } }, "required": [ "success" ], "title": "CreateAppointmentOutput", "type": "object" } ``` ### Parameters Identifier of the solution to associate the entity with Display name for the new entity (e.g. 'Bank Account') Primary name attribute of the new entity (e.g. 'Account Name') Language code (e.g. 1033 for English US) (Default: `1033`) Array of attribute definition objects to add to the entity Description of the new entity Whether the entity supports activities (Default: `false`) Whether the entity supports notes (Default: `false`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "entity": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Entity" } }, "required": [ "success" ], "title": "CreateCustomEntityOutput", "type": "object" } ``` ### Parameters Contact GUID to look up directly Find contacts whose full name contains this value Custom OData $filter expression (e.g. "lastname eq 'Smith'") ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "contacts": { "items": { "additionalProperties": true, "type": "object" }, "title": "Contacts", "type": "array" } }, "required": [ "success" ], "title": "FindContactOutput", "type": "object" } ``` ### Parameters Account GUID to retrieve ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "account": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Account" } }, "required": [ "success" ], "title": "GetAccountOutput", "type": "object" } ``` ### Parameters Optional OData $filter (e.g. 'statecode eq 0' for active accounts) Number of records per page (max 5000) (Default: `50`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "accounts": { "items": { "additionalProperties": true, "type": "object" }, "title": "Accounts", "type": "array" } }, "required": [ "success" ], "title": "ListAccountsOutput", "type": "object" } ``` ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "category_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Category Type" }, "categories": { "items": { "additionalProperties": true, "type": "object" }, "title": "Categories", "type": "array" } }, "required": [ "success" ], "title": "ListAppointmentCategoriesOutput", "type": "object" } ``` ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "options": { "items": { "additionalProperties": true, "type": "object" }, "title": "Options", "type": "array" } }, "required": [ "success" ], "title": "ListAppointmentCategoryOptionsOutput", "type": "object" } ``` ### Parameters Optional OData $filter (e.g. 'statecode eq 0') Number of appointments to return (max 100) (Default: `25`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "appointments": { "items": { "additionalProperties": true, "type": "object" }, "title": "Appointments", "type": "array" } }, "required": [ "success" ], "title": "ListAppointmentsOutput", "type": "object" } ``` ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "solutions": { "items": { "additionalProperties": true, "type": "object" }, "title": "Solutions", "type": "array" } }, "required": [ "success" ], "title": "ListSolutionIdOptionsOutput", "type": "object" } ``` ### Parameters Substring to match against account name ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "accounts": { "items": { "additionalProperties": true, "type": "object" }, "title": "Accounts", "type": "array" } }, "required": [ "success" ], "title": "SearchAccountsOutput", "type": "object" } ``` ### Parameters Appointment activity GUID to update Updated subject/title Updated start in ISO 8601 Updated end in ISO 8601 Updated category value (numeric). Use list_appointment_categories to discover valid values ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "appointment_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Appointment Id" }, "updated_fields": { "items": { "type": "string" }, "title": "Updated Fields", "type": "array" } }, "required": [ "success" ], "title": "UpdateAppointmentOutput", "type": "object" } ``` ## Limits & Quotas - **API limits**: Dynamics 365 enforces per-org service protection limits (typically 6,000 requests per 5-minute sliding window per user). - **Throttling**: Returns HTTP 429 when limits are exceeded; retry after the `Retry-After` header value. - **Error model**: Non-2xx responses are caught and returned as `success=False` + `error` rather than raising. ## Related integrations ## Links - [Microsoft Dynamics 365 Sales](https://dynamics.microsoft.com) --- # Microsoft Entra ID Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/microsoft-entra-id Identity and access management via Microsoft Graph API for users, groups, and directory objects. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/microsoft_entra_id/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Microsoft Entra ID logo ## Overview Add **Microsoft Entra ID** to any ModuleX agent or workflow. Identity and access management via the Microsoft Graph API (`graph.microsoft.com/v1.0`) for managing users, groups, and directory objects in Microsoft Entra ID (formerly Azure Active Directory). **Categories**: Developer Tools & Infrastructure · Identity & Access Management · Enterprise · Security · **Auth**: OAuth2 · **Actions**: 12 ## Authentication ### OAuth2 Authentication Connect using Microsoft Entra ID OAuth (recommended) #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Client ID | Microsoft Entra ID OAuth App Client ID | Yes | `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` | | Client Secret | Microsoft Entra ID OAuth App Client Secret | Yes | `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | #### OAuth Configuration - **Authorization URL**: `https://login.microsoftonline.com/common/oauth2/v2.0/authorize` - **Token URL**: `https://login.microsoftonline.com/common/oauth2/v2.0/token` - **Scopes**: `User.Read`, `User.ReadWrite.All`, `Group.ReadWrite.All`, `GroupMember.ReadWrite.All`, `Directory.ReadWrite.All` ## Available Actions ### Parameters Identifier of the group Identifier of the user to add ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" } }, "required": [ "success" ], "title": "AddMemberToGroupOutput", "type": "object" } ``` ### Parameters The name to display in the address book for the group Set to true for mail-enabled groups The mail alias for the group, unique for groups in the organization. Maximum length is 64 characters. Set to true for security-enabled groups ### Response ```json { "$defs": { "GroupSummary": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "display_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Display Name" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "mail_enabled": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Mail Enabled" }, "mail_nickname": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Mail Nickname" }, "security_enabled": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Security Enabled" }, "group_types": { "items": { "type": "string" }, "title": "Group Types", "type": "array" }, "deleted_date_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Deleted Date Time" } }, "title": "GroupSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "group": { "anyOf": [ { "$ref": "#/$defs/GroupSummary" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "CreateGroupOutput", "type": "object" } ``` ### Parameters Identifier of the group to delete ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" } }, "required": [ "success" ], "title": "DeleteGroupOutput", "type": "object" } ``` ### Parameters Identifier of the user. Leave empty to use the signed-in user. ### Response ```json { "$defs": { "ManagerInfo": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "display_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Display Name" }, "email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email" }, "job_title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Job Title" }, "mobile_phone": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Mobile Phone" } }, "title": "ManagerInfo", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "manager": { "anyOf": [ { "$ref": "#/$defs/ManagerInfo" }, { "type": "null" } ], "default": null }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" } }, "required": [ "success" ], "title": "GetManagerOutput", "type": "object" } ``` ### Parameters Identifier of the user. Leave empty to use the signed-in user. ### Response ```json { "$defs": { "GroupSummary": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "display_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Display Name" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "mail_enabled": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Mail Enabled" }, "mail_nickname": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Mail Nickname" }, "security_enabled": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Security Enabled" }, "group_types": { "items": { "type": "string" }, "title": "Group Types", "type": "array" }, "deleted_date_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Deleted Date Time" } }, "title": "GroupSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "groups": { "items": { "$ref": "#/$defs/GroupSummary" }, "title": "Groups", "type": "array" } }, "required": [ "success" ], "title": "GetMs365GroupsOutput", "type": "object" } ``` ### Response ```json { "$defs": { "GroupSummary": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "display_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Display Name" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "mail_enabled": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Mail Enabled" }, "mail_nickname": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Mail Nickname" }, "security_enabled": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Security Enabled" }, "group_types": { "items": { "type": "string" }, "title": "Group Types", "type": "array" }, "deleted_date_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Deleted Date Time" } }, "title": "GroupSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "groups": { "items": { "$ref": "#/$defs/GroupSummary" }, "title": "Groups", "type": "array" } }, "required": [ "success" ], "title": "GetOrganizationGroupsOutput", "type": "object" } ``` ### Parameters Maximum number of users to return. Omit for no limit. OData filter expression, e.g. 'accountEnabled eq true' (Default: `accountEnabled eq true`) OData search expression, e.g. '"displayName:John"' ### Response ```json { "$defs": { "UserSummary": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "full_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Full Name" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email" }, "user_principal_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "User Principal Name" }, "surname": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Surname" }, "given_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Given Name" }, "job_title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Job Title" }, "mobile_phone": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Mobile Phone" } }, "title": "UserSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "users": { "items": { "$ref": "#/$defs/UserSummary" }, "title": "Users", "type": "array" } }, "required": [ "success" ], "title": "GetOrganizationUsersOutput", "type": "object" } ``` ### Parameters Identifier of the user. Leave empty to use the signed-in user. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "GetProfileOutput", "type": "object" } ``` ### Parameters Identifier of the group Identifier of the user to remove ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" } }, "required": [ "success" ], "title": "RemoveMemberFromGroupOutput", "type": "object" } ``` ### Parameters Keywords to search by The maximum number of groups to return (Default: `100`) ### Response ```json { "$defs": { "GroupSummary": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "display_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Display Name" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "mail_enabled": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Mail Enabled" }, "mail_nickname": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Mail Nickname" }, "security_enabled": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Security Enabled" }, "group_types": { "items": { "type": "string" }, "title": "Group Types", "type": "array" }, "deleted_date_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Deleted Date Time" } }, "title": "GroupSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "groups": { "items": { "$ref": "#/$defs/GroupSummary" }, "title": "Groups", "type": "array" } }, "required": [ "success" ], "title": "SearchGroupsOutput", "type": "object" } ``` ### Parameters Identifier of the group to update Whether people external to the organization can send messages to the group Whether new members added to the group will be auto-subscribed to receive email notifications An optional description for the group The name to display in the address book for the group The mail alias for the group. Maximum length is 64 characters. Set to true for security-enabled groups Specifies the visibility of the group. Allowed values: Public, Private. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" } }, "required": [ "success" ], "title": "UpdateGroupOutput", "type": "object" } ``` ### Parameters Identifier of the user to update The name to display in the address book for the user The SMTP address for the user The mail alias for the user Whether the account is enabled (Default: `true`) The street address of the user's place of business The city in which the user is located The state or province in the user's address The postal code for the user's postal address The country/region in which the user is located ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" } }, "required": [ "success" ], "title": "UpdateUserOutput", "type": "object" } ``` ## Limits & Quotas - **Microsoft Graph throttling**: Varies by resource and tenant; typically 10,000 requests per 10 minutes per app per tenant for most directory endpoints. - **Pagination**: List endpoints may return paginated results; the integration follows `@odata.nextLink` automatically. - **Error model**: Non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. ## Related integrations ## Links - [Microsoft Entra ID](https://entra.microsoft.com) --- # Microsoft Excel Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/microsoft-excel Read, write, and manage Excel workbooks stored in OneDrive via the Microsoft Graph API. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/microsoft_excel/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Microsoft Excel logo ## Overview Add **Microsoft Excel** to any ModuleX agent or workflow. Read, write, and manage Excel workbooks stored in OneDrive via the Microsoft Graph REST API (`graph.microsoft.com/v1.0`). **Categories**: Productivity & Collaboration · Spreadsheets · **Auth**: Microsoft OAuth 2.0 · **Actions**: 9 ## Authentication ### Microsoft OAuth 2.0 Connect to Microsoft Excel via Microsoft Identity Platform OAuth (recommended). Requires consent to access OneDrive files. #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Client ID | Microsoft Entra ID (Azure AD) application Client ID. | Yes | `00000000-0000-0000-0000-000000000000` | | Client Secret | Microsoft Entra ID application Client Secret value. | Yes | `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | #### OAuth Configuration - **Authorization URL**: `https://login.microsoftonline.com/common/oauth2/v2.0/authorize` - **Token URL**: `https://login.microsoftonline.com/common/oauth2/v2.0/token` - **Scopes**: `Files.ReadWrite`, `User.Read`, `offline_access` ## Available Actions ### Parameters Drive item ID of the workbook (the spreadsheet file) in OneDrive. Two-dimensional array of unformatted row values, e.g. [[1, 2, 3], [4, 5, 6]]. Each inner array is one row. ID of the workbook table to append to. Provide either table_id or table_name. Name of the workbook table (set in the Table Design tab). Used when table_id is not available, e.g. for personal accounts. ### Response ```json { "$defs": { "TableRow": { "additionalProperties": false, "description": "One Microsoft Graph workbookTableRow returned by table operations.", "properties": { "index": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Index" }, "values": { "items": { "items": {}, "type": "array" }, "title": "Values", "type": "array" } }, "title": "TableRow", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "row": { "anyOf": [ { "$ref": "#/$defs/TableRow" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "AddAWorksheetTablerowOutput", "type": "object" } ``` ### Parameters Drive item ID of the workbook in OneDrive. Name of the worksheet to append the row to. Array of cell values for the new row, e.g. [1, 2, 3]. Each item is one cell. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "address": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Address" }, "values": { "items": { "items": {}, "type": "array" }, "title": "Values", "type": "array" } }, "required": [ "success" ], "title": "AddRowOutput", "type": "object" } ``` ### Parameters Drive item ID of the workbook in OneDrive. Name of the worksheet to search. Column letter to search, e.g. 'A'. Value to search for. Numeric strings will also be matched as numbers. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "found": { "default": false, "title": "Found", "type": "boolean" }, "row_number": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Row Number" }, "address": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Address" }, "values": { "items": { "items": {}, "type": "array" }, "title": "Values", "type": "array" }, "column_values": { "items": {}, "title": "Column Values", "type": "array" } }, "required": [ "success" ], "title": "FindRowOutput", "type": "object" } ``` ### Parameters Drive item ID of the workbook in OneDrive. Name of the worksheet to read. Array of column letters to retrieve, e.g. ['A', 'C']. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "values": { "additionalProperties": { "items": {}, "type": "array" }, "title": "Values", "type": "object" } }, "required": [ "success" ], "title": "GetColumnsOutput", "type": "object" } ``` ### Parameters Drive item ID of the workbook in OneDrive. Name of the worksheet to read. Range within the worksheet, e.g. 'A1:C4'. If omitted, the entire used range is returned. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "address": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Address" }, "row_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Row Count" }, "column_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Column Count" }, "values": { "items": { "items": {}, "type": "array" }, "title": "Values", "type": "array" } }, "required": [ "success" ], "title": "GetSpreadsheetOutput", "type": "object" } ``` ### Parameters Drive item ID of the workbook in OneDrive. ID of the workbook table whose rows you want. ### Response ```json { "$defs": { "TableRow": { "additionalProperties": false, "description": "One Microsoft Graph workbookTableRow returned by table operations.", "properties": { "index": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Index" }, "values": { "items": { "items": {}, "type": "array" }, "title": "Values", "type": "array" } }, "title": "TableRow", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "rows": { "items": { "$ref": "#/$defs/TableRow" }, "title": "Rows", "type": "array" } }, "required": [ "success" ], "title": "GetTableRowsOutput", "type": "object" } ``` ### Parameters Maximum number of batch pages to fetch (1-500). Default 50. ### Response ```json { "$defs": { "FolderOption": { "additionalProperties": false, "description": "One folder option returned by list_folder_id_options.", "properties": { "value": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Value" }, "label": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Label" } }, "title": "FolderOption", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "options": { "items": { "$ref": "#/$defs/FolderOption" }, "title": "Options", "type": "array" } }, "required": [ "success" ], "title": "ListFolderIdOptionsOutput", "type": "object" } ``` ### Parameters Drive item ID of the workbook in OneDrive. Name of the worksheet containing the cell. Cell address to update, e.g. 'A1'. Value to write to the cell. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "address": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Address" }, "values": { "items": { "items": {}, "type": "array" }, "title": "Values", "type": "array" } }, "required": [ "success" ], "title": "UpdateCellOutput", "type": "object" } ``` ### Parameters Drive item ID of the workbook in OneDrive. ID of the workbook table. Zero-based index of the row to update. Array of cell values for the updated row, e.g. [1, 2, 3]. Each item is one cell. ### Response ```json { "$defs": { "TableRow": { "additionalProperties": false, "description": "One Microsoft Graph workbookTableRow returned by table operations.", "properties": { "index": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Index" }, "values": { "items": { "items": {}, "type": "array" }, "title": "Values", "type": "array" } }, "title": "TableRow", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "row": { "anyOf": [ { "$ref": "#/$defs/TableRow" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "UpdateWorksheetTablerowOutput", "type": "object" } ``` ## Limits & Quotas - **Microsoft Graph throttling**: Graph applies per-app and per-user throttling. Excessive requests return HTTP `429` with a `Retry-After` header. See https://learn.microsoft.com/en-us/graph/throttling for current limits. - **Workbook session limits**: Excel workbook APIs apply additional per-workbook session throttling; consider batching writes via `add_a_worksheet_tablerow` rather than per-cell `update_cell` calls when possible. - **Table updates** (`update_worksheet_tablerow`): work or school Microsoft accounts only. Personal accounts cannot enumerate or update workbook tables. - **Error model**: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. Plan for retries on the agent side based on the error string. ## Related integrations ## Links - [Microsoft Excel](https://www.microsoft.com/en-us/microsoft-365/excel) --- # Microsoft OneDrive Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/microsoft-onedrive Access and manage files in Microsoft OneDrive: search, list, upload, download, create folders, and create sharing links via the Microsoft Graph API. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/microsoft_onedrive/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Microsoft OneDrive logo ## Overview Add **Microsoft OneDrive** to any ModuleX agent or workflow. Access and manage files in Microsoft OneDrive through the Microsoft Graph API (`https://graph.microsoft.com/v1.0`) — search, list, upload, download files, create folders, and produce sharing links using a user's delegated permissions. **Categories**: Cloud Infrastructure · File Storage · Productivity · **Auth**: OAuth2 · **Actions**: 11 ## Authentication ### OAuth2 Authentication Connect using Microsoft (Azure Entra) OAuth Go to https://entra.microsoft.com -> App registrations -> New registration Set supported account types according to your needs (commonly 'Accounts in any organizational directory and personal Microsoft accounts') Add redirect URI (Web): https://api.modulex.dev/credentials/oauth2/callback Under API permissions, add Microsoft Graph delegated permissions: Files.ReadWrite.All, Sites.ReadWrite.All, User.Read, offline_access Create a client secret under Certificates & secrets Copy Application (client) ID and the client secret value #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Client ID | Microsoft (Azure Entra) App Client ID | Yes | `00000000-0000-0000-0000-000000000000` | | Client Secret | Microsoft (Azure Entra) App Client Secret | Yes | `-` | #### OAuth Configuration - **Authorization URL**: `https://login.microsoftonline.com/common/oauth2/v2.0/authorize` - **Token URL**: `https://login.microsoftonline.com/common/oauth2/v2.0/token` - **Scopes**: `Files.ReadWrite.All`, `Sites.ReadWrite.All`, `User.Read`, `offline_access` ## Available Actions ### Parameters The name of the new folder to be created (e.g. 'New Folder'). The ID of the folder which the new folder should be created within. Omit to create the folder at the drive root. Reference of the shared folder which the new folder should be created in (e.g. '/drives/{driveId}/items/{folderId}/children'). Mutually exclusive with parent_folder_id. ### Response ```json { "$defs": { "CreateFolderResult": { "additionalProperties": false, "description": "Microsoft Graph response for creating a folder.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "webUrl": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Weburl" }, "parentReference": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Parentreference" } }, "title": "CreateFolderResult", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "$ref": "#/$defs/CreateFolderResult" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "CreateFolderOutput", "type": "object" } ``` ### Parameters The ID of the DriveItem to create a sharing link for. The type of sharing link to create. One of: view, edit, embed. The scope of link to create. One of: anonymous, organization. ### Response ```json { "$defs": { "CreateLinkResult": { "additionalProperties": false, "description": "Microsoft Graph response for creating a sharing link (Permission resource).", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "link": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Link" } }, "title": "CreateLinkResult", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "$ref": "#/$defs/CreateLinkResult" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "CreateLinkOutput", "type": "object" } ``` ### Parameters The ID of the file to download. Provide either file_id or file_path. The path to the file from the root folder (e.g. 'Documents/My Subfolder/File 1.docx'). Provide either file_id or file_path. The file name to save the downloaded content as, under /tmp. Include the file extension. Optional format to convert the file to. One of: pdf, html. ### Response ```json { "$defs": { "DownloadFileResult": { "additionalProperties": false, "description": "Result envelope for download_file — file saved to /tmp + base64 content.", "properties": { "tmp_file_path": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Tmp File Path" }, "file_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "File Name" }, "size_bytes": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Size Bytes" }, "content_base64": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Content Base64" } }, "title": "DownloadFileResult", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "$ref": "#/$defs/DownloadFileResult" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "DownloadFileOutput", "type": "object" } ``` ### Parameters The name of the file or folder to search for. Set to true to return only files in the response. Defaults to false. (Default: `false`) ### Response ```json { "$defs": { "DriveItemSummary": { "additionalProperties": false, "description": "A DriveItem (file or folder) as returned by Microsoft Graph.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "webUrl": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Weburl" }, "size": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Size" }, "createdDateTime": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Createddatetime" }, "lastModifiedDateTime": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Lastmodifieddatetime" }, "file": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "File" }, "folder": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Folder" }, "parentReference": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Parentreference" } }, "title": "DriveItemSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Count" }, "items": { "items": { "$ref": "#/$defs/DriveItemSummary" }, "title": "Items", "type": "array" } }, "required": [ "success" ], "title": "FindFileByNameOutput", "type": "object" } ``` ### Parameters The ID of the Excel (.xlsx) file in OneDrive. Name of the table as set in the Table Design tab of Excel. By default, the headers are included as the first row. Set true to remove them. (Default: `false`) Number of rows to return. Leave at 0 to return all rows. (Default: `0`) ### Response ```json { "$defs": { "GetExcelTableResult": { "additionalProperties": false, "description": "Result envelope for get_excel_table — table rows as list[list[str]].", "properties": { "rows": { "items": { "items": { "type": "string" }, "type": "array" }, "title": "Rows", "type": "array" }, "row_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Row Count" } }, "title": "GetExcelTableResult", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "$ref": "#/$defs/GetExcelTableResult" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "GetExcelTableOutput", "type": "object" } ``` ### Parameters The ID of the DriveItem to retrieve. ### Response ```json { "$defs": { "DriveItemSummary": { "additionalProperties": false, "description": "A DriveItem (file or folder) as returned by Microsoft Graph.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "webUrl": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Weburl" }, "size": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Size" }, "createdDateTime": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Createddatetime" }, "lastModifiedDateTime": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Lastmodifieddatetime" }, "file": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "File" }, "folder": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Folder" }, "parentReference": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Parentreference" } }, "title": "DriveItemSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "item": { "anyOf": [ { "$ref": "#/$defs/DriveItemSummary" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "GetFileByIdOutput", "type": "object" } ``` ### Parameters The ID of the folder whose children should be listed. Set true to return only files (omit subfolders). (Default: `false`) ### Response ```json { "$defs": { "DriveItemSummary": { "additionalProperties": false, "description": "A DriveItem (file or folder) as returned by Microsoft Graph.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "webUrl": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Weburl" }, "size": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Size" }, "createdDateTime": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Createddatetime" }, "lastModifiedDateTime": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Lastmodifieddatetime" }, "file": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "File" }, "folder": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Folder" }, "parentReference": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Parentreference" } }, "title": "DriveItemSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Count" }, "items": { "items": { "$ref": "#/$defs/DriveItemSummary" }, "title": "Items", "type": "array" } }, "required": [ "success" ], "title": "ListFilesInFolderOutput", "type": "object" } ``` ### Response ```json { "$defs": { "DriveSummary": { "additionalProperties": false, "description": "A Drive object as returned by Microsoft Graph.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "driveType": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Drivetype" }, "owner": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Owner" }, "quota": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Quota" } }, "title": "DriveSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Count" }, "drives": { "items": { "$ref": "#/$defs/DriveSummary" }, "title": "Drives", "type": "array" } }, "required": [ "success" ], "title": "ListMyDrivesOutput", "type": "object" } ``` ### Response ```json { "$defs": { "SharedFolderReferenceOption": { "additionalProperties": false, "description": "A {label, value} option usable as create_folder.shared_folder_reference.", "properties": { "label": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Label" }, "value": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Value" } }, "title": "SharedFolderReferenceOption", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Count" }, "options": { "items": { "$ref": "#/$defs/SharedFolderReferenceOption" }, "title": "Options", "type": "array" } }, "required": [ "success" ], "title": "ListSharedFolderReferenceOptionsOutput", "type": "object" } ``` ### Parameters The query text used to search for items. Values may be matched across several fields including filename, metadata, and file content. Set true to filter out folders from the results. (Default: `false`) ### Response ```json { "$defs": { "DriveItemSummary": { "additionalProperties": false, "description": "A DriveItem (file or folder) as returned by Microsoft Graph.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "webUrl": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Weburl" }, "size": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Size" }, "createdDateTime": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Createddatetime" }, "lastModifiedDateTime": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Lastmodifieddatetime" }, "file": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "File" }, "folder": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Folder" }, "parentReference": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Parentreference" } }, "title": "DriveItemSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Count" }, "items": { "items": { "$ref": "#/$defs/DriveItemSummary" }, "title": "Items", "type": "array" } }, "required": [ "success" ], "title": "SearchFilesOutput", "type": "object" } ``` ### Parameters The ID of the folder where you want to upload the file. Publicly accessible URL of the file to upload. The integration downloads the content and re-uploads it to OneDrive. Name of the new uploaded file (including extension). ### Response ```json { "$defs": { "UploadFileResult": { "additionalProperties": false, "description": "Microsoft Graph response for an uploaded DriveItem (subset of fields).", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "webUrl": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Weburl" }, "size": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Size" } }, "title": "UploadFileResult", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "$ref": "#/$defs/UploadFileResult" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "UploadFileOutput", "type": "object" } ``` ## Limits & Quotas - Microsoft Graph applies per-app and per-user throttling — bursty workloads may receive `429` responses with `Retry-After` hints. See https://learn.microsoft.com/en-us/graph/throttling for current limits. - `download_file` saves the file under the backend's `/tmp` directory **and** returns the content base64-encoded. For very large files, prefer routing through the workflow-engine layer. - `upload_file` accepts only a publicly-accessible source URL. Uploading from a local `/tmp` path is not supported. - Dynamic dropdowns for folder, file, drive, and table selection are not exposed — supply IDs and names directly. Use `list_my_drives`, `list_files_in_folder`, `search_files`, or `find_file_by_name` to discover IDs. - Webhook-style notifications (new file / new folder created) are not exposed in this integration; route those needs to the workflow-engine layer. - **Error model**: non-2xx responses, timeouts, and unexpected exceptions are caught and returned as `success=False` + `error` rather than raising. Agents should branch on the error string to decide whether to retry or surface to the user. ## Related integrations ## Links - [Microsoft OneDrive](https://onedrive.live.com) --- # Microsoft Outlook Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/microsoft-outlook Send, draft, search, and organize email; manage contacts, folders, and categories in Microsoft Outlook via Microsoft Graph. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/microsoft_outlook/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Microsoft Outlook logo ## Overview Add **Microsoft Outlook** to any ModuleX agent or workflow. Send, draft, search, and organize email; manage contacts, folders, and categories in Microsoft Outlook via Microsoft Graph (`graph.microsoft.com/v1.0`). Uses delegated permissions on behalf of an authenticated Microsoft 365 user. **Categories**: Communication · Email · Productivity · Communication · Microsoft · **Auth**: OAuth2 · **Actions**: 20 ## Authentication ### OAuth2 Authentication Connect with your Microsoft account using OAuth 2.0 against the Microsoft identity platform. Register an application at https://entra.microsoft.com (Azure AD > App registrations) Under 'Authentication', add the redirect URI: https://api.modulex.dev/credentials/oauth2/callback Under 'API permissions', add Microsoft Graph delegated permissions: Mail.ReadWrite, Mail.Send, MailboxSettings.Read, Contacts.ReadWrite, User.Read, User.ReadBasic.All, offline_access Under 'Certificates & secrets', create a new client secret Copy the Application (client) ID and the client secret into ModuleX #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Client ID | Microsoft Entra Application (client) ID for the Microsoft Graph app. | Yes | `00000000-0000-0000-0000-000000000000` | | Client Secret | Microsoft Entra client secret for the Microsoft Graph app. | Yes | `-` | #### OAuth Configuration - **Authorization URL**: `https://login.microsoftonline.com/common/oauth2/v2.0/authorize` - **Token URL**: `https://login.microsoftonline.com/common/oauth2/v2.0/token` - **Scopes**: `offline_access`, `User.Read`, `User.ReadBasic.All`, `Mail.ReadWrite`, `Mail.Send`, `MailboxSettings.Read`, `Contacts.ReadWrite` ## Available Actions ### Parameters The identifier of the message to update. The name of the label/category to add. The User ID of a shared mailbox. If not provided, defaults to the authenticated user's mailbox. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "subject": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Subject" }, "categories": { "items": { "type": "string" }, "title": "Categories", "type": "array" } }, "required": [ "success" ], "title": "AddLabelToEmailOutput", "type": "object" } ``` ### Parameters Array of recipient email addresses. Subject of the approval email. URL the recipient clicks to approve / resume the workflow. URL the recipient clicks to cancel the workflow. The User ID of a shared mailbox. If not provided, defaults to the authenticated user's mailbox. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "sent": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Sent" } }, "required": [ "success" ], "title": "ApproveWorkflowOutput", "type": "object" } ``` ### Parameters Given name of the contact. Surname of the contact. Email addresses for the contact (list of strings). Array of phone numbers (list of strings). Additional contact details to merge into the request body. See https://docs.microsoft.com/en-us/graph/api/resources/contact ### Response ```json { "$defs": { "ContactSummary": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "displayName": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Displayname" }, "givenName": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Givenname" }, "surname": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Surname" }, "emailAddresses": { "items": { "additionalProperties": true, "type": "object" }, "title": "Emailaddresses", "type": "array" }, "businessPhones": { "items": { "type": "string" }, "title": "Businessphones", "type": "array" } }, "title": "ContactSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "contact": { "anyOf": [ { "$ref": "#/$defs/ContactSummary" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "CreateContactOutput", "type": "object" } ``` ### Parameters Array of TO recipient email addresses (list of strings). Array of CC recipient email addresses (list of strings). Array of BCC recipient email addresses (list of strings). Subject of the email. Content type of the body. Valid values: text, html. (Default: `text`) Body content of the email in text or HTML. Array of file URLs to fetch and attach to the message (list of strings). Additional Message resource fields merged into the request. See https://docs.microsoft.com/en-us/graph/api/resources/message The User ID of a shared mailbox. If not provided, defaults to the authenticated user's mailbox. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "subject": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Subject" }, "isDraft": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Isdraft" }, "webLink": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Weblink" } }, "required": [ "success" ], "title": "CreateDraftEmailOutput", "type": "object" } ``` ### Parameters The identifier of the message to reply to. Content of the reply in text format. Array of TO recipient email addresses to override on the draft (list of strings). Array of CC recipient email addresses (list of strings). Array of BCC recipient email addresses (list of strings). Subject override for the reply. Array of file URLs to attach to the reply (list of strings). Additional Message resource fields merged into the message body. The User ID of a shared mailbox. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "subject": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Subject" }, "isDraft": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Isdraft" } }, "required": [ "success" ], "title": "CreateDraftReplyOutput", "type": "object" } ``` ### Parameters The identifier of the message containing the attachment. The identifier of the attachment to download. The User ID of a shared mailbox. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "content_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Content Type" }, "size": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Size" }, "content_bytes_base64": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Content Bytes Base64" } }, "required": [ "success" ], "title": "DownloadAttachmentOutput", "type": "object" } ``` ### Parameters Provide email address, given name, surname, or display name (case sensitive). The maximum number of results to return. (Default: `100`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "count": { "default": 0, "title": "Count", "type": "integer" }, "contacts": { "items": { "additionalProperties": true, "type": "object" }, "title": "Contacts", "type": "array" } }, "required": [ "success" ], "title": "FindContactsOutput", "type": "object" } ``` ### Parameters Search query. Can use property syntax like "to:example@example.com" or "subject:example". Not for use with filter or order_by. OData $filter expression, e.g. contains(subject, 'meet for lunch?'). Not for use with search. OData $orderby expression, e.g. receivedDateTime desc. Not for use with search. Maximum number of messages to return. (Default: `100`) If true, expands attachments on each returned message. (Default: `false`) Comma-separated message property names to return (e.g. id,subject,from,receivedDateTime). Shrinks payload size. The User ID of a shared mailbox. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "count": { "default": 0, "title": "Count", "type": "integer" }, "messages": { "items": { "additionalProperties": true, "type": "object" }, "title": "Messages", "type": "array" } }, "required": [ "success" ], "title": "FindEmailOutput", "type": "object" } ``` ### Parameters The user ID owning the shared folder. The ID of the shared folder to search. Search query. Not for use with filter or order_by. OData $filter expression. OData $orderby expression. Maximum number of messages to return. (Default: `100`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "count": { "default": 0, "title": "Count", "type": "integer" }, "messages": { "items": { "additionalProperties": true, "type": "object" }, "title": "Messages", "type": "array" } }, "required": [ "success" ], "title": "FindSharedFolderEmailOutput", "type": "object" } ``` ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "displayName": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Displayname" }, "mail": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Mail" }, "userPrincipalName": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Userprincipalname" } }, "required": [ "success" ], "title": "GetCurrentUserOutput", "type": "object" } ``` ### Parameters The Microsoft Graph message ID. If true, expands attachments on the returned message. (Default: `false`) The User ID of a shared mailbox. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "message": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Message" } }, "required": [ "success" ], "title": "GetMessageOutput", "type": "object" } ``` ### Parameters If provided, only contacts with the given email address will be retrieved. Maximum number of contacts to return. (Default: `100`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "count": { "default": 0, "title": "Count", "type": "integer" }, "contacts": { "items": { "additionalProperties": true, "type": "object" }, "title": "Contacts", "type": "array" } }, "required": [ "success" ], "title": "ListContactsOutput", "type": "object" } ``` ### Parameters Maximum number of folders to return. (Default: `100`) If true, recursively include subfolders. (Default: `false`) If true, include hidden folders. (Default: `false`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "count": { "default": 0, "title": "Count", "type": "integer" }, "folders": { "items": { "additionalProperties": true, "type": "object" }, "title": "Folders", "type": "array" } }, "required": [ "success" ], "title": "ListFoldersOutput", "type": "object" } ``` ### Parameters Maximum number of messages to return. (Default: `100`) The User ID of a shared mailbox. ### Response ```json { "$defs": { "ImportantMailItem": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "subject": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Subject" }, "sender": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Sender" }, "receivedDateTime": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Receiveddatetime" } }, "title": "ImportantMailItem", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "count": { "default": 0, "title": "Count", "type": "integer" }, "data": { "items": { "$ref": "#/$defs/ImportantMailItem" }, "title": "Data", "type": "array" } }, "required": [ "success" ], "title": "ListImportantMailOutput", "type": "object" } ``` ### Parameters The User ID of a shared mailbox. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "count": { "default": 0, "title": "Count", "type": "integer" }, "labels": { "items": { "additionalProperties": true, "type": "object" }, "title": "Labels", "type": "array" } }, "required": [ "success" ], "title": "ListLabelsOutput", "type": "object" } ``` ### Parameters The identifier of the message to move. The identifier of the destination folder. The User ID of a shared mailbox. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "parentFolderId": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Parentfolderid" } }, "required": [ "success" ], "title": "MoveEmailToFolderOutput", "type": "object" } ``` ### Parameters The identifier of the message to update. The name of the label/category to remove. The User ID of a shared mailbox. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "categories": { "items": { "type": "string" }, "title": "Categories", "type": "array" } }, "required": [ "success" ], "title": "RemoveLabelFromEmailOutput", "type": "object" } ``` ### Parameters The identifier of the message to reply to. Content of the reply in text format. Optional override for TO recipients (list of strings). CC recipient email addresses (list of strings). BCC recipient email addresses (list of strings). Optional subject override. Array of file URLs to attach (list of strings). Additional Message resource fields merged into the reply body. The User ID of a shared mailbox. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "sent": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Sent" } }, "required": [ "success" ], "title": "ReplyToEmailOutput", "type": "object" } ``` ### Parameters TO recipient email addresses (list of strings). CC recipient email addresses (list of strings). BCC recipient email addresses (list of strings). Subject of the email. Content type of the body. Valid values: text, html. (Default: `text`) Body content of the email. Array of file URLs to fetch and attach (list of strings). Additional Message resource fields merged into the request. The User ID of a shared mailbox. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "sent": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Sent" } }, "required": [ "success" ], "title": "SendEmailOutput", "type": "object" } ``` ### Parameters The ID of the contact to update. Given name of the contact. Surname of the contact. Replacement email addresses for the contact (list of strings). Replacement phone numbers (list of strings). Additional contact fields merged into the request body. ### Response ```json { "$defs": { "ContactSummary": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "displayName": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Displayname" }, "givenName": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Givenname" }, "surname": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Surname" }, "emailAddresses": { "items": { "additionalProperties": true, "type": "object" }, "title": "Emailaddresses", "type": "array" }, "businessPhones": { "items": { "type": "string" }, "title": "Businessphones", "type": "array" } }, "title": "ContactSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "contact": { "anyOf": [ { "$ref": "#/$defs/ContactSummary" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "UpdateContactOutput", "type": "object" } ``` ## Limits & Quotas - Microsoft Graph throttles per-app and per-mailbox. Mail endpoints allow approximately 10,000 requests per 10 minutes per app per mailbox; consult Microsoft's throttling guidance for exact tiers. - Search uses Microsoft Graph KQL semantics; combining `search` with `filter` or `order_by` is rejected by Microsoft Graph and short-circuited locally with a `success=False` error. - Attachments are accepted as URLs only; the integration fetches each URL and base64-encodes the bytes for Microsoft Graph's `fileAttachment` payload. `download_attachment` returns base64-encoded bytes rather than writing files to disk. - The `approve_workflow` action does not internally suspend a workflow; the caller must supply both `resume_url` and `cancel_url`. The action only sends the approval email. - Error model: non-2xx responses are caught and returned as `success=False` + `error`; the calling agent can retry or surface the error. ## Related integrations ## Links - [Microsoft Outlook](https://outlook.live.com) --- # Microsoft Power BI Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/microsoft-power-bi Business intelligence and analytics platform for interactive visualizations, reports, and dashboards {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/microsoft_power_bi/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Microsoft Power BI logo ## Overview Add **Microsoft Power BI** to any ModuleX agent or workflow. Business intelligence and analytics platform integration against the Power BI REST API (`api.powerbi.com/v1.0/myorg`). **Categories**: Analytics & Data · Business Intelligence · Analytics · Productivity · **Auth**: OAuth2 (Microsoft Entra ID) · **Actions**: 10 ## Authentication ### OAuth2 (Microsoft Entra ID) Connect using Microsoft OAuth2 (recommended) #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Client ID | Microsoft Entra ID App Registration Client ID | Yes | `-` | | Client Secret | Microsoft Entra ID App Registration Client Secret | Yes | `-` | #### OAuth Configuration - **Authorization URL**: `https://login.microsoftonline.com/common/oauth2/v2.0/authorize` - **Token URL**: `https://login.microsoftonline.com/common/oauth2/v2.0/token` - **Scopes**: `https://analysis.windows.net/powerbi/api/.default`, `offline_access` ## Available Actions ### Parameters ID of the Push Dataset (use list_datasets to find IDs) Exact case-sensitive name of the table within the dataset JSON array of row objects matching the table column schema ID of the workspace (omit for My workspace) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "rows_added": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Rows Added" } }, "required": [ "success" ], "title": "AddRowsToPushDatasetOutput", "type": "object" } ``` ### Parameters ID of the dataset to query (use list_datasets to find IDs) DAX expression starting with EVALUATE ID of the workspace (omit for My workspace) Whether to include null values in the response (Default: `true`) UPN of an effective identity for Row-Level Security ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "results": { "items": { "additionalProperties": true, "type": "object" }, "title": "Results", "type": "array" } }, "required": [ "success" ], "title": "ExecuteDaxQueryOutput", "type": "object" } ``` ### Parameters ID of the report to export (use list_reports to find IDs) Output format: PDF, PPTX, PNG, CSV, XLSX, DOCX, XML, MHTML (Default: `PDF`) ID of the workspace (omit for My workspace) Seconds between export status polls (Default: `5`) Maximum seconds to wait for export completion (Default: `300`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "export_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Export Id" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "report_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Report Id" }, "format": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Format" }, "resource_file_extension": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Resource File Extension" }, "resource_location": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Resource Location" }, "file_size_bytes": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "File Size Bytes" }, "file_base64": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "File Base64" }, "percent_complete": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Percent Complete" } }, "required": [ "success" ], "title": "ExportReportOutput", "type": "object" } ``` ### Parameters ID of the dataset (use list_datasets to find IDs) ID of the workspace (omit for My workspace) Number of most recent refresh entries to return (max 200) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "refreshes": { "items": { "additionalProperties": true, "type": "object" }, "title": "Refreshes", "type": "array" } }, "required": [ "success" ], "title": "GetRefreshHistoryOutput", "type": "object" } ``` ### Parameters Power BI report ID (GUID) Workspace group ID (omit for My workspace) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "web_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Web Url" }, "embed_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Embed Url" }, "dataset_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Dataset Id" }, "report_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Report Type" } }, "required": [ "success" ], "title": "GetReportsByIdOutput", "type": "object" } ``` ### Parameters ID of the workspace (omit for My workspace) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "dashboards": { "items": { "additionalProperties": true, "type": "object" }, "title": "Dashboards", "type": "array" } }, "required": [ "success" ], "title": "ListDashboardsOutput", "type": "object" } ``` ### Parameters ID of the workspace (omit for My workspace) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "datasets": { "items": { "additionalProperties": true, "type": "object" }, "title": "Datasets", "type": "array" } }, "required": [ "success" ], "title": "ListDatasetsOutput", "type": "object" } ``` ### Parameters ID of the workspace (omit for My workspace) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "reports": { "items": { "additionalProperties": true, "type": "object" }, "title": "Reports", "type": "array" } }, "required": [ "success" ], "title": "ListReportsOutput", "type": "object" } ``` ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "workspaces": { "items": { "additionalProperties": true, "type": "object" }, "title": "Workspaces", "type": "array" } }, "required": [ "success" ], "title": "ListWorkspacesOutput", "type": "object" } ``` ### Parameters ID of the dataset to refresh (use list_datasets to find IDs) ID of the workspace (omit for My workspace) Email notification behavior: NoNotification, MailOnCompletion, MailOnFailure (Default: `NoNotification`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "status_code": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Status Code" }, "request_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Request Id" }, "dataset_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Dataset Id" }, "location": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Location" } }, "required": [ "success" ], "title": "RefreshDatasetOutput", "type": "object" } ``` ## Limits & Quotas - Power BI REST API is rate-limited per user/app; exact limits depend on capacity (shared vs Premium). - Dataset refreshes: Pro allows up to 8/day; Premium allows up to 48/day. - DAX query execution timeout: 5 minutes server-side. - Export API: Premium-only; exports may take minutes for large reports. - Error model: non-2xx responses are caught and returned as `success=False` + `error` rather than raising. ## Related integrations ## Links - [Microsoft Power BI](https://powerbi.microsoft.com) --- # Microsoft SQL Server Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/microsoft-sql-server Execute queries and manage data in Microsoft SQL Server databases {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/microsoft_sql_server/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Microsoft SQL Server logo ## Overview Add **Microsoft SQL Server** to any ModuleX agent or workflow. Execute queries and manage data in Microsoft SQL Server databases via direct TCP connections using `pymssql`. **Categories**: Databases · Data Infrastructure · **Auth**: SQL Server Connection · **Actions**: 4 ## Authentication ### SQL Server Connection Connect using SQL Server host, port, database, username, and password #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Host | SQL Server hostname or IP address | Yes | `myserver.database.windows.net` | | Port | SQL Server port number | Yes | `1433` | | Username | SQL Server login username | Yes | `-` | | Password | SQL Server login password | Yes | `-` | | Database | Name of the database to connect to | Yes | `-` | | Encrypt | Whether to encrypt the connection (true/false). Use true for Azure SQL. | No | `true` | | Trust Server Certificate | Whether to trust self-signed certificates (true/false). Use true for local dev. | No | `false` | ## Available Actions ### Parameters The SQL query to execute ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "recordset": { "items": { "additionalProperties": true, "type": "object" }, "title": "Recordset", "type": "array" }, "rows_affected": { "items": { "type": "integer" }, "title": "Rows Affected", "type": "array" } }, "required": [ "success" ], "title": "ExecuteRawQueryOutput", "type": "object" } ``` ### Parameters The SQL query to execute, using @name placeholders for parameters Key-value mapping of parameter names to values for the query placeholders ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "recordset": { "items": { "additionalProperties": true, "type": "object" }, "title": "Recordset", "type": "array" }, "rows_affected": { "items": { "type": "integer" }, "title": "Rows Affected", "type": "array" } }, "required": [ "success" ], "title": "ExecuteQueryOutput", "type": "object" } ``` ### Parameters Name of the table to insert into JSON object mapping column names to values for the new row ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "rows_affected": { "items": { "type": "integer" }, "title": "Rows Affected", "type": "array" } }, "required": [ "success" ], "title": "InsertRowOutput", "type": "object" } ``` ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "tables": { "items": { "type": "string" }, "title": "Tables", "type": "array" } }, "required": [ "success" ], "title": "ListTableOptionsOutput", "type": "object" } ``` ## Limits & Quotas - No API rate limits apply; performance depends on the SQL Server instance capacity and network latency. - Connection timeout defaults to pymssql's default (dependent on server responsiveness). - Query execution is wrapped in `asyncio.to_thread` so it does not block the event loop. - Error model: connection failures, query syntax errors, and timeouts are caught and returned as `success=False` + `error` rather than raising. ## Related integrations ## Links - [Microsoft SQL Server](https://www.microsoft.com/en-us/sql-server) --- # Microsoft Teams Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/microsoft-teams Create channels, send channel and chat messages, list teams/channels/chats/messages/shifts, search messages, and retrieve the current user via Microsoft Graph. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/microsoft_teams/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Microsoft Teams logo ## Overview Add **Microsoft Teams** to any ModuleX agent or workflow. Create channels, send channel and chat messages, list teams / channels / chats / messages / shifts, search messages, and retrieve the current user via the Microsoft Graph REST API (`graph.microsoft.com/v1.0`). **Categories**: Communication · Productivity & Collaboration · **Auth**: OAuth2 · **Actions**: 12 ## Authentication ### OAuth2 Authentication Connect with your Microsoft work or school account using OAuth 2.0. Personal accounts are not supported by Microsoft Teams APIs. Go to https://entra.microsoft.com (Microsoft Entra admin center) and open 'App registrations' Create a new registration (single-tenant or multi-tenant, as required by your deployment) Under Authentication, add the redirect URI: https://api.modulex.dev/credentials/oauth2/callback (type: Web) Under API permissions, add Microsoft Graph delegated permissions: User.Read, Team.ReadBasic.All, Channel.ReadBasic.All, ChannelMessage.Read.All, ChannelMessage.Send, Chat.ReadWrite, ChatMessage.Send, Schedule.Read.All, Mail.Read, and offline_access Grant admin consent for the tenant if your organization requires it Under Certificates & secrets, create a Client Secret and copy its Value Copy the Application (client) ID and the Client Secret Value into ModuleX #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Client ID | Microsoft Entra Application (client) ID for the registered app. | Yes | `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` | | Client Secret | Microsoft Entra Client Secret Value for the registered app. | Yes | `-` | #### OAuth Configuration - **Authorization URL**: `https://login.microsoftonline.com/common/oauth2/v2.0/authorize` - **Token URL**: `https://login.microsoftonline.com/common/oauth2/v2.0/token` - **Scopes**: `offline_access`, `User.Read`, `Team.ReadBasic.All`, `Channel.ReadBasic.All`, `ChannelMessage.Read.All`, `ChannelMessage.Send`, `Chat.ReadWrite`, `ChatMessage.Send`, `Schedule.Read.All`, `Mail.Read` ## Available Actions ### Parameters ID of the Microsoft Team. Obtain it via `list_teams`. Display name of the channel. Description of the channel. ### Response ```json { "$defs": { "ChannelResource": { "additionalProperties": false, "description": "Microsoft Graph Channel resource.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "displayName": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Displayname" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "membershipType": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Membershiptype" }, "webUrl": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Weburl" } }, "title": "ChannelResource", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "channel": { "anyOf": [ { "$ref": "#/$defs/ChannelResource" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "CreateChannelOutput", "type": "object" } ``` ### Parameters ID of the chat. Obtain it via `list_chats`. ID of the message to retrieve. Obtain it via `list_messages_in_chat`. ### Response ```json { "$defs": { "ChatMessageResource": { "additionalProperties": false, "description": "Microsoft Graph chatMessage resource (permissive — upstream returns many optional fields).", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "createdDateTime": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Createddatetime" }, "from": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "From" }, "body": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Body" }, "importance": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Importance" }, "webUrl": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Weburl" } }, "title": "ChatMessageResource", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "message": { "anyOf": [ { "$ref": "#/$defs/ChatMessageResource" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "GetChatMessageOutput", "type": "object" } ``` ### Response ```json { "$defs": { "CurrentUser": { "additionalProperties": false, "description": "Subset of Microsoft Graph user resource returned by get_current_user.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "displayName": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Displayname" }, "mail": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Mail" }, "userPrincipalName": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Userprincipalname" } }, "title": "CurrentUser", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "user": { "anyOf": [ { "$ref": "#/$defs/CurrentUser" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "GetCurrentUserOutput", "type": "object" } ``` ### Parameters ID of the Microsoft Team. ID of the channel within the team. The maximum number of messages to return. Omit to fetch all available pages. ### Response ```json { "$defs": { "ChatMessageResource": { "additionalProperties": false, "description": "Microsoft Graph chatMessage resource (permissive — upstream returns many optional fields).", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "createdDateTime": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Createddatetime" }, "from": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "From" }, "body": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Body" }, "importance": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Importance" }, "webUrl": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Weburl" } }, "title": "ChatMessageResource", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "messages": { "items": { "$ref": "#/$defs/ChatMessageResource" }, "title": "Messages", "type": "array" } }, "required": [ "success" ], "title": "ListChannelMessagesOutput", "type": "object" } ``` ### Parameters ID of the Microsoft Team. ### Response ```json { "$defs": { "ChannelResource": { "additionalProperties": false, "description": "Microsoft Graph Channel resource.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "displayName": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Displayname" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "membershipType": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Membershiptype" }, "webUrl": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Weburl" } }, "title": "ChannelResource", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "channels": { "items": { "$ref": "#/$defs/ChannelResource" }, "title": "Channels", "type": "array" } }, "required": [ "success" ], "title": "ListChannelsOutput", "type": "object" } ``` ### Response ```json { "$defs": { "ChatResource": { "additionalProperties": false, "description": "Microsoft Graph Chat resource (with members expanded).", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "topic": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Topic" }, "chatType": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Chattype" }, "createdDateTime": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Createddatetime" }, "lastUpdatedDateTime": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Lastupdateddatetime" }, "webUrl": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Weburl" }, "members": { "items": { "additionalProperties": true, "type": "object" }, "title": "Members", "type": "array" } }, "title": "ChatResource", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "chats": { "items": { "$ref": "#/$defs/ChatResource" }, "title": "Chats", "type": "array" } }, "required": [ "success" ], "title": "ListChatsOutput", "type": "object" } ``` ### Parameters ID of the chat. The maximum number of results to return. Omit to fetch all available pages. ### Response ```json { "$defs": { "ChatMessageResource": { "additionalProperties": false, "description": "Microsoft Graph chatMessage resource (permissive — upstream returns many optional fields).", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "createdDateTime": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Createddatetime" }, "from": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "From" }, "body": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Body" }, "importance": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Importance" }, "webUrl": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Weburl" } }, "title": "ChatMessageResource", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "messages": { "items": { "$ref": "#/$defs/ChatMessageResource" }, "title": "Messages", "type": "array" } }, "required": [ "success" ], "title": "ListMessagesInChatOutput", "type": "object" } ``` ### Parameters ID of the Microsoft Team whose schedule is being queried. ### Response ```json { "$defs": { "ShiftResource": { "additionalProperties": false, "description": "Microsoft Graph Shift resource.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "userId": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Userid" }, "schedulingGroupId": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Schedulinggroupid" }, "createdDateTime": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Createddatetime" }, "lastModifiedDateTime": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Lastmodifieddatetime" }, "sharedShift": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Sharedshift" }, "draftShift": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Draftshift" } }, "title": "ShiftResource", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "shifts": { "items": { "$ref": "#/$defs/ShiftResource" }, "title": "Shifts", "type": "array" } }, "required": [ "success" ], "title": "ListShiftsOutput", "type": "object" } ``` ### Response ```json { "$defs": { "TeamResource": { "additionalProperties": false, "description": "Microsoft Graph Team resource.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "displayName": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Displayname" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "visibility": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Visibility" }, "webUrl": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Weburl" } }, "title": "TeamResource", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "teams": { "items": { "$ref": "#/$defs/TeamResource" }, "title": "Teams", "type": "array" } }, "required": [ "success" ], "title": "ListTeamsOutput", "type": "object" } ``` ### Parameters The type of entity to search for. Valid values: `message` for email, `chatMessage` for Teams chat messages. The query string to search for. The index of the first result to return. (Default: `0`) The number of results to return (max 25). (Default: `25`) ### Response ```json { "$defs": { "SearchResponse": { "additionalProperties": false, "description": "Microsoft Graph search response wrapper.", "properties": { "value": { "items": { "additionalProperties": true, "type": "object" }, "title": "Value", "type": "array" } }, "title": "SearchResponse", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "anyOf": [ { "$ref": "#/$defs/SearchResponse" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "SearchMessagesOutput", "type": "object" } ``` ### Parameters ID of the Microsoft Team. ID of the channel within the team. Message to be sent. Whether the message body is plain text or HTML. Valid values: `text`, `html`. (Default: `text`) An array of JSON strings, each representing an inline hosted image. Each item must be a JSON object with `@microsoft.graph.temporaryId` (string), `contentBytes` (base64-encoded image data), and `contentType` (MIME type, e.g. `image/png`). Reference each image in HTML message bodies using ``. ### Response ```json { "$defs": { "ChatMessageResource": { "additionalProperties": false, "description": "Microsoft Graph chatMessage resource (permissive — upstream returns many optional fields).", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "createdDateTime": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Createddatetime" }, "from": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "From" }, "body": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Body" }, "importance": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Importance" }, "webUrl": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Weburl" } }, "title": "ChatMessageResource", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "message": { "anyOf": [ { "$ref": "#/$defs/ChatMessageResource" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "SendChannelMessageOutput", "type": "object" } ``` ### Parameters ID of the chat to post the message to. Message to be sent. Whether the message body is plain text or HTML. Valid values: `text`, `html`. (Default: `text`) ### Response ```json { "$defs": { "ChatMessageResource": { "additionalProperties": false, "description": "Microsoft Graph chatMessage resource (permissive — upstream returns many optional fields).", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "createdDateTime": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Createddatetime" }, "from": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "From" }, "body": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Body" }, "importance": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Importance" }, "webUrl": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Weburl" } }, "title": "ChatMessageResource", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "message": { "anyOf": [ { "$ref": "#/$defs/ChatMessageResource" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "SendChatMessageOutput", "type": "object" } ``` ## Limits & Quotas - Microsoft Graph applies per-app, per-tenant, and per-resource throttling. Teams messaging endpoints have specific RU-based limits documented at https://learn.microsoft.com/en-us/graph/throttling. Retry on HTTP 429 honoring the `Retry-After` header. - `search_messages` returns at most 25 results per call (upper bound enforced by the Microsoft Graph search API for this entity-type set). Use the `from_` parameter to page beyond the first window. - Dynamic team / channel / chat / message pickers are not available — pass IDs directly; use the corresponding `list_*` action to enumerate IDs. - Webhook / change-notification subscriptions are not part of this integration; polling via the `list_*` actions is the supported pattern. - Error model: non-2xx responses and timeouts are caught and returned as `success=False` plus an `error` string rather than raising. Plan for retries on the agent side based on the error text. ## Related integrations ## Links - [Microsoft Teams](https://www.microsoft.com/microsoft-teams) --- # Mintlify Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/mintlify Documentation platform with AI-powered assistant, semantic search, and project update triggers. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/mintlify/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Mintlify logo ## Overview Add **Mintlify** to any ModuleX agent or workflow. Documentation platform with AI-powered assistant chat, semantic search, and project update triggers against the Mintlify REST APIs (`api-dsc.mintlify.com/v1` and `api.mintlify.com/v1`). **Categories**: Developer Tools & Infrastructure · Documentation · **Auth**: Mintlify API Keys · **Actions**: 3 ## Authentication ### Mintlify API Keys Authenticate using Mintlify assistant API key, admin API key, and project ID. Different actions use different credentials. #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Assistant API Key | Your Mintlify Assistant API key for chat and search endpoints. | Yes | `-` | | Admin API Key | Your Mintlify Admin API key for triggering project updates. | Yes | `-` | | Project ID | Your Mintlify project ID used for update triggers. | Yes | `-` | ## Available Actions ### Parameters The domain identifier from your domain.mintlify.app URL. Can be found in the top left of your dashboard. Browser fingerprint or arbitrary string identifier for message tracking. The content of the message to send to the assistant. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "message_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message Id" }, "response": { "anyOf": [ {}, { "type": "null" } ], "default": null, "title": "Response" } }, "required": [ "success" ], "title": "ChatWithAssistantOutput", "type": "object" } ``` ### Parameters The domain identifier from your domain.mintlify.app URL. Can be found in the top left of your dashboard. The search query to execute against your documentation content. Number of search results to return. Defaults to 10 if not specified. Filter results by documentation version. Filter results by content language. ### Response ```json { "$defs": { "SearchResultItem": { "additionalProperties": false, "description": "A single search result from the documentation.", "properties": { "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" }, "content": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Content" }, "score": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Score" } }, "title": "SearchResultItem", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "results": { "items": { "$ref": "#/$defs/SearchResultItem" }, "title": "Results", "type": "array" }, "total": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Total" } }, "required": [ "success" ], "title": "SearchDocumentationOutput", "type": "object" } ``` ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "TriggerUpdateOutput", "type": "object" } ``` ## Limits & Quotas - No publicly documented rate limits for the Mintlify API. - Error model: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. ## Related integrations ## Links - [Mintlify](https://www.mintlify.com) --- # Mixpanel Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/mixpanel Product analytics platform for tracking user events and behaviors {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/mixpanel/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Mixpanel logo ## Overview Add **Mixpanel** to any ModuleX agent or workflow. Product analytics platform for tracking user events and behaviors via the Mixpanel Track API (`api.mixpanel.com`). **Categories**: Analytics & Data · Analytics · Product Analytics · **Auth**: Project Token · **Actions**: 1 ## Authentication ### Project Token Authenticate using your Mixpanel Project Token for event ingestion Sign in at https://mixpanel.com Go to Settings > Project Settings Copy your Project Token Paste the token below #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Project Token | Your Mixpanel Project Token from Settings > Project Settings | Yes | `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | | Ingestion Base URL | Mixpanel ingestion base URL (optional — leave empty for https://api.mixpanel.com US default). Set to https://api-eu.mixpanel.com for EU data residency projects. | No | `https://api.mixpanel.com` | ## Available Actions ### Parameters The name of the event (e.g. 'Button Click', 'Sign Up', 'Item Purchased') The unique identifier for the user performing the event A set of properties to include with the event describing the user or event details ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "distinct_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Distinct Id" }, "properties": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Properties" } }, "required": [ "success" ], "title": "EmitEventToOutput", "type": "object" } ``` ## Limits & Quotas - No documented per-token rate limits for the Track API ingestion endpoint. - Mixpanel recommends batching events (up to 2000 per request) for high-volume use cases; this integration sends one event per call. - Error model: non-2xx responses and Mixpanel rejections (response body `0`) are caught and returned as `success=False` + `error` rather than raising. ## Related integrations ## Links - [Mixpanel](https://mixpanel.com) --- # Monday.com Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/monday Monday.com work management platform for boards, items, columns, and updates via the GraphQL API {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/monday/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Monday.com logo ## Overview Add **Monday.com** to any ModuleX agent or workflow. Work management platform for creating and managing boards, items, columns, groups, and updates via the Monday.com GraphQL API (`api.monday.com/v2`). **Categories**: Project & Task Management · Productivity & Collaboration · Project Management · **Auth**: OAuth2, API Key · **Actions**: 13 ## Authentication ### OAuth2 Authentication Connect using Monday.com OAuth (recommended for most use cases) #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Client ID | Monday.com OAuth App Client ID | Yes | `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | | Client Secret | Monday.com OAuth App Client Secret | Yes | `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | #### OAuth Configuration - **Authorization URL**: `https://auth.monday.com/oauth2/authorize` - **Token URL**: `https://auth.monday.com/oauth2/token` - **Scopes**: `me:read`, `boards:read`, `boards:write`, `workspaces:read` ### API Key Authentication Authenticate using your Monday.com API token Sign in to your Monday.com account Click your avatar (bottom left) and select 'Developers' Go to 'My Access Tokens' section Copy your personal API token or generate a new one #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Monday.com API Token | Your personal API token from Monday.com developer settings | Yes | `eyJhbGciOiJIUzI1NiJ9...` | ## Available Actions ### Parameters The new board's name The new board's kind: public, private, or share Workspace ID to create the board in. If not specified, the Main Workspace is used Folder ID to create the board in Template board ID to use for the new board ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" } }, "required": [ "success" ], "title": "CreateBoardOutput", "type": "object" } ``` ### Parameters The ID of the board to create the column in The title of the new column The type of column: auto_number, checkbox, country, color_picker, creation_log, date, dependency, dropdown, email, file, hour, item_id, last_updated, link, location, long_text, numbers, people, phone, progress, rating, status, team, tags, text, timeline, time_tracking, vote, week, world_clock Custom labels JSON for status/dropdown columns, e.g. {"1": "Technology", "2": "Marketing"} Description of the new column ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" } }, "required": [ "success" ], "title": "CreateColumnOutput", "type": "object" } ``` ### Parameters The ID of the board to create the group in The name of the new group ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" } }, "required": [ "success" ], "title": "CreateGroupOutput", "type": "object" } ``` ### Parameters The ID of the board to create the item in The new item's name Group ID to create the item in Create Status/Dropdown labels if missing (requires permission to change board structure) JSON string of column values for the new item. See Monday.com Column Type Reference for format details ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" } }, "required": [ "success" ], "title": "CreateItemOutput", "type": "object" } ``` ### Parameters The ID of the board containing the parent item The ID of the parent item to create the subitem under The new subitem's name JSON string of column values for the new subitem ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" } }, "required": [ "success" ], "title": "CreateSubitemOutput", "type": "object" } ``` ### Parameters The ID of the board containing the item The ID of the item to add the update to The update text content The ID of a parent update to reply to ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" } }, "required": [ "success" ], "title": "CreateUpdateOutput", "type": "object" } ``` ### Parameters The ID of the board to retrieve items from JSON object with filter/sort parameters in ItemsQuery format Maximum number of pages to fetch (default 50) (Default: `50`) ### Response ```json { "$defs": { "ColumnValue": { "additionalProperties": false, "description": "A column value returned for an item.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "value": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Value" }, "text": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Text" } }, "title": "ColumnValue", "type": "object" }, "ItemSummary": { "additionalProperties": false, "description": "An item returned from a Monday.com board.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "column_values": { "items": { "$ref": "#/$defs/ColumnValue" }, "title": "Column Values", "type": "array" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "creator_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Creator Id" }, "email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email" }, "relative_link": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Relative Link" }, "state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "State" }, "updated_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated At" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" } }, "title": "ItemSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "items": { "items": { "$ref": "#/$defs/ItemSummary" }, "title": "Items", "type": "array" } }, "required": [ "success" ], "title": "GetBoardItemsPageOutput", "type": "object" } ``` ### Parameters The ID of the board The ID of the item to retrieve column values from List of column IDs to retrieve. If empty, returns all columns. Element type: string ### Response ```json { "$defs": { "ColumnValue": { "additionalProperties": false, "description": "A column value returned for an item.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "value": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Value" }, "text": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Text" } }, "title": "ColumnValue", "type": "object" }, "ItemSummary": { "additionalProperties": false, "description": "An item returned from a Monday.com board.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "column_values": { "items": { "$ref": "#/$defs/ColumnValue" }, "title": "Column Values", "type": "array" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "creator_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Creator Id" }, "email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email" }, "relative_link": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Relative Link" }, "state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "State" }, "updated_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated At" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" } }, "title": "ItemSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "items": { "items": { "$ref": "#/$defs/ItemSummary" }, "title": "Items", "type": "array" } }, "required": [ "success" ], "title": "GetColumnValuesOutput", "type": "object" } ``` ### Parameters The ID of the board to search in The ID of the column to search The value to search for in the specified column Maximum number of pages to fetch (default 50) (Default: `50`) ### Response ```json { "$defs": { "ColumnValue": { "additionalProperties": false, "description": "A column value returned for an item.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "value": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Value" }, "text": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Text" } }, "title": "ColumnValue", "type": "object" }, "ItemSummary": { "additionalProperties": false, "description": "An item returned from a Monday.com board.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "column_values": { "items": { "$ref": "#/$defs/ColumnValue" }, "title": "Column Values", "type": "array" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "creator_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Creator Id" }, "email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email" }, "relative_link": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Relative Link" }, "state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "State" }, "updated_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated At" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" } }, "title": "ItemSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "items": { "items": { "$ref": "#/$defs/ItemSummary" }, "title": "Items", "type": "array" } }, "required": [ "success" ], "title": "GetItemsByColumnValueOutput", "type": "object" } ``` ### Parameters Filter to specific board IDs. Element type: string Filter to boards in specific workspace IDs. Element type: integer Filter by board kind: public, private, or share Filter by state: active, archived, deleted, or all (Default: `all`) Sort by: created_at or used_at (Default: `created_at`) Maximum number of boards to return per page (Default: `25`) Page number to return (Default: `1`) ### Response ```json { "$defs": { "MondayBoard": { "additionalProperties": false, "description": "A board object from Monday.com.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "State" }, "board_kind": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Board Kind" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "workspace_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Workspace Id" } }, "title": "MondayBoard", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "boards": { "items": { "$ref": "#/$defs/MondayBoard" }, "title": "Boards", "type": "array" } }, "required": [ "success" ], "title": "ListBoardsOutput", "type": "object" } ``` ### Response ```json { "$defs": { "WorkspaceOption": { "additionalProperties": false, "description": "A workspace option with label and value.", "properties": { "label": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Label" }, "value": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Value" } }, "title": "WorkspaceOption", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "workspaces": { "items": { "$ref": "#/$defs/WorkspaceOption" }, "title": "Workspaces", "type": "array" } }, "required": [ "success" ], "title": "ListWorkspacesOutput", "type": "object" } ``` ### Parameters The ID of the board containing the item The ID of the item to update JSON string of column values to update. Keys are column IDs, values are new values per Column Types Reference ### Response ```json { "$defs": { "ColumnValue": { "additionalProperties": false, "description": "A column value returned for an item.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "value": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Value" }, "text": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Text" } }, "title": "ColumnValue", "type": "object" }, "ItemSummary": { "additionalProperties": false, "description": "An item returned from a Monday.com board.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "column_values": { "items": { "$ref": "#/$defs/ColumnValue" }, "title": "Column Values", "type": "array" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "creator_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Creator Id" }, "email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email" }, "relative_link": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Relative Link" }, "state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "State" }, "updated_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated At" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" } }, "title": "ItemSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "items": { "items": { "$ref": "#/$defs/ItemSummary" }, "title": "Items", "type": "array" } }, "required": [ "success" ], "title": "UpdateColumnValuesOutput", "type": "object" } ``` ### Parameters The ID of the board containing the item The ID of the item to rename The new item name ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" } }, "required": [ "success" ], "title": "UpdateItemNameOutput", "type": "object" } ``` ## Limits & Quotas - **Rate limits**: Monday.com uses a complexity-based rate limit system. Each query has a complexity cost; the default limit is 10,000,000 complexity points per minute. - **Pagination**: Board items are paginated with a default page size of 500 items per request. The integration handles cursor-based pagination internally for `get_board_items_page` and `get_items_by_column_value`. - **Error model**: GraphQL errors and non-2xx responses are caught and returned as `success=False` + `error` rather than raising. ## Related integrations ## Links - [Monday.com](https://monday.com) --- # Motion Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/motion AI-powered task and project management platform for automatic scheduling {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/motion/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Motion logo Motion logo ## Overview Add **Motion** to any ModuleX agent or workflow. AI-powered task and project management platform with automatic scheduling, accessed via the Motion REST API (`api.usemotion.com/v1`). **Categories**: Project & Task Management · Productivity & Collaboration · Project Management · **Auth**: API Key · **Actions**: 6 ## Authentication ### API Key Authentication Authenticate using your Motion API key Go to https://app.usemotion.com and sign in Navigate to Settings > API Generate a new API key or copy your existing one Paste the API key below #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Motion API Key | Your Motion API key from the Settings > API page | Yes | `xxxxxxxxxxxxxxxxxxxxx` | ## Available Actions ### Parameters The ID of the workspace Name / title of the task The ID of the project to assign the task to ISO 8601 due date. Required for scheduled tasks. Example: 2023-06-28T10:11:14.320-06:00 Duration: NONE, REMINDER, or an integer greater than 0 Task description in GitHub Flavored Markdown Priority level: ASAP, HIGH, MEDIUM, LOW (Default: `MEDIUM`) The user ID to assign the task to List of label names to add to the task The name of the task status ISO 8601 date for auto-scheduled tasks. Example: 2023-06-28 Deadline type for auto-scheduled tasks: HARD, SOFT, NONE Schedule the task must adhere to. Must be 'Work Hours' if scheduling for another user (Default: `Work Hours`) ### Response ```json { "$defs": { "TaskObject": { "additionalProperties": false, "description": "A Motion task object returned by task-related actions.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "status": { "anyOf": [ { "$ref": "#/$defs/TaskStatus" }, { "type": "null" } ], "default": null }, "workspace_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Workspace Id" }, "project_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Project Id" }, "priority": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Priority" }, "assignee_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Assignee Id" }, "labels": { "items": { "type": "string" }, "title": "Labels", "type": "array" }, "due_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Due Date" }, "duration": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Duration" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" } }, "title": "TaskObject", "type": "object" }, "TaskStatus": { "additionalProperties": false, "description": "Status information for a task.", "properties": { "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "is_default_status": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Default Status" }, "is_resolved_status": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Resolved Status" } }, "title": "TaskStatus", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "task": { "anyOf": [ { "$ref": "#/$defs/TaskObject" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "CreateTaskOutput", "type": "object" } ``` ### Parameters The ID of the task to delete ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" } }, "required": [ "success" ], "title": "DeleteTaskOutput", "type": "object" } ``` ### Response ```json { "$defs": { "ScheduleItem": { "additionalProperties": false, "description": "A schedule object returned by the get_schedules action.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "timezone": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Timezone" }, "is_default": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Default" } }, "title": "ScheduleItem", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "schedules": { "items": { "$ref": "#/$defs/ScheduleItem" }, "title": "Schedules", "type": "array" } }, "required": [ "success" ], "title": "GetSchedulesOutput", "type": "object" } ``` ### Parameters The ID of the task to retrieve ### Response ```json { "$defs": { "TaskObject": { "additionalProperties": false, "description": "A Motion task object returned by task-related actions.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "status": { "anyOf": [ { "$ref": "#/$defs/TaskStatus" }, { "type": "null" } ], "default": null }, "workspace_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Workspace Id" }, "project_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Project Id" }, "priority": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Priority" }, "assignee_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Assignee Id" }, "labels": { "items": { "type": "string" }, "title": "Labels", "type": "array" }, "due_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Due Date" }, "duration": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Duration" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" } }, "title": "TaskObject", "type": "object" }, "TaskStatus": { "additionalProperties": false, "description": "Status information for a task.", "properties": { "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "is_default_status": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Default Status" }, "is_resolved_status": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Resolved Status" } }, "title": "TaskStatus", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "task": { "anyOf": [ { "$ref": "#/$defs/TaskObject" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "GetTaskOutput", "type": "object" } ``` ### Parameters The ID of the task to move The ID of the target workspace The user ID to assign the task to in the target workspace ### Response ```json { "$defs": { "TaskObject": { "additionalProperties": false, "description": "A Motion task object returned by task-related actions.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "status": { "anyOf": [ { "$ref": "#/$defs/TaskStatus" }, { "type": "null" } ], "default": null }, "workspace_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Workspace Id" }, "project_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Project Id" }, "priority": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Priority" }, "assignee_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Assignee Id" }, "labels": { "items": { "type": "string" }, "title": "Labels", "type": "array" }, "due_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Due Date" }, "duration": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Duration" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" } }, "title": "TaskObject", "type": "object" }, "TaskStatus": { "additionalProperties": false, "description": "Status information for a task.", "properties": { "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "is_default_status": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Default Status" }, "is_resolved_status": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Resolved Status" } }, "title": "TaskStatus", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "task": { "anyOf": [ { "$ref": "#/$defs/TaskObject" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "MoveWorkspaceOutput", "type": "object" } ``` ### Parameters The ID of the task to update New name / title for the task ISO 8601 due date. Example: 2023-06-28T10:11:14.320-06:00 Duration: NONE, REMINDER, or an integer greater than 0 The ID of the project to assign the task to Task description in GitHub Flavored Markdown Priority level: ASAP, HIGH, MEDIUM, LOW (Default: `MEDIUM`) The user ID to assign the task to List of label names to add to the task The name of the task status ISO 8601 date for auto-scheduled tasks. Example: 2023-06-28 Deadline type for auto-scheduled tasks: HARD, SOFT, NONE Schedule the task must adhere to. Must be 'Work Hours' if scheduling for another user (Default: `Work Hours`) ### Response ```json { "$defs": { "TaskObject": { "additionalProperties": false, "description": "A Motion task object returned by task-related actions.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "status": { "anyOf": [ { "$ref": "#/$defs/TaskStatus" }, { "type": "null" } ], "default": null }, "workspace_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Workspace Id" }, "project_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Project Id" }, "priority": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Priority" }, "assignee_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Assignee Id" }, "labels": { "items": { "type": "string" }, "title": "Labels", "type": "array" }, "due_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Due Date" }, "duration": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Duration" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" } }, "title": "TaskObject", "type": "object" }, "TaskStatus": { "additionalProperties": false, "description": "Status information for a task.", "properties": { "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "is_default_status": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Default Status" }, "is_resolved_status": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Resolved Status" } }, "title": "TaskStatus", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "task": { "anyOf": [ { "$ref": "#/$defs/TaskObject" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "UpdateTaskOutput", "type": "object" } ``` ## Limits & Quotas - **Rate limit**: 12 requests per minute per API key (Motion API documentation). - **Pricing**: API access requires a Motion Individual or Team plan. - **Error model**: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. ## Related integrations ## Links - [Motion](https://www.usemotion.com) --- # MySQL Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/mysql MySQL database integration for executing SQL queries, managing tables, and performing CRUD operations. Uses the aiomysql driver. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/mysql/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} MySQL logo ## Overview Add **MySQL** to any ModuleX agent or workflow. MySQL database integration via the `aiomysql` driver. Provides raw SQL execution + CRUD + stored procedures + introspection. **Categories**: Databases · Database · Data Management · Storage · **Auth**: MySQL Database Credentials · **Actions**: 9 ## Authentication ### MySQL Database Credentials Authenticate using MySQL connection credentials (host/port/user/password/database). #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Host | MySQL server hostname or IP | Yes | `localhost or mysql.example.com` | | Port | MySQL server port (defaults to 3306 if omitted) | No | `3306` | | Username | MySQL username for authentication | Yes | `-` | | Password | MySQL password for authentication | Yes | `-` | | Database | The database name to connect to | Yes | `-` | | SSL Mode | SSL mode: 'disabled', 'verify' (default), or 'skip_verification' | No | `verify | skip_verification | disabled` | ## Available Actions ### Parameters The SQL to execute Values for '%s' placeholders ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "row_count": { "default": 0, "title": "Row Count", "type": "integer" }, "data": { "items": { "additionalProperties": true, "type": "object" }, "title": "Data", "type": "array" } }, "required": [ "success" ], "title": "ExecuteRawQueryOutput", "type": "object" } ``` ### Parameters The table name Column-value pairs to insert ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "table": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Table" }, "affected_rows": { "default": 0, "title": "Affected Rows", "type": "integer" }, "last_insert_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Last Insert Id" }, "columns": { "items": { "type": "string" }, "title": "Columns", "type": "array" } }, "required": [ "success" ], "title": "CreateRowOutput", "type": "object" } ``` ### Parameters The table name WHERE condition with '?' placeholders Values matching the '?' placeholders ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "table": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Table" }, "affected_rows": { "default": 0, "title": "Affected Rows", "type": "integer" } }, "required": [ "success" ], "title": "DeleteRowOutput", "type": "object" } ``` ### Parameters The table name Column-value pairs to update WHERE condition with '?' placeholders Values matching the '?' placeholders ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "table": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Table" }, "affected_rows": { "default": 0, "title": "Affected Rows", "type": "integer" }, "updated_columns": { "items": { "type": "string" }, "title": "Updated Columns", "type": "array" } }, "required": [ "success" ], "title": "UpdateRowOutput", "type": "object" } ``` ### Parameters The table name Column to filter by =, >, >=, <, !=, <=, LIKE (Default: `=`) Value to compare against ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "table": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Table" }, "row_count": { "default": 0, "title": "Row Count", "type": "integer" }, "data": { "items": { "additionalProperties": true, "type": "object" }, "title": "Data", "type": "array" } }, "required": [ "success" ], "title": "FindRowOutput", "type": "object" } ``` ### Parameters The table name WHERE condition with '?' placeholders Values matching the '?' placeholders ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "table": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Table" }, "row_count": { "default": 0, "title": "Row Count", "type": "integer" }, "data": { "items": { "additionalProperties": true, "type": "object" }, "title": "Data", "type": "array" } }, "required": [ "success" ], "title": "ExecuteQueryWithConditionOutput", "type": "object" } ``` ### Parameters Procedure name (may be qualified 'db.proc') Procedure parameters ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "procedure": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Procedure" }, "row_count": { "default": 0, "title": "Row Count", "type": "integer" }, "data": { "anyOf": [ { "items": { "additionalProperties": true, "type": "object" }, "type": "array" }, { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "ExecuteStoredProcedureOutput", "type": "object" } ``` ### Response ```json { "$defs": { "TableEntry": { "additionalProperties": false, "properties": { "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Type" } }, "title": "TableEntry", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "database": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Database" }, "tables": { "items": { "$ref": "#/$defs/TableEntry" }, "title": "Tables", "type": "array" }, "count": { "default": 0, "title": "Count", "type": "integer" } }, "required": [ "success" ], "title": "ListTablesOutput", "type": "object" } ``` ### Parameters The table name ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "table": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Table" }, "columns": { "items": { "additionalProperties": true, "type": "object" }, "title": "Columns", "type": "array" }, "column_count": { "default": 0, "title": "Column Count", "type": "integer" } }, "required": [ "success" ], "title": "DescribeTableOutput", "type": "object" } ``` ## Limits & Quotas - 60s connection timeout. - Connections are not pooled — opened per call, closed via the context manager. - `find_row` operator allowlist: `=`, `>`, `>=`, `<`, `!=`, `<=`, `LIKE`. - `aiomysql` is imported lazily so the manifest can be inspected without the driver installed. ## Related integrations ## Links - [MySQL](https://www.mysql.com/) --- # Nasdaq Data Link Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/nasdaq Access comprehensive financial data from Nasdaq Data Link E360 platform including balance sheets, cash flows, fundamentals, company statistics, and reference data for publicly traded companies. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/nasdaq/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Nasdaq Data Link logo ## Overview Add **Nasdaq Data Link** to any ModuleX agent or workflow. Read-only access to the Nasdaq Data Link E360 platform via the official ``nasdaqdatalink`` Python SDK. Covers balance sheets, cash flows, company stats, fundamental details/summary, and reference data for publicly traded companies. **Categories**: Finance & Payments · Finance · Data · Research · Analytics · **Auth**: API Key · **Actions**: 7 ## Authentication ### API Key Authentication Authenticate using your Nasdaq Data Link API key #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Nasdaq Data Link API Key | Your Nasdaq Data Link API key for authentication | Yes | `xxxxxxxxxxxxxxxxxxxx` | ## Available Actions ### Parameters Stock ticker symbol (e.g. 'AAPL', 'MSFT') Bloomberg FIGI identifier (e.g. 'BBG000BPH459') Calendar date in YYYY-MM-DD format Dimensional view: MRQ (Quarterly), MRY (Annual), or MRT (Trailing-twelve-months) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "records": { "items": { "additionalProperties": true, "type": "object" }, "title": "Records", "type": "array" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" } }, "required": [ "success" ], "title": "GetBalanceSheetOutput", "type": "object" } ``` ### Parameters Stock ticker symbol (e.g. 'AAPL', 'MSFT') Bloomberg FIGI identifier (e.g. 'BBG000BPH459') Calendar date in YYYY-MM-DD format Dimensional view: MRQ (Quarterly), MRY (Annual), or MRT (Trailing-twelve-months) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "records": { "items": { "additionalProperties": true, "type": "object" }, "title": "Records", "type": "array" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" } }, "required": [ "success" ], "title": "GetCashFlowOutput", "type": "object" } ``` ### Parameters Stock ticker symbol (e.g. 'AAPL', 'MSFT') Bloomberg FIGI identifier (e.g. 'BBG000BPH459') ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "records": { "items": { "additionalProperties": true, "type": "object" }, "title": "Records", "type": "array" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" } }, "required": [ "success" ], "title": "GetCompanyStatsOutput", "type": "object" } ``` ### Parameters Stock ticker symbol (e.g. 'AAPL', 'MSFT') Bloomberg FIGI identifier (e.g. 'BBG000BPH459') Calendar date in YYYY-MM-DD format Dimensional view: MRQ (Quarterly), MRY (Annual), or MRT (Trailing-twelve-months) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "records": { "items": { "additionalProperties": true, "type": "object" }, "title": "Records", "type": "array" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" } }, "required": [ "success" ], "title": "GetFundamentalDetailsOutput", "type": "object" } ``` ### Parameters Stock ticker symbol (e.g. 'AAPL', 'MSFT') Bloomberg FIGI identifier (e.g. 'BBG000BPH459') Calendar date in YYYY-MM-DD format Dimensional view: MRQ (Quarterly), MRY (Annual), or MRT (Trailing-twelve-months) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "records": { "items": { "additionalProperties": true, "type": "object" }, "title": "Records", "type": "array" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" } }, "required": [ "success" ], "title": "GetFundamentalSummaryOutput", "type": "object" } ``` ### Parameters Stock ticker symbol (e.g. 'AAPL', 'MSFT') Bloomberg FIGI identifier (e.g. 'BBG000BPH459') ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "records": { "items": { "additionalProperties": true, "type": "object" }, "title": "Records", "type": "array" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" } }, "required": [ "success" ], "title": "GetReferenceDataOutput", "type": "object" } ``` ### Parameters Table type to list fields for: 'balance_sheet', 'cash_flow', 'company_stats', 'fundamental_details', 'fundamental_summary', 'reference_data' ### Response ```json { "$defs": { "NasdaqField": { "additionalProperties": false, "properties": { "name": { "title": "Name", "type": "string" }, "description": { "title": "Description", "type": "string" }, "type": { "title": "Type", "type": "string" }, "filterable": { "default": false, "title": "Filterable", "type": "boolean" } }, "required": [ "name", "description", "type" ], "title": "NasdaqField", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "table_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Table Type" }, "fields": { "items": { "$ref": "#/$defs/NasdaqField" }, "title": "Fields", "type": "array" } }, "required": [ "success" ], "title": "ListAvailableFieldsOutput", "type": "object" } ``` ## Limits & Quotas - Subscription-gated. E360 endpoints require an active Nasdaq Data Link subscription on the NDAQ table family. - DataFrame results are converted to JSON-safe lists of dicts (`NaN` → `None`). Empty DataFrames return `records=[]` with a `message: "No data found..."` (matches legacy). - Lazy SDK import — if `nasdaq-data-link` isn't installed, every table-query tool degrades to `success=False` with an "install with: pip install nasdaq-data-link" error. ## Related integrations ## Links - [Nasdaq Data Link](https://data.nasdaq.com) --- # Netlify Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/netlify Web hosting and automation platform for modern web projects {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/netlify/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Netlify logo ## Overview Add **Netlify** to any ModuleX agent or workflow. Web hosting and automation platform for modern web projects, interfacing with the Netlify REST API (`api.netlify.com/api/v1`). **Categories**: Developer Tools & Infrastructure · Hosting · Ci Cd · **Auth**: OAuth2 · **Actions**: 4 ## Authentication ### OAuth2 Authentication Connect using Netlify OAuth (recommended) #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Client ID | Netlify OAuth App Client ID | Yes | `-` | | Client Secret | Netlify OAuth App Client Secret | Yes | `-` | #### OAuth Configuration - **Authorization URL**: `https://app.netlify.com/authorize` - **Token URL**: `https://api.netlify.com/oauth/token` ## Available Actions ### Parameters The Netlify site ID to retrieve information for ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" }, "ssl_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Ssl Url" }, "admin_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Admin Url" }, "state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "State" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "updated_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated At" }, "default_domain": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Default Domain" }, "custom_domain": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Custom Domain" } }, "required": [ "success" ], "title": "GetSiteOutput", "type": "object" } ``` ### Parameters The Netlify site ID to list files for ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "files": { "items": { "additionalProperties": true, "type": "object" }, "title": "Files", "type": "array" } }, "required": [ "success" ], "title": "ListFilesOutput", "type": "object" } ``` ### Parameters The Netlify site ID to list deploys for Maximum number of deploys to return Maximum number of pages to fetch (1-500) (Default: `50`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "deploys": { "items": { "additionalProperties": true, "type": "object" }, "title": "Deploys", "type": "array" } }, "required": [ "success" ], "title": "ListSiteDeploysOutput", "type": "object" } ``` ### Parameters The Netlify site ID to rollback a deploy for The deploy ID to restore ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "State" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" }, "ssl_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Ssl Url" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "updated_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated At" } }, "required": [ "success" ], "title": "RollbackDeployOutput", "type": "object" } ``` ## Limits & Quotas - Netlify API rate limit: 500 requests per minute per access token (as documented by Netlify). - Non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. - Deploy operations (rollback) may take a few seconds to propagate. ## Related integrations ## Links - [Netlify](https://www.netlify.com) --- # NeverBounce Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/neverbounce Integrate NeverBounce to verify email deliverability in real time — classify addresses as valid, invalid, catch-all, disposable, or unknown — and check your remaining verification credits. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/neverbounce/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} NeverBounce logo ## Overview Add **NeverBounce** to any ModuleX agent or workflow. Real-time email verification and account credit lookup against the NeverBounce v4 REST API (`api.neverbounce.com/v4`). Classifies an address as valid, invalid, catch-all, disposable, or unknown and surfaces role-account and free-provider flags. **Categories**: Sales · Enrichment · Sales Engagement · **Auth**: API Key · **Actions**: 2 ## Authentication ### API Key Authentication Authenticate using your NeverBounce API key Go to https://app.neverbounce.com and sign up or log in Open Settings and select the 'API' section Create a new API key or copy your existing one Paste the API key below #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | NeverBounce API Key | Your NeverBounce API key from app.neverbounce.com | Yes | `secret_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | ## Available Actions ### Parameters Email address to verify (e.g., john@example.com) ### Response ```json { "additionalProperties": false, "description": "Result of verifying a single email address.", "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "deliverable": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Deliverable" }, "role_account": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Role Account" }, "free_email": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Free Email" }, "did_you_mean": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Did You Mean" }, "flags": { "items": { "type": "string" }, "title": "Flags", "type": "array" }, "result": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Result" } }, "required": [ "success" ], "title": "VerifyEmailOutput", "type": "object" } ``` ### Response ```json { "additionalProperties": false, "description": "Remaining verification credits for the account.", "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "credits": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Credits" }, "free_credits": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Free Credits" } }, "required": [ "success" ], "title": "GetCreditsOutput", "type": "object" } ``` ## Limits & Quotas - **Rate limit**: ~60 requests/min on standard plans. - **Credits**: each `verify_email` call consumes one verification credit; `get_credits` is free and does not consume credits. - **Error model**: the v4 API returns HTTP 200 even for API-level failures and signals the outcome via the response envelope's `status` field. A non-`"success"` status, a non-2xx HTTP response, or a timeout is caught and returned as `success=False` + `error` rather than raising. Plan for retries on the agent side based on the error string. ## Related integrations ## Links - [NeverBounce](https://www.neverbounce.com) --- # New Relic Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/new-relic Integrate New Relic into workflows. Run NRQL queries, search monitored entities, fetch entity details, and record deployment change events via the NerdGraph GraphQL API. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/new_relic/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} New Relic logo ## Overview Add **New Relic** to any ModuleX agent or workflow. Query observability data and record deployments in New Relic. Run NRQL queries, search monitored entities, fetch entity details, and create deployment change events through New Relic's NerdGraph GraphQL API (`api.newrelic.com/graphql`, or `api.eu.newrelic.com/graphql` for the EU region). **Categories**: Monitoring & Observability · Monitoring · Observability · **Auth**: API Key · **Actions**: 4 ## Authentication ### API Key Authentication Authenticate using your New Relic user API key Sign in to New Relic at https://one.newrelic.com Open the user menu and go to 'API keys' Create a new 'User' key (its value starts with 'NRAK-') Paste the API key below #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | New Relic User API Key | Your New Relic user API key (starts with 'NRAK-') | Yes | `NRAK-` | ## Available Actions ### Parameters New Relic account ID to query NRQL query to execute New Relic data center region: 'us' (default) or 'eu' (Default: `us`) Optional query timeout in seconds ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "results": { "items": { "additionalProperties": true, "type": "object" }, "title": "Results", "type": "array" }, "result_count": { "default": 0, "title": "Result Count", "type": "integer" } }, "required": [ "success" ], "title": "NrqlQueryOutput", "type": "object" } ``` ### Parameters Entity search query, e.g. name like "api" or domainType = "APM-APPLICATION" New Relic data center region: 'us' (default) or 'eu' (Default: `us`) Pagination cursor from a previous entity search ### Response ```json { "$defs": { "Entity": { "additionalProperties": false, "description": "A single monitored New Relic entity (guid, name, entityType).", "properties": { "guid": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Guid" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "entity_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Entity Type" } }, "title": "Entity", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "count": { "default": 0, "title": "Count", "type": "integer" }, "query": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Query" }, "entities": { "items": { "$ref": "#/$defs/Entity" }, "title": "Entities", "type": "array" }, "next_cursor": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Next Cursor" } }, "required": [ "success" ], "title": "SearchEntitiesOutput", "type": "object" } ``` ### Parameters Entity GUID New Relic data center region: 'us' (default) or 'eu' (Default: `us`) ### Response ```json { "$defs": { "Entity": { "additionalProperties": false, "description": "A single monitored New Relic entity (guid, name, entityType).", "properties": { "guid": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Guid" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "entity_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Entity Type" } }, "title": "Entity", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "entity": { "anyOf": [ { "$ref": "#/$defs/Entity" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "GetEntityOutput", "type": "object" } ``` ### Parameters GUID of the entity associated with the deployment Deployment version, release name, or commit SHA New Relic data center region: 'us' (default) or 'eu' (Default: `us`) Short description of the deployment Longer deployment description Deployment changelog text or URL Commit SHA or identifier associated with the deployment URL to the deployment, build, or release details User or automation that performed the deployment Optional group ID to correlate related changes Custom change event metadata as key-value pairs with string, number, or boolean values Deployment type: 'basic', 'blue green', 'canary', 'rolling', or 'shadow' (Default: `basic`) Event timestamp in milliseconds since Unix epoch ### Response ```json { "$defs": { "ChangeTrackingEntity": { "additionalProperties": false, "description": "The entity a change tracking event is associated with.", "properties": { "guid": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Guid" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" } }, "title": "ChangeTrackingEntity", "type": "object" }, "ChangeTrackingEvent": { "additionalProperties": false, "description": "A created New Relic change tracking (deployment) event.", "properties": { "category": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Category" }, "category_and_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Category And Type" }, "change_tracking_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Change Tracking Id" }, "custom_attributes": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Custom Attributes" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "group_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Group Id" }, "short_description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Short Description" }, "timestamp": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Timestamp" }, "type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Type" }, "user": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "User" }, "entity": { "anyOf": [ { "$ref": "#/$defs/ChangeTrackingEntity" }, { "type": "null" } ], "default": null } }, "title": "ChangeTrackingEvent", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "event": { "anyOf": [ { "$ref": "#/$defs/ChangeTrackingEvent" }, { "type": "null" } ], "default": null }, "messages": { "items": { "type": "string" }, "title": "Messages", "type": "array" } }, "required": [ "success" ], "title": "CreateDeploymentEventOutput", "type": "object" } ``` ## Limits & Quotas - All actions hit the NerdGraph GraphQL endpoint; New Relic enforces account-level NerdGraph rate limits (per-minute request and query-cost budgets). Heavy or unbounded NRQL queries may be throttled or time out. - `nrql_query` accepts an optional `timeout` (seconds) forwarded to NerdGraph; the HTTP client itself caps each call at 90 seconds. - `create_deployment_event` custom attribute names must be letters, numbers, and underscores (not starting with a number), must not contain `.`, and must avoid New Relic's reserved NRQL keywords; invalid names are rejected before the call. A supplied `timestamp` must be within one day of the current time per New Relic's change tracking rules. - **Error model**: non-2xx responses, GraphQL `errors`, and timeouts are caught and returned as `success=False` + `error` rather than raising. ## Related integrations ## Links - [New Relic](https://newrelic.com) --- # Notion Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/notion Notion workspace integration: pages, databases, blocks, users, comments, and search. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/notion/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Notion logo ## Overview Add **Notion** to any ModuleX agent or workflow. Notion v1 REST API integration. Pure HTTP (no SDK dep). 19 actions across pages, databases, blocks, users, comments, and search. **Categories**: Productivity & Collaboration · Productivity · Knowledge Management · Documentation · **Auth**: OAuth2 (Recommended), Internal Integration · **Actions**: 19 ## Authentication ### OAuth2 (Recommended) Connect using Notion OAuth — most secure option #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Client ID | Notion OAuth integration Client ID | Yes | `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` | | Client Secret | Notion OAuth integration Client Secret | Yes | `-` | #### OAuth Configuration - **Authorization URL**: `https://api.notion.com/v1/oauth/authorize` - **Token URL**: `https://api.notion.com/v1/oauth/token` ### Internal Integration Connect using a Notion Internal Integration Token #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Internal Integration Token | Notion Internal Integration secret | Yes | `secret_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | ## Available Actions ### Parameters Search query text Filter (e.g. {'property': 'object', 'value': 'page'}) Sort options Pagination cursor Number of results (max 100) (Default: `100`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "total": { "default": 0, "title": "Total", "type": "integer" }, "has_more": { "default": false, "title": "Has More", "type": "boolean" }, "next_cursor": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Next Cursor" }, "results": { "items": { "additionalProperties": true, "type": "object" }, "title": "Results", "type": "array" } }, "required": [ "success" ], "title": "SearchOutput", "type": "object" } ``` ### Parameters 'database' or 'page' Parent page/database ID Page title Markdown content (converted to paragraph blocks) Direct Notion block array (overrides content) Database properties (required for database parents) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" }, "created_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created Time" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "properties": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Properties" }, "parent": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Parent" } }, "required": [ "success" ], "title": "CreatePageOutput", "type": "object" } ``` ### Parameters Page ID to retrieve Include page child blocks (N+1 fetch) (Default: `true`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" }, "created_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created Time" }, "last_edited_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Edited Time" }, "created_by": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Created By" }, "last_edited_by": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Last Edited By" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "properties": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Properties" }, "parent": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Parent" }, "archived": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Archived" }, "content": { "anyOf": [ { "items": { "additionalProperties": true, "type": "object" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Content" } }, "required": [ "success" ], "title": "GetPageOutput", "type": "object" } ``` ### Parameters Page ID New title Updated properties Archive (true) / unarchive (false) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" }, "last_edited_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Edited Time" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "properties": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Properties" }, "archived": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Archived" } }, "required": [ "success" ], "title": "UpdatePageOutput", "type": "object" } ``` ### Parameters Database ID Filter conditions Sort options Pagination cursor Number of results (max 100) (Default: `100`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "total": { "default": 0, "title": "Total", "type": "integer" }, "has_more": { "default": false, "title": "Has More", "type": "boolean" }, "next_cursor": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Next Cursor" }, "database_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Database Id" }, "results": { "items": { "additionalProperties": true, "type": "object" }, "title": "Results", "type": "array" } }, "required": [ "success" ], "title": "QueryDatabaseOutput", "type": "object" } ``` ### Parameters Database ID ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" }, "created_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created Time" }, "last_edited_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Edited Time" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "properties": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Properties" }, "parent": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Parent" }, "archived": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Archived" }, "is_inline": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Inline" } }, "required": [ "success" ], "title": "GetDatabaseOutput", "type": "object" } ``` ### Parameters Parent page ID Database title Property schema definitions ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" }, "created_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created Time" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "properties": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Properties" }, "parent": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Parent" } }, "required": [ "success" ], "title": "CreateDatabaseOutput", "type": "object" } ``` ### Parameters Database ID New title New description Updated property schema ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" }, "last_edited_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Edited Time" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "properties": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Properties" } }, "required": [ "success" ], "title": "UpdateDatabaseOutput", "type": "object" } ``` ### Parameters Database ID Item properties matching database schema ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" }, "created_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created Time" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "properties": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Properties" } }, "required": [ "success" ], "title": "CreateDatabaseItemOutput", "type": "object" } ``` ### Parameters Block ID ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Type" }, "created_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created Time" }, "last_edited_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Edited Time" }, "has_children": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Has Children" }, "archived": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Archived" }, "parent": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Parent" }, "content": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Content" } }, "required": [ "success" ], "title": "GetBlockOutput", "type": "object" } ``` ### Parameters Parent block or page ID Pagination cursor Number of results (max 100) (Default: `100`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "total": { "default": 0, "title": "Total", "type": "integer" }, "has_more": { "default": false, "title": "Has More", "type": "boolean" }, "next_cursor": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Next Cursor" }, "block_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Block Id" }, "children": { "items": { "additionalProperties": true, "type": "object" }, "title": "Children", "type": "array" } }, "required": [ "success" ], "title": "GetBlockChildrenOutput", "type": "object" } ``` ### Parameters Block / page ID to append into Notion block objects to append ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "block_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Block Id" }, "appended_blocks": { "items": { "additionalProperties": true, "type": "object" }, "title": "Appended Blocks", "type": "array" }, "total_appended": { "default": 0, "title": "Total Appended", "type": "integer" } }, "required": [ "success" ], "title": "AppendBlocksOutput", "type": "object" } ``` ### Parameters Block ID Block object with updated content ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Type" }, "last_edited_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Edited Time" }, "has_children": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Has Children" }, "content": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Content" } }, "required": [ "success" ], "title": "UpdateBlockOutput", "type": "object" } ``` ### Parameters Block ID ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Type" }, "archived": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Archived" } }, "required": [ "success" ], "title": "DeleteBlockOutput", "type": "object" } ``` ### Parameters Pagination cursor Number of results (max 100) (Default: `100`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "total": { "default": 0, "title": "Total", "type": "integer" }, "has_more": { "default": false, "title": "Has More", "type": "boolean" }, "next_cursor": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Next Cursor" }, "users": { "items": { "additionalProperties": true, "type": "object" }, "title": "Users", "type": "array" } }, "required": [ "success" ], "title": "ListUsersOutput", "type": "object" } ``` ### Parameters User ID ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "object": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Object" }, "type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Type" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "avatar_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Avatar Url" }, "person": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Person" }, "bot": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Bot" } }, "required": [ "success" ], "title": "GetUserOutput", "type": "object" } ``` ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "object": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Object" }, "type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Type" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "avatar_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Avatar Url" }, "bot": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Bot" } }, "required": [ "success" ], "title": "GetBotUserOutput", "type": "object" } ``` ### Parameters Rich text array for the comment body Page ID (use this for new comments) Discussion ID (use this for replies) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "created_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created Time" }, "discussion_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Discussion Id" }, "parent": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Parent" }, "rich_text": { "anyOf": [ { "items": { "additionalProperties": true, "type": "object" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Rich Text" }, "created_by": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Created By" } }, "required": [ "success" ], "title": "CreateCommentOutput", "type": "object" } ``` ### Parameters Block / page ID Pagination cursor Number of results (max 100) (Default: `100`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "total": { "default": 0, "title": "Total", "type": "integer" }, "has_more": { "default": false, "title": "Has More", "type": "boolean" }, "next_cursor": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Next Cursor" }, "block_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Block Id" }, "comments": { "items": { "additionalProperties": true, "type": "object" }, "title": "Comments", "type": "array" } }, "required": [ "success" ], "title": "GetCommentsOutput", "type": "object" } ``` ## Limits & Quotas - 30s timeout on every request. - `page_size` clamped to 100 (Notion's max). - All actions wrap in try/except → `success=False` envelope (exa-style). - Trivial markdown→blocks conversion in `create_page.content`: splits on newlines and creates one paragraph block per non-empty line. Use `children` for anything fancier. ## Related integrations ## Links - [Notion](https://www.notion.so) --- # NPM Registry Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/npm Access the npm registry to search packages, get package information, versions, dependencies, and download statistics {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/npm/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} NPM Registry logo ## Overview Add **NPM Registry** to any ModuleX agent or workflow. Read-only access to the public npm registry: package info, search, popular packages, version history, dependency graphs, and download statistics. **Categories**: Developer Tools & Infrastructure · Coding · Package Management · Developer Tools · **Auth**: API Key · **Actions**: 6 ## Authentication ### API Key Authentication Optional authentication for private npm registries. Leave empty for public npm registry access. #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | NPM API Key | Your npm authentication token (optional for public registry) | No | `-` | ## Available Actions ### Parameters The name of the npm package to look up ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "version": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Version" }, "description": { "default": "", "title": "Description", "type": "string" }, "author": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Author" }, "homepage": { "default": "", "title": "Homepage", "type": "string" }, "repository": { "default": "", "title": "Repository", "type": "string" }, "license": { "default": "", "title": "License", "type": "string" }, "keywords": { "items": { "type": "string" }, "title": "Keywords", "type": "array" }, "dependencies": { "additionalProperties": { "type": "string" }, "title": "Dependencies", "type": "object" }, "devDependencies": { "additionalProperties": { "type": "string" }, "title": "Devdependencies", "type": "object" }, "peerDependencies": { "additionalProperties": { "type": "string" }, "title": "Peerdependencies", "type": "object" } }, "required": [ "success" ], "title": "GetPackageInfoOutput", "type": "object" } ``` ### Parameters Search query text Number of results to return (1-250) (Default: `10`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "total": { "default": 0, "title": "Total", "type": "integer" }, "packages": { "items": { "additionalProperties": true, "type": "object" }, "title": "Packages", "type": "array" } }, "required": [ "success" ], "title": "SearchPackagesOutput", "type": "object" } ``` ### Parameters Number of popular packages to return (1-250) (Default: `10`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "packages": { "items": { "additionalProperties": true, "type": "object" }, "title": "Packages", "type": "array" } }, "required": [ "success" ], "title": "GetPopularPackagesOutput", "type": "object" } ``` ### Parameters The name of the npm package ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "dist_tags": { "additionalProperties": { "type": "string" }, "title": "Dist Tags", "type": "object" }, "versions": { "items": { "additionalProperties": { "type": "string" }, "type": "object" }, "title": "Versions", "type": "array" }, "total_versions": { "default": 0, "title": "Total Versions", "type": "integer" } }, "required": [ "success" ], "title": "GetPackageVersionsOutput", "type": "object" } ``` ### Parameters The name of the npm package Specific version (defaults to latest) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "version": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Version" }, "dependencies": { "additionalProperties": { "type": "string" }, "title": "Dependencies", "type": "object" }, "devDependencies": { "additionalProperties": { "type": "string" }, "title": "Devdependencies", "type": "object" }, "peerDependencies": { "additionalProperties": { "type": "string" }, "title": "Peerdependencies", "type": "object" }, "optionalDependencies": { "additionalProperties": { "type": "string" }, "title": "Optionaldependencies", "type": "object" } }, "required": [ "success" ], "title": "GetPackageDependenciesOutput", "type": "object" } ``` ### Parameters The name of the npm package Time period for download statistics (Default: `last-week`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "package": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Package" }, "period": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Period" }, "start": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Start" }, "end": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "End" }, "total_downloads": { "default": 0, "title": "Total Downloads", "type": "integer" }, "daily_downloads": { "items": { "additionalProperties": true, "type": "object" }, "title": "Daily Downloads", "type": "array" } }, "required": [ "success" ], "title": "GetPackageDownloadStatsOutput", "type": "object" } ``` ## Limits & Quotas - npm's public registry has loose, unenforced limits — be reasonable. Two API hosts are involved: `registry.npmjs.org` (metadata) and `api.npmjs.org` (downloads). - `period` must be one of `last-day`, `last-week`, `last-month`, `last-year` (validated client-side; otherwise `success=False`). - Search `size` is clamped to [1, 250]. ## Related integrations ## Links --- # Obsidian Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/obsidian Read, create, update, search, and delete notes in your Obsidian vault. Manage periodic notes, execute commands, and patch content at specific locations. Requires the Obsidian Local REST API plugin. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/obsidian/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Obsidian logo ## Overview Add **Obsidian** to any ModuleX agent or workflow. Read, create, update, search, and delete notes in your Obsidian vault via the [Obsidian Local REST API](https://obsidian.md) plugin. Manage periodic notes, list and execute commands, work with the active file, and patch content at specific headings, block references, or frontmatter fields. The plugin runs locally inside your Obsidian app and is reachable at a `base_url` you pass on every call (default `https://127.0.0.1:27124`). Because the plugin serves a self-signed TLS certificate, the HTTP client does not verify the certificate chain. **Categories**: Productivity & Collaboration · Note Taking · Knowledge Base · **Auth**: API Key · **Actions**: 15 ## Authentication ### API Key Authentication Authenticate using the API key from the Obsidian Local REST API plugin Install the 'Local REST API' community plugin in Obsidian Open the plugin settings and enable it Copy the API key shown in the plugin settings Note the server URL (default https://127.0.0.1:27124) for the base_url parameter Paste the API key below #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Obsidian Local REST API Key | API key from the Obsidian Local REST API plugin settings | Yes | `-` | ## Available Actions ### Parameters Base URL for the Obsidian Local REST API (e.g. https://127.0.0.1:27124) Directory path relative to vault root. Leave empty to list the root. ### Response ```json { "$defs": { "FileEntry": { "additionalProperties": false, "description": "A single file or directory entry in ``list_files``.", "properties": { "path": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Path" }, "type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Type" } }, "title": "FileEntry", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "files": { "items": { "$ref": "#/$defs/FileEntry" }, "title": "Files", "type": "array" } }, "required": [ "success" ], "title": "ListFilesOutput", "type": "object" } ``` ### Parameters Base URL for the Obsidian Local REST API (e.g. https://127.0.0.1:27124) Path to the note relative to vault root (e.g. "folder/note.md") ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "content": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Content" }, "filename": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Filename" } }, "required": [ "success" ], "title": "GetNoteOutput", "type": "object" } ``` ### Parameters Base URL for the Obsidian Local REST API (e.g. https://127.0.0.1:27124) Path for the note relative to vault root (e.g. "folder/note.md") Markdown content for the note ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "filename": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Filename" }, "created": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Created" } }, "required": [ "success" ], "title": "CreateNoteOutput", "type": "object" } ``` ### Parameters Base URL for the Obsidian Local REST API (e.g. https://127.0.0.1:27124) Path to the note relative to vault root (e.g. "folder/note.md") Markdown content to append to the note ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "filename": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Filename" }, "appended": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Appended" } }, "required": [ "success" ], "title": "AppendNoteOutput", "type": "object" } ``` ### Parameters Base URL for the Obsidian Local REST API (e.g. https://127.0.0.1:27124) Path to the note relative to vault root (e.g. "folder/note.md") Content to insert at the target location How to insert content: append, prepend, or replace Type of target: heading, block, or frontmatter Target identifier (heading text, block reference ID, or frontmatter field name) Delimiter for nested headings (default: "::") Whether to trim whitespace from target before matching ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "filename": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Filename" }, "patched": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Patched" } }, "required": [ "success" ], "title": "PatchNoteOutput", "type": "object" } ``` ### Parameters Base URL for the Obsidian Local REST API (e.g. https://127.0.0.1:27124) Path to the note to delete relative to vault root ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "filename": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Filename" }, "deleted": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Deleted" } }, "required": [ "success" ], "title": "DeleteNoteOutput", "type": "object" } ``` ### Parameters Base URL for the Obsidian Local REST API (e.g. https://127.0.0.1:27124) Text to search for across vault notes Number of characters of context around each match (default: 100) ### Response ```json { "$defs": { "SearchMatch": { "additionalProperties": false, "description": "A single matching context snippet within a search result.", "properties": { "context": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Context" } }, "title": "SearchMatch", "type": "object" }, "SearchResultItem": { "additionalProperties": false, "description": "A single result row in ``search``.", "properties": { "filename": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Filename" }, "score": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Score" }, "matches": { "items": { "$ref": "#/$defs/SearchMatch" }, "title": "Matches", "type": "array" } }, "title": "SearchResultItem", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "results": { "items": { "$ref": "#/$defs/SearchResultItem" }, "title": "Results", "type": "array" } }, "required": [ "success" ], "title": "SearchOutput", "type": "object" } ``` ### Parameters Base URL for the Obsidian Local REST API (e.g. https://127.0.0.1:27124) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "content": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Content" }, "filename": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Filename" } }, "required": [ "success" ], "title": "GetActiveOutput", "type": "object" } ``` ### Parameters Base URL for the Obsidian Local REST API (e.g. https://127.0.0.1:27124) Markdown content to append to the active file ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "appended": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Appended" } }, "required": [ "success" ], "title": "AppendActiveOutput", "type": "object" } ``` ### Parameters Base URL for the Obsidian Local REST API (e.g. https://127.0.0.1:27124) Content to insert at the target location How to insert content: append, prepend, or replace Type of target: heading, block, or frontmatter Target identifier (heading text, block reference ID, or frontmatter field name) Delimiter for nested headings (default: "::") Whether to trim whitespace from target before matching ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "patched": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Patched" } }, "required": [ "success" ], "title": "PatchActiveOutput", "type": "object" } ``` ### Parameters Base URL for the Obsidian Local REST API (e.g. https://127.0.0.1:27124) Path to the file relative to vault root Whether to open the file in a new leaf/tab ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "filename": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Filename" }, "opened": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Opened" } }, "required": [ "success" ], "title": "OpenFileOutput", "type": "object" } ``` ### Parameters Base URL for the Obsidian Local REST API (e.g. https://127.0.0.1:27124) ### Response ```json { "$defs": { "CommandItem": { "additionalProperties": false, "description": "A single available command in ``list_commands``.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" } }, "title": "CommandItem", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "commands": { "items": { "$ref": "#/$defs/CommandItem" }, "title": "Commands", "type": "array" } }, "required": [ "success" ], "title": "ListCommandsOutput", "type": "object" } ``` ### Parameters Base URL for the Obsidian Local REST API (e.g. https://127.0.0.1:27124) ID of the command to execute (use list_commands to discover available commands) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "command_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Command Id" }, "executed": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Executed" } }, "required": [ "success" ], "title": "ExecuteCommandOutput", "type": "object" } ``` ### Parameters Base URL for the Obsidian Local REST API (e.g. https://127.0.0.1:27124) Period type: daily, weekly, monthly, quarterly, or yearly ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "content": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Content" }, "period": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Period" } }, "required": [ "success" ], "title": "GetPeriodicNoteOutput", "type": "object" } ``` ### Parameters Base URL for the Obsidian Local REST API (e.g. https://127.0.0.1:27124) Period type: daily, weekly, monthly, quarterly, or yearly Markdown content to append to the periodic note ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "period": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Period" }, "appended": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Appended" } }, "required": [ "success" ], "title": "AppendPeriodicNoteOutput", "type": "object" } ``` ## Limits & Quotas - The Local REST API runs on the user's own machine, so throughput is bound only by the local Obsidian instance; there is no vendor rate limit. - `base_url` must be reachable from where the runtime executes (typically the same host). The default `https://127.0.0.1:27124` only works for a co-located process. - **Error model**: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. ## Related integrations ## Links - [Obsidian](https://obsidian.md) --- # Okta Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/okta Manage users and user-type metadata in an Okta tenant via the Okta Management REST API (``/api/v1`` under your Okta subdomain). {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/okta/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Okta logo Okta logo ## Overview Add **Okta** to any ModuleX agent or workflow. Manage users and user-type metadata in an Okta tenant via the Okta Management REST API (`https://.okta.com/api/v1`). **Categories**: Developer Tools & Infrastructure · Identity & Access Management · Productivity & Collaboration · **Auth**: Okta API Token · **Actions**: 4 ## Authentication ### Okta API Token Authenticate with an Okta SSWS API token plus the tenant subdomain. The token is sent as ``Authorization: SSWS <token>`` and the subdomain is used to build the request host (``https://<subdomain>.okta.com``). Sign in to your Okta admin console. Open Security -> API -> Tokens, then click Create Token. Copy the generated SSWS token (it is shown only once). Your subdomain is the leading part of your admin URL, e.g. for ``acme.okta.com`` the subdomain is ``acme``. #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Okta Subdomain | The tenant subdomain (e.g. ``acme`` for ``acme.okta.com``). Do not include ``.okta.com``. | Yes | `acme` | | Okta API Token | Your SSWS API token from Okta admin (Security -> API -> Tokens). | Yes | `00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | ## Available Actions ### Parameters The first name of the user. The last name of the user. The email address of the user. The login (typically the email) for the user. Optional mobile phone number for the user. Optional ID of a non-default user type. Use list_type_id_options to enumerate available IDs. Whether to execute the activation lifecycle on creation. Defaults to true. (Default: `true`) ### Response ```json { "$defs": { "UserResource": { "additionalProperties": false, "description": "An Okta user as returned by /users endpoints.\n\nField set is intentionally permissive — Okta returns many fields; we\nexpose the ones agents commonly need and let the rest live in ``profile``.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "created": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created" }, "activated": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Activated" }, "statusChanged": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Statuschanged" }, "lastLogin": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Lastlogin" }, "lastUpdated": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Lastupdated" }, "passwordChanged": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Passwordchanged" }, "type": { "anyOf": [ { "$ref": "#/$defs/UserTypeRef" }, { "type": "null" } ], "default": null }, "profile": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Profile" }, "credentials": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Credentials" } }, "title": "UserResource", "type": "object" }, "UserTypeRef": { "additionalProperties": false, "description": "Reference to a user type returned alongside a user resource.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" } }, "title": "UserTypeRef", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "user": { "anyOf": [ { "$ref": "#/$defs/UserResource" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "CreateUserOutput", "type": "object" } ``` ### Parameters The unique identifier of the user (Okta user ID, login, or email). ### Response ```json { "$defs": { "UserResource": { "additionalProperties": false, "description": "An Okta user as returned by /users endpoints.\n\nField set is intentionally permissive — Okta returns many fields; we\nexpose the ones agents commonly need and let the rest live in ``profile``.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "created": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created" }, "activated": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Activated" }, "statusChanged": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Statuschanged" }, "lastLogin": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Lastlogin" }, "lastUpdated": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Lastupdated" }, "passwordChanged": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Passwordchanged" }, "type": { "anyOf": [ { "$ref": "#/$defs/UserTypeRef" }, { "type": "null" } ], "default": null }, "profile": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Profile" }, "credentials": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Credentials" } }, "title": "UserResource", "type": "object" }, "UserTypeRef": { "additionalProperties": false, "description": "Reference to a user type returned alongside a user resource.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" } }, "title": "UserTypeRef", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "user": { "anyOf": [ { "$ref": "#/$defs/UserResource" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "GetUserOutput", "type": "object" } ``` ### Response ```json { "$defs": { "TypeIdOption": { "additionalProperties": false, "description": "One row from the dropdown returned by list_type_id_options.", "properties": { "label": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Label" }, "value": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Value" } }, "title": "TypeIdOption", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "options": { "items": { "$ref": "#/$defs/TypeIdOption" }, "title": "Options", "type": "array" } }, "required": [ "success" ], "title": "ListTypeIdOptionsOutput", "type": "object" } ``` ### Parameters The unique identifier of the user to update. New first name for the user. New last name for the user. New email address for the user. New login for the user. New mobile phone number for the user. Optional new user type ID. Use list_type_id_options to discover valid IDs. ### Response ```json { "$defs": { "UserResource": { "additionalProperties": false, "description": "An Okta user as returned by /users endpoints.\n\nField set is intentionally permissive — Okta returns many fields; we\nexpose the ones agents commonly need and let the rest live in ``profile``.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "created": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created" }, "activated": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Activated" }, "statusChanged": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Statuschanged" }, "lastLogin": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Lastlogin" }, "lastUpdated": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Lastupdated" }, "passwordChanged": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Passwordchanged" }, "type": { "anyOf": [ { "$ref": "#/$defs/UserTypeRef" }, { "type": "null" } ], "default": null }, "profile": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Profile" }, "credentials": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Credentials" } }, "title": "UserResource", "type": "object" }, "UserTypeRef": { "additionalProperties": false, "description": "Reference to a user type returned alongside a user resource.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" } }, "title": "UserTypeRef", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "user": { "anyOf": [ { "$ref": "#/$defs/UserResource" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "UpdateUserOutput", "type": "object" } ``` ## Limits & Quotas - Okta enforces per-org rate limits that vary by endpoint and plan; common defaults are 600 requests/minute for the Users endpoints on developer orgs and higher on production tiers. See https://developer.okta.com/docs/reference/rl-global-mgmt/. - Each response includes `X-Rate-Limit-Limit`, `X-Rate-Limit-Remaining`, and `X-Rate-Limit-Reset` headers; agents that hit `429` should back off until the reset epoch. - `update_user` issues one `GET` plus one `PUT` per call (the existing profile is merged client-side so partial updates do not blank out fields). - **Error model**: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. ## Related integrations ## Links - [Okta](https://www.okta.com) --- # PagerDuty Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/pagerduty Incident management and on-call scheduling platform {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/pagerduty/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} PagerDuty logo ## Overview Add **PagerDuty** to any ModuleX agent or workflow. Incident management, on-call scheduling, and alerting via the PagerDuty REST API (`api.pagerduty.com`). **Categories**: Monitoring & Observability · Incident Management · Developer Tools & Infrastructure · **Auth**: OAuth2 · **Actions**: 4 ## Authentication ### OAuth2 Authentication Connect using PagerDuty OAuth (recommended) #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Client ID | PagerDuty OAuth App Client ID | Yes | `-` | | Client Secret | PagerDuty OAuth App Client Secret | Yes | `-` | #### OAuth Configuration - **Authorization URL**: `https://identity.pagerduty.com/oauth/authorize` - **Token URL**: `https://identity.pagerduty.com/oauth/token` - **Scopes**: `read`, `write` ## Available Actions ### Parameters A succinct description of the nature, symptoms, cause, or effect of the incident The ID of the PagerDuty service to trigger the incident on The urgency of the incident: high or low Additional incident details A string which identifies the incident. Subsequent requests with the same key and service will be rejected if an open incident matches The ID of the escalation policy to assign List of user IDs to assign to the incident Phone number for the conference bridge (format: +1 415-555-1212,,,,1234#) URL for the conference bridge (e.g. a web conference or Slack channel link) ### Response ```json { "$defs": { "IncidentSummary": { "additionalProperties": false, "description": "Core fields returned for an incident by the PagerDuty API.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Type" }, "summary": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Summary" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "urgency": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Urgency" }, "incident_key": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Incident Key" }, "html_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Html Url" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "service": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Service" }, "escalation_policy": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Escalation Policy" }, "assignments": { "items": { "additionalProperties": true, "type": "object" }, "title": "Assignments", "type": "array" } }, "title": "IncidentSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "incident": { "anyOf": [ { "$ref": "#/$defs/IncidentSummary" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "TriggerIncidentOutput", "type": "object" } ``` ### Parameters The ID of the incident to acknowledge ### Response ```json { "$defs": { "IncidentSummary": { "additionalProperties": false, "description": "Core fields returned for an incident by the PagerDuty API.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Type" }, "summary": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Summary" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "urgency": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Urgency" }, "incident_key": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Incident Key" }, "html_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Html Url" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "service": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Service" }, "escalation_policy": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Escalation Policy" }, "assignments": { "items": { "additionalProperties": true, "type": "object" }, "title": "Assignments", "type": "array" } }, "title": "IncidentSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "incident": { "anyOf": [ { "$ref": "#/$defs/IncidentSummary" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "AcknowledgeIncidentOutput", "type": "object" } ``` ### Parameters The ID of the incident to resolve ### Response ```json { "$defs": { "IncidentSummary": { "additionalProperties": false, "description": "Core fields returned for an incident by the PagerDuty API.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Type" }, "summary": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Summary" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "urgency": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Urgency" }, "incident_key": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Incident Key" }, "html_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Html Url" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "service": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Service" }, "escalation_policy": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Escalation Policy" }, "assignments": { "items": { "additionalProperties": true, "type": "object" }, "title": "Assignments", "type": "array" } }, "title": "IncidentSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "incident": { "anyOf": [ { "$ref": "#/$defs/IncidentSummary" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "ResolveIncidentOutput", "type": "object" } ``` ### Parameters The ID of the on-call schedule The ID of the user to search for in the schedule ### Response ```json { "$defs": { "OncallUser": { "additionalProperties": false, "description": "A user found on-call for a schedule.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email" }, "type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Type" }, "html_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Html Url" } }, "title": "OncallUser", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "found": { "default": false, "title": "Found", "type": "boolean" }, "user": { "anyOf": [ { "$ref": "#/$defs/OncallUser" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "FindOncallUserOutput", "type": "object" } ``` ## Limits & Quotas - **Rate limits**: PagerDuty REST API allows up to 960 requests per minute (varies by account tier). - **Throttling**: Requests exceeding the rate limit receive HTTP 429; implement client-side backoff. - **Error model**: non-2xx responses raise `httpx.HTTPStatusError` (Pattern A). The caller should handle retries. ## Related integrations ## Links - [PagerDuty](https://www.pagerduty.com) --- # Pinterest Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/pinterest Visual discovery and social media platform for managing boards, pins, and content. Create pins, organize boards, and manage visual content for marketing and inspiration. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/pinterest/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Pinterest logo ## Overview Add **Pinterest** to any ModuleX agent or workflow. Manage boards, board sections, and pins via the Pinterest REST API v5. **Categories**: Social Media · Marketing & Email · Social Media · Automation · Development · Miscellaneous · Pinterest · **Auth**: OAuth Access Token, OAuth 2.0 · **Actions**: 4 ## Authentication ### OAuth Access Token Authenticate using your Pinterest OAuth access token. The token can be obtained through Pinterest's OAuth 2.0 flow. #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Access Token | Your Pinterest OAuth access token | Yes | `-` | ### OAuth 2.0 Authenticate using Pinterest OAuth 2.0 for full API access with token refresh capabilities. #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | App ID | Your Pinterest App ID from the developer portal | Yes | `-` | | App Secret | Your Pinterest App Secret from the developer portal | Yes | `-` | #### OAuth Configuration - **Authorization URL**: `https://www.pinterest.com/oauth/` - **Token URL**: `https://api.pinterest.com/v5/oauth/token` - **Scopes**: `boards:read`, `boards:write`, `pins:read`, `pins:write`, `user_accounts:read` ## Available Actions ### Parameters Number of boards per page (max 250) (Default: `25`) Cursor for pagination to get the next page ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "boards": { "items": { "additionalProperties": true, "type": "object" }, "title": "Boards", "type": "array" }, "count": { "default": 0, "title": "Count", "type": "integer" }, "bookmark": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Bookmark" } }, "required": [ "success" ], "title": "ListBoardsOutput", "type": "object" } ``` ### Parameters The ID of the board to get sections from Number of sections per page (max 250) (Default: `25`) Cursor for pagination ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "board_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Board Id" }, "sections": { "items": { "additionalProperties": true, "type": "object" }, "title": "Sections", "type": "array" }, "count": { "default": 0, "title": "Count", "type": "integer" }, "bookmark": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Bookmark" } }, "required": [ "success" ], "title": "GetBoardSectionsOutput", "type": "object" } ``` ### Parameters The ID of the board to pin to Title of the pin URL of the image to pin Description of the pin Source link/URL for the pin Alt text for accessibility ID of the board section ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "link": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Link" }, "board_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Board Id" }, "board_section_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Board Section Id" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "pin": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Pin" } }, "required": [ "success" ], "title": "CreatePinOutput", "type": "object" } ``` ### Parameters The ID of the board to list pins from ID of the board section (optional) Number of pins per page (max 250) (Default: `25`) Cursor for pagination ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "board_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Board Id" }, "board_section_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Board Section Id" }, "pins": { "items": { "additionalProperties": true, "type": "object" }, "title": "Pins", "type": "array" }, "count": { "default": 0, "title": "Count", "type": "integer" }, "bookmark": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Bookmark" } }, "required": [ "success" ], "title": "ListPinsOutput", "type": "object" } ``` ## Limits & Quotas - `page_size` is clamped to [1, 250]. - 401 → auth error; 404 → resource-not-found; 400 on create → likely invalid `media_url` or board id. ## Related integrations ## Links - [Pinterest](https://www.pinterest.com) --- # Pipedrive Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/pipedrive Sales CRM and pipeline management platform for tracking deals, contacts, activities, and leads. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/pipedrive/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Pipedrive logo Pipedrive logo ## Overview Add **Pipedrive** to any ModuleX agent or workflow. Sales CRM and pipeline management platform. Connects to the Pipedrive REST API (`{your-domain}.pipedrive.com/api/v1` and `/api/v2`) to manage deals, contacts, leads, activities, organizations, and notes. **Categories**: CRM · Sales · **Auth**: OAuth2 · **Actions**: 26 ## Authentication ### OAuth2 Authentication Connect using Pipedrive OAuth (recommended) #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Client ID | Pipedrive OAuth App Client ID | Yes | `-` | | Client Secret | Pipedrive OAuth App Client Secret | Yes | `-` | #### OAuth Configuration - **Authorization URL**: `https://oauth.pipedrive.com/oauth/authorize` - **Token URL**: `https://oauth.pipedrive.com/oauth/token` - **Scopes**: `deals:full`, `contacts:full`, `leads:full`, `activities:full`, `search:read`, `users:read`, `admin` ## Available Actions ### Parameters Subject of the activity Type of the activity (e.g. call, meeting, task, deadline, email, lunch). Must match an ActivityType key_string in Pipedrive. ID of the user whom the activity will be assigned to. If omitted, assigned to the authorized user. ID of the deal this activity will be associated with ID of the lead this activity will be associated with ID of the organization this activity will be associated with ID of the project this activity will be associated with Due date of the activity. Format: YYYY-MM-DD Due time of the activity in UTC. Format: HH:MM Duration of the activity. Format: HH:MM Set the activity as Busy or Free Whether the activity is done or not Note of the activity (HTML format) Additional details about the activity that will be synced to your external calendar ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "AddActivityOutput", "type": "object" } ``` ### Parameters Deal title ID of the user who will be marked as the owner of this deal ID of the person this deal will be associated with ID of the organization this deal will be associated with ID of the pipeline this deal will be placed in ID of the stage this deal will be placed in Value of the deal. If omitted, value will be set to 0. Currency of the deal (3-character code). If omitted, uses the default currency of the authorized user. Status of the deal. Allowed values: open, won, lost, deleted Deal success probability percentage Message about why the deal was lost (when status=lost) Visibility of the deal. 1 = Owner & followers (private), 3 = Entire company (shared) Expected close date of the deal. Format: YYYY-MM-DD Content of a note to attach to the deal after creation ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "AddDealOutput", "type": "object" } ``` ### Parameters The type of the item. Allowed values: lead, person, deal, organization The IDs of labels to add. Element type: string The ID of the lead (required when type=lead) The ID of the person (required when type=person) The ID of the deal (required when type=deal) The ID of the organization (required when type=organization) Set to true to replace existing labels with the new ones, false to append (Default: `false`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "AddLabelsOutput", "type": "object" } ``` ### Parameters The name of the lead The ID of a person to link to. Required unless organization_id is specified. The ID of an organization to link to. Required unless person_id is specified. The ID of the user who will own the lead The IDs of lead labels to associate. Element type: string (UUID) Expected close date. Format: YYYY-MM-DD Visibility of the lead. Allowed values: 1, 3, 5, 7 Whether the lead was seen in the Pipedrive UI A note to add to the lead ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "AddLeadOutput", "type": "object" } ``` ### Parameters The content of the note in HTML format The ID of the lead to attach the note to The ID of the deal to attach the note to The ID of the person to attach the note to The ID of the organization to attach the note to The ID of the user marked as the author (admin only) Pin the note to the deal (requires deal_id) (Default: `false`) Pin the note to the lead (requires lead_id) (Default: `false`) Pin the note to the organization (requires organization_id) (Default: `false`) Pin the note to the person (requires person_id) (Default: `false`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "AddNoteOutput", "type": "object" } ``` ### Parameters Organization name ID of the user who will be marked as the owner Visibility. 1 = Owner & followers (private), 3 = Entire company (shared) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "AddOrganizationOutput", "type": "object" } ``` ### Parameters Person name ID of the user who will be marked as the owner ID of the organization this person will belong to Email addresses. Element type: object with keys {value, primary, label} Phone numbers. Element type: object with keys {value, primary, label} Visibility. 1 = Owner & followers (private), 3 = Entire company (shared) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "AddPersonOutput", "type": "object" } ``` ### Parameters Maximum number of results to return. If not provided, all leads are returned. Filter leads by the given user ID Filter leads by the given person ID Filter leads by the given organization ID The ID of the filter to use. Takes precedence over other filters. Field and direction to sort by. Valid fields: id, title, owner_id, creator_id, was_seen, expected_close_date, next_activity_id, add_time, update_time ### Response ```json { "$defs": { "LeadItem": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "owner_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Owner Id" }, "person_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Person Id" }, "organization_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Organization Id" }, "was_seen": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Was Seen" }, "expected_close_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Expected Close Date" }, "add_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Add Time" }, "update_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Update Time" } }, "title": "LeadItem", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "leads": { "items": { "$ref": "#/$defs/LeadItem" }, "title": "Leads", "type": "array" }, "total": { "default": 0, "title": "Total", "type": "integer" } }, "required": [ "success" ], "title": "GetAllLeadsOutput", "type": "object" } ``` ### Parameters The ID of the deal to retrieve ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "GetDealOutput", "type": "object" } ``` ### Parameters The ID of the lead to retrieve ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "GetLeadByIdOutput", "type": "object" } ``` ### Parameters The ID of the person to retrieve ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "GetPersonDetailsOutput", "type": "object" } ``` ### Parameters The ID of the filter to apply Filter by owner user ID Filter by associated person ID Filter by associated organization ID Filter by pipeline ID Filter by stage ID Filter by status. Allowed values: open, won, lost, deleted Field to sort by. Allowed values: id, update_time, add_time Sort direction. Allowed values: asc, desc Maximum number of entries to return (max 500) Cursor for pagination to the next page of results ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "deals": { "items": { "additionalProperties": true, "type": "object" }, "title": "Deals", "type": "array" }, "cursor": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Cursor" } }, "required": [ "success" ], "title": "ListDealsOutput", "type": "object" } ``` ### Response ```json { "$defs": { "LabelOption": { "additionalProperties": false, "properties": { "label": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Label" }, "value": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Value" } }, "title": "LabelOption", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "options": { "items": { "$ref": "#/$defs/LabelOption" }, "title": "Options", "type": "array" } }, "required": [ "success" ], "title": "ListLeadLabelIdsOptionsOutput", "type": "object" } ``` ### Response ```json { "$defs": { "LabelOption": { "additionalProperties": false, "properties": { "label": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Label" }, "value": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Value" } }, "title": "LabelOption", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "options": { "items": { "$ref": "#/$defs/LabelOption" }, "title": "Options", "type": "array" } }, "required": [ "success" ], "title": "ListOrganizationLabelIdsOptionsOutput", "type": "object" } ``` ### Response ```json { "$defs": { "LabelOption": { "additionalProperties": false, "properties": { "label": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Label" }, "value": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Value" } }, "title": "LabelOption", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "options": { "items": { "$ref": "#/$defs/LabelOption" }, "title": "Options", "type": "array" } }, "required": [ "success" ], "title": "ListPersonLabelIdsOptionsOutput", "type": "object" } ``` ### Response ```json { "$defs": { "UserOption": { "additionalProperties": false, "properties": { "label": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Label" }, "value": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Value" } }, "title": "UserOption", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "options": { "items": { "$ref": "#/$defs/UserOption" }, "title": "Options", "type": "array" } }, "required": [ "success" ], "title": "ListUserIdOptionsOutput", "type": "object" } ``` ### Parameters The ID of the deal to merge (will be removed) The ID of the deal to merge into (will be kept) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "MergeDealsOutput", "type": "object" } ``` ### Parameters The ID of the person to merge (will be removed) The ID of the person to merge into (will be kept, data prioritized on conflict) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "MergePersonsOutput", "type": "object" } ``` ### Parameters The ID of the lead whose notes to deduplicate The ID of the deal whose notes to deduplicate The ID of the person whose notes to deduplicate The ID of the organization whose notes to deduplicate The ID of the user whose notes to deduplicate The ID of the project whose notes to deduplicate Only remove duplicate notes that contain this keyword ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "total_notes": { "default": 0, "title": "Total Notes", "type": "integer" }, "duplicates_found": { "default": 0, "title": "Duplicates Found", "type": "integer" }, "duplicates_removed": { "items": { "additionalProperties": true, "type": "object" }, "title": "Duplicates Removed", "type": "array" } }, "required": [ "success" ], "title": "RemoveDuplicateNotesOutput", "type": "object" } ``` ### Parameters The type of the item. Allowed values: lead, person, deal, organization ID of the entity to remove labels from The label IDs to remove. Element type: string ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "RemoveLabelsOutput", "type": "object" } ``` ### Parameters The search term (minimum 2 characters, or 1 with exact_match) When true, only full exact matches are returned (not case sensitive) Fields to search from. Allowed values: custom_fields, notes, title. Element type: string Filter by person ID Filter by organization ID Optional fields to include. Allowed: lead.was_seen ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "items": { "items": { "additionalProperties": true, "type": "object" }, "title": "Items", "type": "array" } }, "required": [ "success" ], "title": "SearchLeadsOutput", "type": "object" } ``` ### Parameters The term to search for in note content Filter by lead ID Filter by deal ID Filter by person ID Filter by organization ID Filter by user ID Sort field. Allowed: id, user_id, deal_id, org_id, person_id, content, add_time, update_time Sort direction. Allowed: ASC, DESC (Default: `DESC`) Date from which to fetch notes. Format: YYYY-MM-DD Date until which to fetch notes. Format: YYYY-MM-DD Maximum number of results to return ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "notes": { "items": { "additionalProperties": true, "type": "object" }, "title": "Notes", "type": "array" }, "total": { "default": 0, "title": "Total", "type": "integer" } }, "required": [ "success" ], "title": "SearchNotesOutput", "type": "object" } ``` ### Parameters The search term (minimum 2 characters, or 1 with exact_match) Fields to search from. Allowed: custom_fields, email, notes, phone, name. Element type: string When true, only full exact matches are returned (not case sensitive) Filter by organization ID (upper limit: 2000) Optional fields to include. Allowed: person.picture Pagination start offset Items per page ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "items": { "items": { "additionalProperties": true, "type": "object" }, "title": "Items", "type": "array" } }, "required": [ "success" ], "title": "SearchPersonsOutput", "type": "object" } ``` ### Parameters ID of the deal to update New deal title ID of the new owner user ID of the person to associate ID of the organization to associate ID of the pipeline ID of the stage in the pipeline Value of the deal Currency (3-character code) Status. Allowed values: open, won, lost, deleted Deal success probability percentage Reason the deal was lost (when status=lost) Visibility. 1 = private, 3 = shared A note to add to the deal ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "UpdateDealOutput", "type": "object" } ``` ### Parameters The ID of the lead to update New title of the lead ID of the person to link to ID of the organization to link to ID of the new owner user Lead label IDs. Element type: string (UUID) Expected close date. Format: YYYY-MM-DD Visibility. Allowed values: 1, 3, 5, 7 Whether the lead was seen in the UI Whether the lead is archived ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "UpdateLeadOutput", "type": "object" } ``` ### Parameters The ID of the person to update New name of the person ID of the new owner user ID of the organization this person will belong to Email addresses. Element type: object with keys {value, primary, label} Phone numbers. Element type: object with keys {value, primary, label} Visibility. 1 = private, 3 = shared ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Data" } }, "required": [ "success" ], "title": "UpdatePersonOutput", "type": "object" } ``` ## Limits & Quotas - **Rate limits**: Pipedrive enforces per-plan rate limits. Professional plan: 200 requests/10 seconds; Enterprise plan: 400 requests/10 seconds. Exceeding the limit returns HTTP 429. - **Pagination**: List endpoints use cursor-based (v2) or offset-based (v1) pagination. The `list_deals` tool supports cursor; `get_all_leads` auto-paginates. - **Error model**: Non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. The error string includes the HTTP status code and response body for debugging. ## Related integrations ## Links - [Pipedrive](https://www.pipedrive.com) --- # PostgreSQL Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/postgresql PostgreSQL database integration for executing SQL queries, managing tables, and performing CRUD operations. Uses the asyncpg driver. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/postgresql/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} PostgreSQL logo ## Overview Add **PostgreSQL** to any ModuleX agent or workflow. PostgreSQL database integration via the `asyncpg` driver. Provides raw SQL execution + CRUD + upsert + introspection. **Categories**: Databases · Database · Data Management · Storage · **Auth**: PostgreSQL Database Credentials · **Actions**: 10 ## Authentication ### PostgreSQL Database Credentials Authenticate using PostgreSQL connection credentials (host/port/user/password/database). #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Host | PostgreSQL server hostname or IP | Yes | `localhost or postgres.example.com` | | Port | PostgreSQL server port (defaults to 5432 if omitted) | No | `5432` | | Username | PostgreSQL username for authentication | Yes | `-` | | Password | PostgreSQL password for authentication | Yes | `-` | | Database | The database name to connect to | Yes | `-` | | SSL Mode | SSL mode: 'disabled', 'verify' (default — full verify), or 'skip_verification' (encrypted but no cert check) | No | `verify | skip_verification | disabled` | ## Available Actions ### Parameters The SQL to execute Values for $1, $2, ... placeholders ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "row_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Row Count" }, "data": { "anyOf": [ { "items": { "additionalProperties": true, "type": "object" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Data" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "affected_rows": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Affected Rows" } }, "required": [ "success" ], "title": "ExecuteRawQueryOutput", "type": "object" } ``` ### Parameters The table name Column-value pairs to insert The schema name (default 'public') (Default: `public`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "table": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Table" }, "schema_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Schema Name" }, "inserted_row": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Inserted Row" }, "columns": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Columns" } }, "required": [ "success" ], "title": "CreateRowOutput", "type": "object" } ``` ### Parameters The table name WHERE condition with '?' placeholders Values matching the '?' placeholders in the condition The schema name (default 'public') (Default: `public`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "table": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Table" }, "schema_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Schema Name" }, "affected_rows": { "default": 0, "title": "Affected Rows", "type": "integer" }, "deleted_rows": { "items": { "additionalProperties": true, "type": "object" }, "title": "Deleted Rows", "type": "array" } }, "required": [ "success" ], "title": "DeleteRowOutput", "type": "object" } ``` ### Parameters The table name Column-value pairs to update WHERE condition with '?' placeholders Values matching the '?' placeholders The schema name (default 'public') (Default: `public`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "table": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Table" }, "schema_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Schema Name" }, "affected_rows": { "default": 0, "title": "Affected Rows", "type": "integer" }, "updated_columns": { "items": { "type": "string" }, "title": "Updated Columns", "type": "array" }, "updated_rows": { "items": { "additionalProperties": true, "type": "object" }, "title": "Updated Rows", "type": "array" } }, "required": [ "success" ], "title": "UpdateRowOutput", "type": "object" } ``` ### Parameters The table name Column-value pairs to insert/update Column to use for conflict detection (usually PK) The schema name (default 'public') (Default: `public`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "table": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Table" }, "schema_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Schema Name" }, "upserted_row": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Upserted Row" }, "conflict_target": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Conflict Target" } }, "required": [ "success" ], "title": "UpsertRowOutput", "type": "object" } ``` ### Parameters The table name The column to filter by Comparison operator (=, >, >=, <, !=, <=, LIKE, ILIKE) (Default: `=`) Value to compare against (typed dynamically) The schema name (default 'public') (Default: `public`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "table": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Table" }, "schema_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Schema Name" }, "row_count": { "default": 0, "title": "Row Count", "type": "integer" }, "data": { "items": { "additionalProperties": true, "type": "object" }, "title": "Data", "type": "array" } }, "required": [ "success" ], "title": "FindRowOutput", "type": "object" } ``` ### Parameters The table name WHERE condition with '?' placeholders Values matching the '?' placeholders in the condition The schema name (default 'public') (Default: `public`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "table": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Table" }, "schema_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Schema Name" }, "row_count": { "default": 0, "title": "Row Count", "type": "integer" }, "data": { "items": { "additionalProperties": true, "type": "object" }, "title": "Data", "type": "array" } }, "required": [ "success" ], "title": "ExecuteQueryWithConditionOutput", "type": "object" } ``` ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "schemas": { "items": { "type": "string" }, "title": "Schemas", "type": "array" }, "count": { "default": 0, "title": "Count", "type": "integer" } }, "required": [ "success" ], "title": "ListSchemasOutput", "type": "object" } ``` ### Parameters The schema name (default 'public') (Default: `public`) ### Response ```json { "$defs": { "TableEntry": { "additionalProperties": false, "properties": { "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Type" } }, "title": "TableEntry", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "schema_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Schema Name" }, "tables": { "items": { "$ref": "#/$defs/TableEntry" }, "title": "Tables", "type": "array" }, "count": { "default": 0, "title": "Count", "type": "integer" } }, "required": [ "success" ], "title": "ListTablesOutput", "type": "object" } ``` ### Parameters The table name The schema name (default 'public') (Default: `public`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "table": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Table" }, "schema_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Schema Name" }, "columns": { "items": { "additionalProperties": true, "type": "object" }, "title": "Columns", "type": "array" }, "column_count": { "default": 0, "title": "Column Count", "type": "integer" }, "primary_keys": { "items": { "type": "string" }, "title": "Primary Keys", "type": "array" } }, "required": [ "success" ], "title": "DescribeTableOutput", "type": "object" } ``` ## Limits & Quotas - 60s connection timeout. - `find_row` operator allowlist: `=`, `>`, `>=`, `<`, `!=`, `<=`, `LIKE`, `ILIKE` (case-insensitive accepted as `like`/`ilike` too). - `asyncpg` is imported lazily so the manifest can be inspected even without the driver installed. ## Related integrations ## Links - [PostgreSQL](https://www.postgresql.org/) --- # PostGrid Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/postgrid Programmatic direct mail delivery via the PostGrid Print & Mail API {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/postgrid/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} PostGrid logo ## Overview Add **PostGrid** to any ModuleX agent or workflow. Programmatic direct mail delivery via the PostGrid Print & Mail API (`api.postgrid.com/print-mail/v1`). **Categories**: Marketing & Advertising · Marketing · Business Services · **Auth**: API Key · **Actions**: 3 ## Authentication ### API Key Authentication Authenticate using your PostGrid API key Sign in at https://app.postgrid.com Navigate to 'Settings' > 'API Keys' Copy your live or test API key Paste the API key below #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | PostGrid API Key | Your PostGrid API key from app.postgrid.com | Yes | `live_sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | ## Available Actions ### Parameters The first name of the contact The last name of the contact The contact's company name The contact's first address line The contact's second address line The contact's city The province or state of the contact The contact's email address The contact's phone number The contact's job title The postal code or ZIP code of the contact ISO 3166-1 country code of the contact's address. Defaults to CA (Default: `CA`) A description for the contact If true, skip address verification and mark the address as failed ### Response ```json { "$defs": { "ContactResource": { "additionalProperties": false, "description": "A contact object returned by the PostGrid API.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "object": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Object" }, "live": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Live" }, "first_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "First Name" }, "last_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Name" }, "company_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Company Name" }, "address_line1": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Address Line1" }, "address_line2": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Address Line2" }, "city": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "City" }, "province_or_state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Province Or State" }, "postal_or_zip": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Postal Or Zip" }, "country": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Country" }, "country_code": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Country Code" }, "email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email" }, "phone_number": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Phone Number" }, "job_title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Job Title" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "address_status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Address Status" } }, "title": "ContactResource", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "contact": { "anyOf": [ { "$ref": "#/$defs/ContactResource" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "CreateContactOutput", "type": "object" } ``` ### Parameters The ID or contact object of the receiver The ID or contact object of the sender The HTML content of the letter Location where the address will be placed. One of: top_first_page, insert_blank_page Whether the letter is double sided Whether the letter will be printed in color Page number to be perforated Extra services for the letter. One of: certified, certified_return_receipt, registered Envelope type. One of: standard_double_window, flat The ID of the return envelope to be used Desired date for the letter to be sent out (ISO 8601 format) A description for the letter Whether to use express shipping Mailing class. One of: standard_class, first_class. Defaults to first_class Letter size. One of: us_letter, us_legal, a4 ### Response ```json { "$defs": { "LetterResource": { "additionalProperties": false, "description": "A letter object returned by the PostGrid API.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "object": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Object" }, "live": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Live" }, "send_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Send Date" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" } }, "title": "LetterResource", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "letter": { "anyOf": [ { "$ref": "#/$defs/LetterResource" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "CreateLetterOutput", "type": "object" } ``` ### Parameters The ID or contact object of the receiver The ID or contact object of the sender The HTML content for the front of the postcard The HTML content for the back of the postcard Postcard size. One of: 6x4, 9x6, 11x6 Desired date for the postcard to be sent out (ISO 8601 format) Whether to use express shipping A description for the postcard Mailing class. One of: standard_class, first_class. Defaults to first_class ### Response ```json { "$defs": { "PostcardResource": { "additionalProperties": false, "description": "A postcard object returned by the PostGrid API.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "object": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Object" }, "live": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Live" }, "send_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Send Date" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "size": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Size" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" } }, "title": "PostcardResource", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "postcard": { "anyOf": [ { "$ref": "#/$defs/PostcardResource" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "CreatePostcardOutput", "type": "object" } ``` ## Limits & Quotas - No publicly documented rate limits. PostGrid applies per-account request limits based on plan tier. - Sending physical mail incurs per-item costs based on the PostGrid pricing plan. - Error model: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. ## Related integrations ## Links - [PostGrid](https://www.postgrid.com) --- # PostHog Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/posthog Product analytics platform: events, persons, groups, cohorts, dashboards, insights, experiments, feature flags, surveys, session recordings, error tracking, and more (78 actions). {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/posthog/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} PostHog logo ## Overview Add **PostHog** to any ModuleX agent or workflow. Product analytics platform integration via the PostHog REST + ingest APIs. Pure HTTP. **78 actions** — the largest single integration in the package. **Categories**: Analytics & Data · Data & Analytics · Data · Development · **Auth**: PostHog API Key · **Actions**: 78 ## Authentication ### PostHog API Key Authentication Connect to PostHog using your personal API key, project ID, and instance URL. #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Personal API Key | Your PostHog personal API key | Yes | `phx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | | Project ID | PostHog project ID (found in project settings) | Yes | `12345` | | Instance URL | Your PostHog instance URL — required. MUST be ONLY the host with scheme, no trailing slash, no path. Pick the line that matches your workspace: • US cloud: https://us.posthog.com • EU cloud: https://eu.posthog.com • Self-hosted: https://posthog.your-company.com DO NOT append /api, /api/projects, or any other path — the rest of the URL is added automatically. Trailing slash also not allowed. | Yes | `https://us.posthog.com` | ## Available Actions ### Parameters PostHog project ID PostHog instance URL (default: https://app.posthog.com) Max results (Default: `100`) Results to skip (Default: `0`) Search query Filter by pinned status ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID dashboard_id to retrieve PostHog instance URL (default: https://app.posthog.com) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID Dashboard name PostHog instance URL (default: https://app.posthog.com) Dashboard description Pin the dashboard Tag list ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID Dashboard ID PostHog instance URL (default: https://app.posthog.com) New name New description Pin status Tag list ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID dashboard_id to delete PostHog instance URL (default: https://app.posthog.com) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID PostHog instance URL (default: https://app.posthog.com) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID experiment_id to retrieve PostHog instance URL (default: https://app.posthog.com) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID Experiment name Feature flag key linked to the experiment PostHog instance URL (default: https://app.posthog.com) Experiment description 'product' or 'web' (Default: `product`) Primary metric definitions Secondary metric definitions Experiment variants Minimum detectable effect % (Default: `30`) Filter out internal/test accounts (Default: `true`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID Experiment ID PostHog instance URL (default: https://app.posthog.com) New name New description Primary metrics Secondary metrics Launch the experiment 'won' / 'lost' / 'inconclusive' Archive the experiment ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID experiment_id to delete PostHog instance URL (default: https://app.posthog.com) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID Experiment ID PostHog instance URL (default: https://app.posthog.com) Force refresh (Default: `false`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID PostHog instance URL (default: https://app.posthog.com) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID Feature flag ID (integer). Mutually exclusive with flag_key — provide one or the other. Feature flag key (string). Mutually exclusive with flag_id — provide one or the other. PostHog instance URL (default: https://app.posthog.com) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID Flag key Flag name PostHog instance URL (default: https://app.posthog.com) Description Flag filters Active (Default: `true`) Tag list ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID Flag key to update PostHog instance URL (default: https://app.posthog.com) New name New description Updated filters Active state Tag list ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID flag_id to delete PostHog instance URL (default: https://app.posthog.com) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID PostHog instance URL (default: https://app.posthog.com) Max results (Default: `100`) Results to skip (Default: `0`) Search query ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID insight_id to retrieve PostHog instance URL (default: https://app.posthog.com) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID Insight name PostHog instance URL (default: https://app.posthog.com) Description Insight filter config HogQL/JSON query Dashboard IDs to attach Tag list ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID Insight ID PostHog instance URL (default: https://app.posthog.com) New name New description Filter config Query Tag list ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID insight_id to delete PostHog instance URL (default: https://app.posthog.com) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID Query body PostHog instance URL (default: https://app.posthog.com) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID PostHog instance URL (default: https://app.posthog.com) Max results (Default: `100`) Results to skip (Default: `0`) Search query Filter by status ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID issue_id to retrieve PostHog instance URL (default: https://app.posthog.com) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID PostHog instance URL (default: https://app.posthog.com) Max results (Default: `100`) Results to skip (Default: `0`) Search query ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID survey_id to retrieve PostHog instance URL (default: https://app.posthog.com) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID Survey name 'popover' / 'widget' / 'api' (Default: `popover`) PostHog instance URL (default: https://app.posthog.com) Description Survey questions Trigger conditions Linked feature flag ID Targeting filters Survey appearance config Start date End date ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID Survey ID PostHog instance URL (default: https://app.posthog.com) New name New description Questions Trigger conditions Archive the survey Start date End date ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID survey_id to delete PostHog instance URL (default: https://app.posthog.com) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog instance URL (default: https://app.posthog.com) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters Organization ID PostHog instance URL (default: https://app.posthog.com) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID PostHog instance URL (default: https://app.posthog.com) Max results (Default: `100`) Results to skip (Default: `0`) Search query ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID PostHog instance URL (default: https://app.posthog.com) Max results (Default: `100`) Results to skip (Default: `0`) Search query Filter by event names Filter by property type ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project API key Distinct user ID Event name Event properties ISO 8601 timestamp PostHog ingest URL (default: https://us.i.posthog.com) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project API key List of event objects Flag historical imports (Default: `false`) PostHog ingest URL (default: https://us.i.posthog.com) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project API key Distinct user ID Properties to set PostHog ingest URL (default: https://us.i.posthog.com) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project API key Canonical distinct ID Alias distinct ID to merge in PostHog ingest URL (default: https://us.i.posthog.com) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project API key Distinct user ID Group memberships Person properties Group properties PostHog ingest URL (default: https://us.i.posthog.com) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project API key Group type (e.g. 'company') Group key (the unique ID) Properties to set on the group PostHog ingest URL (default: https://us.i.posthog.com) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID PostHog instance URL (default: https://app.posthog.com) Max results (Default: `100`) Results to skip (Default: `0`) Search query Filter by distinct ID Filter by email ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID person_id to retrieve PostHog instance URL (default: https://app.posthog.com) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID Person ID Properties to set PostHog instance URL (default: https://app.posthog.com) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID Person ID Also delete events (GDPR) (Default: `false`) PostHog instance URL (default: https://app.posthog.com) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID Person IDs to delete Also delete events (Default: `false`) PostHog instance URL (default: https://app.posthog.com) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID Group type index (0-4) PostHog instance URL (default: https://app.posthog.com) Search query ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID Group type index Group key PostHog instance URL (default: https://app.posthog.com) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID PostHog instance URL (default: https://app.posthog.com) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID PostHog instance URL (default: https://app.posthog.com) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID cohort_id to retrieve PostHog instance URL (default: https://app.posthog.com) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID Cohort name PostHog instance URL (default: https://app.posthog.com) Description Static cohort (Default: `false`) Filter config Group filters ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID Cohort ID PostHog instance URL (default: https://app.posthog.com) New name New description Filters Groups ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID cohort_id to delete PostHog instance URL (default: https://app.posthog.com) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID Cohort ID PostHog instance URL (default: https://app.posthog.com) Max results (Default: `100`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID PostHog instance URL (default: https://app.posthog.com) Max results (Default: `100`) Results to skip (Default: `0`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID recording_id to retrieve PostHog instance URL (default: https://app.posthog.com) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID recording_id to delete PostHog instance URL (default: https://app.posthog.com) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID PostHog instance URL (default: https://app.posthog.com) Max results (Default: `100`) Results to skip (Default: `0`) Search query ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID action_id to retrieve PostHog instance URL (default: https://app.posthog.com) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID Action name PostHog instance URL (default: https://app.posthog.com) Description Action steps (conditions) Tag list ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID Action ID PostHog instance URL (default: https://app.posthog.com) New name New description Steps Tag list ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID Action ID PostHog instance URL (default: https://app.posthog.com) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID Action name to find + delete PostHog instance URL (default: https://app.posthog.com) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID PostHog instance URL (default: https://app.posthog.com) Max results (Default: `100`) Results to skip (Default: `0`) Search query ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID annotation_id to retrieve PostHog instance URL (default: https://app.posthog.com) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID Annotation text Date marker (ISO 8601) PostHog instance URL (default: https://app.posthog.com) Linked insight (dashboard item) 'dashboard_item' / 'project' / 'organization' (Default: `organization`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID Annotation ID PostHog instance URL (default: https://app.posthog.com) New content New date marker New scope ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID annotation_id to delete PostHog instance URL (default: https://app.posthog.com) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID PostHog instance URL (default: https://app.posthog.com) Max results (Default: `100`) Results to skip (Default: `0`) Filter by insight ID ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID alert_id to retrieve PostHog instance URL (default: https://app.posthog.com) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID Insight ID Alert name PostHog instance URL (default: https://app.posthog.com) Threshold config User IDs to notify Enabled (Default: `true`) Notification target config ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID Alert ID PostHog instance URL (default: https://app.posthog.com) New name Threshold config User IDs Enabled state Notification targets ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID alert_id to delete PostHog instance URL (default: https://app.posthog.com) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID PostHog instance URL (default: https://app.posthog.com) Max results (Default: `100`) Results to skip (Default: `0`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID feature_id to retrieve PostHog instance URL (default: https://app.posthog.com) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID Feature name PostHog instance URL (default: https://app.posthog.com) Description draft / beta / general-availability / archived (Default: `beta`) Documentation URL Linked feature flag ID ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID Feature ID PostHog instance URL (default: https://app.posthog.com) New name New description Updated stage Updated documentation URL ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID feature_id to delete PostHog instance URL (default: https://app.posthog.com) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ### Parameters PostHog project ID Feature name to find + delete PostHog instance URL (default: https://app.posthog.com) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "result": { "default": null, "title": "Result" } }, "required": [ "success" ], "title": "PostHogResult", "type": "object" } ``` ## Related integrations ## Links - [PostHog](https://posthog.com) --- # Postman Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/postman API development and testing platform for building, monitoring, and managing APIs {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/postman/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Postman logo ## Overview Add **Postman** to any ModuleX agent or workflow. API development and testing platform for building, monitoring, and managing APIs against the Postman REST API (`api.getpostman.com`). **Categories**: Developer Tools & Infrastructure · Api Testing · **Auth**: API Key · **Actions**: 4 ## Authentication ### API Key Authentication Authenticate using your Postman API key Go to https://web.postman.co and sign in Click your avatar in the top-right, then select Settings Navigate to the 'API Keys' tab Generate a new API key or copy your existing one Paste the API key below #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Postman API Key | Your Postman API key from web.postman.co/settings/me/api-keys | Yes | `PMAK-xxxxxxxxxxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | ## Available Actions ### Parameters The name for the new environment The ID of the workspace to create the environment in List of variable objects with keys: key (string), value (string), enabled (boolean), type ('secret' or 'default') ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "environment_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Environment Id" }, "environment_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Environment Name" } }, "required": [ "success" ], "title": "CreateEnvironmentOutput", "type": "object" } ``` ### Response ```json { "$defs": { "WorkspaceSummary": { "additionalProperties": false, "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Type" } }, "title": "WorkspaceSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "workspaces": { "items": { "$ref": "#/$defs/WorkspaceSummary" }, "title": "Workspaces", "type": "array" } }, "required": [ "success" ], "title": "ListWorkspaceIdOptionsOutput", "type": "object" } ``` ### Parameters The ID of the monitor to run ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "run": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Run" } }, "required": [ "success" ], "title": "RunMonitorOutput", "type": "object" } ``` ### Parameters The ID of the environment containing the variable The variable key (name) to update The new value for the variable The ID of the workspace containing the environment Whether the variable is enabled or not ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "environment_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Environment Id" }, "environment_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Environment Name" } }, "required": [ "success" ], "title": "UpdateVariableOutput", "type": "object" } ``` ## Limits & Quotas - **Free plan**: 25 requests/minute per API key. - **Paid plans**: Higher limits; see https://learning.postman.com/docs/developer/postman-api/postman-api-rate-limits/. - **Error model**: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. Plan for retries on the agent side based on the error string. ## Related integrations ## Links - [Postman](https://www.postman.com) --- # Product Hunt Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/product-hunt Discover and explore tech products, topics, and community posts via the Product Hunt GraphQL API {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/product_hunt/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Product Hunt logo ## Overview Add **Product Hunt** to any ModuleX agent or workflow. Discover and explore tech products, topics, and community posts via the Product Hunt GraphQL API (`api.producthunt.com/v2/api/graphql`). **Categories**: Social Media · Productivity & Collaboration · Marketing · **Auth**: OAuth2 · **Actions**: 1 ## Authentication ### OAuth2 Authentication Connect using Product Hunt OAuth (recommended) #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Client ID | Product Hunt OAuth App Client ID | Yes | `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | | Client Secret | Product Hunt OAuth App Client Secret | Yes | `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | #### OAuth Configuration - **Authorization URL**: `https://api.producthunt.com/v2/oauth/authorize` - **Token URL**: `https://api.producthunt.com/v2/oauth/token` - **Scopes**: `public`, `private` ## Available Actions ### Response ```json { "$defs": { "TopicOption": { "additionalProperties": false, "description": "A single topic option with slug and display name.", "properties": { "value": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Value" }, "label": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Label" } }, "title": "TopicOption", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "topics": { "items": { "$ref": "#/$defs/TopicOption" }, "title": "Topics", "type": "array" } }, "required": [ "success" ], "title": "ListTopicOptionsOutput", "type": "object" } ``` ## Limits & Quotas - Product Hunt API v2 rate limits are not publicly documented in detail; typical observed limit is approximately 450 requests per 15-minute window per token. - No per-request pricing; API access is free for authorized applications. - Error model: non-2xx responses and GraphQL-level errors are caught and returned as `success=False` + `error` rather than raising. ## Related integrations ## Links - [Product Hunt](https://www.producthunt.com) --- # Pulse Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/pulse Extract text and structured content from PDFs, images, and Office files using Pulse OCR. Returns clean markdown, page counts, layout bounding boxes, chunks, and extracted figures. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/pulse/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Pulse logo ## Overview Add **Pulse** to any ModuleX agent or workflow. Extract text and structured content from PDFs, images, and Office files using Pulse OCR, against the Pulse REST API (`api.runpulse.com`). **Categories**: AI & Machine Learning · Document Processing · Ocr · **Auth**: API Key · **Actions**: 1 ## Authentication ### API Key Authentication Authenticate using your Pulse API key Go to https://www.runpulse.com and sign up or log in Open your dashboard and navigate to the API keys section Create a new API key or copy your existing one Paste the API key below #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Pulse API Key | Your Pulse API key from the runpulse.com dashboard | Yes | `-` | ## Available Actions ### Parameters Public HTTP(S) URL to a document to process (PDF, image, or Office file) Page range to process, 1-indexed (e.g. "1-2,5"). Omit for all pages. Chunking strategies (comma-separated: semantic, header, page, recursive) Maximum characters per chunk when chunking is enabled Whether to include HTML in the response Whether to extract figures from the document Whether to generate descriptions/captions for extracted figures ### Response ```json { "$defs": { "PlanInfo": { "additionalProperties": false, "description": "Plan usage information returned with each extraction.", "properties": { "pages_used": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Pages Used" }, "tier": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Tier" }, "note": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Note" } }, "title": "PlanInfo", "type": "object" } }, "additionalProperties": false, "description": "Result of parsing a document with Pulse OCR.", "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "markdown": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Markdown" }, "page_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Page Count" }, "job_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Job Id" }, "plan_info": { "anyOf": [ { "$ref": "#/$defs/PlanInfo" }, { "type": "null" } ], "default": null }, "bounding_boxes": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Bounding Boxes" }, "extraction_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Extraction Url" }, "html": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Html" }, "structured_output": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Structured Output" }, "chunks": { "items": {}, "title": "Chunks", "type": "array" }, "figures": { "items": {}, "title": "Figures", "type": "array" } }, "required": [ "success" ], "title": "ParserOutput", "type": "object" } ``` ## Limits & Quotas - **Billing**: Pulse bills in credits; each extraction is billed by the number of pages (or tables) processed. API keys can carry per-key credit caps. - **Large documents**: documents above ~70 pages return an `extraction_url` pointing at the full results instead of inlining the markdown. - **Error model**: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. Plan for retries on the agent side based on the error string. ## Related integrations ## Links - [Pulse](https://www.runpulse.com) --- # Quiver Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/quiver Generate SVG images from text prompts or vectorize raster images into SVGs using QuiverAI. Supports reference images, style instructions, and multiple output generation. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/quiver/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Quiver logo ## Overview Add **Quiver** to any ModuleX agent or workflow. AI-powered SVG generation and raster vectorization against the QuiverAI REST API (`api.quiver.ai/v1`). Generate clean, scalable vector graphics from text prompts or convert raster images into editable SVGs. **Categories**: AI & Machine Learning · Image Generation · Design · **Auth**: API Key · **Actions**: 3 ## Authentication ### API Key Authentication Authenticate using your QuiverAI API key Go to https://quiver.ai and sign up or log in Open your account dashboard and navigate to API keys Create a new API key or copy your existing one Paste the API key below #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | QuiverAI API Key | Your QuiverAI API key from quiver.ai | Yes | `-` | ## Available Actions ### Parameters A text description of the desired SVG The model to use for SVG generation (e.g. "arrow-1.1") (Default: `arrow-1.1`) Style or formatting guidance for the SVG output Reference image URLs to guide SVG generation (up to 4 for arrow-1.1, 16 for arrow-1.1-max) Number of SVGs to generate (1-16, default 1) Sampling temperature (0-2, default 1) Nucleus sampling probability (0-1, default 1) Maximum output tokens (1-65536) Token penalty for prior output (-2 to 2, default 0) ### Response ```json { "$defs": { "SvgArtifact": { "additionalProperties": false, "description": "A single generated SVG returned in the response ``data`` array.", "properties": { "svg": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Svg" }, "mime_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Mime Type" } }, "title": "SvgArtifact", "type": "object" }, "UsageInfo": { "additionalProperties": false, "description": "Token usage statistics returned alongside an SVG response.\n\nQuiver retains these fields for compatibility; they may be zeroed.\nBilling is reported via ``credits`` on the parent output.", "properties": { "input_tokens": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Input Tokens" }, "output_tokens": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Output Tokens" }, "total_tokens": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Total Tokens" } }, "title": "UsageInfo", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "created": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Created" }, "svg_content": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Svg Content" }, "artifacts": { "items": { "$ref": "#/$defs/SvgArtifact" }, "title": "Artifacts", "type": "array" }, "credits": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Credits" }, "usage": { "anyOf": [ { "$ref": "#/$defs/UsageInfo" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "TextToSvgOutput", "type": "object" } ``` ### Parameters URL of the raster image to vectorize into SVG The model to use for vectorization (e.g. "arrow-1.1") (Default: `arrow-1.1`) Automatically crop the image to its dominant subject Square resize target in pixels (128-4096) Sampling temperature (0-2, default 1) Nucleus sampling probability (0-1, default 1) Maximum output tokens (1-65536) Token penalty for prior output (-2 to 2, default 0) ### Response ```json { "$defs": { "SvgArtifact": { "additionalProperties": false, "description": "A single generated SVG returned in the response ``data`` array.", "properties": { "svg": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Svg" }, "mime_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Mime Type" } }, "title": "SvgArtifact", "type": "object" }, "UsageInfo": { "additionalProperties": false, "description": "Token usage statistics returned alongside an SVG response.\n\nQuiver retains these fields for compatibility; they may be zeroed.\nBilling is reported via ``credits`` on the parent output.", "properties": { "input_tokens": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Input Tokens" }, "output_tokens": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Output Tokens" }, "total_tokens": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Total Tokens" } }, "title": "UsageInfo", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "created": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Created" }, "svg_content": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Svg Content" }, "artifacts": { "items": { "$ref": "#/$defs/SvgArtifact" }, "title": "Artifacts", "type": "array" }, "credits": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Credits" }, "usage": { "anyOf": [ { "$ref": "#/$defs/UsageInfo" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "ImageToSvgOutput", "type": "object" } ``` ### Response ```json { "$defs": { "ModelInfo": { "additionalProperties": false, "description": "A single available model row in ``list_models``.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "created": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Created" }, "owned_by": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Owned By" }, "input_modalities": { "items": { "type": "string" }, "title": "Input Modalities", "type": "array" }, "output_modalities": { "items": { "type": "string" }, "title": "Output Modalities", "type": "array" }, "context_length": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Context Length" }, "max_output_length": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Max Output Length" }, "supported_operations": { "items": { "type": "string" }, "title": "Supported Operations", "type": "array" }, "supported_sampling_parameters": { "items": { "type": "string" }, "title": "Supported Sampling Parameters", "type": "array" } }, "title": "ModelInfo", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "models": { "items": { "$ref": "#/$defs/ModelInfo" }, "title": "Models", "type": "array" }, "total_models": { "default": 0, "title": "Total Models", "type": "integer" }, "raw": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Raw" } }, "required": [ "success" ], "title": "ListModelsOutput", "type": "object" } ``` ## Limits & Quotas - **Models**: `arrow-1.1` is the default; `arrow-1.1-max` offers higher fidelity for detailed images and supports more reference images. - **Generation**: `n` of 1-16 outputs per request; `temperature` 0-2, `top_p` 0-1, `presence_penalty` -2 to 2, `max_output_tokens` up to 65536. - **Vectorization**: decoded images cannot exceed 4096 x 4096 px; `target_size` accepts 128-4096 px. - **Billing**: each request reports a `credits` debit; `usage` token fields are retained for compatibility and may be zeroed. - **Error model**: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. Plan for retries on the agent side based on the error string. ## Related integrations ## Links - [Quiver](https://quiver.ai) --- # Railway Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/railway Integrate Railway into workflows to list projects, manage services and environments, monitor deployments, trigger and roll back service deployments, and manage environment variables. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/railway/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Railway logo ## Overview Add **Railway** to any ModuleX agent or workflow. Manage Railway projects, services, environments, deployments, and environment variables against Railway's public GraphQL API (`backboard.railway.com/graphql/v2`). **Categories**: Developer Tools & Infrastructure · Cloud · Ci Cd · **Auth**: API Token · **Actions**: 20 ## Authentication ### API Token Authentication Authenticate using your Railway API token Sign in at https://railway.com and open your account settings Go to the 'Tokens' section under Account Settings Create a new token (account, workspace, or project scope) Paste the token below #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Railway API Token | Your Railway API token from account settings | Yes | `-` | ## Available Actions ### Parameters Railway token type. Use "account" for account, workspace, or OAuth tokens, or "project" for project tokens. (Default: `account`) Workspace ID to list projects from Maximum number of projects to return Cursor for pagination ### Response ```json { "$defs": { "PageInfo": { "additionalProperties": false, "description": "Cursor-pagination metadata returned by list actions.", "properties": { "has_next_page": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Has Next Page" }, "end_cursor": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "End Cursor" } }, "title": "PageInfo", "type": "object" }, "ProjectSummary": { "additionalProperties": false, "description": "A single project row in ``list_projects``.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "updated_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated At" } }, "title": "ProjectSummary", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "projects": { "items": { "$ref": "#/$defs/ProjectSummary" }, "title": "Projects", "type": "array" }, "page_info": { "anyOf": [ { "$ref": "#/$defs/PageInfo" }, { "type": "null" } ], "default": null }, "count": { "default": 0, "title": "Count", "type": "integer" } }, "required": [ "success" ], "title": "ListProjectsOutput", "type": "object" } ``` ### Parameters Railway project ID Railway token type. Use "account" for account, workspace, or OAuth tokens, or "project" for project tokens. (Default: `account`) ### Response ```json { "$defs": { "ProjectDetail": { "additionalProperties": false, "description": "A project with its services and environments (``get_project``).", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "updated_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated At" }, "services": { "items": { "$ref": "#/$defs/ProjectService" }, "title": "Services", "type": "array" }, "environments": { "items": { "$ref": "#/$defs/ProjectEnvironment" }, "title": "Environments", "type": "array" } }, "title": "ProjectDetail", "type": "object" }, "ProjectEnvironment": { "additionalProperties": false, "description": "An environment attached to a project in ``get_project``.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" } }, "title": "ProjectEnvironment", "type": "object" }, "ProjectService": { "additionalProperties": false, "description": "A service attached to a project in ``get_project``.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "icon": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Icon" } }, "title": "ProjectService", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "project": { "anyOf": [ { "$ref": "#/$defs/ProjectDetail" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "GetProjectOutput", "type": "object" } ``` ### Parameters Project name Railway token type. Use "account" for account, workspace, or OAuth tokens, or "project" for project tokens. (Default: `account`) Project description Workspace ID to create the project in Whether the project should be publicly visible Name for the default environment Whether to enable pull request deploys ### Response ```json { "$defs": { "CreatedResource": { "additionalProperties": false, "description": "A freshly created resource carrying just id + name.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" } }, "title": "CreatedResource", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "project": { "anyOf": [ { "$ref": "#/$defs/CreatedResource" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "CreateProjectOutput", "type": "object" } ``` ### Parameters Railway project ID Railway token type. Use "account" for account, workspace, or OAuth tokens, or "project" for project tokens. (Default: `account`) Updated project name Updated project description Whether the project should be publicly visible Whether to enable pull request deploy environments ### Response ```json { "$defs": { "UpdatedProject": { "additionalProperties": false, "description": "The project echoed back by ``update_project``.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" } }, "title": "UpdatedProject", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "project": { "anyOf": [ { "$ref": "#/$defs/UpdatedProject" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "UpdateProjectOutput", "type": "object" } ``` ### Parameters Railway project ID Railway token type. Use "account" for account, workspace, or OAuth tokens, or "project" for project tokens. (Default: `account`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "deleted": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Deleted" } }, "required": [ "success" ], "title": "DeleteProjectOutput", "type": "object" } ``` ### Parameters Railway project ID Destination workspace ID Railway token type. Use "account" for account, workspace, or OAuth tokens, or "project" for project tokens. (Default: `account`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "transferred": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Transferred" } }, "required": [ "success" ], "title": "TransferProjectOutput", "type": "object" } ``` ### Parameters Railway project ID Railway token type. Use "account" for account, workspace, or OAuth tokens, or "project" for project tokens. (Default: `account`) ### Response ```json { "$defs": { "ProjectMember": { "additionalProperties": false, "description": "A single member row in ``list_project_members``.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "role": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Role" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email" }, "avatar": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Avatar" } }, "title": "ProjectMember", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "members": { "items": { "$ref": "#/$defs/ProjectMember" }, "title": "Members", "type": "array" }, "count": { "default": 0, "title": "Count", "type": "integer" } }, "required": [ "success" ], "title": "ListProjectMembersOutput", "type": "object" } ``` ### Parameters Railway project ID Environment name Railway token type. Use "account" for account, workspace, or OAuth tokens, or "project" for project tokens. (Default: `account`) Environment ID to clone from Whether the environment is ephemeral Whether to skip initial deploys for the environment Whether to stage initial changes instead of applying them immediately ### Response ```json { "$defs": { "CreatedResource": { "additionalProperties": false, "description": "A freshly created resource carrying just id + name.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" } }, "title": "CreatedResource", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "environment": { "anyOf": [ { "$ref": "#/$defs/CreatedResource" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "CreateEnvironmentOutput", "type": "object" } ``` ### Parameters Railway environment ID Railway token type. Use "account" for account, workspace, or OAuth tokens, or "project" for project tokens. (Default: `account`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "deleted": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Deleted" } }, "required": [ "success" ], "title": "DeleteEnvironmentOutput", "type": "object" } ``` ### Parameters Railway project ID Service name Railway token type. Use "account" for account, workspace, or OAuth tokens, or "project" for project tokens. (Default: `account`) GitHub repository in owner/name format to deploy from Docker image to deploy, for example redis:7-alpine Git branch to deploy when using a repository source ### Response ```json { "$defs": { "CreatedResource": { "additionalProperties": false, "description": "A freshly created resource carrying just id + name.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" } }, "title": "CreatedResource", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "service": { "anyOf": [ { "$ref": "#/$defs/CreatedResource" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "CreateServiceOutput", "type": "object" } ``` ### Parameters Railway service ID Railway token type. Use "account" for account, workspace, or OAuth tokens, or "project" for project tokens. (Default: `account`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "deleted": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Deleted" } }, "required": [ "success" ], "title": "DeleteServiceOutput", "type": "object" } ``` ### Parameters Railway project ID Railway service ID Railway environment ID Railway token type. Use "account" for account, workspace, or OAuth tokens, or "project" for project tokens. (Default: `account`) Maximum number of deployments to return Cursor for pagination ### Response ```json { "$defs": { "DeploymentSummary": { "additionalProperties": false, "description": "A single deployment row in ``list_deployments``.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" }, "static_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Static Url" }, "can_rollback": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Can Rollback" }, "can_redeploy": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Can Redeploy" } }, "title": "DeploymentSummary", "type": "object" }, "PageInfo": { "additionalProperties": false, "description": "Cursor-pagination metadata returned by list actions.", "properties": { "has_next_page": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Has Next Page" }, "end_cursor": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "End Cursor" } }, "title": "PageInfo", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "deployments": { "items": { "$ref": "#/$defs/DeploymentSummary" }, "title": "Deployments", "type": "array" }, "page_info": { "anyOf": [ { "$ref": "#/$defs/PageInfo" }, { "type": "null" } ], "default": null }, "count": { "default": 0, "title": "Count", "type": "integer" } }, "required": [ "success" ], "title": "ListDeploymentsOutput", "type": "object" } ``` ### Parameters Railway deployment ID Railway token type. Use "account" for account, workspace, or OAuth tokens, or "project" for project tokens. (Default: `account`) ### Response ```json { "$defs": { "DeploymentDetail": { "additionalProperties": false, "description": "The deployment detail returned by ``get_deployment``.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" }, "static_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Static Url" }, "can_rollback": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Can Rollback" }, "can_redeploy": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Can Redeploy" } }, "title": "DeploymentDetail", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "deployment": { "anyOf": [ { "$ref": "#/$defs/DeploymentDetail" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "GetDeploymentOutput", "type": "object" } ``` ### Parameters Railway service ID Railway environment ID Railway token type. Use "account" for account, workspace, or OAuth tokens, or "project" for project tokens. (Default: `account`) Specific Git commit SHA to deploy ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "deployment_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Deployment Id" } }, "required": [ "success" ], "title": "DeployServiceOutput", "type": "object" } ``` ### Parameters Railway deployment ID Railway token type. Use "account" for account, workspace, or OAuth tokens, or "project" for project tokens. (Default: `account`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "restarted": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Restarted" } }, "required": [ "success" ], "title": "RestartDeploymentOutput", "type": "object" } ``` ### Parameters Railway deployment ID to roll back to (must have canRollback) Railway token type. Use "account" for account, workspace, or OAuth tokens, or "project" for project tokens. (Default: `account`) ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "rolled_back": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Rolled Back" } }, "required": [ "success" ], "title": "RollbackDeploymentOutput", "type": "object" } ``` ### Parameters Railway deployment ID Railway token type. Use "account" for account, workspace, or OAuth tokens, or "project" for project tokens. (Default: `account`) Maximum number of log lines to return ### Response ```json { "$defs": { "DeploymentLog": { "additionalProperties": false, "description": "A single log entry in ``get_deployment_logs``.", "properties": { "timestamp": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Timestamp" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" }, "severity": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Severity" } }, "title": "DeploymentLog", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "logs": { "items": { "$ref": "#/$defs/DeploymentLog" }, "title": "Logs", "type": "array" }, "count": { "default": 0, "title": "Count", "type": "integer" } }, "required": [ "success" ], "title": "GetDeploymentLogsOutput", "type": "object" } ``` ### Parameters Railway project ID Railway environment ID Railway token type. Use "account" for account, workspace, or OAuth tokens, or "project" for project tokens. (Default: `account`) Railway service ID. Omit for shared environment variables. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "variables": { "additionalProperties": true, "title": "Variables", "type": "object" }, "count": { "default": 0, "title": "Count", "type": "integer" } }, "required": [ "success" ], "title": "ListVariablesOutput", "type": "object" } ``` ### Parameters Railway project ID Railway environment ID Variable name Variable value Railway token type. Use "account" for account, workspace, or OAuth tokens, or "project" for project tokens. (Default: `account`) Railway service ID. Omit to create or update a shared variable. Whether to skip automatic redeploys after changing the variable ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "upserted": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Upserted" } }, "required": [ "success" ], "title": "UpsertVariableOutput", "type": "object" } ``` ### Parameters Railway project ID Railway environment ID Variable name to delete Railway token type. Use "account" for account, workspace, or OAuth tokens, or "project" for project tokens. (Default: `account`) Railway service ID. Omit to delete a shared variable. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "deleted": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Deleted" } }, "required": [ "success" ], "title": "DeleteVariableOutput", "type": "object" } ``` ## Limits & Quotas - **Transport**: every action is a single POST to the GraphQL endpoint `https://backboard.railway.com/graphql/v2`. - **Rate limits**: Railway applies request-rate and complexity limits to the public API; see the official API docs for current values. - **Error model**: the GraphQL endpoint answers HTTP 200 even for logical errors, returning them in a top-level `errors` array. These, along with non-2xx responses and timeouts, are caught and returned as `success=False` + `error` rather than raising. Plan for retries on the agent side based on the error string. ## Related integrations ## Links - [Railway](https://railway.com) --- # Reflect Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/reflect Note-taking and knowledge management via the Reflect API {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/reflect/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Reflect logo ## Overview Add **Reflect** to any ModuleX agent or workflow. Note-taking and knowledge management via the Reflect API (`reflect.app/api`). **Categories**: Productivity & Collaboration · Note Taking · Knowledge Management · **Auth**: OAuth2 · **Actions**: 5 ## Authentication ### OAuth2 Authentication Connect using Reflect OAuth (recommended) #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Client ID | Reflect OAuth App Client ID | Yes | `-` | | Client Secret | Reflect OAuth App Client Secret | Yes | `-` | #### OAuth Configuration - **Authorization URL**: `https://reflect.app/oauth` - **Token URL**: `https://reflect.app/api/oauth/token` - **Scopes**: `read:graph`, `write:graph` ## Available Actions ### Parameters The graph identifier Text to append to the daily note Name of the list to append to Date of the daily note in ISO 8601 format. Defaults to today. ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" } }, "required": [ "success" ], "title": "AppendDailyNoteOutput", "type": "object" } ``` ### Parameters The graph identifier The URL of the link to create The link title The link description ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" } }, "required": [ "success" ], "title": "CreateLinkOutput", "type": "object" } ``` ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "uid": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Uid" }, "graph_ids": { "items": { "type": "string" }, "title": "Graph Ids", "type": "array" } }, "required": [ "success" ], "title": "GetUserOutput", "type": "object" } ``` ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "graph_ids": { "items": { "type": "string" }, "title": "Graph Ids", "type": "array" } }, "required": [ "success" ], "title": "ListGraphIdOptionsOutput", "type": "object" } ``` ### Parameters The graph identifier ### Response ```json { "$defs": { "LinkItem": { "additionalProperties": false, "description": "A link object returned by the Reflect API.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Url" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "updated_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Updated At" } }, "title": "LinkItem", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "links": { "items": { "$ref": "#/$defs/LinkItem" }, "title": "Links", "type": "array" } }, "required": [ "success" ], "title": "ListLinksOutput", "type": "object" } ``` ## Limits & Quotas - No documented rate limits from the Reflect API. - Error model: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. ## Related integrations ## Links - [Reflect](https://reflect.app) --- # Resend Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/resend Send transactional and marketing emails, retrieve email status, manage contacts, and view domains with Resend. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/resend/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} Resend logo ## Overview Add **Resend** to any ModuleX agent or workflow. Send transactional and marketing emails, retrieve email status, manage contacts, and view domains via the Resend REST API (`api.resend.com`). **Categories**: Communication · Email · Email Marketing · **Auth**: API Key · **Actions**: 8 ## Authentication ### API Key Authentication Authenticate using your Resend API key Go to https://resend.com and sign up or log in Navigate to the 'API Keys' section in the dashboard Create a new API key (it starts with 're_') Paste the API key below #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | Resend API Key | Your Resend API key from resend.com/api-keys | Yes | `re_xxxxxxxxxxxxxxxxxxxxxxxxx` | ## Available Actions ### Parameters Email address to send from (e.g. "sender@example.com" or "Sender Name <sender@example.com>") Recipient email address (e.g. "recipient@example.com" or "Recipient Name <recipient@example.com>") Email subject line Email body content (plain text or HTML based on content_type) Content type for the body: "text" for plain text or "html" (Default: `text`) Carbon copy recipient email address Blind carbon copy recipient email address Reply-to email address Schedule email to be sent later in ISO 8601 format Comma-separated key:value pairs for email tags (e.g. "category:welcome,type:onboarding") ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "to": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "To" }, "subject": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Subject" }, "body": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Body" } }, "required": [ "success" ], "title": "SendOutput", "type": "object" } ``` ### Parameters The ID of the email to retrieve ### Response ```json { "$defs": { "EmailTag": { "additionalProperties": false, "description": "A single tag name/value pair attached to an email.", "properties": { "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "value": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Value" } }, "title": "EmailTag", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "sender": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Sender" }, "to": { "items": { "type": "string" }, "title": "To", "type": "array" }, "subject": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Subject" }, "html": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Html" }, "text": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Text" }, "cc": { "items": { "type": "string" }, "title": "Cc", "type": "array" }, "bcc": { "items": { "type": "string" }, "title": "Bcc", "type": "array" }, "reply_to": { "items": { "type": "string" }, "title": "Reply To", "type": "array" }, "last_event": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Event" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "scheduled_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Scheduled At" }, "tags": { "items": { "$ref": "#/$defs/EmailTag" }, "title": "Tags", "type": "array" } }, "required": [ "success" ], "title": "GetEmailOutput", "type": "object" } ``` ### Parameters Email address of the contact First name of the contact Last name of the contact Whether the contact is unsubscribed from all broadcasts ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" } }, "required": [ "success" ], "title": "CreateContactOutput", "type": "object" } ``` ### Response ```json { "$defs": { "ContactItem": { "additionalProperties": false, "description": "A single contact row in ``list_contacts``.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email" }, "first_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "First Name" }, "last_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Name" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "unsubscribed": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Unsubscribed" } }, "title": "ContactItem", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "contacts": { "items": { "$ref": "#/$defs/ContactItem" }, "title": "Contacts", "type": "array" }, "has_more": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Has More" } }, "required": [ "success" ], "title": "ListContactsOutput", "type": "object" } ``` ### Parameters The contact ID or email address to retrieve ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email" }, "first_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "First Name" }, "last_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Name" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" }, "unsubscribed": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Unsubscribed" } }, "required": [ "success" ], "title": "GetContactOutput", "type": "object" } ``` ### Parameters The contact ID or email address to update Updated first name Updated last name Whether the contact should be unsubscribed from all broadcasts ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" } }, "required": [ "success" ], "title": "UpdateContactOutput", "type": "object" } ``` ### Parameters The contact ID or email address to delete ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "deleted": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Deleted" } }, "required": [ "success" ], "title": "DeleteContactOutput", "type": "object" } ``` ### Response ```json { "$defs": { "DomainItem": { "additionalProperties": false, "description": "A single domain row in ``list_domains``.", "properties": { "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "region": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Region" }, "created_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created At" } }, "title": "DomainItem", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "domains": { "items": { "$ref": "#/$defs/DomainItem" }, "title": "Domains", "type": "array" }, "has_more": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Has More" } }, "required": [ "success" ], "title": "ListDomainsOutput", "type": "object" } ``` ## Limits & Quotas - **Rate limit**: 2 requests/second by default (Resend account level). - **Scheduling**: emails can be scheduled up to 30 days ahead via `scheduled_at` (ISO 8601 timestamp). - **Error model**: non-2xx responses and timeouts are caught and returned as `success=False` + `error` rather than raising. Plan for retries on the agent side based on the error string. ## Related integrations ## Links - [Resend](https://resend.com) --- # RevenueCat Integration for AI Agents & Workflows URL: https://docs.modulex.dev/integrations/tools/revenuecat Manage in-app subscriptions and entitlements. Retrieve customer subscription status, grant or revoke promotional entitlements, record purchases, update subscriber attributes, and manage Google Play subscription billing. {/* GENERATED FILE — do not hand-edit. Source of truth: modulex-integrations/src/modulex_integrations/tools/revenuecat/ Regenerate with: node scripts/generate-integrations.js Deferred (future SEO skills — PLAN §8 Extras): keywords, use cases, FAQ, JSON-LD, branded og:image. Left as stubs on purpose; do not hand-add. */} {/* Self-hosted logo — build-time vendored from modulex.json / Iconify (see scripts/lib/logo.js). */} RevenueCat logo ## Overview Add **RevenueCat** to any ModuleX agent or workflow. Manage in-app subscriptions and entitlements against the RevenueCat REST API v1 (`api.revenuecat.com/v1`). Look up subscribers, grant or revoke promotional entitlements, record purchases, update subscriber attributes, and manage Google Play subscription billing. **Categories**: Finance & Payments · Payments · Subscriptions · **Auth**: API Key · **Actions**: 10 ## Authentication ### API Key Authentication Authenticate using your RevenueCat REST API v1 key Go to https://app.revenuecat.com and log in Open Project Settings → API Keys Create or copy a secret API key (sk_...) for write operations Paste the API key below #### Required Credentials | Field | Description | Required | Format | |-------|-------------|----------|--------| | RevenueCat API Key | Your RevenueCat REST API v1 key (secret key recommended) | Yes | `sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | ## Available Actions ### Parameters The app user ID of the subscriber ### Response ```json { "$defs": { "Subscriber": { "additionalProperties": false, "description": "A RevenueCat subscriber object.\n\n``subscriptions`` and ``entitlements`` are maps keyed by product /\nentitlement identifier; their values are left as opaque dicts so the\nupstream shape is preserved verbatim.", "properties": { "first_seen": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "First Seen" }, "last_seen": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Seen" }, "original_app_user_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Original App User Id" }, "original_application_version": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Original Application Version" }, "original_purchase_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Original Purchase Date" }, "management_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Management Url" }, "subscriptions": { "additionalProperties": true, "title": "Subscriptions", "type": "object" }, "entitlements": { "additionalProperties": true, "title": "Entitlements", "type": "object" }, "non_subscriptions": { "additionalProperties": true, "title": "Non Subscriptions", "type": "object" }, "other_purchases": { "additionalProperties": true, "title": "Other Purchases", "type": "object" }, "subscriber_attributes": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Subscriber Attributes" } }, "title": "Subscriber", "type": "object" }, "SubscriberMetadata": { "additionalProperties": false, "description": "Summary metadata derived from a subscriber payload.", "properties": { "app_user_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "App User Id" }, "first_seen": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "First Seen" }, "active_entitlements": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Active Entitlements" }, "active_subscriptions": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Active Subscriptions" } }, "title": "SubscriberMetadata", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "subscriber": { "anyOf": [ { "$ref": "#/$defs/Subscriber" }, { "type": "null" } ], "default": null }, "metadata": { "anyOf": [ { "$ref": "#/$defs/SubscriberMetadata" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "GetCustomerOutput", "type": "object" } ``` ### Parameters The app user ID of the subscriber to delete ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "deleted": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Deleted" }, "app_user_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "App User Id" } }, "required": [ "success" ], "title": "DeleteCustomerOutput", "type": "object" } ``` ### Parameters The app user ID of the subscriber Store receipt or purchase token: iOS base64 receipt / JWSTransaction, Android purchase token, Amazon receipt, Stripe subscription or Checkout Session ID, Roku transaction ID, or Paddle subscription/transaction ID Platform of the purchase, sent as the X-Platform header. One of: ios, android, amazon, macos, uikitformac, stripe, roku, paddle Product identifier or SKU. Required for Google. Price of the product. Required if you provide a currency. ISO 4217 currency code. Required if you provide a price. Deprecated. Triggers configured restore behavior for shared fetch tokens. Identifier of the offering presented to the customer at purchase time. Payment mode for the introductory period: pay_as_you_go, pay_up_front, or free_trial. Introductory price paid (if any). JSON object of subscriber attributes to set alongside the purchase. Each key maps to {"value": string, "updated_at_ms": number}. UNIX epoch ms used to resolve attribute conflicts at the request level. ### Response ```json { "$defs": { "Subscriber": { "additionalProperties": false, "description": "A RevenueCat subscriber object.\n\n``subscriptions`` and ``entitlements`` are maps keyed by product /\nentitlement identifier; their values are left as opaque dicts so the\nupstream shape is preserved verbatim.", "properties": { "first_seen": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "First Seen" }, "last_seen": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Seen" }, "original_app_user_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Original App User Id" }, "original_application_version": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Original Application Version" }, "original_purchase_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Original Purchase Date" }, "management_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Management Url" }, "subscriptions": { "additionalProperties": true, "title": "Subscriptions", "type": "object" }, "entitlements": { "additionalProperties": true, "title": "Entitlements", "type": "object" }, "non_subscriptions": { "additionalProperties": true, "title": "Non Subscriptions", "type": "object" }, "other_purchases": { "additionalProperties": true, "title": "Other Purchases", "type": "object" }, "subscriber_attributes": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Subscriber Attributes" } }, "title": "Subscriber", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "customer": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Customer" }, "subscriber": { "anyOf": [ { "$ref": "#/$defs/Subscriber" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "CreatePurchaseOutput", "type": "object" } ``` ### Parameters The app user ID of the subscriber The entitlement identifier to grant Deprecated. Provide either duration or end_time_ms (end_time_ms preferred). One of: daily, three_day, weekly, two_week, monthly, two_month, three_month, six_month, yearly, lifetime. Absolute end time in ms since Unix epoch. Use instead of duration. Deprecated. Optional start time in ms since Unix epoch, used with duration. ### Response ```json { "$defs": { "Subscriber": { "additionalProperties": false, "description": "A RevenueCat subscriber object.\n\n``subscriptions`` and ``entitlements`` are maps keyed by product /\nentitlement identifier; their values are left as opaque dicts so the\nupstream shape is preserved verbatim.", "properties": { "first_seen": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "First Seen" }, "last_seen": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Seen" }, "original_app_user_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Original App User Id" }, "original_application_version": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Original Application Version" }, "original_purchase_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Original Purchase Date" }, "management_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Management Url" }, "subscriptions": { "additionalProperties": true, "title": "Subscriptions", "type": "object" }, "entitlements": { "additionalProperties": true, "title": "Entitlements", "type": "object" }, "non_subscriptions": { "additionalProperties": true, "title": "Non Subscriptions", "type": "object" }, "other_purchases": { "additionalProperties": true, "title": "Other Purchases", "type": "object" }, "subscriber_attributes": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Subscriber Attributes" } }, "title": "Subscriber", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "subscriber": { "anyOf": [ { "$ref": "#/$defs/Subscriber" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "GrantEntitlementOutput", "type": "object" } ``` ### Parameters The app user ID of the subscriber The entitlement identifier to revoke ### Response ```json { "$defs": { "Subscriber": { "additionalProperties": false, "description": "A RevenueCat subscriber object.\n\n``subscriptions`` and ``entitlements`` are maps keyed by product /\nentitlement identifier; their values are left as opaque dicts so the\nupstream shape is preserved verbatim.", "properties": { "first_seen": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "First Seen" }, "last_seen": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Seen" }, "original_app_user_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Original App User Id" }, "original_application_version": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Original Application Version" }, "original_purchase_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Original Purchase Date" }, "management_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Management Url" }, "subscriptions": { "additionalProperties": true, "title": "Subscriptions", "type": "object" }, "entitlements": { "additionalProperties": true, "title": "Entitlements", "type": "object" }, "non_subscriptions": { "additionalProperties": true, "title": "Non Subscriptions", "type": "object" }, "other_purchases": { "additionalProperties": true, "title": "Other Purchases", "type": "object" }, "subscriber_attributes": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Subscriber Attributes" } }, "title": "Subscriber", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "subscriber": { "anyOf": [ { "$ref": "#/$defs/Subscriber" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "RevokeEntitlementOutput", "type": "object" } ``` ### Parameters An app user ID to retrieve offerings for X-Platform header value. One of: ios, android, amazon, stripe, roku, paddle. Required for legacy public API keys; ignored with app-specific keys. ### Response ```json { "$defs": { "Offering": { "additionalProperties": false, "description": "A single offering configured for the project.", "properties": { "identifier": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Identifier" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "packages": { "items": { "$ref": "#/$defs/OfferingPackage" }, "title": "Packages", "type": "array" } }, "title": "Offering", "type": "object" }, "OfferingPackage": { "additionalProperties": false, "description": "A single package inside an offering.", "properties": { "identifier": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Identifier" }, "platform_product_identifier": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Platform Product Identifier" } }, "title": "OfferingPackage", "type": "object" }, "OfferingsMetadata": { "additionalProperties": false, "description": "Summary metadata for a list-offerings response.", "properties": { "count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Count" }, "current_offering_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Current Offering Id" } }, "title": "OfferingsMetadata", "type": "object" } }, "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "current_offering_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Current Offering Id" }, "offerings": { "items": { "$ref": "#/$defs/Offering" }, "title": "Offerings", "type": "array" }, "metadata": { "anyOf": [ { "$ref": "#/$defs/OfferingsMetadata" }, { "type": "null" } ], "default": null } }, "required": [ "success" ], "title": "ListOfferingsOutput", "type": "object" } ``` ### Parameters The app user ID of the subscriber JSON object of attributes to set. Each key maps to an object with "value" (string; null or empty deletes the attribute) and "updated_at_ms" (Unix epoch ms used for conflict resolution — required). ### Response ```json { "additionalProperties": false, "properties": { "success": { "title": "Success", "type": "boolean" }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Error" }, "updated": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Updated" }, "app_user_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "App User Id" } }, "required": [ "success" ], "title": "UpdateSubscriberAttributesOutput", "type": "object" } ``` ### Parameters The app user ID of the subscriber The Google Play product identifier of