Skip to content

RFC: report audience size as a range, and audience overlap as an optional read #8023

Open

Description

@bokelley

Problem

A buyer that syncs one CRM list to several platforms needs to know, per platform, how many people the list reached and whether it is worth buying against. Ad-tech tools that sync audiences to platforms show exactly this per destination: audience size, match rate, and records sent. AdCP's sync_audiences result cannot carry what the platforms actually report:

  • Size is one number. matched_count has no bounds and no precision. Most platforms report a range or an approximation for privacy reasons, so a seller has to invent a point value.
    • Meta returns approximate_count_lower_bound / approximate_count_upper_bound, and no matched count or match rate (Custom Audience reference).
    • Google Customer Match returns size_for_display / size_for_search, bucketed size_range_for_display / size_range_for_search, and match_rate_percentage (user_list.proto).
    • LinkedIn returns audienceSize and a rolling-7-day matchedCount, both approximate "for member privacy" (segment destinations).
    • Snap returns approximate_number_users (customer lists); X returns audience_size, null while processing or below minimum (audiences); Pinterest returns size (API description).
  • Matched is not targetable. Google (search vs display) and LinkedIn (audienceSize vs matchedCount) distinguish people matched from people the platform can serve now. AdCP has one field.
  • Match rate has no basis. Google reports a rate; for LinkedIn it can only be derived from approximate counts; Meta exposes none. effective_match_rate cannot say which.
  • No overlap. Buyers deciding whether to run two audiences together, or to suppress one from another, need their overlap. TikTok exposes it through the API (/open_api/v1.3/audience/insight/overlap/), Amazon DSP exposes overlapping audiences, and Meta exposes it in Ads Manager only. AdCP has no way to ask.

Proposed direction

Two additive changes. Nothing existing changes meaning.

1. Size and match fields on each sync_audiences audience result

{
  "audience_id": "crm_customers",
  "status": "ready",
  "matched_count": 15300000,            // unchanged; MAY be absent when the seller cannot report it
  "audience_size": {                     // new: people matched on the platform
    "lower_bound": 14000000,
    "upper_bound": 16500000,
    "precision": "approximate",          // exact | approximate | bucketed
    "as_of": "2026-10-08T12:00:00Z"
  },
  "targetable_size": {                   // new: people the platform can serve now
    "lower_bound": 9000000,
    "upper_bound": 11000000,
    "precision": "bucketed",
    "as_of": "2026-10-08T12:00:00Z"
  },
  "effective_match_rate": 0.42,
  "match_rate_basis": "platform_reported" // new: platform_reported | derived
}
  • lower_bound equals upper_bound when precision is exact.
  • A seller that reports size but not match (Meta) omits matched_count and effective_match_rate; buyers must not read absence as zero.
  • Values below the seller's minimum size follow the existing too_small status and are not disclosed.

2. Overlap as an optional, capability-advertised read

A seller that can compute overlap advertises it in get_adcp_capabilities and accepts:

// request
{ "account": { ... }, "audience_id": "crm_customers", "compare_to": ["site_visitors_30d", "lapsed_buyers"] }

// response
{
  "audience_id": "crm_customers",
  "comparisons": [
    {
      "audience_id": "site_visitors_30d",
      "overlap": { "lower_bound": 2100000, "upper_bound": 2400000, "precision": "approximate", "as_of": "..." },
      "share_of_primary": 0.15,          // overlap / size of audience_id
      "share_of_comparison": 0.31        // overlap / size of the compared audience
    }
  ]
}
  • Both shares are returned because overlap is asymmetric; Meta's UI flips the percentage depending on which audience is selected.
  • Only aggregate counts. Never member identifiers.
  • A seller may refuse comparisons involving an audience below its minimum, with the existing too_small semantics.

Alignment with #7763

#7763 proposes per-account execution readiness: for each product, which account resources it needs, which candidates the account has, and which one the buy uses. An audience is one of those resources. A buyer choosing between a platform's existing custom audiences, or deciding whether a synced list is worth a product, needs the same size facts there as in sync_audiences.

Design questions

  1. Task name and placement: a standalone read (for example get_audience_overlap), or a mode on the audience snapshot read proposed in RFC: audience lifecycle snapshots and status-change notifications #5966?
  2. Should targetable_size allow a per-surface breakdown (Google search vs display), or stay one value with the surface left to the seller?
  3. Maximum compare_to length. Meta's UI compares up to 5 audiences.
  4. Should overlap also be computable by a buyer-side orchestrator that holds the hashed lists itself, and if so does it belong in this task or in a separate buyer-agent capability?

Related

Activity

  1. added this to the 3.3.0 milestone on Oct 8, 2026
  2. added
    rfcProtocol change — auto-adds to roadmap board
    schemaJSON Schema source-of-truth: definitions, codegen artifacts, validation, hygiene
    needs-wg-reviewBlocked on a working-group decision — surface in WG meeting agendas
    media-buyIssue concerns the media-buy protocol domain
    on Oct 8, 2026
  3. added
    claude-triagingTriage routine is actively working on this issue (1-3 min)
    on Oct 8, 2026
  4. bokelley commented on Oct 8, 2026

    @bokelley
    ContributorAuthor

    Triage

    Classification: Feature request (RFC — normative additive)
    Bucket(s): spec / protocol, media-buy
    Status: ready-for-human
    Milestone: 3.3.0

    One-line ruling: Part 1 (audience_size / targetable_size on sync_audiences) is non-breaking and ready for WG vote after the as_of coherency rule is settled; Part 2 (overlap task) needs two design calls before approval.

    Class: normative — optional fields on existing array item; new optional capability-advertised task.

    What the experts said:

    • ad-tech-protocol-expert: Additive and non-breaking (matched_count was already optional in sync-audiences-response.json). One blocker: audience_size.as_of and targetable_size.as_of can diverge from the existing last_synced_at anchor, breaking the schema's single-clock invariant on count fields. Recommends standalone get_audience_overlap following existing capability-advertisement patterns; buyer-side hash computation is a different trust boundary and should be explicitly deferred.
    • adtech-product-expert: Strong buy-side signal — matched_count as an integer is a protocol fiction on every major platform today; targetable_size (matched vs. serveable) is real and not currently representable. Overlap task has genuine pre-buy demand (suppression lists, add-to vs. create-new decisions). Coupling to RFC: per-account execution readiness for products (required resources, candidates, and binding) #7763 should be loose; both issues are independently useful.
    • ad-tech-protocol-expert-deep: Part 1 conditionally approvable with three targeted adjustments: (1) timestamp hierarchy between as_of and last_synced_at; (2) precision enum semantics (platform quantization metadata vs. protocol representation accuracy — needs a third value for open-ceiling top buckets: Meta reports "10M+", Google has the same case); (3) matched_count semantic precedence vs. audience_size when both are present. Part 2 defer until Q1 (standalone task vs. RFC: audience lifecycle snapshots and status-change notifications #5966 mode) and Q2 (per-surface targetable_size) are resolved — both are one-way wire-shape decisions.

    My take: The schema already supports these additions cleanly; the only genuine blocker is resolving the as_of coherency rule before Part 1 ships. Part 2 is straightforward to approve once the WG picks Option A or B on task placement — consensus across all three experts favors standalone.

    Dissent: none.


    WG decision 1 — as_of coherency (blocker on Part 1)

    The existing sync-audiences-response.json if/then requires last_synced_at when status is ready or too_small. Adding sub-object as_of timestamps that can diverge from it creates up to three freshness anchors on one audience item.

    Option A — as_of MUST equal last_synced_at (recommended; no divergence risk):

    +  "audience_size": { "lower_bound": 14000000, "upper_bound": 16500000,
    +                     "precision": "approximate", "as_of": "<same as last_synced_at>" },
    +  "targetable_size": { "lower_bound": 9000000, "upper_bound": 11000000,
    +                       "precision": "bucketed", "as_of": "<same as last_synced_at>" }

    Consumers can safely cross-divide targetable_size / audience_size. Sellers with delayed reach modeling round up to the sync timestamp.

    Option B — as_of MAY differ (e.g., reach modeled on a T-1 data pipeline):

    +  "last_synced_at": "2026-10-08T10:00:00Z",
    +  "audience_size": { "lower_bound": 14000000, "upper_bound": 16500000,
    +                     "precision": "approximate", "as_of": "2026-10-07T06:00:00Z" }

    Accurate for platforms that model reach asynchronously, but requires normative prose declaring cross-subobject arithmetic undefined and specifying which timestamp buyers surface to users.


    WG decision 2 — targetable_size granularity (one-way door)

    Google exposes search vs. display targetable counts separately; Meta does not.

    Option A — flat single value (current proposal; simpler):

    "targetable_size": { "lower_bound": 9000000, "upper_bound": 11000000, "precision": "bucketed" }

    Google sellers choose which surface to report. If per-surface breakdown is needed later, it ships as a new optional targetable_sizes array alongside this field — no migration needed.

    Option B — per-surface array now (forward-looking; design it once):

    "targetable_sizes": [
      { "surface": "search", "lower_bound": 9000000, "upper_bound": 11000000 },
      { "surface": "display", "lower_bound": 14000000, "upper_bound": 16500000 }
    ]

    Expanding flat → array in a later release leaves both fields coexisting with undefined priority semantics. If any current WG member needs per-surface data for budget pacing today, design the array now.


    WG decision 3 — Overlap task placement (expert consensus: standalone)

    All three experts recommend a standalone get_audience_overlap task over a mode on #5966. A mode would couple overlap availability to #5966's 3.4.0 timeline; #5966 slipping would take overlap with it.

    Proposed capability advertisement shape (following existing audience_targeting / audience_activation patterns):

    // get_adcp_capabilities response
    "audience_overlap": {
      "supported": true,
      "max_comparisons": 5   // seller-declared cap; not hardcoded in schema
    }

    Buyer-side hash computation (RFC design question #4) should be explicitly deferred as a separate future capability — it involves a different trust boundary and security profile.


    Related: #5966 open question "which size fields are safe to push" should reference the audience_size object defined here. #7763 audience resource kind can reuse these same size objects without tight coupling — the RFC's cross-reference section already describes the right loose alignment.


    Triaged by Claude Code. Session: https://claude.ai/code/session_012FdAGq9Duv9zGsw6cAuieY


    Generated by Claude Code

  5. added
    claude-triagedIssue has been triaged by the Claude Code triage routine. Remove to re-triage.
    and removed
    claude-triagingTriage routine is actively working on this issue (1-3 min)
    on Oct 8, 2026
  6. bokelley commented on Oct 8, 2026

    @bokelley
    ContributorAuthor

    WG decision brief (3.3) — part 2: audience overlap

    Problem. A buyer choosing between audiences (suppress one from another, add to an existing list or create a new one, run two lists together) needs their overlap, and AdCP has no way to ask. It is also the riskiest audience read: repeated queries over changing lists can be differenced to recover a small cell, so the shape matters more than the field names.

    Who's pulling (platform evidence from this RFC; not independently re-verified): TikTok exposes overlap through its API (/open_api/v1.3/audience/insight/overlap/), Amazon DSP exposes overlapping audiences, Meta exposes it in Ads Manager only. Tools that sync audiences to platforms show size and match per destination. No AdCP seller implements this yet.

    Proposed shape. Draft PR: #8031 (against main, now that #8024 has merged; experimental, media_buy.audience_overlap). A standalone, capability-advertised read, comparing only audiences the buyer synced on the same account:

    // get_audience_overlap { account, audience_id: "crm_customers", compare_to: ["site_visitors_30d", "lapsed_buyers"] }
    { "audience_id": "crm_customers", "comparisons": [
      { "audience_id": "site_visitors_30d", "overlap_status": "ready",
        "overlap": { "lower_bound": 2100000, "upper_bound": 2400000, "precision": "approximate" } },
      { "audience_id": "lapsed_buyers", "overlap_status": "suppressed" } ] }
    // capabilities: media_buy.audience_targeting.audience_overlap { max_comparisons: 5, minimum_overlap_size: 1000 }

    overlap reuses core/audience-size.json and is never exact. Where it departs from the RFC example: no share_of_* fields (a point share times a list size the buyer knows recovers the exact count; buyers derive interval shares), and suppressed never reports zero or says why. Sellers MUST NOT disclose below minimum_overlap_size, MUST pick the range from the true overlap plus a seller-keyed deterministic offset at least that wide, MUST give a stable answer per pair of memberships, and MUST charge every comparison to a principal-scoped budget before evaluating suppression. Security, protocol and product review changes are folded into the PR.

    Open questions

    1. Which audiences can be compared in 3.3? Buyer-synced audiences on the same account only, or also seller-owned segments? Recommend buyer-synced only: the buyer already holds those lists, so overlap adds only the seller's matching result. Seller segments let a buyer test whether a person is in a segment; if wanted later, add a separate optional field rather than widening compare_to.
    2. How hard is the anti-differencing rule? Recommend MUST for the keyed offset, minimum width and budget; SHOULD for "suppress near-duplicate audiences". The near-duplicate form needs per-version membership history and cannot be conformance-tested, and the product review knew of no platform that does it. Alternative: make all of it SHOULD for easier adoption, at the cost of a weaker floor.
    3. Standalone task or a mode on the RFC: audience lifecycle snapshots and status-change notifications #5966 snapshot read? Recommend standalone get_audience_overlap: the triage experts agreed, and a mode ties overlap to RFC: audience lifecycle snapshots and status-change notifications #5966's 3.4 timeline.

    Out of scope for this cut: seller-owned or third-party segments; buyer-side overlap from raw hashed lists (RFC question 4, a different trust boundary); share percentages; per-surface targetable_size; audiences from an external source; comparison across accounts; #7763 and #5966 integration; a reference seller and compliance storyboard (needed before it can graduate from experimental).

    3.3 call. Ship in 3.3 as experimental: optional and capability-gated, so no cost to non-implementers. If Q1 and Q2 are not settled in the freeze week, move it to 3.4 rather than ship a privacy rule nobody can pass.

  7. removed
    needs-wg-reviewBlocked on a working-group decision — surface in WG meeting agendas
    on Oct 28, 2026
  8. bokelley commented on Oct 31, 2026

    @bokelley
    ContributorAuthor

    Part 1 shipped: audience size as a range on sync_audiences in #8024 (3.3.0-beta.0).

    Part 2, get_audience_overlap (#8031), is an open draft, was never merged, and has no decision. The brief (#8023 (comment)) said to move it to 3.4 if the two privacy questions were not settled, and they weren't. Per the WG rule ('anything not decided moves to 3.4') only Part 2 moves to 3.4. Objections by Fri 2026-11-06. The milestone stays 3.3.0 for now; it will be moved after Nov 6.

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

    claude-triagedIssue has been triaged by the Claude Code triage routine. Remove to re-triage.media-buyIssue concerns the media-buy protocol domainrfcProtocol change — auto-adds to roadmap boardschemaJSON Schema source-of-truth: definitions, codegen artifacts, validation, hygiene

    Type

    No type

    Projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions