Skip to content

POST /v1/oauth/token

POST
/v1/oauth/token

OAuth 2.1 token endpoint. Dispatches on grant_type:

  • client_credentials: confidential client exchanges its client_id + client_secret for an access + refresh JWT pair. Existing API keys use this path.
  • authorization_code: exchange a consent-approved code + PKCE verifier for an access JWT + an opaque refresh token. Public DCR clients use this.
  • refresh_token: exchange a refresh for a new pair. Accepts both opaque (public clients; rotating with family revoke-on-reuse) and JWT (confidential clients; stateless).

Error responses follow RFC 6749 §5.2 — {"error": "...", "error_description": "..."} with appropriate HTTP status — so MCP clients like Claude Code can parse them with standard OAuth validators. Form rejections (malformed body, wrong content-type, …) flow through OAuthForm so they emit the same envelope rather than axum’s default 422 + free-form text body.

Wire-level body for /oauth/token. grant_type is intentionally a free- form string — RFC 6749 §1.3 describes it as a URI extension point, so rejecting unknown values at deserialization would conflate “malformed request” (RFC invalid_request) with “we don’t support that grant type” (RFC unsupported_grant_type). The handler dispatches manually and emits the spec-correct error code for each case.

object
client_id
string | null
client_secret
string | null
code

Authorization code (from the redirect after consent).

string | null
code_verifier

PKCE verifier — plaintext string whose S256 hash must match the code_challenge stored at authorize time.

string | null
grant_type

One of client_credentials, authorization_code, refresh_token. Other values are rejected with RFC 6749 §5.2 unsupported_grant_type.

string | null
redirect_uri

Redirect URI presented at authorize time. Must match exactly per RFC 6749 §4.1.3.

string | null
refresh_token
string | null
resource

RFC 8707 resource indicator. For client_credentials, lets a machine client bind its token to a specific MCP resource (e.g. <origin>/mcp) so it satisfies that resource’s audience check at use time. The authorization_code path takes the resource from the authorize request via the stored code row instead, so this field is read only for client_credentials.

string | null

Token pair generated successfully

object
access_token
required
string
expires_in
required
integer format: int64
refresh_token
required
string
scope
string | null
token_type
required
string

Invalid request

Invalid credentials