> ## 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](/docs/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](/docs/reference/release-notes#version-3-1-0), the authoritative version record, and [Migrating from 3.0 to 3.1](/docs/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](/docs/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](/docs/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](/docs/brand-protocol/tasks/verify_brand_claim) · [Security § Designated-task response signing](/docs/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](/docs/media-buy/media-buys/lifecycle#health-impairments) · [Snapshot and log contract](/docs/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](/docs/protocol/snapshot-and-log) · [Webhooks § Persistent channel contract](/docs/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](/docs/media-buy/task-reference/get_products#wholesale-feed-versioning) · [`get_products` § Cache layering](/docs/media-buy/task-reference/get_products#cache-layering) · [`get_signals` § Wholesale signals feed](/docs/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](/docs/media-buy/product-discovery/media-products#signal-targeting) · [Targeting § signal\_targeting\_groups](/docs/media-buy/advanced-topics/targeting#signal_targeting_groups) · [`get_signals`](/docs/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 v1 `format_ids` path still works. `ProductFormatDeclaration` carries an optional `v1_format_ref: [{agent_url, id}]` array so v2 declarations link to one or more v1 named formats — sellers can dual-emit during the 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](/docs/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](/docs/reference/migration/creative-transformers).
</Warning>

→ Spec: [`list_transformers`](/docs/creative/task-reference/list_transformers) · [`build_creative`](/docs/creative/task-reference/build_creative) · [`get_adcp_capabilities` § creative features / evaluator support](/docs/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](/docs/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](/docs/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](/docs/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](/docs/media-buy/advanced-topics/accountability#billing-measurement) · [`report_usage`](/docs/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](/docs/media-buy/media-buys/lifecycle#action-discovery) · [Product discovery § Proposals](/docs/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](/docs/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](/docs/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](/docs/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](/docs/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](/docs/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](/docs/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.
* **`idempotency_key` required on every AdCP task request** (#4399). Closes a longstanding gap where the spec said dedupe via idempotency\_key but didn't require buyers to send one. Sellers MAY reject requests missing the key after 3.1 GA.
* **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`](/docs/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](/docs/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](/docs/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](/docs/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](/docs/reference/versioning#migration-timeline).
