Migrating from 3.2 to 3.3
3.3 is a minor release over 3.2. 3.3 adds optional fields and one optional capability object. No stable field, enum value, or task is removed, renamed, or deprecated. Schema-valid 3.2 messages stay schema-valid unless they already used a name 3.3 now defines:media_buy.features.catalog_ingestion (previously any boolean was admissible; it is now an object that requires catalog_management: true), dooh_placement_attributes.location and dooh_inventory_summary (previously open extension points, now strict shapes; lon, not lng), and the now-reserved ext.adcp. A caller opts into 3.3 only after the peer advertises the exact release in supported_versions. Existing 3.2 integrations that need nothing else can stay pinned to "3.2".
Two changes narrow what a conformant implementation may do, so they are the part of this guide to read first: discovery never provisions an account and signature verification tightens. Everything else is an optional declaration or a clarification.
For the feature narrative, start with What’s new in AdCP 3.3. For prerelease artifacts and SDK timing, use the 3.3 beta program.
Upgrade checklist
Required to claim 3.3 behavior
Discovery tasks never provision an account
A seller that provisions accounts lazily, instead of exposingsync_accounts, now provisions only on a provisioning task: one that commits spend or creates account-owned resources, such as create_media_buy, buy_products, accept_proposal, sync_creatives, sync_catalogs, sync_event_sources, or activate_signal. Discovery and negotiation tasks MUST NOT create or activate an account, or accept a seller’s default terms for the buyer. They include:
get_products,list_productsget_signalsrequest_proposals,refine_proposals,decline_proposals
account that does not resolve returns ACCOUNT_NOT_FOUND on these tasks, even where account is optional, instead of being dropped in favor of public results. A seller that provisions lazily instead of exposing sync_accounts MAY answer a discovery task for a complete natural key it would provision, as if that account existed, without creating it. A seller that exposes sync_accounts returns ACCOUNT_NOT_FOUND. list_accounts is the exception: its account field is a filter, so a key that matches nothing returns an empty list. A seller that provisioned on get_products or on the “first account-scoped request” was conformant under 3.2 and is not under 3.3. Request and response schemas are unchanged; the required side effect is not.
Buyers. Omit account and send brand until the account is provisioned, and provision before request_proposals when you intend to accept. Returning ACCOUNT_NOT_FOUND for an unresolved account was already required and was clarified in 3.2.2. It has one recovery everywhere: provision a natural key with sync_accounts (or the seller’s lazy path), or verify an account_id. An account_id echoed by sync_accounts for a buyer-declared account is a seller handle, not something to assume is accepted as an AccountRef.
The advisory storyboard media_buy_seller/unprovisioned_account_reference grades this for sellers that expose sync_accounts. Its checks become required at runner capability 14.1.0. See Account references before provisioning.
Signature verification tightens
Request signing, webhook signing, governance JWSiss, designated-task response signing, and rights attestations now share one agent-resolution algorithm. Some signatures that verified under 3.2 can fail:
- A pinned
signing_keyskey that is not published in the agent’s JWKS is rejected. The pin narrows the accepted keys for sell-side signatures the agent makes about that publisher’s inventory, such as webhooks; it never adds a key and does not apply to the agent’s other traffic. Where a message covers several publishers, the key must match every pin that applies. Publish every pinned key in the agent’s JWKS. - A pin entry without complete public-key parameters for its
kty, such as one with only akid, matches nothing. Matching is by RFC 7638 thumbprint. key_originsis now checked for every webhook signer that publishesbrand_json_url, including pinned keys, and origin binding (the agent URL’s eTLD+1 equals thebrand_json_urleTLD+1, or a House Portfolioauthorized_operators[]entry covers it) applies to webhook signers.- Webhook discovery starts from
identity.brand_json_url. When it is present, buyers MUST use it and MUST NOT fall back. The 3.x fallback reads/.well-known/brand.jsonat the agent’s host, or its eTLD+1, only for sellers that omit the field. Every resolution failure rejects withwebhook_signature_key_unknown. - Agent URLs match by canonical URL on every surface. Webhook discovery and governance
issno longer compare byte for byte, and the schema’sagent_url_matchverifier constraint is nowcanonical. Twoagents[]entries that match canonically are ambiguous only if they differ intypeor in resolved JWKS source; entries that agree on both count once. - Governance buyer identity for signed requests is the exact operator record that agent resolution selected, and
issmust match exactly one governance-typed entry. - Cached or onboarding mappings must be confirmed against the agent’s
brand_json_urland re-confirmed within the brand.json cache lifetime.
Clarifications
These restate existing rules and need no wire change.adagents.jsonagent URLs.authorized_agents[].urlis the agent’s full protocol endpoint URL including its path, for examplehttps://agent.example.com/mcp, not its origin. List one entry for each agent URL when MCP and A2A are served at different paths. See Agent URL matching.- Trust and verification docs. Only
verify_brand_claimandverify_brand_claimscarry a signed response payload;get_productsresponses are not signed. A missingauthorized_operatorslisting leads to rejection or manual review, never automatic approval. Useagents[]withtype: "brand"or"rights"; thebrand_agentandrights_agentfields were already deprecated. ext.adcpis reserved. The namespace belongs to the AdCP working group. Vendors that usedext.adcpfor their own data should move to their own namespace.- Interim
reporting_webhook.operation_id. Docs-only guidance lets 3.2.1 integrations carryoperation_idonreporting_webhookas an opt-in field. The core field is a proposal still in working-group review. See webhooks. - Already in 3.2.2. The account-reference and trust-docs clarifications were published as documentation-only changes in 3.2.2, so 3.2.2 adopters have seen them.
Adopt by capability
Catalog ingestion declarations
Optional. A seller declaresmedia_buy.features.catalog_ingestion with accepted catalog types, ingestion_modes (feed_url, inline_items), feed formats, content identifier types, max_inline_items_per_request, and item_status_reporting (per_item or feed_level). accepted_catalog_types, ingestion_modes, and item_status_reporting are required. supported_feed_formats is required whenever feed_url is listed, and an empty array means no external formats. max_inline_items_per_request is valid only when inline_items is listed. supported_content_id_types is optional: absent means unknown, empty means none of the named types. Declaring it requires catalog_management: true; a legacy boolean-only catalog_management declaration stays valid. A declaring seller MUST reject an unlisted catalog type, ingestion mode, or feed format, and an explicit content_id_type outside a declared supported_content_id_types list, with UNSUPPORTED_FEATURE before mutation, including on dry_run. Inline items beyond max_inline_items_per_request fail with INVALID_REQUEST. Under per_item reporting, terminal results for created, updated, or unchanged catalogs, and discovery-only reads, carry one item_issues entry per item, so the unique item_id count equals item_count. Use a fresh idempotency_key for each current read, because replays are historical snapshots, and dry-run statuses are previews. Buyers treat an absent declaration as unknown, not unrestricted. See sync_catalogs.
DOOH location and venue summary
Optional. Adddooh_placement_attributes.location (lat, lon, address) to a concrete single-screen placement, and dooh_inventory_summary.venue_counts[] to a network product. The field is lon, not lng. All location fields are optional, and location is disclosure metadata that must not change placement_id or identifiers[]. Each venue_counts row requires geo_level, geo_code, and count; metro rows also require system, native postal rows need country plus system, and country and region rows omit system and country. See DOOH.
ext.adcp.opportunity
Optional. Cooperating get_products integrations can carry an opportunity binding under ext.adcp.opportunity, declared by listing adcp in extensions_supported. Durable association needs an authorized account scope from the request’s account or unambiguous authenticated context, and a mismatched ID on refine is rejected with INVALID_REQUEST; compact refine_proposals inherits the association and does not accept the extension. The typed schema is not in any 3.2.x artifact and links through latest until the first 3.3 release. The core opportunity field on get_products is a proposal still in working-group review, not part of 3.3 today. See AdCP opportunity extension.
Deprecations
None. 3.3 deprecates no stable field, task, or enum value. Existing 3.2 deprecations keep their published removal windows.Compliance and grading
media_buy_seller/unprovisioned_account_referenceis new and advisory until runner capability14.1.0.- Advanced delivery reporting runs only for sellers that advertise wholesale discovery, and proposal-finalization replay only for sellers that advertise idempotency support.
- Hosted grading of governance-aware sellers is covered in AAO Verified.
sales-guaranteedexercises the documented polling path and no longer requires an optional task webhook.- The acceptance-policy discovery scenario provisions and selects its own sandbox account.
Rollback
Stay on, or return to,"3.2": every 3.3 declaration is optional, so removing it restores 3.2 behavior. The two tightenings above are the exception: they apply as soon as you claim 3.3 behavior, so keep a 3.2-pinned route until your discovery and verification paths pass against the beta.