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
8 changes: 8 additions & 0 deletions examples/line-item-display.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,14 @@ materials_due: "2026-09-24"
geo: { allowed: ["US-NY", "US-CA", "US-IL", "US-TX"] }
frequency_cap: { max: { min: 1, max: 5 }, period: "day", basis: device }

exclusions_accepted: # what this seller can enforce (§ 5.5)
types: [domain, iab_content_category, keyword]
max_entries: { domain: 5000, keyword: 500 }
enforcement: { domain: pre_serve, iab_content_category: pre_serve, keyword: best_effort }
exclusions: # supplied by the buyer
domain: ["example-gambling.example"]
keyword: ["crypto"]

pricing:
- cost_method: CPM
gross_rate: 24.00
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]

- § 5.5 — Buyer-supplied block lists: `exclusions_accepted` (what the seller
can enforce) and `exclusions` (the buyer's lists)
([#25](https://github.com/IABTechLab/OpenProposal/issues/25)).

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

Initial draft published for public comment. Ported from the working-group
Expand Down
58 changes: 50 additions & 8 deletions spec/openproposal-3.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,9 @@ treated as `seller-set`.

### 3.3 Re-computation

Because audiences, properties and formats are `selectable`, `addressable_scale`
and `availability` MUST be recomputed against the buyer's actual selection. A
Because audiences, properties and formats are `selectable`, and buyer
`exclusions` (§ 5.5) narrow delivery, `addressable_scale` and `availability`
MUST be recomputed against the buyer's actual selection and exclusions. A
single declared figure is correct only for the default composition.

---
Expand Down Expand Up @@ -251,14 +252,50 @@ channel.
### 5.5 Targeting envelope

Each field declares bounds, not a value. A bare value means the field is
`seller-set`.
`seller-set`. `exclusions_accepted` and `exclusions` are the exception: the
seller declares what it can enforce, and the buyer supplies its lists.

| Field | Type | Mutability | Description |
| :--- | :--- | :--- | :--- |
| `geo` | Object | settable | `allowed[]` of geo codes the buyer may select within |
| `device` | Object | settable | `allowed[]` of device categories |
| `daypart` | Object | settable | `allowed[]` of time windows |
| `frequency_cap` | Object | settable | `{ max: { min, max }, period, basis: device \| household \| listener }`. Basis matters: CTV typically caps at household, audio at listener |
| `exclusions_accepted` | Object | seller-set | What the seller can enforce: `{ types[], max_entries, enforcement, verification[] }`. See below |
| `exclusions` | Object | supplied | The buyer's block lists: `{ domain[], app_bundle[], iab_content_category[], keyword[], list_ref }`. See below |

**Block lists.** The seller declares which exclusion types it accepts and how it
enforces each; the buyer supplies its lists within that declaration.

```yaml
exclusions_accepted:
types: [domain, app_bundle, iab_content_category, keyword]
max_entries: { domain: 5000, keyword: 500 }
enforcement: { domain: pre_serve, app_bundle: pre_serve,
iab_content_category: pre_serve, keyword: best_effort }
verification: ["IAS", "DoubleVerify"]

exclusions:
domain: ["example-gambling.example"]
app_bundle: [{ platform: roku, bundle: "123456" }]
iab_content_category: ["IAB-CT3.0:<id>"] # IAB Content Taxonomy ids
keyword: ["crypto"]
list_ref: { url: "<url>", sha256: "<hash>" }
```

- Each `app_bundle[]` entry is `{ platform, bundle }`. `bundle` is the OpenRTB
2.6 `app.bundle` value; `platform` names the store (`ios` \| `android` \|
`roku` \| `fire_tv` \| `samsung_tv` and other store platforms). A seller MUST
reject an entry whose platform it cannot determine, rather than apply it
across stores.
- `iab_content_category[]` entries are IAB Content Taxonomy ids.
- `enforcement` is `pre_serve` or `best_effort` per type.
- A buyer MUST NOT supply an exclusion type absent from
`exclusions_accepted.types`. A seller MUST reject it deterministically
rather than ignore it.
- A seller MUST NOT present `best_effort` enforcement as a guarantee.
- `list_ref` MUST be hash-pinned, and resolved into the stored record at
`agreed`, as for `catalog_ref` (§ 2.2).

### 5.6 Commercial terms

Expand Down Expand Up @@ -331,6 +368,9 @@ Transitions: `none` → `requested` → `held` → (`expired` | `released` |
| `eligible_bidders[]` | Array[String] | seller-set | Permitted seat or buyer ids |
| `terms_acceptance_required` | Boolean | seller-set | Whether legal terms must be accepted before assent |

`exclusions` (§ 5.5) reveal the buyer's strategy. A seller MUST NOT disclose a
buyer's `exclusions` to any party other than that buyer.

### 5.9 Execution

| Field | Type | Mutability | Description |
Expand Down Expand Up @@ -470,6 +510,8 @@ body is normative.**
| `device` | 5.5 Targeting envelope | Object | settable |
| `daypart` | 5.5 Targeting envelope | Object | settable |
| `frequency_cap` | 5.5 Targeting envelope | Object | settable |
| `exclusions_accepted` | 5.5 Targeting envelope | Object | seller-set |
| `exclusions` | 5.5 Targeting envelope | Object | supplied |
| `pricing[]` | 5.6 Commercial terms | Array[Object] | mixed |
| `pricing[].cost_method` | 5.6 Commercial terms | Enum | seller-set |
| `pricing[].gross_rate` | 5.6 Commercial terms | Number | seller-set |
Expand Down Expand Up @@ -512,21 +554,21 @@ 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`, `specs`, `third_party_tags`, `creative_delivery`, `approval`, `exclusions_accepted`, `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[]` |
| supplied | 4 | Buyer provides data or an artifact. | `brief_ref`, `buyer_supplied`, `assets[]`, `exclusions` |
| requestable | 3 | Buyer may ask outside the declared space. Requires seller assent. | `materials_due`, `hold_status`, `committed_metrics[]` |
| derived | 11 | Computed. Neither party sets it directly. | `version`, `status` (proposal), `assent`, `negotiation_history[]`, `status` (line item), `addressable_scale`, `pricing[].agreed_rate`, `availability`, `deal_id_issuance`, `external_references[]`, `correlation_id` |
| mixed | 2 | Sub-fields carry different markers; see the section. | `pricing[]`, `buyer_requirements` |

### 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, **24 are touched by the buyer** — 12 `selectable`, 5 `settable`, 4
`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
touches — 20 fields — resolves against bounds the seller declared up front. That
touches — 21 fields — resolves against bounds the seller declared up front. That
ratio is the design claim: composition is constraint satisfaction, and
negotiation is the exception.
Loading