A general-purpose LLM product talks to anyone who can open a browser. That includes children who ticked an age box they did not read, teenagers who say they are 19, and adults whose writing style happens to look young. Unlike a video site, the product does not serve a fixed catalogue that can be rated in advance: every response is generated on demand, the conversation can drift into romance, self-harm or sexual content within a few turns, and a companion persona can build an emotional bond over weeks. That is why regulators and the larger AI providers have moved from a sign-up checkbox to layered age assurance.
This article treats age assurance as an engineering system rather than a legal memo. You will see the vocabulary regulators use, the signals available to an LLM product, a resolver that turns those signals into one age state, policy profiles that change model behaviour by age band, a worked example of choosing an estimation buffer, the data you should and should not keep, and the failure modes that show up in production. The legal notes are a snapshot as of October 2026 and are not legal advice; check the current text for every market you ship in.
Vocabulary and the rules that shape it
Three terms get mixed up constantly. Self-declaration is the user stating an age or date of birth. Age estimation infers an age range from evidence such as a face image or behaviour, and always carries an error band. Age verification ties the user to an authoritative record, such as a government ID matched to a live selfie, a bank, or a mobile operator. Together they are called age assurance.
The UK regulator Ofcom, enforcing the Online Safety Act, lists the methods it considers capable of being highly effective: open banking, photo ID matching, facial age estimation, mobile network operator checks, credit card checks, digital identity services and email-based age estimation. It is equally explicit that self-declaration does not qualify, and neither do payment methods that do not themselves require the payer to be 18. Ofcom also expects methods to be technically accurate, robust, reliable and fair, which is a useful checklist for any vendor evaluation even outside the UK.
Two older rules still shape the under-13 end. In the United States, COPPA requires verifiable parental consent before an online service knowingly collects personal information from a child under 13, which is why most general AI products set 13 as the floor in their terms. In the EU, GDPR Article 8 sets the age at which a child can consent to information society services between 13 and 16 depending on the member state. California's SB 243, signed in October 2025 and effective 1 January 2026, adds rules that are specific to companion chatbots: operators must disclose that a companion chatbot may not be suitable for some minors, maintain a protocol for suicidal ideation and self-harm that refers users to crisis services, and, for users the operator knows are minors, show a clear notice at least every three hours of continuous interaction reminding them to take a break and that the chatbot is AI. Commentary on the law reads knowledge as including what the operator reasonably should know, which matters once you run an age classifier: a score that says likely minor is hard to argue you did not know.
Architecture: one age state for every surface
The core design decision is to centralise. Signals arrive from sign-up, billing, a behavioural model, the conversation itself and an optional verification vendor. One resolver combines them into an age state with a source and an expiry, stored once per account. The chat service, image generator, voice mode, memory feature and API gateway all read that state and map it to a policy profile. If each surface made its own guess you would get a teen blocked from romance roleplay in text but not in voice, which is exactly the inconsistency auditors and journalists find first.
OpenAI's public description of its approach in ChatGPT follows this shape. It runs an age prediction model over behavioural and account-level signals, including how long the account has existed, typical times of day of activity, usage patterns over time and the user's stated age, and applies teen safeguards when the account is likely under 18. Adults who are misclassified can restore full access by verifying with a selfie or a government ID through Persona, a third-party identity service, so OpenAI does not handle the document itself. That pairing, a low-friction predictor for everyone plus a high-assurance appeal path for the people it gets wrong, is the pattern to copy.
Resolving signals into an age state
Signals differ in strength and in direction. A verified adult outcome should beat any behavioural score; a user saying they are 14 in chat should beat a stated birth year of 1990, because people lie upward far more often than downward. The resolver encodes that precedence explicitly and treats uncertainty as the teen experience, not the adult one.
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
from enum import Enum
class AgeState(Enum):
VERIFIED_ADULT = "verified_adult"
LIKELY_ADULT = "likely_adult"
UNKNOWN = "unknown"
LIKELY_MINOR = "likely_minor"
MINOR = "minor" # declared or verified under 18
@dataclass
class Signals:
verified_outcome: str | None # "adult" | "minor" | None, from the vendor callback
declared_age: int | None
disclosed_minor_in_chat: bool # classifier on user turns, high precision setting
adult_score: float | None # behaviour model, calibrated P(adult)
account_days: int
ADULT_T, MINOR_T = 0.90, 0.40 # tune on your own labelled data
def resolve(s: Signals) -> tuple[AgeState, str]:
if s.verified_outcome == "adult":
return AgeState.VERIFIED_ADULT, "verification"
if s.verified_outcome == "minor":
return AgeState.MINOR, "verification"
if s.declared_age is not None and s.declared_age < 18:
return AgeState.MINOR, "declared"
if s.disclosed_minor_in_chat:
return AgeState.LIKELY_MINOR, "conversation"
if s.adult_score is None or s.account_days < 7:
return AgeState.UNKNOWN, "insufficient_signal"
if s.adult_score >= ADULT_T:
return AgeState.LIKELY_ADULT, "behaviour"
if s.adult_score < MINOR_T:
return AgeState.LIKELY_MINOR, "behaviour"
return AgeState.UNKNOWN, "behaviour_uncertain"
PROFILE = {
AgeState.VERIFIED_ADULT: "adult_plus", # only tier allowed into opt-in adult features
AgeState.LIKELY_ADULT: "adult",
AgeState.UNKNOWN: "teen", # uncertainty defaults to the protective profile
AgeState.LIKELY_MINOR: "teen",
AgeState.MINOR: "teen",
}
def record(account_id, state, source, store):
store.put(account_id, state=state.value, source=source,
decided_at=datetime.now(timezone.utc),
expires_at=datetime.now(timezone.utc) + timedelta(days=90))Three details matter. First, there is a separate top tier: only a verified adult reaches opt-in mature features, while a likely adult gets the normal adult experience. Second, the state expires, so a behavioural decision made on a week-old account is revisited as evidence accumulates. Third, the source is stored with the state, which is what lets you answer a regulator asking why this account saw that response.
Policy profiles by age band
A policy profile is the bundle of behaviour changes keyed by age band. Keep it as data, versioned and reviewed like code, so legal and trust-and-safety reviewers can read it without reading the chat service.
| Control | Adult | Teen | Why it lives in the profile |
|---|---|---|---|
| System prompt overlay | None | Age-appropriate guidance block | Changes tone and refusals without a separate model |
| Output classifier thresholds | Standard | Lower thresholds for sexual, self-harm, violence | Same classifiers, stricter operating point |
| Romantic or sexual roleplay | Opt-in, verified adults only | Disabled | The most common teen-harm complaint for companion apps |
| Session break reminder | Off | At least every 3 hours of continuous use | SB 243 duty for known minors in companion products |
| Crisis protocol | Referral resources | Referral resources plus escalation review | Both bands need it; minors get a stricter path |
| Memory and personalisation | On | Limited retention | Reduces profiling of minors |
| Image generation of real people | Policy-limited | Disabled | Removes a high-severity abuse vector |
Implement the overlay at the orchestration layer, where the system prompt and classifier configuration are assembled per request, not inside the model weights. You will change these rules more often than you retrain, and the same base model can then serve every band. The content safety pipeline is where the per-profile thresholds plug in.
Worked example: choosing an estimation buffer
Facial age estimation returns a point estimate with an error band, and the error is not uniform across ages, skin tones or image quality. The standard technique is a buffer: accept the estimate only when it is comfortably above the legal threshold, and route everyone in the grey zone to a stronger method. Retail uses the same idea when staff ask anyone who looks under 25 for ID before selling alcohol to over-18s.
Worked example, with illustrative numbers you must replace with your own evaluation. Suppose your vendor-independent test set shows that, for people who are actually 17, the estimator returns 22 or higher in 2 percent of cases and 25 or higher in 0.2 percent. Your risk appetite says at most 0.5 percent of 17-year-olds may pass as adults. A threshold of 22 fails that target and 25 meets it, so you set the buffer at 25. Now look at the other side: suppose 30 percent of genuine adults aged 18 to 24 estimate below 25. Those users are not rejected; they are offered ID matching or another method, and you measure how many finish it. If completion is poor, you add a second option, such as a mobile operator check, rather than lowering the buffer.
The lesson generalises: choose thresholds from the false-accept rate on real minors at the boundary, then spend engineering effort on the step-up path for adults, never the reverse. Measure error separately per demographic group; an estimator that is accurate on average but biased for one group fails the fairness test and pushes that group disproportionately into document checks.
Keep the outcome, not the evidence
Age assurance creates exactly the kind of data store attackers want: government ID images and face photos linked to chat histories. The safest design never lets those artefacts touch your systems. Use a vendor that performs the check in its own environment, deletes inputs on a short, contractual schedule, and returns a signed outcome. Persona, for example, is described as typically deleting the materials from the ChatGPT check within seven days. Your side keeps a small attestation record:
{
"account_id": "acct_7f3c",
"outcome": "adult", # adult | minor | failed
"method": "facial_estimation", # or id_match, mno_check
"vendor_ref": "inq_91ab", # opaque, lets you audit with the vendor
"decided_at": "2026-10-06T08:12:00Z",
"signature_valid": true
}Do not store the date of birth if a boolean over-18 answers every question you have; do not log the selfie URL; do not copy the outcome into analytics events keyed by user. Verify the vendor webhook signature before you write the record, or anyone who can reach the endpoint can mint adults. Conversation-derived cues deserve the same care: store the resulting state and a reference to the turn, not a growing dossier of excerpts. The privacy obligations around these records are covered in GDPR for LLM systems and the right to erasure; leaked identity data is a worse incident than most PII leakage cases.
Failure modes
- Locking out adults. A behaviour model that flags night-time gamers and students as minors will generate a flood of appeals. Track the appeal rate and the share of appeals that verify as adult; a high overturn rate means the threshold is wrong.
- Surface inconsistency. Voice mode, the API and a mobile app each read age differently. Enforce one state service and test every surface with a teen fixture account.
- Webhook forgery and replay. Unsigned or unchecked vendor callbacks let an attacker set any account to adult. Verify signatures, bind the callback to a nonce you issued, and reject reuse.
- Presentation attacks. Photos of a parent, masks and injected camera streams target selfie checks. Require the vendor's liveness and injection detection and ask for their attack test results.
- Shared and family accounts. A parent verifies, a child uses the account. Keep conversation cues active after verification and consider re-prompting when they fire.
- Circumvention guidance. The model itself explaining how to pass the age check. Add it to the teen and unknown profiles' refusal set.
- Silent drift. The behaviour model's score distribution shifts after a product launch or holiday. Monitor the share of accounts in each state weekly.
Trade-offs
| Approach | Friction | Assurance | Main cost |
|---|---|---|---|
| Self-declaration only | None | Not accepted as highly effective by Ofcom | Minors in the adult experience |
| Behavioural prediction | None up front | Probabilistic | Misclassified adults, fairness review |
| Facial estimation with buffer | Low | High above buffer | Grey-zone step-ups, bias testing |
| ID match with liveness | High | High | Drop-off, vendor cost, breach exposure if mishandled |
| Prediction plus verification appeal | Low for most | High where it matters | Two systems to run and monitor |
What to do next
- Inventory every surface where a user can reach the model: web, mobile, voice, API, integrations. List which ones read age today.
- Build the single age state service and resolver, with source and expiry on every record.
- Write the teen and unknown policy profiles as versioned data, then wire them into prompt assembly and classifier thresholds.
- Pick a verification vendor; get its deletion terms, liveness testing and demographic error results in writing, and verify webhook signatures.
- Choose the estimation buffer from false-accept rates on real 16 and 17 year olds, and measure step-up completion for 18 to 24 year olds.
- Map each market's rules, starting with Ofcom's guidance, COPPA, GDPR Article 8 and SB 243 if you run a companion product in California, and record the date you last checked.
- Add dashboards for state distribution, appeal rate, overturn rate and teen-profile classifier hits, and review them weekly.