Repository navigation
Description
What kind of change?
Add or change an enum value
Which section?
5.6 — Commercial terms
Current text
| Key | Meaning |
| :--- | :--- |
| `hold_available` | Whether this line item may be held |
| `hold_duration` | ISO-8601 duration the hold runs for |
| `hold_scope` | `line_item` \| `proposal` |
| `state` | `none` \| `requested` \| `held` \| `expired` \| `released` \| `converted` |
| `hold_expires_at` | Absolute timestamp, set when the hold is granted |
Transitions: `none` → `requested` → `held` → (`expired` | `released` |
`converted`). `converted` is terminal and occurs when the line item reaches
`agreed`.Proposed text
`hold_terms` offers a hold. It is `seller-set`, and it is inside the binding
record (#63). `hold` is live state, outside the record, and is absent on a
shared proposal (#26, #65).
**`HoldTerms`** keys:
| Key | Type | Required | Meaning |
| :--- | :--- | :--- | :--- |
| `available` | Boolean | REQUIRED | Whether this line item may be held |
| `duration` | Duration | OPTIONAL | How long a hold runs from its grant |
**`Hold`** keys:
| Key | Type | Required | Meaning |
| :--- | :--- | :--- | :--- |
| `status` | HoldStatus | REQUIRED | Moved by the transitions below |
| `version` | Integer | REQUIRED | The `version` the hold is on (#64) |
| `requested_terms` | LineItemWrites | OPTIONAL | The composition the buyer asks to hold, which is not a `commit`. A field it doesn't name reads as § 3.1 and § 3.2 put in effect |
| `held_availability` | Availability | OPTIONAL | What the hold reserves, as the seller states it on a grant or a re-offer. It can be less than `requested_terms` asks |
| `position` | Integer | OPTIONAL | Seller-set, 1 for first in line, in the order granted. A later hold moves up only when the one ahead expires, is released or converts. Holds on the same inventory reserve it in that order, so a later hold reserves only what the holds granted ahead of it leave (#67) |
| `valid_until` | Instant | REQUIRED when `status` is `held` | The grant time plus `hold_terms.duration`, or the cap in #65 if that comes first. With no `duration`, the seller sets it within the cap. It MAY remain on `expired` and `released` |
**`HoldStatus`** is `requested` \| `held` \| `declined` \| `expired` \| `released` \|
`converted`.
These transitions are exhaustive:
- No `hold`, or `declined` | `expired` | `released` → `requested`: the buyer asks,
writing `version` and any `requested_terms`. `hold_terms.available` MUST be `true`.
The ask replaces any earlier `hold`.
- `requested` → `held`: the seller grants, writing `valid_until` and any
`held_availability`.
- `requested` → `declined`: the seller declines, `hold_terms.available` becomes
`false`, or the line item leaves `published` or `under_review` other than to
`agreed`.
- `held` → `held`: the seller re-offers a hold its revision makes stale, moving
`version` to the new one, writing any `held_availability`, and lowering
`valid_until` to the new version's cap where that comes first (#65, rule 2).
- `held` → `expired`: `valid_until` passes.
- `held` → `released`: the buyer releases, or the seller releases a hold its
revision makes stale (#65, rule 2).
- `requested` | `held` → `converted`: the line item reaches `agreed`.
`converted` is terminal.
While `status` is `held`, the held terms are the offered terms of `hold.version`,
which #64 freezes; a seller change is a new `version` under #65, rule 2.
A seller's answer MUST set `status` to `held` or `declined`. A repeated request
while `requested` or `held` MUST NOT start a second hold. Under
`linked_execution: all`, the holds on the proposal's line items move as a unit: each
transition applies to all of them, and `valid_until` is the earliest any of them
allows.
Hold transitions do not change the line item's `status` (§ 5.1) and do not
append to `negotiation_history[]` (§ 4.1).The holds PR carries this text. It takes LineItemWrites from the buyer writes PR, Availability from the pricing PR, and linked_execution from @Sirajmx's dependencies PR (#70), which the lifecycle PR narrows to all.
Why
§ 5.6.2 hold_status gives a seller no way to refuse a hold request, and a buyer no way to ask again after a hold lapses. A hold is something the seller grants to one buyer; we ask for declined and re-entry. We also tie the held terms to the held version (our held-terms follow-up), so a seller revision can't reprice them in place. The text also adds the hold position @premesh proposed, as hold.position, per open question 5. It splits hold_status into hold_terms, an offered term, and hold, live state, and drops hold_scope: under linked execution, holds move as a unit.
As drafted (§ 5.6.2):
stateDiagram-v2
none --> requested
requested --> held
held --> expired
held --> released
held --> converted
converted --> [*]
Proposed:
stateDiagram-v2
[*] --> requested
requested --> held
requested --> declined
requested --> converted
held --> held
held --> expired
held --> released
held --> converted
converted --> [*]
declined --> requested
expired --> requested
released --> requested
Our commitment acceptance change request (#67) stops the seller declining a held buyer's commitment for lack of availability. Our lifecycle change request (#65) caps hold.valid_until at the offer's end and the earliest price_valid_until. A hold is on a version, and our versioning change request (#64) makes a published version immutable, so the held terms are frozen by construction: they are the held version's terms. A seller change to the held terms is a new version under #65 rule 2, which releases the hold or re-offers it at the new version. The buyer's commit at the held version goes stale (#65 rule 1). The freeze is therefore not a separate rule. The seller can still decline under #67 for other reasons, price included, but can't reprice the held version. Under a firm entry (the lifecycle PR), only the other grounds it allows are left.
Knock-on effects
§ 6.3 CTV line item (#37): the example shows a held hold that expires as the offer opens. Show hold_terms: { available: false } only: the line item is private_auction and reserves nothing, and hold is absent on a shared proposal. The closing sentence states the same rule on status and history as our companion lifecycle filing (§ 4.3). One buyer's hold lives on the proposal issued to it (#26, our reply there).
Open questions for the working group:
- Answered in our lifecycle change request (#65) and the lifecycle PR. Should the freeze cover proposal-level terms, such as
offer_windowanddescription, and the sibling line items under linked execution, or only the held line item's own terms? The held terms are the whole held version's offered terms, proposal-level terms and sibling line items included, because [Change] § 4.1 — Freeze a version's offered terms; changes create version n+1 and a revised entry #64 freezes every value the seller writes. Under the lifecycle PR, a hold goes stale only when the binding record of the line items it names changes, proposal-level terms included. Outside linked execution, a new version that touches only another line item leaves the hold as it is. - Answered in our lifecycle change request (#65). Should the freeze sit in § 5.6.2
hold_statusas proposed here, or should who moves each status (#65), whose rule 2 bars revision while an ask is open, treat a running hold as an open ask? Neither: [Change] § 4, § 5.1 — Add § 4.3 Lifecycle: who moves each status #65 rule 2 covers a running hold as well as an open ask, and the freeze follows from [Change] § 4.1 — Freeze a version's offered terms; changes create version n+1 and a revised entry #64, so § 5.6.2 needs only the sentence that ties the held terms to the held version. - Answered in our lifecycle change request (#65). Should the seller instead stay free to revise a held line item, provided a
commitat the held version isn't rejected as stale (rule 1 of who moves each status (#65)) until the hold ends? The seller stays free to revise, but the revision is a new version: acommitat the held version goes stale, and the seller releases the hold or re-offers it at the new version ([Change] § 4, § 5.1 — Add § 4.3 Lifecycle: who moves each status #65 rule 2). - Answered in part in our lifecycle change request (#65) and by @EvgenyAndroid, with @premesh: Should the freeze apply under every delivery type, or only where a hold reserves availability (
guaranteedandshare_of_voicein our delivery type change request)? That change request asks what a hold on anon_guaranteedline item firms. The held terms are the held version's terms under every delivery type, because [Change] § 4.1 — Freeze a version's offered terms; changes create version n+1 and a revised entry #64 freezes every published version.heldalways means reserved inventory, and a later hold reserves behind the holds ahead of it. A seller that reserves nothing offers no hold (hold_terms.available: false, or nohold_terms) and states the expiry inpricing[].price_valid_until. That date bars a latecommit([Change] § 4, § 5.1 — Add § 4.3 Lifecycle: who moves each status #65). As @EvgenyAndroid proposed, until then the seller can't change thatpricing[]entry. Nor can it decline acommitunder it except on the grounds a firm entry allows (the lifecycle PR). Still open, in the rules issue: whether holds apply only where inventory is reserved, and which form a seller that reserves nothing uses. - Answered by @premesh. Should concurrent holds on the same inventory be ranked (first right of refusal, or a challenge to commit or release), or is ranking seller policy outside 3.0? Rank them with a seller-set
hold.position, 1 for first in line, in the order granted. A later hold moves up only when the one ahead expires, is released or converts. "There's no challenge", so this machine needs no sub-state. The position is hold status, outside [Change] § 4.1 — Freeze a version's offered terms; changes create version n+1 and a revised entry #64's offered terms, so a queue move creates no version (#64, @EvgenyAndroid, @premesh). First refusal on renewal is a separate, negotiated line-item term, which @premesh callsrenewal_right. - Should
holdcarry a seller-issued hold reference, so a buyer can reconcile a hold against the seller's systems, as @EvgenyAndroid asks? It would sit inhold, outside [Change] § 4.1 — Freeze a version's offered terms; changes create version n+1 and a revised entry #64's offered terms, as @EvgenyAndroid places it. - Answered in part in the holds PR: Should
holdcarry the held quantity, so a buyer can tell a partial hold from a full one, as @EvgenyAndroid asks? The same placement applies. Our commitment acceptance change request (#67) asked whether a hold reserves a quantity, and points tohold.held_availability. That key states what the hold reserves, and it can be less thanhold.requested_termsasks. Still open, in the rules issue: whether a grant MUST state it. - Should a hold stay live under a
committhe buyer makes during it? Otherwise a seller can let the hold run out while thecommitwaits, then decline it for lack of availability. As proposed,held→expiredruns onhold.valid_untilalone, and the buyer can end the wait by withdrawing itscommit. The rules issue lists this with the other hold rules we left out of the text.
Would this break an implementation already built to this draft?
No — additive or editorial
You are commenting as
Other — IAB Tech Lab member (Postindustria).
Before you submit
- I have read CONTRIBUTING.md and agree my contribution may be published under this repository's licence.
- This contains no confidential or commercially sensitive information.
Details and examples: hold_status state machine: full rationale and examples
Revised 2026-11-03: hold_status splits into hold_terms and hold, refused becomes declined, none and hold_scope go, and hold_position becomes hold.position. Open question 1 is answered, 7 in part, and 8 is new.
Analyzed and generated with the help of Claude, fully reviewed by me.
Activity
KonstantinMirin commentedon Oct 22, 2026
One addition: a hold has to freeze the held terms, or a seller revision can reprice them.
#67 stops the seller declining a held buyer's commitment for lack of availability. #65 caps
hold_expires_atat the earliestprice_valid_until. Neither stops the seller revising aheldline item: a hold isn't an ask, so #65 rule 2 misses it. The revision creates a new version (#64), so the buyer'scommitgoes stale (#65 rule 1). The hold keeps availability, not the terms.Proposed, after the transition list:
The seller can still decline under #67 for other reasons, price included, but can't reprice.
As drafted (§ 3, § 5.6.2), read with #64 and #65:
stateDiagram-v2 held --> held : seller revises terms, version n+1 held --> held : commit at version n rejected as stale held --> converted : commit at version n+1 accepted, line item agreed held --> expired held --> releasedProposed:
stateDiagram-v2 held --> converted : commit at the held terms accepted, line item agreed held --> expired : terms may change after held --> released : terms may change after note right of held : seller MUST NOT revise offered terms, except to answer an askOpen questions:
valid_untilanddescription, and the sibling line items of ahold_scope: proposalhold, or only the held line item's own terms?hold_statusas proposed here, or should who moves each status (#65), whose rule 2 bars revision while an ask is open, treat a running hold as an open ask?commitat the held version isn't rejected as stale (rule 1 of who moves each status (#65)) until the hold ends?guaranteedandshare_of_voicein our delivery type change request)? That change request asks what a hold on anon_guaranteedline item firms.Details: Delivery type and hold terms: full rationale
Analyzed and generated with the help of Claude, fully reviewed by me.
EvgenyAndroid commentedon Oct 27, 2026
Supporting
refusedand re-entry. Two further gaps:heldcould mean either, and to a buyer they're different guarantees. Either sayheldalways means reserved inventory, or add a way to state that a commitment is terms-only.premesh commentedon Oct 27, 2026
On ranking concurrent holds: from the OOH side, yes, expose the rank, but keep the model this proposal already has. The buyer requests, the seller grants (
held) or refuses, and a hold endsconverted,expiredorreleased. Until it converts it isn't a commitment, and once contracted the contract terms govern. That is how OOH works today, and I'd keep it seller-granted for agents too.Holds exist because client approval is slow, and agents don't change that. What agents change is that asking costs nothing: an agent can request holds on hundreds of units to keep options open. Because the seller grants, the seller can refuse speculative holds, and that matters more than any rule about ranking.
What's missing for OOH:
hold_position: seller-set integer, 1 = first in line. Several holds often sit on a popular unit in the order granted. A later hold moves up only when the one ahead expires, is released or converts. There's no challenge.hold_available: falseshould be a normal answer on some products (programmatic, rotary), where a firm offer with an expiry does the job.First refusal on renewal is a different thing: the current advertiser's right to the next cycle before it opens up. I'd put it on the line item (
renewal_right: first_refusal | none), not in the hold queue.EvgenyAndroid commentedon Oct 27, 2026
@premesh
hold_available: falseplus a firm offer with an expiry answers the second point I raised above, and the spec already has the expiry:pricing[].price_valid_until. So I'd settle it this way:heldalways means reserved inventory, and a seller that only honours terms says so withhold_available: falseandprice_valid_until.One catch with
hold_position, read with #64. #64 counts every seller-written value as an offered term, and its exclusion list names onlyhold_status.stateandhold_status.hold_expires_at. A seller-sethold_positionwould therefore be an offered term. Each time a hold ahead expires and the queue moves, the seller would mint a new version, and a buyer'scommitat the old version would go stale under #65 rule 1. Ifhold_positiongoes in, it belongs inhold_statusand on #64's exclusion list, along with the hold reference and held quantity from my earlier comment.premesh commentedon Oct 27, 2026
@EvgenyAndroid agreed on both.
hold_available: falsewithprice_valid_untilis a cleaner answer than anything new.And yes,
hold_positionis status, not a term: the buyer isn't agreeing to a position, it's just where they stand while the hold runs. It belongs inhold_statusand on #64's exclusion list. Otherwise every expiry ahead of you makes your commit stale, which is the opposite of what a queue is for.renewal_rightis different. It's negotiated, so it should stay an offered term and version like one.