Skip to main content

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.2 targeting_overlay.demographics.age field on a get_products request:
A storefront reads the structured field only from a request served at AdCP 3.2. When you set demographic targeting on a campaign, Apostra’s proposal requests send it to these storefronts as this field. Built-in platform adapters and external sellers receive the brief only. The brief always reaches the storefront’s sources, so a seller can use age wording in it to recommend age-specific inventory or an age signal it sells. Age wording never causes a refusal on its own. A decision model reads the brief and decides whether it requires or forbids delivery by age, says whether to reach people whose age is unknown, or only names a target audience, states a preference or describes the audience; for a requirement it also reads the ages it allows. Most age wording in a brief is a target audience, such as 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:
Check 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:
The adapter copies the two numbers from the brief. No path invents an age range, translates a generation label such as “Gen Z” into ages, or widens the request to fit a platform. This applies during product discovery and media-buy creation. It does not change targeting on an existing media buy.

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 AdCP accepted_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_narrowed receipt 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_applied warning 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.
See Campaign targeting.

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 request ages 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, with field: "targeting_overlay.demographics.age":
  • INVALID_REQUEST when include_unknown is missing, a bound is not a whole number, no bound is given, min is below 18, min is greater than max, or max is above 120.
  • UNSUPPORTED_FEATURE when one bound is omitted (an open-ended range such as 21+), or when accepted_verification_methods is sent.
A request whose 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.
For a built-in adapter, rewrite the brief as one explicit, closed, supported adult range when it contains:
  • An open endpoint, such as ages 21+, under 35, or up 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 adults or Gen Z.
This fail-closed response is expected. It prevents a storefront or model from silently choosing a different audience.

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, set include_unknown: true. In a brief, say so directly:
You can also state that they must be excluded. The choice is stored with the product, written to the provider targeting object, and checked on readback. Meta may omit the unknown-age field when its effective value is false. In that case, readback still preserves your explicit exclusion under the requested controls and reports an exact match. A provider value of true never satisfies an exclusion or an age-constrained request that did not opt in. On Google Ads, every constrained request writes positive criteria for the selected age buckets and negative criteria for every omitted bucket. This includes a negative 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:
Use the returned product for media-buy creation. The storefront checks the stored product declaration again before publishing it or writing provider state. On Meta, requested and provider controls also show whether unknown ages were included.
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 in overlay_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 AdCP included_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 in ext.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:
Start from the selected ad-server targeting candidate’s complete 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:
  • generic is ordinary buyer-selectable targeting.
  • age is buyer-selectable targeting with a required closed adult range and determination method.
  • property is inventory addressing only and never enters the buyer-selectable signal catalog.
Every age signal also declares one determination method:
  • 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 a demographic_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 with accepted_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:
The storefront applies the same rule it applies when the media buy is created (see Campaign age range at launch). A request with this example returns: 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.
The storefront returns only a product whose fixed age audience is determined by one of those methods. A legacy declaration is held to its own 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.