From 303e90c782f074e704ff30dd776e4c86c8e61b3d Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Tue, 19 May 2026 05:11:10 -0700 Subject: [PATCH 1/2] =?UTF-8?q?feat(canonical-formats):=203.1=20follow-ups?= =?UTF-8?q?=20=E2=80=94=20Taboola=20fixture,=20Pinterest=20worked=20exampl?= =?UTF-8?q?e,=20IAB=20Native=20vocab,=20slot-enum=20fix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes three GA-blocking items from PR #3307 expert review, plus a latent slot-enum bug surfaced by the new fixture: - Latent slot asset_type enum gap: _base.json was missing pixel_tracker / vast_tracker / daast_tracker. Any product with explicit tracker slots (including native_in_feed's default slot set) failed validation. Added all three to the enum and to the size-mutex no-size-semantics branch. - Taboola Content Recommendation reference fixture exercising all 12 native_in_feed default slots including pixel_tracker triple. Brings the canonical fixture suite to 15 fixtures across the 12 canonicals. - Pinterest disambiguation worked example in canonical-formats.mdx covering Promoted Pin (native_in_feed), Collection (sponsored_placement), Idea Pin (image_carousel), Shopping Pin (sponsored_placement single_item). Closes the routing ambiguity Pia / Nastassia flagged. - Five IAB OpenRTB Native 1.2 Data Asset vocab additions: likes (type 4), downloads (type 5), saleprice (type 7), address (type 9), secondary_body_text aliased to desc2 (type 10). phone_number annotated with its IAB type 8 mapping. Migration doc updated: fixture count 12 → 13. Co-Authored-By: Claude Opus 4.7 (1M context) --- .changeset/canonical-formats-3.1-followups.md | 23 ++++++ docs/creative/canonical-formats-migration.mdx | 2 +- docs/creative/canonical-formats.mdx | 15 ++++ .../taboola_content_recommendation.json | 82 +++++++++++++++++++ .../source/core/asset-group-vocabulary.json | 29 ++++++- .../source/formats/canonical/_base.json | 8 +- 6 files changed, 153 insertions(+), 6 deletions(-) create mode 100644 .changeset/canonical-formats-3.1-followups.md create mode 100644 static/examples/products/canonical/taboola_content_recommendation.json diff --git a/.changeset/canonical-formats-3.1-followups.md b/.changeset/canonical-formats-3.1-followups.md new file mode 100644 index 0000000000..6e317fd77c --- /dev/null +++ b/.changeset/canonical-formats-3.1-followups.md @@ -0,0 +1,23 @@ +--- +"adcontextprotocol": patch +--- + +**Canonical formats 3.1 follow-ups — fixture, vocab, Pinterest disambiguation.** + +Closes three of the GA-blocking follow-ups identified in PR #3307 expert review, plus a latent slot-enum bug surfaced by the new fixture: + +- **Latent slot `asset_type` enum gap fixed** in `_base.json`. The canonical-formats slot enum was missing `pixel_tracker`, `vast_tracker`, and `daast_tracker` — meaning any product carrying explicit tracker slots (including the `native_in_feed` default slots) failed validation. Added all three to the enum and to the size-mutex if/then "no size semantics" branch. Discovered by the new native_in_feed fixture; would have hit any 3.1 adopter shipping explicit tracker slots. + + +- **`native_in_feed` reference Product fixture** at `static/examples/products/canonical/taboola_content_recommendation.json`. Realistic Taboola US Content Recommendation product covering all 12 native_in_feed default slots — title, body_text, main_image (1200×627 / 1080×1080), cta with closed enum, advertiser_name, sponsored_label, landing_page_url, display_url, rating, plus impression / viewability / click `pixel_tracker`. CPC pricing, hourly+daily reporting, v1_format_ref points at `native_content`. Brings the canonical fixture suite to 13 (one per canonical, plus generative Veo on video_hosted). + +- **Pinterest disambiguation worked example** in `docs/creative/canonical-formats.mdx`. Spells out which Pinterest product routes to which canonical: Promoted Pin → `native_in_feed`, Pinterest Collection → `sponsored_placement` (catalog-keyed), Idea Pin → `image_carousel`, Shopping Pin → `sponsored_placement` (fanout_mode: single_item). The cleave is asset-bundle vs catalog-row composition; same logic applies to Snap Story / Snap Collection, TikTok TopView / TikTok Collection, etc. Closes the routing ambiguity flagged by Pia + Nastassia at GA review. + +- **5 new IAB OpenRTB Native 1.2 Data Asset vocab entries** in `asset-group-vocabulary.json`: `likes` (type 4), `downloads` (type 5), `saleprice` (type 7), `address` (type 9), `secondary_body_text` aliased to `desc2` (type 10). Promoting these from `slots_override` extensions to canonical vocab tightens validation for app-install and e-commerce native units. `phone_number` description also annotated with its IAB type 8 mapping. + +**Migration doc** updated: fixture count 12 → 13, dropped the "native_in_feed fixture follows in a subsequent PR" placeholder. + +Remaining 3.1 follow-ups tracked separately: +- **SDK codegen (TypeScript + Python)** — multi-week build, the gating dependency for adopter consumption. Schemas shippable today; typed-tagged-union ergonomics arrive with codegen. +- **`native_in_feed` conformance storyboard** — multi-phase YAML to extend `static/compliance/source/protocols/creative/index.yaml` with native sync_creatives + preview coverage. +- **Adopter cohort communication** — Slack-back to the round-1 pilot cohort (Pia, Nastassia, others) confirming nobody has a catalog-less sponsored_placement mid-pilot before GA. diff --git a/docs/creative/canonical-formats-migration.mdx b/docs/creative/canonical-formats-migration.mdx index f34662ca0a..18f12ff124 100644 --- a/docs/creative/canonical-formats-migration.mdx +++ b/docs/creative/canonical-formats-migration.mdx @@ -101,7 +101,7 @@ Most of AdCP doesn't change. v2 builds on the existing primitives: **Dual emission during the migration window**: Products MAY carry `format_ids`, `format_options`, or BOTH; at least one is required (the schema enforces this via an `anyOf`, not `oneOf`). The recommended seller pattern is to author once and let the SDK project to both wire shapes via the [v1↔v2 canonical mapping registry](https://adcontextprotocol.org/schemas/v3/registries/v1-canonical-mapping.json), so every buyer reads what it knows. When both shapes are present on a product, the two MUST refer to the same underlying format declaration — the `format_options[i]` must narrow the canonical that `format_ids[i]` resolves to via the registry. SDKs that derive both shapes from one source guarantee this invariant; SDKs that hand-author both MUST treat divergence as a build error and refuse to emit. Buyers prefer `format_options` when both are present; treat `format_ids` as fallback for v1-only buyers. Sellers whose v1 named formats have no clean v2 projection ship `format_ids` only for those products until they add an explicit `canonical` declaration on the v1 format (see "v1 → v2 canonical mapping" below) — the SDK MUST NOT emit `format_options` for non-projectable formats. -For 12 fully-validated reference Product fixtures spanning the 12 canonical formats (native_in_feed fixture follows in a subsequent PR) — Meta Reels (`video_hosted` vertical), IAB MREC (`image` 300×250), NYTimes HTML5 (`html5`), GAM 3P display tag (`display_tag`), Meta Carousel (`image_carousel`), YouTube VAST pre-roll (`video_vast`), podcast 30s host-read (`audio_hosted`), Triton DAAST audio (`audio_daast`), Amazon Sponsored Products (`sponsored_placement`), Google PMax (`responsive_creative`), ChatGPT brand mention (`agent_placement`), Veo 15s generative video (`video_hosted` with `synthesis_nondeterministic` + `provenance_required`) — plus 1 `get_products` response fixture exercising bundled extensions, see `static/examples/products/canonical/` and `static/examples/get_products_responses/canonical/`. The Veo fixture exercises `synthesis_nondeterministic: true` and `provenance_required: true`. Each fixture passes `npm run test:canonical-fixtures`. +For 13 fully-validated reference Product fixtures spanning all 12 canonical formats — Meta Reels (`video_hosted` vertical), IAB MREC (`image` 300×250), NYTimes HTML5 (`html5`), GAM 3P display tag (`display_tag`), Meta Carousel (`image_carousel`), YouTube VAST pre-roll (`video_vast`), podcast 30s host-read (`audio_hosted`), Triton DAAST audio (`audio_daast`), Amazon Sponsored Products (`sponsored_placement`), Taboola Content Recommendation (`native_in_feed`), Google PMax (`responsive_creative`), ChatGPT brand mention (`agent_placement`), Veo 15s generative video (`video_hosted` with `synthesis_nondeterministic` + `provenance_required`) — plus 1 `get_products` response fixture exercising bundled extensions, see `static/examples/products/canonical/` and `static/examples/get_products_responses/canonical/`. The Veo fixture exercises `synthesis_nondeterministic: true` and `provenance_required: true`. Each fixture passes `npm run test:canonical-fixtures`. ## v1 → v2 canonical mapping diff --git a/docs/creative/canonical-formats.mdx b/docs/creative/canonical-formats.mdx index 3f1533361c..2b6ef3c2cc 100644 --- a/docs/creative/canonical-formats.mdx +++ b/docs/creative/canonical-formats.mdx @@ -861,6 +861,21 @@ A retail-media product that accepts a catalog reference plus a brief, and render `item_production_model: seller_pre_rendered_from_brief` says: for each catalog item, the seller renders ONE creative using the brief plus the catalog item's structured fields (title, image, price). `fanout_mode: per_item` says each item gets its own ad in delivery. Together they capture the multi-output generative pattern (1 brief × N items → N ads) under the existing `sponsored_placement` canonical. +## Worked example — Pinterest: which canonical? + +Pinterest is the canonical disambiguation example because a single platform sells inventory under two structurally different shapes. Buyer agents reading these products must route to the matching canonical or the manifest won't render. + +| Pinterest product | Canonical | Why | +|---|---|---| +| **Promoted Pin** (sponsored single Pin in the home/search feed) | `native_in_feed` | Asset-bundle composition — buyer ships title + image + body + landing URL; Pinterest's renderer assembles the Pin to match feed look-and-feel. No catalog feed; the buyer-supplied bundle IS the creative. | +| **Pinterest Collection** (single hero image + 3 product thumbnails sourced from a catalog) | `sponsored_placement` | Catalog-keyed — buyer ships a `source_catalog` reference (and an optional `hero_asset`); Pinterest composes the per-item thumbnails by reading product catalog rows. `fanout_mode: multi_item_in_creative` distinguishes Collection from per-item retail-media SP. | +| **Pinterest Idea Pin** (multi-page swipeable native unit) | `image_carousel` | Multi-card swipe shape — the carousel canonical handles polymorphic per-card slots. | +| **Pinterest Shopping Pin** (single product Pin keyed to a catalog row) | `sponsored_placement` with `fanout_mode: single_item` | Single rendered creative pulled from one catalog item — still catalog-keyed composition, just one item per ad. | + +The cleave is **asset-bundle vs catalog-row composition**, not "is it Pinterest." Same logic applies to Snap Story Ad (native_in_feed) vs Snap Collection (sponsored_placement), TikTok TopView (native_in_feed via `applies_to_channels: ["social"]`) vs TikTok Collection (sponsored_placement), and so on. Buyer agents route on the composition shape; the publisher's brand of the surface is incidental. + +A buyer agent reading a `format_kind: native_in_feed` product knows to assemble the title/image/body/CTA bundle from its own creative pool. Reading `format_kind: sponsored_placement`, it knows to attach a catalog feed and let the seller compose per-item. The discriminator carries the decision; no per-platform branching needed. + ## Validation flow — `validate_input` Buyers can dry-run a manifest against canonicals and/or specific products without committing to a render. The buyer's manifest below is a v2 manifest (`format_kind: "video_hosted"`); the slot key is the canonical's `asset_group_id` (`video_main`); the asset value carries its `asset_type` discriminator. The buyer asks `validate_input` to check both the canonical contract AND the seller's specific product narrowing in a single round-trip: diff --git a/static/examples/products/canonical/taboola_content_recommendation.json b/static/examples/products/canonical/taboola_content_recommendation.json new file mode 100644 index 0000000000..c8efe52d52 --- /dev/null +++ b/static/examples/products/canonical/taboola_content_recommendation.json @@ -0,0 +1,82 @@ +{ + "$schema": "/schemas/core/product.json", + "product_id": "taboola_content_recommendation_us", + "name": "Taboola — US Content Recommendation", + "description": "In-feed native content-recommendation placement on Taboola's publisher network (CNN, USA Today, Bloomberg, MSN, and ~2 000 other premium properties). Buyer ships an IAB-shaped native asset bundle (title, thumbnail, body, display_url, optional rating); Taboola's renderer composes the unit to match each publisher's feed look-and-feel. Distinct from `sponsored_placement` (catalog-keyed retail-media — Taboola has no per-item catalog feed) and from `responsive_creative` (algorithmic combinator — Taboola uses a single asset bundle, not an asset pool). Renderer-fired pixel trackers (impression, viewability, click) ship as `pixel_tracker` assets.", + "publisher_properties": [ + { + "publisher_domain": "taboola.com", + "selection_type": "all" + } + ], + "channels": [ + "display" + ], + "delivery_type": "non_guaranteed", + "pricing_options": [ + { + "pricing_option_id": "cpc_open_auction", + "pricing_model": "cpc", + "currency": "USD", + "floor_price": 0.1 + } + ], + "reporting_capabilities": { + "available_reporting_frequencies": [ + "hourly", + "daily" + ], + "expected_delay_minutes": 30, + "timezone": "America/New_York", + "supports_webhooks": true, + "available_metrics": [ + "impressions", + "clicks", + "spend", + "ctr", + "viewability", + "conversions" + ], + "date_range_support": "date_range" + }, + "format_options": [ + { + "format_kind": "native_in_feed", + "display_name": "Taboola Sponsored Content — Premium", + "applies_to_channels": ["display"], + "params": { + "title_max_chars": 80, + "body_text_max_chars": 140, + "cta_max_chars": 25, + "cta_values": ["LEARN_MORE", "READ_MORE", "SHOP_NOW", "SIGN_UP", "DOWNLOAD"], + "main_image_sizes": [ + { "width": 1200, "height": 627 }, + { "width": 1080, "height": 1080 } + ], + "max_image_file_size_kb": 500, + "image_formats": ["jpg", "jpeg", "png", "webp"], + "ssl_required": true, + "asset_source": "buyer_uploaded", + "buyer_asset_acceptance": "accepted", + "composition_model": "deterministic", + "slots": [ + { "asset_group_id": "title", "asset_type": "text", "required": true, "max_chars": 80 }, + { "asset_group_id": "body_text", "asset_type": "text", "required": false, "max_chars": 140 }, + { "asset_group_id": "main_image", "asset_type": "image", "required": true }, + { "asset_group_id": "cta", "asset_type": "text", "required": false, "max_chars": 25 }, + { "asset_group_id": "advertiser_name", "asset_type": "text", "required": true }, + { "asset_group_id": "sponsored_label", "asset_type": "text", "required": false }, + { "asset_group_id": "landing_page_url", "asset_type": "url", "required": true }, + { "asset_group_id": "display_url", "asset_type": "text", "required": false }, + { "asset_group_id": "rating", "asset_type": "text", "required": false }, + { "asset_group_id": "impression_tracker", "asset_type": "pixel_tracker", "required": false }, + { "asset_group_id": "viewability_tracker", "asset_type": "pixel_tracker", "required": false }, + { "asset_group_id": "click_tracker", "asset_type": "pixel_tracker", "required": false } + ] + }, + "v1_format_ref": [ + { "agent_url": "https://creative.adcontextprotocol.org", "id": "native_content" } + ] + } + ] +} diff --git a/static/schemas/source/core/asset-group-vocabulary.json b/static/schemas/source/core/asset-group-vocabulary.json index 798fea1549..1eb9c92cd6 100644 --- a/static/schemas/source/core/asset-group-vocabulary.json +++ b/static/schemas/source/core/asset-group-vocabulary.json @@ -127,7 +127,34 @@ "phone_number": { "description": "Phone number for click-to-call placements (E.164 preferred).", "asset_type": "text", - "typical_use": "Google call-only ads, Bing call extensions, click-to-call retail/local placements." + "typical_use": "Google call-only ads, Bing call extensions, click-to-call retail/local placements. IAB OpenRTB Native 1.2 Data Asset type 8." + }, + "likes": { + "description": "Social-proof like / favorite count rendered on the creative (e.g., \"12.4K likes\").", + "asset_type": "text", + "typical_use": "Social-feed native units, app-install native showing aggregate engagement. IAB OpenRTB Native 1.2 Data Asset type 4." + }, + "downloads": { + "description": "Download count rendered on the creative (e.g., \"1M+ downloads\").", + "asset_type": "text", + "typical_use": "App-install native units (Google Play, App Store ad placements, in-feed app promos). IAB OpenRTB Native 1.2 Data Asset type 5." + }, + "saleprice": { + "description": "Sale / discounted price rendered alongside the original `price` slot (e.g., struck-through original + sale price).", + "asset_type": "text", + "typical_use": "Retail / e-commerce native showing a discount or promotion. IAB OpenRTB Native 1.2 Data Asset type 7. Pair with `price` (regular price) and optionally `promo_code`.", + "aliases": ["sale_price", "discount_price"] + }, + "address": { + "description": "Physical address rendered on the creative (e.g., for local-business or click-to-visit placements).", + "asset_type": "text", + "typical_use": "Local-business native, retail-store ads, click-to-directions placements. IAB OpenRTB Native 1.2 Data Asset type 9." + }, + "secondary_body_text": { + "description": "Secondary descriptive copy rendered below the primary `body_text` (extended description).", + "asset_type": "text", + "typical_use": "Long-form native units where the renderer accepts a second descriptive paragraph (Yahoo Native long-form, content-recommendation extended cards). IAB OpenRTB Native 1.2 Data Asset type 10 (\"desc2\").", + "aliases": ["desc2", "body_text_2", "secondary_description"] }, "promo_code": { "description": "Promotional / offer code rendered on the creative.", diff --git a/static/schemas/source/formats/canonical/_base.json b/static/schemas/source/formats/canonical/_base.json index 3a222c922b..3db7ac5da1 100644 --- a/static/schemas/source/formats/canonical/_base.json +++ b/static/schemas/source/formats/canonical/_base.json @@ -62,8 +62,8 @@ }, "asset_type": { "type": "string", - "enum": ["image", "video", "audio", "text", "markdown", "url", "html", "css", "javascript", "vast", "daast", "webhook", "brief", "catalog", "zip", "card", "object"], - "description": "Discriminator selecting the asset schema this slot accepts. SDK codegen uses this to type the slot value. `card` is the multi-card carousel element type (see card-asset.json). `object` is a last-resort fallback for structured non-asset inputs that don't fit any primitive asset_type — prefer specific types whenever possible." + "enum": ["image", "video", "audio", "text", "markdown", "url", "html", "css", "javascript", "vast", "daast", "webhook", "brief", "catalog", "zip", "card", "object", "pixel_tracker", "vast_tracker", "daast_tracker"], + "description": "Discriminator selecting the asset schema this slot accepts. SDK codegen uses this to type the slot value. `card` is the multi-card carousel element type (see card-asset.json). `pixel_tracker` / `vast_tracker` / `daast_tracker` are the renderer-fired measurement-tracker primitives — see `/schemas/core/assets/pixel-tracker-asset.json` and the VAST / DAAST tracker schemas. `object` is a last-resort fallback for structured non-asset inputs that don't fit any primitive asset_type — prefer specific types whenever possible." }, "required": { "type": "boolean", @@ -118,9 +118,9 @@ "then": { "not": { "required": ["max_chars"] } } }, { - "$comment": "Non-size asset types (url, catalog, html, css, javascript, webhook, daast, vast, card, object) reject both — there's no defined per-slot size semantics on those.", + "$comment": "Non-size asset types (url, catalog, html, css, javascript, webhook, daast, vast, card, object, pixel_tracker, vast_tracker, daast_tracker) reject both — there's no defined per-slot size semantics on those.", "if": { - "properties": { "asset_type": { "enum": ["url", "catalog", "html", "css", "javascript", "webhook", "daast", "vast", "card", "object"] } }, + "properties": { "asset_type": { "enum": ["url", "catalog", "html", "css", "javascript", "webhook", "daast", "vast", "card", "object", "pixel_tracker", "vast_tracker", "daast_tracker"] } }, "required": ["asset_type"] }, "then": { From 279aa73bc72bbd1a00763051be038aa27d081b9c Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Tue, 19 May 2026 05:21:51 -0700 Subject: [PATCH 2/2] fix(canonical-formats): close vocab gap for native_in_feed default slots MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Product expert + code reviewer converged on a real gap: the native_in_feed canonical's default slots referenced asset_group_ids (title, main_image, icon, advertiser_name, sponsored_label) that weren't in asset-group-vocabulary.json. The flagship Taboola reference fixture inherited those IDs and validated only via the soft-warning policy — wrong precedent for adopters copying the pattern. - Added five core IAB OpenRTB Native 1.2 vocab entries: title (Title Asset type 1), main_image (Image Asset type 3 main), icon (Image Asset type 1), advertiser_name (sponsoredBy field), sponsored_label (renderer disclosure string). Each carries IAB type annotation and adopter-friendly aliases (e.g., title aliases headline; main_image aliases image_main / hero_image). - price ↔ saleprice cross-reference added to the price description so buyer agents reading `price` alone discover the discount-rendering pattern. - Pinterest Idea Pin row updated to call out video-per-page (carousel canonical's polymorphic image/video per-card slot handles it). - Changeset bumped patch → minor (additive canonical vocab is minor, not patch). - Migration doc fixture count fixed: 14 reference Product fixtures (was miscounted 13). Co-Authored-By: Claude Opus 4.7 (1M context) --- .changeset/canonical-formats-3.1-followups.md | 10 +++--- docs/creative/canonical-formats-migration.mdx | 2 +- docs/creative/canonical-formats.mdx | 2 +- .../source/core/asset-group-vocabulary.json | 36 +++++++++++++++++-- 4 files changed, 41 insertions(+), 9 deletions(-) diff --git a/.changeset/canonical-formats-3.1-followups.md b/.changeset/canonical-formats-3.1-followups.md index 6e317fd77c..d2b76b7521 100644 --- a/.changeset/canonical-formats-3.1-followups.md +++ b/.changeset/canonical-formats-3.1-followups.md @@ -1,5 +1,5 @@ --- -"adcontextprotocol": patch +"adcontextprotocol": minor --- **Canonical formats 3.1 follow-ups — fixture, vocab, Pinterest disambiguation.** @@ -13,11 +13,13 @@ Closes three of the GA-blocking follow-ups identified in PR #3307 expert review, - **Pinterest disambiguation worked example** in `docs/creative/canonical-formats.mdx`. Spells out which Pinterest product routes to which canonical: Promoted Pin → `native_in_feed`, Pinterest Collection → `sponsored_placement` (catalog-keyed), Idea Pin → `image_carousel`, Shopping Pin → `sponsored_placement` (fanout_mode: single_item). The cleave is asset-bundle vs catalog-row composition; same logic applies to Snap Story / Snap Collection, TikTok TopView / TikTok Collection, etc. Closes the routing ambiguity flagged by Pia + Nastassia at GA review. -- **5 new IAB OpenRTB Native 1.2 Data Asset vocab entries** in `asset-group-vocabulary.json`: `likes` (type 4), `downloads` (type 5), `saleprice` (type 7), `address` (type 9), `secondary_body_text` aliased to `desc2` (type 10). Promoting these from `slots_override` extensions to canonical vocab tightens validation for app-install and e-commerce native units. `phone_number` description also annotated with its IAB type 8 mapping. +- **10 new IAB OpenRTB Native 1.2 vocab entries** in `asset-group-vocabulary.json`. + - Five Data Asset additions: `likes` (type 4), `downloads` (type 5), `saleprice` (type 7), `address` (type 9), `secondary_body_text` aliased to `desc2` (type 10). + - Five core-native vocab additions surfaced by product-expert review — the `native_in_feed` canonical's default slots referenced these but the vocab didn't have entries, leaving the flagship fixture authoring against non-canonical IDs: `title` (Title Asset type 1; `headline` is the alias for the singular case, distinct from `headlines` pool used by responsive_creative), `main_image` (Image Asset type 3 main, with `image_main`/`hero_image` aliases), `icon` (Image Asset type 1), `advertiser_name` (the IAB `sponsoredBy` field), `sponsored_label` (renderer disclosure string). + - `phone_number` description annotated with IAB type 8; `body_text` annotated with IAB type 2. `price` description updated to call out the price ↔ saleprice discount-rendering convention. -**Migration doc** updated: fixture count 12 → 13, dropped the "native_in_feed fixture follows in a subsequent PR" placeholder. +**Migration doc** updated: 14 reference Product fixtures, dropped the "native_in_feed fixture follows in a subsequent PR" placeholder. Remaining 3.1 follow-ups tracked separately: - **SDK codegen (TypeScript + Python)** — multi-week build, the gating dependency for adopter consumption. Schemas shippable today; typed-tagged-union ergonomics arrive with codegen. - **`native_in_feed` conformance storyboard** — multi-phase YAML to extend `static/compliance/source/protocols/creative/index.yaml` with native sync_creatives + preview coverage. -- **Adopter cohort communication** — Slack-back to the round-1 pilot cohort (Pia, Nastassia, others) confirming nobody has a catalog-less sponsored_placement mid-pilot before GA. diff --git a/docs/creative/canonical-formats-migration.mdx b/docs/creative/canonical-formats-migration.mdx index 18f12ff124..49194d7361 100644 --- a/docs/creative/canonical-formats-migration.mdx +++ b/docs/creative/canonical-formats-migration.mdx @@ -101,7 +101,7 @@ Most of AdCP doesn't change. v2 builds on the existing primitives: **Dual emission during the migration window**: Products MAY carry `format_ids`, `format_options`, or BOTH; at least one is required (the schema enforces this via an `anyOf`, not `oneOf`). The recommended seller pattern is to author once and let the SDK project to both wire shapes via the [v1↔v2 canonical mapping registry](https://adcontextprotocol.org/schemas/v3/registries/v1-canonical-mapping.json), so every buyer reads what it knows. When both shapes are present on a product, the two MUST refer to the same underlying format declaration — the `format_options[i]` must narrow the canonical that `format_ids[i]` resolves to via the registry. SDKs that derive both shapes from one source guarantee this invariant; SDKs that hand-author both MUST treat divergence as a build error and refuse to emit. Buyers prefer `format_options` when both are present; treat `format_ids` as fallback for v1-only buyers. Sellers whose v1 named formats have no clean v2 projection ship `format_ids` only for those products until they add an explicit `canonical` declaration on the v1 format (see "v1 → v2 canonical mapping" below) — the SDK MUST NOT emit `format_options` for non-projectable formats. -For 13 fully-validated reference Product fixtures spanning all 12 canonical formats — Meta Reels (`video_hosted` vertical), IAB MREC (`image` 300×250), NYTimes HTML5 (`html5`), GAM 3P display tag (`display_tag`), Meta Carousel (`image_carousel`), YouTube VAST pre-roll (`video_vast`), podcast 30s host-read (`audio_hosted`), Triton DAAST audio (`audio_daast`), Amazon Sponsored Products (`sponsored_placement`), Taboola Content Recommendation (`native_in_feed`), Google PMax (`responsive_creative`), ChatGPT brand mention (`agent_placement`), Veo 15s generative video (`video_hosted` with `synthesis_nondeterministic` + `provenance_required`) — plus 1 `get_products` response fixture exercising bundled extensions, see `static/examples/products/canonical/` and `static/examples/get_products_responses/canonical/`. The Veo fixture exercises `synthesis_nondeterministic: true` and `provenance_required: true`. Each fixture passes `npm run test:canonical-fixtures`. +For 14 fully-validated reference Product fixtures spanning all 12 canonical formats — Meta Reels (`video_hosted` vertical), IAB MREC (`image` 300×250), NYTimes HTML5 (`html5`), GAM 3P display tag (`display_tag`), Meta Carousel (`image_carousel`), YouTube VAST pre-roll (`video_vast`), podcast 30s host-read (`audio_hosted`), Triton DAAST audio (`audio_daast`), Amazon Sponsored Products (`sponsored_placement`), Taboola Content Recommendation (`native_in_feed`), Google PMax (`responsive_creative`), ChatGPT brand mention (`agent_placement`), Veo 15s generative video (`video_hosted` with `synthesis_nondeterministic` + `provenance_required`) — plus 1 `get_products` response fixture exercising bundled extensions, see `static/examples/products/canonical/` and `static/examples/get_products_responses/canonical/`. The Veo fixture exercises `synthesis_nondeterministic: true` and `provenance_required: true`. Each fixture passes `npm run test:canonical-fixtures`. ## v1 → v2 canonical mapping diff --git a/docs/creative/canonical-formats.mdx b/docs/creative/canonical-formats.mdx index 2b6ef3c2cc..6c8d3d47af 100644 --- a/docs/creative/canonical-formats.mdx +++ b/docs/creative/canonical-formats.mdx @@ -869,7 +869,7 @@ Pinterest is the canonical disambiguation example because a single platform sell |---|---|---| | **Promoted Pin** (sponsored single Pin in the home/search feed) | `native_in_feed` | Asset-bundle composition — buyer ships title + image + body + landing URL; Pinterest's renderer assembles the Pin to match feed look-and-feel. No catalog feed; the buyer-supplied bundle IS the creative. | | **Pinterest Collection** (single hero image + 3 product thumbnails sourced from a catalog) | `sponsored_placement` | Catalog-keyed — buyer ships a `source_catalog` reference (and an optional `hero_asset`); Pinterest composes the per-item thumbnails by reading product catalog rows. `fanout_mode: multi_item_in_creative` distinguishes Collection from per-item retail-media SP. | -| **Pinterest Idea Pin** (multi-page swipeable native unit) | `image_carousel` | Multi-card swipe shape — the carousel canonical handles polymorphic per-card slots. | +| **Pinterest Idea Pin** (multi-page swipeable native unit; pages may be image or video) | `image_carousel` | Multi-card swipe shape — the carousel canonical's per-card slot is polymorphic image-or-video, which matches Idea Pin pages that mix the two within one Pin. | | **Pinterest Shopping Pin** (single product Pin keyed to a catalog row) | `sponsored_placement` with `fanout_mode: single_item` | Single rendered creative pulled from one catalog item — still catalog-keyed composition, just one item per ad. | The cleave is **asset-bundle vs catalog-row composition**, not "is it Pinterest." Same logic applies to Snap Story Ad (native_in_feed) vs Snap Collection (sponsored_placement), TikTok TopView (native_in_feed via `applies_to_channels: ["social"]`) vs TikTok Collection (sponsored_placement), and so on. Buyer agents route on the composition shape; the publisher's brand of the surface is incidental. diff --git a/static/schemas/source/core/asset-group-vocabulary.json b/static/schemas/source/core/asset-group-vocabulary.json index 1eb9c92cd6..d11effb53d 100644 --- a/static/schemas/source/core/asset-group-vocabulary.json +++ b/static/schemas/source/core/asset-group-vocabulary.json @@ -88,7 +88,37 @@ "body_text": { "description": "Longer free-text body content.", "asset_type": "text", - "typical_use": "Body copy on Reddit, Amazon DSP, LinkedIn formats — distinct from short headlines." + "typical_use": "Body copy on Reddit, Amazon DSP, LinkedIn formats — distinct from short headlines. IAB OpenRTB Native 1.2 Data Asset type 2." + }, + "title": { + "description": "Single headline string rendered verbatim on the creative (no pool / no per-render selection).", + "asset_type": "text", + "typical_use": "Native in-feed units, content-recommendation widgets, single-creative display ads where exactly one headline is rendered. IAB OpenRTB Native 1.2 Title Asset (type 1). Distinct from `headlines` (pool of variants for algorithmic surfaces that pick combinations) — `title` is one fixed string the renderer always uses.", + "aliases": ["headline"] + }, + "main_image": { + "description": "Primary image asset rendered as the visual focal point of the creative.", + "asset_type": "image", + "typical_use": "Native in-feed units, content-recommendation widgets (the large thumbnail above the headline), single-image display creative. IAB OpenRTB Native 1.2 Image Asset type 3 (Main). Common sizes: 1200×627 (1.91:1), 1080×1080 (1:1), 1080×1350 (4:5).", + "aliases": ["image_main", "hero_image"] + }, + "icon": { + "description": "Small square brand or app icon rendered alongside the main image.", + "asset_type": "image", + "typical_use": "App-install native units (app icon), publisher native cards (brand icon), content-recommendation widgets (source publisher icon). IAB OpenRTB Native 1.2 Image Asset type 1 (Icon). Typical size: 80×80 or 100×100, square aspect ratio.", + "aliases": ["icon_image", "brand_icon"] + }, + "advertiser_name": { + "description": "Plain-text name of the advertiser running the creative (rendered with the unit).", + "asset_type": "text", + "typical_use": "Native in-feed units, content-recommendation widgets, sponsored-content cards. Maps to the IAB OpenRTB Native 1.2 `sponsoredBy` field — the renderer pairs this with `sponsored_label` (e.g., ' by '). Required on most native surfaces for transparency.", + "aliases": ["sponsor_name", "brand_name_native"] + }, + "sponsored_label": { + "description": "Disclosure label that identifies the unit as sponsored / promoted / advertising content (e.g., \"Sponsored\", \"Promoted\", \"Ad\").", + "asset_type": "text", + "typical_use": "Native in-feed units, content-recommendation widgets. The renderer pairs this with `advertiser_name` to surface the IAB-required disclosure. Default value typically \"Sponsored\" or \"Ad\"; sellers may localize.", + "aliases": ["disclosure_label", "ad_label"] }, "cards": { "description": "Per-item carousel card array.", @@ -103,9 +133,9 @@ "aliases": ["cta_text", "call_to_action", "action_text", "button_text"] }, "price": { - "description": "Product price slot rendered on the creative.", + "description": "Product price slot rendered on the creative — the regular (non-discounted) price.", "asset_type": "text", - "typical_use": "Pinterest shopping pins, Amazon Sponsored Brand product cards, retail-media catalog rendering, IAB OpenRTB Native 1.2 Data Asset type 6. Often pulled from a catalog field via `field_bindings`." + "typical_use": "Pinterest shopping pins, Amazon Sponsored Brand product cards, retail-media catalog rendering. IAB OpenRTB Native 1.2 Data Asset type 6. Often pulled from a catalog field via `field_bindings`. Pair with `saleprice` (type 7) to render the discount pattern — by IAB convention `price` is the original (struck-through) and `saleprice` is the discounted amount." }, "display_url": { "description": "Visible URL or domain string shown above/below the headline on native units (does not need to match the click destination).",