Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions examples/line-item-ctv-bonus.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# OpenProposal 3.0 — Example: Bonus line item (spec § 6.5)
# Illustrative only. A CTV bonus unlocked when the buyer commits to the display
# and CTV line items together, with at least 50,000 USD on display. Its price is
# its own: nothing is inherited from the line items it requires.
line_item_id: "weather-app-ctv-bonus"
status: published
channel: ctv
properties: { available: [{ id: "weather-app" }], included: ["weather-app"] }
environments: { available: ["ctv"], included: ["ctv"] }
formats: { available: ["ctv/instream-15s"], included: ["ctv/instream-15s"] }
creative_delivery: { method: seller_hosted, protocol: vast_4_2, secure_required: true }

frequency_cap: { max: { min: 1, max: 2 }, period: "day", basis: household }

requires:
all_of: ["business-daily-display", "weather-app-ctv"]
any_of: []
min_commitment:
- { line_item_id: "business-daily-display", basis: spend, amount: 50000.00, currency: USD }

pricing:
- { cost_method: flat, gross_rate: 0.00,
price_valid_until: "2026-12-01T23:59:59Z", currency: USD }

commitment_bounds: { max_impressions: 500000 }

buying_route: SSP_PLATFORM
transaction_mechanism: { available: ["PRIVATE_AUCTION"], included: ["PRIVATE_AUCTION"] }
serving_mode: ssai
1 change: 1 addition & 0 deletions examples/proposal.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ version: 1
seller_id: "reg:examplepub-digital"
seller_name: "Example Publisher US"
type: standard
linked_execution: none # line items may be bought separately (§ 4.1)
status: published
valid_from: "2026-10-01T00:00:00Z"
valid_until: "2026-12-31T23:59:59Z"
Expand Down
4 changes: 4 additions & 0 deletions spec/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ issue that caused it, so the comment record and the spec history line up.

## [Unreleased]

- § 2, § 4.1, § 5.6 — `linked_execution` on the proposal and `requires` on
line items, for linked execution and bonus line items
([#23](https://github.com/IABTechLab/OpenProposal/issues/23)).

## [3.0-draft-1] — 2026-09-22

Initial draft published for public comment. Ported from the working-group
Expand Down
61 changes: 54 additions & 7 deletions spec/openproposal-3.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,9 @@ item. There is no "overridable unless restated" semantics anywhere in this
specification.

**Subsettable proposals.** A buyer MAY select and commit a subset of a
proposal's line items, unless the proposal explicitly mandates linked execution
through its binding terms.
proposal's line items, unless the proposal sets `linked_execution` to `all`
(§ 4.1). A line item that declares `requires` (§ 5.6.3) may be committed only
when its conditions are met.

### 2.1 Summary and full representations

Expand All @@ -79,10 +80,13 @@ 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` · `has_requires`

plus, from the parent proposal: `proposal_id` · `seller_name` · `description` ·
`valid_from` · `valid_until`.
`valid_from` · `valid_until` · `linked_execution`.

`has_requires` is `true` when the line item declares `requires` (§ 5.6.3), so a
screening agent does not recommend a line item that cannot be bought on its own.

`catalog_ref` values MUST remain unresolved in a summary.

Expand Down Expand Up @@ -190,6 +194,7 @@ items.
| `valid_from` · `valid_until` | Timestamp | seller-set | ISO-8601 window the offer holds. **This is the offer window, not the campaign flight** |
| `brief_ref` | String | supplied | Reference to the buyer's brief. REQUIRED when `type` is `custom` |
| `negotiation_history[]` | Array[Object] | derived | Ordered, append-only. `{ version, actor: buyer \| seller, action: proposed \| countered \| accepted \| declined \| withdrawn, fields_changed[], at }`. Entries MUST NOT be edited or removed |
| `linked_execution` | Enum | seller-set | `none` (default, subsettable) \| `all` (every published line item must be committed together) |

### 4.2 Agent comprehension

Expand Down Expand Up @@ -280,6 +285,7 @@ Each field declares bounds, not a value. A bare value means the field is
| `cancellation_policy` | Object | selectable | `available[]` of cancellation rights with `pricing` per option |
| `exclusivity` | Object | selectable | `available[]` of exclusivity terms with `pricing` |
| `creative_policy` | Object | seller-set | Content and category restrictions on creative |
| `requires` | Object | seller-set | OPTIONAL. Conditions that must hold in the same commitment for this line item to be committable. See 5.6.3 |

#### 5.6.1 commitment

Expand Down Expand Up @@ -313,6 +319,39 @@ Transitions: `none` → `requested` → `held` → (`expired` | `released` |
`converted`). `converted` is terminal and occurs when the line item reaches
`agreed`.

#### 5.6.3 requires

| Key | Meaning |
| :--- | :--- |
| `all_of[]` | Line items that MUST all be in the same commitment |
| `any_of[]` | Line items of which at least one MUST be in the same commitment |
| `min_commitment[]` | Purchase-level thresholds: `{ line_item_id, basis, amount, currency }` |

```yaml
requires:
all_of: ["business-daily-display", "weather-app-ctv"]
any_of: []
min_commitment:
- { line_item_id: "business-daily-display", basis: spend, amount: 50000.00, currency: USD }
```

A bonus line item is a line item with `requires` and its own pricing, such as
a discounted `gross_rate` or `cost_method: flat` at 0. A conditional discount
is modelled as a separate line item that `requires` unlocks, not as a price
that changes according to the other line items selected. Every price remains
self-contained.

- References in `requires` MUST point to line items in the same proposal and
version.
- A seller MUST reject a commitment that includes a line item whose `requires`
is unmet. This check MUST be deterministic.
- If a line item referenced in `requires` becomes `sold_out` or `withdrawn`
before commitment, the dependent line item becomes non-committable. After
`agreed`, cancellation of a referenced line item is governed by the
dependent line item's `cancellation_policy`.
- A seller MUST reject a proposal at publication if `requires` references
form a cycle.

### 5.7 Guarantees

| Field | Type | Mutability | Description |
Expand Down Expand Up @@ -375,6 +414,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 Bonus line item

A CTV line item unlocked by buying the display and CTV line items together,
priced on its own. See
[`examples/line-item-ctv-bonus.yaml`](../examples/line-item-ctv-bonus.yaml).

---

## 7. Why this split
Expand Down Expand Up @@ -443,6 +488,7 @@ body is normative.**
| `valid_from` · `valid_until` | 4.1 Identity and lifecycle | Timestamp | seller-set |
| `brief_ref` | 4.1 Identity and lifecycle | String | supplied |
| `negotiation_history[]` | 4.1 Identity and lifecycle | Array[Object] | derived |
| `linked_execution` | 4.1 Identity and lifecycle | Enum | seller-set |
| `description` | 4.2 Agent comprehension | String | seller-set |
| `specifications` | 4.2 Agent comprehension | String/Object | seller-set |
| `best_for[]` | 4.2 Agent comprehension | Array[String] | seller-set |
Expand Down Expand Up @@ -486,6 +532,7 @@ body is normative.**
| `cancellation_policy` | 5.6 Commercial terms | Object | selectable |
| `exclusivity` | 5.6 Commercial terms | Object | selectable |
| `creative_policy` | 5.6 Commercial terms | Object | seller-set |
| `requires` | 5.6 Commercial terms | Object | seller-set |
| `committed_metrics[]` | 5.7 Guarantees | Array[Object] | requestable |
| `measurement_source` | 5.7 Guarantees | Object | selectable |
| `shortfall_remedy` | 5.7 Guarantees | Object | selectable |
Expand All @@ -512,7 +559,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 | 31 | Seller sets and may revise while `draft` or `published`. | `valid_from` · `valid_until`, `linked_execution`, `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`, `requires`, `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[]` |
Expand All @@ -522,8 +569,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 73 fields, **23 are touched by the buyer** — 12 `selectable`, 5 `settable`, 3
`supplied` and 3 `requestable`. The remaining 50 are `seller-set`, `immutable`,
`derived` or `mixed`.

Only the 3 `requestable` fields require a round trip. Everything else the buyer
Expand Down
Loading