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:
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_ORIGINSwith the intended console origin first. For the production console, every row below uses the exact redirect URIhttps://app.sempera.org/api/connectors/oauth/callback. A separately deployed test console must register its exact origin plus/api/connectors/oauth/callbackin 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_KEYusing the existing 32-byte key format described insempera/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.
- 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_atplus 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 theconfigure_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.
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.