diff --git a/.changeset/episode-deadlines.md b/.changeset/episode-deadlines.md new file mode 100644 index 0000000000..01f1d56ff5 --- /dev/null +++ b/.changeset/episode-deadlines.md @@ -0,0 +1,7 @@ +--- +"adcontextprotocol": major +--- + +Rename show/episode to collection/installment for cross-channel clarity. Add installment deadlines, deadline policies, and print-capable creative formats. + +Breaking: show→collection, episode→installment across all schemas, enums, and field names (show_id→collection_id, episode_id→installment_id, etc.). Collections gain kind field (series, publication, event_series, rotation) and deadline_policy for lead-time rules. Installments gain optional booking, cancellation, and staged material submission deadlines. Image asset requirements gain physical units (inches/cm/mm), DPI, bleed (uniform or per-side via oneOf), color space, and print file formats (TIFF, PDF, EPS). Format render dimensions support physical units and decimal aspect ratios. diff --git a/docs.json b/docs.json index c6223e45f5..02669d8097 100644 --- a/docs.json +++ b/docs.json @@ -164,7 +164,7 @@ "docs/media-buy/product-discovery/brief-expectations", "docs/media-buy/product-discovery/example-briefs", "docs/media-buy/product-discovery/media-products", - "docs/media-buy/product-discovery/shows-and-episodes", + "docs/media-buy/product-discovery/collections-and-installments", "docs/media-buy/product-discovery/refinement", "docs/media-buy/media-buys/index", "docs/media-buy/media-buys/optimization-reporting", @@ -606,7 +606,7 @@ "docs/media-buy/product-discovery/brief-expectations", "docs/media-buy/product-discovery/example-briefs", "docs/media-buy/product-discovery/media-products", - "docs/media-buy/product-discovery/shows-and-episodes", + "docs/media-buy/product-discovery/collections-and-installments", "docs/media-buy/product-discovery/refinement", "docs/media-buy/media-buys/index", "docs/media-buy/media-buys/optimization-reporting", diff --git a/docs/building/implementation/seller-integration.mdx b/docs/building/implementation/seller-integration.mdx index e11c74fbaf..512296127e 100644 --- a/docs/building/implementation/seller-integration.mdx +++ b/docs/building/implementation/seller-integration.mdx @@ -92,7 +92,7 @@ For content-centric inventory like podcasts, CTV, or live events, products refer "product_id": "signal_noise_sponsorship", "name": "Signal & Noise — Category Sponsorship", "description": "Category-exclusive sponsorship of the Signal & Noise podcast, including pre-roll and mid-roll host read placements.", - "shows": [{ "publisher_domain": "crestnetwork.example.com", "show_ids": ["signal_noise"] }], + "collections": [{ "publisher_domain": "crestnetwork.example.com", "collection_ids": ["signal_noise"] }], "publisher_properties": ["crestnetwork_podcast"], "channels": ["podcast"], "placements": [ @@ -117,7 +117,7 @@ For content-centric inventory like podcasts, CTV, or live events, products refer } ``` -See [Shows and episodes](/docs/media-buy/product-discovery/shows-and-episodes) for the full content model and [Media products](/docs/media-buy/product-discovery/media-products#exclusivity) for exclusivity patterns. +See [Collections and installments](/docs/media-buy/product-discovery/collections-and-installments) for the full content model and [Media products](/docs/media-buy/product-discovery/media-products#exclusivity) for exclusivity patterns. ### Accept and fulfill buys @@ -176,7 +176,7 @@ Different inventory types use different AdCP features: | Live event | `shows` (cadence: event), `episodes` (flexible_end, tentative), `exclusivity` | | Retail media | `catalog_types`, `catalog_match`, metric optimization | -For content-centric inventory, see [Shows and episodes](/docs/media-buy/product-discovery/shows-and-episodes). For exclusivity and sponsorship patterns, see [Media products](/docs/media-buy/product-discovery/media-products#exclusivity). +For content-centric inventory, see [Collections and installments](/docs/media-buy/product-discovery/collections-and-installments). For exclusivity and sponsorship patterns, see [Media products](/docs/media-buy/product-discovery/media-products#exclusivity). ## Governance enforcement diff --git a/docs/creative/channels/audio.mdx b/docs/creative/channels/audio.mdx index ed61b8cfc3..4bb3f34eda 100644 --- a/docs/creative/channels/audio.mdx +++ b/docs/creative/channels/audio.mdx @@ -243,8 +243,8 @@ In addition to [universal macros](/docs/creative/universal-macros), audio format ### Content Context - `{CONTENT_GENRE}` - podcast, music, news, talk - `{CONTENT_RATING}` - Explicit, Clean -- `{SHOW_NAME}` - Podcast or show name -- `{EPISODE_ID}` - Episode identifier +- `{COLLECTION_NAME}` - Podcast or collection name +- `{INSTALLMENT_ID}` - Episode identifier - `{ARTIST_NAME}` - Currently playing artist (music platforms) - `{TRACK_GENRE}` - Music genre of current track @@ -267,7 +267,7 @@ In addition to [universal macros](/docs/creative/universal-macros), audio format - Optional companion banner on screen-enabled devices ### Podcast Insertion -- **Baked-in**: Permanently encoded in episode +- **Baked-in**: Permanently encoded in installment - **Dynamic insertion (SSAI)**: Personalized, with targeting and reporting ### Companion Banners diff --git a/docs/creative/channels/print.mdx b/docs/creative/channels/print.mdx new file mode 100644 index 0000000000..a838d13369 --- /dev/null +++ b/docs/creative/channels/print.mdx @@ -0,0 +1,436 @@ +--- +title: Print Ads +description: "Print ad formats in AdCP cover newspaper, magazine, and trade publication advertising with physical dimensions, bleed, DPI, and CMYK color space requirements." +"og:title": "AdCP — Print Ads" +--- + +This guide covers how AdCP represents print advertising formats for newspapers, magazines, inserts, and trade publications. + +## How print works in AdCP + +Print uses the same building blocks as every other channel: + +- **Collections** model publications (Vogue, Bergedorfer Zeitung, Ad Age) +- **Installments** model issues (March 2026 issue, Issue #47) +- **Installment deadlines** carry booking, cancellation, and material due dates +- **Creative formats** define physical dimensions, bleed, DPI, and file requirements +- **Placements** define positions (full page, half page, island, inside front cover) + +No print-specific schemas are needed. The standard product model handles print when sellers declare physical units in their formats and deadlines on their installments. + +## Print format characteristics + +Print formats differ from digital in several ways: + +- **Physical units** — dimensions in inches or centimeters, not pixels +- **DPI requirements** — minimum 300 DPI for standard print, 150 DPI for newspaper +- **Bleed** — extra image area beyond the trim that prevents white edges after cutting +- **Color space** — CMYK for full-color print, grayscale for B&W +- **File formats** — press-ready PDF, TIFF, or EPS instead of JPG/PNG + +## Standard print formats + +### Full page (magazine) + +```json +{ + "format_id": { + "agent_url": "https://ads.publisher.example.com", + "id": "full_page" + }, + "name": "Full Page", + "renders": [{ + "role": "primary", + "dimensions": { + "width": 8.375, + "height": 10.875, + "unit": "inches" + } + }], + "assets": [ + { + "item_type": "individual", + "asset_id": "artwork", + "asset_type": "image", + "asset_role": "print_artwork", + "required": true, + "requirements": { + "min_width": 8.375, + "max_width": 8.375, + "min_height": 10.875, + "max_height": 10.875, + "unit": "inches", + "min_dpi": 300, + "bleed": { "uniform": 0.125 }, + "color_space": "cmyk", + "formats": ["pdf", "tiff", "eps"] + }, + "overlays": [ + { "id": "safe_left", "description": "Left trim margin — keep headlines, logos, and CTAs inside", "bounds": { "x": 0, "y": 0, "width": 0.25, "height": 10.875, "unit": "inches" } }, + { "id": "safe_right", "description": "Right trim margin", "bounds": { "x": 8.125, "y": 0, "width": 0.25, "height": 10.875, "unit": "inches" } }, + { "id": "safe_top", "description": "Top trim margin", "bounds": { "x": 0, "y": 0, "width": 8.375, "height": 0.25, "unit": "inches" } }, + { "id": "safe_bottom", "description": "Bottom trim margin", "bounds": { "x": 0, "y": 10.625, "width": 8.375, "height": 0.25, "unit": "inches" } } + ] + } + ] +} +``` + +### Half page portrait (newspaper) + +```json +{ + "format_id": { + "agent_url": "https://ads.publisher.example.com", + "id": "half_page_portrait" + }, + "name": "1/2 Seite Hochformat", + "renders": [{ + "role": "primary", + "dimensions": { + "width": 130, + "height": 185, + "unit": "mm" + } + }], + "assets": [ + { + "item_type": "individual", + "asset_id": "artwork", + "asset_type": "image", + "asset_role": "print_artwork", + "required": true, + "requirements": { + "min_width": 130, + "max_width": 130, + "min_height": 185, + "max_height": 185, + "unit": "mm", + "min_dpi": 150, + "bleed": { "uniform": 3 }, + "color_space": "cmyk", + "formats": ["pdf", "tiff"] + } + } + ] +} +``` + +### Insert/supplement + +Multi-page inserts use a repeatable group for pages: + +```json +{ + "format_id": { + "agent_url": "https://ads.publisher.example.com", + "id": "insert_4page" + }, + "name": "4-Page Insert", + "renders": [{ + "role": "primary", + "dimensions": { + "width": 8.375, + "height": 10.875, + "unit": "inches" + } + }], + "assets": [ + { + "item_type": "repeatable_group", + "asset_group_id": "pages", + "required": true, + "min_count": 4, + "max_count": 4, + "selection_mode": "sequential", + "assets": [ + { + "asset_id": "page", + "asset_type": "image", + "asset_role": "insert_page", + "required": true, + "requirements": { + "min_width": 8.375, + "max_width": 8.375, + "min_height": 10.875, + "max_height": 10.875, + "unit": "inches", + "min_dpi": 300, + "bleed": { "uniform": 0.125 }, + "color_space": "cmyk", + "formats": ["pdf"] + } + } + ] + } + ] +} +``` + +## Publications and issues + +A publication maps to a [collection](/docs/media-buy/product-discovery/collections-and-installments). Each issue maps to an installment with deadlines. + +### The publication (collection) + +```json +{ + "collection_id": "bergedorfer_zeitung", + "name": "Bergedorfer Zeitung", + "kind": "publication", + "description": "Regional daily newspaper for Hamburg-Bergedorf", + "genre": ["IAB12"], + "genre_taxonomy": "iab_content_3.0", + "language": "de", + "cadence": "daily", + "status": "active", + "deadline_policy": { + "booking_lead_days": 4, + "cancellation_lead_days": 3, + "material_stages": [ + { "stage": "final", "lead_days": 2, "label": "Druckfertige PDF" } + ], + "business_days_only": true + } +} +``` + +With `deadline_policy`, the collection declares lead-time rules once. Agents compute absolute deadlines from each installment's `scheduled_at`. A daily newspaper no longer needs to enumerate deadlines for every issue — the policy covers the common case, and individual installments can override when needed (e.g., holiday editions with earlier deadlines). + +### An issue (installment with deadlines) + +```json +{ + "installment_id": "2026-03-28", + "name": "Freitag, 28. März 2026", + "scheduled_at": "2026-03-28T05:00:00+01:00", + "status": "scheduled", + "deadlines": { + "booking_deadline": "2026-03-24T17:00:00+01:00", + "cancellation_deadline": "2026-03-25T12:00:00+01:00", + "material_deadlines": [ + { + "stage": "draft", + "due_at": "2026-03-25T17:00:00+01:00", + "label": "Entwurf zur Prüfung" + }, + { + "stage": "final", + "due_at": "2026-03-26T17:00:00+01:00", + "label": "Druckfertige PDF mit Beschnitt (3mm)" + } + ] + } +} +``` + +For a monthly magazine with longer lead times: + +```json +{ + "installment_id": "2026-05", + "name": "Mai 2026", + "season": "2026", + "installment_number": "5", + "scheduled_at": "2026-05-01T00:00:00+02:00", + "status": "scheduled", + "deadlines": { + "booking_deadline": "2026-03-15T17:00:00+01:00", + "cancellation_deadline": "2026-03-22T17:00:00+01:00", + "material_deadlines": [ + { + "stage": "draft", + "due_at": "2026-03-29T17:00:00+01:00", + "label": "Raw artwork for review and color proofing" + }, + { + "stage": "final", + "due_at": "2026-04-05T17:00:00+02:00", + "label": "Press-ready PDF/X-4, CMYK, 300 DPI, 3mm bleed" + } + ] + } +} +``` + +## Complete product example + +A regional newspaper selling print ad inventory across upcoming issues: + +```json +{ + "products": [ + { + "product_id": "bz_display_april", + "name": "Bergedorfer Zeitung — Display Ads, April 2026", + "channels": ["print"], + "collections": [{ + "publisher_domain": "bergedorfer-zeitung.de", + "collection_ids": ["bergedorfer_zeitung"] + }], + "publisher_properties": [{ + "publisher_domain": "bergedorfer-zeitung.de", + "selection_type": "all" + }], + "installments": [ + { + "installment_id": "2026-04-01", + "name": "Mittwoch, 1. April 2026", + "scheduled_at": "2026-04-01T05:00:00+02:00", + "status": "scheduled", + "deadlines": { + "booking_deadline": "2026-03-26T17:00:00+01:00", + "cancellation_deadline": "2026-03-27T12:00:00+01:00", + "material_deadlines": [ + { "stage": "final", "due_at": "2026-03-28T17:00:00+01:00", "label": "Druckfertige PDF" } + ] + } + }, + { + "installment_id": "2026-04-02", + "name": "Donnerstag, 2. April 2026", + "scheduled_at": "2026-04-02T05:00:00+02:00", + "status": "scheduled", + "deadlines": { + "booking_deadline": "2026-03-27T17:00:00+01:00", + "cancellation_deadline": "2026-03-28T12:00:00+01:00", + "material_deadlines": [ + { "stage": "final", "due_at": "2026-03-29T17:00:00+01:00", "label": "Druckfertige PDF" } + ] + } + } + ], + "placements": [ + { "placement_id": "full_page", "name": "Ganze Seite" }, + { "placement_id": "half_page", "name": "1/2 Seite" }, + { "placement_id": "quarter_page", "name": "1/4 Seite" } + ], + "format_ids": [ + { "agent_url": "https://ads.bergedorfer-zeitung.de", "id": "full_page" }, + { "agent_url": "https://ads.bergedorfer-zeitung.de", "id": "half_page_portrait" }, + { "agent_url": "https://ads.bergedorfer-zeitung.de", "id": "quarter_page" } + ], + "delivery_type": "guaranteed", + "delivery_measurement": { + "provider": "IVW (Informationsgemeinschaft zur Feststellung der Verbreitung von Werbeträgern)", + "notes": "Verified circulation figures, updated quarterly" + }, + "pricing_options": [ + { + "pricing_option_id": "full_page_rate", + "pricing_model": "flat_rate", + "fixed_price": 2400, + "currency": "EUR" + }, + { + "pricing_option_id": "half_page_rate", + "pricing_model": "flat_rate", + "fixed_price": 1350, + "currency": "EUR" + } + ] + } + ] +} +``` + +## Print-specific image requirements + +### Physical dimensions + +Print formats declare dimensions in physical units (`inches` or `cm`) instead of pixels. The `unit` field on both format renders and image asset requirements controls interpretation: + +```json +{ + "dimensions": { + "width": 8.375, + "height": 10.875, + "unit": "inches" + } +} +``` + +When `unit` is absent, dimensions default to pixels (backward compatible with digital formats). + +### Bleed + +Bleed is the extra image area beyond the trim size. After printing, the page is cut to the trim dimensions — bleed ensures ink coverage extends to the edge with no white border. + +Bleed can be specified per-side or uniformly: + +```json +"bleed": { "uniform": 0.125 } +``` + +```json +"bleed": { "top": 0.125, "right": 0.125, "bottom": 0.25, "left": 0.125 } +``` + +Values use the same unit as the parent dimensions. + +### Calculating total image dimensions + +For a full-page magazine ad at 8.375 x 10.875 inches with 0.125" uniform bleed at 300 DPI: + +1. **Total physical size**: (8.375 + 0.125 + 0.125) x (10.875 + 0.125 + 0.125) = 8.625 x 11.125 inches +2. **Pixel dimensions**: 8.625 x 300 = 2588 pixels wide, 11.125 x 300 = 3338 pixels tall +3. **Submitted image**: 2588 x 3338 px, CMYK, PDF or TIFF + +For a European newspaper at 130 x 185 mm with 3 mm uniform bleed at 150 DPI: + +1. **Total physical size**: (130 + 3 + 3) x (185 + 3 + 3) = 136 x 191 mm +2. **Convert to inches**: 136 / 25.4 = 5.354", 191 / 25.4 = 7.520" +3. **Pixel dimensions**: 5.354 x 150 = 803 px wide, 7.520 x 150 = 1128 px tall + +### DPI + +`min_dpi` specifies the minimum dots per inch for acceptable print quality: + +| Use case | Typical min_dpi | +|----------|----------------| +| Magazine (coated stock) | 300 | +| Newspaper (uncoated) | 150 | +| Large format / billboard | 72-150 | + +### Color space + +Print production requires CMYK color separation. Digital images in RGB must be converted before press. The `color_space` field declares what the publisher accepts: + +- `cmyk` — standard for offset and digital print +- `rgb` — accepted when the publisher handles conversion +- `grayscale` — for black-and-white placements + +### File formats + +| Format | Use case | +|--------|----------| +| `pdf` | Press-ready composite (PDF/X-4 recommended) | +| `tiff` | Rasterized artwork, lossless | +| `eps` | Vector artwork with embedded fonts | + +### Safe area (trim margins) + +Print production trims pages to their final size, and the cut can shift slightly. Critical content (headlines, logos, CTAs) placed too close to the trim edge risks being cut off. + +Publishers declare trim margins using the standard [overlay](/docs/creative/formats#overlays) pattern — the same mechanism used for CTV player controls and DOOH bezels. Each overlay marks a zone where creative agents should avoid placing important content: + +```json +"overlays": [ + { "id": "safe_left", "description": "Left trim margin", "bounds": { "x": 0, "y": 0, "width": 6, "height": 185, "unit": "mm" } }, + { "id": "safe_right", "bounds": { "x": 124, "y": 0, "width": 6, "height": 185, "unit": "mm" } }, + { "id": "safe_top", "bounds": { "x": 0, "y": 0, "width": 130, "height": 6, "unit": "mm" } }, + { "id": "safe_bottom", "bounds": { "x": 0, "y": 179, "width": 130, "height": 6, "unit": "mm" } } +] +``` + +A 6mm margin is standard for European newspaper production. Overlay bounds support physical units (`mm`, `cm`, `inches`) alongside `px` and `fraction`, so publishers can express safe areas in whichever unit their prepress workflow uses. + +## Deadlines beyond print + +Installment deadlines are not print-specific. Any channel with advance material requirements uses the same pattern. See [collections and installments](/docs/media-buy/product-discovery/collections-and-installments) for deadlines on podcasts, influencer host reads, and live events. + +## Related documentation + +- [Collections and installments](/docs/media-buy/product-discovery/collections-and-installments) — the collection/installment model and deadlines +- [Creative formats](/docs/creative/formats) — format structure and asset discovery +- [Media channel taxonomy](/docs/reference/media-channel-taxonomy) — all 20 channels including print diff --git a/docs/creative/universal-macros.mdx b/docs/creative/universal-macros.mdx index dc3720f0a8..c5df0e5cb9 100644 --- a/docs/creative/universal-macros.mdx +++ b/docs/creative/universal-macros.mdx @@ -160,8 +160,8 @@ For audio formats with content context: | Macro | Description | Example Value | |-------|-------------|---------------| | `{STATION_ID}` | Radio station or podcast identifier | `WXYZ-FM`, `pod_12345` | -| `{SHOW_NAME}` | Program or show name | `Morning Drive`, `Tech Talk Daily` | -| `{EPISODE_ID}` | Podcast episode identifier | `ep_2025_01_15` | +| `{COLLECTION_NAME}` | Program or collection name | `Morning Drive`, `Tech Talk Daily` | +| `{INSTALLMENT_ID}` | Podcast episode identifier | `ep_2025_01_15` | | `{AUDIO_DURATION}` | Content duration in seconds | `3600` | ### AXE Integration @@ -267,7 +267,7 @@ Use the macro that matches your catalog's `content_id_type`. For example, a prod "duration_ms": 30000 }, "impression_tracker": { - "url": "https://track.brand.com/imp?buy={MEDIA_BUY_ID}&pkg={PACKAGE_ID}&station={STATION_ID}&show={SHOW_NAME}&cb={CACHEBUSTER}" + "url": "https://track.brand.com/imp?buy={MEDIA_BUY_ID}&pkg={PACKAGE_ID}&station={STATION_ID}&show={COLLECTION_NAME}&cb={CACHEBUSTER}" } } } @@ -362,7 +362,7 @@ Not all macros are available in all inventory types. Check format specifications | `{AD_BREAK_ID}` | ❌ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | | **Audio Content** | | | | | | | | | | | `{STATION_ID}` | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | -| `{SHOW_NAME}` | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | +| `{COLLECTION_NAME}` | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **Legend**: - ✅ = Available diff --git a/docs/governance/content-standards/artifacts.mdx b/docs/governance/content-standards/artifacts.mdx index 7178606dd0..2c55f56bb9 100644 --- a/docs/governance/content-standards/artifacts.mdx +++ b/docs/governance/content-standards/artifacts.mdx @@ -282,7 +282,7 @@ The `property_id` uses standard identifier types from the AdCP property schema: | `domain` | `reddit.com` | Websites | | `app_id` | `com.spotify.music` | Mobile apps | | `apple_podcast_id` | `1234567890` | Apple Podcasts | -| `spotify_show_id` | `4rOoJ6Egrf8K2IrywzwOMk` | Spotify podcasts | +| `spotify_collection_id` | `4rOoJ6Egrf8K2IrywzwOMk` | Spotify podcasts | | `youtube_channel_id` | `UCddiUEpeqJcYeBxX1IVBKvQ` | YouTube channels | | `rss_url` | `https://feeds.example.com/podcast.xml` | RSS feeds | diff --git a/docs/learning/tracks/publisher.mdx b/docs/learning/tracks/publisher.mdx index 6dae956d89..2bbaf38c74 100644 --- a/docs/learning/tracks/publisher.mdx +++ b/docs/learning/tracks/publisher.mdx @@ -21,7 +21,7 @@ Completing this track (plus A1–A3) earns the **AdCP practitioner** credential. **~20 min** | Prerequisite: A3 -How a hosted sales agent works: product discovery, catalog integration, capability advertising. Walkthrough of designing your product catalog — including shows and episodes for content-centric inventory like podcasts, CTV series, and live events — and configuring what buyers can discover about you. +How a hosted sales agent works: product discovery, catalog integration, capability advertising. Walkthrough of designing your product catalog — including collections and installments for content-centric inventory like podcasts, CTV series, and live events — and configuring what buyers can discover about you. ### Reading list @@ -35,7 +35,7 @@ How a hosted sales agent works: product discovery, catalog integration, capabili How to structure products: formats, pricing, targeting, and availability. - + Model content-centric inventory: podcasts, CTV series, live events, and episodic content. diff --git a/docs/media-buy/product-discovery/collections-and-installments.mdx b/docs/media-buy/product-discovery/collections-and-installments.mdx new file mode 100644 index 0000000000..b9bdc6d75b --- /dev/null +++ b/docs/media-buy/product-discovery/collections-and-installments.mdx @@ -0,0 +1,1015 @@ +--- +title: Collections and installments +description: "AdCP collections and installments — model podcast, CTV, and streaming content as dimensions on media products. Enables collection-level targeting and installment-specific ad placement." +"og:title": "AdCP — Collections and installments" +testable: true +--- + +{/* Using latest because this schema is not yet released in any version. + Update to correct version alias after the next release. */} + +A [product](/docs/media-buy/product-discovery/media-products) describes inventory along three independent axes: + +- **Publisher properties** — WHERE the ad runs (youtube.com, spotify.com) +- **Collections / installments** — WHAT CONTENT the ad runs in or around (a specific series and its installments) +- **Placements** — WHAT POSITION the ad appears in (pre-roll, mid-roll, host read) + +Collections and placements are parallel dimensions, not hierarchical. "Pre-roll" is a position. "Pinnacle Challenge" is content. A product combines them: "pre-roll on Pinnacle Challenge on Acme Streaming." + +## Channel mapping + +The collection/installment model maps to familiar concepts across media channels: + +| Channel | Collection = | Installment = | Example | +|---|---|---|---| +| Podcast | Program | Episode | "Serial" → "Chapter 1" | +| Linear TV / CTV | Series | Episode / Airing | "Monday Night Football" → "Week 12" | +| Print / Magazine | Publication | Issue | "Vogue Germany" → "May 2026" | +| Newsletter | Publication | Edition | "Money Stuff" → "Tuesday March 24" | +| YouTube / Social | Series | Video / Post | "Hot Ones" → "Gordon Ramsay" | +| DOOH | Network / Loop | Rotation | "Times Square Loop" → "March Rotation" | +| Influencer | Campaign | Post / Drop | "Spring Collection" → "Launch Post" | +| Cinema | Run | Screening | "Summer Blockbuster Run" → "Opening Weekend" | +| Radio | Program | Broadcast | "Morning Drive" → "March 24 Broadcast" | + +The `kind` field on each collection indicates how to interpret it and its installments. + +## The collection object + +A collection is a persistent content program that produces installments over time. Collections work like properties — publishers declare them in their `adagents.json`, and products reference them via `collections` selectors with `publisher_domain` and `collection_ids`. + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `collection_id` | string | Yes | Publisher-assigned identifier. Declared in the publisher's `adagents.json`. Products reference collections via `collections` selectors. Use distribution identifiers for cross-seller matching. | +| `kind` | string | No | What kind of content program: `series` (TV/podcast), `publication` (print issues), `event_series` (live events), `rotation` (DOOH). Defaults to `series`. | +| `name` | string | Yes | Human-readable collection name | +| `description` | string | No | What the collection is about | +| `genre` | string[] | No | Genre tags. When `genre_taxonomy` is present, values are taxonomy IDs (e.g., IAB Content Taxonomy 3.0). Otherwise free-form. | +| `genre_taxonomy` | string | No | Taxonomy system for genre values (e.g., `iab_content_3.0`). Recommended for machine-readable brand safety. | +| `language` | string | No | Primary language (BCP 47) | +| `content_rating` | object | No | Baseline rating: `system` + `rating`. Systems: `tv_parental`, `mpaa`, `podcast`, `esrb`, `bbfc`, `fsk`, `acb`, `custom`. Episodes can override. | +| `cadence` | string | No | `daily`, `weekly`, `seasonal`, `event`, `irregular` | +| `season` | string | No | Current or most recent season identifier (e.g., `"3"`, `"2026"`) | +| `status` | string | No | `active`, `hiatus`, `ended`, `upcoming` | +| `production_quality` | string | No | `professional`, `prosumer`, `ugc`. Seller-declared. Maps to OpenRTB `content.prodq`. | +| `talent` | array | No | Hosts, recurring cast, creators. Each entry has `role`, `name`, and optional `brand_url` linking to [brand.json](/docs/brand-protocol/brand-json). | +| `special` | object | No | When present, this collection is a special — content anchored to a real-world event or occasion. See [specials and limited series](#specials-and-limited-series). | +| `limited_series` | object | No | When present, this collection is a limited series — a bounded run with a defined arc and end date. See [specials and limited series](#specials-and-limited-series). | +| `distribution` | array | No | Where this collection is distributed, with platform-specific identifiers per publisher. | +| `deadline_policy` | object | No | Default deadline rules for installments. Agents compute absolute deadlines from `scheduled_at` minus `lead_days`. Episodes with explicit `deadlines` override. See [deadline policy](#deadline-policy). | +| `related_collections` | array | No | Relationships to other collections: `spinoff`, `companion`, `sequel`, `prequel`, `crossover`. Each entry has `collection_id` + `relationship`. References are scoped to the same publisher's `adagents.json`. Symmetric types (`companion`, `crossover`) do not require both collections to declare. | + +```json +{ + "$schema": "https://adcontextprotocol.org/schemas/latest/core/collection.json", + "collection_id": "pinnacle_challenge", + "name": "The Pinnacle Challenge", + "description": "Competition reality collection with extreme physical and mental challenges", + "genre": ["IAB1", "IAB1-6"], + "genre_taxonomy": "iab_content_3.0", + "language": "en", + "content_rating": { + "system": "tv_parental", + "rating": "TV-PG" + }, + "cadence": "weekly", + "season": "1", + "status": "active", + "production_quality": "professional", + "talent": [ + { + "role": "host", + "name": "Jordan Vega", + "brand_url": "https://jordanvega.example.com/brand.json" + } + ], + "distribution": [ + { + "publisher_domain": "youtube.com", + "identifiers": [ + { "type": "youtube_channel_id", "value": "UCexample123456" } + ] + }, + { + "publisher_domain": "acmestreaming.example.com", + "identifiers": [ + { "type": "imdb_id", "value": "tt9876543" } + ] + } + ] +} +``` + +## The installment object + +An installment is a specific installment of a collection. Not all installments will be known in advance — a weekly podcast may only have next week's installment scheduled, and some installments may be tentative (a playoff Game 7 depends on Game 6). + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `installment_id` | string | Yes | Unique identifier within the collection | +| `collection_id` | string | When needed | Parent collection. Required when the product spans multiple collections. | +| `name` | string | No | Episode title | +| `season` | string | No | Season identifier (e.g., `"1"`, `"2026"`) | +| `installment_number` | string | No | Episode number within the season | +| `scheduled_at` | datetime | No | When the installment airs or publishes | +| `status` | string | No | `scheduled`, `tentative`, `live`, `postponed`, `cancelled`, `aired`, `published` | +| `duration_seconds` | integer | No | Expected duration | +| `flexible_end` | boolean | No | Whether end time is approximate (live events) | +| `valid_until` | datetime | No | When this data expires. Agents should re-query before committing budget to tentative installments. | +| `content_rating` | object | No | Overrides the collection's baseline when present | +| `topics` | string[] | No | Installment-level content topics for brand safety. Uses collection's `genre_taxonomy` when present. | +| `special` | object | No | Installment-specific event context. When present, this installment is anchored to a real-world event. Overrides the collection-level `special`. | +| `guest_talent` | array | No | Installment-specific guests. Additive to the collection's recurring `talent`. | +| `ad_inventory` | object | No | Break-based ad inventory configuration | +| `deadlines` | object | No | Booking, cancellation, and material submission deadlines for this installment. See [installment deadlines](#installment-deadlines). | +| `derivative_of` | object | No | When this installment is a clip, highlight, or recap derived from a full installment. Has `installment_id` + `type` (`clip`, `highlight`, `recap`, `trailer`, `bonus`). Source `installment_id` must be in the same response. | + +### Episode status lifecycle + +| Status | Meaning | +|--------|---------| +| `scheduled` | Confirmed, will happen | +| `tentative` | May not happen (depends on external conditions) | +| `live` | Currently airing or streaming right now | +| `postponed` | Was scheduled but delayed to a future date | +| `cancelled` | Will not happen | +| `aired` | Already broadcast — for back-catalog and replay inventory | +| `published` | Already released — for on-demand catch-up inventory | + +Expected transitions: `scheduled` or `tentative` → `live` → `aired` or `published`. A `scheduled` installment may become `postponed` (delayed) or `cancelled`. A `postponed` installment returns to `scheduled` when rescheduled. A `tentative` installment resolves to `scheduled`, `cancelled`, or `postponed`. + +### Inheritance from collections + +Episodes inherit collection-level fields they do not override: + +- **`content_rating`**: Episode value overrides collection baseline. When absent, the collection's rating applies. +- **`special`**: Episode value overrides collection-level special. When absent, the collection's special applies. A regular collection can have event-anchored installments (e.g., a daily news collection with an election night special). +- **`guest_talent`**: Additive to the collection's recurring `talent` — does not replace it. +- **`topics`**: Additive context for brand safety, not a replacement for the collection's `genre`. + +Buyer agents evaluate both levels: the collection baseline provides the default safety profile, and installment fields refine it for specific installments. + +```json +{ + "$schema": "https://adcontextprotocol.org/schemas/latest/core/installment.json", + "installment_id": "s1e03_the_wall", + "collection_id": "pinnacle_challenge", + "name": "The Wall", + "season": "1", + "installment_number": "3", + "scheduled_at": "2026-04-07T20:00:00Z", + "status": "scheduled", + "duration_seconds": 3600, + "valid_until": "2026-04-06T20:00:00Z", + "content_rating": { + "system": "tv_parental", + "rating": "TV-14" + }, + "topics": ["IAB17-18"], + "guest_talent": [ + { + "role": "guest", + "name": "Samira Okafor", + "brand_url": "https://samiraokafor.example.com/brand.json" + } + ], + "ad_inventory": { + "expected_breaks": 4, + "total_ad_seconds": 480, + "max_ad_duration_seconds": 120, + "unplanned_breaks": false, + "supported_formats": ["video", "audio"] + } +} +``` + +## Installment deadlines + +Episodes can carry booking, cancellation, and material submission deadlines. These apply to any channel where inventory is tied to a scheduled unit — print issues, podcast installments, influencer posts, linear TV airings, DOOH rotations. + +| Field | Type | Description | +|-------|------|-------------| +| `booking_deadline` | datetime | Last date/time to book a placement in this installment | +| `cancellation_deadline` | datetime | Last date/time to cancel without penalty | +| `material_deadlines` | array | Ordered stages for creative material submission | + +Deadlines MUST be chronologically ordered: `booking_deadline` ≤ `cancellation_deadline` ≤ each `material_deadlines[n].due_at` (ascending by array index) ≤ the installment's `scheduled_at`. Buyer agents SHOULD reject installments where deadlines violate this ordering. + +Each material deadline has: + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `stage` | string | Yes | Stage identifier (`draft`, `final`, or seller-defined) | +| `due_at` | datetime | Yes | When materials for this stage are due | +| `label` | string | No | What the seller needs (e.g., "Talking points", "Press-ready PDF with bleed") | + +The two-stage pattern — draft then final — covers a wide range of channels: + +| Channel | Draft stage | Final stage | +|---------|-----------|------------| +| Print | Raw artwork for review | Press-ready PDF with bleed | +| Podcast (host read) | Talking points and brief | Approved script | +| Influencer | Brand guidelines and key messages | Approved post content | +| Linear TV | Rough cut | Encoded broadcast spot | +| DOOH | Draft creative for review | Final assets per screen specs | + +### Podcast with deadlines + +```json +{ + "installment_id": "ep47", + "name": "The future of autonomous supply chains", + "scheduled_at": "2026-04-07T10:00:00Z", + "status": "scheduled", + "guest_talent": [ + { "role": "guest", "name": "Kai Tanaka" } + ], + "deadlines": { + "booking_deadline": "2026-03-28T17:00:00Z", + "cancellation_deadline": "2026-03-31T17:00:00Z", + "material_deadlines": [ + { + "stage": "draft", + "due_at": "2026-04-01T17:00:00Z", + "label": "Talking points and brand guidelines for host read" + }, + { + "stage": "final", + "due_at": "2026-04-04T17:00:00Z", + "label": "Approved script" + } + ] + } +} +``` + +### Print issue with deadlines + +```json +{ + "installment_id": "2026-05", + "name": "Mai 2026", + "season": "2026", + "installment_number": "5", + "scheduled_at": "2026-05-01T00:00:00+02:00", + "status": "scheduled", + "deadlines": { + "booking_deadline": "2026-03-15T17:00:00+01:00", + "cancellation_deadline": "2026-03-22T17:00:00+01:00", + "material_deadlines": [ + { + "stage": "draft", + "due_at": "2026-03-29T17:00:00+01:00", + "label": "Raw artwork for review and color proofing" + }, + { + "stage": "final", + "due_at": "2026-04-05T17:00:00+02:00", + "label": "Press-ready PDF/X-4, CMYK, 300 DPI, 3mm bleed" + } + ] + } +} +``` + +### Influencer post with deadlines + +```json +{ + "installment_id": "post_2026_04_10", + "name": "Spring campaign post", + "scheduled_at": "2026-04-10T12:00:00Z", + "status": "scheduled", + "deadlines": { + "cancellation_deadline": "2026-04-03T17:00:00Z", + "material_deadlines": [ + { + "stage": "draft", + "due_at": "2026-04-05T17:00:00Z", + "label": "Key messages, product images, and brand guidelines" + }, + { + "stage": "final", + "due_at": "2026-04-08T17:00:00Z", + "label": "Approved post content and caption" + } + ] + } +} +``` + +Deadlines are optional. Run-of-collection digital products typically omit them. The pattern is most valuable for guaranteed inventory with advance material requirements. + +## Deadline policy + +High-frequency collections (daily newspapers, weekly podcasts) would generate large payloads if every installment carried explicit deadlines. The `deadline_policy` on a collection declares lead-time rules that agents use to compute deadlines from each installment's `scheduled_at`. + +| Field | Type | Description | +|-------|------|-------------| +| `booking_lead_days` | integer | Days before `scheduled_at` by which the placement must be booked | +| `cancellation_lead_days` | integer | Days before `scheduled_at` by which cancellation is penalty-free | +| `material_stages` | array | Default material submission stages with `stage`, `lead_days`, and optional `label` | +| `business_days_only` | boolean | When true, lead_days counts Mon-Fri only. Defaults to false. | + +### Daily newspaper with policy + +Instead of enumerating deadlines for every issue, the collection declares the rule once: + +```json +{ + "collection_id": "bergedorfer_zeitung", + "name": "Bergedorfer Zeitung", + "kind": "publication", + "cadence": "daily", + "status": "active", + "deadline_policy": { + "booking_lead_days": 4, + "cancellation_lead_days": 3, + "material_stages": [ + { "stage": "final", "lead_days": 2, "label": "Druckfertige PDF" } + ], + "business_days_only": true + } +} +``` + +For the April 1 issue (`scheduled_at: "2026-04-01"`), an agent computes: +- **Booking deadline**: 4 business days before = March 26 +- **Cancellation deadline**: 3 business days before = March 27 +- **Material due**: 2 business days before = March 28 + +Episodes with explicit `deadlines` override the policy. A special edition with tighter turnaround declares its own deadlines; regular issues inherit from the policy. + +### Weekly podcast with policy + +```json +{ + "collection_id": "wonderstruck_weekly", + "name": "Wonderstruck Weekly", + "kind": "series", + "cadence": "weekly", + "deadline_policy": { + "booking_lead_days": 10, + "cancellation_lead_days": 7, + "material_stages": [ + { "stage": "draft", "lead_days": 5, "label": "Talking points and brand guidelines" }, + { "stage": "final", "lead_days": 3, "label": "Approved script for host read" } + ] + } +} +``` + +### Policy vs explicit deadlines + +| Scenario | Use | +|----------|-----| +| Daily newspaper, consistent deadlines | `deadline_policy` on collection | +| Weekly podcast, consistent deadlines | `deadline_policy` on collection | +| Monthly magazine, varying lead times per issue | Explicit `deadlines` on each installment | +| One-off special with tight turnaround | Explicit `deadlines` on that installment, policy covers the rest | + +When both are present, explicit installment `deadlines` take precedence. An agent SHOULD NOT compute deadlines from the policy for installments that have their own. + +## Specials and limited series + +Collections can carry optional annotations that signal their nature to buyer agents. These are composable — a collection can be both a special and a limited series (e.g., a 4-installment Olympics documentary). + +### Specials + +A special is content anchored to a real-world event or occasion. The `special` object can appear on both collections and installments. On a collection, it means the entire collection is event-anchored. On an installment, it means that specific installment is event-anchored (and overrides the collection-level special when present, following the same inheritance pattern as `content_rating`). + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `name` | string | Yes | Name of the event (e.g., "Olympics 2028", "Super Bowl LXI") | +| `category` | string | No | `awards`, `championship`, `concert`, `conference`, `election`, `festival`, `gala`, `holiday`, `premiere`, `product_launch`, `reunion`, `tribute` | +| `starts` | datetime | No | When the event starts | +| `ends` | datetime | No | When the event ends. Omit for single-day events. | + +```json +{ + "$schema": "https://adcontextprotocol.org/schemas/latest/core/collection.json", + "collection_id": "apex_finals_2026", + "name": "Apex Championship Finals 2026", + "genre": ["IAB17", "IAB17-12"], + "genre_taxonomy": "iab_content_3.0", + "cadence": "event", + "status": "upcoming", + "special": { + "name": "Apex Championship Finals 2026", + "category": "championship", + "starts": "2026-05-18T19:00:00Z", + "ends": "2026-05-25T23:00:00Z" + }, + "talent": [ + { "role": "host", "name": "Deshawn Moreaux" } + ] +} +``` + +The `special` field is distinct from `cadence`. Cadence describes release frequency (`event` = one-time or occasional). The `special` object describes what real-world event anchors the content and when it happens — information a buyer agent needs to evaluate timing relevance and premium pricing. + +### Limited series + +A limited series is a bounded content run with a defined arc. Unlike ongoing series, limited series have a planned end. + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `total_installments` | integer | No | Planned number of installments | +| `starts` | datetime | No | When the series begins | +| `ends` | datetime | No | When the series ends | + +```json +{ + "$schema": "https://adcontextprotocol.org/schemas/latest/core/collection.json", + "collection_id": "ember_s3", + "name": "The Ember", + "description": "Award-winning drama following a chef navigating the pressure of running a top restaurant", + "genre": ["IAB1", "IAB1-7"], + "genre_taxonomy": "iab_content_3.0", + "cadence": "weekly", + "status": "active", + "limited_series": { + "total_installments": 8, + "starts": "2026-02-28T21:00:00Z", + "ends": "2026-04-18T21:00:00Z" + } +} +``` + +This tells buyer agents that The Ember is a finite opportunity — 8 installments over 7 weeks. The `limited_series` field is distinct from `season`: a collection can have seasons without being limited (ongoing series have seasons too), and a limited series is a structural commitment that the collection has a planned end. + +## How products reference collections + +Products reference collections via `collections` — an array of `{publisher_domain, collection_ids}` selectors that point to collections declared in the publisher's `adagents.json`. This is the same pattern as `publisher_properties`. Buyers resolve full collection objects from the publisher's `adagents.json`. Episodes are listed per-product in the `installments` array, since different products may scope different installments of the same collection. + +### Run-of-collection (no specific installments) + +A product can reference a collection without listing installments. This means "inventory across this collection, whatever installments air during the flight dates": + +```json +{ + "product_id": "pinnacle_run_of_collection", + "name": "Pinnacle Challenge Run of Show", + "collections": [{ "publisher_domain": "acmestreaming.example.com", "collection_ids": ["pinnacle_challenge"] }], + "placements": [ + { "placement_id": "pre_roll", "name": "Pre-roll" } + ], + "delivery_type": "non_guaranteed", + "pricing_options": [ + { "pricing_option_id": "cpm", "pricing_model": "cpm", "floor_price": 25.00, "currency": "USD" } + ] +} +``` + +### Specific installments + +For premium or guaranteed buys, the seller scopes to specific installments: + +```json +{ + "product_id": "pinnacle_finale", + "name": "Pinnacle Challenge Season Finale Sponsorship", + "collections": [{ "publisher_domain": "acmestreaming.example.com", "collection_ids": ["pinnacle_challenge"] }], + "installments": [ + { + "installment_id": "s1e10_finale", + "name": "The Grand Finale", + "scheduled_at": "2026-06-02T20:00:00Z", + "status": "scheduled", + "ad_inventory": { + "expected_breaks": 6, + "total_ad_seconds": 720, + "unplanned_breaks": false + } + } + ], + "delivery_type": "guaranteed", + "pricing_options": [ + { "pricing_option_id": "flat", "pricing_model": "flat_rate", "fixed_price": 2000000, "currency": "USD" } + ] +} +``` + +## get_products response structure + +Products reference collections via `collections` selectors — the same pattern as `publisher_properties`. Each selector has a `publisher_domain` and `collection_ids` array pointing to collections declared in that publisher's `adagents.json`. Buyers resolve full collection objects from `adagents.json`. + +```json +{ + "products": [ + { + "product_id": "pinnacle_april_bundle", + "name": "Pinnacle Challenge April Sponsorship", + "collections": [{ "publisher_domain": "acmestreaming.example.com", "collection_ids": ["pinnacle_challenge"] }], + "installments": [ + { + "installment_id": "s1e03", + "name": "The Wall", + "scheduled_at": "2026-04-07T20:00:00Z", + "status": "scheduled", + "content_rating": { "system": "tv_parental", "rating": "TV-14" }, + "guest_talent": [{ "role": "guest", "name": "Samira Okafor" }], + "valid_until": "2026-04-06T20:00:00Z" + }, + { + "installment_id": "s1e04", + "name": "TBD", + "scheduled_at": "2026-04-14T20:00:00Z", + "status": "tentative", + "valid_until": "2026-04-07T20:00:00Z" + } + ], + "placements": [ + { "placement_id": "pre_roll", "name": "Pre-roll" }, + { "placement_id": "mid_roll", "name": "Mid-roll" } + ], + "delivery_type": "guaranteed", + "pricing_options": [ + { "pricing_option_id": "flat", "pricing_model": "flat_rate", "fixed_price": 500000, "currency": "USD" } + ] + } + ] +} +``` + +## Canonical collection identity + +A collection's identity is **`{publisher_domain, collection_id}`** — scoped to the publisher that declares it in their `adagents.json`. This means any collection creator can serve as their own canonical registry. + +### Creator as canonical publisher + +A creator like MrBeast declares collections in `mrbeast.com/adagents.json`. Any seller packaging that creator's inventory — a YouTube sales house, a CTV distributor, the creator's own team — references the same canonical source: + +```json +{ + "products": [ + { + "product_id": "beast_games_youtube", + "name": "Beast Games - YouTube Pre-roll", + "collections": [ + { "publisher_domain": "mrbeast.com", "collection_ids": ["beast_games"] } + ], + "publisher_properties": [ + { "publisher_domain": "youtube.com", "property_ids": ["UCX6OQ3DkcsbYNE6H8uQQuVA"] } + ], + "delivery_type": "non_guaranteed", + "pricing_options": [ + { "pricing_option_id": "cpm", "pricing_model": "cpm", "floor_price": 18.00, "currency": "USD" } + ] + } + ] +} +``` + +The buyer sees `mrbeast.com` + `beast_games` regardless of which seller or platform the inventory comes from. No cross-referencing of IMDb IDs or distribution identifiers needed — the creator's domain **is** the registry. + +### When creators don't publish adagents.json + +Not every collection has a creator-owned domain. A streaming platform's original series may only exist in that platform's `adagents.json`. In that case, the platform is the canonical publisher and `collections` points to their domain. Distribution identifiers on the collection object handle cross-seller matching when the same collection appears on multiple platforms without a single canonical source. + +### Identity resolution priority + +Buyer agents should resolve collection identity in this order: + +1. **Canonical publisher** — `publisher_domain` + `collection_id` from the creator's own `adagents.json`. Strongest signal. +2. **Platform-independent identifiers** — `imdb_id`, `gracenote_id`, `eidr_id` from the collection's `distribution` array. Reliable cross-reference when no canonical publisher exists. +3. **Platform-specific identifiers** — Spotify, Apple, YouTube IDs. Useful within a platform but not universal. + +## Discovery examples + +### CTV collection with installments + +A streaming platform selling sponsorships against a known collection with upcoming installments: + +```json +{ + "products": [ + { + "product_id": "nova_kitchen_april", + "name": "Nova Kitchen - April Episodes", + "collections": [{ "publisher_domain": "novastreaming.example.com", "collection_ids": ["nova_kitchen"] }], + "publisher_properties": [{ + "publisher_domain": "novastreaming.example.com", + "selection_type": "all" + }], + "installments": [ + { + "installment_id": "s3e09", + "name": "Fire and Ice", + "scheduled_at": "2026-04-05T21:00:00Z", + "status": "scheduled", + "duration_seconds": 2700, + "ad_inventory": { + "expected_breaks": 3, + "total_ad_seconds": 360, + "max_ad_duration_seconds": 30, + "unplanned_breaks": false, + "supported_formats": ["video"] + } + }, + { + "installment_id": "s3e10", + "scheduled_at": "2026-04-12T21:00:00Z", + "status": "tentative", + "valid_until": "2026-04-06T00:00:00Z" + } + ], + "placements": [ + { "placement_id": "pre_roll", "name": "Pre-roll (15s)" }, + { "placement_id": "mid_roll", "name": "Mid-roll (30s)" } + ], + "delivery_type": "guaranteed", + "pricing_options": [ + { "pricing_option_id": "cpm_fixed", "pricing_model": "cpm", "fixed_price": 38.00, "currency": "USD" } + ] + } + ] +} +``` + +### Related collections and derivative content + +A streaming platform offering a main collection and its companion after-collection, plus highlight clips: + +```json +{ + "products": [ + { + "product_id": "nova_highlights", + "name": "Nova Kitchen Highlights Package", + "collections": [{ "publisher_domain": "novastreaming.example.com", "collection_ids": ["nova_kitchen"] }], + "installments": [ + { + "installment_id": "s3e09", + "name": "Fire and Ice", + "status": "aired", + "duration_seconds": 2700 + }, + { + "installment_id": "s3e09_highlights", + "name": "Fire and Ice - Best Moments", + "status": "published", + "duration_seconds": 180, + "derivative_of": { + "installment_id": "s3e09", + "type": "highlight" + } + } + ], + "placements": [ + { "placement_id": "pre_roll", "name": "Pre-roll (15s)" } + ], + "delivery_type": "non_guaranteed", + "pricing_options": [ + { "pricing_option_id": "cpm", "pricing_model": "cpm", "floor_price": 12.00, "currency": "USD" } + ] + } + ] +} +``` + +The highlight clip references its source installment via `derivative_of`. The companion after-collection is linked via `related_collections` on the main collection — buyers targeting Nova Kitchen can discover the after-collection as an additional reach opportunity. + +### Podcast with distribution + +A podcast network selling across multiple distribution platforms. The collection's `distribution` array in `adagents.json` captures the full distribution footprint; buyers use distribution identifiers for cross-seller matching: + +```json +{ + "products": [ + { + "product_id": "wonderstruck_april", + "name": "Wonderstruck Weekly - April Episodes", + "collections": [{ "publisher_domain": "wonderstruck.example.com", "collection_ids": ["wonderstruck_weekly"] }], + "installments": [ + { + "installment_id": "ep47", + "name": "The future of autonomous supply chains", + "scheduled_at": "2026-04-07T10:00:00Z", + "status": "scheduled", + "guest_talent": [ + { "role": "guest", "name": "Kai Tanaka", "brand_url": "https://kaitanaka.example.com/brand.json" } + ] + }, + { + "installment_id": "ep48", + "scheduled_at": "2026-04-14T10:00:00Z", + "status": "tentative" + }, + { + "installment_id": "ep49", + "scheduled_at": "2026-04-21T10:00:00Z", + "status": "tentative" + } + ], + "placements": [ + { "placement_id": "pre_roll", "name": "Pre-roll (30s)" }, + { "placement_id": "mid_roll", "name": "Mid-roll host read (60s)" } + ], + "delivery_type": "guaranteed", + "pricing_options": [ + { "pricing_option_id": "flat_monthly", "pricing_model": "flat_rate", "fixed_price": 15000, "currency": "USD" }, + { "pricing_option_id": "per_episode", "pricing_model": "flat_rate", "fixed_price": 5000, "currency": "USD" } + ] + } + ] +} +``` + +Tentative installments (ep48, ep49) have no names or guest info — they have not been produced yet. The buyer evaluates based on the collection's baseline profile and the known details of ep47. + +### Live event with tentative installments + +A sports league selling sponsorships against a live event series. Live broadcasts use `flexible_end` because game duration is unpredictable, and `unplanned_breaks` because ad breaks follow game flow (timeouts, period breaks) rather than a fixed schedule. The tentative Game 5 depends on the series outcome — it only happens if neither team wins in four games. + +```json +{ + "products": [ + { + "product_id": "apex_finals_sponsorship", + "name": "Apex Championship Finals — Category Sponsorship", + "collections": [{ "publisher_domain": "apexleague.example.com", "collection_ids": ["apex_championship_2026"] }], + "installments": [ + { + "installment_id": "finals_game4", + "name": "Game 4: Titan City vs Coastal FC", + "scheduled_at": "2026-05-18T19:00:00Z", + "status": "scheduled", + "flexible_end": true, + "ad_inventory": { + "expected_breaks": 8, + "total_ad_seconds": 960, + "max_ad_duration_seconds": 30, + "unplanned_breaks": true, + "supported_formats": ["video"] + } + }, + { + "installment_id": "finals_game5", + "name": "Game 5 — If necessary", + "scheduled_at": "2026-05-21T19:00:00Z", + "status": "tentative", + "flexible_end": true, + "valid_until": "2026-05-19T06:00:00Z", + "ad_inventory": { + "expected_breaks": 8, + "total_ad_seconds": 960, + "max_ad_duration_seconds": 30, + "unplanned_breaks": true, + "supported_formats": ["video"] + } + } + ], + "placements": [ + { "placement_id": "in_game_overlay", "name": "In-game overlay (10s)" }, + { "placement_id": "halftime", "name": "Halftime feature (60s)" } + ], + "delivery_type": "guaranteed", + "exclusivity": "category", + "pricing_options": [ + { "pricing_option_id": "per_game", "pricing_model": "flat_rate", "fixed_price": 750000, "currency": "USD" } + ] + } + ] +} +``` + +This pattern combines several features designed for live inventory: `cadence: "event"` signals a non-recurring series, `flexible_end` tells buyers the broadcast length is approximate, and `unplanned_breaks: true` indicates ad breaks follow game flow rather than a predetermined schedule. The tentative Game 5 includes `valid_until` so buyer agents know when to re-query — if the series ends in four games, that installment will resolve to `cancelled`. Buyers should always check `valid_until` on tentative installments before committing budget. + +### Discovering collections + +Buyers discover collections through the standard `get_products` workflow. Natural language briefs drive collection selection: + +```json +{ + "buying_mode": "brief", + "brief": "Podcast sponsorships for technology collections reaching startup founders in April", + "filters": { + "channels": ["podcast"], + "start_date": "2026-04-01", + "end_date": "2026-04-30" + } +} +``` + +The seller returns products with `collections` selectors. The buyer resolves full collection objects from each publisher's [adagents.json](/docs/governance/property/adagents). There is no separate collection discovery endpoint — collections surface through product discovery and `adagents.json` crawling. + +## Brand safety + +Collections provide a two-level brand safety model: a collection baseline and per-installment overrides. + +### Collection baseline + +A collection's default brand safety profile comes from: +- `content_rating` — the collection's declared rating system and value +- `genre` — content categories (ideally using `genre_taxonomy` for machine-readable evaluation) +- `talent` — hosts and recurring cast, with optional [brand.json](/docs/brand-protocol/brand-json) references for deeper evaluation + +This is what buyers evaluate when individual installment content is not yet known. + +### Installment overrides + +When installment details are available, they can shift the safety profile: +- A `content_rating` that differs from the collection baseline (this week is TV-14 instead of TV-PG) +- `guest_talent` that changes the talent profile (a controversial guest) +- `topics` that add installment-specific content signals + +### What is not modeled + +AdCP does not predict content safety for unknown future installments. A buyer who commits to "all April installments" buys based on the collection's baseline profile, accepting variation. The seller's content standards and the collection's track record are the buyer's basis for that decision. + +## Distribution identifiers + +Each collection's `distribution` array maps it to specific publisher platforms with platform-specific identifiers. This enables cross-seller matching: when two different sellers both offer products for the same collection, a buyer agent can match them via shared identifiers. + +### Cross-seller matching + +Platform-independent identifiers are the most reliable for deduplication: + +| Type | Example | Notes | +|------|---------|-------| +| `imdb_id` | `tt1234567` | Universal cross-platform reference | +| `gracenote_id` | `EP012345678` | Industry standard for TV metadata | +| `eidr_id` | `10.5240/XXXX-XXXX-XXXX-XXXX-XXXX-C` | ISO 10528 for audiovisual content | + +Shows SHOULD include at least one platform-independent identifier when available. + +### Platform-specific identifiers + + + +```json Podcast +{ + "distribution": [ + { + "publisher_domain": "spotify.com", + "identifiers": [{ "type": "spotify_collection_id", "value": "4rOoJ6Egrf8K2IrywzwOMk" }] + }, + { + "publisher_domain": "apple.com", + "identifiers": [{ "type": "apple_podcast_id", "value": "1234567890" }] + }, + { + "publisher_domain": "feeds.example.com", + "identifiers": [{ "type": "rss_url", "value": "https://feeds.example.com/collection.xml" }] + } + ] +} +``` + +```json Video / CTV +{ + "distribution": [ + { + "publisher_domain": "youtube.com", + "identifiers": [{ "type": "youtube_channel_id", "value": "UCexample123456" }] + }, + { + "publisher_domain": "acmestreaming.example.com", + "identifiers": [{ "type": "amazon_title_id", "value": "B0DFBT5GBP" }] + } + ] +} +``` + + + +Available podcast types: `apple_podcast_id`, `spotify_collection_id`, `rss_url`, `podcast_guid`, `amazon_music_id`, `iheart_id`, `podcast_index_id`. Available video/CTV types: `youtube_channel_id`, `youtube_playlist_id`, `amazon_title_id`, `roku_channel_id`, `pluto_channel_id`, `tubi_id`, `peacock_id`, `tiktok_id`, `twitch_channel`. Other: `domain`, `substack_id`. + +## Ad inventory + +Episodes declare break-based ad inventory in the `ad_inventory` object: + +| Field | Type | Description | +|-------|------|-------------| +| `expected_breaks` | integer | Number of planned ad breaks | +| `total_ad_seconds` | integer | Total seconds of ad time across all breaks | +| `max_ad_duration_seconds` | integer | Maximum duration for a single ad within a break | +| `unplanned_breaks` | boolean | `false`: all breaks pre-defined. `true`: breaks driven by live conditions (sports timeouts, live news). | +| `supported_formats` | string[] | Format types supported in breaks (e.g., `"video"`, `"audio"`) | + +For non-break ad formats like host reads, custom integrations, or sponsorships, use product [placements](/docs/media-buy/product-discovery/media-products#placements) instead. A podcast product might have a placement for "mid-roll host read (60s)" — that is a placement on the product, not part of `ad_inventory`. + +## Relationship to LEAP + +The installment model aligns with IAB Tech Lab's LEAP Forecasting API for live streaming events: + +| LEAP concept | AdCP equivalent | +|-------------|----------------| +| UpcomingEvent | Episode (`scheduled_at` + `status`) | +| Content (AdCOM 1.0) | Show metadata (`genre`, `content_rating`, `talent`) | +| AdInventoryConfiguration | Episode `ad_inventory` + product placements | +| Event status | Episode `status` (`scheduled`, `tentative`, `cancelled`) | +| Unplanned breaks | `ad_inventory.unplanned_breaks` | +| Flexible end time | Episode `flexible_end` | + +LEAP targets SSP-to-DSP plumbing. AdCP operates at the agent-to-agent negotiation layer where buying decisions happen. + +## Collection targeting + +Products with multiple collections default to bundles — the buyer gets all listed collections. Sellers can set `collection_targeting_allowed: true` to let buyers target a subset, the same pattern as `property_targeting_allowed` for properties. + +| `collection_targeting_allowed` | Meaning | +|---|---| +| `false` (default) | Bundle — buyer gets all collections | +| `true` | Buyer can select specific collections in the media buy | + +## Multi-collection bundles + +A single product can span multiple collections by listing multiple collection IDs in `collections`. When a product has multiple collections, each installment MUST include `collection_id` so the buyer agent knows which collection each installment belongs to. + +```json +{ + "products": [ + { + "product_id": "technet_april_bundle", + "name": "TechNet Podcast Bundle - April", + "collections": [{ "publisher_domain": "technet.example.com", "collection_ids": ["tech_weekly", "startup_hour"] }], + "installments": [ + { "installment_id": "tw_ep12", "collection_id": "tech_weekly", "scheduled_at": "2026-04-07T10:00:00Z", "status": "scheduled" }, + { "installment_id": "tw_ep13", "collection_id": "tech_weekly", "scheduled_at": "2026-04-14T10:00:00Z", "status": "tentative" }, + { "installment_id": "sh_ep30", "collection_id": "startup_hour", "scheduled_at": "2026-04-09T14:00:00Z", "status": "scheduled" }, + { "installment_id": "sh_ep31", "collection_id": "startup_hour", "scheduled_at": "2026-04-16T14:00:00Z", "status": "tentative" } + ], + "delivery_type": "guaranteed", + "pricing_options": [ + { "pricing_option_id": "bundle", "pricing_model": "flat_rate", "fixed_price": 25000, "currency": "USD" } + ] + } + ] +} +``` + +### Print publication with issues + +A magazine publisher selling display ads across upcoming issues. Each issue is an installment with deadlines for booking, cancellation, and material delivery: + +```json +{ + "products": [ + { + "product_id": "vogue_de_display_q2", + "name": "Vogue Germany — Display Ads, Q2 2026", + "channels": ["print"], + "collections": [{ "publisher_domain": "vogue.de", "collection_ids": ["vogue_de"] }], + "installments": [ + { + "installment_id": "2026-04", + "name": "April 2026", + "season": "2026", + "installment_number": "4", + "scheduled_at": "2026-04-01T00:00:00+02:00", + "status": "scheduled", + "deadlines": { + "booking_deadline": "2026-02-15T17:00:00+01:00", + "cancellation_deadline": "2026-02-22T17:00:00+01:00", + "material_deadlines": [ + { "stage": "draft", "due_at": "2026-03-01T17:00:00+01:00", "label": "Artwork for color proofing" }, + { "stage": "final", "due_at": "2026-03-08T17:00:00+01:00", "label": "Press-ready PDF/X-4, CMYK, 300 DPI" } + ] + } + }, + { + "installment_id": "2026-05", + "name": "Mai 2026", + "season": "2026", + "installment_number": "5", + "scheduled_at": "2026-05-01T00:00:00+02:00", + "status": "scheduled", + "deadlines": { + "booking_deadline": "2026-03-15T17:00:00+01:00", + "cancellation_deadline": "2026-03-22T17:00:00+01:00", + "material_deadlines": [ + { "stage": "draft", "due_at": "2026-03-29T17:00:00+01:00", "label": "Artwork for color proofing" }, + { "stage": "final", "due_at": "2026-04-05T17:00:00+02:00", "label": "Press-ready PDF/X-4, CMYK, 300 DPI" } + ] + } + } + ], + "placements": [ + { "placement_id": "full_page", "name": "Full Page" }, + { "placement_id": "dps", "name": "Double Page Spread" }, + { "placement_id": "ifc", "name": "Inside Front Cover" } + ], + "delivery_type": "guaranteed", + "delivery_measurement": { + "provider": "IVW", + "notes": "Verified circulation, updated quarterly" + }, + "pricing_options": [ + { "pricing_option_id": "full_page", "pricing_model": "flat_rate", "fixed_price": 28000, "currency": "EUR" }, + { "pricing_option_id": "dps", "pricing_model": "flat_rate", "fixed_price": 48000, "currency": "EUR" } + ] + } + ] +} +``` + +The collection/installment model works identically for print publications and audio/video content. The installment's `deadlines` object replaces what the German OBS system handles through separate message exchanges — booking, cancellation, and material delivery are all visible upfront. + +## See also + +- [Media products](/docs/media-buy/product-discovery/media-products) — the full product model +- [Print ads](/docs/creative/channels/print) — print-specific creative formats, physical dimensions, bleed, and DPI +- [brand.json](/docs/brand-protocol/brand-json) — talent identity and brand safety evaluation +- [Product discovery](/docs/media-buy/product-discovery) — how buyers discover inventory via `get_products` diff --git a/docs/media-buy/product-discovery/index.mdx b/docs/media-buy/product-discovery/index.mdx index 94e1f979de..9fb4d0319c 100644 --- a/docs/media-buy/product-discovery/index.mdx +++ b/docs/media-buy/product-discovery/index.mdx @@ -57,12 +57,12 @@ Learn more in [Brief Expectations](/docs/media-buy/product-discovery/brief-expec ### Product Model Products describe sellable inventory along three independent dimensions: - **Publisher properties** — WHERE the ad runs (the website, app, or platform) -- **Shows and episodes** — WHAT CONTENT the ad runs in or around (a series, podcast, or live event) +- **Collections and installments** — WHAT CONTENT the ad runs in or around (a series, podcast, or live event) - **Placements** — WHAT POSITION the ad appears in (pre-roll, mid-roll, host read) -Each product also includes audience targeting, creative format requirements, pricing, and delivery characteristics. When products reference shows, buyers resolve full show metadata (talent, genre, content ratings, distribution identifiers) from the show creator's or publisher's `adagents.json`. +Each product also includes audience targeting, creative format requirements, pricing, and delivery characteristics. When products reference collections, buyers resolve full collection metadata (talent, genre, content ratings, distribution identifiers) from the collection creator's or publisher's `adagents.json`. -Understand the complete product structure in [Media Products](/docs/media-buy/product-discovery/media-products). For show-centric inventory like podcasts, CTV series, and live events, see [Shows and Episodes](/docs/media-buy/product-discovery/shows-and-episodes). +Understand the complete product structure in [Media Products](/docs/media-buy/product-discovery/media-products). For collection-centric inventory like podcasts, CTV series, and live events, see [Collections and Installments](/docs/media-buy/product-discovery/collections-and-installments). ### Catalog-driven discovery For catalog-driven campaigns (retail media, job boards, travel), pass a `catalog` on `get_products` to find products that match your catalog items. Products declare which catalog types they support via `catalog_types`, and the response includes `catalog_match` with matched item counts. See [Catalogs](/docs/creative/catalogs#catalogs-in-the-media-buy-lifecycle). diff --git a/docs/media-buy/product-discovery/media-products.mdx b/docs/media-buy/product-discovery/media-products.mdx index 532560430a..5066442379 100644 --- a/docs/media-buy/product-discovery/media-products.mdx +++ b/docs/media-buy/product-discovery/media-products.mdx @@ -21,8 +21,8 @@ Products declare which pricing models they support. Buyers select a specific pri - `channels` (list[string], optional): Advertising channels this product is sold as (e.g., `["retail_media"]`, `["display", "olv"]`). Sellers SHOULD declare `channels` on products that span non-obvious channels, particularly retail media, CTV/OLV, and multi-channel bundles. Product channels SHOULD be a subset of the union of their properties' `supported_channels`. See [Media Channel Taxonomy](/docs/reference/media-channel-taxonomy). - `format_ids` (list[FormatID], required): Structured format ID references. See [Creative Formats](/docs/creative/formats). - `placements` (list[Placement], optional): Specific ad placements within this product. When provided, buyers can target individual placements when assigning creatives. See [Placements](#placements). -- `shows` (list[ShowSelector], optional): Shows covered by this product, grouped by publisher. Each entry has `publisher_domain` and `show_ids` referencing shows in the publisher's `adagents.json`. See [Shows and episodes](/docs/media-buy/product-discovery/shows-and-episodes). -- `episodes` (list[Episode], optional): Specific episodes available within this product. See [Shows and episodes](/docs/media-buy/product-discovery/shows-and-episodes). +- `shows` (list[CollectionSelector], optional): Shows covered by this product, grouped by publisher. Each entry has `publisher_domain` and `collection_ids` referencing shows in the publisher's `adagents.json`. See [Collections and installments](/docs/media-buy/product-discovery/collections-and-installments). +- `episodes` (list[Episode], optional): Specific episodes available within this product. See [Collections and installments](/docs/media-buy/product-discovery/collections-and-installments). - `delivery_type` (string, required): Either `"guaranteed"` or `"non_guaranteed"`. - `exclusivity` (string, optional): Whether this product offers exclusive access. `"none"` (default when absent) — multiple advertisers can buy simultaneously. `"category"` — one advertiser per industry category. `"exclusive"` — sole sponsorship. Most relevant for guaranteed products tied to specific shows or placements. - `pricing_options` (list[PricingOption], required): Array of available pricing models for this product. See [Pricing Models](#pricing-models). @@ -32,7 +32,7 @@ Products declare which pricing models they support. Buyers select a specific pri - `is_custom` (bool, optional): `true` if the product was generated for a specific brief. - `expires_at` (datetime, optional): If `is_custom`, the time the product is no longer valid. - `property_targeting_allowed` (bool, optional, default: false): Whether buyers can filter this product to a subset of its `publisher_properties`. When `false` (default), the product is "all or nothing" - buyers must accept all properties or the product is excluded from `property_list` filtering results. See [Property Targeting](#property-targeting). -- `show_targeting_allowed` (bool, optional, default: false): Whether buyers can target a subset of this product's `shows`. When `false` (default), the product is a bundle — buyers get all listed shows. When `true`, buyers can select specific shows in the media buy. +- `collection_targeting_allowed` (bool, optional, default: false): Whether buyers can target a subset of this product's `shows`. When `false` (default), the product is a bundle — buyers get all listed shows. When `true`, buyers can select specific shows in the media buy. - `catalog_types` (list[string], optional): Catalog types this product supports for catalog-driven campaigns. A sponsored product listing declares `["product"]`, a job board declares `["job", "offering"]`. Buyers match synced catalogs to products via this field. See [Catalogs](/docs/creative/catalogs). - `catalog_match` (object, optional): When the buyer provides a `catalog` on `get_products`, indicates which catalog items are eligible for this product. Contains `matched_gtins` (cross-retailer GTIN matches), `matched_ids` (generic item ID matches), `matched_count`, and `submitted_count`. - `metric_optimization` (object, optional): Metric optimization capabilities for this product. Presence indicates the product supports `optimization_goals` with `kind: "metric"`. See [Metric optimization](#metric-optimization). @@ -251,11 +251,11 @@ When creating a media buy, buyers can assign different creatives to different pl See [Creative Assignment and Placement Targeting](/docs/media-buy/media-buys/index.mdx#creative-assignment-and-placement-targeting) for more details. -### Shows and episodes +### Collections and installments Shows are a third product dimension alongside formats and placements. While placements describe *where* an ad appears and formats describe *what* the ad looks like, shows describe the *content context* — the programming a viewer is watching. Products can declare `shows` and `episodes` so buyers can target specific shows or episodes when purchasing inventory. -See [Shows and episodes](/docs/media-buy/product-discovery/shows-and-episodes) for the full model, examples, and targeting details. +See [Collections and installments](/docs/media-buy/product-discovery/collections-and-installments) for the full model, examples, and targeting details. ### Exclusivity @@ -264,7 +264,7 @@ The `exclusivity` field indicates whether a product offers exclusive access to i | Value | Meaning | |---|---| | `none` | Multiple advertisers can buy this product simultaneously | -| `category` | One advertiser per industry category (e.g., one auto brand per show sponsorship) | +| `category` | One advertiser per industry category (e.g., one auto brand per collection sponsorship) | | `exclusive` | Sole sponsorship — only one advertiser can buy this product | Exclusivity is most relevant for guaranteed products tied to specific shows or placements, where advertisers want brand separation or sole ownership of a content association. @@ -272,14 +272,14 @@ Exclusivity is most relevant for guaranteed products tied to specific shows or p #### When to use each level - **`none`**: Programmatic inventory, run-of-network, open auction products. Multiple advertisers sharing the same inventory is expected. -- **`category`**: Podcast or CTV sponsorships where competitive separation matters. One auto brand per show, one fintech brand per episode — but multiple non-competing advertisers can buy simultaneously. -- **`exclusive`**: Sole sponsorship of a single show or event. The advertiser is the only brand associated with the content. +- **`category`**: Podcast or CTV sponsorships where competitive separation matters. One auto brand per collection, one fintech brand per installment — but multiple non-competing advertisers can buy simultaneously. +- **`exclusive`**: Sole sponsorship of a single collection or event. The advertiser is the only brand associated with the content. -Publishers SHOULD include `exclusivity` on guaranteed products with `shows`. The implicit default of `"none"` is ambiguous for show-level inventory — buyers cannot tell whether the publisher intends shared inventory or simply omitted the field. +Publishers SHOULD include `exclusivity` on guaranteed products with `shows`. The implicit default of `"none"` is ambiguous for collection-level inventory — buyers cannot tell whether the publisher intends shared inventory or simply omitted the field. #### Content sponsorship pattern -A product combining `delivery_type: "guaranteed"`, `exclusivity: "exclusive"`, and `shows` represents a content sponsorship — the advertiser becomes the sole sponsor of specific content. This is the standard pattern for podcast title sponsorships, CTV show sponsorships, and event-based takeovers. +A product combining `delivery_type: "guaranteed"`, `exclusivity: "exclusive"`, and `shows` represents a content sponsorship — the advertiser becomes the sole sponsor of specific content. This is the standard pattern for podcast title sponsorships, CTV collection sponsorships, and event-based takeovers. ```json { @@ -293,7 +293,7 @@ A product combining `delivery_type: "guaranteed"`, `exclusivity: "exclusive"`, a { "agent_url": "https://ads.crestnetwork.example", "id": "audio_pre_roll_30s" }, { "agent_url": "https://ads.crestnetwork.example", "id": "audio_mid_roll_60s" } ], - "shows": [{ "publisher_domain": "crestnetwork.example", "show_ids": ["signal_noise"] }], + "collections": [{ "publisher_domain": "crestnetwork.example", "collection_ids": ["signal_noise"] }], "delivery_type": "guaranteed", "exclusivity": "exclusive", "pricing_options": [ @@ -307,7 +307,7 @@ A product combining `delivery_type: "guaranteed"`, `exclusivity: "exclusive"`, a } ``` -Category exclusivity works for multi-show bundles where the publisher separates competing brands across a network but still sells to multiple non-competing advertisers: +Category exclusivity works for multi-collection bundles where the publisher separates competing brands across a network but still sells to multiple non-competing advertisers: ```json { @@ -321,7 +321,7 @@ Category exclusivity works for multi-show bundles where the publisher separates { "agent_url": "https://ads.crestnetwork.example", "id": "audio_pre_roll_30s" }, { "agent_url": "https://ads.crestnetwork.example", "id": "audio_mid_roll_60s" } ], - "shows": [{ "publisher_domain": "crestnetwork.example", "show_ids": ["signal_noise", "market_beat", "founder_stories"] }], + "collections": [{ "publisher_domain": "crestnetwork.example", "collection_ids": ["signal_noise", "market_beat", "founder_stories"] }], "delivery_type": "guaranteed", "exclusivity": "category", "pricing_options": [ diff --git a/docs/media-buy/product-discovery/shows-and-episodes.mdx b/docs/media-buy/product-discovery/shows-and-episodes.mdx deleted file mode 100644 index 2ecac416ea..0000000000 --- a/docs/media-buy/product-discovery/shows-and-episodes.mdx +++ /dev/null @@ -1,739 +0,0 @@ ---- -title: Shows and episodes -description: "AdCP shows and episodes — model podcast, CTV, and streaming content as dimensions on media products. Enables show-level targeting and episode-specific ad placement." -"og:title": "AdCP — Shows and episodes" -testable: true ---- - -{/* Using latest because this schema is not yet released in any version. - Update to correct version alias after the next release. */} - -A [product](/docs/media-buy/product-discovery/media-products) describes inventory along three independent axes: - -- **Publisher properties** — WHERE the ad runs (youtube.com, spotify.com) -- **Shows / episodes** — WHAT CONTENT the ad runs in or around (a specific series and its installments) -- **Placements** — WHAT POSITION the ad appears in (pre-roll, mid-roll, host read) - -Shows and placements are parallel dimensions, not hierarchical. "Pre-roll" is a position. "Pinnacle Challenge" is content. A product combines them: "pre-roll on Pinnacle Challenge on Acme Streaming." - -## The show object - -A show is a persistent content program that produces episodes over time. Shows work like properties — publishers declare them in their `adagents.json`, and products reference them via `shows` selectors with `publisher_domain` and `show_ids`. - -| Field | Type | Required | Description | -|-------|------|----------|-------------| -| `show_id` | string | Yes | Publisher-assigned identifier. Declared in the publisher's `adagents.json`. Products reference shows via `shows` selectors. Use distribution identifiers for cross-seller matching. | -| `name` | string | Yes | Human-readable show name | -| `description` | string | No | What the show is about | -| `genre` | string[] | No | Genre tags. When `genre_taxonomy` is present, values are taxonomy IDs (e.g., IAB Content Taxonomy 3.0). Otherwise free-form. | -| `genre_taxonomy` | string | No | Taxonomy system for genre values (e.g., `iab_content_3.0`). Recommended for machine-readable brand safety. | -| `language` | string | No | Primary language (BCP 47) | -| `content_rating` | object | No | Baseline rating: `system` + `rating`. Systems: `tv_parental`, `mpaa`, `podcast`, `esrb`, `bbfc`, `fsk`, `acb`, `custom`. Episodes can override. | -| `cadence` | string | No | `daily`, `weekly`, `seasonal`, `event`, `irregular` | -| `season` | string | No | Current or most recent season identifier (e.g., `"3"`, `"2026"`) | -| `status` | string | No | `active`, `hiatus`, `ended`, `upcoming` | -| `production_quality` | string | No | `professional`, `prosumer`, `ugc`. Seller-declared. Maps to OpenRTB `content.prodq`. | -| `talent` | array | No | Hosts, recurring cast, creators. Each entry has `role`, `name`, and optional `brand_url` linking to [brand.json](/docs/brand-protocol/brand-json). | -| `special` | object | No | When present, this show is a special — content anchored to a real-world event or occasion. See [specials and limited series](#specials-and-limited-series). | -| `limited_series` | object | No | When present, this show is a limited series — a bounded run with a defined arc and end date. See [specials and limited series](#specials-and-limited-series). | -| `distribution` | array | No | Where this show is distributed, with platform-specific identifiers per publisher. | -| `related_shows` | array | No | Relationships to other shows: `spinoff`, `companion`, `sequel`, `prequel`, `crossover`. Each entry has `show_id` + `relationship`. References are scoped to the same publisher's `adagents.json`. Symmetric types (`companion`, `crossover`) do not require both shows to declare. | - -```json -{ - "$schema": "https://adcontextprotocol.org/schemas/latest/core/show.json", - "show_id": "pinnacle_challenge", - "name": "The Pinnacle Challenge", - "description": "Competition reality show with extreme physical and mental challenges", - "genre": ["IAB1", "IAB1-6"], - "genre_taxonomy": "iab_content_3.0", - "language": "en", - "content_rating": { - "system": "tv_parental", - "rating": "TV-PG" - }, - "cadence": "weekly", - "season": "1", - "status": "active", - "production_quality": "professional", - "talent": [ - { - "role": "host", - "name": "Jordan Vega", - "brand_url": "https://jordanvega.example.com/brand.json" - } - ], - "distribution": [ - { - "publisher_domain": "youtube.com", - "identifiers": [ - { "type": "youtube_channel_id", "value": "UCexample123456" } - ] - }, - { - "publisher_domain": "acmestreaming.example.com", - "identifiers": [ - { "type": "imdb_id", "value": "tt9876543" } - ] - } - ] -} -``` - -## The episode object - -An episode is a specific installment of a show. Not all episodes will be known in advance — a weekly podcast may only have next week's episode scheduled, and some episodes may be tentative (a playoff Game 7 depends on Game 6). - -| Field | Type | Required | Description | -|-------|------|----------|-------------| -| `episode_id` | string | Yes | Unique identifier within the show | -| `show_id` | string | When needed | Parent show. Required when the product spans multiple shows. | -| `name` | string | No | Episode title | -| `season` | string | No | Season identifier (e.g., `"1"`, `"2026"`) | -| `episode_number` | string | No | Episode number within the season | -| `scheduled_at` | datetime | No | When the episode airs or publishes | -| `status` | string | No | `scheduled`, `tentative`, `live`, `postponed`, `cancelled`, `aired`, `published` | -| `duration_seconds` | integer | No | Expected duration | -| `flexible_end` | boolean | No | Whether end time is approximate (live events) | -| `valid_until` | datetime | No | When this data expires. Agents should re-query before committing budget to tentative episodes. | -| `content_rating` | object | No | Overrides the show's baseline when present | -| `topics` | string[] | No | Episode-level content topics for brand safety. Uses show's `genre_taxonomy` when present. | -| `special` | object | No | Episode-specific event context. When present, this episode is anchored to a real-world event. Overrides the show-level `special`. | -| `guest_talent` | array | No | Episode-specific guests. Additive to the show's recurring `talent`. | -| `ad_inventory` | object | No | Break-based ad inventory configuration | -| `derivative_of` | object | No | When this episode is a clip, highlight, or recap derived from a full episode. Has `episode_id` + `type` (`clip`, `highlight`, `recap`, `trailer`, `bonus`). Source `episode_id` must be in the same response. | - -### Episode status lifecycle - -| Status | Meaning | -|--------|---------| -| `scheduled` | Confirmed, will happen | -| `tentative` | May not happen (depends on external conditions) | -| `live` | Currently airing or streaming right now | -| `postponed` | Was scheduled but delayed to a future date | -| `cancelled` | Will not happen | -| `aired` | Already broadcast — for back-catalog and replay inventory | -| `published` | Already released — for on-demand catch-up inventory | - -Expected transitions: `scheduled` or `tentative` → `live` → `aired` or `published`. A `scheduled` episode may become `postponed` (delayed) or `cancelled`. A `postponed` episode returns to `scheduled` when rescheduled. A `tentative` episode resolves to `scheduled`, `cancelled`, or `postponed`. - -### Inheritance from shows - -Episodes inherit show-level fields they do not override: - -- **`content_rating`**: Episode value overrides show baseline. When absent, the show's rating applies. -- **`special`**: Episode value overrides show-level special. When absent, the show's special applies. A regular show can have event-anchored episodes (e.g., a daily news show with an election night special). -- **`guest_talent`**: Additive to the show's recurring `talent` — does not replace it. -- **`topics`**: Additive context for brand safety, not a replacement for the show's `genre`. - -Buyer agents evaluate both levels: the show baseline provides the default safety profile, and episode fields refine it for specific installments. - -```json -{ - "$schema": "https://adcontextprotocol.org/schemas/latest/core/episode.json", - "episode_id": "s1e03_the_wall", - "show_id": "pinnacle_challenge", - "name": "The Wall", - "season": "1", - "episode_number": "3", - "scheduled_at": "2026-04-07T20:00:00Z", - "status": "scheduled", - "duration_seconds": 3600, - "valid_until": "2026-04-06T20:00:00Z", - "content_rating": { - "system": "tv_parental", - "rating": "TV-14" - }, - "topics": ["IAB17-18"], - "guest_talent": [ - { - "role": "guest", - "name": "Samira Okafor", - "brand_url": "https://samiraokafor.example.com/brand.json" - } - ], - "ad_inventory": { - "expected_breaks": 4, - "total_ad_seconds": 480, - "max_ad_duration_seconds": 120, - "unplanned_breaks": false, - "supported_formats": ["video", "audio"] - } -} -``` - -## Specials and limited series - -Shows can carry optional annotations that signal their nature to buyer agents. These are composable — a show can be both a special and a limited series (e.g., a 4-episode Olympics documentary). - -### Specials - -A special is content anchored to a real-world event or occasion. The `special` object can appear on both shows and episodes. On a show, it means the entire show is event-anchored. On an episode, it means that specific episode is event-anchored (and overrides the show-level special when present, following the same inheritance pattern as `content_rating`). - -| Field | Type | Required | Description | -|-------|------|----------|-------------| -| `name` | string | Yes | Name of the event (e.g., "Olympics 2028", "Super Bowl LXI") | -| `category` | string | No | `awards`, `championship`, `concert`, `conference`, `election`, `festival`, `gala`, `holiday`, `premiere`, `product_launch`, `reunion`, `tribute` | -| `starts` | datetime | No | When the event starts | -| `ends` | datetime | No | When the event ends. Omit for single-day events. | - -```json -{ - "$schema": "https://adcontextprotocol.org/schemas/latest/core/show.json", - "show_id": "apex_finals_2026", - "name": "Apex Championship Finals 2026", - "genre": ["IAB17", "IAB17-12"], - "genre_taxonomy": "iab_content_3.0", - "cadence": "event", - "status": "upcoming", - "special": { - "name": "Apex Championship Finals 2026", - "category": "championship", - "starts": "2026-05-18T19:00:00Z", - "ends": "2026-05-25T23:00:00Z" - }, - "talent": [ - { "role": "host", "name": "Deshawn Moreaux" } - ] -} -``` - -The `special` field is distinct from `cadence`. Cadence describes release frequency (`event` = one-time or occasional). The `special` object describes what real-world event anchors the content and when it happens — information a buyer agent needs to evaluate timing relevance and premium pricing. - -### Limited series - -A limited series is a bounded content run with a defined arc. Unlike ongoing series, limited series have a planned end. - -| Field | Type | Required | Description | -|-------|------|----------|-------------| -| `total_episodes` | integer | No | Planned number of episodes | -| `starts` | datetime | No | When the series begins | -| `ends` | datetime | No | When the series ends | - -```json -{ - "$schema": "https://adcontextprotocol.org/schemas/latest/core/show.json", - "show_id": "ember_s3", - "name": "The Ember", - "description": "Award-winning drama following a chef navigating the pressure of running a top restaurant", - "genre": ["IAB1", "IAB1-7"], - "genre_taxonomy": "iab_content_3.0", - "cadence": "weekly", - "status": "active", - "limited_series": { - "total_episodes": 8, - "starts": "2026-02-28T21:00:00Z", - "ends": "2026-04-18T21:00:00Z" - } -} -``` - -This tells buyer agents that The Ember is a finite opportunity — 8 episodes over 7 weeks. The `limited_series` field is distinct from `season`: a show can have seasons without being limited (ongoing series have seasons too), and a limited series is a structural commitment that the show has a planned end. - -## How products reference shows - -Products reference shows via `shows` — an array of `{publisher_domain, show_ids}` selectors that point to shows declared in the publisher's `adagents.json`. This is the same pattern as `publisher_properties`. Buyers resolve full show objects from the publisher's `adagents.json`. Episodes are listed per-product in the `episodes` array, since different products may scope different episodes of the same show. - -### Run-of-show (no specific episodes) - -A product can reference a show without listing episodes. This means "inventory across this show, whatever episodes air during the flight dates": - -```json -{ - "product_id": "pinnacle_run_of_show", - "name": "Pinnacle Challenge Run of Show", - "shows": [{ "publisher_domain": "acmestreaming.example.com", "show_ids": ["pinnacle_challenge"] }], - "placements": [ - { "placement_id": "pre_roll", "name": "Pre-roll" } - ], - "delivery_type": "non_guaranteed", - "pricing_options": [ - { "pricing_option_id": "cpm", "pricing_model": "cpm", "floor_price": 25.00, "currency": "USD" } - ] -} -``` - -### Specific episodes - -For premium or guaranteed buys, the seller scopes to specific episodes: - -```json -{ - "product_id": "pinnacle_finale", - "name": "Pinnacle Challenge Season Finale Sponsorship", - "shows": [{ "publisher_domain": "acmestreaming.example.com", "show_ids": ["pinnacle_challenge"] }], - "episodes": [ - { - "episode_id": "s1e10_finale", - "name": "The Grand Finale", - "scheduled_at": "2026-06-02T20:00:00Z", - "status": "scheduled", - "ad_inventory": { - "expected_breaks": 6, - "total_ad_seconds": 720, - "unplanned_breaks": false - } - } - ], - "delivery_type": "guaranteed", - "pricing_options": [ - { "pricing_option_id": "flat", "pricing_model": "flat_rate", "fixed_price": 2000000, "currency": "USD" } - ] -} -``` - -## get_products response structure - -Products reference shows via `shows` selectors — the same pattern as `publisher_properties`. Each selector has a `publisher_domain` and `show_ids` array pointing to shows declared in that publisher's `adagents.json`. Buyers resolve full show objects from `adagents.json`. - -```json -{ - "products": [ - { - "product_id": "pinnacle_april_bundle", - "name": "Pinnacle Challenge April Sponsorship", - "shows": [{ "publisher_domain": "acmestreaming.example.com", "show_ids": ["pinnacle_challenge"] }], - "episodes": [ - { - "episode_id": "s1e03", - "name": "The Wall", - "scheduled_at": "2026-04-07T20:00:00Z", - "status": "scheduled", - "content_rating": { "system": "tv_parental", "rating": "TV-14" }, - "guest_talent": [{ "role": "guest", "name": "Samira Okafor" }], - "valid_until": "2026-04-06T20:00:00Z" - }, - { - "episode_id": "s1e04", - "name": "TBD", - "scheduled_at": "2026-04-14T20:00:00Z", - "status": "tentative", - "valid_until": "2026-04-07T20:00:00Z" - } - ], - "placements": [ - { "placement_id": "pre_roll", "name": "Pre-roll" }, - { "placement_id": "mid_roll", "name": "Mid-roll" } - ], - "delivery_type": "guaranteed", - "pricing_options": [ - { "pricing_option_id": "flat", "pricing_model": "flat_rate", "fixed_price": 500000, "currency": "USD" } - ] - } - ] -} -``` - -## Canonical show identity - -A show's identity is **`{publisher_domain, show_id}`** — scoped to the publisher that declares it in their `adagents.json`. This means any show creator can serve as their own canonical registry. - -### Creator as canonical publisher - -A creator like MrBeast declares shows in `mrbeast.com/adagents.json`. Any seller packaging that creator's inventory — a YouTube sales house, a CTV distributor, the creator's own team — references the same canonical source: - -```json -{ - "products": [ - { - "product_id": "beast_games_youtube", - "name": "Beast Games - YouTube Pre-roll", - "shows": [ - { "publisher_domain": "mrbeast.com", "show_ids": ["beast_games"] } - ], - "publisher_properties": [ - { "publisher_domain": "youtube.com", "property_ids": ["UCX6OQ3DkcsbYNE6H8uQQuVA"] } - ], - "delivery_type": "non_guaranteed", - "pricing_options": [ - { "pricing_option_id": "cpm", "pricing_model": "cpm", "floor_price": 18.00, "currency": "USD" } - ] - } - ] -} -``` - -The buyer sees `mrbeast.com` + `beast_games` regardless of which seller or platform the inventory comes from. No cross-referencing of IMDb IDs or distribution identifiers needed — the creator's domain **is** the registry. - -### When creators don't publish adagents.json - -Not every show has a creator-owned domain. A streaming platform's original series may only exist in that platform's `adagents.json`. In that case, the platform is the canonical publisher and `shows` points to their domain. Distribution identifiers on the show object handle cross-seller matching when the same show appears on multiple platforms without a single canonical source. - -### Identity resolution priority - -Buyer agents should resolve show identity in this order: - -1. **Canonical publisher** — `publisher_domain` + `show_id` from the creator's own `adagents.json`. Strongest signal. -2. **Platform-independent identifiers** — `imdb_id`, `gracenote_id`, `eidr_id` from the show's `distribution` array. Reliable cross-reference when no canonical publisher exists. -3. **Platform-specific identifiers** — Spotify, Apple, YouTube IDs. Useful within a platform but not universal. - -## Discovery examples - -### CTV show with episodes - -A streaming platform selling sponsorships against a known show with upcoming episodes: - -```json -{ - "products": [ - { - "product_id": "nova_kitchen_april", - "name": "Nova Kitchen - April Episodes", - "shows": [{ "publisher_domain": "novastreaming.example.com", "show_ids": ["nova_kitchen"] }], - "publisher_properties": [{ - "publisher_domain": "novastreaming.example.com", - "selection_type": "all" - }], - "episodes": [ - { - "episode_id": "s3e09", - "name": "Fire and Ice", - "scheduled_at": "2026-04-05T21:00:00Z", - "status": "scheduled", - "duration_seconds": 2700, - "ad_inventory": { - "expected_breaks": 3, - "total_ad_seconds": 360, - "max_ad_duration_seconds": 30, - "unplanned_breaks": false, - "supported_formats": ["video"] - } - }, - { - "episode_id": "s3e10", - "scheduled_at": "2026-04-12T21:00:00Z", - "status": "tentative", - "valid_until": "2026-04-06T00:00:00Z" - } - ], - "placements": [ - { "placement_id": "pre_roll", "name": "Pre-roll (15s)" }, - { "placement_id": "mid_roll", "name": "Mid-roll (30s)" } - ], - "delivery_type": "guaranteed", - "pricing_options": [ - { "pricing_option_id": "cpm_fixed", "pricing_model": "cpm", "fixed_price": 38.00, "currency": "USD" } - ] - } - ] -} -``` - -### Related shows and derivative content - -A streaming platform offering a main show and its companion after-show, plus highlight clips: - -```json -{ - "products": [ - { - "product_id": "nova_highlights", - "name": "Nova Kitchen Highlights Package", - "shows": [{ "publisher_domain": "novastreaming.example.com", "show_ids": ["nova_kitchen"] }], - "episodes": [ - { - "episode_id": "s3e09", - "name": "Fire and Ice", - "status": "aired", - "duration_seconds": 2700 - }, - { - "episode_id": "s3e09_highlights", - "name": "Fire and Ice - Best Moments", - "status": "published", - "duration_seconds": 180, - "derivative_of": { - "episode_id": "s3e09", - "type": "highlight" - } - } - ], - "placements": [ - { "placement_id": "pre_roll", "name": "Pre-roll (15s)" } - ], - "delivery_type": "non_guaranteed", - "pricing_options": [ - { "pricing_option_id": "cpm", "pricing_model": "cpm", "floor_price": 12.00, "currency": "USD" } - ] - } - ] -} -``` - -The highlight clip references its source episode via `derivative_of`. The companion after-show is linked via `related_shows` on the main show — buyers targeting Nova Kitchen can discover the after-show as an additional reach opportunity. - -### Podcast with distribution - -A podcast network selling across multiple distribution platforms. The show's `distribution` array in `adagents.json` captures the full distribution footprint; buyers use distribution identifiers for cross-seller matching: - -```json -{ - "products": [ - { - "product_id": "wonderstruck_april", - "name": "Wonderstruck Weekly - April Episodes", - "shows": [{ "publisher_domain": "wonderstruck.example.com", "show_ids": ["wonderstruck_weekly"] }], - "episodes": [ - { - "episode_id": "ep47", - "name": "The future of autonomous supply chains", - "scheduled_at": "2026-04-07T10:00:00Z", - "status": "scheduled", - "guest_talent": [ - { "role": "guest", "name": "Kai Tanaka", "brand_url": "https://kaitanaka.example.com/brand.json" } - ] - }, - { - "episode_id": "ep48", - "scheduled_at": "2026-04-14T10:00:00Z", - "status": "tentative" - }, - { - "episode_id": "ep49", - "scheduled_at": "2026-04-21T10:00:00Z", - "status": "tentative" - } - ], - "placements": [ - { "placement_id": "pre_roll", "name": "Pre-roll (30s)" }, - { "placement_id": "mid_roll", "name": "Mid-roll host read (60s)" } - ], - "delivery_type": "guaranteed", - "pricing_options": [ - { "pricing_option_id": "flat_monthly", "pricing_model": "flat_rate", "fixed_price": 15000, "currency": "USD" }, - { "pricing_option_id": "per_episode", "pricing_model": "flat_rate", "fixed_price": 5000, "currency": "USD" } - ] - } - ] -} -``` - -Tentative episodes (ep48, ep49) have no names or guest info — they have not been produced yet. The buyer evaluates based on the show's baseline profile and the known details of ep47. - -### Live event with tentative episodes - -A sports league selling sponsorships against a live event series. Live broadcasts use `flexible_end` because game duration is unpredictable, and `unplanned_breaks` because ad breaks follow game flow (timeouts, period breaks) rather than a fixed schedule. The tentative Game 5 depends on the series outcome — it only happens if neither team wins in four games. - -```json -{ - "products": [ - { - "product_id": "apex_finals_sponsorship", - "name": "Apex Championship Finals — Category Sponsorship", - "shows": [{ "publisher_domain": "apexleague.example.com", "show_ids": ["apex_championship_2026"] }], - "episodes": [ - { - "episode_id": "finals_game4", - "name": "Game 4: Titan City vs Coastal FC", - "scheduled_at": "2026-05-18T19:00:00Z", - "status": "scheduled", - "flexible_end": true, - "ad_inventory": { - "expected_breaks": 8, - "total_ad_seconds": 960, - "max_ad_duration_seconds": 30, - "unplanned_breaks": true, - "supported_formats": ["video"] - } - }, - { - "episode_id": "finals_game5", - "name": "Game 5 — If necessary", - "scheduled_at": "2026-05-21T19:00:00Z", - "status": "tentative", - "flexible_end": true, - "valid_until": "2026-05-19T06:00:00Z", - "ad_inventory": { - "expected_breaks": 8, - "total_ad_seconds": 960, - "max_ad_duration_seconds": 30, - "unplanned_breaks": true, - "supported_formats": ["video"] - } - } - ], - "placements": [ - { "placement_id": "in_game_overlay", "name": "In-game overlay (10s)" }, - { "placement_id": "halftime", "name": "Halftime feature (60s)" } - ], - "delivery_type": "guaranteed", - "exclusivity": "category", - "pricing_options": [ - { "pricing_option_id": "per_game", "pricing_model": "flat_rate", "fixed_price": 750000, "currency": "USD" } - ] - } - ] -} -``` - -This pattern combines several features designed for live inventory: `cadence: "event"` signals a non-recurring series, `flexible_end` tells buyers the broadcast length is approximate, and `unplanned_breaks: true` indicates ad breaks follow game flow rather than a predetermined schedule. The tentative Game 5 includes `valid_until` so buyer agents know when to re-query — if the series ends in four games, that episode will resolve to `cancelled`. Buyers should always check `valid_until` on tentative episodes before committing budget. - -### Discovering shows - -Buyers discover shows through the standard `get_products` workflow. Natural language briefs drive show selection: - -```json -{ - "buying_mode": "brief", - "brief": "Podcast sponsorships for technology shows reaching startup founders in April", - "filters": { - "channels": ["podcast"], - "start_date": "2026-04-01", - "end_date": "2026-04-30" - } -} -``` - -The seller returns products with `shows` selectors. The buyer resolves full show objects from each publisher's [adagents.json](/docs/governance/property/adagents). There is no separate show discovery endpoint — shows surface through product discovery and `adagents.json` crawling. - -## Brand safety - -Shows provide a two-level brand safety model: a show baseline and per-episode overrides. - -### Show baseline - -A show's default brand safety profile comes from: -- `content_rating` — the show's declared rating system and value -- `genre` — content categories (ideally using `genre_taxonomy` for machine-readable evaluation) -- `talent` — hosts and recurring cast, with optional [brand.json](/docs/brand-protocol/brand-json) references for deeper evaluation - -This is what buyers evaluate when individual episode content is not yet known. - -### Episode overrides - -When episode details are available, they can shift the safety profile: -- A `content_rating` that differs from the show baseline (this week is TV-14 instead of TV-PG) -- `guest_talent` that changes the talent profile (a controversial guest) -- `topics` that add episode-specific content signals - -### What is not modeled - -AdCP does not predict content safety for unknown future episodes. A buyer who commits to "all April episodes" buys based on the show's baseline profile, accepting variation. The seller's content standards and the show's track record are the buyer's basis for that decision. - -## Distribution identifiers - -Each show's `distribution` array maps it to specific publisher platforms with platform-specific identifiers. This enables cross-seller matching: when two different sellers both offer products for the same show, a buyer agent can match them via shared identifiers. - -### Cross-seller matching - -Platform-independent identifiers are the most reliable for deduplication: - -| Type | Example | Notes | -|------|---------|-------| -| `imdb_id` | `tt1234567` | Universal cross-platform reference | -| `gracenote_id` | `EP012345678` | Industry standard for TV metadata | -| `eidr_id` | `10.5240/XXXX-XXXX-XXXX-XXXX-XXXX-C` | ISO 10528 for audiovisual content | - -Shows SHOULD include at least one platform-independent identifier when available. - -### Platform-specific identifiers - - - -```json Podcast -{ - "distribution": [ - { - "publisher_domain": "spotify.com", - "identifiers": [{ "type": "spotify_show_id", "value": "4rOoJ6Egrf8K2IrywzwOMk" }] - }, - { - "publisher_domain": "apple.com", - "identifiers": [{ "type": "apple_podcast_id", "value": "1234567890" }] - }, - { - "publisher_domain": "feeds.example.com", - "identifiers": [{ "type": "rss_url", "value": "https://feeds.example.com/show.xml" }] - } - ] -} -``` - -```json Video / CTV -{ - "distribution": [ - { - "publisher_domain": "youtube.com", - "identifiers": [{ "type": "youtube_channel_id", "value": "UCexample123456" }] - }, - { - "publisher_domain": "acmestreaming.example.com", - "identifiers": [{ "type": "amazon_title_id", "value": "B0DFBT5GBP" }] - } - ] -} -``` - - - -Available podcast types: `apple_podcast_id`, `spotify_show_id`, `rss_url`, `podcast_guid`, `amazon_music_id`, `iheart_id`, `podcast_index_id`. Available video/CTV types: `youtube_channel_id`, `youtube_playlist_id`, `amazon_title_id`, `roku_channel_id`, `pluto_channel_id`, `tubi_id`, `peacock_id`, `tiktok_id`, `twitch_channel`. Other: `domain`, `substack_id`. - -## Ad inventory - -Episodes declare break-based ad inventory in the `ad_inventory` object: - -| Field | Type | Description | -|-------|------|-------------| -| `expected_breaks` | integer | Number of planned ad breaks | -| `total_ad_seconds` | integer | Total seconds of ad time across all breaks | -| `max_ad_duration_seconds` | integer | Maximum duration for a single ad within a break | -| `unplanned_breaks` | boolean | `false`: all breaks pre-defined. `true`: breaks driven by live conditions (sports timeouts, live news). | -| `supported_formats` | string[] | Format types supported in breaks (e.g., `"video"`, `"audio"`) | - -For non-break ad formats like host reads, custom integrations, or sponsorships, use product [placements](/docs/media-buy/product-discovery/media-products#placements) instead. A podcast product might have a placement for "mid-roll host read (60s)" — that is a placement on the product, not part of `ad_inventory`. - -## Relationship to LEAP - -The episode model aligns with IAB Tech Lab's LEAP Forecasting API for live streaming events: - -| LEAP concept | AdCP equivalent | -|-------------|----------------| -| UpcomingEvent | Episode (`scheduled_at` + `status`) | -| Content (AdCOM 1.0) | Show metadata (`genre`, `content_rating`, `talent`) | -| AdInventoryConfiguration | Episode `ad_inventory` + product placements | -| Event status | Episode `status` (`scheduled`, `tentative`, `cancelled`) | -| Unplanned breaks | `ad_inventory.unplanned_breaks` | -| Flexible end time | Episode `flexible_end` | - -LEAP targets SSP-to-DSP plumbing. AdCP operates at the agent-to-agent negotiation layer where buying decisions happen. - -## Show targeting - -Products with multiple shows default to bundles — the buyer gets all listed shows. Sellers can set `show_targeting_allowed: true` to let buyers target a subset, the same pattern as `property_targeting_allowed` for properties. - -| `show_targeting_allowed` | Meaning | -|---|---| -| `false` (default) | Bundle — buyer gets all shows | -| `true` | Buyer can select specific shows in the media buy | - -## Multi-show bundles - -A single product can span multiple shows by listing multiple show IDs in `shows`. When a product has multiple shows, each episode MUST include `show_id` so the buyer agent knows which show each episode belongs to. - -```json -{ - "products": [ - { - "product_id": "technet_april_bundle", - "name": "TechNet Podcast Bundle - April", - "shows": [{ "publisher_domain": "technet.example.com", "show_ids": ["tech_weekly", "startup_hour"] }], - "episodes": [ - { "episode_id": "tw_ep12", "show_id": "tech_weekly", "scheduled_at": "2026-04-07T10:00:00Z", "status": "scheduled" }, - { "episode_id": "tw_ep13", "show_id": "tech_weekly", "scheduled_at": "2026-04-14T10:00:00Z", "status": "tentative" }, - { "episode_id": "sh_ep30", "show_id": "startup_hour", "scheduled_at": "2026-04-09T14:00:00Z", "status": "scheduled" }, - { "episode_id": "sh_ep31", "show_id": "startup_hour", "scheduled_at": "2026-04-16T14:00:00Z", "status": "tentative" } - ], - "delivery_type": "guaranteed", - "pricing_options": [ - { "pricing_option_id": "bundle", "pricing_model": "flat_rate", "fixed_price": 25000, "currency": "USD" } - ] - } - ] -} -``` - -## See also - -- [Media products](/docs/media-buy/product-discovery/media-products) — the full product model -- [brand.json](/docs/brand-protocol/brand-json) — talent identity and brand safety evaluation -- [Product discovery](/docs/media-buy/product-discovery) — how buyers discover inventory via `get_products` diff --git a/docs/media-buy/task-reference/get_products.mdx b/docs/media-buy/task-reference/get_products.mdx index 3a531c067c..def5729e5d 100644 --- a/docs/media-buy/task-reference/get_products.mdx +++ b/docs/media-buy/task-reference/get_products.mdx @@ -240,8 +240,8 @@ Returns an array of `products` and optionally `proposals`. | `delivery_type` | string | `"guaranteed"` or `"non_guaranteed"` | | `delivery_measurement` | DeliveryMeasurement | (Optional) How delivery is measured (impressions, views, etc.) | | `pricing_options` | PricingOption[] | Available pricing models (CPM, CPCV, etc.). Auction options may include `floor_price` and optional `price_guidance`. Bid-based auction models (CPM, vCPM, CPC, CPCV, CPV) may also include optional `max_bid` (boolean). | -| `shows` | ShowSelector[] | (Optional) Shows available in this product. Each entry has `publisher_domain` and `show_ids`. Buyers resolve full show objects from the referenced `adagents.json`. See [Shows and episodes](/docs/media-buy/product-discovery/shows-and-episodes). | -| `show_targeting_allowed` | boolean | (Optional, default: false) Whether buyers can target a subset of this product's shows. When false, the product is a bundle. | +| `shows` | CollectionSelector[] | (Optional) Collections available in this product. Each entry has `publisher_domain` and `collection_ids`. Buyers resolve full collection objects from the referenced `adagents.json`. See [Collections and installments](/docs/media-buy/product-discovery/collections-and-installments). | +| `collection_targeting_allowed` | boolean | (Optional, default: false) Whether buyers can target a subset of this product's shows. When false, the product is a bundle. | | `brief_relevance` | string | Why this product matches the brief (when brief provided) | ### Proposals Array (Optional) diff --git a/docs/reference/release-notes.mdx b/docs/reference/release-notes.mdx index 59a115d1c0..ce25853a79 100644 --- a/docs/reference/release-notes.mdx +++ b/docs/reference/release-notes.mdx @@ -19,7 +19,7 @@ High-level summaries of major AdCP releases with migration guidance. For detaile 2. **Visual Guidelines on Brand Manifest** — Structured `visual_guidelines` field on `brand.json`: photography, graphic style, shapes, iconography, composition, motion, logo placement, colorways, type scale, asset libraries, and restrictions. Enables generative creative systems to produce on-brand assets. -3. **Shows and Episodes** — Content dimension for products representing persistent programs (podcasts, TV series, YouTube channels). `shows` on products references shows declared in an `adagents.json` by domain and show ID — show creators can serve as canonical sources regardless of distribution. `show_targeting_allowed` controls whether buyers can select a subset or get the bundle. Distribution identifiers for cross-seller matching, episode lifecycle states, break-based ad inventory, talent linking, show relationships, international content ratings. +3. **Collections and Installments** — Content dimension for products representing persistent programs (podcasts, TV series, YouTube channels). `collections` on products references collections declared in an `adagents.json` by domain and collection ID — collection creators can serve as canonical sources regardless of distribution. `collection_targeting_allowed` controls whether buyers can select a subset or get the bundle. Distribution identifiers for cross-seller matching, installment lifecycle states, break-based ad inventory, talent linking, collection relationships, international content ratings. 4. **Sponsored Intelligence Channel** — `sponsored_intelligence` added to the media channel taxonomy for AI platform advertising (AI assistants, AI search, generative AI experiences). @@ -204,7 +204,7 @@ High-level summaries of major AdCP releases with migration guidance. For detaile 12. **Typed Asset Requirements** - Discriminated union schemas for all 12 asset types (image, video, audio, text, markdown, HTML, CSS, JavaScript, VAST, DAAST, URL, webhook) using `asset_type` as discriminator. -13. **Universal Macros** - `universal-macro.json` enum defining all 54 standard ad-serving macros, including 6 new additions: `GPP_SID`, `IP_ADDRESS`, `STATION_ID`, `SHOW_NAME`, `EPISODE_ID`, `AUDIO_DURATION`. +13. **Universal Macros** - `universal-macro.json` enum defining all 54 standard ad-serving macros, including 6 new additions: `GPP_SID`, `IP_ADDRESS`, `STATION_ID`, `COLLECTION_NAME`, `INSTALLMENT_ID`, `AUDIO_DURATION`. 14. **Brand Manifest Improvements** - Structured tone object with `voice`, `attributes`, `dos`, `donts` fields. Logo objects gain `orientation`, `background`, `variant`, `usage` fields. diff --git a/docs/reference/roadmap.mdx b/docs/reference/roadmap.mdx index 5d41f8ff89..fab541e0e7 100644 --- a/docs/reference/roadmap.mdx +++ b/docs/reference/roadmap.mdx @@ -43,9 +43,9 @@ Version 3.0 is a major evolution of the protocol. Three beta releases and two re - **beta.2** - Accounts & Agents, property targeting, A2UI for Sponsored Intelligence, CTV/streaming constraints, creative protocol discovery - **beta.3** - Delivery forecasting, brand protocol, account management, commerce media, creative delivery reporting, CPA & TIME pricing, conversion tracking, signal catalog, cursor-based pagination, accessibility, targeting restrictions - **rc.1** - AI provenance and disclosure, creative compliance, structured error recovery, keyword targeting, optimization goals redesign, signal pricing models, dimension breakdowns, reach optimization, device type targeting, typed refinement with seller acknowledgment, agent ergonomics, manifest unification, signal deactivation -- **rc.2** - Brand rights licensing, visual guidelines for generative creative, shows and episodes, AI media channel, property governance integration, account model simplification +- **rc.2** - Brand rights licensing, visual guidelines for generative creative, collections and installments, AI media channel, property governance integration, account model simplification -rc.2 added brand-side capabilities (rights licensing, visual guidelines) and content modeling (shows and episodes) based on working group feedback. +rc.2 added brand-side capabilities (rights licensing, visual guidelines) and content modeling (collections and installments) based on working group feedback. ### Remaining for GA diff --git a/docs/reference/whats-new-in-v3.mdx b/docs/reference/whats-new-in-v3.mdx index 0a91e594ba..1bbfa4ede6 100644 --- a/docs/reference/whats-new-in-v3.mdx +++ b/docs/reference/whats-new-in-v3.mdx @@ -1,6 +1,6 @@ --- title: AdCP 3.0 -description: "What's new in AdCP 3.0: brand identity and rights, creative workflow upgrades, governance, sponsored intelligence, shows and episodes, 20 media channels, and migration guides from v2." +description: "What's new in AdCP 3.0: brand identity and rights, creative workflow upgrades, governance, sponsored intelligence, collections and installments, 20 media channels, and migration guides from v2." "og:title": "AdCP — AdCP 3.0" --- @@ -23,7 +23,7 @@ AdCP 3.0 expands the protocol beyond media buying into brand identity, governanc | **Creative libraries** | Media Buy-centric creative reads/writes | `list_creatives` / `sync_creatives` as Creative Protocol operations plus `supports_generation`, `supports_transformation`, and `has_creative_library` discovery | | **Creative governance** | No creative evaluation protocol | `get_creative_features` for security scanning, quality, content categorization | | **Disclosure matching** | Position-only disclosure handling | Position + persistence-aware disclosure matching (`continuous`, `initial`, `flexible`) | -| **Shows and episodes** | No content programming model | `shows` on products with distribution IDs, episode lifecycle, break-based inventory | +| **Collections and installments** | No content programming model | `shows` on products with distribution IDs, installment lifecycle, break-based inventory | | **Planning ergonomics** | No `time_budget`, `preferred_delivery_types`, `exclusivity`, or per-package flights | `time_budget` + `incomplete`, `preferred_delivery_types`, `exclusivity`, optional `delivery_measurement`, and package-level `start_time` / `end_time` | | **Channel model** | 9 channels | 20 planning-oriented channels (including `sponsored_intelligence`) | | **Capability discovery** | Agent card extensions | Runtime `get_adcp_capabilities` task | @@ -98,14 +98,14 @@ Structured `visual_guidelines` on `brand.json` complement rights by giving gener --- -### Shows and episodes +### Collections and installments -Products can now reference persistent content programs — podcasts, TV series, YouTube channels — via `shows`, which follows the same publisher-scoped selector pattern as `publisher_properties`. Shows are declared in the publisher's `adagents.json` and products reference them by publisher domain and show ID. Buyers resolve full show objects from `adagents.json`. Shows include distribution identifiers for cross-seller matching, episode lifecycle states (scheduled, tentative, live, postponed, cancelled, aired, published), break-based ad inventory configuration, talent linking to `brand.json`, and international content rating systems. +Products can now reference persistent content programs — podcasts, TV series, YouTube channels — via `collections`, which follows the same publisher-scoped selector pattern as `publisher_properties`. Collections are declared in the publisher's `adagents.json` and products reference them by publisher domain and collection ID. Buyers resolve full collection objects from `adagents.json`. Collections include distribution identifiers for cross-seller matching, installment lifecycle states (scheduled, tentative, live, postponed, cancelled, aired, published), break-based ad inventory configuration, talent linking to `brand.json`, and international content rating systems. Shows support relationships (spinoff, companion, sequel, prequel, crossover) and derivative content (clips, highlights, recaps) for comprehensive content modeling. - - Full specification including show schemas, episode lifecycle, and break-based inventory. + + Full specification including collection schemas, installment lifecycle, and break-based inventory. --- diff --git a/server/src/addie/mcp/certification-tools.ts b/server/src/addie/mcp/certification-tools.ts index 078703e21f..18d9493b4c 100644 --- a/server/src/addie/mcp/certification-tools.ts +++ b/server/src/addie/mcp/certification-tools.ts @@ -913,7 +913,7 @@ const MODULE_RESOURCES: Record = { { label: 'Publisher track overview', url: `${DOCS_BASE}/docs/learning/tracks/publisher` }, { label: 'Get products task', url: `${DOCS_BASE}/docs/media-buy/task-reference/get_products` }, { label: 'Media products', url: `${DOCS_BASE}/docs/media-buy/product-discovery/media-products` }, - { label: 'Shows and episodes', url: `${DOCS_BASE}/docs/media-buy/product-discovery/shows-and-episodes` }, + { label: 'Shows and episodes', url: `${DOCS_BASE}/docs/media-buy/product-discovery/collections-and-installments` }, { label: 'Catalogs and product data', url: `${DOCS_BASE}/docs/creative/catalogs` }, { label: 'Capability discovery', url: `${DOCS_BASE}/docs/protocol/get_adcp_capabilities` }, { label: 'Sponsored Intelligence guide', url: `${DOCS_BASE}/docs/sponsored-intelligence/monetizing-ai` }, @@ -927,7 +927,7 @@ const MODULE_RESOURCES: Record = { { label: 'Generative creative', url: `${DOCS_BASE}/docs/creative/generative-creative` }, { label: 'Sales agent creative capabilities', url: `${DOCS_BASE}/docs/creative/sales-agent-creative-capabilities` }, { label: 'List creative formats task', url: `${DOCS_BASE}/docs/creative/task-reference/list_creative_formats` }, - { label: 'Shows and episodes', url: `${DOCS_BASE}/docs/media-buy/product-discovery/shows-and-episodes` }, + { label: 'Shows and episodes', url: `${DOCS_BASE}/docs/media-buy/product-discovery/collections-and-installments` }, { label: 'Get creative delivery task', url: `${DOCS_BASE}/docs/creative/task-reference/get_creative_delivery` }, { label: 'CTV and connected TV', url: `${DOCS_BASE}/docs/creative/channels/ctv` }, { label: 'Social and feed-native', url: `${DOCS_BASE}/docs/creative/channels/social-native` }, @@ -969,7 +969,7 @@ const MODULE_RESOURCES: Record = { { label: 'acquire_rights task', url: `${DOCS_BASE}/docs/brand-protocol/tasks/acquire_rights` }, { label: 'update_rights task', url: `${DOCS_BASE}/docs/brand-protocol/tasks/update_rights` }, { label: 'For rights holders', url: `${DOCS_BASE}/docs/brand-protocol/for-rights-holders` }, - { label: 'Shows and episodes — talent linking', url: `${DOCS_BASE}/docs/media-buy/product-discovery/shows-and-episodes` }, + { label: 'Shows and episodes — talent linking', url: `${DOCS_BASE}/docs/media-buy/product-discovery/collections-and-installments` }, { label: 'Content standards', url: `${DOCS_BASE}/docs/governance/content-standards` }, { label: 'Campaign governance', url: `${DOCS_BASE}/docs/governance/campaign` }, { label: 'Governance protocol', url: `${DOCS_BASE}/docs/governance/overview` }, @@ -1070,7 +1070,7 @@ const MODULE_RESOURCES: Record = { S4: [ { label: 'Governance protocol', url: `${DOCS_BASE}/docs/governance/overview` }, { label: 'Content standards', url: `${DOCS_BASE}/docs/governance/content-standards` }, - { label: 'Shows and episodes — brand safety', url: `${DOCS_BASE}/docs/media-buy/product-discovery/shows-and-episodes` }, + { label: 'Shows and episodes — brand safety', url: `${DOCS_BASE}/docs/media-buy/product-discovery/collections-and-installments` }, { label: 'Property governance', url: `${DOCS_BASE}/docs/governance/property/index` }, { label: 'Campaign governance', url: `${DOCS_BASE}/docs/governance/campaign` }, { label: 'Campaign governance safety model', url: `${DOCS_BASE}/docs/governance/campaign/safety-model` }, diff --git a/static/schemas/source/adagents.json b/static/schemas/source/adagents.json index e71607b9bf..dad030007d 100644 --- a/static/schemas/source/adagents.json +++ b/static/schemas/source/adagents.json @@ -90,11 +90,11 @@ }, "minItems": 1 }, - "shows": { + "collections": { "type": "array", - "description": "Shows produced or distributed by this publisher. Declares the content programs whose inventory is sold through authorized agents. Products in get_products responses reference these shows by show_id.", + "description": "Collections produced or distributed by this publisher. Declares the content programs whose inventory is sold through authorized agents. Products in get_products responses reference these collections by collection_id.", "items": { - "$ref": "/schemas/core/show.json" + "$ref": "/schemas/core/collection.json" } }, "tags": { diff --git a/static/schemas/source/brand.json b/static/schemas/source/brand.json index 23ba787a8e..2e2dc4eb2a 100644 --- a/static/schemas/source/brand.json +++ b/static/schemas/source/brand.json @@ -372,28 +372,28 @@ "phone": { "type": "string", "description": "Contact phone number" } } }, - "shows": { + "collections": { "type": "array", - "description": "Shows this person or brand is associated with. Enables bidirectional linking: a show's talent references brand.json via brand_url, and brand.json links back to shows.", + "description": "Collections this person or brand is associated with. Enables bidirectional linking: a collection's talent references brand.json via brand_url, and brand.json links back to collections.", "items": { "type": "object", "properties": { - "show_id": { + "collection_id": { "type": "string", - "description": "Show identifier as used in the seller's get_products responses" + "description": "Collection identifier as used in the seller's get_products responses" }, "name": { "type": "string", - "description": "Human-readable show name" + "description": "Human-readable collection name" }, "role": { "$ref": "/schemas/enums/talent-role.json", - "description": "This person's role on the show" + "description": "This person's role on the collection" }, "seller_agent_url": { "type": "string", "format": "uri", - "description": "URL of the sales agent that sells inventory for this show. Buyer agents can query this agent for show products." + "description": "URL of the sales agent that sells inventory for this collection. Buyer agents can query this agent for collection products." } }, "required": ["name"], diff --git a/static/schemas/source/content-standards/artifact.json b/static/schemas/source/content-standards/artifact.json index 286e426260..0d91ffb562 100644 --- a/static/schemas/source/content-standards/artifact.json +++ b/static/schemas/source/content-standards/artifact.json @@ -232,9 +232,9 @@ "type": "string", "description": "Apple Podcasts ID" }, - "spotify_show_id": { + "spotify_collection_id": { "type": "string", - "description": "Spotify show ID" + "description": "Spotify collection ID" }, "podcast_guid": { "type": "string", diff --git a/static/schemas/source/core/ad-inventory-config.json b/static/schemas/source/core/ad-inventory-config.json index 845bf4ed19..d92ac49fe6 100644 --- a/static/schemas/source/core/ad-inventory-config.json +++ b/static/schemas/source/core/ad-inventory-config.json @@ -2,13 +2,13 @@ "$schema": "http://json-schema.org/draft-07/schema#", "$id": "/schemas/core/ad-inventory-config.json", "title": "Ad Inventory Configuration", - "description": "Break-based ad inventory configuration for an episode. Describes the ad breaks available within the content. For non-break ad formats (host reads, integrations, sponsorships), use product placements instead.", + "description": "Break-based ad inventory configuration for an installment. Describes the ad breaks available within the content. For non-break ad formats (host reads, integrations, sponsorships), use product placements instead.", "type": "object", "properties": { "expected_breaks": { "type": "integer", "minimum": 0, - "description": "Number of planned ad breaks in the episode" + "description": "Number of planned ad breaks in the installment" }, "total_ad_seconds": { "type": "integer", diff --git a/static/schemas/source/core/show-distribution.json b/static/schemas/source/core/collection-distribution.json similarity index 64% rename from static/schemas/source/core/show-distribution.json rename to static/schemas/source/core/collection-distribution.json index 29f375d27d..75b6487b2d 100644 --- a/static/schemas/source/core/show-distribution.json +++ b/static/schemas/source/core/collection-distribution.json @@ -1,17 +1,17 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "/schemas/core/show-distribution.json", - "title": "Show Distribution", - "description": "A show's presence on a specific publisher platform, identified by platform-specific identifiers. Enables cross-seller matching when the same show is sold by different agents.", + "$id": "/schemas/core/collection-distribution.json", + "title": "Collection Distribution", + "description": "A collection's presence on a specific publisher platform, identified by platform-specific identifiers. Enables cross-seller matching when the same collection is sold by different agents.", "type": "object", "properties": { "publisher_domain": { "type": "string", - "description": "Domain of the publisher platform where the show is distributed (e.g., 'youtube.com', 'spotify.com')" + "description": "Domain of the publisher platform where the collection is distributed (e.g., 'youtube.com', 'spotify.com')" }, "identifiers": { "type": "array", - "description": "Platform-specific identifiers for the show on this publisher", + "description": "Platform-specific identifiers for the collection on this publisher", "items": { "type": "object", "properties": { diff --git a/static/schemas/source/core/collection-selector.json b/static/schemas/source/core/collection-selector.json new file mode 100644 index 0000000000..97bbc4126b --- /dev/null +++ b/static/schemas/source/core/collection-selector.json @@ -0,0 +1,27 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/core/collection-selector.json", + "title": "Collection Selector", + "description": "References collections declared in an adagents.json. Buyers resolve full collection objects by fetching the adagents.json at the given domain and matching collection_ids against its collections array.", + "type": "object", + "properties": { + "publisher_domain": { + "type": "string", + "description": "Domain where the adagents.json declaring these collections is hosted (e.g., 'mrbeast.com'). The collections array in that file contains the authoritative collection definitions.", + "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$" + }, + "collection_ids": { + "type": "array", + "description": "Collection IDs from the adagents.json collections array. Each ID must match a collection_id declared in that file.", + "items": { + "type": "string" + }, + "minItems": 1 + } + }, + "required": [ + "publisher_domain", + "collection_ids" + ], + "additionalProperties": true +} diff --git a/static/schemas/source/core/collection.json b/static/schemas/source/core/collection.json new file mode 100644 index 0000000000..8ec34bc264 --- /dev/null +++ b/static/schemas/source/core/collection.json @@ -0,0 +1,111 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/core/collection.json", + "title": "Collection", + "description": "A recurring inventory container — a named program, publication, event series, or rotation that produces bookable installments on a defined cadence. The kind field indicates how to interpret this collection: 'series' for TV/podcast programs, 'publication' for print/newsletter titles, 'event_series' for live events, 'rotation' for DOOH scheduling. Declared in the publisher's adagents.json and referenced by products via collection selectors.", + "type": "object", + "properties": { + "collection_id": { + "type": "string", + "description": "Publisher-assigned identifier for this collection. Declared in the publisher's adagents.json collections array. Products reference collections via collection selectors with publisher_domain and collection_ids. Use distribution identifiers for cross-seller matching across publishers." + }, + "name": { + "type": "string", + "description": "Human-readable collection name" + }, + "kind": { + "type": "string", + "enum": ["series", "publication", "event_series", "rotation"], + "description": "What kind of content program this is. Helps agents interpret installments correctly: 'series' installments are TV/podcast episodes, 'publication' installments are print issues, 'event_series' installments are live event airings, 'rotation' installments are DOOH scheduling periods. Defaults to 'series' when absent." + }, + "description": { + "type": "string", + "description": "What the collection is about" + }, + "genre": { + "type": "array", + "description": "Genre tags. When genre_taxonomy is present, values are taxonomy IDs (e.g., IAB Content Taxonomy 3.0 codes). Otherwise free-form.", + "items": { + "type": "string" + } + }, + "genre_taxonomy": { + "type": "string", + "description": "Taxonomy system for genre values (e.g., 'iab_content_3.0'). When present, genre values should be valid taxonomy IDs. Recommended for machine-readable brand safety evaluation." + }, + "language": { + "type": "string", + "description": "Primary language (BCP 47 tag, e.g., 'en', 'es-MX')" + }, + "content_rating": { + "$ref": "/schemas/core/content-rating.json", + "description": "Baseline content rating for the collection. Individual installments may override this." + }, + "cadence": { + "$ref": "/schemas/enums/collection-cadence.json", + "description": "How frequently the collection releases new installments" + }, + "season": { + "type": "string", + "description": "Current or most recent season identifier (e.g., '3', '2026', 'spring_2026'). A lightweight label — not a full season object." + }, + "status": { + "$ref": "/schemas/enums/collection-status.json", + "description": "Lifecycle status of the collection" + }, + "production_quality": { + "$ref": "/schemas/enums/production-quality.json", + "description": "Production quality tier. Seller-declared. Maps to OpenRTB content.prodq (professional=1, prosumer=2, ugc=3)." + }, + "talent": { + "type": "array", + "description": "Hosts, recurring cast, creators associated with the collection. Each talent entry may include a brand_url linking to their brand.json identity.", + "items": { + "$ref": "/schemas/core/talent.json" + } + }, + "special": { + "$ref": "/schemas/core/special.json", + "description": "When present, this collection is a special — content anchored to a real-world event or occasion. Individual installments may override with their own event context." + }, + "limited_series": { + "$ref": "/schemas/core/limited-series.json", + "description": "When present, this collection is a limited series — a bounded run with a defined arc, installment count, and end date." + }, + "distribution": { + "type": "array", + "description": "Where this collection is distributed. Each entry maps the collection to a publisher platform with platform-specific identifiers. Collections SHOULD include at least one platform-independent identifier (imdb_id, gracenote_id, eidr_id) when available.", + "items": { + "$ref": "/schemas/core/collection-distribution.json" + } + }, + "deadline_policy": { + "$ref": "/schemas/core/deadline-policy.json", + "description": "Default deadline rules for installments of this collection. Agents compute absolute deadlines from each installment's scheduled_at and these lead times. Installments with explicit deadlines override this policy." + }, + "related_collections": { + "type": "array", + "description": "Relationships to other collections (spin-offs, companion collections, etc.). Each entry references another collection by collection_id within the same publisher's adagents.json.", + "items": { + "type": "object", + "properties": { + "collection_id": { + "type": "string", + "description": "The related collection's collection_id within this seller's response" + }, + "relationship": { + "$ref": "/schemas/enums/collection-relationship.json", + "description": "How the collections are related" + } + }, + "required": ["collection_id", "relationship"], + "additionalProperties": false + } + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "required": ["collection_id", "name"], + "additionalProperties": true +} diff --git a/static/schemas/source/core/deadline-policy.json b/static/schemas/source/core/deadline-policy.json new file mode 100644 index 0000000000..3d0fb4d763 --- /dev/null +++ b/static/schemas/source/core/deadline-policy.json @@ -0,0 +1,52 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/core/deadline-policy.json", + "title": "Deadline Policy", + "description": "Default deadline rules for installments that do not declare their own deadlines. Agents compute absolute deadlines from the installment's scheduled_at and these lead times. Installments with explicit deadlines override this policy. booking_lead_days SHOULD be >= cancellation_lead_days (bookings close before cancellation window closes).", + "type": "object", + "properties": { + "booking_lead_days": { + "type": "integer", + "minimum": 0, + "description": "Days before scheduled_at by which the placement must be booked" + }, + "cancellation_lead_days": { + "type": "integer", + "minimum": 0, + "description": "Days before scheduled_at by which cancellation is penalty-free" + }, + "material_stages": { + "type": "array", + "description": "Default material submission stages. Items MUST be in chronological order (earliest due first). Agents compute due_at as: installment.scheduled_at minus lead_days.", + "items": { + "type": "object", + "properties": { + "stage": { + "type": "string", + "description": "Stage identifier. Standard values: 'draft' (needs seller processing), 'final' (production-ready).", + "examples": ["draft", "final"] + }, + "lead_days": { + "type": "integer", + "minimum": 0, + "description": "Days before scheduled_at this stage is due" + }, + "label": { + "type": "string", + "description": "What the seller needs at this stage" + } + }, + "required": ["stage", "lead_days"], + "additionalProperties": true + }, + "minItems": 1 + }, + "business_days_only": { + "type": "boolean", + "description": "When true, lead_days counts business days (Mon-Fri) rather than calendar days. Defaults to false.", + "default": false + } + }, + "minProperties": 1, + "additionalProperties": true +} diff --git a/static/schemas/source/core/episode.json b/static/schemas/source/core/episode.json deleted file mode 100644 index a5b833f09c..0000000000 --- a/static/schemas/source/core/episode.json +++ /dev/null @@ -1,99 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "/schemas/core/episode.json", - "title": "Episode", - "description": "A specific installment of a show. Episodes inherit show-level fields they don't override: content_rating defaults to the show's baseline, guest_talent is additive to the show's recurring talent, and topics add context beyond the show's genre.", - "type": "object", - "properties": { - "episode_id": { - "type": "string", - "description": "Unique identifier for this episode within the show" - }, - "show_id": { - "type": "string", - "description": "Parent show reference. Required when the product spans multiple shows. Maps to a show_id declared in one of the publishers' adagents.json files referenced by the product's shows selectors." - }, - "name": { - "type": "string", - "description": "Episode title" - }, - "season": { - "type": "string", - "description": "Season identifier (e.g., '1', '2024', 'spring_2026')" - }, - "episode_number": { - "type": "string", - "description": "Episode number within the season (e.g., '3', '47')" - }, - "scheduled_at": { - "type": "string", - "format": "date-time", - "description": "When the episode airs or publishes (ISO 8601)" - }, - "status": { - "$ref": "/schemas/enums/episode-status.json", - "description": "Lifecycle status of the episode" - }, - "duration_seconds": { - "type": "integer", - "minimum": 0, - "description": "Expected duration of the episode in seconds" - }, - "flexible_end": { - "type": "boolean", - "description": "Whether the end time is approximate (live events, sports)" - }, - "valid_until": { - "type": "string", - "format": "date-time", - "description": "When this episode data expires and should be re-queried. Agents should re-query before committing budget to products with tentative episodes." - }, - "content_rating": { - "$ref": "/schemas/core/content-rating.json", - "description": "Episode-specific content rating. Overrides the show's baseline content_rating when present." - }, - "topics": { - "type": "array", - "description": "Content topics for this episode. Uses the same taxonomy as the show's genre_taxonomy when present. Enables episode-level brand safety evaluation beyond content_rating.", - "items": { - "type": "string" - } - }, - "special": { - "$ref": "/schemas/core/special.json", - "description": "Episode-specific event context. When present, this episode is anchored to a real-world event. Overrides the show-level special when present." - }, - "guest_talent": { - "type": "array", - "description": "Episode-specific guests and talent. Additive to the show's recurring talent.", - "items": { - "$ref": "/schemas/core/talent.json" - } - }, - "ad_inventory": { - "$ref": "/schemas/core/ad-inventory-config.json", - "description": "Break-based ad inventory for this episode. For non-break formats (host reads, integrations), use product placements." - }, - "derivative_of": { - "type": "object", - "description": "When this episode is a clip, highlight, or recap derived from a full episode. The source episode_id must reference an episode within the same response.", - "properties": { - "episode_id": { - "type": "string", - "description": "The source episode this content is derived from" - }, - "type": { - "$ref": "/schemas/enums/derivative-type.json", - "description": "What kind of derivative content this is" - } - }, - "required": ["episode_id", "type"], - "additionalProperties": false - }, - "ext": { - "$ref": "/schemas/core/ext.json" - } - }, - "required": ["episode_id"], - "additionalProperties": true -} diff --git a/static/schemas/source/core/format.json b/static/schemas/source/core/format.json index 33b906d6ee..60405da585 100644 --- a/static/schemas/source/core/format.json +++ b/static/schemas/source/core/format.json @@ -104,37 +104,41 @@ }, "dimensions": { "type": "object", - "description": "Dimensions for this rendered piece (in pixels)", + "description": "Dimensions for this rendered piece. Defaults to pixels when unit is absent.", "properties": { "width": { - "type": "integer", - "minimum": 1, - "description": "Fixed width in pixels" + "type": "number", + "exclusiveMinimum": 0, + "description": "Fixed width. Interpretation depends on unit (default: pixels)." }, "height": { - "type": "integer", - "minimum": 1, - "description": "Fixed height in pixels" + "type": "number", + "exclusiveMinimum": 0, + "description": "Fixed height. Interpretation depends on unit (default: pixels)." }, "min_width": { - "type": "integer", - "minimum": 1, - "description": "Minimum width in pixels for responsive renders" + "type": "number", + "exclusiveMinimum": 0, + "description": "Minimum width for responsive renders" }, "min_height": { - "type": "integer", - "minimum": 1, - "description": "Minimum height in pixels for responsive renders" + "type": "number", + "exclusiveMinimum": 0, + "description": "Minimum height for responsive renders" }, "max_width": { - "type": "integer", - "minimum": 1, - "description": "Maximum width in pixels for responsive renders" + "type": "number", + "exclusiveMinimum": 0, + "description": "Maximum width for responsive renders" }, "max_height": { - "type": "integer", - "minimum": 1, - "description": "Maximum height in pixels for responsive renders" + "type": "number", + "exclusiveMinimum": 0, + "description": "Maximum height for responsive renders" + }, + "unit": { + "$ref": "/schemas/enums/dimension-unit.json", + "description": "Unit of measurement for width/height values. Defaults to 'px' when absent. Print formats use 'inches' or 'cm'." }, "responsive": { "type": "object", @@ -154,8 +158,8 @@ }, "aspect_ratio": { "type": "string", - "description": "Fixed aspect ratio constraint (e.g., '16:9', '4:3', '1:1')", - "pattern": "^\\d+:\\d+$" + "description": "Fixed aspect ratio constraint (e.g., '16:9', '4:3', '1:1', '1.91:1')", + "pattern": "^\\d+(\\.\\d+)?:\\d+(\\.\\d+)?$" } } } diff --git a/static/schemas/source/core/installment-deadlines.json b/static/schemas/source/core/installment-deadlines.json new file mode 100644 index 0000000000..2aa57f8ad3 --- /dev/null +++ b/static/schemas/source/core/installment-deadlines.json @@ -0,0 +1,29 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/core/installment-deadlines.json", + "title": "Installment Deadlines", + "description": "Deadlines associated with a bookable installment. Applies to any channel where inventory is tied to a scheduled unit: print issues, podcast episodes, linear TV airings, influencer posts, DOOH rotations.", + "type": "object", + "properties": { + "booking_deadline": { + "type": "string", + "format": "date-time", + "description": "Last date/time to book a placement in this installment (ISO 8601). After this point, the seller will not accept new bookings." + }, + "cancellation_deadline": { + "type": "string", + "format": "date-time", + "description": "Last date/time to cancel without penalty (ISO 8601). Cancellations after this point may incur fees per the seller's terms." + }, + "material_deadlines": { + "type": "array", + "description": "Stages for creative material submission. Items MUST be in chronological order by due_at (earliest first). Typical pattern: 'draft' for raw materials the seller will process, 'final' for production-ready assets. Print example: draft artwork then press-ready PDF. Influencer example: talking points then approved script.", + "items": { + "$ref": "/schemas/core/material-deadline.json" + }, + "minItems": 1 + } + }, + "minProperties": 1, + "additionalProperties": true +} diff --git a/static/schemas/source/core/installment.json b/static/schemas/source/core/installment.json new file mode 100644 index 0000000000..745c533648 --- /dev/null +++ b/static/schemas/source/core/installment.json @@ -0,0 +1,103 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/core/installment.json", + "title": "Installment", + "description": "A single bookable unit within a collection — one episode, issue, event, or rotation period. The parent collection's kind indicates how to interpret each installment: TV/podcast episodes, print issues, live event airings, newsletter editions, or DOOH rotation periods. Installments inherit collection-level fields they don't override: content_rating defaults to the collection's baseline, guest_talent is additive to the collection's recurring talent, and topics add context beyond the collection's genre.", + "type": "object", + "properties": { + "installment_id": { + "type": "string", + "description": "Unique identifier for this installment within the collection" + }, + "collection_id": { + "type": "string", + "description": "Parent collection reference. Required when the product spans multiple collections. Maps to a collection_id declared in one of the publishers' adagents.json files referenced by the product's collection selectors." + }, + "name": { + "type": "string", + "description": "Installment title" + }, + "season": { + "type": "string", + "description": "Season identifier (e.g., '1', '2024', 'spring_2026')" + }, + "installment_number": { + "type": "string", + "description": "Installment number within the season (e.g., '3', '47')" + }, + "scheduled_at": { + "type": "string", + "format": "date-time", + "description": "When the installment airs or publishes (ISO 8601)" + }, + "status": { + "$ref": "/schemas/enums/installment-status.json", + "description": "Lifecycle status of the installment" + }, + "duration_seconds": { + "type": "integer", + "minimum": 0, + "description": "Expected duration of the installment in seconds" + }, + "flexible_end": { + "type": "boolean", + "description": "Whether the end time is approximate (live events, sports)" + }, + "valid_until": { + "type": "string", + "format": "date-time", + "description": "When this installment data expires and should be re-queried. Agents should re-query before committing budget to products with tentative installments." + }, + "content_rating": { + "$ref": "/schemas/core/content-rating.json", + "description": "Installment-specific content rating. Overrides the collection's baseline content_rating when present." + }, + "topics": { + "type": "array", + "description": "Content topics for this installment. Uses the same taxonomy as the collection's genre_taxonomy when present. Enables installment-level brand safety evaluation beyond content_rating.", + "items": { + "type": "string" + } + }, + "special": { + "$ref": "/schemas/core/special.json", + "description": "Installment-specific event context. When present, this installment is anchored to a real-world event. Overrides the collection-level special when present." + }, + "guest_talent": { + "type": "array", + "description": "Installment-specific guests and talent. Additive to the collection's recurring talent.", + "items": { + "$ref": "/schemas/core/talent.json" + } + }, + "ad_inventory": { + "$ref": "/schemas/core/ad-inventory-config.json", + "description": "Break-based ad inventory for this installment. For non-break formats (host reads, integrations), use product placements." + }, + "deadlines": { + "$ref": "/schemas/core/installment-deadlines.json", + "description": "Booking, cancellation, and material submission deadlines for this installment. Present when the installment has time-sensitive inventory that requires advance commitment or material delivery." + }, + "derivative_of": { + "type": "object", + "description": "When this installment is a clip, highlight, or recap derived from a full installment. The source installment_id must reference an installment within the same response.", + "properties": { + "installment_id": { + "type": "string", + "description": "The source installment this content is derived from" + }, + "type": { + "$ref": "/schemas/enums/derivative-type.json", + "description": "What kind of derivative content this is" + } + }, + "required": ["installment_id", "type"], + "additionalProperties": false + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "required": ["installment_id"], + "additionalProperties": true +} diff --git a/static/schemas/source/core/limited-series.json b/static/schemas/source/core/limited-series.json index 3ded6ef212..916dbf71fa 100644 --- a/static/schemas/source/core/limited-series.json +++ b/static/schemas/source/core/limited-series.json @@ -2,13 +2,13 @@ "$schema": "http://json-schema.org/draft-07/schema#", "$id": "/schemas/core/limited-series.json", "title": "Limited Series", - "description": "A bounded content run with a defined arc. Indicates the show has a planned beginning, end, and episode count — distinguishing it from ongoing series.", + "description": "A bounded content run with a defined arc. Indicates the collection has a planned beginning, end, and installment count — distinguishing it from ongoing series.", "type": "object", "properties": { - "total_episodes": { + "total_installments": { "type": "integer", "minimum": 1, - "description": "Planned number of episodes in the series" + "description": "Planned number of installments in the series" }, "starts": { "type": "string", @@ -21,6 +21,6 @@ "description": "When the series ends (ISO 8601)" } }, - "required": ["total_episodes"], + "required": ["total_installments"], "additionalProperties": true } diff --git a/static/schemas/source/core/material-deadline.json b/static/schemas/source/core/material-deadline.json new file mode 100644 index 0000000000..7abfe5724f --- /dev/null +++ b/static/schemas/source/core/material-deadline.json @@ -0,0 +1,25 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/core/material-deadline.json", + "title": "Material Deadline", + "description": "A deadline for creative material submission. Sellers declare stages to distinguish draft materials (e.g., talking points, raw artwork) from production-ready assets (e.g., approved scripts, press-ready PDFs).", + "type": "object", + "properties": { + "stage": { + "type": "string", + "description": "Submission stage identifier. Use 'draft' for materials that need seller processing and 'final' for production-ready assets. Sellers may define additional stages.", + "examples": ["draft", "final"] + }, + "due_at": { + "type": "string", + "format": "date-time", + "description": "When materials for this stage are due (ISO 8601)" + }, + "label": { + "type": "string", + "description": "What the seller needs at this stage (e.g., 'Talking points and brand guidelines', 'Press-ready PDF with bleed')" + } + }, + "required": ["stage", "due_at"], + "additionalProperties": true +} diff --git a/static/schemas/source/core/overlay.json b/static/schemas/source/core/overlay.json index ddd1e5a640..b510bede81 100644 --- a/static/schemas/source/core/overlay.json +++ b/static/schemas/source/core/overlay.json @@ -60,8 +60,8 @@ }, "unit": { "type": "string", - "enum": ["px", "fraction"], - "description": "'px' = absolute pixels from asset top-left. 'fraction' = proportional to asset dimensions (x/y: 0.0 = asset edge, 1.0 = opposite edge; width/height: 0.12 = 12% of asset dimension)." + "enum": ["px", "fraction", "inches", "cm", "mm", "pt"], + "description": "'px' = absolute pixels from asset top-left. 'fraction' = proportional to asset dimensions (0.0 = edge, 1.0 = opposite edge). 'inches', 'cm', 'mm', 'pt' (1/72 inch) = physical units for print overlays, measured from asset top-left." } }, "required": ["x", "y", "width", "height", "unit"], diff --git a/static/schemas/source/core/product.json b/static/schemas/source/core/product.json index aa14a04422..ea930c75f5 100644 --- a/static/schemas/source/core/product.json +++ b/static/schemas/source/core/product.json @@ -53,7 +53,7 @@ }, "exclusivity": { "$ref": "/schemas/enums/exclusivity.json", - "description": "Whether this product offers exclusive access to its inventory. Defaults to 'none' when absent. Most relevant for guaranteed products tied to specific shows or placements." + "description": "Whether this product offers exclusive access to its inventory. Defaults to 'none' when absent. Most relevant for guaranteed products tied to specific collections or placements." }, "pricing_options": { "type": "array", @@ -282,24 +282,24 @@ ], "additionalProperties": true }, - "shows": { + "collections": { "type": "array", - "description": "Shows available in this product. Each entry references shows declared in an adagents.json by domain and show ID. Buyers resolve full show objects from the referenced adagents.json.", + "description": "Collections available in this product. Each entry references collections declared in an adagents.json by domain and collection ID. Buyers resolve full collection objects from the referenced adagents.json.", "items": { - "$ref": "/schemas/core/show-selector.json" + "$ref": "/schemas/core/collection-selector.json" }, "minItems": 1 }, - "show_targeting_allowed": { + "collection_targeting_allowed": { "type": "boolean", "default": false, - "description": "Whether buyers can target a subset of this product's shows. When false (default), the product is a bundle — buyers get all listed shows. When true, buyers can select specific shows in the media buy." + "description": "Whether buyers can target a subset of this product's collections. When false (default), the product is a bundle — buyers get all listed collections. When true, buyers can select specific collections in the media buy." }, - "episodes": { + "installments": { "type": "array", - "description": "Specific episodes included in this product. Each episode references its parent show via show_id when the product spans multiple shows. When absent with shows present, the product covers the shows broadly (run-of-show).", + "description": "Specific installments included in this product. Each installment references its parent collection via collection_id when the product spans multiple collections. When absent with collections present, the product covers the collections broadly (run-of-collection).", "items": { - "$ref": "/schemas/core/episode.json" + "$ref": "/schemas/core/installment.json" } }, "enforced_policies": { diff --git a/static/schemas/source/core/requirements/image-asset-requirements.json b/static/schemas/source/core/requirements/image-asset-requirements.json index 887d17e98e..6bc5b6f924 100644 --- a/static/schemas/source/core/requirements/image-asset-requirements.json +++ b/static/schemas/source/core/requirements/image-asset-requirements.json @@ -6,24 +6,28 @@ "type": "object", "properties": { "min_width": { - "type": "integer", - "minimum": 1, - "description": "Minimum width in pixels. For exact dimensions, set min_width = max_width." + "type": "number", + "exclusiveMinimum": 0, + "description": "Minimum width. Interpretation depends on unit (default: pixels). For exact dimensions, set min_width = max_width." }, "max_width": { - "type": "integer", - "minimum": 1, - "description": "Maximum width in pixels. For exact dimensions, set min_width = max_width." + "type": "number", + "exclusiveMinimum": 0, + "description": "Maximum width. Interpretation depends on unit (default: pixels). For exact dimensions, set min_width = max_width." }, "min_height": { - "type": "integer", - "minimum": 1, - "description": "Minimum height in pixels. For exact dimensions, set min_height = max_height." + "type": "number", + "exclusiveMinimum": 0, + "description": "Minimum height. Interpretation depends on unit (default: pixels). For exact dimensions, set min_height = max_height." }, "max_height": { - "type": "integer", - "minimum": 1, - "description": "Maximum height in pixels. For exact dimensions, set min_height = max_height." + "type": "number", + "exclusiveMinimum": 0, + "description": "Maximum height. Interpretation depends on unit (default: pixels). For exact dimensions, set min_height = max_height." + }, + "unit": { + "$ref": "/schemas/enums/dimension-unit.json", + "description": "Unit of measurement for width/height values. Defaults to 'px' when absent. Print formats use 'inches' or 'cm'." }, "aspect_ratio": { "type": "string", @@ -34,10 +38,47 @@ "type": "array", "items": { "type": "string", - "enum": ["jpg", "jpeg", "png", "gif", "webp", "svg", "avif"] + "enum": ["jpg", "jpeg", "png", "gif", "webp", "svg", "avif", "tiff", "pdf", "eps"] }, "description": "Accepted image file formats" }, + "min_dpi": { + "type": "integer", + "minimum": 1, + "description": "Minimum resolution in dots per inch. Always in DPI regardless of the dimension unit. Standard print requires 300 DPI, newspaper 150 DPI." + }, + "bleed": { + "type": "object", + "description": "Required bleed area beyond the trim size. The submitted image must be larger than the declared dimensions: total width = trim width + left bleed + right bleed, total height = trim height + top bleed + bottom bleed. For uniform bleed: total = trim + (2 * uniform). Uses the same unit as the parent dimensions.", + "oneOf": [ + { + "properties": { + "uniform": { + "type": "number", + "minimum": 0, + "description": "Same bleed on all four sides" + } + }, + "required": ["uniform"], + "additionalProperties": false + }, + { + "properties": { + "top": { "type": "number", "minimum": 0 }, + "right": { "type": "number", "minimum": 0 }, + "bottom": { "type": "number", "minimum": 0 }, + "left": { "type": "number", "minimum": 0 } + }, + "required": ["top", "right", "bottom", "left"], + "additionalProperties": false + } + ] + }, + "color_space": { + "type": "string", + "enum": ["rgb", "cmyk", "grayscale"], + "description": "Required color space. Print typically requires CMYK." + }, "max_file_size_kb": { "type": "integer", "minimum": 1, @@ -57,5 +98,7 @@ "description": "Maximum animation duration in milliseconds (if animation_allowed is true)" } }, + "if": { "required": ["min_dpi"] }, + "then": { "required": ["unit"] }, "additionalProperties": true } diff --git a/static/schemas/source/core/show-selector.json b/static/schemas/source/core/show-selector.json deleted file mode 100644 index 51d1c3687d..0000000000 --- a/static/schemas/source/core/show-selector.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "/schemas/core/show-selector.json", - "title": "Show Selector", - "description": "References shows declared in an adagents.json. Buyers resolve full show objects by fetching the adagents.json at the given domain and matching show_ids against its shows array.", - "type": "object", - "properties": { - "publisher_domain": { - "type": "string", - "description": "Domain where the adagents.json declaring these shows is hosted (e.g., 'mrbeast.com'). The shows array in that file contains the authoritative show definitions.", - "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$" - }, - "show_ids": { - "type": "array", - "description": "Show IDs from the adagents.json shows array. Each ID must match a show_id declared in that file.", - "items": { - "type": "string" - }, - "minItems": 1 - } - }, - "required": [ - "publisher_domain", - "show_ids" - ], - "additionalProperties": true -} diff --git a/static/schemas/source/core/show.json b/static/schemas/source/core/show.json deleted file mode 100644 index 573799a5d8..0000000000 --- a/static/schemas/source/core/show.json +++ /dev/null @@ -1,102 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "/schemas/core/show.json", - "title": "Show", - "description": "A persistent content program that produces episodes over time. Declared in the publisher's adagents.json and referenced by products via shows selectors. Use distribution identifiers for cross-seller matching.", - "type": "object", - "properties": { - "show_id": { - "type": "string", - "description": "Publisher-assigned identifier for this show. Declared in the publisher's adagents.json shows array. Products reference shows via shows selectors with publisher_domain and show_ids. Use distribution identifiers for cross-seller matching across publishers." - }, - "name": { - "type": "string", - "description": "Human-readable show name" - }, - "description": { - "type": "string", - "description": "What the show is about" - }, - "genre": { - "type": "array", - "description": "Genre tags. When genre_taxonomy is present, values are taxonomy IDs (e.g., IAB Content Taxonomy 3.0 codes). Otherwise free-form.", - "items": { - "type": "string" - } - }, - "genre_taxonomy": { - "type": "string", - "description": "Taxonomy system for genre values (e.g., 'iab_content_3.0'). When present, genre values should be valid taxonomy IDs. Recommended for machine-readable brand safety evaluation." - }, - "language": { - "type": "string", - "description": "Primary language (BCP 47 tag, e.g., 'en', 'es-MX')" - }, - "content_rating": { - "$ref": "/schemas/core/content-rating.json", - "description": "Baseline content rating for the show. Individual episodes may override this." - }, - "cadence": { - "$ref": "/schemas/enums/show-cadence.json", - "description": "How frequently the show releases new episodes" - }, - "season": { - "type": "string", - "description": "Current or most recent season identifier (e.g., '3', '2026', 'spring_2026'). A lightweight label — not a full season object." - }, - "status": { - "$ref": "/schemas/enums/show-status.json", - "description": "Lifecycle status of the show" - }, - "production_quality": { - "$ref": "/schemas/enums/production-quality.json", - "description": "Production quality tier. Seller-declared. Maps to OpenRTB content.prodq (professional=1, prosumer=2, ugc=3)." - }, - "talent": { - "type": "array", - "description": "Hosts, recurring cast, creators associated with the show. Each talent entry may include a brand_url linking to their brand.json identity.", - "items": { - "$ref": "/schemas/core/talent.json" - } - }, - "special": { - "$ref": "/schemas/core/special.json", - "description": "When present, this show is a special — content anchored to a real-world event or occasion. Individual episodes may override with their own event context." - }, - "limited_series": { - "$ref": "/schemas/core/limited-series.json", - "description": "When present, this show is a limited series — a bounded run with a defined arc, episode count, and end date." - }, - "distribution": { - "type": "array", - "description": "Where this show is distributed. Each entry maps the show to a publisher platform with platform-specific identifiers. Shows SHOULD include at least one platform-independent identifier (imdb_id, gracenote_id, eidr_id) when available.", - "items": { - "$ref": "/schemas/core/show-distribution.json" - } - }, - "related_shows": { - "type": "array", - "description": "Relationships to other shows (spin-offs, companion shows, etc.). Each entry references another show by show_id within the same publisher's adagents.json.", - "items": { - "type": "object", - "properties": { - "show_id": { - "type": "string", - "description": "The related show's show_id within this seller's response" - }, - "relationship": { - "$ref": "/schemas/enums/show-relationship.json", - "description": "How the shows are related" - } - }, - "required": ["show_id", "relationship"], - "additionalProperties": false - } - }, - "ext": { - "$ref": "/schemas/core/ext.json" - } - }, - "required": ["show_id", "name"], - "additionalProperties": true -} diff --git a/static/schemas/source/core/special.json b/static/schemas/source/core/special.json index 4081dbcf9d..513a7e727b 100644 --- a/static/schemas/source/core/special.json +++ b/static/schemas/source/core/special.json @@ -2,7 +2,7 @@ "$schema": "http://json-schema.org/draft-07/schema#", "$id": "/schemas/core/special.json", "title": "Special", - "description": "Event-anchored content tied to a real-world event or occasion. When present on a show, indicates the show is a special. When present on an episode, indicates that specific episode is event-anchored (overrides or supplements the show-level special).", + "description": "Event-anchored content tied to a real-world event or occasion. When present on a collection, indicates the collection is a special. When present on an installment, indicates that specific installment is event-anchored (overrides or supplements the collection-level special).", "type": "object", "properties": { "name": { diff --git a/static/schemas/source/core/talent.json b/static/schemas/source/core/talent.json index ae378a3646..ba2f3222f7 100644 --- a/static/schemas/source/core/talent.json +++ b/static/schemas/source/core/talent.json @@ -2,16 +2,16 @@ "$schema": "http://json-schema.org/draft-07/schema#", "$id": "/schemas/core/talent.json", "title": "Talent", - "description": "A person associated with a show or episode, with an optional link to their brand.json identity", + "description": "A person associated with a collection or installment, with an optional link to their brand.json identity", "type": "object", "properties": { "role": { "$ref": "/schemas/enums/talent-role.json", - "description": "Role of this person on the show or episode" + "description": "Role of this person on the collection or installment" }, "name": { "type": "string", - "description": "Person's name as credited on the show" + "description": "Person's name as credited on the collection" }, "brand_url": { "type": "string", diff --git a/static/schemas/source/enums/collection-cadence.json b/static/schemas/source/enums/collection-cadence.json new file mode 100644 index 0000000000..e76b6f6027 --- /dev/null +++ b/static/schemas/source/enums/collection-cadence.json @@ -0,0 +1,23 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/enums/collection-cadence.json", + "title": "Collection Cadence", + "description": "How frequently a collection releases new installments", + "type": "string", + "enum": [ + "daily", + "weekly", + "monthly", + "seasonal", + "event", + "irregular" + ], + "enumDescriptions": { + "daily": "New installments every day or weekday", + "weekly": "New installments weekly", + "monthly": "New installments monthly (magazines, newsletters)", + "seasonal": "Installments released in defined seasons with breaks between", + "event": "One-time or occasional special events", + "irregular": "No predictable schedule" + } +} diff --git a/static/schemas/source/enums/collection-relationship.json b/static/schemas/source/enums/collection-relationship.json new file mode 100644 index 0000000000..86ebbfad3b --- /dev/null +++ b/static/schemas/source/enums/collection-relationship.json @@ -0,0 +1,21 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/enums/collection-relationship.json", + "title": "Collection Relationship", + "description": "How two collections are related. References are scoped to the same get_products response. Symmetric types (companion, crossover) do not require both collections to declare the relationship.", + "type": "string", + "enum": [ + "spinoff", + "companion", + "sequel", + "prequel", + "crossover" + ], + "enumDescriptions": { + "spinoff": "A collection derived from the referenced collection (e.g., an after-collection or character spinoff)", + "companion": "A companion collection (behind-the-scenes, reaction, or supplementary content)", + "sequel": "A continuation of the referenced collection", + "prequel": "A predecessor to the referenced collection", + "crossover": "Shows that share installments, guests, or story arcs" + } +} diff --git a/static/schemas/source/enums/show-status.json b/static/schemas/source/enums/collection-status.json similarity index 53% rename from static/schemas/source/enums/show-status.json rename to static/schemas/source/enums/collection-status.json index a36690974b..7f9b7d267c 100644 --- a/static/schemas/source/enums/show-status.json +++ b/static/schemas/source/enums/collection-status.json @@ -1,8 +1,8 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "/schemas/enums/show-status.json", - "title": "Show Status", - "description": "Lifecycle status of a show", + "$id": "/schemas/enums/collection-status.json", + "title": "Collection Status", + "description": "Lifecycle status of a collection", "type": "string", "enum": [ "active", @@ -11,9 +11,9 @@ "upcoming" ], "enumDescriptions": { - "active": "Currently producing and releasing episodes", + "active": "Currently producing and releasing installments", "hiatus": "Temporarily paused, expected to resume", - "ended": "No longer producing new episodes", + "ended": "No longer producing new installments", "upcoming": "Announced but not yet released" } } diff --git a/static/schemas/source/enums/derivative-type.json b/static/schemas/source/enums/derivative-type.json index 40b3f81785..08c09c77b7 100644 --- a/static/schemas/source/enums/derivative-type.json +++ b/static/schemas/source/enums/derivative-type.json @@ -2,7 +2,7 @@ "$schema": "http://json-schema.org/draft-07/schema#", "$id": "/schemas/enums/derivative-type.json", "title": "Derivative Type", - "description": "What kind of derivative content an episode represents relative to its source episode. The source episode_id must reference an episode within the same response.", + "description": "What kind of derivative content an installment represents relative to its source installment. The source installment_id must reference an installment within the same response.", "type": "string", "enum": [ "clip", @@ -12,10 +12,10 @@ "bonus" ], "enumDescriptions": { - "clip": "Short excerpt from the source episode", - "highlight": "Editorially curated best-of segment from the source episode", - "recap": "Summary of the source episode's content", - "trailer": "Promotional content for the source episode", + "clip": "Short excerpt from the source installment", + "highlight": "Editorially curated best-of segment from the source installment", + "recap": "Summary of the source installment's content", + "trailer": "Promotional content for the source installment", "bonus": "Supplementary content (behind-the-scenes, extended interviews, deleted segments)" } } diff --git a/static/schemas/source/enums/dimension-unit.json b/static/schemas/source/enums/dimension-unit.json index 8c69d8afa2..4cfbecd789 100644 --- a/static/schemas/source/enums/dimension-unit.json +++ b/static/schemas/source/enums/dimension-unit.json @@ -8,6 +8,8 @@ "px", "dp", "inches", - "cm" + "cm", + "mm", + "pt" ] } diff --git a/static/schemas/source/enums/distribution-identifier-type.json b/static/schemas/source/enums/distribution-identifier-type.json index f5ac2dcbb8..0315e82085 100644 --- a/static/schemas/source/enums/distribution-identifier-type.json +++ b/static/schemas/source/enums/distribution-identifier-type.json @@ -2,11 +2,11 @@ "$schema": "http://json-schema.org/draft-07/schema#", "$id": "/schemas/enums/distribution-identifier-type.json", "title": "Distribution Identifier Type", - "description": "Platform-specific identifier types for show distribution. Maps a show to its presence on specific platforms, enabling cross-seller matching.", + "description": "Platform-specific identifier types for collection distribution. Maps a collection to its presence on specific platforms, enabling cross-seller matching.", "type": "string", "enum": [ "apple_podcast_id", - "spotify_show_id", + "spotify_collection_id", "rss_url", "podcast_guid", "amazon_music_id", @@ -28,12 +28,12 @@ "substack_id" ], "enumDescriptions": { - "apple_podcast_id": "Apple Podcasts show ID", - "spotify_show_id": "Spotify show URI", + "apple_podcast_id": "Apple Podcasts collection ID", + "spotify_collection_id": "Spotify collection URI", "rss_url": "Canonical RSS feed URL", "podcast_guid": "Podcasting 2.0 GUID", "amazon_music_id": "Amazon Music / Audible ID", - "iheart_id": "iHeartRadio show ID", + "iheart_id": "iHeartRadio collection ID", "podcast_index_id": "PodcastIndex.org ID", "youtube_channel_id": "YouTube channel ID", "youtube_playlist_id": "YouTube playlist ID (for series within a channel)", diff --git a/static/schemas/source/enums/exclusivity.json b/static/schemas/source/enums/exclusivity.json index 8027b45edb..500f58a832 100644 --- a/static/schemas/source/enums/exclusivity.json +++ b/static/schemas/source/enums/exclusivity.json @@ -11,7 +11,7 @@ ], "enumDescriptions": { "none": "No exclusivity — multiple advertisers can buy this product simultaneously", - "category": "Category-exclusive — only one advertiser per industry category (e.g., one auto brand per show sponsorship)", + "category": "Category-exclusive — only one advertiser per industry category (e.g., one auto brand per collection sponsorship)", "exclusive": "Sole sponsorship — only one advertiser can buy this product" } } diff --git a/static/schemas/source/enums/identifier-types.json b/static/schemas/source/enums/identifier-types.json index 80aa7ab066..42df88f781 100644 --- a/static/schemas/source/enums/identifier-types.json +++ b/static/schemas/source/enums/identifier-types.json @@ -22,7 +22,7 @@ "openooh_venue_type", "rss_url", "apple_podcast_id", - "spotify_show_id", + "spotify_collection_id", "podcast_guid" ], "examples": [ diff --git a/static/schemas/source/enums/episode-status.json b/static/schemas/source/enums/installment-status.json similarity index 81% rename from static/schemas/source/enums/episode-status.json rename to static/schemas/source/enums/installment-status.json index fa40af1bc7..87bc4bd987 100644 --- a/static/schemas/source/enums/episode-status.json +++ b/static/schemas/source/enums/installment-status.json @@ -1,8 +1,8 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "/schemas/enums/episode-status.json", - "title": "Episode Status", - "description": "Lifecycle status of an episode", + "$id": "/schemas/enums/installment-status.json", + "title": "Installment Status", + "description": "Lifecycle status of an installment", "type": "string", "enum": [ "scheduled", diff --git a/static/schemas/source/enums/production-quality.json b/static/schemas/source/enums/production-quality.json index 850aa9fea1..4471ba6724 100644 --- a/static/schemas/source/enums/production-quality.json +++ b/static/schemas/source/enums/production-quality.json @@ -2,7 +2,7 @@ "$schema": "http://json-schema.org/draft-07/schema#", "$id": "/schemas/enums/production-quality.json", "title": "Production Quality", - "description": "Production quality tier for show content. Maps to OpenRTB content.prodq: professional=1, prosumer=2, ugc=3. Seller-declared — no external validation.", + "description": "Production quality tier for collection content. Maps to OpenRTB content.prodq: professional=1, prosumer=2, ugc=3. Seller-declared — no external validation.", "type": "string", "enum": [ "professional", diff --git a/static/schemas/source/enums/show-cadence.json b/static/schemas/source/enums/show-cadence.json deleted file mode 100644 index 0245a47f2e..0000000000 --- a/static/schemas/source/enums/show-cadence.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "/schemas/enums/show-cadence.json", - "title": "Show Cadence", - "description": "How frequently a show releases new episodes", - "type": "string", - "enum": [ - "daily", - "weekly", - "seasonal", - "event", - "irregular" - ], - "enumDescriptions": { - "daily": "New episodes every day or weekday", - "weekly": "New episodes weekly", - "seasonal": "Episodes released in defined seasons with breaks between", - "event": "One-time or occasional special events", - "irregular": "No predictable schedule" - } -} diff --git a/static/schemas/source/enums/show-relationship.json b/static/schemas/source/enums/show-relationship.json deleted file mode 100644 index e2fc520518..0000000000 --- a/static/schemas/source/enums/show-relationship.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "/schemas/enums/show-relationship.json", - "title": "Show Relationship", - "description": "How two shows are related. References are scoped to the same get_products response. Symmetric types (companion, crossover) do not require both shows to declare the relationship.", - "type": "string", - "enum": [ - "spinoff", - "companion", - "sequel", - "prequel", - "crossover" - ], - "enumDescriptions": { - "spinoff": "A show derived from the referenced show (e.g., an after-show or character spinoff)", - "companion": "A companion show (behind-the-scenes, reaction, or supplementary content)", - "sequel": "A continuation of the referenced show", - "prequel": "A predecessor to the referenced show", - "crossover": "Shows that share episodes, guests, or story arcs" - } -} diff --git a/static/schemas/source/enums/talent-role.json b/static/schemas/source/enums/talent-role.json index 7f8f29b067..0ff95f3218 100644 --- a/static/schemas/source/enums/talent-role.json +++ b/static/schemas/source/enums/talent-role.json @@ -2,7 +2,7 @@ "$schema": "http://json-schema.org/draft-07/schema#", "$id": "/schemas/enums/talent-role.json", "title": "Talent Role", - "description": "Role of a person associated with a show or episode", + "description": "Role of a person associated with a collection or installment", "type": "string", "enum": [ "host", @@ -16,9 +16,9 @@ "analyst" ], "enumDescriptions": { - "host": "Primary host or anchor of the show", - "guest": "Guest appearing on a specific episode", - "creator": "Show creator or showrunner", + "host": "Primary host or anchor of the collection", + "guest": "Guest appearing on a specific installment", + "creator": "Collection creator or showrunner", "cast": "Regular or recurring cast member", "narrator": "Narrator or voiceover", "producer": "Executive or lead producer with on-screen presence", diff --git a/static/schemas/source/enums/universal-macro.json b/static/schemas/source/enums/universal-macro.json index 68f8804aa2..7cb256929b 100644 --- a/static/schemas/source/enums/universal-macro.json +++ b/static/schemas/source/enums/universal-macro.json @@ -55,8 +55,8 @@ "POD_SIZE", "AD_BREAK_ID", "STATION_ID", - "SHOW_NAME", - "EPISODE_ID", + "COLLECTION_NAME", + "INSTALLMENT_ID", "AUDIO_DURATION", "AXEM", "CATALOG_ID", @@ -125,8 +125,8 @@ "POD_SIZE": "Total ads in the ad break", "AD_BREAK_ID": "Unique ad break identifier", "STATION_ID": "Radio station or podcast identifier", - "SHOW_NAME": "Program or show name", - "EPISODE_ID": "Podcast episode identifier", + "COLLECTION_NAME": "Program or collection name", + "INSTALLMENT_ID": "Installment identifier (podcast episode, print issue, etc.)", "AUDIO_DURATION": "Audio content duration in seconds", "AXEM": "AXE contextual metadata (encoded blob)", "CATALOG_ID": "Buyer-defined catalog identifier from the catalog's catalog_id field", diff --git a/static/schemas/source/index.json b/static/schemas/source/index.json index 58353f41c9..656b6e0e3d 100644 --- a/static/schemas/source/index.json +++ b/static/schemas/source/index.json @@ -74,6 +74,18 @@ "$ref": "/schemas/core/creative-policy.json", "description": "Creative requirements and restrictions for a product" }, + "deadline-policy": { + "$ref": "/schemas/core/deadline-policy.json", + "description": "Default deadline rules for installments based on lead times from scheduled_at" + }, + "installment-deadlines": { + "$ref": "/schemas/core/installment-deadlines.json", + "description": "Booking, cancellation, and material submission deadlines for an installment" + }, + "material-deadline": { + "$ref": "/schemas/core/material-deadline.json", + "description": "A deadline for creative material submission at a specific stage" + }, "response": { "$ref": "/schemas/core/response.json", "description": "Standard response structure (MCP)" diff --git a/static/schemas/source/media-buy/get-products-request.json b/static/schemas/source/media-buy/get-products-request.json index b8f0022fa2..6e53a089a9 100644 --- a/static/schemas/source/media-buy/get-products-request.json +++ b/static/schemas/source/media-buy/get-products-request.json @@ -170,9 +170,9 @@ "data_provider_signals", "max_optimization_goals", "catalog_match", - "shows", - "show_targeting_allowed", - "episodes", + "collections", + "collection_targeting_allowed", + "installments", "brief_relevance", "expires_at", "product_card",