Repository navigation
Description
What kind of change?
Change a field's type or shape
Which section?
5.6 — Commercial terms
Current text
| `pricing[]` | Array[Object] | mixed | See below |
| `pricing[].cost_method` | Enum | seller-set | CPM, CPC, flat, etc. |Proposed text
§ 5.6, in the five-column form of our shared types PR, replace the `pricing[]`,
`pricing[].cost_method`, and `pricing[].currency` rows with:
| Field | Type | Required | Mutability | Description |
| :--- | :--- | :--- | :--- | :--- |
| `pricing[]` | Array[PricingEntry] | REQUIRED | mixed | See below |
| `pricing[].pricing_id` | String | REQUIRED | immutable | Names the entry within the line item. A revision MUST NOT reuse it for a different `cost_method`, `cost_parameters`, `currency`, or `transaction_mechanisms[]` |
| `pricing[].cost_method` | VocabularyTerm | REQUIRED | seller-set | The billable unit. Core terms in § 5.6.5 |
| `pricing[].rate_per` | Integer | REQUIRED when `cost_method` is an extension term | seller-set | `1` \| `1000`. Units per rate. For a core term, a present value MUST match § 5.6.5 |
| `pricing[].unit_label` | String | REQUIRED when `cost_method` is an extension term | seller-set | A short label for one unit, such as `engagement` |
| `pricing[].cost_definition` | DocumentRef | REQUIRED when `cost_method` is an extension term | seller-set | Public definition of one unit and the `cost_parameters` keys |
| `pricing[].cost_parameters` | CostParameters | REQUIRED when the term requires keys | seller-set | The keys the term requires (§ 5.6.5) |
| `pricing[].transaction_mechanisms[]` | Array[TransactionMechanism] | OPTIONAL | seller-set | The `transaction_mechanism` options this entry prices. Absent: every offered option |
| `pricing[].currency` | CurrencyCode | REQUIRED | seller-set | The currency of the entry's rates (below) |
| `pricing[].unit_bounds` | Range[Integer] | OPTIONAL | requestable | Bounds on the units committed under this entry. Absent on a `FLAT` entry |
Then:
`pricing[]` entries are alternatives. The buyer commits under exactly one, the
*committed entry*, named by `commitment.pricing_id`, or the only entry when that
key is absent; it MUST cover the mechanism in effect. Only that entry enters
`agreed_rate` and `derived_units`. Two entries MUST NOT share `cost_method`,
`cost_parameters`, `currency`, and a mechanism.
A rate reads in its own entry's `currency`. A commitment reads in the committed
entry's `currency`, and so does every other `Amount` on the line item:
`commitment_bounds.spend`, § 3.1 `delta_cpm` and `delta_flat`, and a cancellation
fee. OpenProposal never converts.
§ 5.6.1, `commitment`: add `pricing_id`, REQUIRED when `pricing[]` has more than
one entry, the entry the buyer commits under. Remove `currency`.
§ 3.1: a `delta_cpm` enters only a `CPM` or `CPMV` entry. An option priced only by
`delta_cpm` MUST NOT resolve against any other entry.
After § 5.6.2 and our other § 5.6 subsections, add:
#### 5.6.5 Cost methods
`cost_method` is a `VocabularyTerm` (§ 2.2). Its core terms are upper case, like
OpenDirect 2.1 `Line.ratetype`.
| `cost_method` | One unit | Rate per | `cost_parameters` |
| :--- | :--- | :--- | :--- |
| `CPM` | Impression | 1,000 | |
| `CPMV` | Viewable impression | 1,000 | `viewability_standard` |
| `CPC` | Click | 1 | |
| `CPCV` | Completed view: 100% of the creative played | 1 | |
| `CPV` | View | 1 | `view_s` or `view_fraction` |
| `CPP` | Rating point | 1 | `demographic`, `rating_source` |
| `CPA` | Action | 1 | `action_type`, `attribution_window`, `action_source` |
| `PLAY` | DOOH play on one screen or one sync group | 1,000 | |
| `TIME` | One `rate_unit`: `hour` \| `day` \| `week` | 1 | `rate_unit` |
| `FLAT` | The line item | | |
A seller's own extension terms MUST use its `seller.domain` as the `<domain>`. The
host of `cost_definition.url` MUST be the term's `<domain>` or a subdomain of it.
When `commitment.basis` is `spend`, `amount` is the seller total, on the same
terms as `agreed_rate`, including `delta_flat` charges in effect (§ 3.1), which
MUST NOT exceed it. `derived_units` = (`amount` − those charges) ÷ `agreed_rate` ×
rate per, rounded down to a whole unit. When `commitment.basis` is `units`,
`derived_units` is `amount`, and the seller total is `amount` ÷ rate per ×
`agreed_rate` plus those charges.
`FLAT` and `TIME` set these figures from the rate and the flight instead.
**`FLAT`.** `agreed_rate` is the price of the line item, so the seller total is
`agreed_rate` plus those charges. `commitment.basis` MUST be `spend`, and
`derived_units` is absent.
**`TIME`.** `derived_units` is the whole `rate_unit`s elapsed from
`commitment.flight.start` through `commitment.flight.end`, inclusive, and the seller
total is `derived_units` × `agreed_rate` plus those charges.
A seller fee is never a `pricing[]` entry. A receiver MUST treat an extension entry
that breaks these rules as unavailable, not the line item as invalid.Why
We ask two separable things: a vocabulary for cost_method, and a meaning for several pricing[] entries.
cost_method is the billable unit, with no list and no meaning. § 5.6 ends the Enum with "etc.", and § 5.6.1 derives units by an unstated divisor. Views, rating points, and actions also need counting parameters.
A closed list alone fails: a new model, such as #80's outcome-based pricing, waits for registration. Sellers write flat plus prose instead, as #45 does. Free strings fail the other way: CPCV and cost_per_completed_view never match.
A domain prefix can't collide, and it names who answers for the term. With cost_definition on that domain, a reader checks a term's definer as it checks a seller (#61). Inline rate_per keeps the record computable, and unit_label names the unit without a fetch. Terms compare byte for byte, so flat becomes FLAT.
Nothing says whether two pricing[] entries are a choice or charges that add up. The comment template names the gap: "pricing[] sits above transaction_mechanism." @EvgenyAndroid confirms that without this rule, they can execute only a one-entry line item.
A spend amount includes delta_flat charges, as @aleksUIX's fee-basis PR (#117) proposes. derived_units rounds down, the stated rule @EvgenyAndroid asked for. A PLAY is one play on a screen or sync group, following @premesh.
Our pricing PR carries this text and the rest of § 5.6.5. VocabularyTerm comes from our shared types PR, Delta from our buyer writes PR, and CostParameters from our property option PR. The rules issue lists rules we left out, such as one currency per line item.
Details: Pricing models and price alternatives: full rationale and examples
Knock-on effects
- § 2.1: the Summary includes each entry's
pricing_id,cost_method,list_rate,currency,price_type, andunit_bounds. - Our reply on #30 locates an entry by
cost_method; in our lifecycle PR, a historypointerdoes. - Gross or net of commission, and tax, become one § 8 question.
- #82 asks what a DOOH CPM counts; our DOOH PR answers the DOOH part with
impression_multiplier.
Open questions for the working group
- Answered in our pricing PR. Entries with different
cost_methods share onecommitment_bounds. Shouldmin_impressionsandmax_impressionscount the committed entry's units, be renamedmin_unitsandmax_units, move into each entry, or give way to budget-only bounds? They move into each entry asunit_bounds, counted in its unit and absent onFLAT. The budgets becomecommitment_bounds.spend. - Answered in our buyer writes PR. Should § 3.1 Selectable structure
delta_cpmadjust onlyCPMandCPMVentries, as we propose, or read as an adjustment per rate unit on every entry? OnlyCPMandCPMV.delta_fractionmultiplies any entry's rate. - Answered in our audience PR. Should § 5.3 Audience reach
audiencesacceptdelta_fraction(the draft'sdelta_pct) anddelta_flat, so that audience options can price non-CPM entries? Yes: an audience option takes anyDeltakey. - Answered in part in our handoff PR: the deal mapping covers a
CPMentry, and other cost methods have no mapping yet. Still open: should aCPCVor other non-CPMdeal entry be allowed, billed outside the deal on the controlling count? Or discouraged with a SHOULD NOT, or forbidden? The rules issue lists the strict option: deal entries MUST beCPM. - Answered in our pricing PR. Should the buyer name its entry in
commitment.pricing_id, as we propose? Or shouldpricingbecome a § 3.1 Selectable structure selectable field that the buyer sets through theselected[]key proposed in the buyer's selection key (#22)?commitment.pricing_id, REQUIRED whenpricing[]has more than one entry.
Would this break an implementation already built to this draft?
Yes — breaking
Alternatives you considered and rejected
- A closed registry. A proprietary model never gets in.
- AdCOM 1.0's "vendor-specific codes typically starting at 500". Two vendors can pick the same code.
- Prefix core terms too (
iab:CPM). Breaks every example. - One price per line item. Duplicate line items over one inventory, and § 2 subsetting lets a buyer commit both.
- Revenue share. A commission on attributed sales, not a rate on a unit (gist).
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.
Revised 2026-11-03: Rewritten to match our pricing PR, with terms compared byte for byte, bounds on each entry, and FLAT and TIME figures. Open questions 1, 2, 3, and 5 are answered, and 4 in part.
Analyzed and generated with the help of Claude, fully reviewed by me.
Activity
KonstantinMirin commentedon Oct 25, 2026
Revision, from @aleksUIX's fee-basis PR (#117), on @dineshbhat's
commitment.amountchange request (#115). Ourderived_unitssentence reads all of a spendamountas media, so a selecteddelta_flatfee overrunsamountor goes unpaid. We replace that whole sentence with:Gross or net of agency commission stays open in our floor and rate-basis reply on #30, open question 3, and tax in the
billing.tax_inclusiveflag that #115 proposes.amountuses the same terms asagreed_rate, so the formula holds either way.Analyzed and generated with the help of Claude, fully reviewed by me.
EvgenyAndroid commentedon Oct 27, 2026
Implementation evidence for the "entries are alternatives" rule and
pricing_id. Without them I can only execute a line item with exactly onepricing[]entry. With two, nothing tells me whether they combine or which one the buyer accepted, and aselectableoption's price delta can't say which entry it modifies.One gap this and #103 don't close yet: rounding. The display example gives
derived_units: 3333333for 80,000.00 at a 24.00 CPM, which truncates 3,333,333.33. "Informative, not binding" is fine, but two implementations should still get the same number. A stated rule (truncate, or round half-even) would make it reproducible.