> ## Documentation Index
> Fetch the complete documentation index at: https://docs.adcontextprotocol.org/llms.txt
> Use this file to discover all available pages before exploring further.

# What's New in AdCP 3.1

> Adopter overview of AdCP 3.1 — distributed brand.json, dependency-impact webhooks, wholesale feed mirroring, release-precision version negotiation, brand response signing, canonical creative formats, vendor-attested measurement, action discovery, and more. Additive over 3.0; use wire pin 3.1 for the stable release.

<Info>
  **Status: 3.1 is released.** Current stable minor: 3.1, wire pin `adcp_version: "3.1"`. The 3.0 line remains supported for existing integrations pinned to `"3.0"`.
</Info>

AdCP 3.1 is a minor release. Every 3.1 change is **additive** over 3.0: new fields are optional, no required field was removed, no shape changed in a way that breaks a 3.0-conformant client. **No breaking changes for 3.0-conformant agents.** To adopt 3.1, pin production traffic to `"3.1"` after confirming the agent advertises that value in `supported_versions`. For implementation prep, use the [3.0 to 3.1 migration guide](/dist/docs/3.0.23/reference/migration/3-0-to-3-1).

This page is the curated adopter overview for the 3.1 minor release. Need the full 3.1 change list, per-PR detail, or migration tables? Use [Release Notes § Version 3.1.0](/dist/docs/3.0.23/reference/release-notes#version-3-1-0), the authoritative version record, and [Migrating from 3.0 to 3.1](/dist/docs/3.0.23/reference/migration/3-0-to-3-1), the role-based upgrade checklist. For long-form normative reference, follow the link on each headline below.

<Note>
  **Looking for the major v2 → v3 changes instead?** See [What's New in AdCP 3](/dist/docs/3.0.23/reference/whats-new-in-v3). This page covers the 3.0 → 3.1 minor delta only.
</Note>

## Final 3.1 feature set

If you only need the stable 3.1 shape, read this list first:

* **Versioning and validation.** Release-precision `adcp_version` pins, stable wire value `"3.1"`, `supported_versions` advertisement, envelope echo, and version-scoped verification badges.
* **Brand trust.** Distributed `brand.json`, sub-brand self-publishing through `brand_refs[]`, typed brand constraints, authorized operator scoping, and required signed responses for `verify_brand_claim` / `verify_brand_claims`.
* **Signals and product targeting.** Enriched signal definitions, `SignalRef`, wholesale signal enumeration, product-scoped `included_signals`, selectable `signal_targeting_options`, grouped buy-time `signal_targeting_groups`, and clarified owned-vs-marketplace conformance.
* **Wholesale feed mirroring.** Conditional fetch tokens, public/account `cache_scope`, product and signal wholesale-feed webhooks, and repair-by-read semantics for storefronts, registries, and federated marketplaces.
* **Creative formats.** Canonical `format_kind` declarations, publisher format catalogs, `v1_format_ref` dual-emission, hosted audio/video `duration_ms_exact` plus one-sided `duration_ms_range`, published-post references, and video placement semantics.
* **Creative generation.** `list_transformers`, account-scoped transformer selection, strict typed `config`, catalog and variant fan-out, advisory evaluator ranking over `creative-feature-result[]`, spend controls, content macros, free-text params, and per-output pricing receipts.
* **Media-buy operations.** Dependency impairments, buyer-visible `webhook_activity[]`, action discovery, proposal-lifecycle cleanup, currency-scoped product discovery, sponsored/social placement fields, and SI availability status.
* **Measurement and billing.** Vendor-attested `vendor_metric` goals, reach-window semantics, `viewability.viewed_seconds`, windowed delivery recovery, finality flags on delivery and usage, and out-of-band creative billing declarations.
* **Runtime hardening.** Request idempotency on every task, `IDEMPOTENCY_IN_FLIGHT`, open error-code decoding with recovery classification, auth error split, credential-in-payload rejection, webhook operation-id echo, and flat MCP envelope tolerance.
* **SDK and compliance readiness.** Named schemas for code generators, async-response refs, intentionally open payload markers, capability-gated storyboard coverage, packaged compliance bundle closure, and drift checks for release artifacts.

## Why upgrade

3.1 is the production-hardening release. 3.0 shipped the protocol surface — discovery, buy lifecycle, signals, creative library, brand identity. 3.1 closes the operational gaps that surfaced when real agents started running buys against real publishers:

* **You can debug your webhooks now.** Buyer agents inspect their own recent delivery fires via `webhook_activity[]` on `get_media_buys` — HTTP status, fire time, idempotency\_key — instead of guessing why their gateway returned 5xx.
* **You can see why a buy is impaired.** When a creative gets pulled, an audience is suspended, a catalog item is withdrawn, or an event source goes quiet, the buy's `health` flips to `impaired` and `impairments[]` lists every offline dependency with its package\_ids and remediation hint. Both as a snapshot on `get_media_buys` and as a push fire via `notification-type: impairment`.
* **You can mirror wholesale product feed and wholesale signals feed without burning bandwidth.** Conditional-fetch tokens (`if_wholesale_feed_version` — ETag-style), wholesale enumeration on signals (symmetric with products), and account-level wholesale feed webhooks let storefronts, federated marketplaces, and registries hold an up-to-date local replica of every connected agent's buyable products and signals without re-fetching unchanged feed payloads on every poll. Two-layer cache model (`cache_scope: "public" | "account"`) means most accounts dedupe into a single shared cache.
* **You can compose seller-offered signals on media products.** Products can declare `included_signals` for bundled/planned signal metadata, `signal_targeting_allowed` for package-level signal selection, optional inline `signal_targeting_options` for product-specific menus and pricing, and `signal_targeting_rules` for include/exclude/grouping limits. Buyers apply selections through grouped `targeting_overlay.signal_targeting_groups`, while wholesale products can omit inline options and use `get_signals` as the selectable signal feed.
* **You can bind goals to vendor-attested measurement.** Optimization goals can now reference `(vendor, metric_id)` pairs from real measurement vendors (DV, IAS, Adelaide, TVision, Lumen, Kantar, Upwave, Scope3, etc.) — not vendor-agnostic strings the seller can interpret however they want. The measurement-vendor catalog discovery surface itself is experimental in 3.1 (`measurement.core`).
* **Brand verification responses are attestable.** `verify_brand_claim` and `verify_brand_claims` now return a required `signed_response` payload-envelope JWS so downstream partners can retain and verify the brand's answer without relying on transport-session context.
* **You can pin a release and stop fighting drift.** Release-precision `adcp_version` (`"3.1"` for the stable release) on every request; sellers advertise their full `supported_versions` set and echo what they actually served. SDK constructor pins are now a real thing.
* **Sub-brands self-publish.** A brand can publish its own canonical `brand.json` on its own domain while the corporate house declares ownership via a portfolio pointer — same reciprocal pattern as IAB's `ads.txt` / `sellers.json`.
* **Creative formats have a canonical vocabulary.** 12 canonical `format_kind` values + a publisher catalog discovery surface + a projection-ref mechanism. Creative agents also advertise buildable canonical outputs through `creative.supported_formats[]`; targetable entries SHOULD include stable `capability_id` values for `build_creative` routing.
* **Hosted audio/video durations use one range vocabulary.** `duration_ms_range` now covers bounded and one-sided ranges (`[null, 60000]` for "up to 60s", `[15000, null]` for "at least 15s"); fixed slots should use `duration_ms_exact`. No separate min/max duration fields were added.
* **You can discover and select creative transformers.** A new `list_transformers` task surfaces the account-scoped, agent-offered build units — voices, models, styles — the creative analog of media-buy products, with an `expand_params` mode that returns the enumerable option values (e.g. your configured voices) on the same tool. `build_creative` selects one with `transformer_id`, configures it with a typed `config` bag (strict validation — unknown keys and out-of-range values are rejected with field-attributed errors; vendor knobs go in `ext`), and fans out across catalog items (`max_creatives`) and alternatives (`max_variants` + `variant_axis`, with `keep_mode` advisory). A new `BuildCreativeVariantSuccess` response member carries per-variant manifests, recommendation/rank, and a per-leaf pricing receipt. Pricing moves onto the transformer (`pricing_options` `per_unit`), settled via `report_usage`.
* **Video and audio inventory can declare execution semantics.** Products and placements can declare OpenRTB-aligned `video_placement_types` and `audio_distribution_types` so buyers can distinguish instream/accompanying/interstitial/standalone video and music streaming/FM-AM broadcast/podcast/catch-up/web-radio audio without changing buyer-facing channels.
* **Verification badges are version-scoped.** Public 3.1 badge issuance can run alongside active 3.0 badges; buyers pinned to a release read the matching badge version.
* **Action discovery and proposals are mechanical.** Products advertise `allowed_actions[]`; media buys carry `available_actions[]`; proposals use `proposal_status` to say whether finalize is still required before `create_media_buy(proposal_id)`.
* **Billing has finality.** Row-level `is_final` + `finalized_at` on delivery; matching `final` + `finalized_at` + `measurement_window` on `report_usage`. Buyers know when the number stops moving and can reconcile invoices.

Plus a long tail of error-code clarity, auth tightening, idempotency rules, TMP IdentityMatch upgrades, and `adagents.json` scaling work — see the headline list below.

## At a glance

| Area                                        | 3.0                                                                                                                                                   | 3.1                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`brand.json`**                            | Inline `brands[]` under a single house document                                                                                                       | Distributed: brands self-publish on their own domains; houses declare ownership via `brand_refs[]`; mutual-assertion trust; typed `trademarks[]`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| **Brand verification**                      | brand.json discovery only                                                                                                                             | `verify_brand_claim` / `verify_brand_claims` — federated authoritative verification with required `signed_response` payload-envelope JWS evidence                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| **Dependency impact**                       | No protocol surface for "a resource the buy depends on went offline"                                                                                  | `media_buy.health` + `impairments[]` snapshot; `notification-type: impairment` webhooks; `propagation_surfaces` capability; `impairment.coherence` compliance invariant                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| **Webhook foundation**                      | Specced per-feature                                                                                                                                   | One persistent-channel contract: snapshot/log duality, `notification_id` typed at envelope, per-account + per-resource subscription model                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| **Webhook observability**                   | No buyer-side delivery visibility                                                                                                                     | `webhook_activity[]` on `get_media_buys` — buyers self-service debug their own missed fires                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| **Wholesale feed mirroring**                | Re-fetch wholesale on every poll to detect changes                                                                                                    | ETag-style `wholesale_feed_version` / `if_wholesale_feed_version` conditional fetch on `get_products` / `get_signals`; `cache_scope` (public/account) on every response for two-layer cache layering                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| **Wholesale signals**                       | `get_signals` required `signal_spec` or deprecated `signal_ids` — no protocol-conformant way to enumerate the full priced signals feed                | `discovery_mode: "wholesale"` symmetric with `get_products buying_mode: "wholesale"`; paginated full wholesale signals feed enumeration with `signal_ref` identity and `pricing_options[]` populated                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| **Signal identity**                         | `SignalId` / `signal_id.source` (`catalog` or `agent`) was the primary signal identity shape                                                          | `SignalRef` / `signal_ref.scope` is canonical: `data_provider` for provider-published adagents.json signals, `signal_source` for source-native signals, and `product` for product-local media-buy options. Legacy `signal_id` remains accepted during the migration window                                                                                                                                                                                                                                                                                                                                                                     |
| **Product signal metadata**                 | `data_provider_signals` mixed legacy bundled metadata with no selectable package-level signal surface                                                 | `data_provider_signals` is deprecated. Use `included_signals` for non-selectable bundled/planned signals and `signal_targeting_options` for selectable product-scoped signal options with pricing, activation handles, defaults, and grouping hints                                                                                                                                                                                                                                                                                                                                                                                            |
| **Package signal targeting**                | No grouped buy-time surface for seller-offered named signals; storefronts overloaded audience fields or brief text                                    | `targeting_overlay.signal_targeting_groups`: top-level `operator: "all"` with child `any` include groups and `none` exclusion groups. Product `signal_targeting_rules` declares selection mode, direct vs seller-planned resolution, and group limits                                                                                                                                                                                                                                                                                                                                                                                          |
| **Wholesale feed webhooks**                 | None                                                                                                                                                  | Account-level webhooks registered via `sync_accounts.accounts[].notification_configs[]` carry `product.*` / `signal.*` / `wholesale_feed.bulk_change` change payloads with `applies_to.scope`; standard webhook signing and SSRF guards apply                                                                                                                                                                                                                                                                                                                                                                                                  |
| **Creative formats**                        | Format-by-name with per-publisher variants                                                                                                            | 12 canonical `format_kind` values + publisher catalog discovery (`adagents.json formats[]`) + `v1_format_ref` for dual emission + size flexibility (fixed / multi-size / responsive); creative agents advertise buildable outputs in `creative.supported_formats[]` and SHOULD include `capability_id` on targetable entries                                                                                                                                                                                                                                                                                                                   |
| **Hosted audio/video duration constraints** | Fixed durations or closed ranges only                                                                                                                 | `duration_ms_exact` for fixed slots; `duration_ms_range` for bounded and one-sided ranges such as `[null, 60000]` or `[15000, null]`; `[null, null]` is invalid                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| **Creative transformers**                   | `build_creative` built to a target format; render knobs implicit; format-attached `Format.input_format_ids` / `output_format_ids` / `pricing_options` | `list_transformers` discovers account-scoped build units (voices/models/styles) with an `expand_params` option-enumeration mode; `build_creative` selects one via `transformer_id`, takes a strictly-validated typed `config`, and fans out with `max_creatives` (per-catalog-item) + `max_variants`/`variant_axis` + advisory `keep_mode`; new `BuildCreativeVariantSuccess` member returns per-variant manifests, recommendation/`rank`, and per-leaf pricing receipts; rate lives on `transformer.pricing_options` (`per_unit`), settled via `report_usage`. The shipped `BuildCreativeSuccess` / `BuildCreativeMultiSuccess` are unchanged |
| **Version negotiation**                     | Integer `adcp_major_version` per request                                                                                                              | Release-precision `adcp_version` (e.g. `"3.1"` for the stable release) + `adcp.supported_versions` advertisement + envelope echo. Integer field remains as backwards-compatible legacy                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| **Optimization goals**                      | `event` + `metric` kinds, vendor-agnostic                                                                                                             | New `vendor_metric` kind — bind goals to vendor-attested metrics; `vendor_metric_optimization` per-product capability; three-precondition rejection rule                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| **Capability declarations**                 | Per-protocol basics                                                                                                                                   | New: `media_buy.buying_modes` for wholesale products, `signals.discovery_modes` for wholesale signals, `wholesale_feed_versioning`, `wholesale_feed_webhooks`, `supported_optimization_metrics`, `supported_target_kinds`, `media_buy.frequency_capping`, `media_buy.propagation_surfaces`, `creative.bills_through_adcp`, `capabilities.idempotency.in_flight_max_seconds`                                                                                                                                                                                                                                                                    |
| **Video and audio execution discovery**     | Video and audio products relied on free-text descriptions, placement names, or overloaded channels                                                    | `video_placement_types` and `audio_distribution_types` on products, placements, and `get_products.filters` using AdCP-native names for OpenRTB `video.plcmt` and `audio.feed` values                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| **Currency-scoped discovery**               | Buyers could filter budget currency but not the currencies they can transact in for media product pricing                                             | `pricing_currencies` on `get_products.filters`; sellers match product-level `pricing_options`, prune returned product pricing options to requested currencies, and exclude products whose mandatory product-scoped signal charges are not satisfiable in those currencies                                                                                                                                                                                                                                                                                                                                                                      |
| **Delivery reporting**                      | `reach` without window semantics; viewability is rate only                                                                                            | `reach_window` (cumulative / period / rolling); `viewability.viewed_seconds`; windowed pull recovery via `time_granularity` + `include_window_breakdown`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| **Billing surface**                         | Authority via `billing_measurement`; no finality marker                                                                                               | Row-level `is_final` + `finalized_at` on delivery; `final` + `finalized_at` + `measurement_window` on `report_usage`; `creative.bills_through_adcp` capability + `BILLING_OUT_OF_BAND` error                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| **Action discovery**                        | No structured action vocabulary on buys/products                                                                                                      | `allowed_actions[]` on Product (advisory template); `available_actions[]` on `get_media_buys` / `create_media_buy` / `update_media_buy`; proposal executability comes from `proposal_status`, not action-mode hacks                                                                                                                                                                                                                                                                                                                                                                                                                            |
| **Auth + security**                         | Single `AUTH_REQUIRED` error; no transport-channel rule                                                                                               | `AUTH_REQUIRED` split into `AUTH_MISSING` (correctable) + `AUTH_INVALID` (terminal); `CREDENTIAL_IN_ARGS` rejects credentials in request payload; request-signing `protocol_methods_*` namespace                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| **Idempotency**                             | Per-call replay only                                                                                                                                  | Rule 9 (concurrent retries) + Rule 10 (downstream reconciliation); `IDEMPOTENCY_IN_FLIGHT` error code; `capabilities.idempotency.in_flight_max_seconds`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| **Async envelope**                          | Two-shape submitted envelope for create-style tasks                                                                                                   | Three-shape envelope extended to `sync_audiences`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| **TMP IdentityMatch**                       | Basic request/response                                                                                                                                | `serve_window_sec` frequency-cap data flow; `seller_agent_url` required on request; optional `package_ids`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| **`adagents.json`**                         | Authoritative-only discovery                                                                                                                          | Managed-network scale (20 MB cap + `publisher_domains[]` compact form); ads.txt `managerdomain` fallback; tightened `revoked_publisher_domains[]` semantics                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| **Schema housekeeping**                     | —                                                                                                                                                     | Named reusable schemas for SDK generators; `x-adcp-hoist` opt-in marker; `x-adcp-open-payload` marker for intentionally open JSON payload fields; open-string compliance scenarios; `allowed_values` on text-asset-requirements; `vast_tracker` + `daast_tracker` asset types; optional `currency`/`total_budget` on create/update responses                                                                                                                                                                                                                                                                                                   |
| **Compliance suite**                        | Per-tool scenarios                                                                                                                                    | Capability-gated scenarios for `frequency_cap_enforcement`, `per_creative_attribution`, `metric_mode`, ROAS, `audience_buy_flow`, `event_dedup_flow`, `performance_buy_flow`, and `product_signal_targeting`; storyboard `requires` runtime gate; `comply_test_controller` sandbox gate; version-scoped badge evidence; packaged-reference validation for public compliance bundles                                                                                                                                                                                                                                                            |

### Capability-slot migration note

When using SDK helpers such as `definePlatform`, keep unsupported capability slots absent from `get_adcp_capabilities`. A missing slot is an honest scope boundary: storyboards and local test vectors targeting that slot should grade `not_applicable`, not failed.

Current workaround: prerelease/custom runners should add explicit skip gates for vectors outside the declared slot scope. The runner-side follow-up is tracked in `adcp-client#2244`.

## Headline features

### Distributed `brand.json` — sub-brands self-publish

A brand can now publish its **own** canonical `brand.json` on its own domain while the corporate house declares ownership via a portfolio pointer (`brand_refs[]`). The hierarchy stays one level deep — only houses declare ownership. Trust resolves via mutual assertion: both sides reciprocate. Identity attributes (logos, colors, tone, tagline) trust on the leaf's TLS alone; relationship trust (governance propagation, billable inclusion) gates on the reciprocal entry.

Same shape as IAB's `ads.txt` / `sellers.json` / `app-ads.txt` reciprocal-publication pattern, applied to brand identity. Plus: typed `trademarks[]` with optional `status`, `license_type`, `licensor_domain`, `countries`, `nice_classes` (cross-industry disambiguation). Compliance fields resolve strictest-of (brand-level can tighten, never weaken) while identity fields stay brand-wins.

→ Normative spec: [`brand.json` § Distributed publishing](/dist/docs/3.0.23/brand-protocol/brand-json#distributed-publishing) · PR [#4505](https://github.com/adcontextprotocol/adcp/pull/4505)

### `verify_brand_claim` / `verify_brand_claims` — federated brand verification

Two new brand-protocol tasks let partners ask a brand authoritatively whether a claim belongs to it: a brand agent published at the brand's own domain, queried by anyone who needs to verify trademark ownership, ad-creative claims, or asset rights. Federated by design — every brand agent answers for its own brand only. Reframes the email-based self-healing SHOULD from #4505 as a richer pull-based DRM-for-brand-identity surface.

RC4 locks the trust envelope: successful `verify_brand_claim` and `verify_brand_claims` responses require `signed_response`, a payload-envelope JWS over the canonical task-body response. The signature binds the answer to the designated task, resolved brand tenant, responding agent URL, caller/request hash, and `iat`/`exp` freshness window. Verifiers resolve the key with `adcp_use: "response-signing"` and reject any mismatch between the unsigned response fields and `signed_response.payload.response`.

→ Spec: [Brand Protocol § verify\_brand\_claim](/dist/docs/3.0.23/brand-protocol/tasks/verify_brand_claim) · [Security § Designated-task response signing](/dist/docs/3.0.23/building/by-layer/L1/security#designated-task-response-signing) · PRs [#4540](https://github.com/adcontextprotocol/adcp/pull/4540), [#4603](https://github.com/adcontextprotocol/adcp/pull/4603), [#5192](https://github.com/adcontextprotocol/adcp/pull/5192)

### Dependency-impact webhooks and snapshot coherence

When a resource a media buy depends on transitions to an offline state — an audience suspended, a creative suspended/rejected post-approval, a catalog item withdrawn, an event source quiet, a property depublished — buyers see it through two parallel surfaces:

* **Snapshot.** `media_buy.health` flips from `ok` to `impaired`; `media_buy.impairments[]` lists every offline resource with its package\_ids, transition, reason\_code, and remediation hint. The next `get_media_buys` read shows current truth.
* **Log.** `notification-type: impairment` webhooks fire with `notification_id = impairment_id` and the same payload shape, configured via `push_notification_config`.

Either path is complete; buyers reconcile via the snapshot when push and pull disagree. Sellers declare which surfaces they use via `capabilities.media_buy.propagation_surfaces` (`["snapshot"]`, `["webhook"]`, `["snapshot", "webhook"]`, or `["out_of_band"]`). The `impairment.coherence` compliance invariant grades the contract end-to-end (forward, inverse, and health-iff rules; relaxes on terminal-status buys).

→ Spec: [Media Buy Lifecycle § Health & impairments](/dist/docs/3.0.23/media-buy/media-buys/lifecycle#health-impairments) · [Snapshot and log contract](/dist/docs/3.0.23/protocol/snapshot-and-log) · RFC #2853 · PRs #4588, #4601, #4677, #4685, #4690

### Webhook foundation + buyer-side delivery visibility

3.1 codifies one persistent-channel contract for every push surface: snapshot is authoritative, push is at-least-once and unordered, dedupe via `idempotency_key`, correlate state via `notification_id` (now typed at the envelope level on `mcp-webhook-payload.json`), replay = re-read the snapshot. Future webhook RFCs reference the foundation instead of re-deriving it. The subscription model extends to per-account so creative-library-level events (creative state changes) fire even when no media buy directly references the creative.

For production debugging, buyers can opt-in to `webhook_activity[]` on `get_media_buys` — recent fires for the buys they see, with HTTP status, fire time, and `idempotency_key`. No more black-box "the publisher fired but my gateway returned 5xx and I can't see it." Pure self-service: buyers debug their own integration without operator round-trips.

→ Spec: [Snapshot and log contract](/dist/docs/3.0.23/protocol/snapshot-and-log) · [Webhooks § Persistent channel contract](/dist/docs/3.0.23/building/by-layer/L3/webhooks#persistent-channel-contract) · RFC #4582 · PRs #4601, #4701, #4730

### Wholesale feed mirroring — conditional fetch, wholesale signals, webhooks

Three companion proposals let consumers (storefronts, federated marketplaces, registries, agency brand stacks) maintain a near-real-time local mirror of every connected AdCP agent's buyable wholesale product feed and wholesale signals feed without burning bandwidth on per-poll wholesale fetches. Independent and complementary — agents MAY adopt any subset; consumers fall back to wholesale polling against agents that don't.

Terminology: this section uses **wholesale feed** for seller-side products and signals from `get_products` / `get_signals`. It is distinct from `sync_catalogs`, which uploads buyer-provided campaign input feeds to a seller account.

* **Conditional fetch (`if_wholesale_feed_version`).** Every `get_products` / `get_signals` response returns an opaque `wholesale_feed_version` token. Pass it back on the next call and the seller MAY short-circuit with `unchanged: true` — no product or signal payload, no per-page diff. ETag/HTTP semantics. Optional companion `pricing_version` for sellers that move rate cards independently of structural metadata; `if_pricing_version` requires `if_wholesale_feed_version` (schema-enforced via `dependencies`). Backward-compatible: pre-v3.1 agents that ignore the tokens just return full payloads.

* **Wholesale signals (`discovery_mode: "wholesale"`).** Callers can omit `signal_spec` / `signal_refs` / deprecated `signal_ids` and enumerate a signals agent's full priced signals feed, paginated. Symmetric with `get_products` `buying_mode: "wholesale"`, closing the gap that previously forced storefronts and marketplaces into hacky probe queries to mirror the signals feed.

* **Wholesale feed webhooks.** Account-level webhooks registered through `sync_accounts.accounts[].notification_configs[]` emit `product.{created,updated,priced,removed}`, `signal.{created,updated,priced,removed}`, and `wholesale_feed.bulk_change` notifications. Each webhook carries `core/wholesale-feed-webhook.json`: the actual changed product/signal payload or bulk-change summary, the post-change `wholesale_feed_version`, and `applies_to.scope` for cache invalidation. There is no polling event task; consumers repair missed or distrusted pushes through `get_products` / `get_signals`.

**Cache layering is the load-bearing design choice.** Every response declares `cache_scope: "public" | "account"` (schema-required — the safety property of the two-layer cache depends on it). When the request had no `account`, MUST be `"public"`. When the request had `account`, the seller declares `"public"` (this account prices off the rate card — buyer dedupes with the unauthenticated view) or `"account"` (custom overrides — buyer caches under the account key). Most accounts at most sellers price off the public layer, so a consumer holding N account caches typically dedupes into one public cache + a small number of overlays. Events carry `applies_to.scope` (with optional `account_ids[]`) so consumers invalidate the right cache layer — public events cascade to all overlays; account events touch only the named overlays. Sellers MAY downgrade an account from `"account"` back to `"public"` by returning a public-scope response on a previously-account-scoped tuple, signalling "this account no longer has overrides; drop the overlay."

**Security posture is honest.** The advisory-payload framing makes explicit that re-verifying a feed event against `get_products` / `get_signals` defends against transport tampering only — a compromised agent operator re-confirms its own lie. Operator-compromise defense lives in the existing trust anchors that gate spend (signed `create_media_buy` response, `adagents.json` pinned signing keys for marketplace-signal provenance), with content-signing of feed events deferred to the 4.0 R-1 root-of-trust track. Treat events as cheap mirror invalidation, not as the basis for any decision that commits dollars or authority.

Capability declarations: `wholesale_feed_versioning` (conditional fetch + `pricing_version_separate` + `cache_scope_account`), `wholesale_feed_webhooks` (webhook change payloads), `media_buy.buying_modes` and `signals.discovery_modes` (wholesale support). Agents that advertise `product.*` webhook events must also advertise wholesale `get_products`; agents that advertise `signal.*` events must also advertise wholesale `get_signals`; `wholesale_feed.bulk_change` must name only a feed family backed by one of those repair paths. JSON Schema for the webhook envelope at `core/wholesale-feed-webhook.json`, wrapping `core/wholesale-feed-event.json` (discriminated on event\_type with 9 branches + `appliesTo` / `removalReason` `$defs`).

→ Spec: [Wholesale feed webhooks](https://github.com/adcontextprotocol/adcp/blob/main/specs/wholesale-feed-webhooks.md) · [`get_products` § Wholesale feed versioning](/dist/docs/3.0.23/media-buy/task-reference/get_products#wholesale-feed-versioning) · [`get_products` § Cache layering](/dist/docs/3.0.23/media-buy/task-reference/get_products#cache-layering) · [`get_signals` § Wholesale signals feed](/dist/docs/3.0.23/signals/tasks/get_signals#wholesale-signals-feed) · PRs [#4761](https://github.com/adcontextprotocol/adcp/pull/4761) (conditional fetch), [#4762](https://github.com/adcontextprotocol/adcp/pull/4762) (wholesale signals), [#4763](https://github.com/adcontextprotocol/adcp/pull/4763) (feed webhooks), [#4767](https://github.com/adcontextprotocol/adcp/pull/4767) (cluster implementation)

### Product-scoped signal targeting — included vs selectable signals

3.1 adds a product-scoped signal targeting contract for seller-offered signals on media buys. This closes the gap between broad signal discovery and the actual package-level buy surface:

* **`included_signals`** describes signals already bundled into, included in, or seller-planned into a product. These are descriptive product metadata, not buyer-selectable controls.
* **`data_provider_signals` is deprecated.** It remains for compatibility as legacy bundled metadata, but new implementations use `included_signals` for non-selectable signals and `signal_targeting_options` for selectable ones.
* **`signal_targeting_allowed`** tells buyers the product has a package-level `signal_targeting_groups` surface. The default is false.
* **`signal_targeting_options`** is the inline selectable menu when the product needs product-specific pricing, activation handles, defaults/fixed selections, grouping hints, or a brief/refine-selected subset. Wholesale products can omit this field and use `get_signals` as the selectable feed.
* **`signal_targeting_rules`** declares the product-specific composition contract: direct targeting vs seller-planned resolution, optional/required/fixed selection, min/max counts, and grouping limits. This belongs on the product because a single seller may route products through different ad servers or planning layers.

Buyers apply selected signals with `packages[].targeting_overlay.signal_targeting_groups`. The portable baseline is intentionally simple: top-level `operator: "all"` with child `operator: "any"` groups for includes and child `operator: "none"` groups for exclusions. For binary signals, the signal expression uses `value: true`; exclusion is represented by the parent `none` group, not by `value: false`.

Signal identity is also normalized. New payloads use `signal_ref`:

* `scope: "data_provider"` + `data_provider_domain` + `signal_id` for signals defined in a provider's published adagents.json signals.
* `scope: "signal_source"` + `signal_source_url` + `signal_id` for source-native signals not published in upstream adagents.json signals.
* `scope: "product"` + `signal_id` for product-local options meaningful only inside the selected product/package context.

Legacy `SignalId` / `signal_id.source` remains accepted during the minor-version migration window, including on `get_signals`, audience selectors, legacy flat signal targeting, and wholesale signal events, but `SignalRef` is the canonical shape for new clients. Legacy flat `targeting_overlay.signal_targeting` remains schema-valid but deprecated; new package-level composition uses `signal_targeting_groups`.

→ Spec: [Product discovery § Signal targeting](/dist/docs/3.0.23/media-buy/product-discovery/media-products#signal-targeting) · [Targeting § signal\_targeting\_groups](/dist/docs/3.0.23/media-buy/advanced-topics/targeting#signal_targeting_groups) · [`get_signals`](/dist/docs/3.0.23/signals/tasks/get_signals) · PR [#5009](https://github.com/adcontextprotocol/adcp/pull/5009)

### Canonical creative formats — live, 12 canonicals, backwards-compatible

* **Published-post reference creatives.** Existing social/publisher posts are represented as canonical `video_hosted`, `image`, or `native_in_feed` formats with `asset_source: "publisher_owned_reference"` and a `published_post` slot. Products can declare `required_connections[]` for downstream platform grants such as advertiser account and publisher identity connections. Missing or expired grants use `AUTHORIZATION_REQUIRED` with `error.details.missing_connections[]`; recoverable dependency loss moves creatives to `suspended` rather than policy rejection. Catalog-driven retail media remains `sponsored_placement` with `source_catalog`.

Live in 3.1, additive over 3.0. Products carry `format_options[]`: a list of `ProductFormatDeclaration` entries with a `format_kind` discriminator from the canonical enum. **12 canonicals:** `image`, `html5`, `display_tag`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `image_carousel`, `native_in_feed`, `responsive_creative`, `sponsored_placement`, `agent_placement`. The enum also includes `custom` as the escape hatch for adopter-defined shapes that do not fit the canonicals. Three canonicals (`sponsored_placement`, `responsive_creative`, `agent_placement`) plus `custom` are tagged **experimental** within the framework; the remaining canonicals are non-experimental. The promotion queue for new canonicals is tracked in [#3666](https://github.com/adcontextprotocol/adcp/issues/3666).

**Backwards compatibility.** The `format_ids` path remains supported in 3.1, is deprecated beginning in AdCP 3.2, and is removed in AdCP 4.0. `ProductFormatDeclaration` carries an optional `v1_format_ref: [{agent_url, id}]` array so canonical declarations link to one or more named formats — sellers can dual-emit during the 3.x migration window. SDKs treat the enum as **open at parse time**: unknown future canonicals don't fail validation; SDKs may surface a local routing status such as `declared_only`, but that status is not a 3.1 wire field.

**Publisher catalogs.** `list_creative_formats(publisher_domain="…")` returns the publisher's authoritative format list by reading `<publisher_domain>/.well-known/adagents.json formats[]`, falling back to the AAO community mirror, then to agent-derived. Response carries `source: "publisher" | "aao_mirror" | "agent_derived"` so buyers know which tier produced the list.

**Size flexibility.** Display canonicals declare size in three modes: fixed (`width`+`height`), multi-size (`sizes: [{w,h}]` — mirrors OpenRTB `banner.format[]`), or responsive (`min_width`/`max_width`/`min_height`/`max_height`). Mutually exclusive.

**Hosted audio/video duration ranges.** `audio_hosted` and `video_hosted` use `duration_ms_exact` for fixed-duration slots and `duration_ms_range` for bounded or one-sided ranges. Either endpoint of `duration_ms_range` MAY be `null`: `[null, 60000]` means "up to 60 seconds" and `[15000, null]` means "at least 15 seconds". `[null, null]` is invalid, and `duration_ms_exact` takes precedence if both duration fields are present.

→ Spec: [Canonical formats](/dist/docs/3.0.23/creative/canonical-formats) · PRs [#3307](https://github.com/adcontextprotocol/adcp/pull/3307), [#4770](https://github.com/adcontextprotocol/adcp/pull/4770), [#5323](https://github.com/adcontextprotocol/adcp/pull/5323)

### Creative transformers — discover build capability, select, fan out, variant

3.1 introduces **transformers**: the creative analog of media-buy products. A transformer is an agent-offered, account-scoped, selectable unit of build capability — a voice, a model, a style, a director — with a typed configuration surface and per-account pricing. The set is account-specific and dynamic (your configured voices aren't a global enum), so discovery flows agent → buyer the same way `get_products` surfaces account-scoped inventory.

* **`list_transformers`** is the new discovery surface. It returns the transformers a creative agent offers for your account, each with `input_format_ids` / `output_format_ids`, a typed param schema, and (with `include_pricing`) a `per_unit` rate card. Its `expand_params` mode returns the account-scoped enumerable option values for a param — your actual configured voices, for example — on the same tool, instead of forcing you to hold a stale local list. Offered only by agents that set `creative.supports_transformers: true` in `get_adcp_capabilities`.

* **`build_creative` selects and configures a transformer.** Pass `transformer_id` to pick one — the target format(s) MUST be a subset of its `output_format_ids` — and a typed `config` bag keyed to the transformer's params. Validation is strict: the agent MUST reject unknown keys and out-of-range values with field-attributed errors; vendor-specific knobs go in `ext`.

* **Two fan-out axes.** `max_creatives` is the item/catalog axis: N distinct creatives, one per catalog item ("5 of 150" sampling) — distinct from `item_limit`, which caps the items used *within* one creative. `max_variants` (default 1) produces alternatives per creative along a `variant_axis` (`voice` | `theme` | `best_of_n` | `transformer_config` | `custom`, with optional `values[]` and `label`). `keep_mode` (`keep_all` | `keep_one` | `keep_some`) is advisory. Resolutions and quality levels are the **format** axis (`target_format_ids`), not variants.

* **New `BuildCreativeVariantSuccess` response member** (`oneOf` member 3 of 6) carries `creatives[]`, each `{ build_creative_id, catalog_item_ref?, variants[] }`; each variant is `{ build_variant_id, creative_manifest, variant_axis_value?, recommended, rank?, ... }` with a per-leaf pricing receipt (`pricing_option_id` + `vendor_cost` + `currency` + `consumption`). When the build reports cost (the aggregate `vendor_cost` is present), every produced leaf carries its own `vendor_cost` + `currency` (schema-enforced). Top-level `items_total` / `items_returned` plus an aggregate `vendor_cost`. The shipped `BuildCreativeSuccess` / `BuildCreativeMultiSuccess` are **unchanged**.

* **Best-of-N is variants + `keep_mode` + `recommended`/`rank`.** `build_variant_id` is its own namespace — never reuse `preview_id` (preview renders) or served `variant_id` (delivery). You **pay for all produced variants** (`per_unit` × N); keeping is a client act of trafficking the chosen `build_variant_id`(s). A kept variant lazily earns a `creative_id` (added to the library / first trafficked) which flows to `report_usage`. Per-**format** generation is atomic; per-**item** (catalog fan-out) is non-atomic.

* **Evaluator ranking reuses creative-feature discovery.** Agents with `creative.supports_evaluator: true` use the existing `get_adcp_capabilities.governance.creative_features` catalog as the evaluator feature-discovery surface. `rank_by`, `feature_requirement`, and `variants[].eval.features[]` all refer to that same feature vocabulary; `evaluator_id` is a pre-provisioned account preset, not an ID from that catalog. `feature_agent.agent_url` selects an allowlisted external scoring path and `feature_id` disambiguates the requested feature subject to the seller's accepted-verifier entry. Sellers SHOULD echo external judge usage per leaf with fields such as `eval.calls_used` / `eval.seconds_used` when `agent_url` evaluation runs under an `eval_budget`.

* **Pricing moves onto the transformer.** The rate lives on `transformer.pricing_options` (`per_unit`), echoed inline as a per-leaf receipt on `build_creative`, and settled via `report_usage`. `Format.pricing_options` is **deprecated** in favor of `transformer.pricing_options`.

<Warning>
  **Deprecations (3.1, removed in 4.0).** `Format.input_format_ids`, `Format.output_format_ids`, and `Format.pricing_options`, plus the `input_format_ids` / `output_format_ids` filters on `list_creative_formats`, are deprecated and all redirect to `list_transformers`. SDKs honor them through 3.1–3.x; they are removed at 4.0. Migrate format-attached input/output/pricing reads to `list_transformers`. Full migration (incl. the discovery-degradation, per-output-pricing, and best-of-N spend hazards): [Migration › Creative transformers](/dist/docs/3.0.23/reference/migration/creative-transformers).
</Warning>

→ Spec: [`list_transformers`](/dist/docs/3.0.23/creative/task-reference/list_transformers) · [`build_creative`](/dist/docs/3.0.23/creative/task-reference/build_creative) · [`get_adcp_capabilities` § creative features / evaluator support](/dist/docs/3.0.23/protocol/get_adcp_capabilities)

### Release-precision version negotiation — pin your release

Every request and response now carries `adcp_version` (release-precision: `"3.1"` for the stable release); sellers advertise their full `supported_versions` set on `get_adcp_capabilities` and echo the release they actually served at the envelope root. SDKs pin via a constructor option (`adcpVersion: "3.1"` JS, `adcp_version="3.1"` Python, `WithAdcpVersion("3.1")` Go) and emit both the new string and the integer `adcp_major_version` mirror for compatibility with sellers that only read the legacy field. The integer remains functional through all of 3.x — additive ship, no required changes for 3.0-conformant agents. `VERSION_UNSUPPORTED` is typed with `error.data.supported_versions[]` echoed so retry doesn't require an out-of-band lookup.

→ Spec: [Versioning § Version negotiation](/dist/docs/3.0.23/reference/versioning#version-negotiation) · PR [#3493](https://github.com/adcontextprotocol/adcp/pull/3493)

### Vendor-attested measurement — `vendor_metric` goals + per-product capabilities

Optimization goals now support a third `kind: "vendor_metric"` shape — bind goals to vendor-attested metrics like attention (DV, IAS, Adelaide, TVision, Lumen), panel-based brand lift (Kantar, Upwave, Cint), emissions (Scope3, Good-Loop), and retail-media partner metrics. Closes the gap where 3.0's vendor-agnostic enum values like `attention_seconds` were meaningless without a vendor binding.

Sellers declare per-product `vendor_metric_optimization` with `supported_metrics[]` (the `(vendor, metric_id)` pairs the bidding stack can steer toward). A three-precondition rejection rule on goal acceptance — discovery, capability, reporting coherence — ensures the goal is steerable AND reportable end-to-end. Plus seller-level `supported_optimization_metrics` and `supported_target_kinds` on `conversion_tracking` for capability-gated compliance scenarios.

The measurement-vendor catalog that defines `measurement.metrics[]` is experimental in 3.1. Vendors implementing it must declare `measurement.core` in `experimental_features`; buyers should treat catalog discovery as a 3.x experimental surface until measurement tasks and compliance storyboards are frozen.

→ Spec: [Optimization goals § `vendor_metric` kind](/dist/docs/3.0.23/media-buy/media-buys/optimization-reporting#vendor-metric-goals) · PRs [#4668](https://github.com/adcontextprotocol/adcp/pull/4668), [#4669](https://github.com/adcontextprotocol/adcp/pull/4669), [#4649](https://github.com/adcontextprotocol/adcp/pull/4649)

### Delivery reporting — `reach_window`, `viewed_seconds`, windowed pulls

Three additive surfaces close reporting gaps. **`reach_window`** declares the measurement window for `reach` and `frequency` (cumulative / period / rolling) — buyers MUST NOT sum reach across rows without it. **`viewability.viewed_seconds`** reports average in-view duration per measurable impression, the reporting-side counterpart to the `viewed_seconds` optimization goal. **Windowed pull recovery** on `get_media_buy_delivery` accepts `time_granularity` + `include_window_breakdown: true`, returning `windows[]` slices shape-aligned with `reporting_webhook` payloads at the same granularity — a buyer who missed a webhook fire reconstructs identical data by polling. Capability-scoped via `reporting_capabilities.windowed_pull_granularities`; sellers can honestly declare asymmetric webhook-vs-pull frequencies.

→ Spec: [Delivery metrics reference](/dist/docs/3.0.23/media-buy/task-reference/get_media_buy_delivery) · PRs [#4618](https://github.com/adcontextprotocol/adcp/pull/4618), [#4601](https://github.com/adcontextprotocol/adcp/pull/4601)

### Billing surface — authority, finality, and out-of-band

Two complementary changes close the billing-grade reporting story. **Authority + finality flags:** `get_media_buy_delivery` responses now carry row-level `is_final` + `finalized_at` on `media_buy_deliveries[*]` and on each `by_package[*]` — buyers know when a number stops moving and is safe for invoice reconciliation. Symmetric on `report_usage`: each usage record carries `final` (default `true`), `finalized_at`, and `measurement_window`. **`bills_through_adcp` + `BILLING_OUT_OF_BAND`:** creative agents declare via `capabilities.creative.bills_through_adcp` whether they bill on-protocol or out-of-band (flat license, SaaS, bundled enterprise — CM360 is the canonical case). Buyers pre-filter; sellers in out-of-band mode reject `report_usage` calls with the new `BILLING_OUT_OF_BAND` error rather than silently accepting.

→ Spec: [Billing measurement](/dist/docs/3.0.23/media-buy/advanced-topics/accountability#billing-measurement) · [`report_usage`](/dist/docs/3.0.23/accounts/tasks/report_usage) · PRs [#4735](https://github.com/adcontextprotocol/adcp/pull/4735), [#4561](https://github.com/adcontextprotocol/adcp/pull/4561)

### Action discovery — `allowed_actions` and `available_actions`

Structured action vocabulary for buy lifecycle mutations. Products advertise `allowed_actions[]` as an advisory template (which mutations the product *generally* supports, with `modes[]` and `allowed_statuses[]`). Media buys carry `available_actions[]` on `get_media_buys` / `create_media_buy` / `update_media_buy` responses — the current set of valid mutations for *this* buy in its current state. Buyers pre-flight which mutations are valid instead of issuing a call and getting `INVALID_STATE`. Finer-grained values added to `media-buy-valid-action` enum; legacy coarse values retained through 3.x for backwards compat (removed in 4.0).

3.1 removes the pre-GA `requires_proposal` action mode. Proposal lifecycle now has one path: `proposal_status` says whether finalization is required; `finalize` is seller commitment to firm pricing/terms/hold; `create_media_buy(proposal_id)` is buyer acceptance/execution. If an `update_media_buy` request exceeds the current quoted envelope, sellers return `REQUOTE_REQUIRED` instead of modeling a proposal-required action mode. Buyers with cached prerelease action metadata containing `requires_proposal` must discard it and re-read the current product or buy action surface. 3.1 does not define an amendment-quote artifact for updates.

→ Spec: [Media Buy Lifecycle § Action discovery](/dist/docs/3.0.23/media-buy/media-buys/lifecycle#action-discovery) · [Product discovery § Proposals](/dist/docs/3.0.23/media-buy/product-discovery/media-products#proposals) · PR [#4514](https://github.com/adcontextprotocol/adcp/pull/4514)

### Auth + security tightening

Four complementary changes: **`AUTH_REQUIRED` split** into `AUTH_MISSING` (correctable — retry with credentials) and `AUTH_INVALID` (terminal — credentials presented and rejected; rotate or escalate; do NOT auto-retry). Recovery classifications now match operator reality. **`CREDENTIAL_IN_ARGS`** new error code: sellers MUST reject requests that smuggle buyer-principal credentials into the task payload instead of the transport authentication channel — closes a prompt-injection exfiltration surface. **Request-signing `protocol_methods_*` namespace** — RFC 9421 signing scope tightened to the AdCP method surface only. **`comply_test_controller` sandbox gate** — every controller call MUST carry `account.sandbox: true` and the seller MUST verify against its persisted account record, not trust the field. Defense-in-depth boundary between sandbox and production.

→ Spec: [Error handling § Recovery Classification](/dist/docs/3.0.23/building/by-layer/L3/error-handling#recovery-classification) · PRs [#3739](https://github.com/adcontextprotocol/adcp/pull/3739), [#4057](https://github.com/adcontextprotocol/adcp/pull/4057), [#4326](https://github.com/adcontextprotocol/adcp/pull/4326), [#4382](https://github.com/adcontextprotocol/adcp/pull/4382)/[#4392](https://github.com/adcontextprotocol/adcp/pull/4392)

### Idempotency — Rules 9 + 10 + `IDEMPOTENCY_IN_FLIGHT`

Two new rules close production-edge cases. **Rule 9 (concurrent retries):** when a buyer retries before the original call has produced a cached response, the seller MAY return `IDEMPOTENCY_IN_FLIGHT` (new error code) instead of blocking — useful when the first call invokes a slow downstream system (SSP, ad server, payment provider). Buyers MUST treat it as transient and MUST NOT mint a fresh `idempotency_key`. **Rule 10 (downstream reconciliation):** explicit guidance on how buyers reconcile when an `IDEMPOTENCY_EXPIRED` response arrives and they have evidence the original succeeded — perform a natural-key check (e.g., `get_media_buys` by `context.internal_campaign_id`) before generating a fresh key. **`capabilities.idempotency.in_flight_max_seconds`** new capability — seller declares how long an in-flight call can take so buyers tune retry pacing.

→ Spec: [Calling an agent § Idempotency](/dist/docs/3.0.23/protocol/calling-an-agent) · PRs [#4402](https://github.com/adcontextprotocol/adcp/pull/4402), [#4409](https://github.com/adcontextprotocol/adcp/pull/4409)

### TMP IdentityMatch upgrades

Three additive changes: **`serve_window_sec`** new required field on responses (1–300 seconds) — router caches the eligibility decision for this many seconds before re-querying. Replaces the prior `ttl_sec` framing with frequency-cap-data-flow-aware semantics. **`seller_agent_url`** now required on requests so the router can route the decision back to the originating seller. **`package_ids`** moved from required to optional — routers can ask "is this user eligible at all?" without enumerating packages.

→ Spec: [TMP IdentityMatch implementation](/dist/docs/3.0.23/trusted-match/identity-match-implementation) · PRs [#4070](https://github.com/adcontextprotocol/adcp/pull/4070), [#3687](https://github.com/adcontextprotocol/adcp/pull/3687)

### `adagents.json` — managed-network scale, manager-domain fallback, revocation semantics

Three production-scale improvements. **Managed-network scale:** authoritative `adagents.json` now caps at 20 MB; managers publishing large agent networks switch to a compact `publisher_domains[]` form that lists owned domains without inlining every property. **Manager-domain fallback:** when a publisher's authoritative `adagents.json` is absent, crawlers fall back to the `managerdomain` declared in `ads.txt` — closes the discovery gap for S3 / CloudFront-hosted publishers that can't return a 404 directly. **Revocation semantics:** `revoked_publisher_domains[]` is now strictly time-bound — revocation is a published fact with a discoverable timestamp, not a silent removal. Tightens trust propagation across the managed network.

→ Spec: [`adagents.json` reference](/dist/docs/3.0.23/governance/property/adagents) · PRs [#4504](https://github.com/adcontextprotocol/adcp/pull/4504), [#4173](https://github.com/adcontextprotocol/adcp/pull/4173), [#4536](https://github.com/adcontextprotocol/adcp/pull/4536)

### Compliance suite — capability-gated scenarios

Capability-gated storyboard scenarios let sellers run *only* what they claim. New scenarios for `frequency_cap_enforcement`, `per_creative_attribution`, `metric_mode` + ROAS (using a `contains:` matcher), `audience_buy_flow`, `event_dedup_flow`, and `performance_buy_flow` (capability-gated CPA buys). Plus a new `requires` runtime gate on Storyboards that conditions execution on declared capabilities — no more all-or-nothing scenarios. The full set is enumerated on the [Compliance catalog](/dist/docs/3.0.23/building/verification/compliance-catalog).

**Late pre-GA compliance update:** money-moving seller specialisms now exercise baseline `sync_governance` registration before spend-committing flows: `sales-guaranteed`, `sales-non-guaranteed`, `sales-broadcast-tv`, `sales-catalog-driven`, `sales-social`, and the generative seller flow under `creative-generative`. This does not make every seller governance-aware; `governance-aware-seller` remains the opt-in claim for `check_governance` consultation and propagation. Existing pre-GA sellers claiming these specialisms must implement `sync_governance` registration and reject payloads with more than one `governance_agents` entry to remain conformant in 3.1 grading.

**Compliance packaging closure:** packaged compliance artifacts are now self-contained. Webhook receiver envelope vectors live under the versioned compliance tree, and release validation fails when authored vector/test-kit references do not resolve inside the packaged `/compliance/{version}/` bundle or protocol tarball. Signal conformance is also split by obligation: `signal-owned` and the baseline signals protocol are discovery-only (`get_signals`), while `signal-marketplace` requires `activate_signal`.

→ Spec: [Compliance catalog](/dist/docs/3.0.23/building/verification/compliance-catalog) · PRs [#4312](https://github.com/adcontextprotocol/adcp/pull/4312), [#4642](https://github.com/adcontextprotocol/adcp/pull/4642), [#4664](https://github.com/adcontextprotocol/adcp/pull/4664), [#4722](https://github.com/adcontextprotocol/adcp/pull/4722), [#4727](https://github.com/adcontextprotocol/adcp/pull/4727), [#4731](https://github.com/adcontextprotocol/adcp/pull/4731), [#5187](https://github.com/adcontextprotocol/adcp/pull/5187)

### Final-spec clarifications (WG-review batch)

Normative tightenings landed as the spec settled through prerelease validation. Mostly low-risk — adopters who'd already inferred reasonable defaults will continue working — but worth knowing about because the 3.1 grader checks them.

* **`PROPOSAL_NOT_FOUND` error code** (#4043). Completes the proposal-lifecycle error catalog (alongside `PROPOSAL_EXPIRED` and `PROPOSAL_NOT_COMMITTED`). Sellers MUST return it when a referenced `proposal_id` isn't recognized — wrong tenant, evicted from cache, or never finalized. Recovery: correctable.
* **Forward-compatible `error.code` decoding** (#4227). Receivers MUST treat `error.code` as an **open enum** — decode unknown codes without rejecting, classify recovery from `error.recovery`, default to `transient` when recovery is absent. Senders from 3.1 onward MUST populate `error.recovery` on every error. Unblocks additive-in-patch for error codes on future maintenance lines without breaking pinned-version receivers.
* **`get_products` keyed replay support** (#4399). Closes a longstanding gap where the spec said dedupe via idempotency\_key but did not account for a nominal read tool that can create tasks or finalize proposals. Sellers MUST accept keyed calls and apply the replay contract. AdCP 3.2 follows with additive split tools whose read and mutation contracts are explicit; legacy `get_products` remains key-optional throughout 3.x.
* **MCP tool wrappers MUST tolerate envelope fields** (#4399). The protocol envelope (`status`, `context_id`, `context`, `task_id`, `timestamp`, `replayed`, `adcp_error`, `governance_context`, `idempotency_key`) on MCP requests now goes through the wrapper layer instead of being rejected as "unexpected fields." Closes a wrapper-layer bug where adopters had to omit envelope fields to call MCP successfully.
* **MCP serialization normalization** (#2911). Drops `payload.required` from the protocol-envelope schema; adds the `context` field at envelope level; clarifies the flat-sibling MCP wire shape (envelope and body fields at the root, no nested `payload:` key). Adopters who'd implemented the de-facto flat shape are unaffected.
* **Idempotency replay returns historical snapshot** (#4371). When a buyer retries a stateful create call (e.g., `create_media_buy`) within the replay window, the seller MUST return the **historical snapshot** of state-tracking fields (`status`, `confirmed_at`, etc.) — not the current state. Otherwise an at-most-once retry mutates the response from underneath the buyer.
* **`refine[]` finalize-exclusivity + multi-finalize atomicity** (#4107). `get_products` `refine[]` semantics tightened: when any entry uses `action: "finalize"`, every entry in the array MUST be proposal-scoped with `action: "finalize"`; sellers reject mixes of finalize and non-finalize refinement with `INVALID_REQUEST`. Multi-finalize across multiple proposals is allowed only when the seller can guarantee atomic commit across every named proposal. Sellers that cannot guarantee that atomicity MUST reject the multi-finalize array with `MULTI_FINALIZE_UNSUPPORTED` (preferred) or `INVALID_REQUEST`, and buyers can then sequence single-proposal finalize calls if they accept the looser commit guarantee.
* **`pending_creatives` status disambiguation** (#4196). The description now explicitly states buyer action is required — sellers awaiting creative sync MUST surface what's missing (creative count, deadline) in the response message rather than just emitting the status enum value. Clarifies adopter-facing UX without changing the wire shape.
* **`notices` advisory channel on runner-output-contract** (#4418). Storyboard runners surface non-failure advisories (e.g., "agent still advertises a deprecated specialism, but the storyboard passed") through a structured `notices[]` field on the run output. Replaces ad-hoc `skip.detail` prose carrying advisory text — unparseable for graders and dashboards.
* **Governance body-level `status` renamed** (#4897). `check_governance` response: `status` → `verdict` (enum unchanged: `approved` / `denied` / `conditions`). `report_plan_outcome` response: `status` → `outcome_state` (enum unchanged: `accepted` / `findings`). `get_plan_audit_logs` entries cascade: `entries[].status` → `entries[].verdict` for consistency. Frees the top-level `status` key for the envelope task-status under MCP flat-on-the-wire serialization (#4876, #2911). Migration: rename the property in every emitter and consumer of these three response shapes; values do not change. Governance is an experimental surface per `x-status`, so this is a sanctioned 3.1 wire-shape adjustment ahead of GA.
* **Media-buy body-level `status` collision — additive-deprecate** (#4895). `create_media_buy` and `update_media_buy` success responses gain a new top-level `media_buy_status` field. The legacy top-level `status: MediaBuyStatus` form is marked `deprecated: true` and removed in **3.2** (#4906); compliance storyboards already require the new field. Nested `status` on `get-media-buys-response`, `get-media-buy-delivery-response`, and `core/media-buy.json` are out of scope here and addressed in the **4.0** cascade (#4905). Full migration: [Migration › `media_buy_status`](/dist/docs/3.0.23/reference/migration/media-buy-status).
* **Proposal lifecycle, signal privacy metadata, and measurement locks.** `proposal_status` is the per-proposal source of truth, `supports_proposals` is a conformance grading declaration, `finalize` is seller commitment, and `create_media_buy(proposal_id)` is buyer execution. The pre-GA `requires_proposal` action mode is removed in favor of `REQUOTE_REQUIRED` for updates outside the quoted envelope; buyers with cached prerelease action metadata containing `requires_proposal` must invalidate it and re-read the relevant product or buy surface. Signal definitions do not declare Global Privacy Control support, and projected `consent_basis` / `art9_basis` values on `get_signals` rows remain provider-declared signal-definition posture. The measurement catalog remains experimental and agents implementing it declare `measurement.core` in `experimental_features`.

→ Full batch shipped as one commit: PR [#4796](https://github.com/adcontextprotocol/adcp/pull/4796) (`4c124545f1`). See per-issue links for the full prose.

### Misc schema additions

**`media_buy.frequency_capping` capability declaration** (#4670) — seller declares which frequency-cap surfaces it honors.

**SDK generation ergonomics** (#5168) — common inline object and array item shapes now have stable core schema names so SDKs do not invent local wrapper names. `x-adcp-open-payload` marks intentionally open JSON payload fields for generators that need to preserve open maps rather than collapsing them into closed typed models.

**Open compliance scenario strings** (#5168) — `comply_test_controller.scenario`, `list_scenarios.scenarios[]`, and `compliance_testing.scenarios[]` are `string` rather than a closed enum; SDKs SHOULD parse them as strings and MAY layer known-value helpers locally. SDKs that previously generated a literal union for these fields should widen to `string` and keep default handling for unknown scenario names.

**`x-adcp-hoist` opt-in marker** (#4630) — canonically-shared object schemas declare themselves as hoistable into shared types. **`allowed_values` on text-asset-requirements** (#4333) — closed-set text assets (CTA, etc.) declare the allowed values so buyers can constrain generation. **`vast_tracker` + `daast_tracker` asset types** (#3051) — video and audio tracker assets. **Optional `currency` + `total_budget`** on `create_media_buy` / `update_media_buy` success responses (#4417). **Async envelope to `sync_audiences`** (#4571) — three-shape submitted envelope (Success / Error / Submitted) extended from create-style tasks.

→ See [Release Notes § Version 3.1.0](/dist/docs/3.0.23/reference/release-notes#version-3-1-0) for the per-PR detail.

## Adopter action

| If you are…                                                                                                                     | What you need to do                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| A 3.0-conformant production agent                                                                                               | Nothing required to remain on 3.0 — 3.1 changes are additive and a 3.0 client keeps working. When you are ready to claim 3.1, bump your SDK pin to `"3.1"` to pick up the new fields and emit `adcp_version` for forward-compatibility with future minors.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| A buyer running production campaigns                                                                                            | Bump your SDK and pass `adcpVersion: "3.1"` on construction after the seller advertises `"3.1"` in `supported_versions`. Implement `webhook_activity[]` reads when you suspect a missed fire. Read `media_buy.health` + `impairments[]` on every `get_media_buys` poll. Implement the `impairment.coherence` invariant in your reconciliation pipeline.                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| A seller running production buys                                                                                                | Surface `media_buy.health` + `impairments[]` whenever a referenced resource transitions offline. Declare `capabilities.media_buy.propagation_surfaces` honestly. Implement `webhook_activity[]` so buyers can debug their integration end-to-end without operator round-trips — buyer self-service is integration-friction reduction, not seller charity. Mark `is_final` on every delivery row so buyers know when to reconcile.                                                                                                                                                                                                                                                                                                                                                           |
| A seller claiming money-moving specialisms                                                                                      | Implement `sync_governance` on the account surface, register one governance agent per account using the brand/operator account reference, and reject registrations with multiple `governance_agents` entries. `check_governance` calls are still only required when also claiming `governance-aware-seller`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| A brand agent implementing `verify_brand_claim` or `verify_brand_claims`                                                        | Return `signed_response` on every success response. Publish a per-brand `adcp_use: "response-signing"` JWK, keep unsigned response fields byte-equivalent to `signed_response.payload.response`, and retain bulk audit evidence with the original request and result index.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| A seller using proposals or action discovery                                                                                    | Route proposal execution from `proposal_status`, not `supports_proposals` or action modes. Do not emit `requires_proposal`; use `REQUOTE_REQUIRED` when an update exceeds the quoted envelope and the buyer must rediscover terms or create a separate buy.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| A buyer with cached prerelease `action_mode: "requires_proposal"` values                                                        | Treat the cached value as unknown. Do not map it to `requires_approval`; that mode is an asynchronous human-approval gate and has no proposal artifact. If the cached value was used for proposal executability, re-read the proposal through `get_products` and branch on `Proposal.proposal_status`: `draft` means finalize first, `committed` is ready for `create_media_buy(proposal_id)`. If it came from product `allowed_actions[]` or buy `available_actions[]`, invalidate that cached action metadata and re-read the current product or buy action surface.                                                                                                                                                                                                                      |
| A prerelease 3.1 signals adopter that previously saw `data_subject_rights.gpc_honored`                                          | Remove the field from signal-level rights routing. 3.1 does not declare Global Privacy Control support on signal definitions; GPC remains a serve-time publisher/bidstream concern. Use provider policy, registry disclosures, and CCPA/state-law opt-out routing such as `ccpa_opt_out_url` for implementation guidance, but do not infer GPC handling from `data_subject_rights`.                                                                                                                                                                                                                                                                                                                                                                                                         |
| A sub-brand team that wants self-publish authority                                                                              | Stand up `/.well-known/brand.json` at your own domain as a Brand Canonical Document. Declare `house_domain: "<parent-house>"`. Ask the parent house team to reciprocate via `brand_refs[]`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| A consumer maintaining a wholesale product feed and wholesale signals feed mirror (storefront, federated marketplace, registry) | Bootstrap via `get_products buying_mode: "wholesale"` and/or `get_signals discovery_mode: "wholesale"`; persist the returned `wholesale_feed_version` + `cache_scope`. On subsequent polls send `if_wholesale_feed_version` and skip the full payload when the seller responds `unchanged: true`. If the agent declares `wholesale_feed_webhooks.supported`, register change webhooks through `sync_accounts.accounts[].notification_configs[]`; apply the webhook payload to the mirror and track `applies_to.scope` to invalidate the right cache layer (public vs. account overlay). Handle `wholesale_feed.bulk_change` or missed pushes by re-reading `get_products` / `get_signals`.                                                                                                  |
| A signals agent                                                                                                                 | Declare `signals.discovery_modes: ["brief", "wholesale"]` to expose your full priced signals feed for mirroring. Return `cache_scope` on every response (REQUIRED — schema-enforced). Pre-3.1 callers without `discovery_mode` continue to get brief-mode behavior. If you only claim `signal-owned`, `get_signals` discovery is enough; claim `signal-marketplace` only when you also implement `activate_signal`.                                                                                                                                                                                                                                                                                                                                                                         |
| A sales / signals agent serving wholesale feed mirrors at scale                                                                 | Declare `wholesale_feed_webhooks.supported: true` and support webhook registration through `sync_accounts.accounts[].notification_configs[]`, with standard account-level webhook signing, endpoint proof-of-control before activation, SSRF guards, account/caller authorization checks, and the notification-config fan-out cap. Keep `event_types[]` consistent with declared repair reads: `product.*` requires wholesale `get_products`, `signal.*` requires wholesale `get_signals`. The webhook emitter MUST include the actual changed product/signal payload or bulk-change summary and apply the same per-caller scope filter as your wholesale task at event-emission time — multi-tenant agents that can't reliably scope events per-principal MUST NOT declare the capability. |
| A measurement vendor (attention, brand lift, emissions, retail)                                                                 | Publish your experimental `measurement.metrics[]` catalog at your AdCP agent and declare `experimental_features: ["measurement.core"]`. Sellers declaring `vendor_metric_optimization` per product can now bind optimization goals to your `(vendor, metric_id)` pairs.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| A creative agent                                                                                                                | Declare `capabilities.creative.bills_through_adcp` honestly. If you bill out of band, reject `report_usage` calls with `BILLING_OUT_OF_BAND` rather than silently accepting.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| A creative agent offering transformers                                                                                          | Declare `creative.supports_transformers: true` and serve `list_transformers` (including the `expand_params` option-enumeration mode). On `build_creative`, validate the typed `config` strictly — reject unknown keys and out-of-range values with field-attributed errors, route vendor knobs through `ext`, and verify each target format is a subset of the transformer's `output_format_ids`. Return `BuildCreativeVariantSuccess` with per-leaf pricing receipts when fanning out across catalog items or variants. Move rate cards from `Format.pricing_options` to `transformer.pricing_options` and settle through `report_usage`.                                                                                                                                                  |
| A seller or creative agent declaring hosted audio/video formats                                                                 | Use `duration_ms_exact` for fixed-duration slots and `duration_ms_range` for bounded or one-sided ranges. `[null, 60000]` means "up to 60 seconds"; `[15000, null]` means "at least 15 seconds"; `[null, null]` is invalid. Do not add separate bare min/max duration fields.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| Maintaining SDK fixtures, compliance mirrors, or release packaging                                                              | Keep compliance vector and test-kit references inside the packaged `/compliance/{version}/` tree. Treat missing bundle-relative references as release blockers.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| An SDK author                                                                                                                   | Pin `published_version` to the published 3.1 artifact you bundle; emit `adcp_version` (release-precision string) plus `adcp_major_version` (integer mirror); normalize semver values to release-precision before wire emission (`"3.1.0"` → `"3.1"`); surface `VERSION_UNSUPPORTED` as a typed error rather than auto-downshifting.                                                                                                                                                                                                                                                                                                                                                                                                                                                         |

## Migration

**Bottom line: no breaking changes; additive only; you should bump.**

All 3.1 changes are **additive** over 3.0. New fields are optional, no required field was removed, and no shape changed in a way that breaks a 3.0-conformant client. A buyer running against a 3.1 seller without upgrading their SDK keeps working — they just won't see the new fields. A seller running 3.0 schemas against a 3.1 buyer keeps working — the buyer's new fields are silently ignored.

But the new surfaces solve real production problems, and the longer you stay on 3.0 the more you're operating without the production-hardening 3.1 added: webhook delivery debug, dependency-impact observability, billing finality flags, action discovery, vendor-attested measurement, release-precision negotiation. **Bump as soon as your SDK is ready.**

The one publisher-visible behavior change is on `brand.json` `trademarks[]`: free-text `status` / `countries` values now validate against typed enum / ISO 3166-1 alpha-2 — non-conforming values surface as schema errors. If your `trademarks[]` published unrestricted free text, normalize the values before claiming 3.1.

Prerelease 3.1 adopters should also update prerelease-only integrations: brand verification success responses now require `signed_response`; proposal/action code must remove the temporary `requires_proposal` action mode and use `REQUOTE_REQUIRED` for update repricing; buyers with cached `requires_proposal` values must invalidate and re-read the relevant proposal, product, or buy surface rather than mapping them to `requires_approval`; signal definitions do not declare Global Privacy Control support; `signal-owned` conformance is discovery-only while `signal-marketplace` remains the activation claim; hosted audio/video declarations should use one-sided `duration_ms_range` rather than separate bare min/max fields; and measurement catalog discovery remains experimental behind `measurement.core`. These were prerelease cleanup items before GA, not 3.0 breaking changes.

**Validator obligation for 3.1 SDKs.** The wholesale feed mirroring work tightens one shape within 3.1: `cache_scope` is schema-required on every `get_products` / `get_signals` response (the safety property of the two-layer cache depends on it — see [Cache layering](/dist/docs/3.0.23/media-buy/task-reference/get_products#cache-layering)). Pre-3.1 sellers correctly omit the field and remain conformant to their declared version. SDKs that validate strictly against the 3.1 schema MUST select the validator based on the server-declared `adcp_version` (the same release-precision mechanism 3.1 ships in version negotiation): for responses with `adcp_version` starting `3.0`, the 3.1 `cache_scope`-required constraint MUST be relaxed. This is a tightening within 3.1, not a 3.0 break — but SDKs that hardcode the 3.1 schema without version-pinned validation will reject correct 3.0 traffic. Version-pinned validation is the right pattern for every 3.x→3.(x+1) tightening; cache\_scope is the first time it's load-bearing.

For the per-PR detail, see [Release Notes § Version 3.1.0](/dist/docs/3.0.23/reference/release-notes#version-3-1-0). For the version-negotiation cadence and the 3.1 → 3.2 → 4.0 timeline, see [Versioning & Governance § Migration timeline](/dist/docs/3.0.23/reference/versioning#migration-timeline).
