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.
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.
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"
}'The url must be a full URL on an allowed host for the selected service.
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.
{
"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
}
]
}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:
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.
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" }
}'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"
}
}'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" }]
}
}'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:
Additionally, proxy requests are blocked if the passport has already been checked out. This generates a credential_after_checkout security event.
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.
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.
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.
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
}'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.
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.
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 shared action use for the current calendar month.
curl https://api.getstack.run/v1/proxy/usage \
-H "Authorization: Bearer sk_live_your_key"{
"tier": "pro",
"limit": 1500000,
"used": 205411,
"remaining": 1294589,
"period": "2026-04"
}For Enterprise tier, limit and remaining are null (unlimited).
// 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.