Proxy a PATCH request to an integration with server-side credential injection

High-value endpoint. Accepts an upstream HTTP request and forwards it to the upstream API after injecting the stored credentials server-side. Credentials never leave Appstrate.

Authentication: bearer only — either an API key with the credential-proxy:call scope (carried by a key created with scopes omitted or empty when its creator holds it) or an OIDC-issued JWT (device-flow access token for the interactive CLI, dashboard access token for second-party apps). Cookie sessions are rejected. Session binding pins the X-Session-Id to the first principal (API key or JWT user) that used it.

Optional Appstrate-User header scopes the call to an end-user's connection (API-key auth only).

URL and headers can contain {{credential_field}} placeholders substituted against the integration's credential schema. Set X-Substitute-Body: 1 to run the same substitution on the request body (verbs that carry one).

Boolean control headers (X-Substitute-Body, X-Stream-Request, X-Stream-Response) take 1 or 0; any other value is a 400.

Every response carries RFC 9209 Proxy-Status: appstrate; received-status=<n> on an upstream response relayed whatever its status (a relayed 401 carries no platform WWW-Authenticate challenge — it is the upstream refusing the connection's credential), appstrate; error=<type> on a response the proxy produced itself, whose problem code names the cause.

PATCH/api/credential-proxy/proxy

Authorization

AuthorizationBearer <token>

API key authentication. Use Authorization: Bearer apst_... header. The org is resolved from the key — no X-Org-Id header needed. A key is apst_ + 30 base62 characters + a 6-character base62 CRC32 of those 30, so a malformed key is refused without a lookup.

In: header

Header Parameters

X-Space-Id*string

Space id (spc_…) the API key is scoped to.

X-Integration-Id*string

Scoped integration package name (e.g. @afps/gmail).

X-Target*string

Upstream endpoint: an absolute URL, or one whose {{credential_field}} placeholders (e.g. {{site_url}}/wp-json/…) the platform substitutes from the connection before any check; the substituted URL must be absolute. It must match the integration manifest auth's authorized_uris (rendered for the connection) unless allow_all_uris: true. allow_all_uris is ignored when a {{credential_field}} placeholder appears in this URL, a header, or a substituted body: the target and every redirect hop must then match authorized_uris, and the call is refused when that list is empty.

X-Session-Id*string

Caller-chosen session id; scopes the cookie jar. Fresh UUID per CLI invocation is typical.

X-Substitute-Body?string

When 1, the request body is decoded as UTF-8 and {{field}} placeholders are substituted. Ignored on verbs that do not carry a body (GET, DELETE).

Value in"0" | "1"
Appstrate-User?string

Impersonation header — scopes the call to this end-user's connection.

Match^eu_
X-Stream-Request?string

When 1, forward the request body as a stream instead of buffering. Required for uploads larger than the buffered body cap; the upstream content length is still validated against CREDENTIAL_PROXY_LIMITS.max_request_bytes. Ignored on verbs that do not carry a body (GET, DELETE).

Value in"0" | "1"
X-Stream-Response?string

When 1, stream the upstream response body through the 100 MB streaming cap instead of buffering it. Skips the buffered max_response_bytes truncation, so X-Truncated is not emitted; an oversized stream is aborted rather than truncated.

Value in"0" | "1"
X-Max-Response-Size?string

Optional cap (in bytes) on the buffered upstream response before truncation. Clamped to CREDENTIAL_PROXY_LIMITS.max_response_bytes. Ignored when X-Stream-Response: 1 is set.

X-Run-Id?string

Optional run id (run_…) of the run this call acts for — sent by a runner executing it (appstrate run --report). Must name an in-flight run of the calling actor in this space: an unknown id or one of another space is a 404, another actor's run a 403, a finished run a 400. It binds the call to the run's snapshot: the call reaches ONLY the connections the run's kickoff bound to the integration (every layer applied, agent-level ones included — admin pins, enforced defaults, launch overrides, member pins): one bound connection is used; several require X-Connection-Id naming one of them (409 must_choose_connection when absent, 400 connection_not_in_run when it names another); none is a 404, and so is a bound one no longer reachable (deleted or unshared); a bound one that needs reconnecting is a 409 needs_reconnection. Without it no agent is in play, so the admin and member pins (set per agent) cannot apply — only the space-level rules described under X-Connection-Id do.

X-Connection-Id?string

Optional explicit connection UUID. With X-Run-Id, it must name a connection the run bound (see X-Run-Id). Without it, the space-level rules apply in this order: (1) an ENFORCED org default of the integration binds its set — a named id must be a member (400 connection_not_in_org_default otherwise); (2) the named connection, after validating it is one of the caller's own (user or end-user) or a connection another member shared in the request's space, of the requested integration; (3) a SOFT org default binds its set; (4) the caller's own connections: exactly one is used, none with some shared by other members is a 409 must_choose_connection (a shared connection is never used unless named or set as a default), none at all a 404, several a 409 must_choose_connection. A default set of one is used, several are a 409 must_choose_connection over the set, and a member the caller cannot reach is a 409 pinned_connection_unavailable, and a bound connection whose credentials need reconnecting (a default's member included) a 409 needs_reconnection. A non-uuid value is a 400; mismatched or unknown ids surface as 404 — no credentials.

Formatuuid

Request Body

application/octet-stream

Forwarded as-is to the upstream. Optional placeholder substitution via X-Substitute-Body.

curl -X PATCH "https://your-instance/api/credential-proxy/proxy" \  -H "X-Space-Id: string" \  -H "X-Integration-Id: string" \  -H "X-Target: string" \  -H "X-Session-Id: string"
Empty
{
  "type": "http://example.com",
  "title": "string",
  "status": 0,
  "detail": "string",
  "instance": "string",
  "code": "string",
  "request_id": "string",
  "param": "string",
  "retry_after": 0,
  "errors": [
    {
      "field": "string",
      "code": "string",
      "message": "string",
      "title": "string",
      "candidate_connections": [
        {
          "id": "string",
          "label": "string",
          "account_id": "string",
          "owned_by_actor": true,
          "needs_reconnection": true
        }
      ],
      "connection_id": "string",
      "missing_scopes": [
        "string"
      ],
      "owned_by_actor": true,
      "required_scopes": [
        "string"
      ],
      "auth_key": "string",
      "required_auth_key": "string",
      "available_auth_keys": [
        "string"
      ],
      "connect_url": "http://example.com",
      "expiresAt": "2019-08-24T14:15:22Z",
      "packageId": "string"
    }
  ]
}
{
  "type": "http://example.com",
  "title": "string",
  "status": 0,
  "detail": "string",
  "instance": "string",
  "code": "string",
  "request_id": "string",
  "param": "string",
  "retry_after": 0,
  "errors": [
    {
      "field": "string",
      "code": "string",
      "message": "string",
      "title": "string",
      "candidate_connections": [
        {
          "id": "string",
          "label": "string",
          "account_id": "string",
          "owned_by_actor": true,
          "needs_reconnection": true
        }
      ],
      "connection_id": "string",
      "missing_scopes": [
        "string"
      ],
      "owned_by_actor": true,
      "required_scopes": [
        "string"
      ],
      "auth_key": "string",
      "required_auth_key": "string",
      "available_auth_keys": [
        "string"
      ],
      "connect_url": "http://example.com",
      "expiresAt": "2019-08-24T14:15:22Z",
      "packageId": "string"
    }
  ]
}
{
  "type": "http://example.com",
  "title": "string",
  "status": 0,
  "detail": "string",
  "instance": "string",
  "code": "string",
  "request_id": "string",
  "param": "string",
  "retry_after": 0,
  "errors": [
    {
      "field": "string",
      "code": "string",
      "message": "string",
      "title": "string",
      "candidate_connections": [
        {
          "id": "string",
          "label": "string",
          "account_id": "string",
          "owned_by_actor": true,
          "needs_reconnection": true
        }
      ],
      "connection_id": "string",
      "missing_scopes": [
        "string"
      ],
      "owned_by_actor": true,
      "required_scopes": [
        "string"
      ],
      "auth_key": "string",
      "required_auth_key": "string",
      "available_auth_keys": [
        "string"
      ],
      "connect_url": "http://example.com",
      "expiresAt": "2019-08-24T14:15:22Z",
      "packageId": "string"
    }
  ]
}
{
  "type": "http://example.com",
  "title": "string",
  "status": 0,
  "detail": "string",
  "instance": "string",
  "code": "string",
  "request_id": "string",
  "param": "string",
  "retry_after": 0,
  "errors": [
    {
      "field": "string",
      "code": "string",
      "message": "string",
      "title": "string",
      "candidate_connections": [
        {
          "id": "string",
          "label": "string",
          "account_id": "string",
          "owned_by_actor": true,
          "needs_reconnection": true
        }
      ],
      "connection_id": "string",
      "missing_scopes": [
        "string"
      ],
      "owned_by_actor": true,
      "required_scopes": [
        "string"
      ],
      "auth_key": "string",
      "required_auth_key": "string",
      "available_auth_keys": [
        "string"
      ],
      "connect_url": "http://example.com",
      "expiresAt": "2019-08-24T14:15:22Z",
      "packageId": "string"
    }
  ]
}
{
  "type": "http://example.com",
  "title": "string",
  "status": 0,
  "detail": "string",
  "instance": "string",
  "code": "string",
  "request_id": "string",
  "param": "string",
  "retry_after": 0,
  "errors": [
    {
      "field": "string",
      "code": "string",
      "message": "string",
      "title": "string",
      "candidate_connections": [
        {
          "id": "string",
          "label": "string",
          "account_id": "string",
          "owned_by_actor": true,
          "needs_reconnection": true
        }
      ],
      "connection_id": "string",
      "missing_scopes": [
        "string"
      ],
      "owned_by_actor": true,
      "required_scopes": [
        "string"
      ],
      "auth_key": "string",
      "required_auth_key": "string",
      "available_auth_keys": [
        "string"
      ],
      "connect_url": "http://example.com",
      "expiresAt": "2019-08-24T14:15:22Z",
      "packageId": "string"
    }
  ]
}
{
  "type": "http://example.com",
  "title": "string",
  "status": 0,
  "detail": "string",
  "instance": "string",
  "code": "string",
  "request_id": "string",
  "param": "string",
  "retry_after": 0,
  "errors": [
    {
      "field": "string",
      "code": "string",
      "message": "string",
      "title": "string",
      "candidate_connections": [
        {
          "id": "string",
          "label": "string",
          "account_id": "string",
          "owned_by_actor": true,
          "needs_reconnection": true
        }
      ],
      "connection_id": "string",
      "missing_scopes": [
        "string"
      ],
      "owned_by_actor": true,
      "required_scopes": [
        "string"
      ],
      "auth_key": "string",
      "required_auth_key": "string",
      "available_auth_keys": [
        "string"
      ],
      "connect_url": "http://example.com",
      "expiresAt": "2019-08-24T14:15:22Z",
      "packageId": "string"
    }
  ]
}
{
  "type": "https://docs.appstrate.dev/errors/rate-limited",
  "title": "Rate Limited",
  "status": 429,
  "detail": "Too many requests. Please try again shortly.",
  "code": "rate_limited",
  "request_id": "req_abc123",
  "retry_after": 30
}
{
  "type": "https://docs.appstrate.dev/errors/internal-error",
  "title": "Internal Server Error",
  "status": 500,
  "detail": "An unexpected error occurred. Please try again or contact support.",
  "code": "internal_error",
  "request_id": "req_abc123"
}
{
  "type": "http://example.com",
  "title": "string",
  "status": 0,
  "detail": "string",
  "instance": "string",
  "code": "string",
  "request_id": "string",
  "param": "string",
  "retry_after": 0,
  "errors": [
    {
      "field": "string",
      "code": "string",
      "message": "string",
      "title": "string",
      "candidate_connections": [
        {
          "id": "string",
          "label": "string",
          "account_id": "string",
          "owned_by_actor": true,
          "needs_reconnection": true
        }
      ],
      "connection_id": "string",
      "missing_scopes": [
        "string"
      ],
      "owned_by_actor": true,
      "required_scopes": [
        "string"
      ],
      "auth_key": "string",
      "required_auth_key": "string",
      "available_auth_keys": [
        "string"
      ],
      "connect_url": "http://example.com",
      "expiresAt": "2019-08-24T14:15:22Z",
      "packageId": "string"
    }
  ]
}
{
  "type": "http://example.com",
  "title": "string",
  "status": 0,
  "detail": "string",
  "instance": "string",
  "code": "string",
  "request_id": "string",
  "param": "string",
  "retry_after": 0,
  "errors": [
    {
      "field": "string",
      "code": "string",
      "message": "string",
      "title": "string",
      "candidate_connections": [
        {
          "id": "string",
          "label": "string",
          "account_id": "string",
          "owned_by_actor": true,
          "needs_reconnection": true
        }
      ],
      "connection_id": "string",
      "missing_scopes": [
        "string"
      ],
      "owned_by_actor": true,
      "required_scopes": [
        "string"
      ],
      "auth_key": "string",
      "required_auth_key": "string",
      "available_auth_keys": [
        "string"
      ],
      "connect_url": "http://example.com",
      "expiresAt": "2019-08-24T14:15:22Z",
      "packageId": "string"
    }
  ]
}
Empty