Skip to content

Integrations for coding agents ​

This page is the working reference for coding agents that build or use organisation-built integrations. The hosted OpenCloud Agent loads the same workflow from its organisation-integrations skill. For the product model and the dashboard, read the guide; for manifest fields, read the manifest reference.

Choose the approach ​

The app needs…Do this
A system another app in the organisation already publishesDeclare a provider: custom slot and call its operations.
A reusable connection to a system for several appsBuild a provider app that publishes provides.integration.
Google Workspace, Slack, HubSpot, Asana, or another built-in providerUse the built-in slot instead.

Find what the organisation publishes before building anything:

sh
opencloud integration custom-list <app-id>

MCP clients call list_custom_integrations. Integration names are unique in the organisation.

What stays with the person ​

Agents never ask for, read, print, log, store, or return credential values, OAuth tokens, or webhook URLs. These steps are always human actions in OpenCloud:

  • entering credential values or signing in with OAuth under Integrations → Organisation integrations;
  • sharing a provider app with the organisation (App → Sharing, Use audience);
  • registering the OAuth redirect URI or a connection's webhook URL with the external system;
  • binding a calling_user slot to their own account.

An agent may bind an existing eligible connection to an app-account slot when the user asked it to.

Commands ​

Public CLI 3.11.0 or later provides these commands. The hosted MCP tools work with any client.

TaskCLIMCP
List published contractsopencloud integration custom-list <app-id>list_custom_integrations
Show slots, bindings, eligible connectionsopencloud integration list <app-id>None; the person uses App → Integrations
Bind an app-account slotopencloud integration bind <app-id> <slot> --connection-id <id> --idempotency-key <key>None; the person binds in App → Integrations
Development modes and test connectionopencloud app dev integration list [dir]get_dev_integrations
Use the real system for one dev slotopencloud app dev integration mode <dir> <slot> live|fakeset_dev_integration_mode
Choose a provider's dev test connectionopencloud app dev integration test-connection <dir> <connection-id>set_dev_integration_test_connection
Send a synthetic event to a dev handleropencloud app dev integration inject <dir> <slot> --type <type>inject_dev_integration_event
Inspect production event deliveriesopencloud integration events <app-id>list_integration_event_deliveries

Give every mutating command an explicit idempotency key and reuse it only when retrying the same intended effect.

Build a provider ​

  1. Declare the contract in a schema-3 manifest. Every Function it names is access: system. Pin SDK 2.6.0 when the contract has sync, webhook, or events; operations alone work from 2.2.0.
  2. Give each operation a realistic, deterministic fake output (at most 16 KiB). Consumers develop against it.
  3. Partition stored data by connection with connection_id uuid not null default auth.connection_id() and an RLS policy using (auth.is_system() and connection_id = auth.connection_id()) with check (…) that covers all commands.
  4. Deploy once so the integration appears in the catalog, ask the user to connect a sandbox or read-only account, select it as the development test connection, and invoke each Function with realistic input. Verification does not require invoking contract Functions, so never add development-only branches or hard-coded data to them.
  5. Tell the user what remains: sharing the provider app, connecting accounts, and registering redirect or webhook URLs.

Function contracts ​

FunctionInputReturnsCan emit events
OperationThe consumer's inputJSON (at most 1 MiB, 25 s) or a typed errorYes
sync{} on the cron schedule, per bound connectionAnything; runs up to 15 minutesYes
webhook{ method, headers, body, receivedAt }; body is the exact request text (at most 256 KiB)The response for the senderYes
OAuth exchange{ code, redirectUri, codeVerifier }{ accessToken, refreshToken?, expiresIn? }No
OAuth refresh{ refreshToken }{ accessToken, refreshToken?, expiresIn? }No

Operation, sync, and webhook Functions read credential values with secrets.require(NAME) and an OAuth access token with secrets.require("OAUTH_ACCESS_TOKEN"). On SDK 2.6.0 their context has integration with connectionId, trigger, operation, consumerAppId, and emit({ type, id, data }).

  • Typed 4xx errors reach the consumer with their code. Throw errors.unauthorized only when the external system rejects the connection's credentials or token: the connection becomes Reconnect required and consumers receive INTEGRATION_RECONNECT_REQUIRED.
  • Webhook Functions verify the sender's signature over body before parsing, with Web Crypto (crypto.subtle): Functions run on Deno and have no global Buffer. 2xx results go back to the sender, 401 and 403 pass through, other 4xx become 400, and failures become 503 so the sender retries.
  • Event id is the external system's stable ID: each subscribed binding records it once.
  • OAuth exchange posts the code, redirect URI, verifier, client ID, and the client secret from an app secrets entry to the provider's token endpoint. Ask the owner to set that secret with opencloud secret configure.

Use an integration ​

yaml
integrations:
  crm:
    provider: custom
    integration: acme-crm
    account: app
    cardinality: one
    capabilities: [contacts.read]
    events: { function: on-contact, types: [contact.created] }
ts
const { contacts } = await integrations.use("crm").call("contacts.list", {});
  • Declare only the capabilities you call. With cardinality: many, pass { bindingId } as the third argument.
  • A calling_user slot acts with the signed-in person's own connection. On INTEGRATION_CONNECTION_REQUIRED, show error.details.connectUrl (SDK 2.6.0) as a "Connect" link. Development always returns fake data for these slots.
  • An event handler is a system Function that receives { id, type, occurredAt, integration, provider, bindingId, data } at least once. Store by id, and give its table a policy for all commands such as using (auth.is_system()) with check (auth.is_system()): create returns the new row, so an insert-only policy fails with 403.

In development, every operation returns its fake output until you switch an app-account slot to live. Test event handlers by injecting synthetic events and reusing the idempotency key to prove a replay is harmless. After deploying, bind a connection the user already created, then check opencloud integration events <app-id> and the Function logs.

Errors ​

CodeAction
INTEGRATION_PROVIDER_UNAVAILABLECheck the published name with custom-list; the provider may be undeployed or briefly unavailable.
CUSTOM_INTEGRATION_ACCESS_DENIEDThe provider app is not shared with this person.
INTEGRATION_CONNECTION_REQUIREDBind a connection (app slot) or show connectUrl (calling_user).
INTEGRATION_RECONNECT_REQUIREDThe external system rejected the connection; its manager replaces the credentials or signs in again (calling_user: show connectUrl).
INTEGRATION_CAPABILITY_DENIED, INTEGRATION_GRANT_DENIEDDeclare the capability and bind again.
INTEGRATION_BINDING_REQUIREDPass a bindingId.
INTEGRATION_EVENT_NOT_DECLAREDEmit or inject only declared event types.
INTEGRATION_TEST_CONNECTION_REQUIREDSelect a development test connection before invoking a contract Function.
INTEGRATION_OAUTH_FUNCTION_NOT_INVOCABLEOAuth exchange and refresh never run in development; connect an account instead.
INTEGRATION_NAME_TAKENChoose another integration name.
Event handler 403The system identity cannot write or read back its row.

Self-hosted infrastructure for agent-built applications.