diff --git a/docs/media-buy/advanced-topics/pricing-models.md b/docs/media-buy/advanced-topics/pricing-models.md new file mode 100644 index 0000000000..561fb80d07 --- /dev/null +++ b/docs/media-buy/advanced-topics/pricing-models.md @@ -0,0 +1,483 @@ +--- +title: Pricing Models +description: Comprehensive guide to AdCP's flexible pricing models including CPM, CPCV, CPP, CPC, and DOOH support +keywords: [pricing models, CPM, CPCV, CPP, CPC, CPV, GRP, video pricing, DOOH, share of voice, measurement] +--- + +# Pricing Models + +AdCP supports multiple pricing models to accommodate different advertising channels and business objectives. Publishers declare which pricing models they support, and buyers select from available options. + +## Publisher-Declared, Buyer-Selected Model + +### How It Works + +1. **Publishers declare pricing options** in their products via `pricing_options` array (each with unique `pricing_option_id`) +2. **Buyers discover available options** through `get_products` +3. **Buyers select a specific option** when creating a media buy via `pricing_option_id` +4. **Delivery is measured** according to the declared `delivery_measurement` provider + +### Key Benefits + +- **Flexibility**: Publishers can offer multiple pricing models for the same inventory +- **Currency Support**: Publishers specify supported currencies; buyers must match +- **Market Standards**: Each channel (TV, video, display, performance) can use its natural pricing unit +- **Clear Expectations**: Both parties agree on pricing before campaign launch + +## Measurement & Source of Truth + +### Measurement Provider as Source of Truth + +**The product declares the measurement provider, and the buyer accepts that provider as the source of truth for the buy.** + +Publishers specify their measurement provider in the product: + +```json +{ + "product_id": "premium_video", + "delivery_measurement": { + "provider": "Google Ad Manager with IAS viewability verification", + "notes": "MRC-accredited viewability measurement. 50% in-view for 1 second (display) or 2 seconds (video)." + } +} +``` + +**Common Measurement Providers:** +- **Ad Servers**: Google Ad Manager, Freewheel, SpringServe +- **Attention Metrics**: Adelaide, Lumen, TVision +- **Third-Party Verification**: IAS, DoubleVerify, Scope3 +- **TV/Audio Measurement**: Nielsen, Comscore, iSpot.tv, Triton Digital +- **DOOH**: Geopath, Vistar, Place Exchange + +By accepting the product, buyers agree to use the declared measurement provider as the authoritative source for delivery metrics. + +### Best Practices + +**For Publishers:** +- Clearly identify your measurement provider (ad server and any third-party verification) +- Explain your measurement methodology in plain language +- For DOOH, specify your audience measurement source (e.g., Geopath, venue sensors) + +**For Buyers:** +- Review the measurement provider before committing budget +- Ensure the provider meets your campaign requirements +- Negotiate audit rights in contracts if needed + +## Supported Pricing Models + +### CPM (Cost Per Mille) +**Cost per 1,000 impressions** - Traditional display advertising pricing. + +**Use Cases**: Display, native, banner advertising + +**Example**: +```json +{ + "pricing_option_id": "cpm_usd_guaranteed", + "pricing_model": "cpm", + "rate": 12.50, + "currency": "USD", + "is_fixed": true, + "min_spend_per_package": 5000 +} +``` + +**Billing**: Charged per 1,000 ad impressions served + +--- + +### CPCV (Cost Per Completed View) +**Cost per 100% video/audio completion** - Payment only for fully completed views. + +**Use Cases**: Video campaigns, audio ads, pre-roll video + +**Example**: +```json +{ + "pricing_option_id": "cpcv_usd_guaranteed", + "pricing_model": "cpcv", + "rate": 0.15, + "currency": "USD", + "is_fixed": true +} +``` + +**Billing**: Charged only when viewer completes 100% of the video/audio ad. Completion is measured by the declared measurement provider. + +--- + +### CPV (Cost Per View) +**Cost per view at threshold** - Payment when viewer reaches publisher-defined threshold. + +**Use Cases**: Video campaigns with shorter completion requirements + +**Example**: +```json +{ + "pricing_model": "cpv", + "rate": 0.08, + "currency": "USD", + "is_fixed": true, + "parameters": { + "view_threshold": 0.5 + } +} +``` + +**Billing**: Charged when viewer reaches threshold (e.g., 50% completion, 30 seconds) + +**Parameters**: +- `view_threshold`: Decimal from 0.0 to 1.0 (e.g., 0.5 = 50% completion) + +--- + +### CPP (Cost Per Point) +**Cost per Gross Rating Point** - Traditional TV/radio buying metric. + +**Use Cases**: Connected TV, linear TV, radio, audio streaming + +**Example**: +```json +{ + "pricing_model": "cpp", + "rate": 250.00, + "currency": "USD", + "is_fixed": true, + "parameters": { + "demographic": "A18-49", + "min_points": 50 + }, + "min_spend_per_package": 12500 +} +``` + +**Billing**: Charged per rating point delivered to target demographic + +**Parameters**: +- `demographic`: Target demographic (e.g., "A18-49", "W25-54", "M35+") +- `min_points`: Minimum GRP commitment required + +**Metrics Reported**: +- `grps`: Total Gross Rating Points delivered +- `reach`: Unique individuals reached +- `frequency`: Average frequency per individual + +**Measurement Requirements**: + +CPP pricing requires **certified demographic measurement**. Publishers should declare their measurement provider: + +```json +{ + "pricing_model": "cpp", + "rate": 250.00, + "delivery_measurement": { + "provider": "Nielsen DAR", + "notes": "Panel-based demographic measurement for A18-49. GRP reports available weekly." + } +} +``` + +**Common Measurement Providers for CPP**: +- **Nielsen DAR/TV**: Industry-standard TV measurement +- **Comscore**: Campaign Ratings for CTV +- **iSpot.tv**: Advanced TV analytics +- **Triton Digital**: Audio/streaming measurement + +Buyers should verify the measurement provider meets their campaign requirements before accepting CPP deals. + +--- + +### CPC (Cost Per Click) +**Cost per click** - Performance-based pricing for engagement. + +**Use Cases**: Direct response campaigns, search ads, social advertising + +**Example**: +```json +{ + "pricing_model": "cpc", + "rate": 1.50, + "currency": "USD", + "is_fixed": false, + "price_guidance": { + "floor": 0.50, + "p50": 1.20, + "p75": 2.00 + } +} +``` + +**Billing**: Charged only when user clicks the ad + +--- + +### Flat Rate +**Fixed cost** - Single payment regardless of delivery volume. + +**Use Cases**: Sponsorships, takeovers, exclusive placements, branded content + +**Example**: +```json +{ + "pricing_model": "flat_rate", + "rate": 50000.00, + "currency": "USD", + "is_fixed": true +} +``` + +**Billing**: Fixed cost for the entire campaign period + +--- + +## Digital Out-of-Home (DOOH) Pricing + +DOOH advertising uses existing pricing models—typically **CPM** or **flat_rate**—with optional parameters to describe the inventory allocation. + +### Basic Concepts + +- **CPM for DOOH**: Priced per thousand impressions, where impressions are calculated based on venue traffic (e.g., Geopath data) +- **Flat rate for DOOH**: Fixed cost for specific duration or allocation (hourly, daily, or exclusive takeover) + +### Simple Example: Billboard Takeover + +```json +{ + "product_id": "billboard_takeover", + "name": "Premium Billboard - 24 Hour Takeover", + "pricing_options": [{ + "pricing_model": "flat_rate", + "rate": 50000.00, + "currency": "USD", + "is_fixed": true + }], + "delivery_measurement": { + "provider": "Geopath", + "notes": "Venue traffic data updated monthly. Estimated 2.5M impressions over 24 hours." + } +} +``` + +### DOOH Parameters (Optional) + +Publishers may include additional parameters to describe DOOH inventory allocation: + +- `duration_hours`: Duration for time-based pricing +- `sov_percentage`: Share of voice (% of available plays) +- `daypart`: Specific time periods (e.g., "morning_commute") +- `venue_package`: Named collection of screens + +**Note**: DOOH measurement and buying practices vary by market. Publishers should clearly explain their measurement methodology and inventory allocation in the product description and `delivery_measurement` field. + +--- + +## Multi-Currency Support + +Publishers can offer the same product in multiple currencies: + +```json +{ + "product_id": "premium_video", + "pricing_options": [ + { + "pricing_option_id": "cpm_usd_guaranteed", + "pricing_model": "cpm", + "rate": 45.00, + "currency": "USD", + "is_fixed": true + }, + { + "pricing_option_id": "cpm_eur_guaranteed", + "pricing_model": "cpm", + "rate": 40.00, + "currency": "EUR", + "is_fixed": true + }, + { + "pricing_option_id": "cpm_gbp_guaranteed", + "pricing_model": "cpm", + "rate": 35.00, + "currency": "GBP", + "is_fixed": true + } + ] +} +``` + +**Buyer Responsibility**: Buyers must select a currency that the publisher supports. + +## Fixed vs. Auction Pricing + +### Fixed Pricing (`is_fixed: true`) +- Publisher sets a fixed rate +- Rate is guaranteed and predictable +- Common for guaranteed inventory +- Requires `rate` field + +### Auction Pricing (`is_fixed: false`) +- Final price determined through auction +- Publisher provides `price_guidance` with floor and percentiles +- Common for non-guaranteed inventory +- Buyer submits `bid_price` in media buy request + +**Auction Example**: +```json +{ + "pricing_option_id": "cpcv_usd_auction", + "pricing_model": "cpcv", + "currency": "USD", + "is_fixed": false, + "price_guidance": { + "floor": 0.08, + "p25": 0.10, + "p50": 0.12, + "p75": 0.15, + "p90": 0.18 + } +} +``` + +## Buyer Selection Process + +Currency is set at the **media buy level**, packages specify their pricing option and budget allocation: + +```json +{ + "buyer_ref": "campaign_001", + "budget": { + "total": 100000, + "currency": "USD" + }, + "start_time": "2025-01-01T00:00:00Z", + "end_time": "2025-01-31T23:59:59Z", + "promoted_offering": "Q1 Brand Campaign", + "packages": [{ + "buyer_ref": "pkg_ctv", + "products": ["premium_ctv"], + "format_ids": ["video_15s", "video_30s"], + "budget": 50000, + "pacing": "even", + "pricing_option_id": "cpcv_usd_auction", + "bid_price": 0.16 + }] +} +``` + +**How it works:** +1. Media buy sets overall `budget.currency` (e.g., "USD") - applies to all packages +2. Each package selects a specific `pricing_option_id` from the product (e.g., "cpcv_usd_auction") +3. The pricing option ID fully specifies the pricing model, currency, and whether it's fixed or auction +4. Package sets its budget allocation as a number in the media buy's currency +5. Package can specify `pacing` strategy (even, frontload, etc.) +6. If the selected pricing option is auction-based (`is_fixed: false`), package must include `bid_price` + +## Reporting Metrics by Pricing Model + +Different pricing models report different primary metrics: + +| Pricing Model | Primary Metric | Secondary Metrics | +|---------------|----------------|-------------------| +| CPM | impressions | clicks, ctr, spend | +| CPCV | completed_views | impressions, completion_rate, spend | +| CPV | views | impressions, quartile_data, spend | +| CPP | grps | reach, frequency, spend | +| CPC | clicks | impressions, ctr, spend | +| Flat Rate | N/A | impressions, reach, frequency | + +## Example: Multi-Model CTV Product + +A publisher offering Connected TV inventory with multiple pricing options: + +```json +{ + "product_id": "ctv_premium_sports", + "name": "Premium Sports CTV", + "description": "High-engagement sports content on CTV devices", + "format_ids": ["video_15s", "video_30s"], + "delivery_type": "guaranteed", + "pricing_options": [ + { + "pricing_option_id": "cpm_usd_guaranteed", + "pricing_model": "cpm", + "rate": 55.00, + "currency": "USD", + "is_fixed": true, + "min_spend_per_package": 15000 + }, + { + "pricing_option_id": "cpcv_usd_guaranteed", + "pricing_model": "cpcv", + "rate": 0.22, + "currency": "USD", + "is_fixed": true, + "min_spend_per_package": 15000 + }, + { + "pricing_option_id": "cpp_usd_m18-49", + "pricing_model": "cpp", + "rate": 300.00, + "currency": "USD", + "is_fixed": true, + "parameters": { + "demographic": "M18-49", + "min_points": 50 + }, + "min_spend_per_package": 15000 + } + ] +} +``` + +A buyer could choose CPP pricing if they're planning TV buys, CPCV if optimizing for engagement, or CPM for reach-based campaigns. + +## Best Practices + +### For Publishers + +1. **Offer relevant pricing models** - Match pricing to your inventory type and buyer expectations +2. **Set appropriate minimums** - Use `min_spend_per_package` to ensure campaign viability +3. **Provide price guidance** - For auction pricing, give realistic floor and percentile data +4. **Consider multi-currency** - Support currencies of your target markets +5. **Document parameters** - Clearly explain thresholds, demographics, and action types + +### For Buyers + +1. **Select appropriate model** - Choose pricing that aligns with campaign objectives +2. **Match currency** - Ensure you select a currency the publisher supports +3. **Set realistic budgets** - Account for minimum spend requirements +4. **Align goals with pricing** - Set delivery goals that match your pricing model +5. **Monitor relevant metrics** - Focus on the metrics that matter for your pricing model + +## Migration from CPM-Only + +For backward compatibility, products can still use the deprecated `is_fixed_price` and `cpm` fields. However, new implementations should use `pricing_options`. + +**Old Format** (deprecated): +```json +{ + "product_id": "display_standard", + "is_fixed_price": true, + "cpm": 12.50 +} +``` + +**New Format**: +```json +{ + "product_id": "display_standard", + "pricing_options": [{ + "pricing_option_id": "cpm_usd_guaranteed", + "pricing_model": "cpm", + "rate": 12.50, + "currency": "USD", + "is_fixed": true + }] +} +``` + +## Related Documentation + +- [Media Products](../product-discovery/media-products.md) - Product model reference +- [Creating Media Buys](../task-reference/create_media_buy.md) - How to select pricing when buying +- [Delivery Reporting](../task-reference/get_media_buy_delivery.md) - Understanding metrics by pricing model +- [Glossary](../../reference/glossary.md) - Pricing and metric definitions diff --git a/docs/media-buy/product-discovery/media-products.md b/docs/media-buy/product-discovery/media-products.md index e7cff2090e..9c13dc39fb 100644 --- a/docs/media-buy/product-discovery/media-products.md +++ b/docs/media-buy/product-discovery/media-products.md @@ -13,42 +13,90 @@ A **Product** is the core sellable unit in AdCP. This document details the Produ - `description` (string, required) - `formats` (list[Format], required): See [Creative Formats](../../creative/formats.md). - `delivery_type` (string, required): Either `"guaranteed"` or `"non_guaranteed"`. -- `is_fixed_price` (bool, required): `true` if the price is fixed, `false` if it is auction-based. -- `cpm` (float, optional): The fixed Cost Per Mille. **Required** if `is_fixed_price` is `true`. -- `price_guidance` (PriceGuidance, optional): Pricing guidance for auction-based products. **Required** if `is_fixed_price` is `false`. -- `min_spend` (float, optional): Minimum budget requirement in USD. +- `pricing_options` (list[PricingOption], required): Array of available pricing models for this product. See [Pricing Models](#pricing-models). - `measurement` (Measurement, optional): Included measurement capabilities. 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. +### Deprecated Fields + +- `is_fixed_price` (bool): **DEPRECATED in v1.7.0** - Use `pricing_options` instead. +- `cpm` (float): **DEPRECATED in v1.7.0** - Use `pricing_options` instead. +- `min_spend` (float): **DEPRECATED in v1.7.0** - Use `pricing_options[].min_spend` instead. + ### Pricing Models -AdCP supports two pricing models, determined by the `is_fixed_price` flag. +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 -#### Guaranteed, Fixed-Price Products -These products represent reserved inventory with a predictable price and delivery. -- `delivery_type`: `"guaranteed"` -- `is_fixed_price`: `true` -- `cpm`: A fixed float value (e.g., `45.00`). +- **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 Action) - Cost per conversion/acquisition +- **CPL** (Cost Per Lead) - Cost per lead generated +- **CPP** (Cost Per Point) - Cost per Gross Rating Point (TV/audio) +- **Flat Rate** - Fixed cost regardless of delivery volume + +#### PricingOption Structure + +Each pricing option includes: +```json +{ + "pricing_option_id": "cpcv_usd_guaranteed", + "pricing_model": "cpcv", + "rate": 0.15, + "currency": "USD", + "is_fixed": true, + "parameters": { + "view_threshold": 1.0 + }, + "min_spend_per_package": 5000 +} +``` + +For auction-based pricing (`is_fixed: false`), include `price_guidance`: +```json +{ + "pricing_option_id": "cpm_usd_auction", + "pricing_model": "cpm", + "currency": "USD", + "is_fixed": false, + "price_guidance": { + "floor": 10.00, + "p25": 12.50, + "p50": 15.00, + "p75": 18.00, + "p90": 22.00 + } +} +``` -#### Non-Guaranteed, Variable-Price Products -These products represent inventory available in an auction. The final price is not fixed. -- `delivery_type`: `"non_guaranteed"` -- `is_fixed_price`: `false` -- `price_guidance`: A `PriceGuidance` object that helps the buyer make an informed bid. +#### Delivery Measurement (Required) -**Example `PriceGuidance`:** +All products MUST declare their measurement provider: ```json { - "floor": 10.00, - "p25": 12.50, - "p50": 15.00, - "p75": 18.00, - "p90": 22.00 + "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"` + ### Measurement Object For products that include measurement (common in retail media): @@ -80,39 +128,93 @@ A server can offer a general catalog, but it can also return: ## Product Examples -### Standard Product +### Standard CTV Product (Multiple Pricing Options) ```json { "product_id": "connected_tv_prime", "name": "Connected TV - Prime Time", "description": "Premium CTV inventory 8PM-11PM", - "format_ids": ["video_standard"], + "format_ids": ["video_15s", "video_30s"], "delivery_type": "guaranteed", - "is_fixed_price": true, - "cpm": 45.00 + "pricing_options": [ + { + "pricing_option_id": "cpm_usd_guaranteed", + "pricing_model": "cpm", + "rate": 45.00, + "currency": "USD", + "is_fixed": true, + "min_spend_per_package": 10000 + }, + { + "pricing_option_id": "cpcv_usd_guaranteed", + "pricing_model": "cpcv", + "rate": 0.18, + "currency": "USD", + "is_fixed": true, + "min_spend_per_package": 10000 + }, + { + "pricing_option_id": "cpp_usd_p18-49", + "pricing_model": "cpp", + "rate": 250.00, + "currency": "USD", + "is_fixed": true, + "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." + } } ``` -### Custom Product +### Auction-Based Display Product ```json { "product_id": "custom_abc123", "name": "Custom - Gaming Enthusiasts", "description": "Custom audience package for gaming campaign", - "format_ids": ["display_300x250"], + "format_ids": ["display_300x250", "display_728x90"], "delivery_type": "non_guaranteed", - "is_fixed_price": false, - "price_guidance": { - "floor": 5.00, - "p50": 8.00, - "p75": 12.00 + "pricing_options": [ + { + "pricing_option_id": "cpm_usd_auction", + "pricing_model": "cpm", + "currency": "USD", + "is_fixed": false, + "price_guidance": { + "floor": 5.00, + "p50": 8.00, + "p75": 12.00 + } + }, + { + "pricing_option_id": "cpc_usd_auction", + "pricing_model": "cpc", + "currency": "USD", + "is_fixed": false, + "price_guidance": { + "floor": 0.50, + "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": "2024-02-15T00:00:00Z" + "expires_at": "2025-02-15T00:00:00Z" } ``` -### Retail Media Product +### Retail Media Product with Measurement ```json { "product_id": "albertsons_pet_category_offsite", @@ -120,13 +222,24 @@ A server can offer a general catalog, but it can also return: "description": "Target Albertsons shoppers who have purchased pet products in the last 90 days. Reach them across premium display and video inventory.", "format_ids": [ "display_300x250", - "display_728x90", - "video_15s_vast" + "display_728x90", + "video_15s" ], "delivery_type": "guaranteed", - "is_fixed_price": true, - "cpm": 13.50, - "min_spend": 10000, + "pricing_options": [ + { + "pricing_option_id": "cpm_usd_guaranteed", + "pricing_model": "cpm", + "rate": 13.50, + "currency": "USD", + "is_fixed": true, + "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." + }, "measurement": { "type": "incremental_sales_lift", "attribution": "deterministic_purchase", diff --git a/docs/reference/glossary.md b/docs/reference/glossary.md index 03628b84a2..daabe732f8 100644 --- a/docs/reference/glossary.md +++ b/docs/reference/glossary.md @@ -30,34 +30,71 @@ Classification of signals as "marketplace" (third-party), "owned" (first-party), ## C -**CPM (Cost Per Mille)** -Pricing model based on cost per thousand impressions. +**CPC (Cost Per Click)** +Pricing model based on cost per click on the advertisement. -**Customer Account** +**CPCV (Cost Per Completed View)** +Pricing model based on cost per 100% video or audio completion. + +**CPM (Cost Per Mille)** +Pricing model based on cost per thousand impressions. Traditional display advertising pricing. + +**CPP (Cost Per Point)** +Pricing model based on cost per Gross Rating Point (GRP), commonly used in TV and audio advertising. + +**CPV (Cost Per View)** +Pricing model based on cost per view at a publisher-defined threshold (e.g., 50% video completion). + +**Completed View** +A video or audio ad that has been viewed to 100% completion. Used for CPCV pricing and completion rate metrics. + +**Completion Rate** +The percentage of video or audio ads that are viewed to 100% completion (completed_views / impressions). + +**Customer Account** Direct advertiser or agency account with specific seat access and negotiated rates. ## D -**Deployment** (Signals Protocol) +**Daypart** +Specific time-of-day segment for time-based advertising, commonly used in DOOH (e.g., morning_commute, evening_prime, overnight). + +**Deployment** (Signals Protocol) The availability status of a signal on specific platforms, including activation state and timing. -**Devices** (Signals Protocol) +**Devices** (Signals Protocol) Size unit representing unique device identifiers (cookies, mobile IDs) - typically the largest reach metric. -**Device Type** (Media Buy Protocol) +**Device Type** (Media Buy Protocol) Targeting dimension for platform types: mobile, desktop, tablet, CTV, audio, DOOH. -**DSP (Demand-Side Platform)** +**DOOH (Digital Out-of-Home)** +Digital advertising displayed on screens in public spaces such as billboards, transit stations, airports, and retail locations. Uses CPM or flat_rate pricing with parameters for SOV, duration, and venue targeting. + +**DSP (Demand-Side Platform)** Technology platform that allows advertisers to buy advertising inventory programmatically. ## E -**Estimated Activation Time** +**Estimated Activation Time** Predicted timeframe for signal deployment, typically 24-48 hours for new activations. +## F + +**Flat Rate** +Fixed-cost pricing model where a single payment is made regardless of delivery volume. Common for sponsorships and takeovers. + +**Frequency** +The average number of times an individual is exposed to an advertisement during a campaign. + +## G + +**GRP (Gross Rating Point)** +A unit of measurement for television and radio advertising representing 1% of the target audience. Used in CPP pricing models. Total GRPs = Reach % × Average Frequency. Measured by third-party providers such as Nielsen, Comscore, iSpot.tv, or Triton Digital. + ## H -**Households** +**Households** Size unit representing unique household addresses, useful for geographic and family-based targeting. ## I @@ -91,47 +128,81 @@ First-party signal data belonging to the advertiser or platform. ## P -**Platform Account** +**Platform Account** Master account representing an advertising platform that can syndicate signals to multiple customers. -**Prompt** +**Pricing Model** +The method by which advertising inventory is priced and billed. AdCP supports CPM, CPC, CPCV, CPV, CPA, CPL, CPP, and flat rate models. + +**Pricing Option** +A specific pricing model offered by a publisher for a product, including rate, currency, and parameters. + +**Prompt** Natural language description used to discover relevant signals (e.g., "high-income sports enthusiasts", "premium automotive content", "users in urban areas during evening hours"). -**Provider** +**Provider** The company or platform that supplies signal data (e.g., LiveRamp, Experian, Peer39, weather services). +## Q + +**Quartile (Video)** +Milestones in video ad viewing: Q1 (25% viewed), Q2 (50% viewed), Q3 (75% viewed), Q4 (100% complete). Used to measure video engagement. + ## R -**Relevance Score** +**Reach** +The number or percentage of unique individuals exposed to an advertisement at least once during a campaign. + +**Relevance Score** Numerical rating (0-1) indicating how well a signal matches the discovery prompt. -**Relevance Rationale** +**Relevance Rationale** Human-readable explanation of why an audience received its relevance score. -**Revenue Share** +**Revenue Share** Pricing model based on a percentage of media spend rather than fixed CPM. ## S -**Seat** +**Screen Time** +Total duration an ad was displayed across all DOOH screens, measured in seconds. Used for DOOH delivery reporting. + +**Seat** A specific advertising account within a platform, typically representing a brand or campaign. -**Segment ID** +**Segment ID** The specific identifier used for signal activation, may differ from signal_id. -**Size Unit** (Signals Protocol) +**Share of Voice (SOV)** +Percentage of available ad inventory allocated to a specific advertiser in DOOH loops. Expressed as 0.0-1.0 (e.g., 0.15 = 15% SOV). + +**Size Unit** (Signals Protocol) The measurement type for signal size: individuals, devices, or households. ## T -**Third-Party Signal** +**Takeover** +Exclusive 100% share of voice placement on DOOH inventory for a specific time period. Priced as flat_rate with sov_percentage: 100. + +**Third-Party Signal** Signal data licensed from external providers, also known as marketplace signals. +**Time-Based Pricing** +Pricing structure based on duration (hourly, daily, or daypart) rather than impressions. Common in DOOH advertising using flat_rate model. + ## U -**Usage Reporting** +**Usage Reporting** Daily reporting of signal utilization for billing and optimization purposes. +## V + +**Venue** +Physical location where DOOH advertising is displayed (e.g., airport terminal, transit station, retail store). Used for DOOH targeting and delivery reporting. + +**Venue Package** +Named collection of DOOH screens across specific venues (e.g., 'times_square_network', 'airport_terminals'). Used in DOOH pricing parameters. + ## B **Budget** @@ -152,9 +223,15 @@ Protocol feature allowing publishers to require manual approval for operations. ## L -**Line Item** +**Line Item** The basic unit of inventory in ad servers like Google Ad Manager, represented as packages in AdCP. +**Loop Duration** +The length of time for a complete rotation of ads in a DOOH display, measured in seconds. Used to calculate frequency and share of voice. + +**Loop Plays** +The number of times an ad was displayed in a DOOH loop rotation. Key metric for DOOH delivery reporting. + ## M **MCP (Model Context Protocol)** diff --git a/static/schemas/README.md b/static/schemas/README.md index a64d16f685..5cb594646c 100644 --- a/static/schemas/README.md +++ b/static/schemas/README.md @@ -21,7 +21,6 @@ schemas/ │ │ ├── package.json │ │ ├── creative-asset.json │ │ ├── targeting.json -│ │ ├── budget.json │ │ ├── frequency-cap.json │ │ ├── format.json │ │ ├── measurement.json diff --git a/static/schemas/v1/core/budget.json b/static/schemas/v1/core/budget.json deleted file mode 100644 index 2677034603..0000000000 --- a/static/schemas/v1/core/budget.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "/schemas/v1/core/budget.json", - "title": "Budget", - "description": "Budget configuration for a media buy or package", - "type": "object", - "properties": { - "total": { - "type": "number", - "description": "Total budget amount", - "minimum": 0 - }, - "currency": { - "type": "string", - "description": "ISO 4217 currency code", - "pattern": "^[A-Z]{3}$", - "examples": ["USD", "EUR", "GBP"] - }, - "pacing": { - "$ref": "/schemas/v1/enums/pacing.json" - } - }, - "required": ["total", "currency"], - "additionalProperties": false -} \ No newline at end of file diff --git a/static/schemas/v1/core/delivery-metrics.json b/static/schemas/v1/core/delivery-metrics.json new file mode 100644 index 0000000000..cd168b2289 --- /dev/null +++ b/static/schemas/v1/core/delivery-metrics.json @@ -0,0 +1,173 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/v1/core/delivery-metrics.json", + "title": "Delivery Metrics", + "description": "Standard delivery metrics that can be reported at media buy, package, or creative level", + "type": "object", + "properties": { + "impressions": { + "type": "number", + "description": "Impressions delivered", + "minimum": 0 + }, + "spend": { + "type": "number", + "description": "Amount spent", + "minimum": 0 + }, + "clicks": { + "type": "number", + "description": "Total clicks", + "minimum": 0 + }, + "ctr": { + "type": "number", + "description": "Click-through rate (clicks/impressions)", + "minimum": 0, + "maximum": 1 + }, + "views": { + "type": "number", + "description": "Views at threshold (for CPV)", + "minimum": 0 + }, + "completed_views": { + "type": "number", + "description": "100% completions (for CPCV)", + "minimum": 0 + }, + "video_completions": { + "type": "number", + "description": "DEPRECATED: Use completed_views instead", + "minimum": 0 + }, + "completion_rate": { + "type": "number", + "description": "Completion rate (completed_views/impressions)", + "minimum": 0, + "maximum": 1 + }, + "conversions": { + "type": "number", + "description": "Conversions (reserved for future CPA pricing support)", + "minimum": 0 + }, + "leads": { + "type": "number", + "description": "Leads generated (reserved for future CPL pricing support)", + "minimum": 0 + }, + "grps": { + "type": "number", + "description": "Gross Rating Points delivered (for CPP)", + "minimum": 0 + }, + "reach": { + "type": "number", + "description": "Unique reach - units depend on measurement provider (e.g., individuals, households, devices, cookies). See delivery_measurement.provider for methodology.", + "minimum": 0 + }, + "frequency": { + "type": "number", + "description": "Average frequency per individual (typically measured over campaign duration, but can vary by measurement provider)", + "minimum": 0 + }, + "quartile_data": { + "type": "object", + "description": "Video quartile completion data", + "properties": { + "q1_views": { + "type": "number", + "description": "25% completion views", + "minimum": 0 + }, + "q2_views": { + "type": "number", + "description": "50% completion views", + "minimum": 0 + }, + "q3_views": { + "type": "number", + "description": "75% completion views", + "minimum": 0 + }, + "q4_views": { + "type": "number", + "description": "100% completion views", + "minimum": 0 + } + } + }, + "dooh_metrics": { + "type": "object", + "description": "DOOH-specific metrics (only included for DOOH campaigns)", + "properties": { + "loop_plays": { + "type": "integer", + "description": "Number of times ad played in rotation", + "minimum": 0 + }, + "screens_used": { + "type": "integer", + "description": "Number of unique screens displaying the ad", + "minimum": 0 + }, + "screen_time_seconds": { + "type": "integer", + "description": "Total display time in seconds", + "minimum": 0 + }, + "sov_achieved": { + "type": "number", + "description": "Actual share of voice delivered (0.0 to 1.0)", + "minimum": 0, + "maximum": 1 + }, + "calculation_notes": { + "type": "string", + "description": "Explanation of how DOOH impressions were calculated" + }, + "venue_breakdown": { + "type": "array", + "description": "Per-venue performance breakdown", + "items": { + "type": "object", + "properties": { + "venue_id": { + "type": "string", + "description": "Venue identifier" + }, + "venue_name": { + "type": "string", + "description": "Human-readable venue name" + }, + "venue_type": { + "type": "string", + "description": "Venue type (e.g., 'airport', 'transit', 'retail', 'billboard')" + }, + "impressions": { + "type": "integer", + "description": "Impressions delivered at this venue", + "minimum": 0 + }, + "loop_plays": { + "type": "integer", + "description": "Loop plays at this venue", + "minimum": 0 + }, + "screens_used": { + "type": "integer", + "description": "Number of screens used at this venue", + "minimum": 0 + } + }, + "required": ["venue_id", "impressions"], + "additionalProperties": false + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": true +} diff --git a/static/schemas/v1/core/package.json b/static/schemas/v1/core/package.json index 8d24b94194..602e83e55f 100644 --- a/static/schemas/v1/core/package.json +++ b/static/schemas/v1/core/package.json @@ -18,7 +18,9 @@ "description": "ID of the product this package is based on" }, "budget": { - "$ref": "/schemas/v1/core/budget.json" + "type": "number", + "description": "Budget allocation for this package in the currency specified by the pricing option", + "minimum": 0 }, "impressions": { "type": "number", diff --git a/static/schemas/v1/core/pricing-option.json b/static/schemas/v1/core/pricing-option.json new file mode 100644 index 0000000000..7e88efc154 --- /dev/null +++ b/static/schemas/v1/core/pricing-option.json @@ -0,0 +1,29 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/v1/core/pricing-option.json", + "title": "Pricing Option", + "description": "A pricing model option offered by a publisher for a product. Each pricing model has its own schema with model-specific requirements.", + "oneOf": [ + { + "$ref": "/schemas/v1/pricing-options/cpm-fixed-option.json" + }, + { + "$ref": "/schemas/v1/pricing-options/cpm-auction-option.json" + }, + { + "$ref": "/schemas/v1/pricing-options/cpc-option.json" + }, + { + "$ref": "/schemas/v1/pricing-options/cpcv-option.json" + }, + { + "$ref": "/schemas/v1/pricing-options/cpv-option.json" + }, + { + "$ref": "/schemas/v1/pricing-options/cpp-option.json" + }, + { + "$ref": "/schemas/v1/pricing-options/flat-rate-option.json" + } + ] +} diff --git a/static/schemas/v1/core/product.json b/static/schemas/v1/core/product.json index a87bc3f2f7..f355deca84 100644 --- a/static/schemas/v1/core/product.json +++ b/static/schemas/v1/core/product.json @@ -46,44 +46,37 @@ "delivery_type": { "$ref": "/schemas/v1/enums/delivery-type.json" }, - "is_fixed_price": { - "type": "boolean", - "description": "Whether this product has fixed pricing (true) or uses auction (false)" - }, - "cpm": { - "type": "number", - "description": "Cost per thousand impressions", - "minimum": 0 - }, - "currency": { - "type": "string", - "description": "ISO 4217 currency code", - "pattern": "^[A-Z]{3}$", - "examples": ["USD", "EUR", "GBP"] - }, - "min_spend": { - "type": "number", - "description": "Minimum budget requirement", - "minimum": 0 + "pricing_options": { + "type": "array", + "description": "Available pricing models for this product", + "items": { + "$ref": "/schemas/v1/core/pricing-option.json" + }, + "minItems": 1 }, "estimated_exposures": { "type": "integer", "description": "Estimated exposures/impressions for guaranteed products", "minimum": 0 }, - "floor_cpm": { - "type": "number", - "description": "Minimum CPM for non-guaranteed products (bids below this are rejected)", - "minimum": 0 - }, - "recommended_cpm": { - "type": "number", - "description": "Recommended CPM to achieve min_exposures target for non-guaranteed products", - "minimum": 0 - }, "measurement": { "$ref": "/schemas/v1/core/measurement.json" }, + "delivery_measurement": { + "type": "object", + "description": "Measurement provider and methodology for delivery metrics. The buyer accepts the declared provider as the source of truth for the buy. REQUIRED for all products.", + "properties": { + "provider": { + "type": "string", + "description": "Measurement provider(s) used for this product (e.g., 'Google Ad Manager with IAS viewability', 'Nielsen DAR', 'Geopath for DOOH impressions')" + }, + "notes": { + "type": "string", + "description": "Additional details about measurement methodology in plain language (e.g., 'MRC-accredited viewability. 50% in-view for 1s display / 2s video', 'Panel-based demographic measurement updated monthly')" + } + }, + "required": ["provider"] + }, "reporting_capabilities": { "$ref": "/schemas/v1/core/reporting-capabilities.json" }, @@ -118,7 +111,8 @@ "description", "format_ids", "delivery_type", - "is_fixed_price" + "delivery_measurement", + "pricing_options" ], "additionalProperties": false } \ No newline at end of file diff --git a/static/schemas/v1/enums/pricing-model.json b/static/schemas/v1/enums/pricing-model.json new file mode 100644 index 0000000000..822e72f66a --- /dev/null +++ b/static/schemas/v1/enums/pricing-model.json @@ -0,0 +1,23 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/v1/enums/pricing-model.json", + "title": "Pricing Model", + "description": "Supported pricing models for advertising products", + "type": "string", + "enum": [ + "cpm", + "cpc", + "cpcv", + "cpv", + "cpp", + "flat_rate" + ], + "enumDescriptions": { + "cpm": "Cost Per Mille - cost per 1,000 impressions", + "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 (e.g., 50% completion)", + "cpp": "Cost Per Point - cost per Gross Rating Point or Target Rating Point (TV/audio)", + "flat_rate": "Flat Rate - fixed cost regardless of delivery volume (sponsorships, takeovers)" + } +} diff --git a/static/schemas/v1/index.json b/static/schemas/v1/index.json index 38d4b5231e..f58c42edd0 100644 --- a/static/schemas/v1/index.json +++ b/static/schemas/v1/index.json @@ -4,12 +4,12 @@ "title": "AdCP Schema Registry v1", "version": "1.0.0", "description": "Registry of all AdCP JSON schemas for validation and discovery", - "adcp_version": "1.7.0", + "adcp_version": "1.8.0", "standard_formats_version": "1.0.0", "versioning": { "note": "All request/response schemas include adcp_version field. Compatibility follows semantic versioning rules." }, - "lastUpdated": "2025-10-08", + "lastUpdated": "2025-10-12", "baseUrl": "/schemas/v1", "schemas": { "core": { @@ -35,10 +35,6 @@ "$ref": "/schemas/v1/core/targeting.json", "description": "Audience targeting criteria" }, - "budget": { - "$ref": "/schemas/v1/core/budget.json", - "description": "Budget configuration for a media buy or package" - }, "frequency-cap": { "$ref": "/schemas/v1/core/frequency-cap.json", "description": "Frequency capping settings" @@ -51,6 +47,10 @@ "$ref": "/schemas/v1/core/measurement.json", "description": "Measurement capabilities included with a product" }, + "delivery-metrics": { + "$ref": "/schemas/v1/core/delivery-metrics.json", + "description": "Standard delivery metrics for reporting" + }, "creative-policy": { "$ref": "/schemas/v1/core/creative-policy.json", "description": "Creative requirements and restrictions for a product" @@ -98,12 +98,20 @@ "creative-manifest": { "$ref": "/schemas/v1/core/creative-manifest.json", "description": "Complete specification of a creative with all assets needed for rendering" + }, + "pricing-option": { + "$ref": "/schemas/v1/core/pricing-option.json", + "description": "A pricing model option offered by a publisher for a product" } } }, "enums": { "description": "Enumerated types and constants", "schemas": { + "pricing-model": { + "$ref": "/schemas/v1/enums/pricing-model.json", + "description": "Supported pricing models for advertising products" + }, "delivery-type": { "$ref": "/schemas/v1/enums/delivery-type.json", "description": "Type of inventory delivery" @@ -146,6 +154,39 @@ } } }, + "pricing-options": { + "description": "Individual pricing model schemas with model-specific validation. CPM supports both fixed and auction pricing; all other models are fixed-rate only.", + "schemas": { + "cpm-fixed-option": { + "$ref": "/schemas/v1/pricing-options/cpm-fixed-option.json", + "description": "Cost Per Mille (CPM) fixed-rate pricing for direct/guaranteed deals" + }, + "cpm-auction-option": { + "$ref": "/schemas/v1/pricing-options/cpm-auction-option.json", + "description": "Cost Per Mille (CPM) auction-based pricing for programmatic/non-guaranteed inventory" + }, + "cpc-option": { + "$ref": "/schemas/v1/pricing-options/cpc-option.json", + "description": "Cost Per Click (CPC) fixed-rate pricing for performance campaigns" + }, + "cpcv-option": { + "$ref": "/schemas/v1/pricing-options/cpcv-option.json", + "description": "Cost Per Completed View (CPCV) fixed-rate pricing for video/audio" + }, + "cpv-option": { + "$ref": "/schemas/v1/pricing-options/cpv-option.json", + "description": "Cost Per View (CPV) fixed-rate pricing with threshold" + }, + "cpp-option": { + "$ref": "/schemas/v1/pricing-options/cpp-option.json", + "description": "Cost Per Point (CPP) fixed-rate pricing for TV/audio with demographic measurement" + }, + "flat-rate-option": { + "$ref": "/schemas/v1/pricing-options/flat-rate-option.json", + "description": "Flat rate pricing for DOOH and sponsorships" + } + } + }, "media-buy": { "description": "Media buy task request/response schemas", "supporting-schemas": { diff --git a/static/schemas/v1/media-buy/create-media-buy-request.json b/static/schemas/v1/media-buy/create-media-buy-request.json index 1285ff1137..af6a771e1d 100644 --- a/static/schemas/v1/media-buy/create-media-buy-request.json +++ b/static/schemas/v1/media-buy/create-media-buy-request.json @@ -43,7 +43,9 @@ "description": "Campaign end date/time in ISO 8601 format" }, "budget": { - "$ref": "/schemas/v1/core/budget.json" + "type": "number", + "description": "Total budget for this media buy. Currency is determined by the pricing_option_id selected in each package.", + "minimum": 0 }, "reporting_webhook": { "allOf": [ diff --git a/static/schemas/v1/media-buy/get-media-buy-delivery-response.json b/static/schemas/v1/media-buy/get-media-buy-delivery-response.json index 99c3cd5136..757016ef28 100644 --- a/static/schemas/v1/media-buy/get-media-buy-delivery-response.json +++ b/static/schemas/v1/media-buy/get-media-buy-delivery-response.json @@ -122,88 +122,57 @@ "type": "boolean", "description": "Indicates this delivery contains updated data for a previously reported period. Buyer should replace previous period data with these totals." }, + "pricing_model": { + "$ref": "/schemas/v1/enums/pricing-model.json", + "description": "Pricing model used for this media buy" + }, "totals": { - "type": "object", - "description": "Aggregate metrics for this media buy across all packages", - "properties": { - "impressions": { - "type": "number", - "description": "Total impressions delivered", - "minimum": 0 - }, - "spend": { - "type": "number", - "description": "Total amount spent", - "minimum": 0 - }, - "clicks": { - "type": "number", - "description": "Total clicks (if applicable)", - "minimum": 0 - }, - "ctr": { - "type": "number", - "description": "Click-through rate (clicks/impressions)", - "minimum": 0, - "maximum": 1 + "allOf": [ + { + "$ref": "/schemas/v1/core/delivery-metrics.json" }, - "video_completions": { - "type": "number", - "description": "Total video completions (if applicable)", - "minimum": 0 - }, - "completion_rate": { - "type": "number", - "description": "Video completion rate (completions/impressions)", - "minimum": 0, - "maximum": 1 + { + "type": "object", + "description": "Aggregate metrics for this media buy across all packages", + "properties": { + "effective_rate": { + "type": "number", + "description": "Effective rate paid per unit based on pricing_model (e.g., actual CPM for 'cpm', actual cost per completed view for 'cpcv', actual cost per point for 'cpp')", + "minimum": 0 + } + }, + "required": ["spend"] } - }, - "required": ["impressions", "spend"], - "additionalProperties": false + ] }, "by_package": { "type": "array", "description": "Metrics broken down by package", "items": { - "type": "object", - "properties": { - "package_id": { - "type": "string", - "description": "Publisher's package identifier" - }, - "buyer_ref": { - "type": "string", - "description": "Buyer's reference identifier for this package" - }, - "impressions": { - "type": "number", - "description": "Package impressions", - "minimum": 0 + "allOf": [ + { + "$ref": "/schemas/v1/core/delivery-metrics.json" }, - "spend": { - "type": "number", - "description": "Package spend", - "minimum": 0 - }, - "clicks": { - "type": "number", - "description": "Package clicks", - "minimum": 0 - }, - "video_completions": { - "type": "number", - "description": "Package video completions", - "minimum": 0 - }, - "pacing_index": { - "type": "number", - "description": "Delivery pace (1.0 = on track, <1.0 = behind, >1.0 = ahead)", - "minimum": 0 + { + "type": "object", + "properties": { + "package_id": { + "type": "string", + "description": "Publisher's package identifier" + }, + "buyer_ref": { + "type": "string", + "description": "Buyer's reference identifier for this package" + }, + "pacing_index": { + "type": "number", + "description": "Delivery pace (1.0 = on track, <1.0 = behind, >1.0 = ahead)", + "minimum": 0 + } + }, + "required": ["package_id", "spend"] } - }, - "required": ["package_id", "impressions", "spend"], - "additionalProperties": false + ] } }, "daily_breakdown": { diff --git a/static/schemas/v1/media-buy/package-request.json b/static/schemas/v1/media-buy/package-request.json index 0ae7cfcc47..2f22e97332 100644 --- a/static/schemas/v1/media-buy/package-request.json +++ b/static/schemas/v1/media-buy/package-request.json @@ -3,104 +3,64 @@ "$id": "/schemas/v1/media-buy/package-request.json", "title": "Package Request", "description": "Package configuration for media buy creation", - "oneOf": [ - { - "type": "object", - "description": "Package with explicit format IDs", - "properties": { - "buyer_ref": { - "type": "string", - "description": "Buyer's reference identifier for this package" - }, - "product_id": { - "type": "string", - "description": "Product ID for this package (recommended - replaces deprecated products array)" - }, - "products": { - "type": "array", - "description": "DEPRECATED: Use product_id instead. Array of product IDs - only first product will be used", - "deprecated": true, - "items": { - "type": "string" - } - }, - "format_ids": { - "type": "array", - "description": "Array of format IDs that will be used for this package - must be supported by the product", - "items": { - "type": "string", - "description": "Format ID referencing a format from list_creative_formats" - } - }, - "budget": { - "$ref": "/schemas/v1/core/budget.json" - }, - "targeting_overlay": { - "$ref": "/schemas/v1/core/targeting.json" - }, - "creative_ids": { - "type": "array", - "description": "Creative IDs to assign to this package at creation time", - "items": { - "type": "string" - } - } - }, - "anyOf": [ - {"required": ["buyer_ref", "product_id", "format_ids"]}, - {"required": ["buyer_ref", "products", "format_ids"]} - ], - "not": { - "required": ["format_selection"] - }, - "additionalProperties": false + "type": "object", + "properties": { + "buyer_ref": { + "type": "string", + "description": "Buyer's reference identifier for this package" }, - { - "type": "object", - "description": "Package with dynamic format selection", - "properties": { - "buyer_ref": { - "type": "string", - "description": "Buyer's reference identifier for this package" - }, - "product_id": { - "type": "string", - "description": "Product ID for this package (recommended - replaces deprecated products array)" - }, - "products": { - "type": "array", - "description": "DEPRECATED: Use product_id instead. Array of product IDs - only first product will be used", - "deprecated": true, - "items": { - "type": "string" - } - }, - "format_selection": { - "type": "object", - "description": "Dynamic format selection criteria" - }, - "budget": { - "$ref": "/schemas/v1/core/budget.json" - }, - "targeting_overlay": { - "$ref": "/schemas/v1/core/targeting.json" - }, - "creative_ids": { - "type": "array", - "description": "Creative IDs to assign to this package at creation time", - "items": { - "type": "string" - } - } - }, - "anyOf": [ - {"required": ["buyer_ref", "product_id", "format_selection"]}, - {"required": ["buyer_ref", "products", "format_selection"]} - ], - "not": { - "required": ["format_ids"] + "product_id": { + "type": "string", + "description": "Product ID for this package (recommended - replaces deprecated products array)" + }, + "products": { + "type": "array", + "description": "DEPRECATED: Use product_id instead. Array of product IDs - only first product will be used", + "deprecated": true, + "items": { + "type": "string" + } + }, + "format_ids": { + "type": "array", + "description": "Array of format IDs that will be used for this package - must be supported by the product. If omitted, defaults to all formats supported by the product.", + "items": { + "type": "string", + "description": "Format ID referencing a format from list_creative_formats" }, - "additionalProperties": false + "minItems": 1 + }, + "budget": { + "type": "number", + "description": "Budget allocation for this package in the media buy's currency", + "minimum": 0 + }, + "pacing": { + "$ref": "/schemas/v1/enums/pacing.json" + }, + "pricing_option_id": { + "type": "string", + "description": "ID of the selected pricing option from the product's pricing_options array" + }, + "bid_price": { + "type": "number", + "description": "Bid price for auction-based CPM pricing (required if using cpm-auction-option)", + "minimum": 0 + }, + "targeting_overlay": { + "$ref": "/schemas/v1/core/targeting.json" + }, + "creative_ids": { + "type": "array", + "description": "Creative IDs to assign to this package at creation time", + "items": { + "type": "string" + } } - ] + }, + "anyOf": [ + {"required": ["buyer_ref", "product_id", "budget", "pricing_option_id"]}, + {"required": ["buyer_ref", "products", "budget", "pricing_option_id"]} + ], + "additionalProperties": false } diff --git a/static/schemas/v1/media-buy/update-media-buy-request.json b/static/schemas/v1/media-buy/update-media-buy-request.json index 20631ef624..cce04138c1 100644 --- a/static/schemas/v1/media-buy/update-media-buy-request.json +++ b/static/schemas/v1/media-buy/update-media-buy-request.json @@ -32,7 +32,9 @@ "description": "New end date/time in ISO 8601 format" }, "budget": { - "$ref": "/schemas/v1/core/budget.json" + "type": "number", + "description": "Updated total budget for this media buy. Currency is determined by the pricing_option_id selected in each package.", + "minimum": 0 }, "packages": { "type": "array", @@ -49,7 +51,9 @@ "description": "Buyer's reference for the package to update" }, "budget": { - "$ref": "/schemas/v1/core/budget.json" + "type": "number", + "description": "Updated budget allocation for this package in the currency specified by the pricing option", + "minimum": 0 }, "active": { "type": "boolean", diff --git a/static/schemas/v1/pricing-options/cpc-option.json b/static/schemas/v1/pricing-options/cpc-option.json new file mode 100644 index 0000000000..0114da7541 --- /dev/null +++ b/static/schemas/v1/pricing-options/cpc-option.json @@ -0,0 +1,36 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/v1/pricing-options/cpc-option.json", + "title": "CPC Pricing Option", + "description": "Cost Per Click fixed-rate pricing for performance-driven advertising campaigns", + "type": "object", + "properties": { + "pricing_option_id": { + "type": "string", + "description": "Unique identifier for this pricing option within the product (e.g., 'cpc_usd_fixed')" + }, + "pricing_model": { + "type": "string", + "const": "cpc", + "description": "Cost per click" + }, + "rate": { + "type": "number", + "description": "Fixed CPC rate (cost per click)", + "minimum": 0 + }, + "currency": { + "type": "string", + "description": "ISO 4217 currency code", + "pattern": "^[A-Z]{3}$", + "examples": ["USD", "EUR", "GBP", "JPY"] + }, + "min_spend_per_package": { + "type": "number", + "description": "Minimum spend requirement per package using this pricing option, in the specified currency", + "minimum": 0 + } + }, + "required": ["pricing_option_id", "pricing_model", "rate", "currency"], + "additionalProperties": false +} diff --git a/static/schemas/v1/pricing-options/cpcv-option.json b/static/schemas/v1/pricing-options/cpcv-option.json new file mode 100644 index 0000000000..f6d0e5e4f7 --- /dev/null +++ b/static/schemas/v1/pricing-options/cpcv-option.json @@ -0,0 +1,36 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/v1/pricing-options/cpcv-option.json", + "title": "CPCV Pricing Option", + "description": "Cost Per Completed View (100% video/audio completion) fixed-rate pricing", + "type": "object", + "properties": { + "pricing_option_id": { + "type": "string", + "description": "Unique identifier for this pricing option within the product (e.g., 'cpcv_usd_guaranteed')" + }, + "pricing_model": { + "type": "string", + "const": "cpcv", + "description": "Cost per completed view (100% completion)" + }, + "rate": { + "type": "number", + "description": "Fixed CPCV rate (cost per 100% completion)", + "minimum": 0 + }, + "currency": { + "type": "string", + "description": "ISO 4217 currency code", + "pattern": "^[A-Z]{3}$", + "examples": ["USD", "EUR", "GBP", "JPY"] + }, + "min_spend_per_package": { + "type": "number", + "description": "Minimum spend requirement per package using this pricing option, in the specified currency", + "minimum": 0 + } + }, + "required": ["pricing_option_id", "pricing_model", "rate", "currency"], + "additionalProperties": false +} diff --git a/static/schemas/v1/pricing-options/cpm-auction-option.json b/static/schemas/v1/pricing-options/cpm-auction-option.json new file mode 100644 index 0000000000..4003e8f8a1 --- /dev/null +++ b/static/schemas/v1/pricing-options/cpm-auction-option.json @@ -0,0 +1,63 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/v1/pricing-options/cpm-auction-option.json", + "title": "CPM Auction Pricing Option", + "description": "Cost Per Mille (cost per 1,000 impressions) with auction-based pricing - common for programmatic/non-guaranteed inventory", + "type": "object", + "properties": { + "pricing_option_id": { + "type": "string", + "description": "Unique identifier for this pricing option within the product (e.g., 'cpm_usd_auction')" + }, + "pricing_model": { + "type": "string", + "const": "cpm", + "description": "Cost per 1,000 impressions" + }, + "currency": { + "type": "string", + "description": "ISO 4217 currency code", + "pattern": "^[A-Z]{3}$", + "examples": ["USD", "EUR", "GBP", "JPY"] + }, + "price_guidance": { + "type": "object", + "description": "Pricing guidance for auction-based CPM bidding", + "properties": { + "floor": { + "type": "number", + "description": "Minimum bid price - publisher will reject bids under this value", + "minimum": 0 + }, + "p25": { + "type": "number", + "description": "25th percentile winning price", + "minimum": 0 + }, + "p50": { + "type": "number", + "description": "Median winning price", + "minimum": 0 + }, + "p75": { + "type": "number", + "description": "75th percentile winning price", + "minimum": 0 + }, + "p90": { + "type": "number", + "description": "90th percentile winning price", + "minimum": 0 + } + }, + "required": ["floor"] + }, + "min_spend_per_package": { + "type": "number", + "description": "Minimum spend requirement per package using this pricing option, in the specified currency", + "minimum": 0 + } + }, + "required": ["pricing_option_id", "pricing_model", "price_guidance", "currency"], + "additionalProperties": false +} diff --git a/static/schemas/v1/pricing-options/cpm-fixed-option.json b/static/schemas/v1/pricing-options/cpm-fixed-option.json new file mode 100644 index 0000000000..87c33c2ba6 --- /dev/null +++ b/static/schemas/v1/pricing-options/cpm-fixed-option.json @@ -0,0 +1,36 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/v1/pricing-options/cpm-fixed-option.json", + "title": "CPM Fixed Rate Pricing Option", + "description": "Cost Per Mille (cost per 1,000 impressions) with guaranteed fixed rate - common for direct/guaranteed deals", + "type": "object", + "properties": { + "pricing_option_id": { + "type": "string", + "description": "Unique identifier for this pricing option within the product (e.g., 'cpm_usd_guaranteed')" + }, + "pricing_model": { + "type": "string", + "const": "cpm", + "description": "Cost per 1,000 impressions" + }, + "rate": { + "type": "number", + "description": "Fixed CPM rate (cost per 1,000 impressions)", + "minimum": 0 + }, + "currency": { + "type": "string", + "description": "ISO 4217 currency code", + "pattern": "^[A-Z]{3}$", + "examples": ["USD", "EUR", "GBP", "JPY"] + }, + "min_spend_per_package": { + "type": "number", + "description": "Minimum spend requirement per package using this pricing option, in the specified currency", + "minimum": 0 + } + }, + "required": ["pricing_option_id", "pricing_model", "rate", "currency"], + "additionalProperties": false +} diff --git a/static/schemas/v1/pricing-options/cpp-option.json b/static/schemas/v1/pricing-options/cpp-option.json new file mode 100644 index 0000000000..5ee9ed0d22 --- /dev/null +++ b/static/schemas/v1/pricing-options/cpp-option.json @@ -0,0 +1,54 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/v1/pricing-options/cpp-option.json", + "title": "CPP Pricing Option", + "description": "Cost Per Point (Gross Rating Point) fixed-rate pricing for TV and audio campaigns requiring demographic measurement", + "type": "object", + "properties": { + "pricing_option_id": { + "type": "string", + "description": "Unique identifier for this pricing option within the product (e.g., 'cpp_usd_p18-49')" + }, + "pricing_model": { + "type": "string", + "const": "cpp", + "description": "Cost per Gross Rating Point" + }, + "rate": { + "type": "number", + "description": "Fixed CPP rate (cost per rating point)", + "minimum": 0 + }, + "currency": { + "type": "string", + "description": "ISO 4217 currency code", + "pattern": "^[A-Z]{3}$", + "examples": ["USD", "EUR", "GBP", "JPY"] + }, + "parameters": { + "type": "object", + "description": "CPP-specific parameters for demographic targeting and GRP requirements", + "properties": { + "demographic": { + "type": "string", + "pattern": "^[PMWAC][0-9]{2}(-[0-9]{2}|\\+)$", + "description": "Target demographic in Nielsen format: P/M/W/A/C + age range. Examples: P18-49 (Persons 18-49), M25-54 (Men 25-54), W35+ (Women 35+), A18-34 (Adults 18-34), C2-11 (Children 2-11)" + }, + "min_points": { + "type": "number", + "description": "Minimum GRPs/TRPs required for this pricing option", + "minimum": 0 + } + }, + "required": ["demographic"], + "additionalProperties": false + }, + "min_spend_per_package": { + "type": "number", + "description": "Minimum spend requirement per package using this pricing option, in the specified currency", + "minimum": 0 + } + }, + "required": ["pricing_option_id", "pricing_model", "rate", "currency", "parameters"], + "additionalProperties": false +} diff --git a/static/schemas/v1/pricing-options/cpv-option.json b/static/schemas/v1/pricing-options/cpv-option.json new file mode 100644 index 0000000000..827678b6a3 --- /dev/null +++ b/static/schemas/v1/pricing-options/cpv-option.json @@ -0,0 +1,67 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/v1/pricing-options/cpv-option.json", + "title": "CPV Pricing Option", + "description": "Cost Per View (at publisher-defined threshold) fixed-rate pricing for video/audio", + "type": "object", + "properties": { + "pricing_option_id": { + "type": "string", + "description": "Unique identifier for this pricing option within the product (e.g., 'cpv_usd_50pct')" + }, + "pricing_model": { + "type": "string", + "const": "cpv", + "description": "Cost per view at threshold" + }, + "rate": { + "type": "number", + "description": "Fixed CPV rate (cost per view)", + "minimum": 0 + }, + "currency": { + "type": "string", + "description": "ISO 4217 currency code", + "pattern": "^[A-Z]{3}$", + "examples": ["USD", "EUR", "GBP", "JPY"] + }, + "parameters": { + "type": "object", + "description": "CPV-specific parameters defining the view threshold", + "properties": { + "view_threshold": { + "oneOf": [ + { + "type": "number", + "description": "Percentage completion threshold for CPV pricing (0.0 to 1.0, e.g., 0.5 = 50% completion)", + "minimum": 0, + "maximum": 1 + }, + { + "type": "object", + "description": "Time-based view threshold for CPV pricing", + "properties": { + "duration_seconds": { + "type": "integer", + "description": "Seconds of viewing required (e.g., 30 for YouTube-style '30 seconds = view')", + "minimum": 1 + } + }, + "required": ["duration_seconds"], + "additionalProperties": false + } + ] + } + }, + "required": ["view_threshold"], + "additionalProperties": false + }, + "min_spend_per_package": { + "type": "number", + "description": "Minimum spend requirement per package using this pricing option, in the specified currency", + "minimum": 0 + } + }, + "required": ["pricing_option_id", "pricing_model", "rate", "currency", "parameters"], + "additionalProperties": false +} diff --git a/static/schemas/v1/pricing-options/flat-rate-option.json b/static/schemas/v1/pricing-options/flat-rate-option.json new file mode 100644 index 0000000000..bb14f9f5be --- /dev/null +++ b/static/schemas/v1/pricing-options/flat-rate-option.json @@ -0,0 +1,82 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/v1/pricing-options/flat-rate-option.json", + "title": "Flat Rate Pricing Option", + "description": "Flat rate pricing for DOOH, sponsorships, and time-based campaigns - fixed cost regardless of delivery volume", + "type": "object", + "properties": { + "pricing_option_id": { + "type": "string", + "description": "Unique identifier for this pricing option within the product (e.g., 'flat_rate_usd_24h_takeover')" + }, + "pricing_model": { + "type": "string", + "const": "flat_rate", + "description": "Fixed cost regardless of delivery volume" + }, + "rate": { + "type": "number", + "description": "Flat rate cost", + "minimum": 0 + }, + "currency": { + "type": "string", + "description": "ISO 4217 currency code", + "pattern": "^[A-Z]{3}$", + "examples": ["USD", "EUR", "GBP", "JPY"] + }, + "is_fixed": { + "type": "boolean", + "description": "Whether this is a fixed rate (true) or auction-based (false)", + "const": true + }, + "parameters": { + "type": "object", + "description": "Flat rate parameters for DOOH and time-based campaigns", + "properties": { + "duration_hours": { + "type": "number", + "description": "Duration in hours for time-based flat rate pricing (DOOH)", + "minimum": 0 + }, + "sov_percentage": { + "type": "number", + "description": "Guaranteed share of voice as percentage (DOOH, 0-100)", + "minimum": 0, + "maximum": 100 + }, + "loop_duration_seconds": { + "type": "integer", + "description": "Duration of ad loop rotation in seconds (DOOH)", + "minimum": 1 + }, + "min_plays_per_hour": { + "type": "integer", + "description": "Minimum number of times ad plays per hour (DOOH frequency guarantee)", + "minimum": 0 + }, + "venue_package": { + "type": "string", + "description": "Named venue package identifier for DOOH (e.g., 'times_square_network', 'airport_terminals')" + }, + "estimated_impressions": { + "type": "integer", + "description": "Estimated impressions for this flat rate option (informational, commonly used with SOV or time-based DOOH)", + "minimum": 0 + }, + "daypart": { + "type": "string", + "description": "Specific daypart for time-based pricing (e.g., 'morning_commute', 'evening_prime', 'overnight')" + } + }, + "additionalProperties": false + }, + "min_spend_per_package": { + "type": "number", + "description": "Minimum spend requirement per package using this pricing option, in the specified currency", + "minimum": 0 + } + }, + "required": ["pricing_option_id", "pricing_model", "currency", "is_fixed", "rate"], + "additionalProperties": false +} diff --git a/tests/example-validation.test.js b/tests/example-validation.test.js index 92497bdcff..ef3cf4130d 100644 --- a/tests/example-validation.test.js +++ b/tests/example-validation.test.js @@ -354,14 +354,6 @@ test('Targeting example validates against schema', () => { ); }); -test('Budget example validates against schema', () => { - return validateAgainstSchema( - exampleData.budget, - '/schemas/v1/core/budget.json', - 'Budget example' - ); -}); - test('Frequency Cap example validates against schema', () => { return validateAgainstSchema( exampleData.frequencyCap, diff --git a/tests/schema-validation.test.js b/tests/schema-validation.test.js index adcc607958..b2791c9bbd 100644 --- a/tests/schema-validation.test.js +++ b/tests/schema-validation.test.js @@ -272,12 +272,11 @@ async function runTests() { await test('Core schemas have appropriate required fields', () => { const coreSchemas = schemas.filter(([path]) => path.includes('/core/')); const requiredFieldChecks = { - 'product.json': ['product_id', 'name', 'description', 'format_ids', 'delivery_type', 'is_fixed_price'], + 'product.json': ['product_id', 'name', 'description', 'format_ids', 'delivery_type'], 'media-buy.json': ['media_buy_id', 'status', 'promoted_offering', 'total_budget', 'packages'], 'package.json': ['package_id', 'status'], 'creative-asset.json': ['creative_id', 'name', 'format_id', 'assets'], - 'error.json': ['code', 'message'], - 'budget.json': ['total', 'currency'] + 'error.json': ['code', 'message'] }; for (const [schemaPath, schema] of coreSchemas) {