Skip to content

[Change] § 1.1, § 5.9, § 5.10 — Carry the Deals API deal: its id, its origin, and the buyer's seats #132

Open

Description

@KonstantinMirin

What kind of change?

Add a new field

Which section?

Whole document / more than one section

Current text

§ 1.1:

| Direct buy | OpenDirect 2.1 · Order + Line |
| Programmatic guaranteed | OpenDirect 2.1 + Deals API · `deal_id` |
| PMP deal | Deals API + OpenRTB 2.6 · Deal |
| Open market | OpenRTB 2.6 · open auction, no deal |

§ 5.9:

| `deal_id_issuance` | String | derived | When and how the deal id is minted. Issued at commitment time |
| `buyer_requirements` | Object | mixed | `{ seat_required, dsp_allowed[] }` are seller-set; `{ dsp_seat_id, dsp_id, seat_owner_entity }` are supplied |

§ 5.10:

| `external_references[]` | Array[Object] | derived | `{ system, namespace, id, role }`. Replaces platform-specific key names. A buyer-owned reference such as a Prisma `io_id` is supplied |

Proposed text

§ 1.1, replace the table with one row per `transaction_mechanism` (§ 5.9):

| `transaction_mechanism` | Downstream standard | `reference_type` (§ 5.10) |
| :--- | :--- | :--- |
| `direct_io` | OpenDirect 2.1 · Order + Line | `order`, `line` |
| `programmatic_guaranteed` | OpenDirect 2.1 · Order + Line, as the reservation; Deals API 1.0 · `Deal` | `order`, `line`, `deal` |
| `preferred_deal` | Deals API 1.0 · `Deal` + OpenRTB 2.6 · `Deal` | `deal` |
| `private_auction` | Deals API 1.0 · `Deal` + OpenRTB 2.6 · `Deal` | `deal` |

Buying in the OpenRTB 2.6 open auction is outside this specification: with
no deal ID, no bid request can name a line item.

**Deals API handoff.** A line item agreed under a deal mechanism (§ 5.9)
becomes a Deals API 1.0 `Deal`. The system named by `deal_origin` (§ 5.9)
pushes the `Deal` to the DSP named by `buyer_requirements.seats.dsp`. The
deal ID is in that system's namespace: it assigns the ID, or accepts one the
seller supplies. The `Deal` takes these values from the agreed line item:

| Deals API 1.0 | OpenRTB 2.6 | From the agreed line item |
| :--- | :--- | :--- |
| `id` | `Deal.id`, `Bid.dealid` | None: the ID in `deal_origin`'s namespace. The `external_references[]` entry with `reference_type: deal` records it (§ 5.10) |
| `origin` | | `deal_origin` |
| `seller` | | The proposal's `seller.domain` |
| `wseat` | `Deal.wseat` | `buyer_requirements.seats.ids[]` |
| `adtypes` | | From `channel`: `display` 1, `video` 2, `audio` 3, `native` 4, and `ctv` 2 when `placement.plcmt` is present. Otherwise unset: the record doesn't state the ad's type |
| `terms.startdate`, `terms.enddate` | | `commitment.flight.start`, `commitment.flight.end` |
| `terms.countries` | | The countries of `geo` or `markets` in effect, in alpha-3 (below), without a country excluded whole. Unset when the line item declares neither |

`commitment.flight.end` and `terms.enddate` are the flight's last second,
inclusive. The Deals API 1.0 doesn't say whether `enddate` is inclusive, so
this mapping states it.

`terms.countries` takes the ISO 3166-1 alpha-3 code of each country. The
Deals API 1.0 says "ISO-3166-3", and this mapping reads that as alpha-3. The
list names whole countries, so `US` minus `US-TX` sends `USA`, and `geo` in
the agreed record holds the exclusion.

The line item's `status` ends at `agreed` (§ 5.1). After the push, the deal's
state is the Deals API `sellerstatus` and, for each seat, `BuyerStatus.status`.
OpenProposal has no field for either.

§ 5.9, replace the `deal_id_issuance` row:

| `deal_origin` | RegistrableDomain | seller-set | REQUIRED when the line item offers a deal mechanism; absent otherwise. The advertising system domain whose namespace holds the deal ID and that pushes the deal, as the Deals API 1.0 `origin`: typically an SSP, or the ad server for a deal it books itself |

§ 5.9, `buyer_requirements`: replace `dsp_id` and `dsp_seat_id` with the
supplied `seats`, a `SeatRef { dsp, ids[] }`. `dsp` is the DSP's primary
domain. `ids[]` holds the buyer's seats on the deal, as the Deals API 1.0
`wseat`: the ids the DSP issues, the same values OpenRTB 2.6 carries in
`Deal.wseat`. A seat always travels with its DSP. `seat_required` becomes
`seats_expected`. `dsp_allowed[]` becomes `allowed_dsps[]`, each entry a
DSP's primary domain. `seat_owner_entity` becomes `seat_owner`, a `Party`
(absent: the `buyer`).

§ 5.10, `external_references[]` row, append:

Once the deal exists in `deal_origin`, the seller MUST record its ID as one
`deal` entry on the line item. An entry is not a term and MAY be added after
`agreed`.

§ 5.10, `ExternalReference`: `role` becomes `reference_type`. Its core term
`deal`: the deal ID that bid requests carry, with `system` set to
`deal_origin`. It is the Deals API `id` and the OpenRTB 2.6 `Deal.id`,
whether `deal_origin` assigned it or accepted it from the seller. An ID that
bid requests don't carry, such as the origin's internal record ID, is not a
`deal` entry.

Why

The handoff to the Deals API has no key. We ask OpenProposal to carry the deal as the Deals API defines it: its ID, the system whose namespace holds it, and the buyer's seats.

§ 1.1 routes programmatic guaranteed through "Deals API · deal_id". The Deals API 1.0 Deal has no deal_id. It has id, "in origin's namespace", which should match the OpenRTB deal.id. OpenProposal has no field for the ID either: deal_id_issuance describes minting in free text.

@franks33 (#124) asks for the minted deal ID in external_references[] at agreed, with named roles. Their follow-up wants no "two unconnected ledgers". IAB Tech Lab's reference seller agent already does this. On its unmerged feat/openproposal-mapping branch, the Ad Manager mapper writes an external_references[] entry with role: deal, noting that deal_id_issuance "only says how ids are minted". We make that the rule, and name system by the Deals API origin. A buyer then joins its agreed record, the push, and every bid on one ID.

The Deals API names who pushes: the required origin. The ID is in its namespace, and implementers use "the Deal ID from the Origin system that did the PUSH". A buyer needs it before committing, to check that its DSP has a seat there. deal_origin replaces deal_id_issuance, which @VJAlkimi (#35) lists as a seller or SSP assertion, not a derived value. Timing needs no field. Our binding-record change request (#63) keeps the ID outside the binding record, so recording it after agreed changes no term. Print, in the MVFP print channel request (#102), has no deal and omits deal_origin.

The Deals API wseat is an array, and BuyerStatus accepts or rejects each seat. dsp_seat_id holds one seat, so an agency buying on two seats can't say so. A seller maps dsp_id to a DSP by hand, as @dtbarne shows on #2.

Status needs a boundary, not new values. Our agreed-baseline comment (#68) ends OpenProposal's lifecycle at agreed, and the Deals API sellerstatus starts there.

As drafted (§ 1.1, § 5.9):

stateDiagram-v2
    direction LR
    state "OpenProposal line item" as OP {
        agreed
    }
    state "Deals API 1.0 Deal" as DA {
        deal : sellerstatus, BuyerStatus.status
    }
    OP --> DA : lands in "Deals API · deal_id"
    note right of DA : neither side has a deal_id field, and nothing names the origin
Loading

Proposed:

stateDiagram-v2
    direction LR
    state "OpenProposal line item" as OP {
        agreed
    }
    state "Deals API 1.0 Deal, pushed by deal_origin" as DA {
        seller : sellerstatus
        buyer : BuyerStatus.status, per seat in wseat
    }
    OP --> DA : push, id recorded as external_references[] reference_type deal
    note left of OP : lifecycle ends at agreed
Loading

Details: Deals API alignment: full crosswalk

Knock-on effects

Open questions for the working group

  1. Does the curator's own deal ID, Deals API curation.cdealid, need a core reference_type term beside deal, with system set to the curator domain? Or is an extension term enough? OpenProposal has no curator field. @Sirajmx's cross-publisher composition (#28) names the original seller with source_ref.
  2. A seller may be able to set one line item up on several SSPs. Does it publish one line item per deal_origin? Or is deal_origin selectable (§ 3.1), so the buyer picks the SSP where its DSP has a seat?
  3. Should the push carry the agreed baseline, a VersionRef (proposal_id, version, line_item_id) with the proposal's seller.domain? A DSP could then join a deal to its agreed record without the buyer's agent. The Deals API has no field for it except ext. Our agreed-baseline comment (#68) names that baseline, and our post-agreement PR types it. @dineshbhat's comment on it asks which Deals API operation carries a revised deal.

Would this break an implementation already built to this draft?

Yes — breaking

Alternatives you considered and rejected

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: Uses the coming PRs' names, and the handoff PR closes this issue. Replaced fields are removed, not deprecated. A DSP is named by its domain. Adds adtypes and terms.countries rows.

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions