Exchange an authorization code

Redeem the code from /oauth/authorize for an access token and a refresh token.

POST /oauth/token

Redeems the code /oauth/authorize sent back to your redirect_uri, with the PKCE verifier that answers its challenge.

Authorization

The code and verifier are the credential; no Bearer token. The client authenticates as below.

Client authentication

Every grant on /oauth/token authenticates the client the same way:

ClientHow it authenticates
Publicclient_id in the body, no secret (none).
ConfidentialIts secret, either as Authorization: Basic base64(client_id:client_secret) (client_secret_basic) or as client_id + client_secret in the body (client_secret_post).

A confidential client authenticates on every grant it uses, including authorization_code and refresh_token. If it sends client_id in both the header and the body, the two must agree. If the client has an IP allowlist, requests from other addresses fail with invalid_client.

Body

Form-encoded (application/x-www-form-urlencoded).

FieldTypeRequiredDescription
grant_typestringYesauthorization_code
codestringYesThe code from the redirect. Single-use, and lives for seconds.
code_verifierstringYesThe PKCE verifier matching the challenge sent to /oauth/authorize. Every code from /oauth/authorize carries a challenge.
redirect_uristringYesThe same string the authorization request carried.
client_idstringYesThe client the code was issued to.
client_secretstringNoConfidential clients using client_secret_post.

Response

200, with Cache-Control: no-store.

{
  "token_type": "Bearer",
  "access_token": "eyJ…",
  "expires_in": 3600,
  "refresh_token": "…"
}

No id_token is returned. Read the user’s profile from /users/me. The token’s claims are in Tokens.

Errors

Every refusal is 400 with an RFC 6749 code and nothing else: {"error": "invalid_grant"}.

StatuserrorWhen
400invalid_requestcode is missing, client_id is malformed, redirect_uri is missing when the authorization request carried one, or client_id differs between header and body.
400invalid_grantThe code was rejected: unknown, expired, replayed, wrong verifier, wrong redirect_uri, issued to another client or another org. Deliberately not told apart.
400invalid_clientUnknown client, a wrong secret, or a disallowed IP.
400invalid_requestThe host is not an org.
400unsupported_grant_typegrant_type is not one of the five the endpoint accepts.

Example

curl -X POST https://acme.loginwith.dev/oauth/token \
  -d grant_type=authorization_code \
  -d code=… \
  -d code_verifier=… \
  -d redirect_uri=https://app.acme.com/auth/callback \
  -d client_id=cli_…