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"):
- User calls
POST /api/v1/integrations/{id}/auth/start. - Core asks the integration backend for the OAuth redirect URL.
- User authenticates with the external provider.
- Provider redirects to the Core's callback URL.
- Core asks the integration backend to exchange the code for tokens.
- 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_integrationstool
When an action is called, the Core sends an __integration RPC to your backend with:
action: the action IDinput: the caller's input (validated againstinputSchema)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:
- Input validation before execution.
- 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 headersbody: 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:
- App binding — if the request includes an
x-app-idheader and a binding exists with aconnectionId, use that connection. - Delegation — if a delegation connection exists for this user, follow it.
- 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
- Build an Application for the foundation every integration inherits.
- Secret Vault for how credentials are stored and injected.
- REST API Reference for all endpoints.