From 0bc3f7e05240fab93e6dcf9ee180df264602d35f Mon Sep 17 00:00:00 2001 From: "Constantine.mirin" Date: Sat, 24 Oct 2026 23:24:08 -0400 Subject: [PATCH 1/2] Add placement to say what each impression is --- examples/line-item-ctv.yaml | 1 + examples/line-item-rewarded-video.yaml | 37 +++++++++++++++++ spec/CHANGELOG.md | 5 +++ spec/openproposal-3.0.md | 56 ++++++++++++++++++++++++-- 4 files changed, 95 insertions(+), 4 deletions(-) create mode 100644 examples/line-item-rewarded-video.yaml diff --git a/examples/line-item-ctv.yaml b/examples/line-item-ctv.yaml index fda3994..55f289e 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, skip: 0 } # Instream (1), not skippable (§ 5.2.1) 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..7fbc38f --- /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.1) + 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..c544585 100644 --- a/spec/CHANGELOG.md +++ b/spec/CHANGELOG.md @@ -5,6 +5,11 @@ 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 + ([#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..d84fded 100644 --- a/spec/openproposal-3.0.md +++ b/spec/openproposal-3.0.md @@ -79,7 +79,8 @@ item: `line_item_id` · `channel` · `content_detail` · property names · `formats.included[]` · `pricing.cost_method` · `pricing.gross_rate` · `min_spend` · `addressable_scale` · `committed_metrics[]` · -`transaction_mechanism` +`transaction_mechanism`, and, when present, `placement.reward` · +`placement.instl` · `placement.plcmt` · `placement.skip` plus, from the parent proposal: `proposal_id` · `seller_name` · `description` · `valid_from` · `valid_until`. @@ -225,6 +226,46 @@ 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. Codes are values of +[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), each written as its name +followed by its code. + +| 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. +- 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 +416,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 +501,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 +560,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 +570,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 From 81de4d827440cc42d8e027d5f88843e8da109bdd Mon Sep 17 00:00:00 2001 From: "Constantine.mirin" Date: Sun, 25 Oct 2026 00:21:48 -0400 Subject: [PATCH 2/2] Define placement matching and value types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the summary rule out of the § 2.1 key list, drop skip from the CTV example, and mark the CHANGELOG entry as breaking. --- examples/line-item-ctv.yaml | 2 +- examples/line-item-rewarded-video.yaml | 2 +- spec/CHANGELOG.md | 3 ++- spec/openproposal-3.0.md | 24 ++++++++++++++++++------ 4 files changed, 22 insertions(+), 9 deletions(-) diff --git a/examples/line-item-ctv.yaml b/examples/line-item-ctv.yaml index 55f289e..f895b87 100644 --- a/examples/line-item-ctv.yaml +++ b/examples/line-item-ctv.yaml @@ -4,7 +4,7 @@ status: published channel: ctv properties: { available: [{ id: "weather-app" }], included: ["weather-app"] } environments: { available: ["ctv"], included: ["ctv"] } -placement: { plcmt: 1, skip: 0 } # Instream (1), not skippable (§ 5.2.1) +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 index 7fbc38f..fde5a71 100644 --- a/examples/line-item-rewarded-video.yaml +++ b/examples/line-item-rewarded-video.yaml @@ -6,7 +6,7 @@ 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.1) +placement: # every impression of this line item (§ 5.2) reward: 1 instl: 1 plcmt: 3 # Interstitial (3) diff --git a/spec/CHANGELOG.md b/spec/CHANGELOG.md index c544585..66bbd75 100644 --- a/spec/CHANGELOG.md +++ b/spec/CHANGELOG.md @@ -7,7 +7,8 @@ issue that caused it, so the comment record and the spec history line up. - § 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 + 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 diff --git a/spec/openproposal-3.0.md b/spec/openproposal-3.0.md index d84fded..59c624f 100644 --- a/spec/openproposal-3.0.md +++ b/spec/openproposal-3.0.md @@ -79,14 +79,17 @@ item: `line_item_id` · `channel` · `content_detail` · property names · `formats.included[]` · `pricing.cost_method` · `pricing.gross_rate` · `min_spend` · `addressable_scale` · `committed_metrics[]` · -`transaction_mechanism`, and, when present, `placement.reward` · -`placement.instl` · `placement.plcmt` · `placement.skip` +`transaction_mechanism` plus, from the parent proposal: `proposal_id` · `seller_name` · `description` · `valid_from` · `valid_until`. `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. @@ -234,11 +237,16 @@ Keys follow [AdCOM 1.0 `Placement`](https://github.com/InteractiveAdvertisingBur [`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. Codes are values of +[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), each written as its name -followed by its code. +[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 | | :--- | :--- | :--- | :--- | @@ -261,7 +269,11 @@ only. 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. + 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