Token Introspection

Validating an access token locally confirms that Descope issued it and that it hasn't expired. The claims inside it, such as roles, permissions, tenant, and custom attributes, reflect what was true when the token was issued. If an admin removes a role or revokes access afterward, a locally validated token keeps working until it expires.

Token introspection closes that gap. Instead of trusting the claims in the token, your server asks Descope about the token on each request: whether it's still active, and what the user's current roles and permissions are. You get live authorization data and revocations that take effect immediately, at the cost of a network call to Descope on every request you introspect.

For local JWT validation, see Session Validation.

When to Introspect

Introspection is optional, and most APIs and MCP servers don't need it. Validate tokens locally when:

  • You run high-throughput microservices or internet-facing endpoints where every millisecond of latency matters
  • You can accept role, permission, and revocation changes taking effect when the user's access token next expires and refreshes

Introspect when:

  • You handle highly sensitive data, such as banking or healthcare records, and can't tolerate the 5 to 10 minutes it can take for a short-lived access token to expire after access is revoked
  • Roles, permissions, or tenant assignments can change mid-session, and the change must apply on the next request
  • You need custom attributes or JWT template output that isn't in the token

If you need fresher data than the token carries but can tolerate a short lag, cache introspection responses for a short TTL. That keeps most requests local while bounding how stale the authorization data can get.

The active tenant (dct) is fixed for the life of a token, so introspection won't help with switching tenants. To switch, the user disconnects and signs in again with a different organization, which issues a new token. See Users in More Than One Tenant for more details.

Note

The Management SDK or API can also read a user's current state, but it requires a management key, which is a privileged server credential. The endpoints on this page authenticate with the access token the client already sent, or with the app's own client credentials, which is why MCP servers and third-party APIs prefer them.

Choosing an Endpoint

Descope offers two ways to introspect a token:

  • The /introspect endpoint (RFC 7662) accepts Federated Application tokens only. Your resource server authenticates with its own client credentials and gets back whether the token is active, plus the user's current roles and permissions
  • The /userinfo endpoint works for every app type, including Inbound Apps and Agentic Clients. Your server sends the user's access token, and Descope returns the user's current claims, or an error if the token is no longer valid

Federated Applications and Inbound Apps use different routes, and the routes aren't interchangeable. Send each app type's access token to its matching endpoint:

App typeEndpointIssued byAPI reference
Federated Application (OIDC){baseUrl}/oauth2/v1/introspect or {baseUrl}/oauth2/v1/userinfoStandard OIDC authorization code, client credentials, or device flows against a Federated AppOIDC endpoints
Inbound App{baseUrl}/oauth2/v1/apps/userinfoInbound App /oauth2/v1/apps/token flowsGet UserInfo, Post UserInfo
Inbound App (project-scoped){baseUrl}/oauth2/v1/apps/{projectId}/userinfoSame Inbound App tokens; path advertised in some MCP discovery documentsSame behavior as /oauth2/v1/apps/userinfo

Replace {baseUrl} with your project base URL, for example https://api.descope.com, a regional host, or a custom domain.

Note

An Inbound App access token won't work against /oauth2/v1/userinfo, and a Federated Application access token won't work against /oauth2/v1/apps/userinfo. If you're unsure which type you have, check the app's discovery document or the token's issuer.

Federated Application /introspect Endpoint

Use the RFC 7662 introspection endpoint when a resource server needs to ask Descope whether a Federated Application token is still active. The resource server authenticates with its own client credentials, not the user's token.

POST {baseUrl}/oauth2/v1/introspect
Content-Type: application/x-www-form-urlencoded
Authorization: Basic <base64(client_id:client_secret)>

token=<access_token>
curl -X POST "__BaseURL__/oauth2/v1/introspect" \
  -u "<CLIENT_ID>:<CLIENT_SECRET>" \
  -d "token=<ACCESS_TOKEN>"

To target a specific SSO application, use POST {baseUrl}/{ssoAppId}/oauth2/v1/introspect.

Authentication

The endpoint requires client authentication, so public clients can't call it. Send the app's client ID and secret with HTTP Basic (client_secret_basic) or in the request body (client_secret_post). Descope identifies the project and app from the caller's client_id, not from the token being introspected.

Response

An inactive, expired, revoked, or unknown token returns HTTP 200 with {"active": false}, not an error, so check the active field rather than the status code.

An active token returns the standard claims (active, scope, client_id, sub, aud, iss, exp, iat, nbf, jti, token_type) plus the user's current roles and permissions. Descope loads the roles and permissions the same way as /userinfo, so your resource server authorizes against live data rather than the claims from when the token was issued.

Discovery

The Federated App's discovery document advertises introspection_endpoint and introspection_endpoint_auth_methods_supported (RFC 8414), so standards-based resource servers and gateways can find the endpoint automatically.

Note

/introspect currently accepts Federated Application tokens only. For Inbound App tokens, including tokens issued to MCP clients and Agentic Clients, use the Inbound App /userinfo endpoint.

Federated Application /userinfo Endpoint

Federated Applications also support the standard OIDC UserInfo endpoint, which authenticates with the user's access token instead of the app's client credentials:

GET {baseUrl}/oauth2/v1/userinfo
Authorization: Bearer <access_token>
curl -X GET "__BaseURL__/oauth2/v1/userinfo" \
  -H "Authorization: Bearer <ACCESS_TOKEN>"

The response includes standard OIDC profile claims, plus custom claims and tenant roles and permissions depending on the requested scopes. See Using OIDC Endpoints for the full flow.

Inbound App /userinfo Endpoint

Inbound Apps don't have an /introspect endpoint, so the UserInfo endpoint under the /apps path is how you introspect their tokens. It supports both GET and POST.

GET {baseUrl}/oauth2/v1/apps/userinfo
Authorization: Bearer <access_token>
curl -X GET "__BaseURL__/oauth2/v1/apps/userinfo" \
  -H "Authorization: Bearer <ACCESS_TOKEN>"

A successful response means Descope still accepts the token for that app, and the returned claims are safe to use for authorization. Any other response means the token is invalid or revoked, so reject the request.

The claims reflect the user's current profile as Descope resolves it for that token, following your JWT template and the granted scopes.

Some clients, including MCP discovery metadata, advertise the project-scoped variant {baseUrl}/oauth2/v1/apps/{projectId}/userinfo. It accepts the same Inbound App access tokens.

Introspection at a Gateway

A gateway that only checks signatures, such as a JWT authorizer, validates tokens at the edge but doesn't return live claims. If the services behind the gateway need current authorization data, call /userinfo, or /introspect for Federated App tokens, from the gateway or the service. Otherwise, validate locally and accept that claims can lag until the token expires.

Was this helpful?

On this page