API Reference
Token Exchange
Exchange authorization code for age token
Token Exchange
Exchange authorization code for age token.
Endpoint
POST https://app.ageonce.com/api/oauth/tokenRequest
Headers
Content-Type: application/jsonBody
{
"client_id": "cl_your_client_id",
"client_secret": "cs_your_client_secret",
"code": "authorization_code_from_callback",
"redirect_uri": "https://example.com/callback"
}Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
client_id | string | Yes | Your Client ID |
client_secret | string | Yes | Your Client Secret |
code | string | Yes | Authorization code from callback |
redirect_uri | string | Yes | Same redirect_uri used during authorization |
Response
Success (200)
{
"age_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImFnZW9uY2UtcHVibGljLWtleSJ9...",
"token_type": "Bearer",
"expires_in": 600,
"transaction_id": "550e8400-e29b-41d4-a716-446655440000"
}| Field | Type | Description |
|---|---|---|
age_token | string | JWT token with age information |
token_type | string | Always "Bearer" |
expires_in | number | Token lifetime in seconds |
transaction_id | string | Unique Audit ID for this verification (same value as verification_id in the JWT). We recommend storing it (e.g. in your order or database) if you need to track when and how access was granted — you can then search by this ID in Dashboard Audit Logs to see verification details. |
Audit: Save transaction_id when you grant access (e.g. after token exchange). Later you can look up the verification in the Dashboard to see when and how it was completed.
Error (400/401)
{
"error": "invalid_grant",
"error_description": "Authorization code has expired"
}| Error | Description |
|---|---|
invalid_request | Missing required parameters |
invalid_client | Invalid client_id or client_secret |
invalid_grant | Invalid or expired code |
unauthorized_client | redirect_uri does not match |
Examples
curl -X POST https://app.ageonce.com/api/oauth/token \
-H "Content-Type: application/json" \
-d '{
"client_id": "cl_abc123",
"client_secret": "cs_secret456",
"code": "auth_code_xyz",
"redirect_uri": "https://example.com/callback"
}'const response = await fetch('https://app.ageonce.com/api/oauth/token', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
client_id: process.env.AGEONCE_CLIENT_ID,
client_secret: process.env.AGEONCE_CLIENT_SECRET,
code: authorizationCode,
redirect_uri: process.env.AGEONCE_REDIRECT_URI,
}),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error_description);
}
const { age_token, expires_in, transaction_id } = await response.json();import requests
response = requests.post(
'https://app.ageonce.com/api/oauth/token',
json={
'client_id': os.environ['AGEONCE_CLIENT_ID'],
'client_secret': os.environ['AGEONCE_CLIENT_SECRET'],
'code': authorization_code,
'redirect_uri': os.environ['AGEONCE_REDIRECT_URI'],
}
)
if response.status_code != 200:
error = response.json()
raise Exception(error.get('error_description'))
data = response.json()
age_token = data['age_token']
transaction_id = data.get('transaction_id') # Audit ID for compliance$response = file_get_contents('https://app.ageonce.com/api/oauth/token', false, stream_context_create([
'http' => [
'method' => 'POST',
'header' => 'Content-Type: application/json',
'content' => json_encode([
'client_id' => getenv('AGEONCE_CLIENT_ID'),
'client_secret' => getenv('AGEONCE_CLIENT_SECRET'),
'code' => $authorizationCode,
'redirect_uri' => getenv('AGEONCE_REDIRECT_URI'),
]),
],
]));
$data = json_decode($response, true);
if (isset($data['error'])) {
throw new Exception($data['error_description']);
}
$ageToken = $data['age_token'];
$transactionId = $data['transaction_id'] ?? null; // Audit ID for complianceAge Token Structure
JWT token contains the following claims:
{
"sub": "anonymous",
"age_verified": true,
"min_age": 18,
"age_over": 18,
"verification_id": "550e8400-e29b-41d4-a716-446655440000",
"verified_at": "2026-02-11T12:00:00Z",
"client_id": "cl_abc123",
"iat": 1739275200,
"exp": 1739361600,
"iss": "ageonce"
}| Claim | Type | Description |
|---|---|---|
sub | string | Always "anonymous" (privacy) |
age_verified | boolean | Whether age is verified |
min_age | number | Minimum verified age (alias for age_over) |
age_over | number | Age threshold verified (e.g. 16, 18, 21) |
verification_id | string | Transaction ID — same as transaction_id from token exchange. Use for audit trail and Dashboard Audit Logs. |
verified_at | string | ISO 8601 verification date |
client_id | string | Client ID |
iat | number | Unix timestamp of creation |
exp | number | Unix timestamp of expiration |
iss | string | Always "ageonce" |
Token is signed with RS256 algorithm. Public key is available via JWKS endpoint.
Limitations
- Authorization code can be used only once
- Code is valid for 1 minute from creation
- redirect_uri must exactly match the one used during authorization