---
title: "Exchange an authorization code or a refresh token for an access token"
method: POST
path: "/oauth/agent/token"
tags: ["Agents"]
---

# Exchange an authorization code or a refresh token for an access token

`POST /oauth/agent/token`

## Request

- Base URL: `https://api.customdomain.ai`
- URL: `https://api.customdomain.ai/oauth/agent/token`
- Auth: none required

`grant_type=authorization_code` (with the PKCE `code_verifier`) or `grant_type=refresh_token`. The access token is an ES256 JWT that lives 15 minutes and is verified locally against `/.well-known/jwks.json`; it carries the tenant, the application the grant is bound to, the grant, the scopes and `aud`. A refresh token rotates: every refresh returns a new one and retires the old, and presenting a retired one again revokes the whole grant (a client racing itself within ten seconds is only refused). Errors use the RFC 6749 envelope.

## Request body

- AgentTokenRequest
  - `grant_type` 'authorization_code' | 'refresh_token', required
  - `client_id` string, required
  - `code` string: With `authorization_code`: the single use code from the redirect. It lives 5 minutes.
  - `code_verifier` string: With `authorization_code`: the PKCE verifier for the `code_challenge` sent to authorize.
  - `redirect_uri` string, uri: With `authorization_code`: exactly the `redirect_uri` sent to authorize.
  - `refresh_token` string: With `refresh_token`: the latest refresh token. It is good for one exchange.
  - `resource` union: RFC 8707. The resource the token is for. With `authorization_code` it must be the one the person approved; with `refresh_token` it picks the audience of the new access token (the MCP endpoint when omitted). In a form body repeat the field; in JSON send a string or a list.
    - string, uri
    - string[]

## Response `200`

The tokens

- AgentTokenResponse
  - `access_token` string, required: An ES256 JWT. Verify it against `/.well-known/jwks.json`.
  - `token_type` 'Bearer', required
  - `expires_in` integer, required: Seconds the access token lives (900).
  - `refresh_token` string: A new refresh token. The one just used no longer works.
  - `scope` string: The scopes the grant carries, separated by spaces.

## Other responses

- `400`: `invalid_request`, `invalid_grant` (a code or refresh token that is invalid, expired, spent or revoked, a failed PKCE check, a grant that is no longer active), `invalid_target` (a resource that is not the one approved) or `unsupported_grant_type`.
- `503`: Agent access is not enabled on this deployment (`temporarily_unavailable`).

---

[API](https://skmtc.dev/customdomain/apis/customdomain-control-plane-api.md) · [All operations](https://skmtc.dev/customdomain/apis/customdomain-control-plane-api/llms.txt) · [OpenAPI document](https://skmtc.dev/customdomain/apis/customdomain-control-plane-api/revisions/34d89cb7a7cd?raw)
