DocsBuildBuild an Integration

Build an Integration

An integration bridges RootCX to an external API: Slack, Gmail, Stripe, or any service. It exposes actions that your applications and AI agents can call, handles OAuth flows for per-user authentication, and receives webhooks from external services. Credentials are stored in the encrypted Secret Vault and injected at runtime.

Under the hood, an integration is a standard RootCX application with type: "integration" in the manifest. It inherits RBAC and Audit Logs. Once deployed, any app or AI agent in your fleet can use its actions without a binding step.


Structure of an integration

slack/
├── manifest.json         # Actions, credentials, webhooks
├── backend/
│   └── index.ts          # Action handlers + OAuth logic
└── package.json          # Dependencies (SDK for external API)

Your AI coding agent generates all of this. You review and deploy with rootcx deploy.


The manifest

The manifest declares the integration type, its credentials, actions, and webhooks:

{
  "appId": "slack",
  "name": "Slack",
  "version": "1.0.0",
  "type": "integration",
  "description": "Send messages and receive events from Slack",
  "instructions": "Use send_message to post to channels. Channel format: #channel-name or channel ID.",
  "configSchema": {
    "properties": {
      "botToken": {
        "type": "string",
        "platformSecret": "SLACK_BOT_TOKEN"
      }
    },
    "required": ["botToken"]
  },
  "userAuth": {
    "type": "redirect"
  },
  "actions": [
    {
      "id": "send_message",
      "name": "Send Message",
      "description": "Post a message to a Slack channel",
      "inputSchema": {
        "type": "object",
        "properties": {
          "channel": { "type": "string", "description": "Channel name or ID" },
          "text": { "type": "string", "description": "Message content" }
        },
        "required": ["channel", "text"]
      }
    }
  ],
  "webhooks": ["message"]
}

Key manifest fields for integrations

Field Description
type Must be "integration".
configSchema Declares platform-level credentials. Fields with platformSecret are stored in the encrypted vault.
userAuth How individual users authenticate: "redirect" (OAuth) or "credentials" (manual entry with a schema).
actions Operations that apps and agents can call. Each gets an RBAC permission.
webhooks Inbound webhook names. Each gets a unique token URL.
instructions Free-form text surfaced to AI agents via list_integrations. Explain usage patterns and parameter conventions.
dataContract Optional. Declare entities if the integration needs to store synced data.
crons Optional. Declare schedules for periodic sync tasks.

Credentials

Integrations support two credential models:

Platform secrets (shared)

A single set of credentials shared across the platform (e.g., a bot token, API key, or OAuth app credentials). Declared via platformSecret in the configSchema:

"configSchema": {
  "properties": {
    "clientId": { "type": "string", "platformSecret": "GMAIL_CLIENT_ID" },
    "clientSecret": { "type": "string", "platformSecret": "GMAIL_CLIENT_SECRET" }
  },
  "required": ["clientId", "clientSecret"]
}

An admin saves these via PUT /api/v1/integrations/{id}/config. They are stored in the AES-256-GCM encrypted vault and injected into the integration backend as config on every action call.

Per-user credentials (OAuth or manual)

Each user authenticates individually with the external service.

OAuth flow (userAuth.type: "redirect"):

  1. User calls POST /api/v1/integrations/{id}/auth/start.
  2. Core asks the integration backend for the OAuth redirect URL.
  3. User authenticates with the external provider.
  4. Provider redirects to the Core's callback URL.
  5. Core asks the integration backend to exchange the code for tokens.
  6. Tokens are encrypted and stored per-user.

Manual credentials (userAuth.type: "credentials"):

"userAuth": {
  "type": "credentials",
  "schema": {
    "properties": {
      "host": { "type": "string", "label": "IMAP Host" },
      "password": { "type": "string", "secret": true }
    }
  }
}

User submits credentials via POST /api/v1/integrations/{id}/auth/credentials.

Credential delegation for AI agents

Agents do not have their own OAuth tokens. Instead, an admin delegates a user's connection to the agent:

POST /api/v1/integrations/{id}/auth/delegate
{ "agent_app_id": "support_bot" }

When the agent calls an integration action, the Core resolves the delegated connection and passes the original user's credentials. The agent acts on behalf of that user.


Actions

Actions are operations exposed to apps and agents. Each action declared in the manifest:

  • Gets an RBAC permission: integration:{integrationId}:{actionId}
  • Becomes callable via POST /api/v1/integrations/{id}/actions/{actionId}
  • Is discoverable by AI agents via the list_integrations tool

When an action is called, the Core sends an __integration RPC to your backend with:

  • action: the action ID
  • input: the caller's input (validated against inputSchema)
  • config: platform secrets (decrypted)
  • userCredentials: the caller's OAuth tokens or credentials (if connected)
  • userId: the effective user ID

Your backend executes the action and returns the result.

inputSchema and outputSchema

Both are optional JSON Schema objects. They serve two purposes:

  1. Input validation before execution.
  2. AI agents use them to understand what parameters to pass and what to expect back.

Webhooks

Declare webhooks to receive HTTP callbacks from external services:

{
  "webhooks": ["push_notification", "message"]
}

Each webhook gets a token. The Core routes incoming POSTs to your backend via an __webhook RPC with:

  • headers: all HTTP headers
  • body: parsed JSON (or string)
  • rawBody: base64-encoded raw body (for signature verification)
  • config: platform secrets

Retrieve webhook URLs after deploy: GET /api/v1/apps/{appId}/webhooks.

Full reference: Webhooks.


Backend handler format

Integration backends handle special RPC methods. Your AI coding agent generates these, but here is what they do:

Method When it fires What it returns
__integration An action is called Action result (JSON)
__auth_start User initiates OAuth { "redirectUrl": "https://..." }
__auth_callback OAuth provider redirects back { "credentials": {...}, "email": "...", "account": "..." }
__webhook External service sends a webhook Response to return to the caller
__bind Admin saves platform config Optional { "mergeConfig": {...} } for dynamic config

The __auth_callback handler exchanges the authorization code for tokens and returns them. The Core encrypts and stores them. The integration never handles raw credential storage.


Using an integration

Once deployed, any app or agent can call its actions.

From the SDK

import { useIntegration } from "@rootcx/sdk";

const { connected, connect, call } = useIntegration("slack");

await connect();
await call("send_message", { channel: "#general", text: "Hello!" });

From the API

POST /api/v1/integrations/slack/actions/send_message
{ "channel": "#general", "text": "Hello!" }

From an AI agent

Agents discover integrations via list_integrations and call them via call_integration. No code needed. The agent just needs the tool:call_integration permission.


Connections

A user can have multiple connections to the same integration (e.g., two Gmail inboxes, a personal and work Slack). Each connection has an id and a label (typically the email or account name).

Managing connections

Method Path Description
GET /api/v1/integrations/{id}/connections List the caller's connections
PATCH /api/v1/integrations/{id}/connections/{connId} Update label
DELETE /api/v1/integrations/{id}/connections/{connId} Delete connection and credentials
GET /api/v1/integrations/{id}/connected-users List user IDs with connections (admin only)

App bindings

Bind an integration to an app to pin which connection it uses:

Method Path Description
GET /api/v1/apps/{app_id}/integrations List bindings ([{integrationId, enabled, connectionId, createdAt}])
POST /api/v1/apps/{app_id}/integrations Bind ({ integrationId, connectionId? })
DELETE /api/v1/apps/{app_id}/integrations/{id} Unbind

Pass connectionId on bind to select which account the app uses. If omitted, the user's first connection is used.

Credential resolution

When an action executes, the Core resolves credentials in this order:

  1. App binding — if the request includes an x-app-id header and a binding exists with a connectionId, use that connection.
  2. Delegation — if a delegation connection exists for this user, follow it.
  3. First direct connection — fall back to the user's oldest direct connection.

Deduplication

Connecting the same account twice (same label) reuses the existing connection and refreshes its credentials.


API endpoints

Method Path Description
GET /api/v1/integrations List installed integrations (with configured status)
GET /api/v1/integrations/catalog List available integrations from catalog
POST /api/v1/integrations/catalog/{id}/deploy Deploy from catalog (admin)
DELETE /api/v1/integrations/catalog/{id} Undeploy integration (admin)
PUT /api/v1/integrations/{id}/config Save platform config (admin)
POST /api/v1/integrations/{id}/actions/{actionId} Execute action
GET /api/v1/integrations/{id}/auth Check auth status ({ connected, connectionCount })
POST /api/v1/integrations/{id}/auth/start Start OAuth flow
POST /api/v1/integrations/{id}/auth/credentials Submit credentials manually
POST /api/v1/integrations/{id}/auth/delegate Delegate credentials to agent (admin)
DELETE /api/v1/integrations/{id}/auth Disconnect (delete credentials)
GET /api/v1/integrations/auth/callback OAuth callback (provider redirects here)
GET /api/v1/integrations/{id}/connections List caller's connections
PATCH /api/v1/integrations/{id}/connections/{connId} Update connection label
DELETE /api/v1/integrations/{id}/connections/{connId} Delete a connection
GET /api/v1/integrations/{id}/connected-users List connected user IDs (admin)
GET /api/v1/apps/{app_id}/integrations List app bindings
POST /api/v1/apps/{app_id}/integrations Bind integration to app
DELETE /api/v1/apps/{app_id}/integrations/{id} Unbind

Deploy

rootcx deploy

On deploy, the Core:

  • Registers the integration in the catalog.
  • Creates RBAC permissions for each action (integration:{id}:{actionId}).
  • Generates webhook token URLs.
  • Starts the backend worker.

Next steps