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
1 change: 1 addition & 0 deletions examples/line-item-audio.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ production:
pricing: { seller_built: { delta_flat: 12000.00 } }

frequency_cap: { max: { min: 1, max: 6 }, period: "day", basis: listener }
time_zone: viewer_local

pricing:
- { cost_method: CPM, gross_rate: 18.00, floor: 15.84,
Expand Down
1 change: 1 addition & 0 deletions examples/line-item-ctv.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ formats: { available: ["ctv/instream-15s", "ctv/instream-30s"],
creative_delivery: { method: seller_hosted, protocol: vast_4_2, secure_required: true }

frequency_cap: { max: { min: 1, max: 4 }, period: "day", basis: household }
time_zone: viewer_local

pricing:
- { cost_method: CPM, gross_rate: 45.00, floor: 39.60,
Expand Down
1 change: 1 addition & 0 deletions examples/line-item-display.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ 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 }
time_zone: "America/New_York"

pricing:
- cost_method: CPM
Expand Down
7 changes: 7 additions & 0 deletions spec/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ issue that caused it, so the comment record and the spec history line up.

## [Unreleased]

- § 5.5 — `time_zone` on the line item is the clock for `daypart` and
`frequency_cap.period`: an IANA time zone name or `viewer_local`, REQUIRED
with either. A `daypart` window is `{ days[], start, end }` in civil time,
with `end` exclusive and later than `start`; a window that crosses midnight
is two windows. `frequency_cap.period` is a calendar period in `time_zone`.
Breaking ([#111](https://github.com/IABTechLab/OpenProposal/issues/111)).

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

Initial draft published for public comment. Ported from the working-group
Expand Down
36 changes: 30 additions & 6 deletions spec/openproposal-3.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -250,16 +250,39 @@ channel.

### 5.5 Targeting envelope

Each field declares bounds, not a value. A bare value means the field is
`seller-set`.
Each targeting field except `time_zone` declares bounds, not a value. A bare
value means the field is `seller-set`.

| 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 |
| `daypart` | Object | settable | `allowed[]` of `{ days[], start, end }`: `days[]` from `monday` \| `tuesday` \| `wednesday` \| `thursday` \| `friday` \| `saturday` \| `sunday`, HH:MM civil time, `end` exclusive, up to `24:00` |
| `time_zone` | String | seller-set | IANA time zone name, or `viewer_local` (where the ad renders). The clock for `daypart` and `frequency_cap.period`. REQUIRED with either |
| `frequency_cap` | Object | settable | `{ max: { min, max }, period, basis: device \| household \| listener }`. Basis matters: CTV typically caps at household, audio at listener |

**Window shape.** A `daypart` window is `{ days[], start, end }`, with `days[]`
from Monday to Sunday, `start` and `end` as HH:MM, and `end` exclusive,
later than `start`, and allowed to be `24:00`. A window that crosses midnight
is two windows:

```yaml
# Late fringe, 23:00 to 01:00, Monday night into Tuesday
daypart:
allowed:
- { days: [monday], start: "23:00", end: "24:00" }
- { days: [tuesday], start: "00:00", end: "01:00" }
time_zone: "America/New_York"
```

**Clock.** `time_zone` names a zone in the
[IANA time zone database](https://www.iana.org/time-zones), such as
America/New_York or UTC. Windows follow daylight-saving rules: a skipped
hour matches no instant; a repeated hour matches both. Like the calendar Day of
[OpenDirect 2.1 `Line.frequencyinterval`](https://github.com/InteractiveAdvertisingBureau/OpenDirect/blob/82bc294314aba017fc639ba634aecc53adfd675f/OpenDirect.v2.1.final.md#object_line),
`frequency_cap.period` is a calendar period in `time_zone`; weeks start on
Monday.

### 5.6 Commercial terms

| Field | Type | Mutability | Description |
Expand Down Expand Up @@ -469,6 +492,7 @@ body is normative.**
| `geo` | 5.5 Targeting envelope | Object | settable |
| `device` | 5.5 Targeting envelope | Object | settable |
| `daypart` | 5.5 Targeting envelope | Object | settable |
| `time_zone` | 5.5 Targeting envelope | String | seller-set |
| `frequency_cap` | 5.5 Targeting envelope | Object | settable |
| `pricing[]` | 5.6 Commercial terms | Array[Object] | mixed |
| `pricing[].cost_method` | 5.6 Commercial terms | Enum | seller-set |
Expand Down Expand Up @@ -512,7 +536,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`, `specs`, `third_party_tags`, `creative_delivery`, `approval`, `time_zone`, `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[]` |
Expand All @@ -522,8 +546,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
Expand Down