Skip to content

[Change] § 5.6 — Give cost_method an extensible vocabulary, and say what several pricing[] entries mean #94

Open

Description

@KonstantinMirin

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, and unit_bounds.
  • Our reply on #30 locates an entry by cost_method; in our lifecycle PR, a history pointer does.
  • 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

  1. Answered in our pricing PR. Entries with different cost_methods share one commitment_bounds. Should min_impressions and max_impressions count the committed entry's units, be renamed min_units and max_units, move into each entry, or give way to budget-only bounds? They move into each entry as unit_bounds, counted in its unit and absent on FLAT. The budgets become commitment_bounds.spend.
  2. Answered in our buyer writes PR. Should § 3.1 Selectable structure delta_cpm adjust only CPM and CPMV entries, as we propose, or read as an adjustment per rate unit on every entry? Only CPM and CPMV. delta_fraction multiplies any entry's rate.
  3. Answered in our audience PR. Should § 5.3 Audience reach audiences accept delta_fraction (the draft's delta_pct) and delta_flat, so that audience options can price non-CPM entries? Yes: an audience option takes any Delta key.
  4. Answered in part in our handoff PR: the deal mapping covers a CPM entry, and other cost methods have no mapping yet. Still open: should a CPCV or other non-CPM deal 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 be CPM.
  5. Answered in our pricing PR. Should the buyer name its entry in commitment.pricing_id, as we propose? Or should pricing become a § 3.1 Selectable structure selectable field that the buyer sets through the selected[] key proposed in the buyer's selection key (#22)? commitment.pricing_id, REQUIRED when pricing[] 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

  1. added
    change-requestProposes specific normative wording
    normativeChanges what the spec requires
    breakingWould break an implementation already built to this draft
    on Oct 23, 2026
  2. KonstantinMirin commented on Oct 25, 2026

    @KonstantinMirin
    Author

    Revision, from @aleksUIX's fee-basis PR (#117), on @dineshbhat's commitment.amount change request (#115). Our derived_units sentence reads all of a spend amount as media, so a selected delta_flat fee overruns amount or goes unpaid. We replace that whole sentence with:

    When `basis` is `spend`, `amount` is the seller total, on the same
    terms as `agreed_rate`, including selected `delta_flat` charges
    (§ 3.1), which MUST NOT exceed it. `FLAT` commits on `spend`.
    For other entries, `derived_units` = (`amount` − those charges) ÷
    `agreed_rate` × rate per; `TIME` units are `time_unit`s within the
    flight. When `basis` is `units`, the seller total is `amount` ÷ rate
    per × `agreed_rate` plus those charges; `min_spend` and
    `commitment_bounds` budgets apply to it. A seller fee is never a
    `pricing[]` entry.

    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_inclusive flag that #115 proposes. amount uses the same terms as agreed_rate, so the formula holds either way.

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

  3. EvgenyAndroid commented on Oct 27, 2026

    @EvgenyAndroid

    Implementation evidence for the "entries are alternatives" rule and pricing_id. Without them I can only execute a line item with exactly one pricing[] entry. With two, nothing tells me whether they combine or which one the buyer accepted, and a selectable option'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: 3333333 for 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.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    breakingWould break an implementation already built to this draftchange-requestProposes specific normative wordingnormativeChanges what the spec requiressection:5.6-commercial

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions