Skip to content

§ 5.6, § 5.6.5 to § 5.6.7 — Pricing: cost methods, currency, cancellation, and exclusivity (#94, #98) - #146

Open
KonstantinMirin wants to merge 92 commits into
IABTechLab:mainfrom
KonstantinMirin:op30-pricing
Open

KonstantinMirin wants to merge 92 commits into
IABTechLab:mainfrom
KonstantinMirin:op30-pricing

Conversation

@KonstantinMirin

@KonstantinMirin KonstantinMirin commented Nov 4, 2026 •

Copy link
Copy Markdown

What this changes

Draft 1 doesn't say what two pricing[] entries mean or which currency applies. Its cancellation and exclusivity options have no keys.

  • pricing[] entries are alternatives, named by pricing_id, and the buyer commits under one (§ 5.6). An entry can name the mechanisms it prices.
  • § 5.6.5 states the sign of agreed_rate, and a list_rate counter can change included[] (§ 4.3.1), from @aleksUIX. separation adds location, and @premesh's asks add paid_zone and a line item's preemption, with its remedy. A demographic term states co-viewers and unknown age.
  • § 5.6.5 opens cost_method, with ten core terms and the unit each counts (FLAT has none). It defines derived_units, the seller total, and FLAT and TIME figures.
  • A rate reads in its entry's CurrencyCode, and other amounts in the committed entry's, so commitment.currency and min_commitments[].currency go. Our packages PR moves min_commitments[].
  • Every line-item Amount shares one basis, which § 8 question 9 leaves open.
  • commitment_bounds.spend replaces min_spend and the budget keys, and pricing[].unit_bounds the impression keys, in the entry's unit.
  • § 5.6.6 defines the cancellation option and its CancellationNotice; § 5.6.7, the exclusivity option.

Related issue

Closes #94. Closes #98. Both are ours.

Refs #115, #30, #127, #125, #121, #117, and #70.

Builds on

This PR builds on our holds PR and our handoff PR, and through them on our post-agreement PR, our signing PR, our buyer writes PR, our lifecycle PR, our parties PR, our errors and extensions PR, and our shared types PR. It carries #18, #70, #71, and #72 by @Sirajmx, #73, #74, #75, and #76 by @Kishore-2572, and #118 by @aleksUIX. Review our seven pricing commits, starting with "Define pricing alternatives, money, bounds, and cancellation", the #117 merge, and the holds and handoff merges.

Type

  • Editorial — no normative effect
  • Normative — changes what the spec requires
  • Breaking — would break an implementation already built to this draft
  • Examples / schema / docs only

Checklist

  • The body section is updated — § 1.1, § 2.1, § 2.2, § 2.4, § 2.5, § 3.1 to § 3.4, § 4.1, § 4.3, § 4.3.1, § 4.3.2, § 4.3.4, § 4.4, § 5.6, § 5.6.1, § 5.6.3, new § 5.6.5 to § 5.6.7, § 6, § 8, and docs/open-questions.md OQ-9
  • Appendix A (field index) matches the body — pricing_id, rate_per, unit_label, cost_definition, cost_parameters, transaction_mechanisms[], and unit_bounds added under pricing[], preemption added, and min_spend removed. Rates are Amount, currency is a CurrencyCode, and availability, commitment_bounds, and exclusivity are typed. scripts/check_appendix.py passes, with Availability, CancellationOption, ExclusivityTerm, MinCommitment, and CancellationNotice off the pending list
  • Appendix B (fields by marker, and the counts) matches the body — immutable 9 → 10, seller-set 40 → 45, requestable 4 → 5; 87 → 94 fields
  • Any affected example in examples/ is updated and still parses — the renames throughout, and flight bounds. Display shows USD and EUR alternatives, and audio shows priced exclusivity. Display, CTV, and audio offer cancellation options, and the issued proposal records a partial cancellation. Both record_digest values are recomputed, and manifest.json is regenerated. scripts/check_examples.py recomputes the commitment arithmetic, the bounds, and the cancellation shares, and passes. scripts/check_pricing_fixtures.py passes 60 of 60
  • RFC 2119 keywords used deliberately — MUST / SHOULD / MAY are not synonyms
  • The no-inheritance rule (§ 2) still holds — every new key sits on the line item or its pricing[] entry
  • Anything selectable declares available[] and included[]; anything settable declares bounds (§ 3.1, § 3.2) — cancellation_policy and exclusivity keep both; the commitment bounds are commitment_bounds.spend and unit_bounds
  • No confidential or commercially sensitive content

Knock-on effects

Open questions

  1. Answered by @aleksUIX. A counter on a composed rate needs no separate final rate: list_rate carries it, with the negotiated options in included[], so a counter may change both. Dropping accepted_counter and selection_key stands.
  2. Yes, answered by @aleksUIX. § 5.6.5 states the rule, and scripts/check_examples.py flags a spend commitment with no rate.
  3. Should a CancellationNotice name the dated_shares[] entry it cancels under?
  4. [Change] § 5.6 — Give cost_method an extensible vocabulary, and say what several pricing[] entries mean #94 questions 3 and 4, and [Change] § 5.6 — Give cancellation_policy and exclusivity options a typed shape #98 questions 1, 3, 5, and 6, stay open.

By opening this pull request I agree my contribution may be published under this
repository's licence, as set out in CONTRIBUTING.md.

Analyzed and generated with the help of Claude, fully reviewed by me.

Sirajmx and others added 30 commits October 6, 2026 16:28
§ 5.6 defines floor as the seller's offer floor at proposal time, not
the downstream auction floor and not a guarantee that bids will clear.
Editorial: the meaning of the field is unchanged.

Refs IABTechLab#16
A proposal can say its line items must be bought together
(linked_execution, § 4.1), and a line item can name the line items or
spend that must be in the same commitment (requires, § 5.6.3). A bonus
line is a line item with requires and its own price. The § 2 "binding
terms" hook now points at a field, and the Summary Representation
carries has_requires and linked_execution.

Adds a CTV bonus line example (§ 6.5) and linked_execution: none to the
proposal example.

Refs IABTechLab#23
The seller declares which exclusion types it accepts and how it
enforces each (exclusions_accepted); the buyer supplies its lists
(exclusions). Blocked apps carry their store, { platform, bundle },
so one store id is never applied across stores. Exclusions are
recompute inputs (§ 3.3) and are never disclosed to other parties
(§ 5.8). The display example declares and supplies lists.

Refs IABTechLab#25
A line item whose rate differs by market can declare geo with the
selectable structure of § 3.1: available[], included[], pricing deltas
and max_select. A geo that declares only allowed[] stays settable, so
existing payloads are unchanged. When several priced markets are
selected, the highest delta applies. The audio example is priced by
market.

Refs IABTechLab#14
A commitment names the proposal and version it was priced against
(§ 5.6.1). The seller rejects a new attempt on a stale version, or past
valid_until or price_valid_until, and returns the current version. An
authenticated replay with the same idempotency_key and terms returns the
stored result without re-running those checks; the same key with
different terms is a conflict. Keys are opaque and scoped to one buyer
and seller pair. § 4.1 states that each revision while published
increments version.

The display, CTV and audio examples carry proposal_id and version.

Refs IABTechLab#24
§ 4.1 adds an optional, immutable proposal_template_id linking an
issued proposal to the seller's template. Each issued proposal has its
own status, version and negotiation_history[], and buyers never need
the template id to transact. § 7 adds a non-normative note separating
seller-side templates from the issued proposals buyers commit against.

Appendix A gains one row; Appendix B counts immutable 6 → 7 and
72 fields. The proposal example carries a proposal_template_id.

Refs IABTechLab#26
§ 5.8 adds terms_ref ({ url, sha256, version }), REQUIRED when
terms_acceptance_required is true, hash-pinned and frozen at agreed, so
assent binds to an exact document. § 5.10 adds agreement_ref, an
informative pointer to the master or annual agreement a line item is
priced under, and documents the io and agreement roles on
external_references[]. IO generation is stated to be a connector
concern, and § 7 adds a non-normative note on relationships.

agreement_ref is seller-set; a buyer-held agreement id travels as an
external_references[] entry with role: agreement, so the field keeps
one marker (§ 3).

Appendix A gains two rows; Appendix B counts seller-set 29 → 31 and
73 fields. The display example carries terms_ref.

Refs IABTechLab#27
A new § 2.3 lets a seller publish its public standard line items at
/.well-known/openproposal.json: seller_id, generated_at, spec_version
and, per proposal, proposal_id, version, the Summary Representation of
each line item and a full_url. ETag / Last-Modified are required, only
line items with visibility public appear, and entries must match the
current version. Buyers discover from the manifest, then fetch the Full
Representation of shortlisted line items.

Adds examples/manifest.json (§ 6.5) and visibility: public to the CTV
and audio examples; the registry_gated display line item is not listed.

Refs IABTechLab#29
The specification does not define authentication yet (WS1; see IABTechLab#62),
so the replay rule now applies to a replay from the same buyer rather
than an authenticated replay.

Refs IABTechLab#24
agreement_ref.parties used buyer_id, which the specification does not
define. parties[] now names the seller by its seller_id and the buyer
by the id it holds in eligible_bidders[] (section 5.8).

Refs IABTechLab#27
The rule that a hold on a linked_execution: all proposal uses
hold_scope: proposal came from a comment on IABTechLab#23, not the change request
as filed. It moves to the PR description as a point for the working
group, so this PR carries only the filed change.

Refs IABTechLab#23
IABTechLab#25 names the content-category list iab_content_category[], a list of
IAB Content Taxonomy ids. The earlier commit renamed it content_category
with a version-pinned taxonomy_ref, which nobody proposed. This restores
the filed name and shape; which taxonomy version to reference is left
to the working group.

Refs IABTechLab#25
IABTechLab#14 doesn't say how deltas combine when a buyer selects more than one
priced market, and neither did any comment on it. The earlier commit
said the highest delta applies; that is a design choice nobody
proposed, so it moves to the PR description as a question.

Refs IABTechLab#14
Read the media condition from the ad served when an impression accepts
several media, name the seller as the party that serves, place linear TV
outside the table, and state environments as (distribution channel,
device type) pairs. Insert environments.available[] into the summary
list without rewrapping it, shorten the changelog entry and drop
audio_streaming from OQ-7 in docs/open-questions.md.

Refs IABTechLab#90
time_zone is the line item's clock for daypart windows and
frequency_cap.period: an IANA time zone name or viewer_local. A window
is { days[], start, end } in civil time with an exclusive end later
than start, up to 24:00; a window that crosses midnight is two windows.
List the `days[]` values once in the `daypart` row, as `monday` | ... |
`sunday`. Write HH:MM, America/New_York, and UTC as plain text, since
they're a format and examples, not field names. The meaning is
unchanged.
"At least one of" an empty list is never satisfied, so the bonus line
would fail a literal check. An absent key sets no condition. Only the
examples change; the any_of[] row and the rules stay as filed in IABTechLab#23.

Refs IABTechLab#23
The floor row now says it "neither constrains nor describes the
downstream auction floor", as IABTechLab#16 asked, and names that floor: the
per-impression floor the exchange applies at auction time (OpenRTB 2.6
Imp.bidfloor).

Refs IABTechLab#16
From the Postindustria review on IABTechLab#73:
- a replay matches on the same body, every value the buyer sent except
  idempotency_key, and returns the stored result whether an acceptance
  or a rejection; a retry against a new version uses a new key;
- version increments on each revision of the proposal, not only while
  published, so a counter under review also moves it;
- "deterministic" now says what the checks depend on: the stored
  proposal and the time the seller receives the attempt;
- the price check names the pricing[] entry the commitment is priced
  against.

Refs IABTechLab#24
# Conflicts:
#	spec/openproposal-3.0.md
The decline rule no longer counts a line item whose preemption names
exclusivity, so the sale that preempts it can go through. Preempted
delivery is a shortfall. The reason holds where and when the line item
delivers, notice becomes notice_duration as on the cancellation option,
and paid_zone names the part of a venue past its paid entry.

Refs IABTechLab#146
A seller MUST keep a rejection whose retry is not same until
offer_window.end, or as long as it keeps a proposal with no end. Any
commit that wrote an entry stays findable by its key, and a replay the
seller no longer remembers runs the checks as a new commit with that key.

Refs IABTechLab#128
…ntract

No IAB Tech Lab document defines the AAMP wire and lifecycle contract
yet. The pre-release iab-agentic-primitives library's OpenAPI file and
JSON Schemas define the messages the reference agents exchange, so an
implementer can use them as the binding until the contract is published.

Refs IABTechLab#59
@KonstantinMirin

Copy link
Copy Markdown
Author

Thanks, @premesh, and thanks to your transit team. This PR now has both:

  • separation adds the core term paid_zone: the part of each venue past its paid entry, such as inside a station's fare gates. location stays the whole venue. separation already says where a term holds, so the boundary is one more term there, not a separate key.
  • A line item MAY state preemption (§ 5.6.7), with a reason and an optional notice_duration. The core reason is exclusivity: a later exclusivity term that holds where and when this line item delivers. A seller MUST NOT preempt a line item without it, or for another reason. Preemption doesn't excuse a shortfall.

The domination's terms sit on its own line items, inside its package (the packages PR, #154), and preemption sits on the line items sold to other buyers.

When a domination preempts faces, do your contracts give a makegood, a credit, or something else?

Analyzed and generated with the help of Claude, fully reviewed by me.

@premesh

premesh commented Nov 7, 2026

Copy link
Copy Markdown

@KonstantinMirin thanks, both read right. On remedy: it varies by contract, so I'd not fix one in the spec. Suggest an optional seller-set remedy on preemption (makegood | credit | none as an open vocabulary), stated up front, so the preempted buyer knows what they get before committing.

The manifest's hold_limits states the most holds a seller grants at once, per
buyer domain and per advertiser. A seller declines a request over a declared
limit, and under a per-advertiser limit may decline one that names no
advertiser.
Where a value whose Type cell is String conflicts with a typed field of the
same version in the binding record, the typed field governs. Identifiers and
codes are excluded, and an Enum or shared-type value is a typed field.
A line item's preemption MAY state a remedy, from an open vocabulary with the
core terms makegood, credit, and none. It settles the delivery a preemption
takes on any delivery_type, in place of shortfall_remedy, so a preempted
share-of-voice buyer knows what it gets before committing.
@KonstantinMirin

Copy link
Copy Markdown
Author

Thanks, @premesh. Added to this PR: preemption MAY state a remedy, from an open vocabulary whose core terms are makegood, credit, and none. The buyer reads it before committing, and it settles the delivery a preemption takes on any delivery_type.

That closes a gap for loop spots. Under § 5.7.1 of our settlement PR (#147), a share-of-voice buy without a committed level has no shortfall when a domination takes its share. #147 now counts that delivery as a shortfall when preemption states a remedy, and settles it under that remedy.

Analyzed and generated with the help of Claude, fully reviewed by me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

5 participants