> ## Documentation Index
> Fetch the complete documentation index at: https://docs.adcontextprotocol.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Media Products

> AdCP media products — the core sellable unit in the protocol. Covers product structure, pricing options, delivery types, format references, and catalog-driven inventory.

A **Product** is the core sellable unit in AdCP. This document details the Product model, including its pricing and delivery types, and how products are discovered and structured in the system.

<Tip>
  **Pricing Models**
  Products declare which pricing models they support. Buyers select a specific pricing option when creating media buys. See the complete [Pricing Models Guide](/dist/docs/3.0.0-rc.2/media-buy/advanced-topics/pricing-models) for details on CPM, CPCV, CPP, CPC, CPA, vCPM, flat rate, and time-based pricing.
</Tip>

## The Product Model

* `product_id` (string, required)
* `name` (string, required)
* `description` (string, required)
* `publisher_properties` (list\[PublisherPropertySelector], required): Publisher properties covered by this product. See [Property Targeting](#property-targeting).
* `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](/dist/docs/3.0.0-rc.2/reference/media-channel-taxonomy).
* `format_ids` (list\[FormatID], required): Structured format ID references. See [Creative Formats](/dist/docs/3.0.0-rc.2/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).
* `show_ids` (list\[string], optional): Show IDs available within this product. When provided, buyers can target specific shows. See [Shows and episodes](/dist/docs/3.0.0-rc.2/media-buy/product-discovery/shows-and-episodes).
* `episodes` (list\[Episode], optional): Specific episodes available within this product. See [Shows and episodes](/dist/docs/3.0.0-rc.2/media-buy/product-discovery/shows-and-episodes).
* `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).
* `delivery_measurement` (object, optional): Who measures ad delivery — the ad server and viewability vendor used to count impressions (e.g., "Google Ad Manager with IAS viewability"). When absent, buyers should apply their own measurement defaults. See [Delivery Measurement](#delivery-measurement).
* `outcome_measurement` (OutcomeMeasurement, optional): Business outcome measurement included with the product — incremental sales lift, brand lift studies, etc. Common for retail media products.
* `creative_policy` (CreativePolicy, optional): Creative requirements and restrictions.
* `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).
* `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](/dist/docs/3.0.0-rc.2/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).
* `max_optimization_goals` (integer, optional): Maximum number of `optimization_goals` this product accepts on a package. When absent, no limit is declared. Most social platforms accept only 1.
* `conversion_tracking` (object, optional): Conversion event tracking capabilities. Presence indicates the product supports `optimization_goals` with `kind: "event"`. See [Conversion tracking](#conversion-tracking-1).
* `product_card` (object, optional): Visual card definition for displaying this product in user interfaces. See [Product Cards](#product-cards).

### Metric optimization

Products that support `optimization_goals` with `kind: "metric"` declare their capabilities in `metric_optimization`. No event source or conversion tracking setup is required for metric goals — the seller tracks these metrics natively.

```json theme={null}
{
  "metric_optimization": {
    "supported_metrics": ["clicks", "views", "completed_views", "engagements"],
    "supported_view_durations": [2, 6, 15],
    "supported_targets": ["cost_per", "threshold_rate"]
  }
}
```

| Field                      | Type      | Required | Description                                                                                                                                                                                                                                         |
| -------------------------- | --------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `supported_metrics`        | string\[] | Yes      | Metric kinds this product can optimize for. Buyers should only request metric goals for kinds listed here.                                                                                                                                          |
| `supported_view_durations` | number\[] | No       | Video view duration thresholds (in seconds) supported for `completed_views` goals. When absent, the seller uses their platform default.                                                                                                             |
| `supported_targets`        | string\[] | No       | Target kinds available: `cost_per`, `threshold_rate`. Values match `target.kind` on the optimization goal. Only listed kinds are accepted. When omitted, buyers can set target-less metric goals (maximize volume) but cannot set specific targets. |

### Conversion tracking

Products that support `optimization_goals` with `kind: "event"` declare their capabilities in `conversion_tracking`. Seller-level capabilities (supported event types, UID types, attribution windows) are declared in [`get_adcp_capabilities`](/dist/docs/3.0.0-rc.2/protocol/get_adcp_capabilities).

```json theme={null}
{
  "conversion_tracking": {
    "action_sources": ["website", "app"],
    "supported_targets": ["cost_per", "per_ad_spend", "maximize_value"],
    "platform_managed": false
  }
}
```

| Field               | Type      | Required | Description                                                                                                                                                                                                                            |
| ------------------- | --------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `action_sources`    | string\[] | No       | Action sources relevant to this product (e.g., a retail media product might have `in_store` and `website`).                                                                                                                            |
| `supported_targets` | string\[] | No       | Target kinds available for event goals: `cost_per`, `per_ad_spend`, `maximize_value`. Values match `target.kind` on the optimization goal. Only listed kinds are accepted. When omitted, buyers can still set target-less event goals. |
| `platform_managed`  | boolean   | No       | Whether the seller provides always-on measurement (e.g., retailer purchase attribution). When true, `sync_event_sources` returns seller-managed event sources.                                                                         |

See [Conversion Tracking & Optimization Goals](/dist/docs/3.0.0-rc.2/media-buy/conversion-tracking) for the full optimization goals reference.

### Pricing Models

Publishers declare which pricing models they support for each product. Buyers select from the available options when creating a media buy. This approach supports:

* **Multiple pricing models per product** - Publishers can offer the same inventory via different pricing structures
* **Multi-currency support** - Publishers declare supported currencies; buyers must use a supported currency
* **Flexible pricing** - Support for CPM, CPCV, CPP (GRP-based), CPA, and more

#### Supported Pricing Models

* **CPM** (Cost Per Mille) - Cost per 1,000 impressions (traditional display)
* **CPC** (Cost Per Click) - Cost per click on the ad
* **CPCV** (Cost Per Completed View) - Cost per 100% video/audio completion
* **CPV** (Cost Per View) - Cost per view at publisher-defined threshold
* **CPA** (Cost Per Acquisition) - Cost per conversion event (purchase, lead, signup, etc.)
* **CPP** (Cost Per Point) - Cost per Gross Rating Point (TV/audio)
* **Flat Rate** - Fixed cost regardless of delivery volume
* **Time** - Cost per time unit (day, week, month) that scales with campaign duration

#### PricingOption Structure

Each pricing option includes:

```json theme={null}
{
  "$schema": "https://adcontextprotocol.org/schemas/v3/pricing-options/cpcv-option.json",
  "pricing_option_id": "cpcv_usd_guaranteed",
  "pricing_model": "cpcv",
  "fixed_price": 0.15,
  "currency": "USD",
  "min_spend_per_package": 5000
}
```

For auction-based pricing (no `fixed_price`), use `floor_price` for minimum bid constraints and optional `price_guidance` for percentile hints. Bid-based auction models (`cpm`, `vcpm`, `cpc`, `cpcv`, `cpv`) may also include `max_bid` as a boolean signal that `bid_price` switches from exact honored price to buyer ceiling mode:

```json theme={null}
{
  "$schema": "https://adcontextprotocol.org/schemas/v3/pricing-options/cpm-option.json",
  "pricing_option_id": "cpm_usd_auction",
  "pricing_model": "cpm",
  "currency": "USD",
  "floor_price": 10.00,
  "max_bid": true,
  "price_guidance": {
    "p25": 12.50,
    "p50": 15.00,
    "p75": 18.00,
    "p90": 22.00
  }
}
```

#### Delivery Measurement

Products SHOULD declare their measurement provider when available:

```json theme={null}
{
  "delivery_measurement": {
    "provider": "Google Ad Manager with IAS viewability verification",
    "notes": "MRC-accredited viewability. 50% in-view for 1s display / 2s video."
  }
}
```

Common provider examples:

* `"Google Ad Manager with IAS viewability"`
* `"Nielsen DAR for P18-49 demographic measurement"`
* `"Geopath DOOH traffic counts updated monthly"`
* `"Comscore vCE for video completion tracking"`
* `"Self-reported impressions from proprietary ad server"`

### Outcome Measurement Object

For products that include outcome measurement (common in retail media):

```json theme={null}
{
  "type": "incremental_sales_lift",
  "attribution": "deterministic_purchase",
  "window": { "interval": 30, "unit": "days" },
  "reporting": "weekly_dashboard"
}
```

### CreativePolicy Object

Defines creative requirements and restrictions:

```json theme={null}
{
  "$schema": "https://adcontextprotocol.org/schemas/v2/core/creative-policy.json",
  "co_branding": "required",
  "landing_page": "retailer_site_only",
  "templates_available": true
}
```

### Placements

Products can optionally declare specific ad placements within their inventory. When placements are provided:

* **Buyers purchase the entire product** - Packages always target the whole product, not individual placements
* **Placement targeting happens at creative assignment** - Different creatives can be assigned to different placements
* **Omitting placement targeting** - Creatives without placement\_ids run on all placements in the package

#### Placement Object Structure

```json theme={null}
{
  "$schema": "https://adcontextprotocol.org/schemas/v2/core/placement.json",
  "placement_id": "homepage_banner",
  "name": "Homepage Banner",
  "description": "Above-the-fold banner on the homepage",
  "format_ids": [
    {"agent_url": "https://creative.adcontextprotocol.org", "id": "display_728x90"},
    {"agent_url": "https://creative.adcontextprotocol.org", "id": "display_970x250"}
  ]
}
```

#### Example: Product with Placements

```json theme={null}
{
  "product_id": "news_site_premium",
  "name": "News Site Premium Package",
  "description": "Premium placements across news site",
  "format_ids": [
    {"agent_url": "https://creative.adcontextprotocol.org", "id": "display_728x90"},
    {"agent_url": "https://creative.adcontextprotocol.org", "id": "display_300x250"}
  ],
  "placements": [
    {
      "placement_id": "homepage_banner",
      "name": "Homepage Banner",
      "format_ids": [{"agent_url": "https://creative.adcontextprotocol.org", "id": "display_728x90"}]
    },
    {
      "placement_id": "article_sidebar",
      "name": "Article Sidebar",
      "format_ids": [{"agent_url": "https://creative.adcontextprotocol.org", "id": "display_300x250"}]
    }
  ],
  "delivery_type": "guaranteed",
  "pricing_options": [...]
}
```

When creating a media buy, buyers can assign different creatives to different placements:

```json theme={null}
{
  "packages": [
    {
      "product_id": "news_site_premium",
      "creative_assignments": [
        {
          "creative_id": "creative_1",
          "placement_ids": ["homepage_banner"]
        },
        {
          "creative_id": "creative_2",
          "placement_ids": ["article_sidebar"]
        }
      ]
    }
  ]
}
```

See [Creative Assignment and Placement Targeting](/dist/docs/3.0.0-rc.2/media-buy/media-buys/index.mdx#creative-assignment-and-placement-targeting) for more details.

### Shows and episodes

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 `show_ids` and `episodes` so buyers can target specific shows or episodes when purchasing inventory.

See [Shows and episodes](/dist/docs/3.0.0-rc.2/media-buy/product-discovery/shows-and-episodes) for the full model, examples, and targeting details.

### Exclusivity

The `exclusivity` field indicates whether a product offers exclusive access to its inventory. Defaults to `"none"` when absent.

| Value       | Meaning                                                                          |
| ----------- | -------------------------------------------------------------------------------- |
| `none`      | Multiple advertisers can buy this product simultaneously                         |
| `category`  | One advertiser per industry category (e.g., one auto brand per show 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.

#### 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.

Publishers SHOULD include `exclusivity` on guaranteed products with `show_ids`. 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.

#### Content sponsorship pattern

A product combining `delivery_type: "guaranteed"`, `exclusivity: "exclusive"`, and `show_ids` 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.

```json theme={null}
{
  "product_id": "signal_noise_sponsor",
  "name": "Signal & Noise — Exclusive Sponsorship",
  "description": "Sole sponsorship of Signal & Noise, a weekly technology podcast. Includes pre-roll and mid-roll placements across all episodes.",
  "publisher_properties": [
    { "publisher_domain": "crestnetwork.example", "property_ids": ["crest_podcasts"] }
  ],
  "format_ids": [
    { "agent_url": "https://ads.crestnetwork.example", "id": "audio_pre_roll_30s" },
    { "agent_url": "https://ads.crestnetwork.example", "id": "audio_mid_roll_60s" }
  ],
  "show_ids": ["signal_noise"],
  "delivery_type": "guaranteed",
  "exclusivity": "exclusive",
  "pricing_options": [
    {
      "pricing_option_id": "flat_monthly",
      "pricing_model": "flat_rate",
      "fixed_price": 25000,
      "currency": "USD"
    }
  ]
}
```

Category exclusivity works for multi-show bundles where the publisher separates competing brands across a network but still sells to multiple non-competing advertisers:

```json theme={null}
{
  "product_id": "crest_business_bundle",
  "name": "Crest Business Podcast Bundle — Category Sponsorship",
  "description": "Sponsorship across three business podcasts. One advertiser per industry category across all shows.",
  "publisher_properties": [
    { "publisher_domain": "crestnetwork.example", "property_ids": ["crest_podcasts"] }
  ],
  "format_ids": [
    { "agent_url": "https://ads.crestnetwork.example", "id": "audio_pre_roll_30s" },
    { "agent_url": "https://ads.crestnetwork.example", "id": "audio_mid_roll_60s" }
  ],
  "show_ids": ["signal_noise", "market_beat", "founder_stories"],
  "delivery_type": "guaranteed",
  "exclusivity": "category",
  "pricing_options": [
    {
      "pricing_option_id": "flat_quarterly",
      "pricing_model": "flat_rate",
      "fixed_price": 60000,
      "currency": "USD"
    }
  ]
}
```

### Property Targeting

The `property_targeting_allowed` flag indicates whether buyers can filter a product to a subset of its `publisher_properties` when using property list filtering via `get_products`.

#### Behavior

* **`property_targeting_allowed: false` (default)**: The product is "all or nothing." If the buyer's `property_list` doesn't include all of the product's properties, the product is excluded from results entirely.

* **`property_targeting_allowed: true`**: Buyers can filter the product to properties matching their `property_list`. The product is included in results if there is any intersection between its properties and the buyer's list.

#### Use Cases

| Use Case           | `property_targeting_allowed` | Why                                                |
| ------------------ | ---------------------------- | -------------------------------------------------- |
| Run of Network     | `false`                      | Buyers must accept the entire network              |
| Premium Bundles    | `false`                      | Sports + News bundle sold together                 |
| Flexible Inventory | `true`                       | Buyers can target specific sites within a category |

#### Examples

**All-or-nothing product** (`property_targeting_allowed: false`):

```json theme={null}
{
  "product_id": "premium_news_bundle",
  "name": "Premium News Bundle",
  "publisher_properties": [
    { "publisher_domain": "news.example.com", "property_ids": ["site_a", "site_b", "site_c"] }
  ],
  "property_targeting_allowed": false
}
```

When a buyer calls `get_products` with a `property_list` containing only `site_a` and `site_b`, this product is **excluded** because the buyer's list doesn't include all properties (`site_c` is missing).

**Flexible product** (`property_targeting_allowed: true`):

```json theme={null}
{
  "product_id": "news_category_flexible",
  "name": "News Category - Flexible Targeting",
  "publisher_properties": [
    { "publisher_domain": "news.example.com", "property_ids": ["tech", "sports", "finance", "politics"] }
  ],
  "property_targeting_allowed": true
}
```

When a buyer calls `get_products` with a `property_list` containing only `tech` and `sports`, this product is **included** because there is an intersection. The buyer can then purchase this product and target only the matching properties via `targeting_overlay.property_list` in the package.

### Custom & Principal-Specific Products

A server can offer a general catalog, but it can also return:

* **Principal-Specific Products**: Products reserved for or negotiated with specific clients
* **Custom Products**: Dynamically generated products with `is_custom: true` and an `expires_at` timestamp

## Product Examples

### Standard CTV Product (Multiple Pricing Options)

```json theme={null}
{
  "product_id": "connected_tv_prime",
  "name": "Connected TV - Prime Time",
  "description": "Premium CTV inventory 8PM-11PM",
  "publisher_properties": [
    { "publisher_domain": "streaming.example.com", "selection_type": "all" }
  ],
  "format_ids": [
    {
      "agent_url": "https://creative.adcontextprotocol.org",
      "id": "video_15s"
    },
    {
      "agent_url": "https://creative.adcontextprotocol.org",
      "id": "video_30s"
    }
  ],
  "delivery_type": "guaranteed",
  "pricing_options": [
    {
      "pricing_option_id": "cpm_usd_guaranteed",
      "pricing_model": "cpm",
      "fixed_price": 45.00,
      "currency": "USD",
      "min_spend_per_package": 10000
    },
    {
      "pricing_option_id": "cpcv_usd_guaranteed",
      "pricing_model": "cpcv",
      "fixed_price": 0.18,
      "currency": "USD",
      "min_spend_per_package": 10000
    },
    {
      "pricing_option_id": "cpp_usd_p18-49",
      "pricing_model": "cpp",
      "fixed_price": 250.00,
      "currency": "USD",
      "parameters": {
        "demographic": "P18-49",
        "min_points": 50
      },
      "min_spend_per_package": 12500
    }
  ],
  "delivery_measurement": {
    "provider": "Nielsen DAR for P18-49 demographic measurement",
    "notes": "Panel-based measurement for GRP delivery. Impressions measured via Comscore vCE."
  }
}
```

### Auction-Based Display Product

```json theme={null}
{
  "product_id": "custom_abc123",
  "name": "Custom - Gaming Enthusiasts",
  "description": "Custom audience package for gaming campaign",
  "publisher_properties": [
    { "publisher_domain": "gaming.example.com", "selection_type": "all" }
  ],
  "format_ids": [
    {
      "agent_url": "https://creative.adcontextprotocol.org",
      "id": "display_300x250"
    },
    {
      "agent_url": "https://creative.adcontextprotocol.org",
      "id": "display_728x90"
    }
  ],
  "delivery_type": "non_guaranteed",
  "pricing_options": [
    {
      "pricing_option_id": "cpm_usd_auction",
      "pricing_model": "cpm",
      "currency": "USD",
      "floor_price": 5.00,
      "price_guidance": {
        "p50": 8.00,
        "p75": 12.00
      }
    },
    {
      "pricing_option_id": "cpc_usd_auction",
      "pricing_model": "cpc",
      "currency": "USD",
      "floor_price": 0.50,
      "price_guidance": {
        "p50": 1.20,
        "p75": 2.00
      }
    }
  ],
  "delivery_measurement": {
    "provider": "Google Ad Manager with IAS viewability",
    "notes": "MRC-accredited viewability. 50% in-view for 1s display."
  },
  "is_custom": true,
  "expires_at": "2025-02-15T00:00:00Z"
}
```

### Retail Media Product with Measurement

```json theme={null}
{
  "product_id": "albertsons_pet_category_offsite",
  "name": "Pet Category Shoppers - Offsite Display & Video",
  "description": "Target Albertsons shoppers who have purchased pet products in the last 90 days. Reach them across premium display and video inventory.",
  "publisher_properties": [
    { "publisher_domain": "groceryretail.example.com", "selection_type": "all" }
  ],
  "format_ids": [
    {
      "agent_url": "https://creative.adcontextprotocol.org",
      "id": "display_300x250"
    },
    {
      "agent_url": "https://creative.adcontextprotocol.org",
      "id": "display_728x90"
    },
    {
      "agent_url": "https://creative.adcontextprotocol.org",
      "id": "video_15s"
    }
  ],
  "delivery_type": "guaranteed",
  "pricing_options": [
    {
      "pricing_option_id": "cpm_usd_guaranteed",
      "pricing_model": "cpm",
      "fixed_price": 13.50,
      "currency": "USD",
      "min_spend_per_package": 10000
    }
  ],
  "delivery_measurement": {
    "provider": "Self-reported impressions from proprietary ad server",
    "notes": "Impressions counted per IAB guidelines. Viewability measured via IAS."
  },
  "outcome_measurement": {
    "type": "incremental_sales_lift",
    "attribution": "deterministic_purchase",
    "window": { "interval": 30, "unit": "days" },
    "reporting": "weekly_dashboard"
  },
  "creative_policy": {
    "co_branding": "optional",
    "landing_page": "must_include_retailer",
    "templates_available": true
  }
}
```

## Product Cards

Product cards provide visual representations of products for display in user interfaces. Publishers can optionally include card definitions that reference card formats and provide the assets needed to render attractive visual cards.

### Card Types

Publishers should provide at least the standard card, and optionally a detailed card:

**Standard Card** (`product_card`):

* Compact 300x400px card for product grids and lists
* Supports 2x density images for retina displays
* Quick visual identification of products

**Detailed Card** (`product_card_detailed`, optional):

* Responsive layout with text description alongside hero carousel
* Markdown specifications section below
* Full product documentation similar to media kits

### Structure

```json theme={null}
{
  "product_id": "ctv_premium",
  "name": "Premium CTV Inventory",
  // ... other product fields ...

  "product_card": {
    "format_id": {
      "agent_url": "https://creative.adcontextprotocol.org",
      "id": "product_card_standard"
    },
    "manifest": {
      "display_name": "Premium CTV - Living Room Audiences",
      "hero_image_url": "https://cdn.example.com/products/ctv_hero.jpg",
      "brief_highlight": "Perfect for reaching cord-cutters and premium streaming audiences"
    }
  },

  "product_card_detailed": {
    "format_id": {
      "agent_url": "https://creative.adcontextprotocol.org",
      "id": "product_card_detailed"
    },
    "manifest": {
      "display_name": "Premium CTV - Living Room Audiences",
      "description": "Reach high-income households with premium CTV inventory during peak viewing hours...",
      "carousel_images": [
        "https://cdn.example.com/products/ctv_context1.jpg",
        "https://cdn.example.com/products/ctv_context2.jpg"
      ],
      "specifications_markdown": "# Technical Specifications\n\n..."
    }
  }
}
```

### Rendering Cards

Cards can be rendered in two ways:

1. **Via `preview_creative`**: Pass the card format and manifest to generate a rendered card
2. **Pre-rendered**: Publishers can pre-generate cards and serve them directly

This flexibility allows publishers to choose between dynamic generation or static hosting based on their infrastructure.

### Standard Card Formats

The AdCP reference creative agent defines two standard card formats:

* **`product_card_standard`** (300x400px) - Compact card for product browsing
* **`product_card_detailed`** (responsive) - Rich card with carousel and full specs

Publishers can also define custom card formats to match their branding or highlight unique product attributes.

**Note**: Standard card format definitions are maintained in the [creative-agent repository](https://github.com/adcontextprotocol/creative-agent), not in this protocol specification.

### When to Include Product Cards

Product cards are optional but recommended for:

* Products with strong visual identity (e.g., specific shows, events, publications)
* Premium products where visual presentation enhances perceived value
* Complex products where visual highlights help explain capabilities
* Products targeting specific audiences that benefit from visual representation

Use the detailed card variant when you want to provide comprehensive product documentation similar to media kit pages.

### Client Rendering Guidelines

When displaying products in UIs, clients should follow this fallback order:

1. **If `product_card` exists** → Render card via `preview_creative` or display pre-rendered image
2. **If neither exists** → Render text-only representation (product name + description)
3. **If card rendering fails** → Gracefully fall back to text-only representation

This ensures a consistent user experience regardless of what product metadata is available.

## Proposals

Publishers can return **proposals** alongside products - structured media plans with budget allocations that buyers can execute directly.

### What Are Proposals?

A proposal is a recommended buying strategy that groups products with suggested budget allocations. Proposals encode publisher expertise - the kind of media planning guidance that traditionally required human sales reps.

Key characteristics:

* **Actionable**: Buyers execute proposals directly via `create_media_buy` with a `proposal_id`
* **Budget-agnostic**: Allocations use percentages, allowing the same proposal to scale to any budget
* **Forecast-equipped**: Proposals and allocations can include delivery forecasts to help buyers evaluate expected performance before purchase

### Proposal Structure

```json theme={null}
{
  "proposal_id": "swiss_balanced_v1",
  "name": "Swiss Multi-Channel Plan",
  "description": "Balanced coverage across devices and language regions",
  "allocations": [
    {
      "product_id": "ch_desktop_de",
      "allocation_percentage": 20,
      "pricing_option_id": "cpm_usd_fixed",
      "rationale": "Primary desktop audience in German Switzerland",
      "tags": ["desktop", "german"]
    },
    {
      "product_id": "ch_desktop_fr",
      "allocation_percentage": 30,
      "tags": ["desktop", "french"]
    },
    {
      "product_id": "ch_mobile_de",
      "allocation_percentage": 8,
      "tags": ["mobile", "german"]
    },
    {
      "product_id": "ch_mobile_fr",
      "allocation_percentage": 12,
      "tags": ["mobile", "french"]
    },
    {
      "product_id": "ch_inapp_de",
      "allocation_percentage": 12,
      "tags": ["in-app", "german"]
    },
    {
      "product_id": "ch_inapp_fr",
      "allocation_percentage": 18,
      "tags": ["in-app", "french"]
    }
  ],
  "total_budget_guidance": {
    "min": 30000,
    "recommended": 50000,
    "currency": "USD"
  },
  "brief_alignment": "Achieves 50/20/30 channel split (desktop/mobile/in-app) and 40/60 language split (German/French)",
  "forecast": {
    "points": [
      {
        "budget": 50000,
        "metrics": {
          "impressions": { "low": 800000, "mid": 1200000, "high": 1500000 },
          "reach": { "low": 400000, "mid": 600000, "high": 750000 },
          "clicks": { "mid": 4800 }
        }
      }
    ],
    "method": "modeled",
    "currency": "USD",
    "valid_until": "2025-04-15T00:00:00Z"
  }
}
```

The `tags` field enables grouping allocations by dimension:

* **By channel**: desktop (50%) + mobile (20%) + in-app (30%) = 100%
* **By language**: German (40%) + French (60%) = 100%

### Iterating on Proposals

Proposals can be refined using `buying_mode: "refine"` with the `refine` array. Reference proposals by ID — the seller returns an updated proposal with revised allocations, forecasts, and pricing:

```
// Initial discovery
get_products({
  buying_mode: "brief",
  brief: "Swiss campaign, $50k, 50% desktop/20% mobile/30% in-app, 40% German/60% French"
})

// Response includes proposal "swiss_balanced_v1"

// Refine the proposal
get_products({
  buying_mode: "refine",
  refine: [
    { scope: "product", id: "ch_desktop_de", action: "include" },
    { scope: "product", id: "ch_desktop_fr", action: "include" },
    { scope: "product", id: "ch_mobile_de", action: "include" },
    { scope: "product", id: "ch_mobile_fr", action: "include" },
    { scope: "product", id: "ch_inapp_de", action: "include" },
    { scope: "product", id: "ch_inapp_fr", action: "include" },
    { scope: "proposal", id: "swiss_balanced_v1", action: "include", ask: "focus more on German speakers - try 60/40 instead of 40/60" }
  ]
})

// Seller returns an updated proposal with revised allocations
```

See [`get_products` refinement](/dist/docs/3.0.0-rc.2/media-buy/task-reference/get_products#refinement) for the full workflow and examples.

### Executing a Proposal

To execute a proposal, provide the `proposal_id` and `total_budget` in `create_media_buy`:

```json theme={null}
{
  "buyer_ref": "swiss_q1_campaign",
  "proposal_id": "swiss_balanced_v1",
  "total_budget": {
    "amount": 50000,
    "currency": "USD"
  },
  "brand": { "domain": "acmecorp.com" },
  "start_time": "2025-04-01T00:00:00Z",
  "end_time": "2025-04-30T23:59:59Z"
}
```

The publisher converts the proposal's allocation percentages into packages:

* `ch_desktop_de`: 20% × $50,000 = $10,000
* `ch_desktop_fr`: 30% × $50,000 = $15,000
* etc.

This approach simplifies complex multi-line-item campaigns to a single proposal execution.

### When Publishers Return Proposals

Publishers include proposals when:

* The brief requests specific allocation strategies (channel splits, language splits, etc.)
* The publisher can provide strategic guidance based on campaign goals
* Multiple products work better together than individually

Publishers typically omit proposals in `wholesale` mode (the buyer is directing targeting and allocation themselves) or when the brief doesn't suggest a multi-product strategy.

Proposals are optional — publishers may return only products if allocation guidance isn't applicable. In `refine` mode, sellers MAY return proposals alongside refined products even when the buyer did not include proposal entries. Proposals are a seller suggestion — allocation and campaign optimization are primarily orchestrator (buyer-side agent) responsibilities.

### Delivery Forecasts

Publishers can attach delivery forecasts to proposals and individual allocations to help buyers evaluate expected performance before committing budget.

Each forecast contains a `points` array of one or more ForecastPoints, each pairing a budget level with metric ranges (low/mid/high). A single point is a standard forecast; multiple points ordered by ascending budget show how metrics scale with spend.

Metric keys come from two vocabularies:

* **Delivery/engagement**: `forecastable-metric` enum values (impressions, reach, clicks, spend, views, completed\_views, grps, etc.)
* **Outcomes**: `event-type` enum values (purchase, lead, app\_install, add\_to\_cart, subscribe, etc.)

This lets sellers forecast both delivery ("1.2M impressions") and outcomes ("1,800 purchases") in a single forecast. Each forecast declares its method:

* **`estimate`** — rough approximation based on historical averages or heuristics
* **`modeled`** — derived from predictive models or historical data
* **`guaranteed`** — contractually committed delivery levels backed by reserved inventory

#### Forecast Range Units

The `forecast_range_unit` field tells consumers how to interpret the points array — what axis the curve represents:

* **`spend`** (default) — points at ascending budget levels. Standard budget curve.
* **`reach_freq`** — points at ascending reach/frequency targets. Used in broadcast planning where the publisher shows how cost scales with frequency goals.
* **`weekly`** / **`daily`** — metrics are per-period values. Budget refers to total campaign spend. A frequency of 3.2 with `weekly` means 3.2 exposures per week.
* **`clicks`** / **`conversions`** — points at ascending outcome targets. Used in goal-based planning (e.g., "tell me your conversion goal, I'll tell you the budget").

A spend curve and a reach/frequency curve may contain identical data — the difference is the publisher's intent. A spend curve says "here's what different budgets buy." A reach/frequency curve says "here's what it costs to hit different frequency targets." Consumers can read either curve in either direction.

Temporal units (`weekly`, `daily`) change how metrics are interpreted. Without a range unit (or with `spend`), a frequency of 3.2 means 3.2 total campaign exposures. With `weekly`, it means 3.2 exposures per week.

Forecasts can appear at two levels:

* **Proposal-level**: aggregate forecast for the entire media plan
* **Allocation-level**: per-product forecast for individual line items

Allocation-level forecasts may not sum to the proposal-level forecast due to audience overlap and frequency capping. When both are present, the proposal-level forecast is authoritative for total delivery estimation.

For cross-channel planning, forecasts declare a `reach_unit` (individuals, households, devices, accounts, cookies) so buyers can compare reach across publishers. GRP-based forecasts (linear TV, radio) use `demographic_system` and `demographic` to specify the target demo, following the same pattern as CPP pricing.

#### Budget Curve

Multiple forecast points at ascending budget levels show how metrics scale with spend, helping buyers find the optimal investment level:

```json theme={null}
{
  "points": [
    {
      "budget": 25000,
      "metrics": {
        "impressions": { "low": 400000, "mid": 500000, "high": 600000 },
        "reach": { "mid": 180000 },
        "clicks": { "mid": 2000 }
      }
    },
    {
      "budget": 50000,
      "metrics": {
        "impressions": { "low": 850000, "mid": 1050000, "high": 1200000 },
        "reach": { "mid": 320000 },
        "clicks": { "mid": 4200 }
      }
    },
    {
      "budget": 100000,
      "metrics": {
        "impressions": { "low": 1500000, "mid": 1900000, "high": 2200000 },
        "reach": { "mid": 500000 },
        "clicks": { "mid": 7600 }
      }
    }
  ],
  "method": "modeled",
  "currency": "USD",
  "reach_unit": "individuals"
}
```

The curve reveals diminishing returns — doubling budget from $50K to $100K increases reach by \~56%, not 2x. Buyers can use this to negotiate or reallocate budget across publishers.

#### CTV with GRP Demographics

TV and audio forecasts use `demographic_system` and `demographic` to specify the target demo. This ensures buyer and seller agree on the measurement notation:

```json theme={null}
{
  "points": [
    {
      "budget": 75000,
      "metrics": {
        "grps": { "low": 45, "mid": 60, "high": 72 },
        "impressions": { "mid": 3200000 },
        "reach": { "low": 800000, "mid": 1100000, "high": 1300000 },
        "frequency": { "mid": 2.9 }
      }
    }
  ],
  "method": "modeled",
  "currency": "USD",
  "demographic_system": "nielsen",
  "demographic": "P18-49",
  "reach_unit": "households"
}
```

The `reach_unit: "households"` tells buyers this CTV publisher counts reach by household, not individual. A display publisher reporting `reach_unit: "devices"` is measuring something different — buyers should not directly compare the two reach numbers.

#### Retail Media with Outcome Forecasts

Retail media publishers can forecast both delivery metrics and conversion outcomes. Outcome metric keys use `event-type` values:

```json theme={null}
{
  "points": [
    {
      "budget": 30000,
      "metrics": {
        "impressions": { "low": 600000, "mid": 750000, "high": 900000 },
        "clicks": { "mid": 6000 },
        "purchase": { "low": 1200, "mid": 1800, "high": 2400 },
        "add_to_cart": { "mid": 4500 }
      }
    }
  ],
  "method": "modeled",
  "currency": "USD"
}
```

Here `impressions` and `clicks` are `forecastable-metric` values while `purchase` and `add_to_cart` are `event-type` values. Both use ForecastRange (low/mid/high) and coexist in the same metrics map.

#### Allocation-Level Forecasts

When a proposal includes per-allocation forecasts, buyers can evaluate each product independently:

```json theme={null}
{
  "proposal_id": "retail_holiday_v1",
  "name": "Holiday Retail Campaign",
  "allocations": [
    {
      "product_id": "sponsored_search",
      "allocation_percentage": 40,
      "forecast": {
        "points": [
          {
            "budget": 20000,
            "metrics": {
              "impressions": { "mid": 500000 },
              "clicks": { "mid": 15000 },
              "purchase": { "mid": 900 }
            }
          }
        ],
        "method": "modeled",
        "currency": "USD"
      }
    },
    {
      "product_id": "offsite_display",
      "allocation_percentage": 60,
      "forecast": {
        "points": [
          {
            "budget": 30000,
            "metrics": {
              "impressions": { "low": 1800000, "mid": 2200000, "high": 2600000 },
              "reach": { "mid": 450000 },
              "purchase": { "low": 400, "mid": 600, "high": 800 }
            }
          }
        ],
        "method": "modeled",
        "currency": "USD",
        "reach_unit": "accounts"
      }
    }
  ],
  "forecast": {
    "points": [
      {
        "budget": 50000,
        "metrics": {
          "impressions": { "low": 2100000, "mid": 2700000, "high": 3100000 },
          "reach": { "mid": 520000 },
          "purchase": { "low": 1100, "mid": 1500, "high": 1900 }
        }
      }
    ],
    "method": "modeled",
    "currency": "USD",
    "reach_unit": "accounts"
  }
}
```

Note that the allocation forecasts (900 + 600 = 1,500 purchases) happen to match the proposal forecast in this example, but they often won't — audience overlap and frequency capping mean the whole is typically less than the sum of its parts. The proposal-level forecast is authoritative for total delivery.

#### Broadcast Audio Spot Plan

Broadcast and audio publishers can return spot-plan proposals with `daypart_targets` on each allocation and weekly frequency projections via `forecast_range_unit: "weekly"`. This pattern lets the publisher solve the optimization problem — the buyer specifies frequency goals, and the publisher returns the plan that achieves them:

```json theme={null}
{
  "proposal_id": "iheart_q4_audio",
  "name": "Q4 Audio - Adults 25-54",
  "allocations": [
    {
      "product_id": "morning_drive_30s",
      "allocation_percentage": 50,
      "daypart_targets": [
        {
          "days": ["monday", "tuesday", "wednesday", "thursday", "friday"],
          "start_hour": 6,
          "end_hour": 10,
          "label": "Morning Drive"
        }
      ],
      "rationale": "Morning drive delivers highest reach against P25-54 with 3x weekly frequency at 2 spots/day",
      "forecast": {
        "points": [
          {
            "budget": 37500,
            "metrics": {
              "grps": { "mid": 42 },
              "reach": { "low": 140000, "mid": 180000, "high": 210000 },
              "frequency": { "mid": 3.2 },
              "impressions": { "mid": 576000 }
            }
          }
        ],
        "forecast_range_unit": "weekly",
        "method": "modeled",
        "currency": "USD",
        "demographic_system": "nielsen",
        "demographic": "P25-54",
        "reach_unit": "individuals"
      }
    },
    {
      "product_id": "afternoon_drive_30s",
      "allocation_percentage": 30,
      "daypart_targets": [
        {
          "days": ["monday", "tuesday", "wednesday", "thursday", "friday"],
          "start_hour": 15,
          "end_hour": 19,
          "label": "Afternoon Drive"
        }
      ],
      "rationale": "Afternoon drive complements morning with incremental reach and frequency overlap",
      "forecast": {
        "points": [
          {
            "budget": 22500,
            "metrics": {
              "grps": { "mid": 28 },
              "reach": { "low": 95000, "mid": 120000, "high": 145000 },
              "frequency": { "mid": 2.4 },
              "impressions": { "mid": 288000 }
            }
          }
        ],
        "forecast_range_unit": "weekly",
        "method": "modeled",
        "currency": "USD",
        "demographic_system": "nielsen",
        "demographic": "P25-54",
        "reach_unit": "individuals"
      }
    },
    {
      "product_id": "daytime_30s",
      "allocation_percentage": 20,
      "daypart_targets": [
        {
          "days": ["monday", "tuesday", "wednesday", "thursday", "friday"],
          "start_hour": 10,
          "end_hour": 15,
          "label": "Daytime"
        }
      ],
      "rationale": "Daytime fill provides frequency reinforcement at lower CPP",
      "forecast": {
        "points": [
          {
            "budget": 15000,
            "metrics": {
              "grps": { "mid": 18 },
              "reach": { "low": 60000, "mid": 80000, "high": 95000 },
              "frequency": { "mid": 1.8 },
              "impressions": { "mid": 144000 }
            }
          }
        ],
        "forecast_range_unit": "weekly",
        "method": "modeled",
        "currency": "USD",
        "demographic_system": "nielsen",
        "demographic": "P25-54",
        "reach_unit": "individuals"
      }
    }
  ],
  "forecast": {
    "points": [
      {
        "budget": 75000,
        "metrics": {
          "grps": { "mid": 82 },
          "reach": { "low": 220000, "mid": 280000, "high": 330000 },
          "frequency": { "mid": 4.1 },
          "impressions": { "mid": 1008000 }
        }
      }
    ],
    "forecast_range_unit": "weekly",
    "method": "modeled",
    "currency": "USD",
    "demographic_system": "nielsen",
    "demographic": "P25-54",
    "reach_unit": "individuals"
  }
}
```

The `forecast_range_unit: "weekly"` on each forecast tells the buyer that all metrics are per-week values — frequency of 3.2 means 3.2 exposures per week, not 3.2 over the entire campaign. Budget (\$75K) is total campaign spend.

The `daypart_targets` on each allocation specify the publisher's recommended time windows. These are the same structure used in `targeting` for hard daypart constraints — here the publisher is prescribing the spot plan rather than the buyer constraining it.

Note that allocation-level reach doesn't sum to the proposal level (180K + 120K + 80K > 280K) because of audience overlap across dayparts — the same listener may hear morning drive and afternoon drive spots. The proposal-level forecast accounts for this overlap.

## Integration with Discovery

Products are discovered through the [Product Discovery](/dist/docs/3.0.0-rc.2/media-buy/product-discovery) process, which uses natural language to match campaign briefs with available inventory. Once products are identified, they can be purchased via `create_media_buy`.

## See Also

* [Product Discovery](/dist/docs/3.0.0-rc.2/media-buy/product-discovery) - How to discover products using natural language
* [Media Buys](/dist/docs/3.0.0-rc.2/media-buy/media-buys) - How to purchase products
* [Targeting](/dist/docs/3.0.0-rc.2/media-buy/advanced-topics/targeting) - Detailed targeting options
* [Creative Formats](/dist/docs/3.0.0-rc.2/creative/formats) - Understanding format specifications and discovery
