Overview
Exact age targeting reaches a storefront in one of two ways, depending on the kind of storefront. Storefronts on the Storefront-built or Agent-supplied path (see Storefront agents in discovery) take the exact range from the AdCP 3.2targeting_overlay.demographics.age field on a get_products request:
reaching adults 25-59 or Targeting: A25-54: it says who the campaign is
for, not that no one else may see it, so it is used for recommendations only.
A brief states a requirement only when it limits delivery to the range
(25-54 only, exclusively) or forbids ages (do not show to anyone under 18). To make that decision, the storefront sends the brief text to Cloudflare Workers AI, which
runs the Clef decision model; no other request data is sent. Ages count
however they are written, in digits, in words, or in another language. An age
is read only when the model is clearly confident in it. If it cannot make a
confident age-requirement decision, no age targeting is applied and the
response explains how to send structured age targeting.
A request served at AdCP 3.2 (including Apostra’s own proposal requests)
gets a hard age requirement read from the brief applied exactly as if it had
been sent in targeting_overlay.demographics.age: only the products described
in Which products match a requested range
are returned, and the others are omitted, so the result may be empty. The
response confirms the reading once:
brief_targeting to see how the brief was read; if it is wrong, send
the range in targeting_overlay.demographics.age or reword the brief. People
whose age is unknown are excluded unless the brief says to include them. A
reading is applied only when the structured field would accept the same range:
a closed range from 18 to 120. A requirement whose ages were not read with
confidence, an open-ended range such as 35+, or an exclusion of a middle
range such as exclude men aged 21-35 is used for recommendations only, and
each product says so with the note below. When the request also sends
targeting_overlay.demographics.age, that range is applied and nothing is
confirmed, unless it admits ages the brief forbids: a brief requiring ages
18-55 with a structured range of 21-64 returns INVALID_REQUEST on brief,
as does a brief that says to include people whose age is unknown with
include_unknown: false (or the reverse). A structured range inside the
brief’s range, such as 25-54, is accepted.
A request served at AdCP 3.1 has no structured age field and no
confirmation, so age wording is used for recommendations only. When the brief
states a hard age requirement, every product returned ends its description
with this note:
The age stated in the brief was used to recommend this product but was not applied as targeting. To restrict delivery by age, send targeting_overlay.demographics.age on an AdCP 3.2 request.If the brief reader is unavailable or cannot make a confident age-requirement decision, no age is read from the brief. Every returned product instead ends with this note:
The brief could not be read for age targeting, so any age requirement it states was not applied. To restrict delivery by age, send targeting_overlay.demographics.age on an AdCP 3.2 request.
A request that sends
targeting_overlay.demographics.age gets only the
products described in
Which products match a requested range,
whatever the brief says, and no note. If the decision model cannot be reached
or cannot make a confident age-requirement decision, no age is read from the
brief and every returned product carries the unavailable-reader note above,
unless the request supplied structured age targeting.
Built-in platform adapters (the platforms listed below) currently read one
closed age range from the brief:
Campaign age range at launch
When a campaign with an age range launches, each package is handled by what its product and seller can do. Here a product’s audience is “fixed” only when it delivers that age to every impression, as described in Which products match a requested range. A survey-based or modeled audience claim is treated as no fixed audience. The campaign’s age range also carries the age data it accepts as AdCPaccepted_bases: verified and declared, plus inferred when the campaign
allows modeled age. An age the product includes as a signal counts only when
every basis the signal states is accepted; a signal that states no basis does
not count:
- The product’s audience is fixed to a range inside the campaign’s, such
as 18-34 for an 18-55 campaign: the package is bought with the product’s
narrower range. The launch response confirms what was sent with a
demographics_narrowedreceipt naming the product and both ranges. This matters when you selected the product by id or changed the campaign’s range after reviewing proposals. A product fixed to exactly the campaign’s range is bought as is, with no receipt. - The seller can apply age targeting (today, a Meta storefront that approves media buys automatically, under AdCP 3.2): the package carries the campaign’s range.
- Any other seller: the package is created without the range, and the
launch response carries a
demographics_not_appliedwarning naming each affected product. The media buy still launches; it runs without age targeting. - The product’s audience is fixed to a range that reaches ages outside the campaign’s, such as 18-64 for a 25-54 campaign, and the seller cannot apply the campaign’s range: that media buy is refused, because it would deliver to people the campaign excludes. Choose a product inside the range or widen the campaign’s range.
Platform support
Each storefront must prove that it can execute the requested range exactly.ages 21-35 therefore works on Meta and Spotify. ages 25-44 also works on
Google Ads because it is the exact union of two closed native buckets. A range
that cuts through a bucket, or a closed range such as 65-99 projected onto
Google’s open-ended 65+ bucket, is never approximated.
Snap: Australia minimum age
Snap prohibits reaching people under 16 when a media buy targets Australia exclusively. If a Snap media buy targets Australia only and the brief has not requested an age range, Apostra automatically applies an 18+ audience before sending the buy to Snap — so if you target Australia and don’t say 18+, Snap will get an 18+ audience. This only applies when Australia is the sole targeted country; combining Australia with another country does not trigger it. If you explicitly requestages 13-17 on an Australia-only Snap buy, the
media buy is rejected before it reaches Snap rather than having that request
silently overridden.
Requests that need clarification
A Storefront-built or Agent-supplied storefront rejects a structured age it cannot read exactly, withfield: "targeting_overlay.demographics.age":
INVALID_REQUESTwheninclude_unknownis missing, a bound is not a whole number, no bound is given,minis below 18,minis greater thanmax, ormaxis above 120.UNSUPPORTED_FEATUREwhen one bound is omitted (an open-ended range such as 21+), or whenaccepted_verification_methodsis sent.
accepted_bases is empty or names anything other than
verified, declared or inferred is rejected before discovery runs, on
targeting_overlay.demographics.age.accepted_bases.
A built-in adapter asks for clarification on an ambiguous, excluded, or
open-ended age range in the brief. A closed age range in the brief that the
platform cannot target exactly, such as ages 18-55 on Snap, does not empty
discovery: the adapter still returns its products, leaves the age out of the
brief it plans from, and says so in the response message. The message names
the range you asked for and the age groups the platform can target, and
ext.scope3_brief_strategy carries the same facts for agents
(clarification_codes: ["unsupported_age_targeting"], age_targeting_applied: false, requested_age and targetable_age_ranges). The returned products
carry no age targeting. Buying with the same unrepresentable range in
targeting_overlay.demographics.age is still refused, so choose a range the
platform can target exactly or buy without age targeting. A platform that
cannot target age at all returns its products with a message saying so.
An exact but unsupported age range on a composition storefront receives a
counter-pitch instead: a composition
storefront returns products that fit the rest of the brief and includes an
additive limitations entry. The proposal is composed from inventory without
age targeting; fixed-age inventory is excluded rather than silently delivering
to a different age audience. Its message says this explicitly, requested
preserves the requested range, excluded_ranges lists fixed ranges omitted
from the counter-pitch, and supported_ranges lists the exact ranges the seller
can execute (or is empty when none are declared). The storefront never
broadens, approximates, or inverts the age request.
- An open endpoint, such as
ages 21+,under 35, orup to 35. - More than one or a disjoint range, such as
ages 21-24 or 35-44. - An exclusion, such as
exclude people ages 21-35. - A minimum below 18.
- Conflicting or ambiguous bounds.
- A descriptive label without numbers, such as
young adultsorGen Z.
Unknown ages on Meta and Google Ads
Meta and Google Ads can deliver to people whose age is unknown. A constrained-age request excludes them unless it explicitly says to include them. In the structured field, setinclude_unknown: true. In a brief, say so
directly:
AGE_RANGE_UNDETERMINED criterion unless the brief
explicitly includes unknown ages; an include-unknown choice instead writes it
as a positive criterion. Exact readback verifies the complete positive and
negative set. Other platforms reject an include-unknown request unless their
row above says otherwise.
Read the applied targeting
A returned product with verified age targeting includes the applied range at:Signal-based path: Age targeting through product-level signals
(described in this guide) is carried by the selected product via
ext.scope3_product_targeting.demographics. It is not a
targeting_overlay field; the paths are separate.AdCP 3.2 overlay path: overlaySupport.demographics is the permission
signal for demographic overlays, and demographicTargeting is the companion
product-scoped compiler contract for supported age bounds and unknown-age
handling. save_media_buy rejects targetingOverlay.demographics before
dispatch while the application seller transport remains on AdCP 3.1. Treat
these fields as
discovery/readback data for now, and use signal targeting or omit
demographics when staging a media buy.Which products match a requested range
For a requested range, such as 18-55 with unknown ages excluded, a Storefront-built or Agent-supplied storefront returns a product only when it can deliver that range:
A
demographic_targeting.age declaration bounds what can be set: continuous
bounds must contain the request, enumerated intervals must compose it exactly
with no gap, and its unknown_handling must allow the request’s
include_unknown. A product whose age runs only through signals in that
declaration is not matched yet. When the buyer
restricts how age may be determined,
with accepted_bases, a fixed audience is returned only when every basis it
states is accepted, and a product that lets the buyer set age is returned only
when its demographic_targeting.age.supported_bases include at least one
accepted basis. The temporary ext.scope3_age_targeting extension limits the
fixed audience by its determination method instead, and a product that lets the
buyer set age declares no method, so it is not returned. A survey-based or
modeled claim in the legacy extension is not returned even when the buyer
accepts that method or basis; an age signal with that basis is.
For a storefront’s own composed products, the same rule applies to the
inventory and age signals they are composed from, so each composed product
carries exactly the requested range. A composed product whose every inventory
source delivers its age (an age signal the storefront applies, the source’s own
age targeting, or users whose ages are verified or self-provided) describes
that age as an included signal, so other storefronts and buyers re-checking it
later read it the same way. Inventory that only lets the buyer set age is not
composed for an age request yet.
Seller impact
Sellers using a built-in adapter do not need a new configuration field. A custom or upstream sales agent is matched when its product describes either an age it delivers or age targeting support inoverlay_support.demographics
(with demographic_targeting.age for any bounds); a product with neither is
filtered from an age request. A buyer that names accepted_bases is shown a
product that lets the buyer set age only when its
demographic_targeting.age.supported_bases list a basis the buyer accepts, so
list supported_bases to stay visible to those requests.
Describe an age the product delivers
A product that delivers one age range to every impression lists it in AdCPincluded_signals, the signals that describe what the product is and that
buyers do not select:
ext.apostra.age_determination_bases lists how the ages are known, using
AdCP’s age-determination-basis values: verified, declared or inferred.
A buyer that names accepted_bases gets the product only when every basis it
lists is accepted, so a signal that states no basis does not satisfy such a
request. See Determination bases on an included signal.
Survey evidence or modeled audience composition (“60% of our readers are
25-54”) is not an age the product delivers. It belongs in AdCP
audience_evidence, which never satisfies a hard age request.
A seller with a managed ad-server source declares both with
save_wholesale_product: includedAgeSignal carries the entry above
(demographic_predicate, age_determination_bases of verified or declared,
and data_provider), and audienceEvidence carries the evidence. Storefronts
refuse a modelled basis on an included signal and point to audience evidence.
See Declare who a product reaches. Descriptive copy
such as “great for young adults” is not enough either; the structured
predicate must be present.
Legacy fixed-age declaration
Until every seller describes its age as an included signal, storefronts also read the earlier declaration inext.scope3_product_targeting.demographics.age: the range, its
determinationMethod and the responsible dataProvider. It is read only when
the product includes no age signal, and it counts for a hard age request only
when a built-in adapter applies it, or when every person’s age is verified or
self-provided (user_verified or user_provided), for example an age-gated
site or registration data checked at sign-in. Each method falls under one AdCP
basis: user_verified is verified, user_provided is declared, and
survey_based and assumptive are inferred. This declaration will be
retired once sellers emit included signals.
Map ad-server targeting to an age signal
An ad-server-backed seller can map existing ad-server targeting to a first-class age signal:adapterConfig; the abbreviated object above only illustrates the age fields.
The age meaning comes exclusively from the age signal type and its range.
A signal named “Adults 25-54” without those fields is not treated as an age
signal. Signal Manager supports three semantic types:
genericis ordinary buyer-selectable targeting.ageis buyer-selectable targeting with a required closed adult range and determination method.propertyis inventory addressing only and never enters the buyer-selectable signal catalog.
survey_based: the provider surveyed a sample of users and inferred the age of the rest from it.assumptive: the provider inferred or modeled the age.user_provided: the person supplied their age, without identity verification.user_verified: the supplied age was verified against an identity source.
dataProvider separately names who supplied the dataset. It may name the
seller, a first-party registration system, or a third-party data provider; the
provider name does not substitute for the determination method.
When a request asks for the signal’s range, the storefront can select products
from that signal’s source and automatically applies the signal on the buy. A
different bucket is not accepted yet, including a narrower one, and
signal-backed age buckets do not include unknown ages.
Product declarations and mapped age signals describe targeting that will be
applied, not the full range the source might be capable of creating. For
example, a product or signal declaring ages 18-65 does not prove that ages
25-54 will be applied, so it is not returned for a 25-54 request. A source that
can apply any requested range declares age targeting support instead.
Determination bases on an included signal
AdCP 3.2 describes an age a product delivers as an included signal with ademographic_predicate, but a signal listing has no field for how those ages
are known. A buyer that accepts only verified or self-declared ages
(accepted_bases) cannot otherwise tell a verified age signal from a modeled
one. Until AdCP adds the field
(adcontextprotocol/adcp#8145),
storefronts read the bases from ext.apostra.age_determination_bases on the
signal entry and emit them there on their own composed products. This is the
only part of age matching that is not AdCP’s own shape, and it will move to
the AdCP field when that lands. Structured soft audience preferences are
proposed separately in
adcontextprotocol/adcp#8125.
Restrict acceptable age determination
A request served at AdCP 3.2 states how ages must be known withaccepted_bases on the structured age, the same field a campaign sends when a
media buy is created. AdCP’s age-determination-basis values are verified,
declared and inferred:
Leave
accepted_bases out to place no constraint on how age is determined.
accepted_verification_methods is not supported in discovery (see
Requests that need clarification).
The storefront does not change what it returns to fit the list: products that
fail it are omitted, so the result may be empty. For a storefront’s own
composed products, only inventory that satisfies the list is composed. When you
refine a stored product under accepted_bases, the
product must state the bases of its age; one that states none, such as an age
applied by a built-in platform adapter, is reported as unable to be refined.
The temporary extension
Before AdCP 3.2, a buyer could send the Apostra extension below instead. It still works, on any request, and keeps its own meaning: the exact methods it lists.determinationMethod, so a request that lists survey_based does not return an
assumptive audience. An included age signal is held to its bases: each must
fall under an accepted method (verified under user_verified, declared
under user_provided, inferred under survey_based or assumptive). A
product that lets the buyer set age is not returned, because it declares no
method. Missing, unknown, or disallowed provenance fails closed; it is never
treated as approval. A list that is not made up of survey_based,
assumptive, user_provided and user_verified accepts nothing.
A request that sends both the extension and accepted_bases must satisfy both:
a product is returned only when it passes the method check and the basis check.