Connections
Connections is how a workspace hooks up its first-party data — CRM, calls, comms, docs, support — and its tracked social accounts. Browse the catalog and manage connections in the app, over the API, or read them from an MCP agent.
What you can connect
Every connector is first-party— each connectable one has a real Amdahl puller behind it, so a “Connect” button always leads somewhere. The connector catalog is the single source of truth; every surface (the app, the API, the MCP resources) is generated from it, and the catalog endpoint below returns the live list rather than a number that drifts. A handful of entries are marked coming soon(catalogued but not yet connectable) — today that’s Circleback; the rest are live.
- CRM — HubSpot (OAuth), Salesforce (OAuth), Pipedrive (API key).
- Calls — Gong (OAuth), Fathom (API key), Granola (API key), Fireflies (API key), Grain (API key), Aircall (API key). Circleback (API key) is coming soon.
- Comms — Gmail (OAuth, personal), Outlook (OAuth, personal), Slack (OAuth).
- Docs — Notion (OAuth).
- Support — Pylon (API key).
- Social — X / Twitter (handle), LinkedIn (handle).
Each connector declares an auth method that decides how you connect it: oauth(approve in the provider’s browser flow), api_key (paste a key), or handle (give a public account handle, for X and LinkedIn). It also declares its ownership: most connectors are workspace-owned (one org-wide connection), but Gmail and Outlook are personal — each member connects their own mailbox, and the connection gets an owner (a workspace member).
In the app
Open Connectionsfrom the sidebar. The top of the page lists what’s already connected — each instance is a row with a live status pill, a last-synced time, and a kebab to disconnect. Below it is the full catalog grouped by category; click Connecton any card and Amdahl runs the right flow for that connector’s auth method (a provider redirect, a key dialog, or a handle dialog).
For a personal connector (Gmail / Outlook) the row shows an owner picker so you can attribute the mailbox to a teammate. Sync cadenceis not set here — it’s a platform-admin, per-connector-type global setting (Settings → admin → Connector Cadence).
Open a connection
Click any connected row to open its detail page. Every connector gets one: the connection’s status, last sync, who added it, and any last error, plus the actions you’d expect — Rename, Sync now, Reconnect (when a connection has errored), and Disconnect. Personal connectors also show the owner picker here.
For a social account (X / LinkedIn) the detail page is the full social view — follower count, engagement summary, a follower / engagement trend chart, a post scatter, and a scrollable feed of the account’s tracked posts with per-post metrics.
Apps & Access gating
Connections is a console segment, so it’s gated the same two ways every segment is:
- Entitlement — a workspace turns the whole segment on or off in Settings → Apps.
- Surface access — in Settings → Access a role or member gets Hidden / Read / Write on the Connections surface. Read = view connected sources; Write = connect / disconnect / set owner.
As everywhere in Amdahl, the grandfather default is enabled + full access until a tenant sets a policy.
Over the API
Connections is a REST surface under /api/platform/v1/connections. Authenticate with the X-API-Key header (see Using the API). Reads need connections:read; connecting and disconnecting need connections:write / connections:delete. Every route is additionally gated by the Connections surface (GET = read, POST / DELETE / PATCH = write).
List the catalog — what CAN be connected
GET /connections/catalog returns the full connector catalog. Optional ?kind= and ?category= filters narrow it. Each entry carries a puller_status (live or planned) and a connectable flag, so a coming soon connector (e.g. Circleback) is in the list but cannot yet be connected.
curl https://app.amdahl.co/api/platform/v1/connections/catalog \
-H "X-API-Key: amdahl_sk_live_your_key_here"{
"connectors": [
{
"type": "hubspot",
"name": "HubSpot",
"kind": "first_party",
"category": "crm",
"auth_method": "oauth",
"ownership": "workspace",
"instancing": "single",
"puller_status": "live"
},
{
"type": "x",
"name": "X / Twitter",
"kind": "first_party",
"category": "social",
"auth_method": "handle",
"ownership": "workspace",
"instancing": "single",
"puller_status": "live"
}
]
}List connections — what IS connected
GET /connections returns the established instances, newest-first. GET /connections/:id fetches one; GET /connections/:id/status is a lean status-only projection (id, status, health, is_syncing, last_synced_at, last_run) you can poll for a connecting / syncing badge.
A connection id that belongs to another workspace, or one that does not exist, returns an identical 404 — the response never reveals whether the connection exists, so the id itself leaks nothing across tenants.
curl https://app.amdahl.co/api/platform/v1/connections \
-H "X-API-Key: amdahl_sk_live_your_key_here"{
"connections": [
{
"id": "c3f9a1e2-7b4d-4a10-9c2f-8e6b1d04a7f5",
"connector_type": "hubspot",
"name": "HubSpot",
"status": "connected",
"health": "healthy",
"is_syncing": false,
"last_synced_at": "2026-06-12T18:04:11Z",
"last_run": {
"id": "run_8f21",
"status": "completed",
"started_at": "2026-06-12T18:03:40Z",
"finished_at": "2026-06-12T18:04:11Z",
"streams_total": 8,
"streams_failed": 0,
"records_written": 1247,
"error_reason": null
},
"owner_user_id": null
},
{
"id": "a1b2c3d4-e5f6-4711-8899-0a1b2c3d4e5f",
"connector_type": "x",
"name": "X / Twitter",
"status": "connected",
"health": "stale",
"is_syncing": false,
"handle": "acme",
"display_name": "Acme",
"last_synced_at": "2026-06-09T17:10:00Z",
"last_run": {
"id": "run_77ab",
"status": "partial",
"started_at": "2026-06-12T17:00:00Z",
"finished_at": "2026-06-12T17:00:30Z",
"streams_total": 2,
"streams_failed": 1,
"records_written": 18,
"error_reason": "rate_limit"
}
}
]
}Connection health & statuses
Every connection carries two related signals. status is the raw lifecycle state (connected / syncing / pending / paused / error / disconnected). health is a richer, derived signal that also looks at the most recent sync run — a connection can be status: "connected" yet health: "stale" or "degraded", which the raw status alone cannot tell you. Prefer healthfor any “is this connection OK?” decision.
healthy— connected and the last sync succeeded recently.syncing— a sync is in flight right now (seeis_syncing).stale— connected, but no successful sync within the freshness window; the data is aging.degraded— the last run only partially succeeded (some streams failed). Sync again to backfill.needs_reauth— the provider rejected the credentials; reconnect required.rate_limited— the provider throttled the last sync; Amdahl retries automatically (no action needed).error— the last run failed for a non-auth, non-rate-limit reason.disconnected— removed / soft-deleted.
Precedence, highest wins, is disconnected > needs_reauth > syncing > rate_limited > error > degraded > stale > healthy.
is_syncing is a server-authoritative boolean — it is real, flipping true for the duration of an actual in-flight sync — so a client can poll GET /connections/:id/status (the lean projection) while it is true and stop the moment it clears; a reload mid-sync resumes correctly because the flag lives on the server.
last_run is the audit row behind health: the most recent sync run’s status (running / completed / failed / partial / skipped_locked), started_at / finished_at, streams_total / streams_failed, records_written, and an error_reason (auth / rate_limit / transient / config / unknown, or null on success). It is null on a connection that has never synced.
Connect a source
POST /connections takes a connector_typeand routes on that connector’s auth method. The response mode tells you what happened: connected (done), oauth_redirect (send the user to authorize_url), or already_connected.
Handle (X / LinkedIn) — give the public account handle:
curl https://app.amdahl.co/api/platform/v1/connections \
-H "X-API-Key: amdahl_sk_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"connector_type": "x",
"account_handle": "acme",
"external_account_id": "1538291",
"display_name": "Acme"
}'{
"mode": "connected",
"connection": {
"id": "a1b2c3d4-e5f6-4711-8899-0a1b2c3d4e5f",
"connector_type": "x",
"status": "connected",
"handle": "acme",
"is_syncing": true
}
}API key (Pipedrive, Fathom, Pylon, …) — paste the key:
curl https://app.amdahl.co/api/platform/v1/connections \
-H "X-API-Key: amdahl_sk_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"connector_type": "pipedrive",
"api_key": "pd_live_xxxxxxxxxxxxxxxx",
"name": "Pipedrive"
}'{
"mode": "connected",
"connection": {
"id": "b7c8d9e0-1234-4567-89ab-cdef01234567",
"connector_type": "pipedrive",
"status": "connected",
"is_syncing": true
}
}OAuth (HubSpot, Salesforce, Gong, Slack, Notion, Gmail, Outlook) — start the flow with just the type, then redirect the user to the returned URL:
curl https://app.amdahl.co/api/platform/v1/connections \
-H "X-API-Key: amdahl_sk_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{ "connector_type": "hubspot" }'{
"mode": "oauth_redirect",
"authorize_url": "https://app.hubspot.com/oauth/authorize?client_id=…&state=…",
"connection": {
"id": "c3f9a1e2-7b4d-4a10-9c2f-8e6b1d04a7f5",
"connector_type": "hubspot",
"status": "pending"
}
}Disconnect
DELETE /connections/:id soft-disconnects — the row is retained so you can re-connect later without losing history.
curl -X DELETE \
https://app.amdahl.co/api/platform/v1/connections/c3f9a1e2-7b4d-4a10-9c2f-8e6b1d04a7f5 \
-H "X-API-Key: amdahl_sk_live_your_key_here"{ "id": "c3f9a1e2-7b4d-4a10-9c2f-8e6b1d04a7f5", "status": "disconnected" }Reconnect a broken connection
POST /connections/:id/reconnect restores a broken connection in placeon the existing data source — it never creates a duplicate row, so the connection id and its sync history survive. Reach for it when a connection’s health is needs_reauth or error. Like connect, it routes on the connector’s auth method and the response mode tells you what happened:
- OAuth — no body; returns
{ "mode": "oauth_redirect", "authorize_url": "…" }. Send the user there to re-do the handshake; the existing row flips topendingand the callback refreshes it. - API key — send
{ "api_key": "…" }(the replacement key); returns{ "mode": "connected", "connection": { … } }. - Handle (X / LinkedIn) — no body; re-triggers the sync and returns
{ "mode": "connected" }.
curl -X POST \
https://app.amdahl.co/api/platform/v1/connections/c3f9a1e2-7b4d-4a10-9c2f-8e6b1d04a7f5/reconnect \
-H "X-API-Key: amdahl_sk_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{ "api_key": "pd_live_xxxxxxxxxxxxxxxx" }'{
"mode": "connected",
"connection": {
"id": "c3f9a1e2-7b4d-4a10-9c2f-8e6b1d04a7f5",
"connector_type": "pipedrive",
"status": "connected",
"health": "syncing",
"is_syncing": true
}
}Reconnect needs connections:write (the same scope as connect). It is REST + app only — like connect / disconnect, it is not on MCP.
Set a personal connection’s owner
PATCH /connections/:id sets the owner of a personal connector (Gmail / Outlook). Pass owner_user_id, or null to clear it. Workspace connectors (like HubSpot) reject this with a 400.
curl -X PATCH \
https://app.amdahl.co/api/platform/v1/connections/d4e5f6a7-8901-4234-5678-90abcdef1234 \
-H "X-API-Key: amdahl_sk_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{ "owner_user_id": "9f8e7d6c-5b4a-4938-2716-0504f3e2d1c0" }'{
"id": "d4e5f6a7-8901-4234-5678-90abcdef1234",
"connector_type": "gmail",
"owner_user_id": "9f8e7d6c-5b4a-4938-2716-0504f3e2d1c0"
}Sync cadence (platform admin)
Cadence is nota per-connection setting — it’s a platform-admin, per-connector-type global override, an operational cost / freshness call Amdahl makes once per connector and applies to every tenant. GET /api/admin/connector-cadence is readable by any platform member; PUT is superadmin / engineering only.
curl -X PUT \
https://app.amdahl.co/api/admin/connector-cadence/hubspot \
-H "X-API-Key: amdahl_sk_live_platform_key" \
-H "Content-Type: application/json" \
-d '{ "sync_interval": "1h" }'sync_interval is one of the bounded presets 1m / 10m / 30m / 1h / 6h / 1d, or null to clear the override and fall back to the connector default.
Not sure of an exact route or input shape? Call /api/platform/v1/operations — it returns the catalog of every operation your key can call (the connections.* reads and writes included) with its JSON schema.
From an MCP agent
Over MCP the Connections reads are resources on the connection:// scheme, authorized by connections:read(it’s in the read-only MCP bundle). An agent reads a resource URI the same way it reads any other:
connection://catalog— what can be connected.connection://list— what is connected.connection://<id>— one connection.connection://<id>/status— lean status, for polling a connecting / syncing badge.
Writes are intentionally not on MCP. Connecting, disconnecting, and setting an owner are console + REST only — wiring up a data source is workspace configuration, and putting it behind an agent tool would be a confused-deputy risk (the same fence we draw around workspace and team lifecycle). Sync cadence is platform-admin and is likewise not on MCP. So an agent can seewhat’s connected, but a human connects it.
One catalog, reads everywhere — writes stay in the console
Reads are at parity across every surface. The connector catalog, the connected instances, and per-connection status are reachable from MCP (connection:// resources), the REST API (GET /connections*), and the app (the Connections page) — all gated by the same connections:read scope and the Connections surface.
Writes are REST + app only. Connect, disconnect, and set-owner run from the REST API and the Connections page, and are deliberately off MCP. Sync cadence is platform-admin only. It is the same access model you see across Amdahl: one catalog, one set of rules, enforced identically on whichever surface you reach the data from.