Relay
Call third-party APIs on behalf of your users without handling their access tokens.
Relay calls third-party APIs on your users’ behalf without exposing their credentials to your application. Send the request to WorkOS, and relay attaches the user’s credential server-side and streams the provider’s response back.
Relay is in early access. Contact support via email or Slack to request access for your environment.
Relay 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 relay, the token never leaves WorkOS: the agent authenticates with your WorkOS API key and names the user it acts for.
Relay 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-Relay-Userheader identifies the user the request acts on behalf of, scoped byX-Relay-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. Relay 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-Relay-Upstream-Statusheader.
Relay 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/relay, 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 relay base URL, and move the original URL into the
X-Relay-URLheader. - Replace the provider token with your WorkOS API key in
Authorization. - Name the user in
X-Relay-User, addingX-Relay-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-Relay-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-Relay-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-Relay-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-Relay-URL. The provider is resolved from the
URL’s host, so your code keeps working with the provider’s real URLs.
Alternatively, prefix the provider’s API path with the relay base URL and
the provider’s slug. The slug in the path selects the provider, and the path and
query string are appended to the provider’s default API host, so
/relay/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-Relay-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
relay_authorization_required response as a user who never connected at all.
If a connected user keeps getting a 402, check that the
X-Relay-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-Relay-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 relay 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 relay_upstream_error. |
| HTTPS only | X-Relay-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-Relay- 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. |
Relay 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 relay are JSON objects with code and message
fields. Provider errors are passed through unchanged, so check for
X-Relay-Upstream-Status to tell the two apart.
| Status | Code | Meaning |
|---|---|---|
401 |
– | Missing or invalid WorkOS API key. |
400 |
relay_missing_url |
URL routing was used but the X-Relay-URL header is absent. |
400 |
relay_invalid_url |
X-Relay-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 |
relay_user_required |
The X-Relay-User header is absent. |
400 |
relay_user_not_found |
No user with that ID exists in the API key’s environment. |
402 |
relay_authorization_required |
The user has no usable connection for this provider, or the organization scope doesn’t match. Includes an authorization_url. |
404 |
relay_provider_not_found |
The slug or the X-Relay-URL host doesn’t match a supported provider, or the provider isn’t enabled in this environment. |
404 |
– | A generic 404 with no code field means relay isn’t enabled for this environment. |
502 |
relay_credential_error |
The user’s credential couldn’t be resolved, for a reason that re-authorization won’t fix. |
502 |
relay_upstream_error |
The provider was unreachable or timed out. |