Token proxy
Call third-party APIs on behalf of your users without handling their access tokens.
The token proxy calls third-party APIs on your users’ behalf without exposing their credentials to your application. Send the request to WorkOS, and the token proxy attaches the user’s credential server-side and streams the provider’s response back.
The token proxy is in early access. Contact support via email or Slack to request access for your environment.
The token proxy is a forward HTTP proxy built for AI agents and other untrusted runtimes. With access tokens, your infrastructure fetches a provider access token and calls the provider directly, so the token exists inside the calling environment, where it can be copied, logged, or leaked. With the token proxy, the token never leaves WorkOS: the agent authenticates with your WorkOS API key and names the user it acts for.
The token proxy removes provider tokens from the calling environment, not the WorkOS API key. That key authenticates every WorkOS API call for the environment, so treat it as a secret even in a sandbox: inject it at request time rather than baking it into agent-visible code or prompts, and rotate it from the API Keys section of the dashboard if a runtime is compromised.
Every proxied request follows the same lifecycle:
- Authenticate. Your WorkOS API key is verified and determines the environment. The key is stripped from the request and is never sent upstream.
- Resolve the user. The
X-Token-Proxy-Userheader identifies the user the request acts on behalf of, scoped byX-Token-Proxy-Organizationwhen the connection belongs to an organization. - Look up the connected account. WorkOS finds the user’s Pipes connection
for the provider you named. If the user hasn’t connected, or needs to
re-authorize, the response is a
402with anauthorization_urlto send them to. - Fetch the credential. The user’s access token is retrieved from the Pipes credential store, and refreshed automatically if it has expired.
- Inject and forward. Proxy control headers, cookies, and forwarding
metadata are stripped, the provider credential is injected into the
Authorizationheader, and the request is forwarded to the provider over HTTPS. - Stream the response. The provider’s status, headers, and body are streamed
back verbatim, with an added
X-Token-Proxy-Upstream-Statusheader.
The token proxy builds on your existing Pipes setup. You need:
- A WorkOS API key for the environment, from the API Keys section of the WorkOS Dashboard.
- The target provider enabled for your environment, configured in the Pipes section of the dashboard or through the data integration API.
- A connected account for the user, created when the user authorizes the provider through the Pipes widget or an authorization URL from the get authorization URL endpoint.
The base URL is https://api.workos.com/token-proxy, and any HTTP method is
accepted. To convert a direct provider call into a proxied one:
- Keep the method, body, and content headers as they are.
- Send the request to the token proxy base URL, and move the original URL into
the
X-Token-Proxy-URLheader. - Replace the provider token with your WorkOS API key in
Authorization. - Name the provider in
X-Token-Proxy-Providerand the user inX-Token-Proxy-User, addingX-Token-Proxy-Organizationfor organization-scoped connections.
| Header | Required | Description |
|---|---|---|
Authorization |
Yes | Bearer followed by your WorkOS API key. Determines the environment. Never forwarded to the provider. |
X-Token-Proxy-User |
Yes | The ID of the user the request acts on behalf of, for example user_01EHZ…. Must belong to the API key’s environment. |
X-Token-Proxy-Organization |
Conditional | The organization the connection was authorized under, for example org_01KV9…. Required when the connection is organization-scoped, and must be omitted when it isn’t. |
X-Token-Proxy-Provider |
URL routing only | The slug of the provider to call, for example github. Named explicitly rather than inferred, since one API host can back several providers. |
X-Token-Proxy-URL |
URL routing only | The full upstream URL, including path and query string. Must use HTTPS. |
All other request headers, including Content-Type, Accept, and
provider-specific headers, are forwarded unchanged. See
limits and behavior for the headers that are stripped.
Put the complete upstream URL in X-Token-Proxy-URL and the provider’s slug in
X-Token-Proxy-Provider. Your code keeps working with the provider’s real URLs,
and naming the provider keeps the target unambiguous when several providers share
an API host, as Gmail, Google Calendar, and generic Google access all do on
www.googleapis.com.
Alternatively, prefix the provider’s API path with the token proxy base URL and
the provider’s slug. The slug in the path selects the provider, so no
X-Token-Proxy-Provider header is needed. The path and query string are appended
to the provider’s default API host, so /token-proxy/github/user proxies to
https://api.github.com/user.
URL routing is the better default: it can reach any of a provider’s allowed hosts rather than only the default one, and it keeps the provider’s own URLs intact in your code and logs.
Pipes connections are either scoped to an organization or user-only, and the connection lookup requires an exact match on that scope:
- If the user authorized the provider under an organization, requests must
include
X-Token-Proxy-Organizationwith that organization’s ID. - If the user authorized the provider without an organization, the header must be omitted.
A scope mismatch in either direction returns the same 402
token_proxy_authorization_required response as a user who never connected at all.
If a connected user keeps getting a 402, check that the
X-Token-Proxy-Organization header matches the organization on their connected
account before asking them to re-authorize.
When the user has no usable connection for the provider – they never connected,
their grant was revoked, or the token can no longer be refreshed – the response is
402 Payment Required:
Surface the authorization_url to the user, let them complete the provider’s
OAuth flow, then retry the original request.
authorization_url is null when WorkOS can’t build an authorization URL for
the provider, which usually means the provider’s Pipes configuration is
incomplete. Fall back to your own connection flow, such as the
Pipes widget, instead of assuming the field is present.
Authorization-required responses use 402, not 401 or 403, so they can’t
be confused with a WorkOS authentication failure (401) or a
provider-returned 401 or 403 passed through from upstream.
On success you receive the provider’s status code, headers, and body exactly as
the provider sent them, streamed as they arrive. Provider errors are passed
through untouched, so a GitHub 404 comes back as a 404 with GitHub’s own
error body.
Every proxied response carries an X-Token-Proxy-Upstream-Status header with the
provider’s status code. Its presence means the request reached the provider, so
you can distinguish an upstream 404 from a token proxy 404 such as an unknown
provider.
| Behavior | Detail |
|---|---|
| Request body | Forwarded byte-for-byte, up to 5 MB. GET and HEAD requests send no body. |
| Upstream timeout | 30 seconds. A provider that doesn’t respond in time yields 502 token_proxy_upstream_error. |
| HTTPS only | X-Token-Proxy-URL must use HTTPS. Credentials are never injected into a plaintext request. |
| Host allowlist | Requests can only target a supported provider’s allowed hosts. Anything else is rejected before the request is sent. |
| Redirects | Not followed. Redirect responses are returned as-is. |
| Stripped request headers | Hop-by-hop headers such as Connection and Transfer-Encoding, Cookie, forwarding metadata such as X-Forwarded-*, Via, and X-Real-IP, your Authorization header, and anything prefixed X-Token-Proxy- or X-WorkOS-. |
| Stripped response headers | Hop-by-hop headers and Set-Cookie. Compressed bodies are decoded in transit, so Content-Encoding and Content-Length may be removed. |
The token proxy supports the following providers. With path routing, the request goes to the provider’s base API host. With URL routing, any listed host is allowed. The provider must also be enabled for your environment.
Each provider’s API domains are part of its WorkOS configuration, so this list grows as providers are added without any change on your side.
| Provider | Slug | Allowed hosts |
|---|---|---|
| Asana | asana |
app.asana.com |
| Attio | attio |
api.attio.com |
| Box | box |
api.box.com |
| Calendly | calendly |
api.calendly.com |
| Datadog | datadog |
api.datadoghq.com |
| Dropbox | dropbox |
api.dropboxapi.com |
| Front | front |
api2.frontapp.com |
| GitHub | github |
api.github.com, github.com |
| GitLab | gitlab |
gitlab.com |
| Gong | gong |
api.gong.io |
google |
www.googleapis.com |
|
| Help Scout | helpscout |
api.helpscout.net |
| HubSpot | hubspot |
api.hubapi.com |
| Intercom | intercom |
api.intercom.io |
| Jira | jira |
api.atlassian.com |
| Linear | linear |
api.linear.app |
| Microsoft | microsoft |
graph.microsoft.com |
| Notion | notion |
api.notion.com |
| npm | npm |
registry.npmjs.org |
| QuickBooks | quickbooks |
quickbooks.api.intuit.com |
| Salesforce | salesforce |
login.salesforce.com |
| Sentry | sentry |
sentry.io |
| Slack | slack |
slack.com, api.slack.com |
| Stripe | stripe |
api.stripe.com |
| Xero | xero |
api.xero.com |
| Zendesk | zendesk |
api.zendesk.com |
| Zoom | zoom |
api.zoom.us |
Errors generated by the token proxy are JSON objects with code and message
fields. Provider errors are passed through unchanged, so check for
X-Token-Proxy-Upstream-Status to tell the two apart.
| Status | Code | Meaning |
|---|---|---|
401 |
– | Missing or invalid WorkOS API key. |
400 |
token_proxy_missing_url |
URL routing was used but the X-Token-Proxy-URL header is absent. |
400 |
token_proxy_provider_required |
URL routing was used but the X-Token-Proxy-Provider header is absent. |
400 |
token_proxy_invalid_url |
X-Token-Proxy-URL isn’t a valid URL or doesn’t use HTTPS, or the resolved target host isn’t one of the provider’s allowed hosts. |
400 |
token_proxy_user_required |
The X-Token-Proxy-User header is absent. |
400 |
token_proxy_user_not_found |
No user with that ID exists in the API key’s environment. |
402 |
token_proxy_authorization_required |
The user has no usable connection for this provider, or the organization scope doesn’t match. Includes an authorization_url. |
404 |
token_proxy_provider_not_found |
The slug doesn’t match a supported provider, or the provider isn’t enabled in this environment. |
404 |
– | A generic 404 with no code field means the token proxy isn’t enabled for this environment. |
502 |
token_proxy_credential_error |
The user’s credential couldn’t be resolved, for a reason that re-authorization won’t fix. |
502 |
token_proxy_upstream_error |
The provider was unreachable or timed out. |