diff --git a/examples/line-item-ctv.yaml b/examples/line-item-ctv.yaml index fda3994..f895b87 100644 --- a/examples/line-item-ctv.yaml +++ b/examples/line-item-ctv.yaml @@ -4,6 +4,7 @@ status: published channel: ctv properties: { available: [{ id: "weather-app" }], included: ["weather-app"] } environments: { available: ["ctv"], included: ["ctv"] } +placement: { plcmt: 1 } # Instream (1) (§ 5.2) formats: { available: ["ctv/instream-15s", "ctv/instream-30s"], included: ["ctv/instream-15s"], pricing: { "ctv/instream-30s": { delta_cpm: 8.00 } } } diff --git a/examples/line-item-rewarded-video.yaml b/examples/line-item-rewarded-video.yaml new file mode 100644 index 0000000..fde5a71 --- /dev/null +++ b/examples/line-item-rewarded-video.yaml @@ -0,0 +1,37 @@ +# OpenProposal 3.0 — Example: Rewarded video line item (spec § 6.5) +# Illustrative only. In-app rewarded video across a pack of puzzle games. +line_item_id: "puzzle-games-rewarded" +status: published +channel: video +properties: { available: [{ id: "puzzle-pack", name: "Puzzle game pack" }], + included: ["puzzle-pack"] } +environments: { available: ["app_mobile"], included: ["app_mobile"] } +placement: # every impression of this line item (§ 5.2) + reward: 1 + instl: 1 + plcmt: 3 # Interstitial (3) + skip: 0 # watched to the end for the reward + playmethod: [3] # Initiates on Click with Sound On (3) + api: [6, 7] # MRAID 3.0 (6), OMID 1.0 (7) +formats: { available: ["video/30s"], included: ["video/30s"] } +creative_delivery: { method: buyer_hosted, protocol: vast_4_2, secure_required: true } + +frequency_cap: { max: { min: 1, max: 3 }, period: "day", basis: device } + +pricing: + - { cost_method: CPM, gross_rate: 22.00, floor: 19.50, + price_valid_until: "2026-12-01T23:59:59Z", currency: USD } + +commitment_bounds: { min_budget: 10000.00 } + +committed_metrics: + - { metric: completion_rate, value: 0.95, basis: mrc } + - { metric: viewable_rate, value: 0.90, basis: mrc } +measurement_source: # third-party vendors run through OMID 1.0 (7) + available: [{ vendor: "IAS", attribution_window: "30d" }, + { vendor: "DoubleVerify", attribution_window: "30d" }] + included: ["IAS"] + +buying_route: SSP_PLATFORM +transaction_mechanism: { available: ["PREFERRED_DEAL"], included: ["PREFERRED_DEAL"] } +serving_mode: client_side diff --git a/spec/CHANGELOG.md b/spec/CHANGELOG.md index 1dfacef..66bbd75 100644 --- a/spec/CHANGELOG.md +++ b/spec/CHANGELOG.md @@ -5,6 +5,12 @@ issue that caused it, so the comment record and the spec history line up. ## [Unreleased] +- § 2.1, § 5.2 — `placement` on the line item states what every impression + is, in AdCOM 1.0 and OpenRTB 2.6 terms: rewarded, interstitial, video + placement subtype, skippability, playback, API frameworks and refresh. + `plcmt` is REQUIRED for video ads. Breaking + ([#89](https://github.com/IABTechLab/OpenProposal/issues/89)). + ## [3.0-draft-1] — 2026-09-22 Initial draft published for public comment. Ported from the working-group diff --git a/spec/openproposal-3.0.md b/spec/openproposal-3.0.md index 6940958..59c624f 100644 --- a/spec/openproposal-3.0.md +++ b/spec/openproposal-3.0.md @@ -86,6 +86,10 @@ plus, from the parent proposal: `proposal_id` · `seller_name` · `description` `catalog_ref` values MUST remain unresolved in a summary. +A line item that carries `placement` (§ 5.2) MUST also give, in its summary, +whichever of `placement.reward` · `placement.instl` · `placement.plcmt` · +`placement.skip` it declares. + **Full Representation** — every field, with all `catalog_ref` values resolved. A full fetch MUST return it. @@ -225,6 +229,55 @@ channel. | `properties` | Object | selectable | `available[]` of `{ id, name, domains, supported_formats }`, `included[]`, `pricing`, `max_select` | | `content_detail` | Object | seller-set | Shows, series, sections, network verticals | | `environments` | Object | selectable | `available[]` from `web_desktop` \| `web_mobile` \| `app_mobile` \| `ctv` \| `dooh` \| `audio_streaming` | +| `placement` | Object | seller-set | What every impression of this line item is: rewarded, interstitial, video placement subtype, skippability, playback, API frameworks and refresh. `plcmt` is REQUIRED for video ads. See 5.2.1 | + +#### 5.2.1 placement + +Keys follow [AdCOM 1.0 `Placement`](https://github.com/InteractiveAdvertisingBureau/AdCOM/blob/26c59ba1b235cd8d938c9877409e506f1b8d3f0c/AdCOM%20v1.0%20FINAL.md#object_placement) and its +[`DisplayPlacement`](https://github.com/InteractiveAdvertisingBureau/AdCOM/blob/26c59ba1b235cd8d938c9877409e506f1b8d3f0c/AdCOM%20v1.0%20FINAL.md#object_displayplacement), +[`VideoPlacement`](https://github.com/InteractiveAdvertisingBureau/AdCOM/blob/26c59ba1b235cd8d938c9877409e506f1b8d3f0c/AdCOM%20v1.0%20FINAL.md#object_videoplacement) and +[`AudioPlacement`](https://github.com/InteractiveAdvertisingBureau/AdCOM/blob/26c59ba1b235cd8d938c9877409e506f1b8d3f0c/AdCOM%20v1.0%20FINAL.md#object_audioplacement) subtypes, and the matching +[OpenRTB 2.6 `Imp`](https://github.com/InteractiveAdvertisingBureau/openrtb2.x/blob/1debba8001f40eb14597102dec3205afddf811d4/2.6.md#objectimp) attributes. + +Each key takes an integer, except `playmethod[]` and `api[]`, which take +arrays of integers, and `refresh`, an object whose `min_interval_s` is a +REQUIRED integer. `plcmt`, `playmethod[]` and `api[]` take any code in +[List: Plcmt Subtypes - Video](https://github.com/InteractiveAdvertisingBureau/AdCOM/blob/26c59ba1b235cd8d938c9877409e506f1b8d3f0c/AdCOM%20v1.0%20FINAL.md#list_plcmtsubtypesvideo), +[List: Playback Methods](https://github.com/InteractiveAdvertisingBureau/AdCOM/blob/26c59ba1b235cd8d938c9877409e506f1b8d3f0c/AdCOM%20v1.0%20FINAL.md#list_playbackmethods) and +[List: API Frameworks](https://github.com/InteractiveAdvertisingBureau/AdCOM/blob/26c59ba1b235cd8d938c9877409e506f1b8d3f0c/AdCOM%20v1.0%20FINAL.md#list_apiframeworks) respectively. This +section names each code by its AdCOM name followed by the code, such as +Instream (1). + +| Key | Required | Meaning | AdCOM 1.0, OpenRTB 2.6 | +| :--- | :--- | :--- | :--- | +| `reward` | OPTIONAL | `0` (no) \| `1` (yes). The user receives a reward for viewing the ad | `Placement.reward`, `imp.rwdd` | +| `instl` | OPTIONAL | `0` (no) \| `1` (yes). The ad is interstitial or full screen | `DisplayPlacement.instl`, `imp.instl` | +| `plcmt` | REQUIRED for video ads (`channel` `video`, or video on `channel` `ctv`); otherwise OPTIONAL | One value from List: Plcmt Subtypes - Video: Instream (1), Accompanying Content (2), Interstitial (3), No Content/Standalone (4), Pause (5), Screensaver (6), Overlay (7), Squeezeback (8), In-scene (9) | `VideoPlacement.ptype`, `video.plcmt` | +| `skip` | OPTIONAL | Video or audio. `0` (no) \| `1` (yes). The placement lets the user skip the ad | `VideoPlacement.skip`, `AudioPlacement.skip`, `video.skip` | +| `skipafter` | OPTIONAL | When `skip` is `1`: seconds the ad plays before the placement enables skipping | `VideoPlacement.skipafter`, `AudioPlacement.skipafter`, `video.skipafter` | +| `playmethod[]` | OPTIONAL | Video or audio. Values from List: Playback Methods in use, such as Initiates without any specific user interaction with Sound On (1), Initiates without any specific user interaction Sound Off by Default (2), Initiates on Click with Sound On (3) and Continuous Playback (7) | `VideoPlacement.playmethod`, `AudioPlacement.playmethod`, `video.playbackmethod` | +| `api[]` | OPTIONAL | Values from List: API Frameworks that every impression supports, such as MRAID 3.0 (6), OMID 1.0 (7) and SIMID 1.1 (9) | `DisplayPlacement.api`, `VideoPlacement.api`, `AudioPlacement.api`, `banner.api`, `video.api`, `audio.api` | +| `refresh` | OPTIONAL | `{ min_interval_s }`: minimum seconds a creative renders before the slot refreshes | OpenRTB 2.6 only: `imp.refresh.refsettings[].minint` | + +`plcmt` takes its OpenRTB 2.6 name. AdCOM 1.0 carries the same list as +`VideoPlacement.ptype`. OpenRTB 2.6 `Audio` has no `skip`, `skipafter` or +`playbackmethod`, so for audio those keys map to AdCOM 1.0 `AudioPlacement` +only. + +- A key that is present holds for every impression of the line item. An absent + key does not constrain it: a line item that omits `instl` can deliver to + interstitial and non-interstitial impressions alike. AdCOM 1.0 reads an + absent `reward` or `instl` as `0`; here it means either value. +- The seller MUST NOT deliver the line item to an impression that does not + match a key that is present. An impression matches `api[]` when it supports + every listed framework, `playmethod[]` when its playback method is one of the + listed values, and `refresh` when the slot renders each creative for at least + `min_interval_s` seconds before it refreshes, or does not refresh. It matches + any other key when its value is equal. +- Unless `api[]` includes OMID 1.0 (7), a line item whose `committed_metrics[]` + includes `viewable_rate` and whose `environments.available[]` includes + `app_mobile` or `ctv` SHOULD NOT offer a `measurement_source` vendor other + than the seller. ### 5.3 Audience reach @@ -375,6 +428,12 @@ See [`examples/line-item-ctv.yaml`](../examples/line-item-ctv.yaml). See [`examples/line-item-audio.yaml`](../examples/line-item-audio.yaml). +### 6.5 Rewarded video line item + +Shows `placement` on in-app rewarded video, with OMID support that lets a +buyer select a third-party `measurement_source`. See +[`examples/line-item-rewarded-video.yaml`](../examples/line-item-rewarded-video.yaml). + --- ## 7. Why this split @@ -454,6 +513,7 @@ body is normative.** | `properties` | 5.2 Inventory composition | Object | selectable | | `content_detail` | 5.2 Inventory composition | Object | seller-set | | `environments` | 5.2 Inventory composition | Object | selectable | +| `placement` | 5.2 Inventory composition | Object | seller-set | | `audiences` | 5.3 Audience reach | Object | selectable | | `addressable_scale` | 5.3 Audience reach | Integer | derived | | `activation_route` | 5.3 Audience reach | Object | selectable | @@ -512,7 +572,7 @@ body is normative.** | Marker | Count | Meaning | Fields | | :--- | :--- | :--- | :--- | | immutable | 6 | Set once at creation. Neither party may change it. | `proposal_id`, `seller_id`, `seller_name`, `type`, `line_item_id`, `channel` | -| seller-set | 29 | Seller sets and may revise while `draft` or `published`. | `valid_from` · `valid_until`, `description`, `specifications`, `best_for[]`, `not_suitable_for[]`, `seasonality_notes`, `content_detail`, `specs`, `third_party_tags`, `creative_delivery`, `approval`, `pricing[].cost_method`, `pricing[].gross_rate`, `pricing[].seller_rate`, `pricing[].price_valid_until`, `pricing[].currency`, `pricing[].floor`, `commitment_bounds`, `min_spend`, `creative_policy`, `visibility`, `required_credentials[]`, `eligible_bidders[]`, `terms_acceptance_required`, `buying_route`, `serving_mode`, `serving_platform`, `billing`, `supported_order_systems[]` | +| seller-set | 30 | Seller sets and may revise while `draft` or `published`. | `valid_from` · `valid_until`, `description`, `specifications`, `best_for[]`, `not_suitable_for[]`, `seasonality_notes`, `content_detail`, `placement`, `specs`, `third_party_tags`, `creative_delivery`, `approval`, `pricing[].cost_method`, `pricing[].gross_rate`, `pricing[].seller_rate`, `pricing[].price_valid_until`, `pricing[].currency`, `pricing[].floor`, `commitment_bounds`, `min_spend`, `creative_policy`, `visibility`, `required_credentials[]`, `eligible_bidders[]`, `terms_acceptance_required`, `buying_route`, `serving_mode`, `serving_platform`, `billing`, `supported_order_systems[]` | | selectable | 12 | Buyer chooses from a declared set. Resolves without assent. | `properties`, `environments`, `audiences`, `activation_route`, `formats`, `production`, `cancellation_policy`, `exclusivity`, `measurement_source`, `shortfall_remedy`, `reporting`, `transaction_mechanism` | | settable | 5 | Buyer sets a value within declared bounds. Resolves without assent. | `geo`, `device`, `daypart`, `frequency_cap`, `commitment` | | supplied | 3 | Buyer provides data or an artifact. | `brief_ref`, `buyer_supplied`, `assets[]` | @@ -522,8 +582,8 @@ body is normative.** ### What the distribution says -Of 71 fields, **23 are touched by the buyer** — 12 `selectable`, 5 `settable`, 3 -`supplied` and 3 `requestable`. The remaining 48 are `seller-set`, `immutable`, +Of 72 fields, **23 are touched by the buyer** — 12 `selectable`, 5 `settable`, 3 +`supplied` and 3 `requestable`. The remaining 49 are `seller-set`, `immutable`, `derived` or `mixed`. Only the 3 `requestable` fields require a round trip. Everything else the buyer