Skip to content

Exchange or refresh a token

POST
/oauth/token
curl --request POST \
--url https://qinto-api.blotout.io/oauth/token \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data grant_type=authorization_code \
--data 'code=qac_9f2c…' \
--data redirect_uri=https://example.com/callback \
--data 'client_id=qc_4b1d…' \
--data 'client_secret=qcs_71aa…' \
--data code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk \
--data 'refresh_token=qor_5e0b…' \
--data scope=team:read \
--data resource=https://qinto-api.blotout.io \
--data 'subject_token=qoa_83fe…' \
--data subject_token_type=urn:ietf:params:oauth:token-type:access_token \
--data requested_token_type=urn:ietf:params:oauth:token-type:access_token

Issues qoa_ access tokens: exchanges a one-time authorization code from the consent flow, rotates a qor_ refresh token into its successor plus a fresh access token, or re-audiences a token for another Qinto resource server (RFC 8693).

How it works

Requests are form-encoded per RFC 6749. Confidential clients authenticate with HTTP Basic or client_secret_post; public clients send their client_id alone. resource is single-valued — every access token is bound to exactly one audience — and a replayed code or reused refresh token revokes the whole grant. A token exchange takes subject_token and subject_token_type, carries the subject’s teams and scope across unchanged, never outlives it, and answers 429 when the session’s exchange budget is exhausted. Responses carry Cache-Control: no-store.

RFC 6749 form-encoded token request.

Media typeapplication/x-www-form-urlencoded
object
grant_type
required

The grant type: authorization_code to redeem a consent code, refresh_token to rotate a refresh token, or urn:ietf:params:oauth:grant-type:token-exchange for the RFC 8693 exchange.

string

Example

authorization_code
code

The one-time qac_ authorization code from the consent redirect. Required for authorization_code.

string

Example

qac_9f2c…
redirect_uri

The exact redirect URI used in the authorization request. Required for authorization_code.

string

Example

https://example.com/callback
client_id

The app’s client id. Required unless the client authenticates with HTTP Basic.

string

Example

qc_4b1d…
client_secret

The app’s client secret for client_secret_post authentication. Public clients omit it.

string

Example

qcs_71aa…
code_verifier

The PKCE code verifier whose S256 digest was sent as the authorization request’s code challenge. Required for authorization_code.

string

Example

dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
refresh_token

The current qor_ refresh token to rotate. Required for refresh_token.

string

Example

qor_5e0b…
scope

Optional narrowing scope for refresh_token: a space-delimited subset of the grant’s scope.

string

Example

team:read
resource

Optional RFC 8707 resource the token is minted for. Single-valued: every access token is bound to exactly one audience.

string

Example

https://qinto-api.blotout.io
subject_token

The qoa_ access token to re-audience. Required for urn:ietf:params:oauth:grant-type:token-exchange.

string

Example

qoa_83fe…
subject_token_type

The type of subject_token, required by RFC 8693 §2.1 for the exchange. Only urn:ietf:params:oauth:token-type:access_token is accepted.

string

Example

urn:ietf:params:oauth:token-type:access_token
requested_token_type

The type of token to issue. Optional; only urn:ietf:params:oauth:token-type:access_token is accepted.

string

Example

urn:ietf:params:oauth:token-type:access_token

The issued tokens.

Media typeapplication/json
object
access_token
required

The issued qoa_ bearer token, valid for expires_in seconds at exactly one resource server.

string
token_type
required

Always bearer.

string
Allowed values: bearer
expires_in
required

Access-token lifetime in seconds.

number
refresh_token

The new qor_ refresh token. Present when the grant carries offline_access; each use rotates it, so store this one and discard its predecessor.

string
scope
required

The scope actually granted (RFC 6749 §5.1), which may be narrower than requested.

string
issued_token_type

The type of the issued token. Present on a token exchange, where RFC 8693 §2.2.1 requires it.

string

Example

{
"access_token": "qoa_83fe…",
"token_type": "bearer",
"expires_in": 3600,
"refresh_token": "qor_5e0b…",
"scope": "team:read offline_access",
"issued_token_type": "urn:ietf:params:oauth:token-type:access_token"
}

The request failed: invalid_request, invalid_grant, unsupported_grant_type, invalid_scope, or invalid_target.

Media typeapplication/json
object
error
required

The RFC 6749 / RFC 8707 error code.

string
Allowed values: invalid_request invalid_client invalid_grant unsupported_grant_type invalid_scope invalid_target
error_description

A human-readable explanation of the failure, safe to log.

string

Example

{
"error": "invalid_request",
"error_description": "The authorization code has expired."
}

invalid_client — client authentication failed.

Media typeapplication/json
object
error
required

The RFC 6749 / RFC 8707 error code.

string
Allowed values: invalid_request invalid_client invalid_grant unsupported_grant_type invalid_scope invalid_target
error_description

A human-readable explanation of the failure, safe to log.

string

Example

{
"error": "invalid_request",
"error_description": "The authorization code has expired."
}

The token-exchange budget for this session is exhausted; retry shortly.

Media typeapplication/json
object
error
required

The RFC 6749 / RFC 8707 error code.

string
Allowed values: invalid_request invalid_client invalid_grant unsupported_grant_type invalid_scope invalid_target
error_description

A human-readable explanation of the failure, safe to log.

string

Example

{
"error": "invalid_request",
"error_description": "The authorization code has expired."
}