AgeOnce Docs
API Reference

Token Validation

Validate an AgeOnce access token

Token Validation

Validate an access_token with the AgeOnce API, or locally with the public key.

Endpoint

GET https://api.ageonce.com/api/verify/token/{access_token}

The token is a path parameter. URL-encode it.

Response

Valid token (200)

{
  "success": true,
  "valid": true,
  "age_verified": true,
  "age_over": 18,
  "verification_level": "first_verification",
  "expires_at": "2026-02-11T12:10:00.000Z",
  "issued_at": "2026-02-11T12:00:00.000Z",
  "session_id": "user-id",
  "client_id": "ap_live_your_client_id",
  "nonce": null,
  "transaction_id": "550e8400-e29b-41d4-a716-446655440000"
}

client_id is the JWT aud claim. transaction_id is the same Audit ID returned by token exchange. Search it in Dashboard Audit Logs.

Invalid or expired token (401)

{
  "success": false,
  "valid": false,
  "message": "Invalid or expired token"
}

Examples

curl "https://api.ageonce.com/api/verify/token/YOUR_ACCESS_TOKEN"
async function validateToken(token) {
  const response = await fetch(
    `https://api.ageonce.com/api/verify/token/${encodeURIComponent(token)}`
  );
  const data = await response.json();

  if (response.ok && data.valid && data.age_verified) {
    console.log('Verified over:', data.age_over);
    return true;
  }

  console.log('Validation failed:', data.message);
  return false;
}
import requests
from urllib.parse import quote

def validate_token(token):
    response = requests.get(
        f"https://api.ageonce.com/api/verify/token/{quote(token, safe='')}"
    )
    data = response.json()

    if response.ok and data.get('valid') and data.get('age_verified'):
        print(f"Verified over: {data['age_over']}")
        return True

    print(f"Validation failed: {data.get('message')}")
    return False
function validateToken($token) {
    $url = 'https://api.ageonce.com/api/verify/token/' . rawurlencode($token);
    $response = file_get_contents($url);
    $data = json_decode($response, true);

    if (!empty($data['valid']) && !empty($data['age_verified'])) {
        echo 'Verified over: ' . $data['age_over'];
        return true;
    }

    echo 'Validation failed: ' . ($data['message'] ?? 'Unknown error');
    return false;
}

API vs local validation

AspectAPI validationLocal validation
SpeedOne HTTP requestFaster after the key is cached
SimplicityRead age_over from JSONVerify the RS256 signature yourself
KeyServer uses the current keyCache JWKS or GET /api/verify/public-key

For high-load checks, validate locally and cache the public key.

Local validation

GET /api/verify/public-key returns the PEM public key. Verify with RS256 and issuer https://ageonce.io. age_over is the verified age threshold.

const jwt = require('jsonwebtoken');

async function validateLocally(token) {
  const keyResponse = await fetch('https://api.ageonce.com/api/verify/public-key');
  const { public_key: publicKey } = await keyResponse.json();

  const payload = jwt.verify(token, publicKey, {
    algorithms: ['RS256'],
    issuer: 'https://ageonce.io',
  });

  return payload.age_verified === true && payload.age_over >= 18;
}

The same key is published as JWKS at GET /api/.well-known/jwks.json. Issued tokens do not include kid, so use that single key rather than looking up a header key id.

On this page