STACK
MENU
DOCS / API REFERENCE / PROXY

Proxy API

The Proxy API sends authenticated HTTP requests to connected services. STACK injects the stored OAuth token or API key into the outbound request. A proxy-only agent does not receive the credential.

The Proxy API is available on every tier, including Free. A valid X-Passport-Token header is required on every proxy request.

Make a Proxy Request

Send an HTTP request through STACK to a connected service. STACK resolves the credential, injects authentication headers, forwards the request to the full target URL, screens the response, and returns the final permitted result.

bash
curl -X POST https://api.getstack.run/v1/proxy \
  -H "Authorization: Bearer sk_live_your_key" \
  -H "X-Passport-Token: eyJhbGciOiJFZERTQSIs..." \
  -H "Content-Type: application/json" \
  -d '{
    "service": "github",
    "method": "GET",
    "url": "https://api.github.com/user/repos?per_page=5"
  }'

ProxyRequestInput Fields

  • service (string, required) - provider slug (e.g. "github", "slack", "openai"). Maps to a connected service.
  • method (enum, required) - HTTP method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE"
  • url (string, required) - full target URL (e.g. "https://api.github.com/user/repos"), not a relative path
  • headers (Record<string, string>, optional) - extra headers to include. Auth headers injected by STACK will override these.
  • body (any, optional) - request body for POST, PUT, and PATCH requests
  • query (Record<string, string>, optional) - query parameters to append to the URL

The url must be a full URL on an allowed host for the selected service.

Required Headers

  • Authorization: Bearer $STACK_TOKEN - operator-scoped credential
  • X-Passport-Token - a valid passport JWT. Required for all proxy requests.
  • X-STACK-Authority-Request-Id - a stable 22-256 character base64url operation handle. Required for POST, PUT, PATCH, and DELETE. Persist it before sending and reuse it only for an unchanged retry.

Response

The proxy returns the screened and redacted response wrapped in a STACK envelope with status, safe headers, and body. Non-GET responses also include the stable operation ID.

json
{
  "status": 200,
  "headers": {
    "content-type": "application/json; charset=utf-8",
    "x-ratelimit-remaining": "4999"
  },
  "body": [
    {
      "id": 123456,
      "name": "my-repo",
      "full_name": "user/my-repo",
      "private": false
    }
  ]
}

Proxy Enabled Flag

Each service connection has a proxy_enabled flag that must be set to true before the proxy can be used for that service. Toggle it using the service proxy-toggle endpoint:

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 }'

If proxy_enabled is false on the target service connection, proxy requests return 403 Forbidden.

Common Examples

GitHub: List repositories

bash
curl -X POST https://api.getstack.run/v1/proxy \
  -H "Authorization: Bearer sk_live_your_key" \
  -H "X-Passport-Token: eyJ..." \
  -H "Content-Type: application/json" \
  -d '{
    "service": "github",
    "method": "GET",
    "url": "https://api.github.com/user/repos",
    "query": { "sort": "updated", "per_page": "10" }
  }'

Slack: Post a message

bash
curl -X POST https://api.getstack.run/v1/proxy \
  -H "Authorization: Bearer sk_live_your_key" \
  -H "X-Passport-Token: eyJ..." \
  -H "X-STACK-Authority-Request-Id: op_job_4821_refund_0001" \
  -H "Content-Type: application/json" \
  -d '{
    "service": "slack",
    "method": "POST",
    "url": "https://slack.com/api/chat.postMessage",
    "body": {
      "channel": "C0123456789",
      "text": "Deployed v2.1.0 to production"
    }
  }'

OpenAI: Chat completion

bash
curl -X POST https://api.getstack.run/v1/proxy \
  -H "Authorization: Bearer sk_live_your_key" \
  -H "X-Passport-Token: eyJ..." \
  -H "X-STACK-Authority-Request-Id: op_job_4821_openai_0001" \
  -H "Content-Type: application/json" \
  -d '{
    "service": "openai",
    "method": "POST",
    "url": "https://api.openai.com/v1/chat/completions",
    "body": {
      "model": "gpt-4o",
      "messages": [{ "role": "user", "content": "Hello" }]
    }
  }'

Passport Scope Checking

STACK verifies that the requested service is within the passport's scope. The passport JWT contains intent_services and services arrays. The proxy checks that the service field matches one of these.

If the service is not in scope, the request is blocked with 403 in every accountability mode, and a credential_outside_scope security event is recorded. The mode sets only the severity of that event:

  • enforced and standard - critical
  • logged - warning

Additionally, proxy requests are blocked if the passport has already been checked out. This generates a credential_after_checkout security event.

Authority enforcement

When the Passport carries authority_refs, STACK resolves the exact active binding and enforces its pinned agent key, receiver, action, resource, Mission, validity, approval, and limits before forwarding the request. Missing, stale, revoked, or unavailable required state denies the action.

A proxy-mediated authority use proves STACK enforcement. It is not a native receiver proof. Native verification uses a signed receiver request and a signed-then-encrypted presentation delivered directly to the receiver. See Authority API.

How Credential Injection Works

STACK determines the correct authentication method based on the service provider and injects the appropriate headers into the outbound request. The agent's original request never contains the credential.

  • OAuth services - STACK adds an Authorization: Bearer <access_token> header
  • API key services - STACK injects the key into the provider-specific header
  • Custom services - STACK injects the stored credential according to template configuration
  • URL templating - URLs containing {{instance_url}} are resolved using the credential data

Write operation recovery

Every non-GET request is one durable operation. Its operation handle is bound to the exact tenant, agent, Passport, authority and approval references, service connection and credential version, destination, headers, query and body. Concurrent or delayed calls with the same handle and request join the existing operation. Reusing the handle with any changed request returns 409 Conflict.

Generic HTTP operations have at most one STACK transport attempt. If STACK has stored a final screened response, an authorised caller can read it again. If the request may have reached the provider but no result can be proved, delivery_state is outcome_unknown; STACK does not treat that as failure and does not automatically repeat the write. Stripe refunds use a STACK-derived Stripe idempotency key and provider lookup. STACK permits at most three transport attempts for a Stripe refund, all within 23 hours of the first durable dispatch. Each retry rechecks the original Passport, authority and connection before it can reach Stripe.

bash
curl -X POST https://api.getstack.run/v1/proxy/operations/get \
  -H "Authorization: Bearer sk_live_your_key" \
  -H "X-Passport-Token: eyJ..." \
  -H "Content-Type: application/json" \
  -d '{
    "operation_id": "op_job_4821_refund_0001",
    "include_response": true
  }'
  • delivery_state - admitted | dispatching | response_received | outcome_unknown
  • replay_state - none | screening | available | expired | erased
  • accounting_state - reserved | committed | released
  • provider_outcome - provider reconciliation state; Stripe can also return provider_status

The operation ID is correlation, not authentication, and is sent in the request body rather than the URL. Agent reads require current operator access and a current Passport covering the original agent, connection and request scope. An owner or admin can still read non-payload metadata in Console after that Passport expires, but cannot use the operation handle to read the stored response. Unauthorised probes return the same not-found response as an unknown ID. The encrypted stored response is readable for up to 24 hours, then its payload is scrubbed while non-payload metadata and hashes remain.

Action Allowance

One admitted proxy operation uses one unit from the shared monthly action allowance. Exact retries of that operation do not add another unit. Status and stored-response reads are unmetered. A request refused after policy evaluation still consumes an attempt.

  • Free - 25,000 metered actions / month
  • Developer ($19/mo) - 250,000
  • Pro ($99/mo) - 1,500,000
  • Business ($349/mo) - 10,000,000
  • Enterprise - unlimited monthly allowance

Above the allowance, STACK debits the Wallet. A request returns 402 when the Wallet cannot cover the overage. Provider-side charges remain separate and are billed under the provider's own terms.

Check Proxy Usage

Check shared action use for the current calendar month.

bash
curl https://api.getstack.run/v1/proxy/usage \
  -H "Authorization: Bearer sk_live_your_key"
json
{
  "tier": "pro",
  "limit": 1500000,
  "used": 205411,
  "remaining": 1294589,
  "period": "2026-04"
}

For Enterprise tier, limit and remaining are null (unlimited).

Security Model

  • Credential isolation - a proxy-only agent does not receive the raw credential
  • Revocation - disconnecting a service rejects the next proxy request
  • Passport-gated - every proxy request requires a valid passport JWT
  • Scope enforcement - only services listed in the passport scope are accessible
  • Post-checkout blocking - no proxy requests after passport checkout
  • Audit trail - every proxy request is logged as credential.proxy with agent ID and passport JTI
  • Write recovery - a stable operation handle binds an exact non-GET request to one generic STACK dispatch
  • Credential access tracking - STACK tracks which services were accessed per passport for Signal 9 (credential_unreported)

Error Handling

json
// Missing X-Passport-Token
{ "error": { "code": "FORBIDDEN", "message": "X-Passport-Token header required for proxy requests" } }

// Wallet cannot cover overage (402)
{ "error": { "code": "INSUFFICIENT_BALANCE", "message": "Monthly action allowance exceeded and the wallet has insufficient balance for overage. Top up at https://getstack.run/billing." } }

// Service not in passport scope (any accountability mode)
{ "error": { "code": "FORBIDDEN", "message": "Service notion not in passport scope" } }

// Proxy disabled on connection
{ "error": { "code": "FORBIDDEN", "message": "Proxy access disabled for service notion" } }

// Passport already checked out
{ "error": { "code": "FORBIDDEN", "message": "Passport already checked out" } }

STACK screens and redacts the upstream response before it is returned or stored. Permitted provider data, including customer PII requested from a service such as Stripe, can still be visible to the agent. Scope the request and the agent's disclosure permissions accordingly.

stack | Docs