Skip to content

RFC: declare supported match keys (uid_type × vendor × scope × use) in capabilities and Product.identity #8375

Closed

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[] (refs enums/uid-type.json), plus supported_identifier_types[], minimum_audience_size and matching_latency_hours.
  • media_buy.conversion_tracking.supported_uid_types[] for attribution.
  • frequency_capping.supported_per_units[] and frequency-cap.per, using enums/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_audiences with audience_type of crm / suppression / lookalike_seed, referenced through audience_include / audience_exclude overlays.
  • On the TMP side, provider registration already declares uid_types, and the router filters forwarded identities to that set.

Gaps

  1. No link between key and use. supported_uid_types is 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's per unit (for example households) to the identifier that defines it.
  2. 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_identifier is available.
  3. 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 under audience_targeting, so a capping-only seller doesn't claim targeting). supported_uid_types stays 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. Requires persistent_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_match is blocked when persistent_identifier is false.
  • hashed_email may be declared for audience and attribution uses, not identity_match, until TMPX: resolve hashed_email in creative macros (prohibit or salt) #8449 is resolved. hashed_phone stays in supported_identifier_types.
  • Freshness: changes surface through adcp.capability_changes / capabilities.changed, with the match-key path in changed_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

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 id media_buy.match_keys in experimental_features (or fold it into media_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_hours already covers timing.

Open questions for the working group

  1. Should this be a new experimental feature id, or part of media_buy.product_identity?
  2. Is scope a property of each key, as proposed here, or of the vendor?
  3. Should TMP provider uid_types move to the same match_key shape so the media buy and TMP declarations converge, or stay separate?
  4. Does get_products need a required_match_keys filter, parallel to required_overlay_support?
  5. Does uses need a retargeting value distinct from audience_include with a crm audience, 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.json
  • static/schemas/source/core/product-identity.json
  • static/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]

Activity

  1. newbiechris741 commented on Oct 21, 2026

    @newbiechris741

    "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.

  2. bokelley commented on Oct 21, 2026

    @bokelley
    Contributor

    Thanks for this. I checked the "3.2 already covers" list against main and 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_cap use that only covers caps the seller enforces on its own packages. Today we have four separate pieces with no story connecting them:

    Layer Who counts Spans Where
    Package frequency_cap Seller One package 3.x
    MediaBuy frequency_cap Seller Every package in one buy #7449
    Frequency groups Seller Several buys with one seller #7490 (3.4, cross-seller out of scope)
    TMP IdentityMatch Buyer (impression tracker → cap-state store keyed (identity, seller_agent_url, package_id)) Any seller that participates in TMP specs/identitymatch-fcap-architecture.md

    The 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:

    • Where enforcement happens. Seller-enforced caps (package, MediaBuy, group) apply within one seller. Buyer-enforced caps through TMP IdentityMatch apply across sellers. Both can apply to one package, with AND semantics, as caps compose today.
    • Split the uses value in two. Use frequency_cap when the seller enforces the cap. Add a separate value (for example identity_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.
    • Answer open question 3 now. TMP provider uid_types should use the same match_key shape. Otherwise the buy-time declaration and the serve-time declaration drift apart, and this RFC doesn't deliver cross-publisher capping.
    • Scope compatibility across sellers. A household cap only works across sellers whose keys resolve at households from compatible graphs. That's what the vendor field is for. The RFC should say what "compatible" means, at least as a first pass: same uid_type, plus the same vendor domain when the type is other.

    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. scope shouldn't be limited to one value

    One key can resolve at more than one scope; RampID, for example, can be individual or household depending on the graph. Either make scope an 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_updated

    Top-level last_updated is legacy. "Changes between OS releases" is exactly what last_modified, cache_ttl_seconds, and the revision token plus the capabilities.changed webhook 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_email is in uid-type.json and in supported_identifier_types. hashed_phone is only in the second. Say which list match keys draw from, and whether hashed PII can be a match key at all.

    Minor: Product.identity requires persistent_identifier, so any seller declaring match_keys per product must also declare it. That's fine, but say so in the RFC.

    Moving this to 3.3.0.

  3. added this to the 3.3.0 milestone on Oct 21, 2026
  4. added
    rfcProtocol change — auto-adds to roadmap board
    needs-wg-reviewBlocked on a working-group decision — surface in WG meeting agendas
    media-buyIssue concerns the media-buy protocol domain
    on Oct 21, 2026
  5. newbiechris741 commented on Oct 21, 2026

    @newbiechris741

    "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.

  6. geneticallymodifiedfoodforthought commented on Oct 22, 2026

    @geneticallymodifiedfoodforthought
    Author

    Thanks both.

    @bokelley — agree on all five. Plan for v2 of the body:

    1. Keep the cross-publisher story in this RFC rather than a companion. Add identity_match as a use distinct from seller-enforced frequency_cap, move TMP provider registration to the match_key shape (keeping uid_types for back-compat), define compatibility as same uid_type + same vendor domain when other, and include your four-layer table with AND composition.
    2. scope becomes scopes[].
    3. Freshness rule points at adcp.capability_changes and capabilities.changed; a match-key change appears in changed_paths.
    4. Per-product keys inherit surface from Product.channels (normative); optional channels[] on seller-wide entries only.
    5. Match keys draw from uid-type.json only. hashed_email allowed for audience and attribution uses, not identity_match, until the TMPX hashed-email follow-up is resolved. hashed_phone stays in supported_identifier_types.

    Plus the persistent_identifier note.

    @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.

  7. bokelley commented on Oct 24, 2026

    @bokelley
    Contributor

    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, not channels. enums/channels.json has no web/app split (display covers both), so it can't express "resolves in apps but not on web". Seller-wide keys take an optional property_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 on website, 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. vendor is required for uid_type: other, for any households scope, and for maid or publisher_first_party at individuals. Two keys are compatible at scope S when they have the same uid_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:

    • Seller-wide keys sit at media_buy.supported_match_keys, not under audience_targeting, so a seller that only does frequency capping doesn't have to claim audience targeting.
    • identity_match is blocked when persistent_identifier is false.

    Follow-ups:

    @newbiechris741: geo-scoped caps stay out of scope here, as discussed above.

  8. bokelley commented on Oct 28, 2026

    @bokelley
    Contributor

    WG decision brief (Session D, 2026-11-02)

    Closed-schema check of #8446 (rebased onto main in a scratch tree; nothing pushed):

    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.

    2. Ben's two asks on #8446: leave coverage out, and require vendor for graph-derived scopes. Accept both?
    Recommend yes to both.

    • Keep the vendor rule as written: it is the only thing that makes a household cap comparable across sellers.
    • Coverage can't be verified and is commercially sensitive. If it returns, it belongs with verified measurement, not in a match key.

    3. Should core/match-key.json stay additionalProperties: false?
    Recommend opening it now.

    • DR-0019 closes only join-key objects (class A) and row envelopes (class B) and keeps capability entries open. This is a capability entry.
    • Under DR-0036 it can never gain a field in 3.x once stable. get_products: required_match_keys filter for cross-publisher cap planning #8450 and the channel qualifier are the likely next additions.
    • Opening costs nothing while it's experimental. The vendor rules are allOf and stay.
  9. bokelley commented on Oct 30, 2026

    @bokelley
    Contributor

    Consent round (async; the Nov 2 session is cancelled): objections by Fri 2026-11-06. Proceeding as the Session D brief recommends:

    1. Match keys ship in 3.3 as experimental, landing in beta.2 and not graduating at 3.3 GA. The slice is feat(media-buy): declare supported match keys for cross-publisher frequency capping #8446 as drafted, after the rebase plus maxItems: 5 on scopes, maxItems: 64 on supported_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.
    2. Ben's two asks are accepted: coverage stays out, and vendor is required for graph-derived scopes.
    3. core/match-key.json is opened now (a capability entry under DR-0019), so it can grow after graduation. The vendor rules stay as allOf.

    An objection by Friday brings it to the next live session, Mon 2026-11-09.

  10. bokelley commented on Nov 7, 2026

    @bokelley
    Contributor

    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/maxLength bounds from the consent note. It does not graduate at GA. The follow-ups stay open: #8450 (the required_match_keys filter) and #8451 (conformance coverage before graduation), now in the 3.4.0 deferred milestone.

    🤖 Generated with Claude Code

  11. bokelley commented on Nov 7, 2026

    @bokelley
    Contributor

    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.

  12. removed
    needs-wg-reviewBlocked on a working-group decision — surface in WG meeting agendas
    on Nov 7, 2026
  13. bokelley commented on Nov 8, 2026

    @bokelley
    Contributor

    Shipped as an experimental feature in 3.3.0-beta.5 via #8446 (media_buy.supported_match_keys, identity.match_keys, behind media_buy.match_keys). Graduation to stable is tracked in #8451. Closing this RFC as implemented.

  14. unreconciled commented on Nov 9, 2026

    @unreconciled

    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 (graph by default, or serve_time), makes vendor name 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.

  15. geneticallymodifiedfoodforthought commented on Nov 9, 2026

    @geneticallymodifiedfoodforthought
    Author

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    media-buyIssue concerns the media-buy protocol domainrfcProtocol change — auto-adds to roadmap boardspec / protocol

    Type

    No type

    Projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions