Guides Integration

Exchange Code for Tokens

The authorization code is single-use and short-lived. Exchange it from your server for an ID token proving authentication and an access token you use to fetch the age result from /userinfo

POSThttps://app.agewallet.io/user/token

Parameters are sent in the request body as form fields, with Content-Type: application/x-www-form-urlencoded. Sending JSON returns unsupported_grant_type.

ParameterRequiredPurpose
grant_typeYesAlways authorization_code.
codeYesThe one-time code from the callback.
redirect_uriYesYour callback URL. Must match the /authorize request exactly.
client_idYesYour registered client ID.
client_secretConfidential clientsYour client secret. Omit for public clients, which rely on PKCE instead.
code_verifierYesThe original, unhashed verifier you generated before /authorize.

Success Response

FieldWhat is it for
id_tokenProves the user authenticated. Validate it, but don’t read the age status from it.
access_tokenBearer token for /userinfo. This is the call that returns the age result.
token_typeAlways Bearer.
expires_inLifetime of the access token in seconds. 900 is 15 minutes.

No refresh token is issued. A verification is a one-time event: if you need a fresh result, start a new authorization request.

When the exchange fails

Failures return HTTP 400 with an error field (invalid_client is returned as HTTP 401). invalid_grant covers most of them and is worth checking against all four causes before opening a ticket.

ErrorUsual cause
invalid_grantThe code expired, was already used, the redirect_uri does not match the /authorize request, or the code_verifier does not match the challenge you sent.
invalid_clientWrong client_id, or a missing or incorrect client_secret.
invalid_requestA redirect_uri or code_verifier is missing.
unsupported_grant_typegrant_type is not authorization_code — including when the body is sent as JSON rather than form-encoded, which makes all parameters unreadable.

Last updated August 6, 2026

Was this page helpful?