Since April 2026 the amended COPPA Rule treats a child's selfie as personal information. California and the EU are tightening age rules too. So a lot of teams are adding facial age estimation to signup right now.

The most common mistake I see is this:``

if (estimatedAge >= 18) allow(); 

Every model has an error margin. A 17-year-old estimated at 18.4 walks straight through.

Use a buffer zone instead:

function route(age: number) { if (age >= 22) return "allow"; // clearly adult if (age < 16) return "minor_experience"; // clearly a minor return "verify_document"; // near the threshold } 

Most users clear instantly. Only the narrow band near your threshold pays for a document check.

A few rules that keep this compliant:

  • Set the bounds from data. Use the model's published NIST FATE AEV error near your threshold, not a guess.
  • Delete the image right away. Keep it in memory, send it, drop it. Never log it.
  • Fail closed. If the API errors out, send the user to verification. Don't let them through.
  • Keep the age out of analytics. The gate returns a decision, never the age.
  • Use the app store signal first if you have one. Texas already requires it.

I've been testing this with ROC's age estimation API. It's NIST-ranked, it doesn't retain images, and you get ``100 free production credits to find where your buffer should sit.

How do you set your bounds?

Disclosure: I work with ROC.