ThunderPhone 2.0 is live.Self-serve, from 2¢/min.Read the announcement

Agents

App Connections

Connect Google Calendar, Sheets, Slack, HubSpot, Salesforce, and Cal.com, and control which tools each agent may use.

A provider connection links a third-party account to your org and exposes a curated set of agent tools (e.g. "create a calendar event", "post to Slack"). You control capability at two levels:

  • Connection-level allowed_operations — which operation groups the whole connection permits (e.g. Slack post: false).
  • Per-agent attachments — which agents may use the connection, and optionally a narrower per-agent operation override.

OAuth-based providers are connected from the dashboard (the browser OAuth consent flow can't be driven by an API key). Once connected, everything on this page is manageable via the API. Cal.com uses an API key instead and can be connected directly.

Providers and their tools

ProviderproviderAuthOperation groups (defaults)Agent tools
Google Calendargoogle_calendarOAuthview_events ✓, create_events ✓, update_events ✗, delete_eventscalendar_list_events, calendar_check_availability, calendar_create_event, calendar_update_event, calendar_cancel_event
Google Sheetsgoogle_sheetsOAuthappend_rowssheets_append_row (requires sheet configuration)
SlackslackOAuthview ✓, post ✗, searchslack_list_channels, slack_post_message, slack_search_messages
HubSpothubspotOAuthview ✓, writehubspot_search_contacts, hubspot_get_contact, hubspot_upsert_contact, hubspot_add_note
SalesforcesalesforceOAuthview ✓, writesalesforce_search_records, salesforce_get_record, salesforce_upsert_lead, salesforce_log_activity
Cal.comcalcomAPI keyview ✓, book ✓, managecalcom_list_event_types, calcom_check_availability, calcom_create_booking, calcom_cancel_booking

Each tool maps to one operation group — disabling the group removes every tool it gates from all attached agents.

Endpoints

MethodPathDescription
GET/v1/provider-connectionsList connections + provider catalog
GET/v1/provider-connections/{connection_id}Retrieve one connection
PATCH/v1/provider-connections/{connection_id}Update connection-level allowed_operations
DELETE/v1/provider-connections/{connection_id}Disconnect
POST/v1/provider-connections/{provider}/api-keyConnect an API-key provider (Cal.com)
PUT/v1/provider-connections/{connection_id}/agents/{agent_id}Attach an agent (optional per-agent override)
DELETE/v1/provider-connections/{connection_id}/agents/{agent_id}Detach an agent
POST/v1/provider-connections/{connection_id}/sheets/picker-tokenMint a short-lived token for the Google Picker
POST/v1/provider-connections/{connection_id}/sheets/inspectList a picked spreadsheet's tabs
POST/v1/provider-connections/{connection_id}/sheets/configurePick the target spreadsheet + sheet

{connection_id} is the connection's UUID. All writes require the admin role. (The browser-only OAuth start/callback routes are not part of the public API.)

Connection object

{
  "id": "9e1f6a3c-…",
  "provider": "slack",
  "provider_account_email": "ops@acme.com",
  "provider_account_id": "T024BE7LD",
  "provider_metadata": { "team_name": "Acme" },
  "granted_scopes": ["channels:read", "channels:history", "groups:read", "groups:history"],
  "allowed_operations": { "view": true, "post": true, "search": true },
  "effective_operations": { "view": true, "post": false, "search": true },
  "token_expires_at": null,
  "created_at": "2026-04-20T18:24:10.113Z",
  "updated_at": "2026-04-20T18:24:10.113Z",
  "agent_attachments": [
    {
      "agent_id": 12,
      "agent_name": "Customer Support Agent",
      "allowed_operations": null,
      "tool_names": ["slack_list_channels", "slack_post_message", "slack_search_messages"]
    }
  ]
}
FieldTypeDescription
idUUIDConnection id
providerstringProvider key (see table above)
provider_account_email / provider_account_idstringIdentity of the connected account
provider_metadataobjectProvider-specific info (e.g. the configured spreadsheet for Sheets)
granted_scopesarray of stringOAuth scopes actually granted
allowed_operationsobjectConnection-level operation toggles as configured, e.g. {"view": true, "post": false}. This is the raw ceiling — it does not reflect whether the OAuth grant covers each operation's required scopes
effective_operationsobjectRead-only, additive field. The same toggles after intersecting with granted_scopes — i.e. what's actually enforced. An operation can show true in allowed_operations but false here if the grant is missing a required scope (re-consent narrowed, a scope was added to an operation after the connection was created, etc.). Use this field, not allowed_operations, to know what an agent can really do
token_expires_attimestamp | nullAccess-token expiry (refreshed automatically)
agent_attachmentsarrayAttached agents. allowed_operations: null means "inherit the connection's"; tool_names is the resulting tool list — connection allowed_operations, the per-agent override, and granted_scopes all intersected

Tokens and API keys are never returned by any endpoint.


List connections

cURL
curl https://api.thunderphone.com/v1/provider-connections \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY"
Response
{
  "configured": true,
  "providers": {
    "slack": {
      "label": "Slack",
      "configured": true,
      "auth_kind": "oauth",
      "default_operations": { "view": true, "post": false, "search": true }
    }
  },
  "connections": [ /* Connection objects */ ],
  "agents": [{ "id": 12, "name": "Customer Support Agent" }]
}

providers is the full catalog with per-provider availability (configured is false when the ThunderPhone deployment lacks that provider's OAuth app). agents is a convenience list for building attachment UIs.


Update allowed operations

cURL
curl -X PATCH https://api.thunderphone.com/v1/provider-connections/9e1f6a3c-… \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"allowed_operations": {"view": true, "post": true, "search": true}}'

allowed_operations must be an object whose keys are that provider's operation groups (unknown keys are rejected with 400). Returns the updated connection object.

Authorization model

There are three layers, evaluated together at tool-synthesis and tool-execution time:

  1. Connection-level allowed_operations — the ceiling for the whole connection, set via PATCH.
  2. Per-agent override (agent_attachments[].allowed_operations) — an optional narrower subset for one agent, set via attach/detach. null means "inherit the connection's ceiling" — an override can only turn operations off, never grant one the connection ceiling denies.
  3. granted_scopes — what the OAuth grant actually covers. Each operation requires specific scopes (e.g. Slack post requires chat:write); an operation enabled at both the connection and agent level still can't run if the grant doesn't cover it.

effective_operations on the connection object is operations (1) and (3) intersected — it does not factor in a per-agent override, since that's per-attachment. The tool_names on each agent_attachments entry is the fully-intersected result: connection ceiling ∩ per-agent override (if any) ∩ granted scopes. That list is what the agent's tool schema actually contains, and execute_provider_tool re-checks the same intersection at call time (defense in depth against a stale route or shared connection dispatching a call the agent's current config would no longer allow).

Disconnect

DELETE /v1/provider-connections/{id} returns 204 No Content and removes the connection and every agent attachment.


Connect with an API key

Only for auth_kind: "api_key" providers — currently Cal.com.

cURL
curl -X POST https://api.thunderphone.com/v1/provider-connections/calcom/api-key \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"api_key": "cal_live_…"}'

The key is validated against the provider before saving; invalid keys return 400 with an api_key field error. Returns 201 Created with the connection object.


Attach / detach agents

Attaching gives the agent the connection's tools on its next deploy of call config (tool lists are computed per call).

Attach (inherit connection ops)
curl -X PUT https://api.thunderphone.com/v1/provider-connections/9e1f6a3c-…/agents/12 \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'
Attach (narrower override)
curl -X PUT https://api.thunderphone.com/v1/provider-connections/9e1f6a3c-…/agents/12 \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"allowed_operations": {"view": true, "post": false, "search": false}}'
FieldTypeRequiredDescription
allowed_operationsobject | nullnoPer-agent override. Omit / null to inherit the connection-level toggles

PUT is an upsert — repeat it to change the override. Returns the connection object with updated agent_attachments. DELETE on the same path detaches the agent (204).


Configure the target Google Sheet

sheets_append_row writes to one configured spreadsheet + sheet per connection. ThunderPhone holds the per-file drive.file Google scope, so the connection can only open spreadsheets the user has explicitly granted by picking them in the Google Picker (the dashboard runs this flow). Setup is three steps (Sheets connections only — other providers return 404 on these routes):

Mint a Picker token

cURL
curl -X POST https://api.thunderphone.com/v1/provider-connections/9e1f6a3c-…/sheets/picker-token \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY"
Response
{ "access_token": "ya29.…" }

Returns a short-lived access token for the connection's own Google credential. Load the Google Picker with this token (setOAuthToken) so the user's pick grants file access to the same credential the agent's tools use at call time.

Inspect a spreadsheet

cURL
curl -X POST https://api.thunderphone.com/v1/provider-connections/9e1f6a3c-…/sheets/inspect \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"spreadsheet_id": "1AbC…"}'
Response
{
  "spreadsheet_id": "1AbC…",
  "title": "Lead intake",
  "sheets": ["Leads", "Archive"]
}

Takes the file id returned by the Picker and lists the tab names. 400 when the spreadsheet has not been granted through the Picker (or access was revoked).

Configure

cURL
curl -X POST https://api.thunderphone.com/v1/provider-connections/9e1f6a3c-…/sheets/configure \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"spreadsheet_id": "1AbC…", "sheet_name": "Leads"}'

Reads row 1 of the chosen sheet as the column headers (at least one non-empty header required — 400 otherwise) and stores spreadsheet_id, sheet_name, and the header map in provider_metadata. Returns the updated connection object. The agent tool then appends one row per call, mapping its arguments onto those headers.