Skip to main content

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.

Status: 3.1 beta. The spec is feature-complete for 3.1; the published @adcp/sdk and the runtime grader are advisory-only against the new surfaces during beta. 3.1 GA target: 2026-05-29. Adopters can pin adcp_version: "3.1-beta" today and migrate the pin to "3.1" on GA without code changes.
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. But schemas moved — 3.1 introduces meaningful new surfaces that solve production problems 3.0 left open. You should bump your pin to 3.1 as soon as your SDK is ready to pick up the new fields. For per-PR detail and migration tables, see Release Notes § Version 3.1.0. For long-form normative reference, follow the link on each headline below.
Looking for the major v2 → v3 changes instead? See What’s New in AdCP 3. This page covers the 3.0 → 3.1 minor delta only.

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 catalogs without burning bandwidth. Conditional-fetch tokens (if_catalog_version — ETag-style), wholesale enumeration on signals (symmetric with products), and per-agent change-feed events at GET /catalog/events let storefronts, federated marketplaces, and registries hold an up-to-date local replica of every connected agent’s catalog without re-fetching unchanged inventory on every poll. Two-layer cache model (cache_scope: "public" | "account") means most accounts dedupe into a single shared cache.
  • 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.
  • You can pin a release and stop fighting drift. Release-precision adcp_version ("3.1", "3.1-beta") 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 — no more per-publisher format spaghetti.
  • Action discovery is mechanical. Products advertise allowed_actions[]; media buys carry available_actions[]. Buyers pre-flight which mutations are valid instead of failing mid-flight.
  • 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

Area3.03.1
brand.jsonInline brands[] under a single house documentDistributed: brands self-publish on their own domains; houses declare ownership via brand_refs[]; mutual-assertion trust; typed trademarks[]
Brand verificationbrand.json discovery onlyverify_brand_claim / verify_brand_claims — federated authoritative verification (partners can ask the brand if a claim belongs to it)
Dependency impactNo 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 foundationSpecced per-featureOne persistent-channel contract: snapshot/log duality, notification_id typed at envelope, per-account + per-resource subscription model
Webhook observabilityNo buyer-side delivery visibilitywebhook_activity[] on get_media_buys — buyers self-service debug their own missed fires
Catalog mirroringRe-fetch wholesale on every poll to detect changesETag-style catalog_version / if_catalog_version conditional fetch on get_products / get_signals; cache_scope (public/account) on every response for two-layer cache layering
Wholesale signalsget_signals required signal_spec or signal_ids — no protocol-conformant way to enumerate the full priced catalogdiscovery_mode: "wholesale" symmetric with get_products buying_mode: "wholesale"; paginated full-catalog enumeration with pricing_options[] populated
Per-agent change feedNoneGET /catalog/events (UUID v7 cursor) emits product.* / signal.* / catalog.bulk_change events with applies_to.scope for cache invalidation; optional webhook subscriptions with HMAC anti-replay (signed timestamp + monotonic delivery seq) and SSRF guards
Creative formatsFormat-by-name with per-publisher variants12 canonical format_kind values + publisher catalog discovery (adagents.json formats[]) + v1_format_ref for dual emission + size flexibility (fixed / multi-size / responsive)
Version negotiationInteger adcp_major_version per requestRelease-precision adcp_version (e.g. "3.1") + adcp.supported_versions advertisement + envelope echo. Integer field remains as backwards-compatible legacy
Optimization goalsevent + metric kinds, vendor-agnosticNew vendor_metric kind — bind goals to vendor-attested metrics; vendor_metric_optimization per-product capability; three-precondition rejection rule
Capability declarationsPer-protocol basicsNew: supported_optimization_metrics, supported_target_kinds, media_buy.frequency_capping, media_buy.propagation_surfaces, creative.bills_through_adcp, capabilities.idempotency.in_flight_max_seconds
Delivery reportingreach without window semantics; viewability is rate onlyreach_window (cumulative / period / rolling); viewability.viewed_seconds; windowed pull recovery via time_granularity + include_window_breakdown
Billing surfaceAuthority via billing_measurement; no finality markerRow-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 discoveryNo structured action vocabulary on buys/productsallowed_actions[] on Product (advisory template); available_actions[] on get_media_buys / create_media_buy / update_media_buy responses
Auth + securitySingle AUTH_REQUIRED error; no transport-channel ruleAUTH_REQUIRED split into AUTH_MISSING (correctable) + AUTH_INVALID (terminal); CREDENTIAL_IN_ARGS rejects credentials in request payload; request-signing protocol_methods_* namespace
IdempotencyPer-call replay onlyRule 9 (concurrent retries) + Rule 10 (downstream reconciliation); IDEMPOTENCY_IN_FLIGHT error code; capabilities.idempotency.in_flight_max_seconds
Async envelopeTwo-shape submitted envelope for create-style tasksThree-shape envelope extended to sync_audiences
TMP IdentityMatchBasic request/responseserve_window_sec frequency-cap data flow; seller_agent_url required on request; optional package_ids
adagents.jsonAuthoritative-only discoveryManaged-network scale (20 MB cap + publisher_domains[] compact form); ads.txt managerdomain fallback; tightened revoked_publisher_domains[] semantics
Schema housekeepingx-adcp-hoist opt-in marker; allowed_values on text-asset-requirements; vast_tracker + daast_tracker asset types; optional currency/total_budget on create/update responses
Compliance suitePer-tool scenariosCapability-gated scenarios for frequency_cap_enforcement, per_creative_attribution, metric_mode, ROAS, audience_buy_flow, event_dedup_flow, performance_buy_flow; storyboard requires runtime gate; comply_test_controller sandbox gate

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 · PR #4505

verify_brand_claim / verify_brand_claims — federated brand verification

Three 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. → Spec: Brand Protocol § verify_brand_claim · PRs #4540, #4603

Dependency-impact webhooks and snapshot coherence

When a resource a media buy depends on transitions to an offline state — an audience suspended, a creative 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 · Snapshot and log contract · 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 · Webhooks § Persistent channel contract · RFC #4582 · PRs #4601, #4701, #4730

Catalog mirroring — conditional fetch, wholesale signals, per-agent change feed

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 catalog 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.
  • Conditional fetch (if_catalog_version). Every get_products / get_signals response returns an opaque catalog_version token. Pass it back on the next call and the seller MAY short-circuit with unchanged: true — no products/signals 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_catalog_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_ids and enumerate a signals agent’s full priced catalog, paginated. Symmetric with get_products buying_mode: "wholesale", closing the gap that previously forced storefronts and marketplaces into hacky probe queries to mirror signals catalogs.
  • Per-agent change feed. New GET /catalog/events endpoint with UUID-v7 cursor pagination emits product.{created,updated,priced,removed}, signal.{created,updated,priced,removed}, and catalog.bulk_change events. Optional webhook subscriptions deliver low-latency notifications with HMAC signed over {timestamp}\n{delivery_seq}\n{body} for anti-replay, monotonic per-subscription delivery sequence, jittered coalescing (60–300s independent per (subscription_id, event_id)), SSRF guards on subscription URL registration (HTTPS-only, RFC1918 / metadata-IP / DNS-rebind defense, TLS 1.2+ + cert chain validation), subscriptions scoped to creator principal with per-principal cap, secret rotation via PATCH with overlap window. New RETENTION_EXPIRED error for cursors the agent no longer holds — consumer re-bootstraps via wholesale.
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: catalog_versioning (conditional fetch + pricing_version_separate + cache_scope_account), catalog_change_feed (events / webhooks / retention window / event types), media_buy.buying_modes and signals.discovery_modes (wholesale support). JSON Schema for the event payload at core/catalog-event.json (discriminated on event_type with 9 branches + appliesTo / removalReason $defs) and the feed-poll response wrapper at core/catalog-events-response.json. → Spec: Catalog change feed · get_products § Catalog versioning · get_products § Cache layering · get_signals § Wholesale enumeration · PRs #4761 (conditional fetch), #4762 (wholesale signals), #4763 (change feed), #4767 (cluster implementation)

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

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, responsive_creative, sponsored_placement, agent_placement, custom — plus native_in_feed. Three of those (sponsored_placement, responsive_creative, agent_placement) are tagged experimental within the framework; the rest are stable. The promotion queue for new canonicals is tracked in #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; they’re surfaced as runtime_status: declared_only for routing purposes. 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. → Spec: Canonical formats · PRs #3307, #4770

Release-precision version negotiation — pin your release

Every request and response now carries adcp_version (release-precision: "3.1", "3.1-beta"); 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 · PR #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. → Spec: Optimization goals § vendor_metric kind · PRs #4668, #4669, #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 · PRs #4618, #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 · report_usage · PRs #4735, #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). → Spec: Media Buy Lifecycle § Action discovery · PR #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 · PRs #3739, #4057, #4326, #4382/#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 · PRs #4402, #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 · PRs #4070, #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 · PRs #4504, #4173, #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. → Spec: Compliance catalog · PRs #4312, #4642, #4664, #4722, #4727, #4731

Final-spec clarifications (WG-review batch)

Ten normative tightenings landed as the spec settled into beta. Mostly low-risk — adopters who’d already inferred reasonable defaults will continue working — but worth knowing about because the grader will check them at GA.
  • 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 one entry uses action: "finalize", that entry MUST be the only one in the array — multi-finalize is rejected. Multi-finalize across multiple proposals goes through separate get_products calls. Atomicity guarantee: finalize either succeeds completely or leaves the proposal unchanged.
  • 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.
→ Full batch shipped as one commit: PR #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. 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 for the per-PR detail.

Adopter action

If you are…What you need to do
A 3.0-conformant production agentNothing required — 3.1 changes are additive and a 3.0 client keeps working. But you should bump your SDK pin to 3.1 as soon as it’s available to pick up the new fields and emit adcp_version for forward-compatibility with future minors.
A buyer running production campaignsBump your SDK; pass adcpVersion: "3.1" (or your release) on construction. 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 buysSurface 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 sub-brand team that wants self-publish authorityStand 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 catalog mirror (storefront, federated marketplace, registry)Bootstrap via get_products buying_mode: "wholesale" and/or get_signals discovery_mode: "wholesale"; persist the returned catalog_version + cache_scope. On subsequent polls send if_catalog_version and skip the full payload when the seller responds unchanged: true. If the agent declares catalog_change_feed.supported, subscribe via webhook or poll GET /catalog/events; track applies_to.scope on events to invalidate the right cache layer (public vs. account overlay). Handle RETENTION_EXPIRED and catalog.bulk_change by re-bootstrapping.
A signals agentDeclare signals.discovery_modes: ["brief", "wholesale"] to expose your full priced catalog for mirroring. Return cache_scope on every response (REQUIRED — schema-enforced). Pre-3.1 callers without discovery_mode continue to get brief-mode behavior.
A sales / signals agent serving catalog mirrors at scaleDeclare catalog_change_feed.supported: true and run GET /catalog/events with UUID v7 cursors, ≥7-day retention. Optionally serve webhook subscriptions per the anti-replay (signed timestamp + monotonic delivery seq), SSRF (HTTPS-only, RFC1918/metadata-IP/DNS-rebind guards), ownership-scoped CRUD, and per-principal-cap rules in specs/catalog-change-feed.md. The feed MUST apply the same per-caller scope filter as your wholesale endpoint 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 measurement.metrics[] catalog at your AdCP agent. Sellers declaring vendor_metric_optimization per product can now bind optimization goals to your (vendor, metric_id) pairs.
A creative agentDeclare 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.
An SDK authorPin published_version to a 3.1 release; emit adcp_version (release-precision string) plus adcp_major_version (integer mirror); normalize semver values to release-precision before wire emission ("3.1.0-beta.1""3.1-beta.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 the 3.1 GA cut. Validator obligation for 3.1 SDKs. The catalog-sync cluster 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). 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. For the version-negotiation cadence and the 3.1 → 3.2 → 4.0 timeline, see Versioning & Governance § Migration timeline.