AgeOnce Docs
API Reference

Token Validation

Age token validation via API

Token Validation

Age token validation via AgeOnce API.

Endpoint

POST https://app.ageonce.com/api/oauth/validate

Request

Headers

Content-Type: application/json

Body

{
  "token": "eyJhbGciOiJSUzI1NiIs..."
}

Parameters

ParameterTypeRequiredDescription
tokenstringYesAge token to validate

Response

Valid token (200)

{
  "valid": true,
  "payload": {
    "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"
  }
}

The verification_id in the payload is the Transaction ID (same as transaction_id from token exchange). Store it if you need to track when and how access was granted; you can search by this ID in Dashboard Audit Logs for compliance and support.

Invalid token (200)

{
  "valid": false,
  "error": "Token has expired"
}

Note: invalid token returns HTTP 200 but with valid: false.

Possible errors

ErrorDescription
Token has expiredToken expired
Invalid signatureInvalid token signature
Invalid issuerInvalid issuer
Malformed tokenMalformed token format

Examples

curl -X POST https://app.ageonce.com/api/oauth/validate \
  -H "Content-Type: application/json" \
  -d '{
    "token": "eyJhbGciOiJSUzI1NiIs..."
  }'
async function validateToken(token) {
  const response = await fetch('https://app.ageonce.com/api/oauth/validate', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ token }),
  });

  const data = await response.json();

  if (data.valid && data.payload.age_verified) {
    console.log('User age verified:', data.payload.min_age);
    return true;
  }

  console.log('Validation failed:', data.error);
  return false;
}
import requests

def validate_token(token):
    response = requests.post(
        'https://app.ageonce.com/api/oauth/validate',
        json={'token': token}
    )
    
    data = response.json()
    
    if data.get('valid') and data.get('payload', {}).get('age_verified'):
        print(f"User age verified: {data['payload']['min_age']}")
        return True
    
    print(f"Validation failed: {data.get('error')}")
    return False
function validateToken($token) {
    $response = file_get_contents('https://app.ageonce.com/api/oauth/validate', false, stream_context_create([
        'http' => [
            'method' => 'POST',
            'header' => 'Content-Type: application/json',
            'content' => json_encode(['token' => $token]),
        ],
    ]));
    
    $data = json_decode($response, true);
    
    if ($data['valid'] && $data['payload']['age_verified']) {
        echo "User age verified: " . $data['payload']['min_age'];
        return true;
    }
    
    echo "Validation failed: " . ($data['error'] ?? 'Unknown error');
    return false;
}

API vs Local Validation

AspectAPI validationLocal validation
SpeedSlower (HTTP request)Faster
SimplicitySimplerRequires JWKS
ReliabilityAlways up-to-date keyNeed to cache keys
OfflineDoesn't workWorks

For high-load systems, we recommend local validation with JWKS caching.

Local validation

For local validation, use the JWKS endpoint to get the public key.

const jwt = require('jsonwebtoken');
const jwksClient = require('jwks-rsa');

const client = jwksClient({
  jwksUri: 'https://app.ageonce.com/api/oauth/jwks',
  cache: true,
  cacheMaxAge: 86400000, // 24 hours
});

function getKey(header, callback) {
  client.getSigningKey(header.kid, (err, key) => {
    callback(null, key.publicKey || key.rsaPublicKey);
  });
}

jwt.verify(token, getKey, { algorithms: ['RS256'], issuer: 'ageonce' }, (err, decoded) => {
  if (err) {
    console.log('Invalid token:', err.message);
  } else {
    console.log('Valid token:', decoded);
  }
});

On this page