Repository navigation
Description
Status (9 Nov 2026): Implemented as experimental in 3.3.0-beta.5 via #8446, behind
media_buy.match_keys. Not graduating at 3.3 GA. Graduation needs #8451 (conformance), #8449 (TMPX hashed_email) and one seller plus one buyer emitting keys. The body below reflects what shipped (v2). The original v1 text is kept at the end because earlier comments refer to it.
Problem
Buyers need to know, before they buy, whether a seller can resolve the identifiers they plan to send, and for which purpose. That matters for suppression, retargeting and frequency capping. Today a buyer can learn part of this, but not enough to plan a buy.
What 3.2.x already covers
get_adcp_capabilities→media_buy.audience_targeting.supported_uid_types[](refsenums/uid-type.json), plussupported_identifier_types[],minimum_audience_sizeandmatching_latency_hours.media_buy.conversion_tracking.supported_uid_types[]for attribution.frequency_capping.supported_per_units[]andfrequency-cap.per, usingenums/reach-unit.json(individuals, households, devices, accounts, cookies, custom).Product.identity.persistent_identifier, which is experimental and was added in 3.2.0 (Identity-absence coherence: sellers with no persistent identifier must not accept identity semantics they cannot honor #7176).sync_audienceswithaudience_typeofcrm/suppression/lookalike_seed, referenced throughaudience_include/audience_excludeoverlays.- On the TMP side, provider registration already declares
uid_types, and the router filters forwarded identities to that set.
Gaps
- No link between key and use.
supported_uid_typesis one flat list for audience matching. A seller can't say that a key works for suppression but not for frequency capping. Nothing ties a cap'sperunit (for examplehouseholds) to the identifier that defines it. - Seller-wide only. Products from the same seller can resolve different keys. For example, CTV inventory may resolve household keys, app inventory MAIDs, and web inventory publisher first-party IDs. At product level, only
persistent_identifieris available. - No vendor field for graphs outside the enum. RampID, ID5, UID2, EUID and PAIR have enum values. Every other cross-device or household graph falls back to
other+ext, which a buyer can't compare across sellers.
Why now
WebKit, "Blocking Identity Resolution Infrastructure" (19 Oct 2026): in the iOS, iPadOS, macOS and visionOS 27 releases, Safari and WebKit block network access to identity-resolution infrastructure at the domain level. Apple names no vendors. Trade press reports several identity and data vendors affected (AdExchanger). The change applies to web inventory. The post doesn't address apps or IDFA.
The consequence for the protocol is that one seller's matchable keys now differ by surface. They can also change between OS point releases, without a spec change. A static, seller-wide supported_uid_types list can't express that truthfully.
As shipped (v2)
Intent: let buyers and sellers state, before committing addressable spend, which identifiers each side can resolve, at which scopes and for which uses, across any identity provider, without tying a key to a single category.
core/match-key.json
| Field | Notes |
|---|---|
uid_type |
Required. From enums/uid-type.json only (enum unchanged). |
vendor |
BrandRef. Required for uid_type: other, for any households scope, and for maid or publisher_first_party at individuals. |
scopes[] |
Required. Values from enums/reach-unit.json. One key can resolve at several scopes. |
uses[] |
Required, min 1: audience_include, audience_exclude, frequency_cap (seller-enforced), identity_match (forwarded to buyer-side TMP IdentityMatch), conversion_attribution. |
property_types[] |
Optional, seller-wide entries only (website, mobile_app, ctv_app, …). |
Arrays carry the maxItems / maxLength bounds from the consent note.
Where it appears
- Seller-wide:
media_buy.supported_match_keys[](not underaudience_targeting, so a capping-only seller doesn't claim targeting).supported_uid_typesstays as the fallback for 3.0–3.2 buyers. - Per product:
Product.identity.match_keys[], authoritative for that product, a subset of the seller-wide list, inheriting the product's surfaces. Requirespersistent_identifier.
Rules
- Compatibility at scope S: same
uid_type, both list S, and vendor domains match when both declare one. A household cap counts across two sellers only when both resolve households through the same graph. identity_matchis blocked whenpersistent_identifieris false.hashed_emailmay be declared for audience and attribution uses, notidentity_match, until TMPX: resolve hashed_email in creative macros (prohibit or salt) #8449 is resolved.hashed_phonestays insupported_identifier_types.- Freshness: changes surface through
adcp.capability_changes/capabilities.changed, with the match-key path inchanged_paths. - Capability only: no match rates, audience sizes, coverage (e.g. Safari share within
website) or per-user data.
Frequency capping layers (AND composition)
[paste BOK's four-layer table from his first comment]
Open follow-ups
- get_products: required_match_keys filter for cross-publisher cap planning #8450
required_match_keysfilter onget_products(3.4.0) - Conformance coverage for match keys before graduating media_buy.match_keys #8451 conformance coverage before graduation (3.4.0)
- TMPX: resolve hashed_email in creative macros (prohibit or salt) #8449 TMPX hashed_email
- Match keys: declare households a seller forms at serve time (scope_resolution) #8832 serve-time households (
scope_resolution), proposed
Compatibility and status
- All fields are additive and optional. 3.2 and earlier buyers and sellers can ignore them.
x-status: experimental. Sellers implementing the surface list a new feature idmedia_buy.match_keysinexperimental_features(or fold it intomedia_buy.product_identity; see the open questions).
Not in scope
- Counting, cap policy and enforcement. These stay where TMP Identity Match puts them (
specs/identitymatch-fcap-architecture.md). - Cross-seller frequency counting. It's also out of scope in RFC: cross-MediaBuy frequency groups as a separately identified resource #7490.
- Changes to
enums/uid-type.json. - Match-rate or quality hints. They can't be verified and they're commercially sensitive.
matching_latency_hoursalready covers timing.
Open questions for the working group
- Should this be a new experimental feature id, or part of
media_buy.product_identity? - Is
scopea property of each key, as proposed here, or of the vendor? - Should TMP provider
uid_typesmove to the samematch_keyshape so the media buy and TMP declarations converge, or stay separate? - Does
get_productsneed arequired_match_keysfilter, parallel torequired_overlay_support? - Does
usesneed aretargetingvalue distinct fromaudience_includewith acrmaudience, or is the existing overlay naming enough?
Affected files
static/schemas/source/core/match-key.json(new)static/schemas/source/protocol/get-adcp-capabilities-response.jsonstatic/schemas/source/core/product-identity.jsonstatic/schemas/source/core/experimental-feature-id.json(if a new id is chosen)docs/protocol/get_adcp_capabilities.mdx,docs/media-buy/product-discovery/media-products.mdx,docs/reference/experimental-status.mdx.changeset/*.md(minor)
Related
#6872 (uids[].value shape, 3.3.0) · #4330 (BrandRef vendor alignment) · #7490 (cross-MediaBuy frequency groups) · #7176 (identity-absence coherence) · #4324 (audience activation methods) · #6684 (PAIR scoping)
Original proposal (v1, 18 Oct 2026), superseded
[current body, unchanged]
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsShipped
Activity
newbiechris741 commentedon Oct 21, 2026
"but not enough to plan a buy."
IMO a buying agent could never be able to plan a buy on a bunch of IDs because the IDs don't map to all the media types that the buying agent has to evaluate to create a plan. A buying agent restricted to an ID universe is highly compromised and not worth operating. Once the plan is established, strategy confirmed and channels allocated, and media has been selected that includes IDs, then IDs become the means of execution in those areas. As with Apple's moves, support for Geo and Temporal structure become much more important.
bokelley commentedon Oct 21, 2026
Thanks for this. I checked the "3.2 already covers" list against
mainand it's accurate. The vendor-as-BrandRef shape is what we wanted. Comments before WG review:1. We need a generalized cross-publisher frequency capping story, and it has to include TMP
Cross-publisher frequency capping is the main use case behind this work. As written, the RFC sends it to TMP and declares a
frequency_capuse that only covers caps the seller enforces on its own packages. Today we have four separate pieces with no story connecting them:frequency_capfrequency_cap(identity, seller_agent_url, package_id))specs/identitymatch-fcap-architecture.mdThe only place cross-publisher counting can happen is buyer-side. That works only if each seller resolves, and forwards to the buyer, an identity the buyer's tracker recognizes, at a compatible scope. Match keys are what connects those pieces, so this RFC should state the full model:
usesvalue in two. Usefrequency_capwhen the seller enforces the cap. Add a separate value (for exampleidentity_match) for "this key is forwarded to buyer-side IdentityMatch for this product." A buyer planning a cross-publisher cap needs the second, and today nothing tells them whether a product's impressions can be counted against their cap.uid_typesshould use the samematch_keyshape. Otherwise the buy-time declaration and the serve-time declaration drift apart, and this RFC doesn't deliver cross-publisher capping.householdsfrom compatible graphs. That's what thevendorfield is for. The RFC should say what "compatible" means, at least as a first pass: sameuid_type, plus the same vendor domain when the type isother.If this gets too big for one issue, I'm happy to split it into a companion RFC. Either way it should land in 3.3 together with match keys, not after them.
2.
scopeshouldn't be limited to one valueOne key can resolve at more than one scope; RampID, for example, can be individual or household depending on the graph. Either make
scopean array, or state explicitly that sellers repeat the entry once per scope. The objective was specifically not to tie each identifier to a single category.3. Use the current cache fields, not
last_updatedTop-level
last_updatedis legacy. "Changes between OS releases" is exactly whatlast_modified,cache_ttl_seconds, and the revision token plus thecapabilities.changedwebhook are for. The freshness rule should refer to those.4. Surface isn't in the shape
The "why now" section argues that Safari web, app and CTV resolve different keys, but a match key has no surface or channel qualifier. Declaring keys per product handles this only if sellers split products by surface. Either say so normatively, or add an optional channel qualifier.
5. Hashed identifiers appear in two lists
hashed_emailis inuid-type.jsonand insupported_identifier_types.hashed_phoneis only in the second. Say which list match keys draw from, and whether hashed PII can be a match key at all.Minor:
Product.identityrequirespersistent_identifier, so any seller declaringmatch_keysper product must also declare it. That's fine, but say so in the RFC.Moving this to 3.3.0.
newbiechris741 commentedon Oct 21, 2026
"The only place cross-publisher counting can happen is buyer-side" Correct but it's also pre-campaign in the planning task to model frequency. From the cross media campaign desired reach and frequency, the model assigns dollars and media volume per channel, per media supplier, per geography within time. The advertiser wants the planning to match delivery, so the buyer needs to enforce a frequency cap per geo and that geo has to line up with sales, store locations and non-TMP campaign data.
geneticallymodifiedfoodforthought commentedon Oct 22, 2026
Thanks both.
@bokelley — agree on all five. Plan for v2 of the body:
identity_matchas a use distinct from seller-enforcedfrequency_cap, move TMP provider registration to thematch_keyshape (keepinguid_typesfor back-compat), define compatibility as sameuid_type+ same vendor domain whenother, and include your four-layer table with AND composition.scopebecomesscopes[].adcp.capability_changesandcapabilities.changed; a match-key change appears inchanged_paths.Product.channels(normative); optionalchannels[]on seller-wide entries only.uid-type.jsononly.hashed_emailallowed for audience and attribution uses, notidentity_match, until the TMPX hashed-email follow-up is resolved.hashed_phonestays insupported_identifier_types.Plus the
persistent_identifiernote.@newbiechris741 — fair on "plan a buy"; IDs are execution once channels are allocated. I'll reword to "before committing addressable spend." Per-geo caps can already be expressed as geo-targeted packages with package caps; cross-media R&F modelling by geo and time is a planning/forecast question I'd keep out of this RFC. Happy to link a separate issue if you open one.
bokelley commentedon Oct 24, 2026
Draft implementation of the v2 plan is in #8446 (experimental, behind
media_buy.match_keys). Open questions 1, 2, 3 and 5 are answered there as agreed above. Two points need an explicit WG decision before merge:1. The surface qualifier is
property_types, notchannels.enums/channels.jsonhas no web/app split (displaycovers both), so it can't express "resolves in apps but not on web". Seller-wide keys take an optionalproperty_types[](website,mobile_app,ctv_app, …). Product-level keys inherit the product's surfaces.What this deliberately doesn't express: Safari versus Chrome within
website. A seller that resolves a key on Chrome but not Safari still declares it onwebsite, and the buyer can't see the Safari share. That's coverage, which match keys exclude because it can't be verified and it's commercially sensitive. The alternative is a browser/OS qualifier, and that goes stale with every OS release. Is the WG OK with leaving coverage out, given that iOS 27 is the reason for doing this now?2. Graph-derived scopes must name the graph.
vendoris required foruid_type: other, for anyhouseholdsscope, and formaidorpublisher_first_partyatindividuals. Two keys are compatible at scope S when they have the sameuid_type, both list S, and their vendor domains match when both declare one. As a result, a household cap counts across two sellers only when both resolve households through the same graph. Is that the right strictness for a first pass?Also in the PR:
media_buy.supported_match_keys, not underaudience_targeting, so a seller that only does frequency capping doesn't have to claim audience targeting.identity_matchis blocked whenpersistent_identifieris false.Follow-ups:
required_match_keysfilter onget_products(open question 4). Deferred until the compatibility rule settles.hashed_emailkeys can't listidentity_match.@newbiechris741: geo-scoped caps stay out of scope here, as discussed above.
bokelley commentedon Oct 28, 2026
WG decision brief (Session D, 2026-11-02)
Closed-schema check of #8446 (rebased onto
mainin a scratch tree; nothing pushed):lint:additive-minorreports 0 new against 3.2.7.media_buyin the capabilities response is open, somedia_buy.supported_match_keysis a legal addition under DR-0036.Product.identityand TMPprovider-registrationare closed, andmatch_keysis added to both. That passes only because both are experimental since the release that introduced them (DR-0036 scope clause; the lint skips them). Graduating either freezes it closed withmatch_keysinside.tests/capabilities-structural-bounds.test.cjs.supported_match_keysandmatch-key.scopeshave nomaxItems, and new or experimental fields must enforce bounds rather than annotate them. The pinned worst-case size needs updating.maininget-adcp-capabilities-response.json,package.jsonand two tests.1. Ship match keys in 3.3 as experimental (beta.2, about Nov 17), or move to 3.4?
Recommend 3.3, experimental, landing in beta.2 and not graduating at 3.3 GA.
core/match-key.json,enums/match-key-use.json,media_buy.supported_match_keys,Product.identity.match_keys, TMP providermatch_keys, and the feature idmedia_buy.match_keys.required_match_keysfilter (get_products: required_match_keys filter for cross-publisher cap planning #8450), conformance (Conformance coverage for match keys before graduating media_buy.match_keys #8451), andhashed_emailunderidentity_match(TMPX: resolve hashed_email in creative macros (prohibit or salt) #8449).maxItems: 5onscopes,maxItems: 64onsupported_match_keys(11 uid types), and the new worst-case pin. Experimental-only changes are patch-level, so being wrong costs little. Waiting leaves sellers that must answer iOS 27 putting this inext.2. Ben's two asks on #8446: leave coverage out, and require
vendorfor graph-derived scopes. Accept both?Recommend yes to both.
vendorrule as written: it is the only thing that makes a household cap comparable across sellers.3. Should
core/match-key.jsonstayadditionalProperties: false?Recommend opening it now.
vendorrules areallOfand stay.bokelley commentedon Oct 30, 2026
Consent round (async; the Nov 2 session is cancelled): objections by Fri 2026-11-06. Proceeding as the Session D brief recommends:
maxItems: 5onscopes,maxItems: 64onsupported_match_keys, and the updated worst-case pin. Out of the slice: get_products: required_match_keys filter for cross-publisher cap planning #8450, Conformance coverage for match keys before graduating media_buy.match_keys #8451 and TMPX: resolve hashed_email in creative macros (prohibit or salt) #8449. Graduation needs Conformance coverage for match keys before graduating media_buy.match_keys #8451, TMPX: resolve hashed_email in creative macros (prohibit or salt) #8449 resolved, and one seller and one buyer emitting keys.vendoris required for graph-derived scopes.core/match-key.jsonis opened now (a capability entry under DR-0019), so it can grow after graduation. Thevendorrules stay asallOf.An objection by Friday brings it to the next live session, Mon 2026-11-09.
feat(media-buy): declare supported match keys for cross-publisher fre…
feat(media-buy): bound match-key arrays and open the match-key entry
feat(media-buy): declare supported match keys for cross-publisher fre…
feat(media-buy): bound match-key arrays and open the match-key entry
bokelley commentedon Nov 7, 2026
Consent closed (2026-11-07): no objections by Fri Nov 6. Match-key declaration ships in 3.3 as experimental through #8446, with the
maxItems/maxLengthbounds from the consent note. It does not graduate at GA. The follow-ups stay open: #8450 (therequired_match_keysfilter) and #8451 (conformance coverage before graduation), now in the 3.4.0 deferred milestone.🤖 Generated with Claude Code
feat(media-buy): declare supported match keys for cross-publisher fre…
bokelley commentedon Nov 7, 2026
No objections by 2026-11-06, so the consent round passed. #8446 is merged (experimental,
media_buy.match_keys) and ships in the next 3.3 beta. It does not graduate at 3.3 GA. Graduation needs #8451, #8449 resolved, and one seller and one buyer emitting keys. #8450 and #8451 are on 3.4.0.bokelley commentedon Nov 8, 2026
Shipped as an experimental feature in 3.3.0-beta.5 via #8446 (
media_buy.supported_match_keys,identity.match_keys, behindmedia_buy.match_keys). Graduation to stable is tracked in #8451. Closing this RFC as implemented.unreconciled commentedon Nov 9, 2026
Writing as an independent practitioner; I work on open problems in CTV identity and audience reach.
I opened #8832 as a follow-up before match keys graduate. Many CTV sellers form the household themselves at request time, from the IP address plus device signals, rather than through a graph, while the shipped rule says household resolution always comes from a graph. The PR adds an optional
scope_resolution(graphby default, orserve_time), makesvendorname the party that forms the household rather than a data supplier, and adds one compatibility condition so serve-time households match only under the party that formed them. It is additive: no shipped declaration changes meaning.geneticallymodifiedfoodforthought commentedon Nov 9, 2026
Updated the description to the shipped v2 shape. The v1 body still showed a single required
scope, which contradicted the original intent: a key must be able to resolve at several scopes (household, device, individual) rather than be tied to one. That was fixed in v2 (scopes[], per @bokelley's review) but never reflected here. v1 is kept in a collapsed block so earlier comments still read correctly.Remaining gap against the original intent: the shipped rule assumes households come from a graph, which leaves out sellers that form households at serve time. #8832 addresses this additively; I support it landing before graduation.