An age detection API estimates how old a person is from a photo of their face. You send an image, and you get back an age range for every face in it. This tutorial calls one from Python, then uses the result to label a group photo, bucket ages for analytics, and flag possible minors. We tested every script on public domain photos of people whose age is known.
Want to try it on your own photos? Get a key for the Face Analyzer API.
Results on photos with known ages
The two children were photographed by Lewis Hine, who wrote their age in his captions. The third photo is John Glenn's official NASA portrait from April 1998, taken when he was 76.
| Photo | Real age | API age range | Midpoint |
|---|---|---|---|
| Luigi, newsboy, Sacramento, 1915 | 6 | 6 to 12 | 9 |
| Addie Card, mill spinner, Vermont, 1910 | 12 | 9 to 15 | 12 |
| John Glenn, NASA portrait, 1998 | 76 | 73 to 79 | 76 |
All three real ages fall inside the returned range, and the midpoint is exact for two of them. Each request took about one second.
Detect age in Python
You need Python 3 with requests and a RapidAPI key in the RAPIDAPI_KEY environment variable. Save this as age_api.py:
import os
import requests
API_URL = "https://faceanalyzer-ai.p.rapidapi.com/faceanalysis"
HEADERS = {
"x-rapidapi-host": "faceanalyzer-ai.p.rapidapi.com",
"x-rapidapi-key": os.environ["RAPIDAPI_KEY"],
}
def detect_ages(path):
"""Return one age estimate per face found in the image."""
with open(path, "rb") as f:
r = requests.post(API_URL, headers=HEADERS, files={"image": f}, timeout=30)
r.raise_for_status()
data = r.json()
if data.get("statusCode") != 200:
raise RuntimeError(data["body"].get("error", "unknown error"))
results = []
for face in data["body"]["faces"]:
age = face["facialFeatures"]["AgeRange"]
results.append({
"low": age["Low"],
"high": age["High"],
"estimate": (age["Low"] + age["High"]) / 2,
"box": face["boundingBox"],
})
# Sort faces left to right so the output order is stable
return sorted(results, key=lambda f: f["box"]["topLeft"]["x"])
if __name__ == "__main__":
for face in detect_ages("glenn.jpg"):
print(f"Age {face['low']}-{face['high']} (estimate {face['estimate']:.0f})")
On the John Glenn portrait it prints:
Age 73-79 (estimate 76)
An image with no face returns an empty list rather than an error, so "no face" is a normal outcome.
What the response contains: facialFeatures.AgeRange holds Low and High in whole years. The midpoint is the best single number for charts and averages. boundingBox uses coordinates between 0 and 1, relative to the image size. The same object also returns gender, smile, glasses and emotions.
One detail: when a request fails (for example an image URL the API cannot download), the HTTP status is still 200 and the error is in statusCode and body.error. That is why the function checks statusCode and does not rely on raise_for_status alone.
Estimate the age of everyone in a group photo
Each face comes with its box, so you can draw the age range above every person (pip install pillow):
from PIL import Image, ImageDraw, ImageFont
from age_api import detect_ages
def annotate(path, out_path):
img = Image.open(path).convert("RGB")
w, h = img.size
draw = ImageDraw.Draw(img)
font = ImageFont.truetype("DejaVuSans-Bold.ttf", max(18, w // 40))
for face in detect_ages(path):
tl, br = face["box"]["topLeft"], face["box"]["bottomRight"]
box = (tl["x"] * w, tl["y"] * h, br["x"] * w, br["y"] * h)
draw.rectangle(box, outline=(124, 58, 237), width=max(3, w // 300))
label = f"{face['low']}-{face['high']}"
x, y = box[0], max(0, box[1] - font.size - 12)
tw = draw.textlength(label, font=font)
draw.rectangle((x, y, x + tw + 16, y + font.size + 10), fill=(124, 58, 237))
draw.text((x + 8, y + 4), label, fill="white", font=font)
img.save(out_path, quality=90)
annotate("artemis.jpg", "artemis_ages.jpg")
On NASA's official Artemis II crew portrait, the API found all four faces in one request:
Batch a folder and bucket ages for analytics
This script scans a folder, writes one CSV row per face and counts faces per age bracket:
import csv
from collections import Counter
from pathlib import Path
from age_api import detect_ages
BRACKETS = [(0, 12, "0-12"), (13, 17, "13-17"), (18, 24, "18-24"),
(25, 34, "25-34"), (35, 44, "35-44"), (45, 54, "45-54"),
(55, 64, "55-64"), (65, 200, "65+")]
def bracket(age):
for low, high, name in BRACKETS:
if low <= age <= high:
return name
def scan_folder(folder, csv_path):
counts = Counter()
with open(csv_path, "w", newline="") as f:
writer = csv.writer(f)
writer.writerow(["file", "face", "low", "high", "estimate", "bracket"])
for path in sorted(Path(folder).glob("*.jpg")):
for i, face in enumerate(detect_ages(path)):
b = bracket(round(face["estimate"]))
counts[b] += 1
writer.writerow([path.name, i, face["low"], face["high"],
face["estimate"], b])
return counts
counts = scan_folder("photos", "ages.csv")
for name in [b[2] for b in BRACKETS]:
print(f"{name:>6}: {counts[name]}")
On our four test photos (seven faces), it prints:
0-12: 2
13-17: 0
18-24: 0
25-34: 0
35-44: 4
45-54: 0
55-64: 0
65+: 1
For analytics, the midpoint is the right input: errors in both directions cancel out over many faces. Running the same photos twice returned the same numbers.
Flag possible minors
For safety decisions, use the Low value and a buffer zone where you ask for more proof:
from age_api import detect_ages
ADULT_AGE = 18
BUFFER = 7 # treat anyone who might be under 25 as "check further"
def classify(path):
faces = detect_ages(path)
if not faces:
return "no_face"
youngest = min(f["low"] for f in faces)
if youngest < ADULT_AGE:
return "possible_minor"
if youngest < ADULT_AGE + BUFFER:
return "verify_id"
return "adult"
for name in ["luigi.jpg", "addie.jpg", "glenn.jpg", "artemis.jpg", "noface.jpg"]:
print(name, classify(name))
luigi.jpg possible_minor
addie.jpg possible_minor
glenn.jpg adult
artemis.jpg adult
noface.jpg no_face
The rule looks at the youngest face, so one child in a group picture is enough to route it for review. For a complete age gate with Flask, consent and data retention, see the face-based age verification tutorial. To choose a provider for regulated checks, see the comparison of age verification APIs.
Production tips
- Upload the file when you can. The API also accepts a public image URL, but some hosts block server-side downloads. A Wikimedia Commons URL returned "Could not load image", while the same photo uploaded as a file worked.
-
Use ranges for decisions, midpoints for statistics. The API estimates apparent age. Studio lighting, makeup and retouching can make an adult look younger, which is another reason to decide on
Lowwith a buffer. - Give the face enough pixels. In the Luigi photo the face covers less than 10% of the image width, and the API still found it.
- Store the result, not the photo. Age is personal data in many jurisdictions: save the numbers, delete the image, and tell users what you process.
Full tutorial with images: Age Detection API in Python on ai-engine.net.
Sources
- Lewis Hine, Luigi, 6 years old, newsboy, Sacramento, 1915 (Wikimedia Commons, public domain)
- Lewis Hine, Addie Card, 12 years old, North Pownal, Vermont, 1910 (National Gallery of Art, CC0)
- Astronaut U.S. Senator John Glenn, NASA portrait, 14 April 1998 (public domain)
- Artemis II crew portrait, NASA, 29 March 2023 (public domain)
Try it on your own photos: grab a key from the Face Analyzer API page and run
age_api.py.

Top comments (0)