Skip to content

Connectors

A connector is a fixed set of actions (search Gmail, comment on a PR, post to Slack) that call the vendor’s real API. You connect a service once, usually through an OAuth consent screen, and its credentials are stored encrypted per tenant. Every agent in the workspace can then call it, either as an LLM tool or directly over REST. There are 18 built-in connectors, counted from the executor’s dispatch table (connector_executor.go).

Connector actionsHow a tool call or REST call reaches an external API with a stored credential, and what happens when nothing is installed.
Agent or RESTtool call · curlPOST …/execute?action=nameconnector_installsAES-256-GCM at restConnector executor18 connectorsExternal APISlack · GitHub · …Fixture serverrecorded responses
Swipe sideways to pan.

1. An action is requested

The model calls an assigned connector tool, or a client posts to the execute endpoint with params.

  • Outside PAL
  • Control plane
  • Data stores
Area Connector (id) Actions Credential
Communication Slack (slack) list channels and users, post messages, reply in threads OAuth or bot token
Discord (discord) list guilds, send messages bot token or OAuth
Telegram (telegram) send messages, fetch updates bot token
Twilio (twilio) send SMS, list messages, place calls, add conference participants Account SID + Auth Token + phone number (or Messaging Service SID)
Email and calendar Gmail (gmail) list, search, send Google OAuth or email + app password
Google Calendar (google-calendar) list and create events Google OAuth
Docs and storage Google Drive (google-drive) list files Google OAuth
Google Sheets (google-sheets) get a spreadsheet, get cell values Google OAuth
Notion (notion) search, list databases OAuth or integration token
Dev tools GitHub (github) list repos, list and get PRs and issues, create issues, comment on PRs OAuth or personal access token
Linear (linear) list and create issues API key or OAuth
Jira (jira) list and create issues email + API token + domain, or OAuth
Sentry (sentry) list and get issues auth token
PagerDuty (pagerduty) list and get incidents, create and update incidents, add notes REST API key
Vercel (vercel) list projects and deployments access token
CRM and sales HubSpot (hubspot) list contacts and deals API key or OAuth
Salesforce (salesforce) run SOQL queries access token + instance URL (OAuth supported)
Commerce Stripe (stripe) list charges and customers secret key or OAuth
  1. Open Connectors (/connectors) in the dashboard.
  2. For OAuth providers (Gmail, Google Calendar, Drive, Sheets, Slack, GitHub, Discord, Notion, Linear, Jira, HubSpot, Stripe, Salesforce), click the provider button, for example Sign in with Google or Add to Slack, and authorize. This works when the deployment has OAuth client credentials configured.
  3. For key-based providers (Telegram, Twilio, Sentry, PagerDuty, Vercel), choose the manual option, for example Enter bot token, and fill in the credentials. They are tested before the connector shows as connected.

PagerDuty has no dashboard card yet; install it over the API (below). Uninstall with DELETE /v1/connectors/{id} to revoke access.

Credentials are encrypted at rest with AES-256-GCM when PAL_CREDENTIAL_KEY is set. Without it they are stored as plaintext, so set it outside dev. Credentials never appear in logs, traces or run state: connector actions run in the control plane, which resolves the credential at call time.

Call any action directly with POST /v1/connectors/{id}/execute?action=<action> and a JSON body of params. Install needs the connectors:write scope; execute needs runs:execute.

Terminal window
curl -X POST "http://localhost:8080/v1/connectors/gmail/execute?action=search" \
-H "Authorization: Bearer $PAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query": "from:boss@company.com subject:urgent"}'

A successful call returns {"connector", "action", "data"}. A failed vendor call returns 502 with {"error", "connector", "action"}. An uninstalled connector returns 404 with a missing-credentials error naming it.

Agents get a curated subset of actions as tools (for example github__list_prs, slack__post_message, gmail__search) and decide when to call them. The full list is always callable over REST and from workflow connector nodes.

Connector / action Params Credential (config field) Class
slack / reply_thread channel, text, thread_ts (all required) OAuth, or botToken write
github / comment_pr owner, repo, number, body (all required) OAuth, or personalAccessToken write
sentry / get_issue organization, issue_id (required) authToken read-only
google-calendar / create_event summary, start, end (required, RFC3339); optional calendarId (default primary), timeZone, description, location, attendees (array of emails) Google OAuth write
pagerduty / list_incidents optional status (triggered, acknowledged, resolved), limit (default 25) apiKey read-only
pagerduty / get_incident incident_id (required) apiKey read-only
pagerduty / create_incident title, service_id, from (required; from is the acting user’s email); optional details apiKey write
pagerduty / update_incident incident_id, from, status (required; acknowledged or resolved) apiKey write
pagerduty / add_note incident_id, content, from (all required) apiKey write

Slack post_message also accepts an optional thread_ts; reply_thread is the same call with thread_ts required. PagerDuty agents get list_incidents and get_incident as tools. create_incident, update_incident and add_note are REST-only, plus usable from workflow connector nodes.

Terminal window
# install PagerDuty (config.apiKey = a REST API key)
curl -X POST http://localhost:8080/v1/connectors/install \
-H "Authorization: Bearer $PAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{"connectorId": "pagerduty", "displayName": "PagerDuty", "config": {"apiKey": "..."}}'
# read an incident
curl -X POST "http://localhost:8080/v1/connectors/pagerduty/execute?action=get_incident" \
-H "Authorization: Bearer $PAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{"incident_id": "PABC123"}'
# acknowledge it (write; "from" is required by PagerDuty)
curl -X POST "http://localhost:8080/v1/connectors/pagerduty/execute?action=update_incident" \
-H "Authorization: Bearer $PAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{"incident_id": "PABC123", "from": "oncall@example.com", "status": "acknowledged"}'
# reply in a Slack thread
curl -X POST "http://localhost:8080/v1/connectors/slack/execute?action=reply_thread" \
-H "Authorization: Bearer $PAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{"channel": "C0123", "thread_ts": "1700000000.000100", "text": "On it."}'

PAL Runtime keeps an explicit allowlist of read-only actions in connector_safety.go (for example list_*, get_*, search and query on the built-in connectors). Every other action, including any action on an unknown connector, counts as side-effecting. In the workflow engine, confidence gating (off by default) can route side-effecting steps to a human first.

Connectors are installed per workspace, not per agent. Once connected, every agent in the workspace can call it. The agent page’s Connectors card is a read-only list linking to /connectors. To limit the blast radius, use budget toolLimits (workflow tool and connector nodes only), the approval and confidence gates, or a separate workspace.

OAuth scopes come from oauth_providers.go. Gmail asks for gmail.modify (read, send, label; no permanent delete), GitHub for repo and read:user, Slack for channels:read, chat:write and users:read.

Point a vendor at a local fixture server. This is dev and test only.

  1. Start the fixture server from services/control-plane. It listens on loopback and serves recorded responses for Slack, GitHub, Sentry, PagerDuty and Google Calendar.

    Terminal window
    go run ./cmd/connector-replay # 127.0.0.1:9191
    go run ./cmd/connector-replay -addr 127.0.0.1:9292
  2. Point the control plane at it before starting it. PAL_ENV must not be prod, production or staging.

    Terminal window
    export PAL_CONNECTOR_BASEURL_SLACK=http://127.0.0.1:9191
    export PAL_CONNECTOR_BASEURL_GITHUB=http://127.0.0.1:9191
    export PAL_CONNECTOR_BASEURL_SENTRY=http://127.0.0.1:9191
    export PAL_CONNECTOR_BASEURL_GOOGLE=http://127.0.0.1:9191
    export PAL_CONNECTOR_BASEURL_PAGERDUTY=http://127.0.0.1:9191
    make run-api
  3. Install a connector with any non-empty token, execute an action, then read what the server saw:

    Terminal window
    curl -s http://127.0.0.1:9191/__requests

    Each recorded call has method, path, query, header, body, hasAuth, matched and status. The Authorization header is never stored, only whether it was set.

Variable Vendor host replaced Connectors affected
PAL_CONNECTOR_BASEURL_SLACK slack.com Slack
PAL_CONNECTOR_BASEURL_GITHUB api.github.com GitHub
PAL_CONNECTOR_BASEURL_SENTRY sentry.io Sentry
PAL_CONNECTOR_BASEURL_GOOGLE www.googleapis.com Google Calendar, Google Drive
PAL_CONNECTOR_BASEURL_PAGERDUTY api.pagerduty.com PagerDuty

The value must be an absolute http or https URL with a host. Only scheme and host are replaced; the vendor path is kept. An unmatched request gets 404 with {"error": "no recorded fixture for METHOD PATH"}.

Fixtures are exact method + path matches in internal/connectorreplay/fixtures.json, embedded in the binary: Slack conversations.list and chat.postMessage; GitHub acme/api pulls list, PR 42, and a comment on 42; Sentry org acme issues list and issue 1001; PagerDuty incidents list, get PABC123, create, update; Google Calendar primary events list and insert. Use those owner, repo, org and ids in your test params.

  • Overrides are refused in production. With PAL_ENV set to prod, production or staging, the control plane exits if any PAL_CONNECTOR_BASEURL_* variable is set, because a bearer token must never go to an arbitrary host. The outbound transport also ignores overrides in prod at request time. A malformed value is fatal in every environment.
  • Outbound calls refuse internal addresses. Each connector request checks the resolved IP when it connects, including after a redirect. Cloud metadata and link-local addresses are always refused. Loopback and private ranges (RFC 1918, ULA, CGNAT) are refused too, unless dev mode sets PAL_CONNECTOR_ALLOW_PRIVATE=1 or the request goes to a PAL_CONNECTOR_BASEURL_* fixture. Production ignores both. Connector calls don’t use HTTPS_PROXY/HTTP_PROXY. A deployment that routes egress through a proxy must allow the control plane direct egress to vendor hosts.
  • Tenant-supplied hosts are allowlisted. The Jira domain must be under atlassian.net, for example acme.atlassian.net. The Salesforce instance URL must be an https origin under salesforce.com or force.com, which covers *.my.salesforce.com, sandbox domains and test.salesforce.com. Any other value is rejected before a request is sent, so Jira Data Center, Jira Server and custom Jira domains aren’t supported yet.
  • No plug-in interface. A new connector is a code change.
  • Workspace-wide access. There is no per-agent connector assignment.