AAMP 3.0 · Draft for public comment
Version 3.0 draft 1 · Published for comment 2026-09-22
Status: Draft. This document is open for public comment. It is not a ratified IAB Tech Lab standard and MUST NOT be treated as one. See CONTRIBUTING.md for how to comment and docs/comment-process.md for what happens to your comment.
License. OpenProposal Specification by IAB Tech Lab is licensed under a Creative Commons Attribution 3.0 License. To view a copy of this license, visit creativecommons.org/licenses/by/3.0/. See LICENSE.
OpenProposal is the core AAMP 3.0 object: the descriptive specification for what is being bought and sold, engineered so a buying agent can parse and evaluate it at machine speed. It pulls through from initial brief to binding commercial terms, and is the canonical object a seller manages alongside its OMS and ad server.
The key words MUST, MUST NOT, REQUIRED, SHALL, SHALL NOT, SHOULD, SHOULD NOT, RECOMMENDED, MAY and OPTIONAL in this document are to be interpreted as described in RFC 2119.
OpenProposal defines commercial and operational offers across their lifecycle, from proposal creation to binding agreement. It carries both the shape of an offer and the states and interactions that act on it.
Two shapes of the object exist:
- Standard proposal — published unilaterally by the seller's agent. No brief required.
- Custom proposal — built in response to a buyer's agent brief, referenced via
brief_ref.
OpenProposal does not replace IAB Tech Lab execution standards. Each line item
lands in the one its transaction_mechanism maps to:
| OpenProposal concept | Downstream standard |
|---|---|
| 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 |
Proposal → the merchandising pitch, identity, lifecycle state and assent
Line Items[] → fully self-contained buyable units, each in exactly one channel
No-inheritance rule. Each line item MUST be fully self-contained. A field on a line item MUST NOT inherit its value from the proposal or from a sibling line item. There is no "overridable unless restated" semantics anywhere in this specification.
Subsettable proposals. A buyer MAY select and commit a subset of a proposal's line items, unless the proposal explicitly mandates linked execution through its binding terms.
Every line item has two representations.
Summary Representation — what a buying agent reads when screening many sellers' shelves. A catalog response MAY return it. It MUST contain, per line item:
line_item_id · channel · content_detail · property names ·
formats.included[] · pricing.cost_method · pricing.gross_rate ·
min_spend · addressable_scale · committed_metrics[] ·
transaction_mechanism
plus, from the parent proposal: proposal_id · seller_name · description ·
valid_from · valid_until.
catalog_ref values MUST remain unresolved in a summary.
Full Representation — every field, with all catalog_ref values resolved.
A full fetch MUST return it.
Any field whose value is drawn from a seller-maintained catalog MAY carry a reference in place of an inline value:
catalog_ref: "<seller_namespace>/<collection>/<entry_id>@<version>"
example: "examplepub/audiences/smb-decision-makers@3"
- A field MUST carry either an inline value or a
catalog_ref, never both. - A
catalog_refis not inheritance. There are no override semantics, and nothing is implied by a field's position in the document. The no-inheritance rule stands. - References MUST be version-pinned. A catalog entry MUST be immutable once referenced; a change creates a new version.
- References MUST resolve during discovery.
- When a proposal reaches status
agreed, everycatalog_refMUST be replaced by its resolved value in the stored record, so the committed artifact is self-contained and immutable.
Every field carries exactly one marker.
| Marker | Meaning |
|---|---|
immutable |
Set once at creation. Neither party may change it. |
seller-set |
The seller sets it and MAY revise it while status is draft or published. The buyer cannot change it directly. |
selectable |
The buyer chooses from a seller-declared set. Each option MAY carry a price delta. Resolves without seller assent. |
settable |
The buyer supplies a value within seller-declared bounds. Resolves without seller assent. |
supplied |
The buyer provides data or an artifact. The seller does not pre-populate it. |
requestable |
The buyer MAY ask for a value outside the declared space. Requires explicit seller assent. |
derived |
Computed from other fields. Neither party sets it directly. |
Resolution without a round trip. selectable, settable and supplied
fields MUST resolve without a round trip — everything needed to validate and
price them is declared in the proposal. Only requestable fields require the
counterparty to respond.
A field is never silently reverted. An unset selectable or settable field
takes its declared included[] or default, and that default MUST be explicit in
the proposal.
Every field marked selectable MUST declare:
| Key | Required | Meaning |
|---|---|---|
available[] |
REQUIRED | The options the seller offers |
included[] |
REQUIRED | Which options are in the base rate. MAY be empty |
pricing |
OPTIONAL | Delta per option beyond those included, as { option_id: { delta_cpm | delta_flat | delta_pct } } |
max_select |
OPTIONAL | Cap on how many options the buyer may choose |
A field marked selectable that carries a bare value and no available[] MUST
be treated as seller-set.
audience_pricing in earlier drafts was the precedent for this pattern — it
expressed "if you change X, the price moves by Y". pricing generalises it to
every dimension a seller is willing to vary, and replaces it.
Every field marked settable MUST declare its bounds: either allowed[] (an
enumerated set) or min / max (a numeric or date range).
A field marked settable that carries a bare value and no bounds MUST be
treated as seller-set.
Because audiences, properties and formats are selectable, addressable_scale
and availability MUST be recomputed against the buyer's actual selection. A
single declared figure is correct only for the default composition.
The proposal establishes identity, lifespan, negotiation state and the merchandising case for the line items beneath it. Nothing here is a commercial commitment, and nothing here is channel-specific — that all lives on the line items.
| Field | Type | Mutability | Description |
|---|---|---|---|
proposal_id |
String | immutable | Unique identity for the proposal object |
version |
Integer | derived | Monotonically increasing revision number |
seller_id |
String | immutable | IAB Tech Lab registry reference |
seller_name |
String | immutable | Canonical legal seller entity name, from the registry |
type |
Enum | immutable | standard | custom |
status |
Enum | derived | draft | published | under_review | agreed | withdrawn | expired |
assent |
Object | derived | Buyer and seller assent records and timestamps for the binding version. Signature scheme is WS1 |
valid_from · valid_until |
Timestamp | seller-set | ISO-8601 window the offer holds. This is the offer window, not the campaign flight |
brief_ref |
String | supplied | Reference to the buyer's brief. REQUIRED when type is custom |
negotiation_history[] |
Array[Object] | derived | Ordered, append-only. { version, actor: buyer | seller, action: proposed | countered | accepted | declined | withdrawn, fields_changed[], at }. Entries MUST NOT be edited or removed |
The case a buying agent reads to decide whether to look further.
| Field | Type | Mutability | Description |
|---|---|---|---|
description |
String | seller-set | REQUIRED. Prose, not a name. Where the "why buy" case is made, including which channels a multi-channel proposal spans |
specifications |
String/Object | seller-set | Structured detail at media-kit depth |
best_for[] |
Array[String] | seller-set | Advisory planning hints. Non-normative, and never the basis for a match |
not_suitable_for[] |
Array[String] | seller-set | Advisory exclusion guidance. Non-normative |
seasonality_notes |
String | seller-set | When the proposal performs differently |
Everything a buying agent needs to transact, per buyable unit, in exactly one channel.
| Field | Type | Mutability | Description |
|---|---|---|---|
line_item_id |
String | immutable | Identity within the proposal |
status |
Enum | derived | draft | published | under_review | agreed | withdrawn | sold_out | expired. Independent of the proposal's status, so one line item may sell out while the rest of the shelf stays live |
channel |
Enum | immutable | display | video | audio | ctv | dooh | native. The discriminator for how the rest of the line item is read |
| Field | Type | Mutability | Description |
|---|---|---|---|
properties |
Object | selectable | available[] of { id, name, domains, supported_formats }, included[], pricing, max_select |
content_detail |
Object | seller-set | Shows, series, sections, network verticals |
environments |
Object | selectable | available[] from web_desktop | web_mobile | app_mobile | ctv | dooh | audio_streaming |
| Field | Type | Mutability | Description |
|---|---|---|---|
audiences |
Object | selectable | available[] of { id, name, taxonomy_ref, provenance, addressable_scale }, included[], pricing as delta_cpm per option, max_select |
addressable_scale |
Integer | derived | Reachable units in the flight, recomputed against the buyer's selection. Not segment size |
activation_route |
Object | selectable | available[] from publisher_side | clean_room | id_partner, included[], pricing |
buyer_supplied |
Object | supplied | { segments_allowed, suppression_allowed, match_rate_expected, segments[], suppression_list }. match_rate_expected is the seller's stated expectation; segments[] and suppression_list are buyer-populated |
| Field | Type | Mutability | Description |
|---|---|---|---|
formats |
Object | selectable | available[] of registry format ids namespaced by channel (display/300x250, audio/host-read-30s), included[], pricing, max_select |
specs |
Object | seller-set | As defined by the referenced format registry entry. OpenProposal does not hardcode a shape per channel |
third_party_tags |
Object | seller-set | Accepted wrapper types and VAST versions |
creative_delivery |
Object | seller-set | { method: buyer_hosted | seller_hosted | api | platform_managed, protocol, secure_required, endpoint } |
assets[] |
Array[Object] | supplied | Buyer-delivered. { asset_id, format_ref, url_or_tag, submitted_at, approval_status } |
materials_due |
Timestamp/Offset | requestable | Deadline for creative to land. A buyer MAY request a later date |
approval |
Object | seller-set | Review SLA and revision policy |
production |
Object | selectable | Custom units. available[] of build options with pricing and lead_time; { custom_unit, build_owner, lead_time, cost } |
Each field declares bounds, not a value. A bare value means the field is
seller-set.
| Field | Type | Mutability | Description |
|---|---|---|---|
geo |
Object | settable | allowed[] of geo codes the buyer may select within |
device |
Object | settable | allowed[] of device categories |
daypart |
Object | settable | allowed[] of time windows |
frequency_cap |
Object | settable | { max: { min, max }, period, basis: device | household | listener }. Basis matters: CTV typically caps at household, audio at listener |
| Field | Type | Mutability | Description |
|---|---|---|---|
pricing[] |
Array[Object] | mixed | See below |
pricing[].cost_method |
Enum | seller-set | CPM, CPC, flat, etc. |
pricing[].gross_rate |
Number | seller-set | The list rate the buyer pays |
pricing[].agreed_rate |
Number | derived | The transacted rate after selections and any agreed request |
pricing[].seller_rate |
Number | seller-set | What the publisher receives |
pricing[].price_valid_until |
Timestamp | seller-set | How long the posted rate holds. Distinct from the proposal's valid_until |
pricing[].currency |
String | seller-set | ISO-4217 |
pricing[].floor |
Number | seller-set | OPTIONAL. The lowest rate the seller will consider. Enables a requestable counter to be evaluated without a round trip |
availability |
Object | derived | { unit, basis, as_of, granularity, type: forecast | reservable, quantity }. Recomputed against the buyer's selection |
commitment_bounds |
Object | seller-set | The envelope inside which a buyer may commit: { min_impressions, max_impressions, min_budget, max_budget } |
commitment |
Object | settable | The buyer's actual commitment. See 5.6.1 |
hold_status |
Object | requestable | See 5.6.2 |
min_spend |
Number | seller-set | Minimum spend for this line item |
cancellation_policy |
Object | selectable | available[] of cancellation rights with pricing per option |
exclusivity |
Object | selectable | available[] of exclusivity terms with pricing |
creative_policy |
Object | seller-set | Content and category restrictions on creative |
Absent on a published standard proposal. Populated during composition. Frozen
when status becomes agreed.
| Key | Required | Meaning |
|---|---|---|
basis |
REQUIRED | spend | units |
amount |
REQUIRED | The figure committed |
currency |
REQUIRED when basis = spend |
ISO-4217 |
flight_start |
REQUIRED | The buyer's campaign start. Distinct from valid_from |
flight_end |
REQUIRED | The buyer's campaign end. Distinct from valid_until |
derived_units |
derived | Computed from amount, agreed_rate and cost_method. Informative, not binding |
idempotency_key |
REQUIRED | Buyer-generated, unique per commitment attempt |
A request carrying an idempotency_key already seen MUST return the original
result and MUST NOT create a second commitment.
| 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.
| Field | Type | Mutability | Description |
|---|---|---|---|
committed_metrics[] |
Array[Object] | requestable | { metric, value, basis } tuples — viewable_rate for display, completion_rate for video/audio/CTV, an audience-modelled metric for DOOH. Seller declares a ceiling; a buyer MAY request above it |
measurement_source |
Object | selectable | available[] of { vendor, attribution_window, qualifier } with pricing. Lets a buyer transact in its own currency |
shortfall_remedy |
Object | selectable | available[] from makegood | credit | none, plus remedy_terms |
reporting |
Object | selectable | available[] of { feed_type: log_level | aggregate | none, cadence, delivery_mechanism, metrics_provided[], latency } with pricing |
| Field | Type | Mutability | Description |
|---|---|---|---|
visibility |
Enum | seller-set | public | registry_gated | named |
required_credentials[] |
Array[Object] | seller-set | { type: iab_registry | dsp_seat | custom, scheme: api_key | oauth2_client_credentials | jwt_keypair | oidc_federated | none, issuer, id_required } |
eligible_bidders[] |
Array[String] | seller-set | Permitted seat or buyer ids |
terms_acceptance_required |
Boolean | seller-set | Whether legal terms must be accepted before assent |
| Field | Type | Mutability | Description |
|---|---|---|---|
buying_route |
Enum | seller-set | DIRECT_PUBLISHER | PUBLISHER_AD_SERVER | SSP_PLATFORM | DSP | SEARCH_SOCIAL |
transaction_mechanism |
Object | selectable | available[] from DIRECT_IO | PROGRAMMATIC_GUARANTEED | PREFERRED_DEAL | PRIVATE_AUCTION | OPEN_AUCTION. A seller MAY offer the same inventory through more than one |
serving_mode |
Enum | seller-set | client_side | ssai | dai |
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 |
serving_platform |
String | seller-set | Where the line item serves |
| Field | Type | Mutability | Description |
|---|---|---|---|
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 |
correlation_id |
String | derived | One identity for this line item's commitment, carried across every system it touches |
billing |
Object | seller-set | { supplier_id, billing_supplier_id, payment_terms, invoicing_party }. Who delivers is not always who invoices |
supported_order_systems[] |
Array[String] | seller-set | Declared up front so the agent knows it will reconcile |
Runnable copies of every example below live in examples/.
Shows selectable audiences with per-option pricing, a catalog reference, a
buyer commitment, and settable bounds. See
examples/line-item-display.yaml.
See examples/line-item-ctv.yaml.
See examples/line-item-audio.yaml.
- No inheritance mechanism to specify or debug. Fields live at exactly one level, so there is no "overridable unless restated" rule to maintain.
- The proposal reads as a pitch, not a schema dump.
description,specifications,best_forandseasonality_notesare what a model needs to decide whether to look further. - Every line item is independently transactable, in exactly one channel. A buying agent can hand a single line item to a downstream system without resolving proposal-level defaults or cross-channel ambiguity.
- Channel support cost one new field and one generalisation —
channel, andcommitted_metrics[]as tuples — rather than a schema fork per channel. The format registry, not OpenProposal, carries what is channel-specific about creative shape. - Composition resolves without a round trip. Because the seller declares the option set and its pricing up front, a buyer agent performs constraint satisfaction rather than negotiation. Only genuinely out-of-envelope asks require the counterparty to respond.
Comment is specifically invited on the following. Each has a tracking issue — see docs/open-questions.md.
- Does per-field mutability belong in 3.0? Sections 3 and 5 make composition part of the object. The alternative is a catalog format where a buyer selects whole line items and everything else is a round trip.
- Do seller catalogs (
catalog_ref) belong in 3.0, or should the object stay fully inline until a later version? - Cross-channel guarantee comparability is unsolved. Once one line item
guarantees
viewable_rateand anothercompletion_rate, there is no normalised way for a buying agent to compare or optimise across them. Likely WS4, but worth stating rather than pretending 3.0 covers it. - The format registry has no entries for
audio/*,ctv/*ordooh/*.specsdelegates to a registry that cannot currently answer for half the declared channels. - Is a
min_line_itemsfield needed on the proposal to require a bundle purchase, or is that left to buyer discretion? - Should
payment_termsandinvoicing_partybe catalog references rather than repeated on every line item, given they are usually identical across a seller's shelf? environments[]needs validation against real inventory. The enum is proposed, not derived from implementation experience.
Every field in the specification, in document order, with its mutability marker. Generated from the body of this document; if the two disagree, the body is normative.
| Field | Section | Type | Marker |
|---|---|---|---|
proposal_id |
4.1 Identity and lifecycle | String | immutable |
version |
4.1 Identity and lifecycle | Integer | derived |
seller_id |
4.1 Identity and lifecycle | String | immutable |
seller_name |
4.1 Identity and lifecycle | String | immutable |
type |
4.1 Identity and lifecycle | Enum | immutable |
status |
4.1 Identity and lifecycle | Enum | derived |
assent |
4.1 Identity and lifecycle | Object | derived |
valid_from · valid_until |
4.1 Identity and lifecycle | Timestamp | seller-set |
brief_ref |
4.1 Identity and lifecycle | String | supplied |
negotiation_history[] |
4.1 Identity and lifecycle | Array[Object] | derived |
description |
4.2 Agent comprehension | String | seller-set |
specifications |
4.2 Agent comprehension | String/Object | seller-set |
best_for[] |
4.2 Agent comprehension | Array[String] | seller-set |
not_suitable_for[] |
4.2 Agent comprehension | Array[String] | seller-set |
seasonality_notes |
4.2 Agent comprehension | String | seller-set |
line_item_id |
5.1 Identity | String | immutable |
status |
5.1 Identity | Enum | derived |
channel |
5.1 Identity | Enum | immutable |
properties |
5.2 Inventory composition | Object | selectable |
content_detail |
5.2 Inventory composition | Object | seller-set |
environments |
5.2 Inventory composition | Object | selectable |
audiences |
5.3 Audience reach | Object | selectable |
addressable_scale |
5.3 Audience reach | Integer | derived |
activation_route |
5.3 Audience reach | Object | selectable |
buyer_supplied |
5.3 Audience reach | Object | supplied |
formats |
5.4 Creative requirements | Object | selectable |
specs |
5.4 Creative requirements | Object | seller-set |
third_party_tags |
5.4 Creative requirements | Object | seller-set |
creative_delivery |
5.4 Creative requirements | Object | seller-set |
assets[] |
5.4 Creative requirements | Array[Object] | supplied |
materials_due |
5.4 Creative requirements | Timestamp/Offset | requestable |
approval |
5.4 Creative requirements | Object | seller-set |
production |
5.4 Creative requirements | Object | selectable |
geo |
5.5 Targeting envelope | Object | settable |
device |
5.5 Targeting envelope | Object | settable |
daypart |
5.5 Targeting envelope | Object | settable |
frequency_cap |
5.5 Targeting envelope | Object | settable |
pricing[] |
5.6 Commercial terms | Array[Object] | mixed |
pricing[].cost_method |
5.6 Commercial terms | Enum | seller-set |
pricing[].gross_rate |
5.6 Commercial terms | Number | seller-set |
pricing[].agreed_rate |
5.6 Commercial terms | Number | derived |
pricing[].seller_rate |
5.6 Commercial terms | Number | seller-set |
pricing[].price_valid_until |
5.6 Commercial terms | Timestamp | seller-set |
pricing[].currency |
5.6 Commercial terms | String | seller-set |
pricing[].floor |
5.6 Commercial terms | Number | seller-set |
availability |
5.6 Commercial terms | Object | derived |
commitment_bounds |
5.6 Commercial terms | Object | seller-set |
commitment |
5.6 Commercial terms | Object | settable |
hold_status |
5.6 Commercial terms | Object | requestable |
min_spend |
5.6 Commercial terms | Number | seller-set |
cancellation_policy |
5.6 Commercial terms | Object | selectable |
exclusivity |
5.6 Commercial terms | Object | selectable |
creative_policy |
5.6 Commercial terms | Object | seller-set |
committed_metrics[] |
5.7 Guarantees | Array[Object] | requestable |
measurement_source |
5.7 Guarantees | Object | selectable |
shortfall_remedy |
5.7 Guarantees | Object | selectable |
reporting |
5.7 Guarantees | Object | selectable |
visibility |
5.8 Transaction eligibility | Enum | seller-set |
required_credentials[] |
5.8 Transaction eligibility | Array[Object] | seller-set |
eligible_bidders[] |
5.8 Transaction eligibility | Array[String] | seller-set |
terms_acceptance_required |
5.8 Transaction eligibility | Boolean | seller-set |
buying_route |
5.9 Execution | Enum | seller-set |
transaction_mechanism |
5.9 Execution | Object | selectable |
serving_mode |
5.9 Execution | Enum | seller-set |
deal_id_issuance |
5.9 Execution | String | derived |
buyer_requirements |
5.9 Execution | Object | mixed |
serving_platform |
5.9 Execution | String | seller-set |
external_references[] |
5.10 External references and settlement | Array[Object] | derived |
correlation_id |
5.10 External references and settlement | String | derived |
billing |
5.10 External references and settlement | Object | seller-set |
supported_order_systems[] |
5.10 External references and settlement | Array[String] | seller-set |
| Marker | Count | Meaning | Fields |
|---|---|---|---|
| immutable | 6 | Set once at creation. Neither party may change it. | proposal_id, seller_id, seller_name, type, line_item_id, channel |
| seller-set | 29 | Seller sets and may revise while draft or published. |
valid_from · valid_until, description, specifications, best_for[], not_suitable_for[], seasonality_notes, content_detail, specs, third_party_tags, creative_delivery, approval, pricing[].cost_method, pricing[].gross_rate, pricing[].seller_rate, pricing[].price_valid_until, pricing[].currency, pricing[].floor, commitment_bounds, min_spend, creative_policy, visibility, required_credentials[], eligible_bidders[], terms_acceptance_required, buying_route, serving_mode, serving_platform, billing, supported_order_systems[] |
| selectable | 12 | Buyer chooses from a declared set. Resolves without assent. | properties, environments, audiences, activation_route, formats, production, cancellation_policy, exclusivity, measurement_source, shortfall_remedy, reporting, transaction_mechanism |
| settable | 5 | Buyer sets a value within declared bounds. Resolves without assent. | geo, device, daypart, frequency_cap, commitment |
| supplied | 3 | Buyer provides data or an artifact. | brief_ref, buyer_supplied, assets[] |
| requestable | 3 | Buyer may ask outside the declared space. Requires seller assent. | materials_due, hold_status, committed_metrics[] |
| derived | 11 | Computed. Neither party sets it directly. | version, status (proposal), assent, negotiation_history[], status (line item), addressable_scale, pricing[].agreed_rate, availability, deal_id_issuance, external_references[], correlation_id |
| mixed | 2 | Sub-fields carry different markers; see the section. | pricing[], buyer_requirements |
Of 71 fields, 23 are touched by the buyer — 12 selectable, 5 settable, 3
supplied and 3 requestable. The remaining 48 are seller-set, immutable,
derived or mixed.
Only the 3 requestable fields require a round trip. Everything else the buyer
touches — 20 fields — resolves against bounds the seller declared up front. That
ratio is the design claim: composition is constraint satisfaction, and
negotiation is the exception.