Docs

Connections

Connector CRUD over the API and MCP - browse the catalog, connect a source, watch its syncs, and fix it when it breaks, with display metadata on every read

Connections are how a workspace hooks up its first-party data — CRM, call recordings, comms, docs, support tickets, and tracked social handles. The whole lifecycle is addressable here: browse what can be connected, connect it, watch it sync, and fix it when it breaks. Every read is self-describing — the catalog tells you each connector's display metadata (name, logo, category) and exactly which fields its connect flow needs, so a client renders the whole surface from the API with no hardcoded connector list.

OperationRESTMCPScope
The connector catalogconnections.list_catalogGET /connections/catalogconnections tool, action catalogconnections:read
Connected instancesconnections.listGET /connectionsconnections tool, action listconnections:read
One connectionconnections.getGET /connections/:idconnections tool, action getconnections:read
Status (for polling)connections.get_statusGET /connections/:id/statusconnections tool, action statusconnections:read
Sync-run historyconnections.list_runsGET /connections/:id/runsconnections tool, action runsconnections:read
Data summaryconnections.get_summaryGET /connections/:id/summaryconnections tool, action summaryconnections:read
Connect a sourceconnections.connectPOST /connectionsconnections tool, action connectconnections:write
Disconnectconnections.disconnectDELETE /connections/:idconnections tool, action disconnectconnections:delete
Set the ownerconnections.updatePATCH /connections/:idconnections tool, action updateconnections:write
Repair in placeconnections.reconnectPOST /connections/:id/reconnectconnections tool, action reconnectconnections:write
Call filtersconnections.set_call_filtersPUT /connections/:id/call-filtersconnections:write
CRM recency windowconnections.set_crm_recencyPUT /connections/:id/crm-recencyconnections:write
Comms filterconnections.set_comms_filterPUT /connections/:id/comms-filterconnections:write
GET/connections/catalogauth required

Everything that CAN be connected - one entry per connector, carrying display metadata, the connect-flow spec, and whether it is connectable right now.

GET/connectionsauth required

Everything that IS connected - newest first, each row carrying live health and its connector's display block.

POST/connectionsauth required

Connect a source. One endpoint covers every flow: an API key connects in one call, a handle resolves and connects, OAuth hands back an authorize URL.

Which keys can call what

The six reads authorize for every key tier, including read-only MCP keys — browsing the catalog and checking a connection's health is safe everywhere. The four lifecycle writes (connect / disconnect / update / reconnect) and the three config writes need connections:write (or connections:delete for disconnect), which is an editor-level grant:

  • OAuth tokens (a human member's delegated session — Claude Desktop and other MCP clients connected via OAuth) carry the write scopes. The token re-checks the member's workspace membership on every request, so revoking the member revokes the capability.
  • API key bundles do not carry the write scopes. A headless agent key can read everything here but cannot connect or disconnect a source; connector lifecycle stays with a person (or a person's delegated session).

The three set_* config writes are REST-only — they are not on the MCP tool. The connect action accepts an inline call_filters object instead, so an MCP connect of a call recorder never lands in the paused-until-configured state.

The catalog is self-describing

Each GET /connections/catalog entry carries everything a client needs to render and drive the connector with no hardcoded knowledge:

FieldNotes
typeThe connector id (hubspot, gong, attio, …). What you pass to connect.
nameDisplay name ("HubSpot").
kindfirst_party.
categorycrm | calls | comms | docs | support | social — the grouping a picker renders.
authMethodoauth | api_key | handle | webhook — which connect flow applies.
logo{ key, url, monogram }. url is an absolute asset URL, or null when no asset ships — then render the two-letter monogram. Never a fabricated path.
connect{ method, fields, keyUrl } — the connect-flow spec. See below.
connectableWhether connect will accept this type right now; unavailableReason says why not (e.g. a coming-soon connector).
single_instanceThe effective connect rule. Read this — never the raw instancing capability — when deciding whether to offer a second connect.
ownershipworkspace (one shared connection) | personal (per-member, e.g. Gmail / Outlook — update can set its owner).
description, dataStreamsWhat the connector is and what it pulls.

The connect spec

connect.fields declares exactly what input the connect flow needs, so a form (or an agent) is generated from the catalog:

  • api_key connectors declare one field: { key: 'api_key', label, secret: true, placeholder, help }. placeholder shows the key's shape when the vendor pins one (sk-apik_... for AskElephant) and is null otherwise — a made-up key shape is worse than no hint. help carries the vendor-specific scope guidance, which is worth surfacing: most connector failures are not a wrong key but a key that cannot see enough, and that presents as an empty workspace rather than as an error.
  • handle connectors (X / LinkedIn) declare { key: 'account_handle', secret: false } — the public handle to track.
  • oauth connectors declare fields: [] — the flow is a redirect, not a form. See the OAuth handoff below.

connect.keyUrl is the vendor page where the customer creates the credential, or null when there is nowhere to send them (an OAuth redirect, a social handle, or a deployment whose token you issue yourself). Render it as a link next to the key field.

A few connectors ask for more than one field, and the spec is how you know. Never assume api_key means exactly one input — read connect.fields and render what it declares:

  • Aircall declares a non-secret api_id ahead of the key, because its API is HTTP Basic and needs both halves of the pair. Sending only api_key is refused with invalid_argument.
  • Computer Agent declares a non-secret base_url ahead of the key — the deployment to drive.

Each field also carries resupplyOnReconnect, which is what a reconnect form should filter on. A credential is replaced wholesale on re-key, so every half of it must be collected again (api_key, and Aircall's api_id), while a field stored on the connection row is not (base_url — a re-key keeps the deployment it was pointed at). Guessing is silent both ways: hide a credential half and the reconnect is refused with no field to fix it; show a stored-config field and you collect a value reconnect drops.

Connecting

POST /connections takes { connector_type, ... } and routes on the connector's auth_method. The response's mode discriminator tells you what happened:

modeWhenWhat you got
connectedapi_key or handle connectA live connection — the credential was stored (or the handle resolved) and the first sync was triggered.
oauth_redirectoauth connectAn authorize_url. Send the human there; the provider callback completes the connection server-side. Poll GET /connections/:id/status until health leaves syncing.
already_connectedA duplicateThe existing connection, unchanged. Workspace-owned data sources are single-instance; a second connect of the same handle dedupes.

Per-flow request fields: api_key connectors take api_key (plus api_id where the catalog declares that field, and optionally call_filters for call recorders — atomic with the connect, so the source is never created paused-but-unconfigured); handle connectors take account_handle; all flows accept an optional display name. A connector whose catalog entry says connectable: false is refused up front with connector_not_connectable.

Attio call recordings

An Attio connection syncs call recordings and transcripts alongside the CRM objects — an Attio-only workspace gets its call transcripts through the one connection, with no separate call recorder. The workspace API key needs three extra scopes for that: meeting:read, record_permission:read, and call_recording:read. A key without them still connects and keeps the CRM objects syncing; the two recording streams fail on their own while everything else flows. To turn recordings on later, re-issue the key with the extra scopes and pass it to POST /connections/:id/reconnect.

Watching

  • GET /connections rows each carry a connector block — { type, name, category, logo } — so a list renders names and logos straight off the read, plus derived health (healthy | syncing | stale | degraded | needs_reauth | rate_limited | error | disconnected) and last_run.
  • GET /connections/:id/status is the lean polling read: status, health, is_syncing, and nothing heavy. Use it for a connecting / syncing badge.
  • GET /connections/:id/runs is the sync-run history, newest first — per run: status, started_at / finished_at, streams_total / streams_failed, records_written, and a bucketed error_reason (auth | rate_limit | transient | config | unknown). Raw provider error text is never surfaced.
  • GET /connections/:id/summary is a glanceable snapshot of the connection's own synced data — per-category stats (deals and contacts for a CRM, calls for a recorder), computed live. A connector that has not synced yet degrades to a partial summary with null stats, never an error; null never renders as 0.

Fixing

POST /connections/:id/reconnect repairs a broken connection in place — it never creates a second row, so the connection's history and its warehouse data stay attached. Per flow: oauth re-mints the authorize URL, api_key takes a fresh api_key (and api_id where the catalog declares it — re-keying stores the whole pair), handle clears the error and re-triggers the sync. A connection that is already healthy is refused with invalid_argument — there is nothing to restore; disconnect first if you mean to replace it.

DELETE /connections/:id disconnects — the row is retained (status disconnected) so a later reconnect restores it in place.

On MCP

The connections coarse tool carries ten actions: the six reads (catalog / list / get / status / runs / summary) and the four lifecycle writes (connect / disconnect / update / reconnect). Reads are also reachable as native MCP resources on the connection:// scheme (connection://catalog, connection://list, connection://<id>, connection://<id>/status, connection://<id>/runs, connection://<id>/summary) for clients that implement resources/*.

Scope gating is per action: reads need connections:read (on every bundle including read-only), connect / update / reconnect need connections:write, and disconnect needs connections:delete — which in practice means an OAuth-connected client can manage connectors and an API-key client can watch them.

Sync cadence is not here

How often a connector type syncs is a platform-level operational setting, not a per-connection knob — there is no cadence field on any of these ops. If a source looks stale, read /runs first: a rate_limit or auth error_reason is the actual story far more often than cadence.