> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sempera.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Tools and integrations

> Tool definitions, test-mode execution, integrations and their connection lifecycle.

**Tools** describe capabilities: what an Agent can do mid-call (look up a caller, quote a service, find openings, request a booking, take a message). **Integrations** describe connections: the provider account a tool reaches (a booking system, a calendar, a CRM, an MCP server, a custom HTTP service).

Both are workspace-level resources. An Agent attaches them by id through its draft.

## Tools

`POST /v1/tools` with `{"definition": {...}}`. A definition has a stable id (minted), `name`, `description`, `kind`, `parameters` (the input schema), `request` (method, URL, headers, body template), `auth` (a secret **reference**, `SEMPERA_SECRET_<NAME>` held on Sempera's servers, never a value), timeouts and retry policy. `GET /v1/tools`, `GET /v1/tools/{id}`, `PATCH /v1/tools/{id}` (with the `revision` you read; a save built on an older revision is refused with `409 conflict`, `reason: stale_tool` and the revision on file), `DELETE /v1/tools/{id}` (archive; `?restore=1` to restore; an optional `?revision=` names the revision you read, and one that is no longer on file is refused with `409 conflict`, `reason: stale_tool` and the revision on file, with nothing archived or restored; without it the call behaves as before).

### Executing a tool

`POST /v1/tools/{id}/execute` with `{"arguments": {...}, "environment": "FIXTURE"|"MOCK"|"SANDBOX"}` runs the tool in test mode and returns a `tool_execution` record: `id`, `input`, `output`, `status`, `error`, `latency_ms`, `started_at`, `request_id`. A tool that writes is never sent from here without `confirm_write: true`. `GET /v1/tools/{id}/executions` lists recent executions.

Production executions happen inside conversations and appear on the Session's tool timeline, not here.

## Integrations

`POST /v1/integrations` with `{"definition": {...}}`, `GET /v1/integrations`, `PATCH /v1/integrations/{id}` (with the `revision` you read; a save built on an older revision is refused with `409 conflict`, `reason: stale_connection` and the revision on file), `DELETE /v1/integrations/{id}` (archive; `?restore=1` to restore; an optional `?revision=` names the revision you read, and one that is no longer on file is refused with `409 conflict`, `reason: stale_connection` and the revision on file, with nothing archived or restored; without it the call behaves as before), and `POST /v1/integrations/{id}/verify`, which probes the connection with its real credentials and reports the result. `GET /v1/integrations/{integration_id}` reads one connection and `GET /v1/integrations/{integration_id}/actions` lists the actions it offers a tool (`integrations:read`); both answer in the `{"data": ..., "errors": null}` envelope.

Connection lifecycle: `configured` -> `verification_required` -> `connected` -> (`degraded` | `error`) -> `disabled`.

Provider credentials live as secret references on Sempera's servers and are never returned. Provider-specific APIs do not leak into this surface: a salon's booking system and a restaurant's POS are both "an integration" with capabilities behind tools.

## Tools home and native connections

`/workspace/{workspace}/tools` has one top row: Tools, Analytics, Secrets, Connections (`?section=tools|analytics|secrets|connections`). Code snippets stay inside Tools. The former `/integrations` address redirects to Connections; the GitHub installation return continues to use its existing handoff.

Create tool opens `/workspace/{workspace}/tools/create`, reuses the existing organization tool form and registry create route, then reads the saved tool back and opens its workspace detail. Creating never attaches or executes it. The same workspace membership and `configure_integration` capability as the agent-level form are required. Tool and integration listbox filters narrow the catalogue and analytics without changing the stored execution counts; integration identity uses the saved connector or OAuth connection reference.

Create connection opens an anchored provider popover. Selecting an unconfigured provider explains the missing OAuth application credentials without creating a connection. Salesforce still exposes Production and Sandbox at that boundary. Once configured, the existing consent and token-exchange flow remains the only way to establish a connected account.

Analytics reads recorded tool executions for the selected agent, including counts, success and latency. No executions is an empty state, not a sample chart. Secrets reuses `/v1/secrets`: workspace-encrypted values are write-only after save and resolved only when the owning workspace's tool runs.

Create connection offers Google Calendar, Slack, Salesforce, HubSpot, Calendly, Cal.com and Notion. Each creation has its own registry ID and grant, so multiple accounts of the same provider are supported. A saved definition or declared action catalogue is not evidence that a provider was contacted. OAuth consent and server-side token exchange establish the connection; the row shows the account label, standing, granted scopes and last credential use. Choose the connection and individual actions in an agent's Apps section, then publish that draft.

Native actions use real provider APIs through Sempera's existing connection service:

| Provider | Actions |
| - | - |
| Google Calendar | Selected calendar availability/events; owner-enabled appointment changes |
| Slack | List channels, post message |
| Salesforce | Find/create lead, find/create contact |
| HubSpot | Find/create contact, create note |
| Calendly | Available times, single-use booking link |
| Cal.com | Available slots, create booking |
| Notion | Search, create page |

A Slack or Notion connection names one destination, the channel or database id in `selected_resource` (`{"channel_id": "C0123456789"}` or `{"database_id": "..."}`). On a call, the agent posts only there: the model is not offered the choice, a different destination is refused and nothing is sent. A connection with no destination offers no post or page tool on calls until its owner chooses one in Connections. Triggers and alert rules keep the destination their owner configured.

Writes follow the existing environment/write-permission policy and the agent's published tool selections. The execution evidence records status, timing and argument names, not credential values or customer record bodies. Native OAuth destinations are pinned; Salesforce uses the provider-issued instance URL, never a mutable registry address. Consent state is signed, single-use, bound to workspace, user and starting browser, and expires. Tokens are exchanged and refreshed server-side and encrypted using workspace/connection-bound associated data. No native access or refresh token is returned to the browser.

## Owner setup checklist

This is the single checklist for these seven apps. Nothing is activated or deployed by adding the code. Without both client values and the existing OAuth encryption key, a row says **Not configured** and cannot claim success.

* Set `SEMPERA_APP_ORIGINS` with the intended console origin first. For the production console, every row below uses the exact redirect URI `https://app.sempera.org/api/connectors/oauth/callback`. A separately deployed test console must register its exact origin plus `/api/connectors/oauth/callback` in a separate app or as an additional allowed redirect. No wildcards or workspace-specific redirect paths. Start and exchange use the same stored URI.
* Provision `SEMPERA_SECRET_OAUTH_TOKEN_KEY` using the existing 32-byte key format described in `sempera/oauth_crypto.py`, through the deployment secret store, not the browser or repository. Keep it stable across instances and restarts. This is separate from each provider's OAuth client secret.
* Create the apps below, register the exact redirect, grant the listed scopes or capabilities, and set both environment variables on Sempera's servers.

| Provider and app registration | Exact production redirect URI | Requested scopes/capabilities | Server environment variables |
| - | - | - | - |
| Google Calendar: Google Cloud OAuth web application | `https://app.sempera.org/api/connectors/oauth/callback` | `openid`, `email`, `calendar.calendarlist.readonly`, `calendar.events.readonly`, `calendar.events.freebusy` under `https://www.googleapis.com/auth/` for calendar scopes; appointment changes request `calendar.events.owned` separately | `SEMPERA_OAUTH_GOOGLE_CLIENT_ID`, `SEMPERA_OAUTH_GOOGLE_CLIENT_SECRET` |
| Slack: `https://api.slack.com/apps`, create app, OAuth & Permissions | `https://app.sempera.org/api/connectors/oauth/callback` | Slack's own bot scopes channels:read, groups:read and chat:write (not Sempera API scopes); invite the bot to channels it should use | `SEMPERA_OAUTH_SLACK_CLIENT_ID`, `SEMPERA_OAUTH_SLACK_CLIENT_SECRET` |
| Salesforce: Setup, App Manager / External Client App Manager, OAuth web-server app | `https://app.sempera.org/api/connectors/oauth/callback` | `api`, `refresh_token`; enable authorization code and PKCE, allow the connecting user's CRM object permissions | `SEMPERA_OAUTH_SALESFORCE_CLIENT_ID`, `SEMPERA_OAUTH_SALESFORCE_CLIENT_SECRET` |
| HubSpot: `https://developers.hubspot.com/`, developer account, public OAuth app | `https://app.sempera.org/api/connectors/oauth/callback` | `crm.objects.contacts.read`, `crm.objects.contacts.write` (contacts and associated notes) | `SEMPERA_OAUTH_HUBSPOT_CLIENT_ID`, `SEMPERA_OAUTH_HUBSPOT_CLIENT_SECRET` |
| Calendly: `https://developer.calendly.com/`, register OAuth application | `https://app.sempera.org/api/connectors/oauth/callback` | `default`; authorize an account with event types and scheduling access | `SEMPERA_OAUTH_CALENDLY_CLIENT_ID`, `SEMPERA_OAUTH_CALENDLY_CLIENT_SECRET` |
| Cal.com: Settings, Developer, OAuth, create OAuth client | `https://app.sempera.org/api/connectors/oauth/callback` | `READ_PROFILE`, `READ_BOOKING`; slots and booking creation use the public event-type endpoints | `SEMPERA_OAUTH_CALCOM_CLIENT_ID`, `SEMPERA_OAUTH_CALCOM_CLIENT_SECRET` |
| Notion: `https://www.notion.so/profile/integrations`, public integration | `https://app.sempera.org/api/connectors/oauth/callback` | No scope parameter; enable Read content and Insert content, then share the intended pages/databases during consent | `SEMPERA_OAUTH_NOTION_CLIENT_ID`, `SEMPERA_OAUTH_NOTION_CLIENT_SECRET` |

* Salesforce's modal selects Production (`login.salesforce.com`) or Sandbox (`test.salesforce.com`). Register/allow the client in the target organization. The saved token endpoint preserves that choice during refresh and revoke.
* PKCE S256 is used for Salesforce, Calendly and Cal.com. Slack, HubSpot and Notion use their configured confidential-client authorization-code flow. If Slack token rotation is enabled, refresh follows the returned expiry.
* Disconnect erases local token ciphertext and attempts provider revocation. Slack, Salesforce, HubSpot, Calendly and Notion have revocation handlers. Cal.com's OAuth implementation has no configured revocation endpoint: the UI explicitly asks the owner to remove access at Cal.com. A failed revocation is never represented as provider-confirmed. Salesforce, HubSpot, Calendly and Notion revoke this grant's own token even when another connection names the same account. Slack connections on the same Slack team can share one installation token, so Slack keeps the account-wide sibling safeguard keyed on the team id: its token is revoked only when no other live connection uses that team. Cal.com has no revocation and is never compared with other connections.
* Salesforce grants refresh from `issued_at` plus a conservative ten-minute lifetime, and a refresh updates the saved instance URL. Legacy grants without an instance show a reconnect action, not Connected. Refreshes are serialized by grant id in Sempera's database, including callers in separate server processes.
* Validate with disposable accounts: consent, account/scopes readback, an agent read, an approved write, automatic refresh, disconnect, and refusal on the next tool use. Automated fake-provider tests are not vendor certification; real consent remains unverified until these owner steps are completed.

## Hosted console connection routes

These authenticated console routes use the workspace's one connection registry in Sempera's database when that database is the canonical client store. They do not create another registry or automatically import a file kept on Sempera's servers. Configuration needs the `configure_integration` capability and workspace-wide scope; checks need `run_local_test`. The database independently isolates the stored rows. Workspace status and billing entitlement are checked before use; an explicit `connectors.enabled=false` disables connections. Disconnect remains available for credential cleanup.

| Method and literal path | Purpose |
| - | - |
| `GET /api/connectors/registry` | List this workspace's connections and usage. With `?cleanup=1`, a workspace whose connections are switched off can still read the list (`cleanup_only: true`), so a signed-in account can be disconnected |
| `POST /api/connectors/registry/create` | Create a reference-only connection |
| `POST /api/connectors/registry/update` | Save against the revision read |
| `POST /api/connectors/registry/archive` | Reversible archive with attachment guard, not credential revocation |
| `POST /api/connectors/probe` | MOCK definition check, or SANDBOX authorized native-provider read |
| `GET /api/connectors` | Load an agent's reviewed attachments with its client scope |
| `POST /api/connectors/calendar/resources` | Read subscribed Google calendars |
| `POST /api/connectors/resources` | Read eligible Slack channels or Notion databases; `{id, cursor?}` with `next_cursor` pagination |
| `GET /api/connectors/oauth/providers` | Provider configuration standing |
| `POST /api/connectors/oauth/start` | Start browser-bound consent |
| `GET /api/connectors/oauth/callback` | Consume consent once and save encrypted credentials |
| `GET /api/connectors/oauth/status` | Read grant standing without tokens |
| `POST /api/connectors/oauth/disconnect` | Forget local tokens and attempt supported remote revocation |

SANDBOX here means a read-only authorization check, not a claim that the connected account is a vendor sandbox. A successful check records VERIFIED; a refused check records FAILED. Neither proves that every action is permitted by that account. MOCK never reads a token or records provider success.

Slack's channel and Notion's database/title column are selected by the owner and read back from the authorized provider before saving `selected_resource`. They are content-bound into the published version. Missing destinations refuse runtime use. The model cannot pass a channel, database or title-column override. Slack link/media unfurling is disabled. Changing a destination requires a new publication; the hosted dispatch refuses a connection changed since publication.

Hosted calls dispatch native actions through the existing signed `POST /worker/data` operation `connector.call`. Sempera's servers accept it only while this exact call is admitted and in progress, recheck the version hashes, workspace, entitlement and current connection, and run the normal connection service. Provider credentials stay on Sempera's servers, never on the call service that carries the audio. Unknown write responses are not retried. No new public `/v1` route or separate integration store is introduced here.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.