Private betaRequest early access →

Sign in

Token introspection and revocation

Access tokens are signed, so a resource server can verify one offline against the JWKS. That is fast, and it cannot see a decision made after the token was minted: a revoked grant, a killed agent, a password change. Introspection is the call that can. Revocation is how a client ends a grant on purpose.

Both are advertised in the discovery document as introspection_endpoint and revocation_endpoint.

Introspection — is this token live now?

POST /api/v1/oauth/introspect, form-encoded: token (and an optional token_type_hint, which is ignored). It answers for access tokens of your own organization.

Who may ask

A resource server of the same organization, holding the authz:check scope — the scope /authz/service-check already asks of a resource server. Authenticate in one of two ways:

WayDetail
A bearer tokenAuthorization: Bearer <token> (or DPoP) where the token is a client_credentials access token whose scope includes authz:check. Its DPoP binding is honoured, and its client must still be enabled.
Your client's credentialsclient_secret_basic or client_secret_post of a confidential client that is allowed the authz:check scope and the client_credentials grant.

Grant the scope by adding authz:check to the client's allowed scopes (Organization → OAuth Clients). A caller that is not authenticated, has no authz:check, or is a public client gets a 401 or 403 and learns nothing about the token. Failed secrets count against the same brake as the token endpoint (429 with Retry-After).

The answer

A token that is live for your organization (aud is zynth-services for a platform token, or your resource identifier for one minted for your API):

JSON
{
  "active": true,
  "scope": "openid email",
  "client_id": "zc_…",
  "sub": "5f1c…",
  "tid": "9a07…",
  "aud": "zynth-services",
  "iss": "zynth-auth",
  "exp": 1790000000,
  "iat": 1789999100,
  "token_type": "Bearer"
}

token_type is DPoP for a sender-constrained token, with cnf: {"jkt": "…"} (RFC 9449 §6.2); it never claims Bearer for a bound token.

iss here is the access token's issuer (zynth-auth), not the OIDC issuer in the discovery document, which belongs to id_tokens. They are different documents; see which iss to expect. Do not feed this value to a verifier configured from discovery.

Everything else is exactly this, and nothing more:

JSON
{ "active": false }

active: false carries no reason, deliberately — the same shape for every token that is not live for your organization:

  • a bad signature, an expired token, a refresh token (a resource server never holds one), garbage;
  • a token whose audience is neither the platform nor a resource registered in the token's organization;
  • a token of another organization;
  • a revoked session, or a person who no longer stands — membership ended, status changed, password changed, sessions revoked everywhere;
  • a disabled or deleted issuing client;
  • an agent that is retired, killed, or whose containment epoch is later than the token.

Liveness is decided by the same checks the platform enforces its own API with, so a token this call calls active is one the platform would honour at that instant. That includes the agent kill switch: a contained agent's token still verifies offline until it expires, and introspection answers active: false at once.

Using it well

  • An active: true answer is true at the instant it was given, not for the token's lifetime. Cache it for seconds, not minutes, and never longer than the token's exp.
  • Rate limits. Introspection has its own per-client allowance, resolved from your plan: free 600, pro 6,000 and enterprise 60,000 requests per 5-minute window, negotiable per organization. It applies whichever way you authenticate (a client_credentials bearer token or the client's secret), so switching between them does not buy more, and introspecting does not spend the client's /token allowance, nor the reverse. Past the allowance you get 429 with Retry-After; the limit fails closed. A resource server that introspects at volume should cache each answer for a few seconds (see above) and verify offline in between.
  • Introspecting every request makes Zynth's availability part of yours. A common pattern: verify offline on every request, introspect on a short timer or before a sensitive action.
  • Do not decode the access token for its claims; take them from the introspection response. Access token claims are not part of the contract.
Shell
curl -s https://<issuer>/api/v1/oauth/introspect \
  -u "$CLIENT_ID:$CLIENT_SECRET" \
  -d token="$ACCESS_TOKEN"

Revocation — a client ends its own grant

POST /api/v1/oauth/revoke, form-encoded: token and an optional token_type_hint (ignored). The client authenticates exactly as at the token endpoint: a confidential client with its secret, a public client by naming its client_id.

You sendWhat happens
A refresh token or a user access token issued to your clientThe grant's session is revoked. Every refresh and access token of that grant stops at once — at the platform API, at /userinfo, and at introspection. 200 with {}.
A client_credentials access token400 unsupported_token_type. There is no session to end, and a 200 would claim a revocation that did not happen. Disable the client to stop that token.
A token that is invalid, expired, unknown, or issued to another client200 with {}, and nothing is done.

The last row is a deliberate difference from the letter of RFC 7009 §2.1, which says another client's token is "refused". Answering it differently from garbage would tell a client that a token it should not hold is real, so every such request gets the same answer.

Revocation ends the tokens, not the person's consent: the application stays under Account → Connected apps until they disconnect it, so a later sign-in that asks for the same scopes does not show the consent screen again.

Which to use

You want to…Use
Stop one signed-in person's session at an applicationRevocation from that application
Know, at a resource server, that a token has been cut offIntrospection
Stop an agent everywhere, immediatelyPOST /api/v1/agents/{agent_id}/kill-switch — introspection then reports its tokens inactive
Stop a client_credentials tokenDisable the client, or let the 15-minute token expire