DEV Community

Cover image for Build a Face-Based Age Verification System with API
AI Engine
AI Engine

Posted on • Originally published at ai-engine.net

Build a Face-Based Age Verification System with API

Age-restricted products need a way to verify that a customer is old enough. Alcohol delivery, online gambling, vaping shops, and some social platforms require an age verification system by law. Most solutions rely on ID document uploads, which creates friction. A faster approach: estimate the customer's age from a selfie using a face analysis API.

This tutorial builds a complete age verification endpoint in Python using the Face Analyzer API.

Curious how it works? Try the Face Analyzer API on your own images.


How Face-Based Age Verification Works

The user takes a selfie. The API detects the face, analyzes facial features, and returns an estimated age range (e.g., 25-35). Your app checks whether the lower bound meets the legal threshold (18, 21, etc.).

This is faster than document verification (under 1 second vs 10-30 seconds), works on any device with a camera, and doesn't require the user to have their ID. Unlike biometric authentication that stores templates, this can be stateless: process the image, get the result, discard the photo.

What the API Returns

The /faceanalysis endpoint returns an AgeRange object with Low and High bounds for each detected face:

{
  "statusCode": 200,
  "body": {
    "faces": [
      {
        "facialFeatures": {
          "Gender": "Female",
          "Smile": true,
          "Eyeglasses": false,
          "Sunglasses": false,
          "AgeRange": { "Low": 9, "High": 15 },
          "Emotions": ["HAPPY"]
        }
      }
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

AgeRange.Low is what matters. If it's below your threshold, the user doesn't pass. Using the lower bound is the conservative approach.

Age Verification Function in Python

import requests

API_URL = "https://faceanalyzer-ai.p.rapidapi.com/faceanalysis"
HEADERS = {
    "x-rapidapi-host": "faceanalyzer-ai.p.rapidapi.com",
    "x-rapidapi-key": "YOUR_API_KEY",
}

def verify_age(image_path: str, min_age: int = 18) -> dict:
    """Check if the person in the image meets the minimum age."""
    with open(image_path, "rb") as f:
        resp = requests.post(API_URL, headers=HEADERS, files={"image": f})

    data = resp.json()
    faces = data["body"]["faces"]

    if not faces:
        return {"allowed": False, "reason": "no face detected"}

    age_range = faces[0]["facialFeatures"]["AgeRange"]
    estimated_low = age_range["Low"]
    estimated_high = age_range["High"]

    allowed = estimated_low >= min_age
    return {
        "allowed": allowed,
        "age_range": f"{estimated_low}-{estimated_high}",
        "threshold": min_age,
        "reason": "meets age requirement" if allowed else "below age threshold",
    }

result = verify_age("selfie.jpg", min_age=21)
print(result)
# {"allowed": False, "age_range": "9-15", "threshold": 21, "reason": "below age threshold"}
Enter fullscreen mode Exit fullscreen mode

Wrapping It in a Flask Endpoint

from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route("/verify-age", methods=["POST"])
def verify_age_endpoint():
    if "image" not in request.files:
        return jsonify({"error": "no image provided"}), 400

    image = request.files["image"]
    min_age = int(request.form.get("min_age", 18))

    resp = requests.post(
        API_URL,
        headers=HEADERS,
        files={"image": ("selfie.jpg", image.read(), image.content_type)},
    )
    data = resp.json()
    faces = data["body"]["faces"]

    if not faces:
        return jsonify({"allowed": False, "reason": "no face detected"}), 200

    age_range = faces[0]["facialFeatures"]["AgeRange"]
    allowed = age_range["Low"] >= min_age

    return jsonify({
        "allowed": allowed,
        "age_range": {"low": age_range["Low"], "high": age_range["High"]},
        "threshold": min_age,
    })

if __name__ == "__main__":
    app.run(port=5000)
Enter fullscreen mode Exit fullscreen mode

See the full implementation with cURL and JavaScript examples in the complete age verification tutorial.


Testing It with cURL

curl -X POST \
  'https://faceanalyzer-ai.p.rapidapi.com/faceanalysis' \
  -H 'x-rapidapi-host: faceanalyzer-ai.p.rapidapi.com' \
  -H 'x-rapidapi-key: YOUR_API_KEY' \
  -F 'image=@selfie.jpg'
Enter fullscreen mode Exit fullscreen mode

Compliance by Industry

  • Alcohol delivery - 21 (US) / 18 (EU, UK). Face estimation + ID fallback for borderline cases
  • Vaping / tobacco - 21 (US) / 18 (EU). Face estimation at checkout
  • Online gambling - 18-21 depending on jurisdiction. Face estimation + full KYC for account creation
  • Adult content - 18. Face estimation (UK Online Safety Act compliant)
  • Social media - 13-16. Face estimation for parental consent flow

Regulations are tightening: UK Online Safety Act, US state laws (Louisiana, Virginia, Utah), EU Digital Services Act. An age verification API lets you add compliance in one endpoint call.

Handling Edge Cases

  • No face detected - Blurry photo or no visible face. Ask the user to retake
  • Multiple faces - Use the face with the largest bounding box (closest to camera)
  • Borderline age - API returns 16-22, threshold is 18. Use AgeRange.Low and deny, or offer an ID upload fallback
  • Sunglasses - Check the Sunglasses boolean in the response and ask to remove them
  • Spoofing - Someone holds up a printed photo. Add liveness detection (blink check, head turn) for high-risk use cases

Tips

  • Always use AgeRange.Low for the threshold check - the conservative approach protects you legally
  • Offer an ID upload fallback for borderline cases instead of flat-out rejecting users
  • Don't store the selfie. Process, get the result, discard. Log only the decision for audit (GDPR)
  • Set the threshold higher than the legal minimum (e.g., 21 instead of 18) for a safety margin
  • Check the Sunglasses field and ask users to remove them for better accuracy

Read the full guide with comparison tables, JavaScript examples, and detailed compliance breakdown on ai-engine.net.

Top comments (0)