STACK
MENU
DOCS / API REFERENCE / SERVICES

Services API

Services represent external platforms and APIs that your agents can access. STACK currently maintains a catalog of 147 services and supports custom connections for HTTPS APIs outside the catalog. Customer credentials are protected with AWS KMS envelope encryption and decrypted in memory only when an authorized retrieval or proxy call needs them.

All endpoints require a bearer credential. Operator keys and user OAuth tokens can use this API; partner grants can reach only the catalog, connected-service, connect, and grant routes mapped to their granted scopes. Connection, configuration, and grant changes refuse agent and Passport contexts and require at least the standard member role. Revoking an agent's grants remains agent-reachable because it only removes access.

List Available Services

GET /v1/services

Retrieve the full catalog of available services that can be connected. Each service includes its provider key, display name, and whether it supports OAuth or credential-based connection.

bash
curl https://api.getstack.run/v1/services \
  -H "Authorization: Bearer sk_live_your_key"

List Connected Services

GET /v1/services/connected

Retrieve all services currently connected to your operator account. Includes connection status, verification state, and who connected the service. Credentials are never returned in list responses - use the Credentials API to retrieve them.

bash
curl https://api.getstack.run/v1/services/connected \
  -H "Authorization: Bearer sk_live_your_key"

Connect a Catalog Service

POST /v1/services/connect

Connect a service from the catalog. Provide the service ID, desired scopes, and optionally an OAuth token (for OAuth services) or redirect URI.

bash
curl -X POST https://api.getstack.run/v1/services/connect \
  -H "Authorization: Bearer sk_live_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "service_id": "svc_slack",
    "scopes": ["channels:read", "chat:write"],
    "redirect_uri": "https://getstack.run/api/oauth/callback"
  }'

ConnectServiceInput Fields

  • service_id (string, required) - the service ID from the catalog
  • scopes (string[], required) - at least one scope must be specified
  • redirect_uri (string, optional) - OAuth redirect URI
  • allowed_hosts (string[], optional) - required for per-tenant hosts that STACK cannot derive

The request body can also include oauth_token (for completing an OAuth flow) and member_id (to attribute the connection to a specific member).

Returns 201 with the created connection object.

Connect a Custom Service

POST /v1/services/custom

Connect a custom service that is not in the STACK catalog. Supports both single-field credentials (API key string) and multi-field credentials (key-value pairs). Custom services are private to your account and do not appear in the public catalog.

Single-field Credential

bash
curl -X POST https://api.getstack.run/v1/services/custom \
  -H "Authorization: Bearer sk_live_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Internal CRM",
    "description": "Company internal CRM API",
    "credential": "crm_key_abc123",
    "scopes": ["read", "write"]
  }'

Multi-field Credential

bash
curl -X POST https://api.getstack.run/v1/services/custom \
  -H "Authorization: Bearer sk_live_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "SFTP Server",
    "description": "Production SFTP for invoice uploads",
    "credential": {
      "host": "sftp.example.com",
      "username": "stack-agent",
      "password": "s3cur3p4ss",
      "port": "22"
    }
  }'

ConnectCustomServiceInput Fields

  • name (string, required) - display name, 1-100 chars
  • description (string, optional) - human-readable description, max 500 chars
  • credential (string | Record<string, string>, optional) - single API key string or key-value pairs. Optional to allow enabling a service without connecting.
  • scopes (string[], optional) - free-form scopes for documentation purposes
  • template (string, optional) - if provided, validates credential fields against the named template
  • oauth_auth_url (string, optional) - custom OAuth authorization URL
  • oauth_token_url (string, optional) - custom OAuth token URL
  • allowed_hosts (string[], optional) - hosts that may receive this credential. Required when STACK cannot derive a host.

If providing oauth_auth_url, you must also provide oauth_token_url (and vice versa). They must be provided together.

Custom services are stored with a custom_ prefix on the provider field (e.g., custom_internal_crm). Multi-field credentials are stored as an encrypted JSON string and returned as a credentials object on retrieval.

Returns 201 with the created connection object.

Connect Application Credentials

POST /v1/services/connect-application

Connect a catalog service with client credentials or another application identity.

  • service_id (string, required) - catalog service ID
  • credential (object, required) - non-empty application credential fields
  • scopes (string[], optional) - granted application scopes
  • allowed_hosts (string[], optional) - required for per-tenant hosts that STACK cannot derive

OAuth Token Lifecycle

STACK detects two OAuth token-lifecycle patterns automatically based on the shape of the stored credential JSON. Detection happens at proxy-call and credential-retrieval time. Plain string tokens, API keys, and Basic-auth credentials pass through unchanged.

1. Refresh-token rotation (delegated flow)

When a stored credential carries a refresh_token field and an expires_at within 60 seconds of the current time, STACK posts a refresh_token grant against the connection'soauth_token_url, persists the new tokens (KMS-encrypted, with an updated expires_at), and returns the freshened credential to the caller. A Redis single-flight lock prevents two concurrent calls from both refreshing.

Required credential JSON shape:

json
{
  "access_token": "eyJ0eXAi...",
  "refresh_token": "M.R3_BAY...",
  "expires_at": "2026-04-29T12:34:56.000Z"
}

This shape is what the dashboard's OAuth callback already produces — connections made via the standard OAuth dance (Outlook, Gmail, GitHub, Slack, etc.) get refresh-token rotation transparently with no caller changes.

2. Client-credentials grant (application-only / service-account)

When a stored credential carries client_id andclient_secret but noaccess_token, STACK exchanges them for an application-only access token via the client_credentials grant. The result is cached in Redis until expiry-30s and injected on every proxy call. No human in the loop, no MFA, no browser — suitable for fully autonomous service-account agents.

Required credential JSON shape (Microsoft Entra example):

json
{
  "client_id": "<azure-app-client-id>",
  "client_secret": "<azure-app-client-secret>",
  "tenant_id": "<your-entra-tenant-id>",
  "cc_scope": "https://graph.microsoft.com/.default",
  "cc_token_url": "https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token"
}
  • cc_scope (string, required) — OAuth scope for the grant. Microsoft pattern: "<resource>/.default" (e.g. https://graph.microsoft.com/.default).
  • cc_token_url (string, optional) — token endpoint override. If absent, falls back to the connection's oauth_token_url. STACK substitutes {tenant_id} into /common/ paths automatically when tenant_id is present.
  • tenant_id (string, optional) — used for tenant-scoped Microsoft endpoints; non-Microsoft providers can omit.

Worked example: Microsoft Entra app sending mail as a service account

Suppose an agent needs to send mail asagent@example.com with no human in the loop. The setup:

  • 1. Register an Azure App (Microsoft Entra) with application permission Mail.Send. Have an admin grant tenant-wide consent.
  • 2. Get client_id, client_secret, tenant_id from the app registration.
  • 3. Connect to STACK as a custom service with the JSON shape shown above.
  • 4. Grant the agent access to the connection with scope ["Mail.Send"].
  • 5. The agent calls /v1/proxy with its passport. STACK obtains an application access token and forwards to graph.microsoft.com/v1.0/users/{userId}/sendMail.
  • 6. Disconnect the connection to revoke access. The next STACK-verified proxy call fails.
bash
curl -X POST https://api.getstack.run/v1/services/custom \
  -H "Authorization: Bearer sk_live_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Outlook application",
    "credential": {
      "client_id": "your-azure-app-id",
      "client_secret": "your-azure-app-secret",
      "tenant_id": "your-entra-tenant-id",
      "cc_scope": "https://graph.microsoft.com/.default",
      "cc_token_url": "https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token"
    },
    "scopes": ["Mail.Send"]
  }'

STACK is conservative on errors: any failure in the token endpoint (network, 4xx, 5xx) leaves the existing credential untouched. The proxy then 401s on the upstream call, surfacing the failure naturally rather than masking it.

Disconnect a Service

DELETE /v1/services/:id/disconnect

Disconnect a service and permanently delete its stored credentials. This also cascades: any agents with grants to this connection have their active passports revoked.

bash
curl -X DELETE https://api.getstack.run/v1/services/conn_abc123/disconnect \
  -H "Authorization: Bearer sk_live_your_key"

Disconnecting deletes the stored credential and marks active Passports for affected agents as revoked. The next STACK-verified call rejects those Passports. Any workflow that depends on the connection will fail until it is reconnected.

Verify a Connection

POST /v1/services/:id/verify

Test that a service connection is working by making a health check request against the service API. For services with credential templates, STACK uses the template's verification URL. For OAuth services, STACK calls a known "me" endpoint (e.g., Slack's auth.test, GitHub's /user).

bash
curl -X POST https://api.getstack.run/v1/services/conn_abc123/verify \
  -H "Authorization: Bearer sk_live_your_key"

Returns the verification result with status healthy or an error. Updates the verification_status and verified_at columns on the connection.

json
{
  "status": "healthy",
  "verified_at": "2026-04-15T10:35:00Z"
}

Grant Agent Access to a Service

POST /v1/services/grant

Grant an agent access to a specific service connection with scoped permissions. The scopes must be a subset of the connection's scopes. The grant also sets the maximum cross-service data flow that future Passports may carry.

bash
curl -X POST https://api.getstack.run/v1/services/grant \
  -H "Authorization: Bearer sk_live_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "agent_id": "agt_abc123",
    "service_connection_id": "conn_abc123",
    "scopes": ["channels:read", "chat:write"]
  }'

GrantAgentAccessInput Fields

  • agent_id (string, required) - the agent to grant access to
  • service_connection_id (string, required) - the service connection ID
  • scopes (string[], required) - at least one scope, must be a subset of the connection scopes
  • constraints (array, optional) - parameter limits that passports may tighten but not loosen
  • intents (array, optional) - intent references expanded into constraints
  • allow_data_from (string[], optional) - service names whose data may flow into this service. Omitted or [] allows no cross-service flow. ["*"] is a standing grant covering the agent's currently active service grants; Passport issuance resolves it to exact names and never signs the wildcard.

A Passport may inherit or narrow this list, but cannot add another source. Adding a source to an existing grant is a governance action. Bearer API, MCP, and CLI callers receive403 GOVERNANCE_APPROVAL_REQUIRED; after a human approves the exact request in the dashboard, the caller retries it withX-Governance-Approval. Existing Passports do not change when a grant or service connection changes.

Returns 201 with the created grant object.

Revoke Agent Access

DELETE /v1/services/agents/:agentId/revoke

Revoke all service grants for an agent. This also cascades: active passports for the agent are revoked since the agent lost its service grants.

bash
curl -X DELETE https://api.getstack.run/v1/services/agents/agt_abc123/revoke \
  -H "Authorization: Bearer sk_live_your_key"
json
{
  "success": true
}

Get Agent Permissions

GET /v1/agents/:agentId/permissions

Retrieve all service grants for a specific agent, showing which connections and scopes the agent has access to.

bash
curl https://api.getstack.run/v1/agents/agt_abc123/permissions \
  -H "Authorization: Bearer sk_live_your_key"

Toggle Proxy Access

POST /v1/services/:id/proxy-toggle

Enable or disable proxy access for a specific service connection. When proxy is enabled, agents can use the Proxy API to make authenticated requests through STACK without accessing the raw credential.

bash
curl -X POST https://api.getstack.run/v1/services/conn_abc123/proxy-toggle \
  -H "Authorization: Bearer sk_live_your_key" \
  -H "Content-Type: application/json" \
  -d '{ "proxy_enabled": true }'

Request Body

  • proxy_enabled (boolean, required) - true to enable proxy, false to disable

Configure Service Mode

POST /v1/services/configure

Configure how a service connection is shared within the organization. The mode controls whether a single connection is shared across all members or each member has their own.

bash
curl -X POST https://api.getstack.run/v1/services/configure \
  -H "Authorization: Bearer sk_live_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "service_id": "svc_slack",
    "provider": "slack",
    "mode": "per_member"
  }'

Request Body

  • service_id (string, required) - the service ID
  • provider (string, required) - the provider slug
  • mode (enum, required) - "shared" | "per_member"

List Service Configurations

GET /v1/services/configurations

Retrieve all service configurations for your organization, showing the sharing mode for each configured service.

bash
curl https://api.getstack.run/v1/services/configurations \
  -H "Authorization: Bearer sk_live_your_key"

Delete Service Configuration

DELETE /v1/services/configurations/:provider

Remove the service configuration for a provider, reverting it to default behavior.

bash
curl -X DELETE https://api.getstack.run/v1/services/configurations/slack \
  -H "Authorization: Bearer sk_live_your_key"

List Credential Templates

GET /v1/services/templates

Retrieve all available credential templates. Templates define the fields required to connect a service, along with help URLs and verification endpoint configurations.

bash
curl https://api.getstack.run/v1/services/templates \
  -H "Authorization: Bearer sk_live_your_key"
json
{
  "templates": [
    {
      "provider": "openai",
      "name": "OpenAI",
      "fields": [
        {
          "key": "api_key",
          "label": "API Key",
          "type": "password",
          "required": true,
          "placeholder": "sk-proj-..."
        }
      ],
      "help_url": "https://platform.openai.com/api-keys",
      "verification_url": "https://api.openai.com/v1/models",
      "verification_headers": {
        "Authorization": "Bearer {{api_key}}"
      }
    }
  ]
}

Template field values use {{field_key}} interpolation in verification headers and URLs. When verifying a connection, STACK substitutes the actual credential values and checks for a 2xx response.

Tier Limits

Service connections are unlimited on every tier. Connecting one does not use the shared monthly action allowance.

Endpoint Summary

text
GET    /v1/services                          - list catalog services
GET    /v1/services/connected                - list your connections
GET    /v1/services/templates                - list credential templates
GET    /v1/services/configurations           - list service configurations
POST   /v1/services/connect                  - connect a catalog service
POST   /v1/services/connect-application      - connect with application credentials
POST   /v1/services/custom                   - connect a custom service
POST   /v1/services/configure                - set service sharing mode
POST   /v1/services/grant                    - grant agent access
POST   /v1/services/:id/verify              - verify a connection
POST   /v1/services/:id/proxy-toggle        - toggle proxy access
POST   /v1/services/:id/allowed-hosts       - replace the connection host allowlist
DELETE /v1/services/:id/disconnect           - disconnect a service
DELETE /v1/services/agents/:agentId/revoke   - revoke agent grants
DELETE /v1/services/configurations/:provider - delete configuration
GET    /v1/agents/:agentId/permissions       - get agent service permissions
stack | Docs