> ## Documentation Index
> Fetch the complete documentation index at: https://docs.zapier.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Token exchange

> Exchange a partner-signed JWT for access and connect tokens using POST /oauth/token — patterns for MCP vs Actions/Triggers.

# Token exchange

<Note>
  **White label key concept:** Token exchange is where Zapier uses your JWT’s **user id** and **tenant/workspace id** claims to **just-in-time provision** an **opaque Zapier user and workspace** under your partner account. Your end users don’t log into Zapier directly, and usage is billed to your partner account.
</Note>

All flows below use the same OAuth token endpoint:

```text theme={null}
POST https://zapier.com/oauth/token
```

Use `Content-Type: application/x-www-form-urlencoded` and **keep `client_id` / `client_secret` server-side**.

## Partner-signed JWT

Sign the JWT with **RS256**, **RS384**, **RS512**, **ES256**, **ES384**, or **ES512**. Zapier validates the signature using the public keys from your registered JWKS URL. Use a `kid` in the JWT header that matches your JWKS.

### Example header

```json theme={null}
{
  "alg": "RS256",
  "typ": "JWT",
  "kid": "your-key-id"
}
```

### Example payload

```json theme={null}
{
  "sub": "user_123",
  "org_id": "org_456",
  "iss": "https://accounts.your-app.com",
  "aud": "connect.zapier.com",
  "iat": 1711900000,
  "exp": 1711900300,
  "nbf": 1711900000
}
```

The exact names of the **user id** and **tenant/workspace id** claims are configured during [partner onboarding](./partner-onboarding) (this example uses `sub` and `org_id`; yours might use e.g. `tenant_id`).

### Common claims

For a successful token exchange, Zapier expects **all** of the claims in this table to be present and valid.

| Claim              | Meaning                                                                |
| ------------------ | ---------------------------------------------------------------------- |
| `sub`              | User id in your system                                                 |
| `org_id` (example) | Tenant / workspace / account id (claim name is configured with Zapier) |
| `iss`              | Issuer — must match the value registered with Zapier                   |
| `aud`              | Audience — must be `connect.zapier.com`                                |
| `iat`              | Issued-at (Unix seconds)                                               |
| `exp`              | Expiry — keep **short** (for example max \~5 minutes after `iat`)      |
| `nbf`              | Not-before (typically same as `iat`)                                   |

## Token types

### User access token (Bearer)

A **user access token** is the White Label **API token** your backend uses to call Zapier APIs: list connections, Action Runs, trigger subscriptions, and so on. Treat it as a secret: **never send it to the browser**.

### Connect token (Connect UI / MCP authorize)

A **connect token** is a **short-lived** (6m), **single-use** secret your backend obtains when you need to send the user through a **browser flow** (Connect UI for app connections, or the MCP authorization endpoint). Pass it only to your frontend for **launching that flow** (for example as the `token` query parameter on the Connect URL). **Do not** cache it long-term.

When you request a connect token via `POST /oauth/token`, you must include **`resource`**: an absolute URL that identifies where the token will be used. It must be either **`https://connect.zapier.com/to/{app}`** (same Connect UI URL you open, including host) or **`https://mcp.zapier.com/api/v1/connect/{app}`** (the app’s MCP server URL / authorize `resource`). Zapier binds the token to that URL; ConnectApp sends the same `resource` when exchanging the token for a user session.

<Note>
  **OAuth response shape:** Token responses use the standard OAuth JSON field **`access_token`** for the issued secret — even when that secret is a **connect token**, not a user access token. That is because `POST /oauth/token` serves **multiple grant types and token types** with one response envelope. In your code, read the connect token from `access_token` in the JSON body and name it clearly (for example `connectToken`) in your application.
</Note>

***

## Recommended patterns (by use case)

### A) Actions API / Triggers API (embedded)

Use a **user access token** for all server-side API calls. When you need Connect UI, obtain a **connect token** in a **second** exchange using that user access token.

**1) JWT → user access token**

```bash theme={null}
curl -X POST https://zapier.com/oauth/token \
  -d grant_type=urn:ietf:params:oauth:grant-type:token-exchange \
  -d client_id=<ZAPIER_CLIENT_ID> \
  -d client_secret=<ZAPIER_CLIENT_SECRET> \
  -d subject_token=<YOUR_JWT> \
  -d subject_token_type=urn:ietf:params:oauth:token-type:external-jwt \
  -d requested_token_type=urn:ietf:params:oauth:token-type:access-token \
  -d "scope=connection:read action:run zap zap:write"
```

Example scopes above are illustrative; your onboarding may specify a different `scope` string.

**2) User access token → connect token**

When the user must connect or reconnect an app, exchange the **user access token** for a **connect token**. Set **`resource`** to the Connect UI URL you will open (example uses the production Connect host and implementation id):

```bash theme={null}
curl -X POST https://zapier.com/oauth/token \
  -d grant_type=urn:ietf:params:oauth:grant-type:token-exchange \
  -d client_id=<ZAPIER_CLIENT_ID> \
  -d client_secret=<ZAPIER_CLIENT_SECRET> \
  -d subject_token=<USER_ACCESS_TOKEN> \
  -d subject_token_type=urn:ietf:params:oauth:token-type:access-token \
  -d requested_token_type=urn:ietf:params:oauth:token-type:connect-token \
  -d scope=connection:write \
  -d resource=https://connect.zapier.com/to/<APP_IMPLEMENTATION_ID>
```

<Note>
  Exact parameters for the second hop (including `scope`) can depend on your partner configuration. If something fails, verify the scopes and token types enabled for your OAuth client with Zapier.
</Note>

Use the connect token in `access_token` from the response as the `token` query parameter when opening [Connect UI](./connection-flow).

### B) MCP (AI agent connections)

For MCP, you typically **do not** need a White Label user access token **before** opening the MCP authorization step. Exchange the partner-signed **JWT** directly for a **connect token**, then run the MCP OAuth authorization-code flow; the **MCP access token** you use against the MCP server comes from that flow (see [AI agent connections & automations](./use-cases/ai-agent-connections)).

**JWT → connect token (MCP)**

Use the app’s **`mcp_server_url`** as **`resource`** (the same value you pass as the `resource` query parameter on the MCP authorize URL):

```bash theme={null}
curl -X POST https://zapier.com/oauth/token \
  -d grant_type=urn:ietf:params:oauth:grant-type:token-exchange \
  -d client_id=<ZAPIER_CLIENT_ID> \
  -d client_secret=<ZAPIER_CLIENT_SECRET> \
  -d subject_token=<YOUR_JWT> \
  -d subject_token_type=urn:ietf:params:oauth:token-type:external-jwt \
  -d requested_token_type=urn:ietf:params:oauth:token-type:connect-token \
  -d scope=connection:write \
  -d resource=<MCP_SERVER_URL>
```

Again, the connect token is returned in the JSON field **`access_token`**.

***

## User and workspace provisioning

Zapier maps your JWT claims to a **user** and **workspace** under your partner account. The first time a new combination of configured claim values appears in a token exchange, Zapier **just-in-time** provisions that workspace and user. Later exchanges with the same mapping reuse them.

***

## Output and next steps

* **User access token**: keep server-side only; use as `Authorization: Bearer` for Zapier APIs.
* **Connect token**: pass to the browser only to start Connect UI or the MCP authorize URL. Single-use and short-lived — generate a fresh one each time you start a connection flow.

## Refreshing tokens

When an MCP access token expires (`expires_in` is typically 3600 seconds / 1 hour), use the `refresh_token` to obtain a new one without requiring the user to re-authorize. See [AI agent connections — Refresh the MCP access token](./use-cases/ai-agent-connections#5-refresh-the-mcp-access-token) for the request format.

<Warning>
  Each refresh returns a **new** `refresh_token`. Always store and use the latest one — previous refresh tokens are invalidated.
</Warning>

For error responses during refresh, see [Error handling](./error-handling#token-refresh-post-oauthtoken).

Next: [Connection flow](./connection-flow) for popup vs redirect, reconnect, and callback parameters. For MCP, continue with [AI agent connections & automations](./use-cases/ai-agent-connections).
