Documentation Index Fetch the complete documentation index at: https://docs.getcargo.ai/llms.txt
Use this file to discover all available pages before exploring further.
Custom integrations allow you to extend Cargo’s capabilities by connecting to any external service or API. By building a custom integration server, you can create actions, data extractors, and autocomplete endpoints that seamlessly integrate with Cargo’s workflows.
Overview
A custom integration is an HTTP server that implements a specific API contract. Cargo communicates with your integration server to:
Fetch the manifest — Describes your integration’s capabilities (actions, extractors, autocompletes)
Authenticate connections — Validates user credentials when creating a connector
Execute actions — Performs operations in your external service
Fetch data — Pulls data from your service into Cargo data models
Provide autocomplete options — Powers dynamic dropdowns in the Cargo UI
Getting started
A custom integration can be hosted in two ways:
As a Cargo Hosting worker (recommended) — write the integration as an edge fetch(request, env) handler and let Cargo host it. No infra, no ngrok, no separate domain.
As an externally hosted server — a Node/Express (or any language) server you host yourself. Use this when you need a runtime that isn’t supported by workers, or you already have an existing server to wrap.
Option A — Build with a Cargo Worker (recommended)
Step 1: Scaffold the worker
npx @cargo-ai/cli hosting worker init my-integration --template custom-integration
cd my-integration
npm install
This drops a TypeScript worker (typed against @cargo-ai/worker-sdk ) that already implements the Custom Integration HTTP contract:
my-integration/
├── manifest.json # outboundAllowlist (hosts the worker may call)
├── package.json
├── tsconfig.json
├── scripts/
│ └── copy-runtime-files.mjs # copies manifest.json + package.json into dist/
└── src/
├── index.ts # default { fetch(request, env) } — routes the contract
├── getManifest.ts # GET /manifest
├── authenticate.ts # POST /authenticate
├── listUsers.ts # POST /listUsers
├── completeOauth.ts # POST /completeOauth
├── actions/ # POST /actions/<slug>/execute
├── extractors/ # POST /extractors/<slug>/{fetch,count}
├── autocompletes/ # POST /autocompletes/<slug>
└── dynamicSchemas/ # POST /dynamicSchemas/<slug>
Edit src/getManifest.ts (manifest), src/authenticate.ts (credential check), and the per-action / per-extractor handlers under src/. Add any external hosts you call to manifest.json#outboundAllowlist. Run npm run type:check to validate against the typed contract.
# Returns <workerUuid>
cargo-ai hosting worker create --name "My Integration" --slug my-integration
# Compiles src/ → dist/ and copies manifest.json + package.json + package-lock.json into dist/
npm run build
# Returns <deploymentUuid>. Note the `--source ./dist` — that's the compiled bundle.
cargo-ai hosting deployment create --worker-uuid < workerUui d > --source ./dist
cargo-ai hosting deployment promote --uuid < deploymentUui d >
The Cargo Hosting build pipeline runs npm ci + esbuild dist/index.js --bundle --format=esm --platform=neutral --target=es2022 to produce the final edge bundle.
Step 3: Register the worker as a custom integration
cargo-ai connection custom-integration create \
--kind worker \
--worker-uuid < workerUui d >
Cargo will fetch GET /manifest from your worker (cached for 5 minutes) and the integration will appear in your workspace’s connector catalog.
Use cargo-ai hosting worker init x --list-templates to see all available
worker templates.
Option B — Self-hosted external server
Use this path when you can’t run on a worker (long-running compute, large native dependencies, an existing Express/Flask/Go service you want to expose, etc.).
Step 1: Create your integration server
Start by cloning the dummy integration repository:
git clone https://github.com/getcargohq/dummy-integration.git
cd dummy-integration
npm install
Run the development server:
Your integration server will start on a local port (e.g., http://localhost:3000).
Step 2: Expose your local server with ngrok
During development, you can use ngrok to expose your local server to the internet:
This will give you a public URL like https://abc123.ngrok.io that you can use to register your integration with Cargo.
For production, deploy your integration server to a cloud provider (AWS, GCP,
Vercel, Railway, etc.) and use that URL instead.
Step 3: Register the external server in Cargo
cargo-ai connection custom-integration create \
--kind external \
--base-url https://abc123.ngrok.io
Or via raw HTTP:
curl -X POST https://api.getcargo.io/v1/connection/customIntegrations \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"kind": "external",
"baseUrl": "https://abc123.ngrok.io"
}'
Once registered, Cargo will fetch the manifest from your server and the integration will appear in your workspace’s connector catalog.
Your external integration server must be publicly accessible. Cargo’s backend
needs to make HTTP requests to your server’s endpoints.
Cargo API for custom integrations
Manage your custom integrations using these API endpoints. All endpoints require authentication.
Create a custom integration
POST /v1/connection/customIntegrations
Request body — discriminated by kind:
// Externally hosted server
{
"kind" : "external" ,
"baseUrl" : "https://your-integration-server.com"
}
// Cargo Hosting worker-backed
{
"kind" : "worker" ,
"workerUuid" : "11111111-2222-3333-4444-555555555555"
}
Response:
{
"customIntegration" : {
"uuid" : "ci_abc123" ,
"kind" : "external" ,
"baseUrl" : "https://your-integration-server.com" ,
"createdAt" : "2025-01-01T00:00:00Z"
}
}
List custom integrations
GET /v1/connection/customIntegrations/list
Response:
{
"customIntegrations" : [
{
"uuid" : "ci_abc123" ,
"kind" : "external" ,
"baseUrl" : "https://your-integration-server.com" ,
"createdAt" : "2025-01-01T00:00:00Z"
},
{
"uuid" : "ci_def456" ,
"kind" : "worker" ,
"workerUuid" : "11111111-2222-3333-4444-555555555555" ,
"createdAt" : "2025-01-01T00:00:00Z"
}
]
}
Get a custom integration
GET /v1/connection/customIntegrations/:uuid
Response:
{
"connector" : {
"uuid" : "ci_abc123" ,
"kind" : "external" ,
"baseUrl" : "https://your-integration-server.com" ,
"createdAt" : "2025-01-01T00:00:00Z"
}
}
Update a custom integration
PUT /v1/connection/customIntegrations/:uuid
Request body — pass either baseUrl (for external integrations) or workerUuid (for worker integrations); the integration’s kind cannot change.
{
"baseUrl" : "https://new-integration-server.com"
}
Delete a custom integration
DELETE /v1/connection/customIntegrations/:uuid
Integration server API
Your integration server must implement the following HTTP endpoints:
GET /manifest
Returns the integration manifest describing all capabilities.
Response:
{
"name" : "My Integration" ,
"description" : "A custom integration for my service" ,
"icon" : "<svg>...</svg>" ,
"color" : "#6366F1" ,
"url" : "https://myservice.com" ,
"connector" : {
"config" : {
"jsonSchema" : {
"type" : "object" ,
"properties" : {
"apiKey" : {
"title" : "API Key" ,
"type" : "string"
}
},
"required" : [ "apiKey" ]
},
"uiSchema" : {}
},
"rateLimit" : {
"unit" : "minute" ,
"max" : 60
}
},
"actions" : {
"createRecord" : {
"name" : "Create Record" ,
"description" : "Creates a new record in the service" ,
"config" : {
"jsonSchema" : {
"type" : "object" ,
"properties" : {
"name" : {
"title" : "Name" ,
"type" : "string"
}
},
"required" : [ "name" ]
},
"uiSchema" : {}
}
}
},
"extractors" : {
"fetchRecords" : {
"name" : "Fetch Records" ,
"description" : "Fetches records from the service" ,
"config" : {
"jsonSchema" : {},
"uiSchema" : {}
},
"mode" : {
"kind" : "fetch" ,
"isIncremental" : false
},
"preview" : "records"
}
},
"autocompletes" : {
"listOptions" : {
"params" : {
"jsonSchema" : {}
},
"cacheExpirationInSeconds" : 300
}
}
}
POST /authenticate
Validates the connector configuration (credentials).
Request body:
{
"connectorConfig" : {
"apiKey" : "encrypted_value"
}
}
Response (success):
Response (error):
{
"outcome" : "error" ,
"reason" : "unauthenticated" ,
"errorMessage" : "Invalid API key provided"
}
POST /listUsers
Lists users available in the connected service (optional).
Request body:
{
"connectorConfig" : {
"apiKey" : "encrypted_value"
}
}
Response:
[
{
"id" : "user_123" ,
"email" : "john@example.com" ,
"firstName" : "John" ,
"lastName" : "Doe" ,
"profileImage" : "https://example.com/avatar.png"
}
]
POST /actions/[actionSlug]/execute
Executes an action. The actionSlug corresponds to a key in the actions object returned by your /manifest endpoint.
Request body:
{
"connectorConfig" : {
"apiKey" : "encrypted_value"
},
"actionConfig" : {
"name" : "New Record Name"
}
}
Response (completed):
{
"outcome" : "executed" ,
"title" : "Record created successfully" ,
"data" : {
"id" : "rec_123" ,
"name" : "New Record Name" ,
"createdAt" : "2025-01-01T00:00:00Z"
}
}
Response (in progress):
{
"outcome" : "executing"
}
Fetches data for a data model extractor.
Request body:
{
"connectorConfig" : {
"apiKey" : "encrypted_value"
},
"extractorConfig" : {},
"meta" : {}
}
Response:
{
"outcome" : "fetched" ,
"columns" : [
{
"slug" : "id" ,
"type" : "string" ,
"label" : "ID"
},
{
"slug" : "name" ,
"type" : "string" ,
"label" : "Name"
},
{
"slug" : "createdAt" ,
"type" : "date" ,
"label" : "Created At"
}
],
"idColumnSlug" : "id" ,
"titleColumnSlug" : "name" ,
"uniqueColumns" : [],
"data" : {
"kind" : "records" ,
"records" : [
{
"action" : "upsert" ,
"override" : true ,
"record" : {
"id" : "rec_123" ,
"name" : "Record 1" ,
"createdAt" : "2025-01-01T00:00:00Z"
}
}
],
"hasMore" : false
}
}
Returns the count of records for preview purposes.
Response:
POST /autocompletes/[autocompleteSlug]
Provides options for dynamic dropdowns in the UI.
Request body:
{
"connectorConfig" : {
"apiKey" : "encrypted_value"
},
"params" : {}
}
Response:
{
"results" : [
{
"label" : "Option 1" ,
"value" : "option_1" ,
"description" : "Description for option 1"
},
{
"label" : "Option 2" ,
"value" : "option_2"
}
]
}
POST /dynamicSchemas/[schemaSlug]
Returns dynamic JSON schemas based on runtime parameters.
Request body:
{
"connectorConfig" : {
"apiKey" : "encrypted_value"
},
"params" : {}
}
Response:
{
"jsonSchema" : {
"type" : "object" ,
"properties" : {
"dynamicField" : {
"title" : "Dynamic Field" ,
"type" : "string"
}
}
},
"uiSchema" : {}
}
POST /completeOauth
Completes OAuth flow for integrations using OAuth authentication.
Request body:
{
"code" : "oauth_authorization_code" ,
"redirectUri" : "https://app.getcargo.io/oauth/callback"
}
Response:
{
"value" : "encrypted_oauth_token"
}
To power dynamic dropdowns in your action configuration forms, use the IntegrationAutocompleteWidget in your uiSchema. This widget calls your /autocompletes/{slug} endpoint to fetch options.
Basic usage
In your /manifest endpoint response, include an autocompletes entry and reference it in an action’s uiSchema:
{
"autocompletes" : {
"listWorkspaces" : {
"params" : {
"jsonSchema" : {}
},
"cacheExpirationInSeconds" : 300
}
},
"actions" : {
"syncData" : {
"name" : "Sync Data" ,
"description" : "Sync data from a workspace" ,
"config" : {
"jsonSchema" : {
"type" : "object" ,
"properties" : {
"workspaceId" : {
"type" : "string" ,
"title" : "Workspace"
}
},
"required" : [ "workspaceId" ]
},
"uiSchema" : {
"workspaceId" : {
"ui:widget" : "IntegrationAutocompleteWidget" ,
"ui:options" : {
"slug" : "listWorkspaces" ,
"params" : {}
}
}
}
}
}
}
}
Option Type Description slugstring The autocomplete slug (matches entry in /manifest response) paramsobject Static or dynamic parameters to pass to the endpoint allowRefreshboolean Show a refresh button to reload options
Dynamic parameters
You can reference other form values in params using special path expressions:
{
"uiSchema" : {
"accounts" : {
"ui:widget" : "IntegrationAutocompleteWidget" ,
"ui:options" : {
"slug" : "listAccounts" ,
"params" : {
"workspaceId" : "$this.$parent.workspaceId"
}
}
}
}
}
Path expressions:
Expression Description $thisCurrent field value $parentParent object in the form structure $rootRoot of the form data
This allows cascading dropdowns where one field’s options depend on another field’s selected value.
Using dynamic schemas
For fields where the schema depends on runtime values (like user selections), use the DynamicSchemaWidget. This widget fetches the JSON schema from your /dynamicSchemas/{slug} endpoint.
Basic usage
In your /manifest endpoint response, include a dynamicSchemas entry and reference it in an action’s uiSchema:
{
"dynamicSchemas" : {
"getFieldSchema" : {
"params" : {
"jsonSchema" : {
"type" : "object" ,
"properties" : {
"objectType" : {
"type" : "string"
}
}
}
}
}
},
"actions" : {
"createRecord" : {
"name" : "Create Record" ,
"description" : "Create a record with dynamic fields" ,
"config" : {
"jsonSchema" : {
"type" : "object" ,
"properties" : {
"objectType" : {
"type" : "string" ,
"title" : "Object Type"
},
"fields" : {
"type" : "object" ,
"title" : "Fields"
}
}
},
"uiSchema" : {
"objectType" : {
"ui:widget" : "IntegrationAutocompleteWidget" ,
"ui:options" : {
"slug" : "listObjectTypes"
}
},
"fields" : {
"ui:widget" : "DynamicSchemaWidget" ,
"ui:options" : {
"slug" : "getFieldSchema" ,
"params" : {
"objectType" : "$this.$parent.objectType"
}
}
}
}
}
}
}
}
Option Type Description slugstring The dynamic schema slug (matches entry in /manifest response) paramsobject Parameters to pass to the endpoint (supports path expressions)
Endpoint response
Your /dynamicSchemas/{slug} endpoint should return both the JSON schema and UI schema for the field:
{
"jsonSchema" : {
"type" : "object" ,
"properties" : {
"name" : {
"type" : "string" ,
"title" : "Name"
},
"email" : {
"type" : "string" ,
"title" : "Email"
}
}
},
"uiSchema" : {}
}
Dynamic schemas are useful when your integration has different fields per
object type (e.g., CRM objects like Contacts vs Companies) or when fields are
user-configurable.
Best practices
Validate inputs Always validate connector and action configurations before processing
requests.
Handle errors gracefully Return meaningful error messages to help users troubleshoot issues.
Implement rate limiting Define rate limits in your /manifest response to prevent overwhelming your
service.
Use caching Enable caching for autocomplete endpoints to improve performance.
Example integration
For a complete working example, see the Cargo Dummy Integration repository on GitHub.
The repository includes:
Full project structure with TypeScript
Example /manifest endpoint with actions and extractors
Authentication endpoint implementation
Action execution handlers
Development and build scripts