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).
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
Available connectors
Section titled “Available connectors”| 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 |
Connect one
Section titled “Connect one”- Open Connectors (
/connectors) in the dashboard. - 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.
- 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 an action
Section titled “Call an action”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.
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.
Newer actions
Section titled “Newer actions”| 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.
# 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 incidentcurl -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 threadcurl -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."}'Read-only versus side-effecting
Section titled “Read-only versus side-effecting”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.
Which agents can use a connector
Section titled “Which agents can use a connector”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.
Test without a vendor account
Section titled “Test without a vendor account”Point a vendor at a local fixture server. This is dev and test only.
-
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:9191go run ./cmd/connector-replay -addr 127.0.0.1:9292 -
Point the control plane at it before starting it.
PAL_ENVmust not beprod,productionorstaging.Terminal window export PAL_CONNECTOR_BASEURL_SLACK=http://127.0.0.1:9191export PAL_CONNECTOR_BASEURL_GITHUB=http://127.0.0.1:9191export PAL_CONNECTOR_BASEURL_SENTRY=http://127.0.0.1:9191export PAL_CONNECTOR_BASEURL_GOOGLE=http://127.0.0.1:9191export PAL_CONNECTOR_BASEURL_PAGERDUTY=http://127.0.0.1:9191make run-api -
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/__requestsEach recorded call has
method,path,query,header,body,hasAuth,matchedandstatus. TheAuthorizationheader 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.
Limits
Section titled “Limits”- Overrides are refused in production. With
PAL_ENVset toprod,productionorstaging, the control plane exits if anyPAL_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=1or the request goes to aPAL_CONNECTOR_BASEURL_*fixture. Production ignores both. Connector calls don’t useHTTPS_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 exampleacme.atlassian.net. The Salesforce instance URL must be anhttpsorigin undersalesforce.comorforce.com, which covers*.my.salesforce.com, sandbox domains andtest.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.