Token Exchange

Token exchange (RFC 8693) lets a client trade a token it already holds, the subject token, for a new token scoped to a downstream Resource. Inbound Apps and Agentic Clients use it to narrow or retarget a credential without sending the user through login again. Credentials stored in Connections come from a different endpoint, covered in Fetching a Connection Credential.

Token exchange works differently from the authentication grants, which decide how a client gets its first token and are turned on per app. Token exchange is always available at the project's token endpoint, with no per-app toggle and no consent screen. A policy with the Delegated access (token exchange) grant type is the only thing that decides whether an exchange succeeds.

How It Works

  1. The client gets a subject token with an authentication grant, such as authorization code, client credentials, or JWT bearer.
  2. The client calls /token again with grant_type=urn:ietf:params:oauth:grant-type:token-exchange, naming the target Resource and, optionally, the scopes it wants.
  3. Descope evaluates policies. If a rule allows Delegated access (token exchange) for that client, target, and scopes, Descope returns the new token. Otherwise it denies the request and logs a policy violation.

Configuring the Policy

On the Policies page, create or update a policy with:

  • Subjects: The Inbound App or Agentic Client that may perform the exchange, or conditions on client tags, tenant, and so on
  • Targets: The Resource or Connection, and the scopes this policy grants
  • Grant types: Delegated access (token exchange)

The new token only carries scopes that the policy allows, the target defines, and the client requested. See Policies → Grant Types for the full model and examples such as tenant-scoped delegated access.

Exchanging for a Resource Token

Use this when a client holds a Descope access token and needs a separate token for your API or MCP server, with a different aud or scope.

OAuth clientapp or agentDescopetoken endpointResource AAPI or MCP serverResource BAPI or MCP server1. authenticate (any grant)2. token A (aud = Resource A)3. call with token Acheck aud = Resource Atoken exchange4. exchange token A for Resource Bpolicy: Delegated access5. token B (aud = Resource B)6. call with token Bcheck aud = Resource B

Select any step to see what happens. The client keeps token A, and each Resource accepts only tokens issued for its own audience.

Send a POST to the token endpoint:

ParameterDescription
grant_typeurn:ietf:params:oauth:grant-type:token-exchange
subject_tokenThe access token (or other token type) being exchanged
subject_token_typeUsually urn:ietf:params:oauth:token-type:access_token
resourceTarget Resource URI (RFC 8707)
scopeOptional subset of scopes; must be allowed by policy
Client authenticationclient_id / client_secret, or other method configured on the app
curl -X POST "__BaseURL__/oauth2/v1/apps/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \
  -d "client_id=<CLIENT_ID>" \
  -d "client_secret=<CLIENT_SECRET>" \
  -d "subject_token=<SUBJECT_TOKEN>" \
  -d "subject_token_type=urn:ietf:params:oauth:token-type:access_token" \
  -d "resource=https://your-api.example.com" \
  -d "scope=orders.read"

The new token still represents the same user and tenant as the subject token. Your Resource validates it (iss, aud, exp, scope) like any other token. See Developing APIs and Using a Resource Token for how to validate it.

The aud check is what keeps each hop separate. If your Resource rejects any token whose aud isn't its own URI, a token issued for another service can't be replayed against it, and a caller can't skip a gateway in front of it by presenting the token it was given for that gateway.

Fetching a Connection Credential

Credentials from the Connections vault don't come from the token endpoint. When an agent, MCP server, or gateway needs to call a third-party API, it presents its Descope access token to the Connection token endpoints instead. Descope still checks a policy with the Delegated access (token exchange) grant type, with the Connection as the target, before it returns the stored OAuth token or API key. See Downstream credential access for the request.

When to Use It

ScenarioSee also
A user signed in once, and the client needs a scoped token for one API or MCP serverResources, MCP server
A gateway exchanges an agent's token for a token scoped to one of the Resources behind itGateways
An autonomous agent with an M2M token needs a narrower Resource tokenClient credentials
An agent needs an XAA token (ID-JAG) for a third-party resource serverHow XAA Works

Troubleshooting

When no policy permits the requested subject, target, or scopes, Descope returns no token and writes a Warning audit event with the client, target, requested scopes, and evaluation outcome. The Agentic Activity Dashboard rolls these up. Common fixes:

  • Add or update a policy with Delegated access (token exchange) for the client and the Resource or Connection
  • Confirm the Resource or Connection defines the scopes you're requesting
  • Make sure the subject token is still valid and was issued to the same client, or satisfies your policy's subject conditions

For the endpoint and parameter reference, see Authorization Server. For end-to-end request examples, see Using Inbound Apps.

Was this helpful?

On this page