Skip to content

RFC(media-buy): add an explicit buyer-authored per-auction bid cap #6106

Description

@bokelley

Problem

AdCP already represents outcome optimization cleanly through
optimization_goals:

  • no target means maximize volume within budget;
  • target.kind: cost_per expresses a cost-per-result target; and
  • target.kind: per_ad_spend expresses a return-on-ad-spend target.

AdCP also has an existing representation for a per-auction ceiling: when the
selected PricingOption advertises max_bid: true, package bid_price is the
buyer's maximum willingness to pay rather than an exact/manual bid. That
behavior was introduced in #1163.

The remaining gap is not that AdCP cannot carry a maximum bid at all. The gap
is that buyer-authored bidding policy is overloaded onto bid_price, whose
meaning changes based on seller metadata:

  • with PricingOption.max_bid: true, it is a hard auction ceiling;
  • otherwise, it is an exact/manual bid or price.

That makes normalization and exact readback ambiguous for buyers and adapters,
especially when an outcome goal and a per-auction ceiling need to coexist. A
cost-per target must not be silently interpreted as a bid cap, and a bid cap
must not be translated into an average-cost control.

Proposed narrow addition

Add an explicit buyer-authored bidding-policy shape for a hard per-auction
ceiling on package create, update, and read models. For example:

type PackageBiddingPolicy = {
  /**
   * Maximum amount the seller/provider may submit in one auction, expressed
   * in the selected pricing option's currency and auction unit.
   */
  max_bid: number
}

type PackageRequest = {
  // existing fields
  bidding?: PackageBiddingPolicy
}

type PackageUpdate = {
  // existing fields
  bidding?: PackageBiddingPolicy | null
}

The exact field name and whether this should be a reusable BiddingPolicy
type are open to maintainer preference. The important semantic requirement is
that the canonical buyer input explicitly means a per-auction ceiling.

This proposal deliberately leaves OptimizationGoal.target.cost_per and
OptimizationGoal.target.per_ad_spend unchanged. They describe the outcome
objective; the auction ceiling is an optional execution constraint that may be
composed with the primary optimization goal.

Semantics

  1. bidding.max_bid is a hard per-auction ceiling, not an average cost target,
    realized clearing price, or guarantee of delivery.
  2. Its currency and unit come from the selected pricing option. The field is
    valid only for an auction-priced option that supports buyer-specified bid
    ceilings.
  3. Sellers must validate support and all combinations before any provider
    mutation. Unsupported caps fail closed; they must not be converted to
    cost_per or ignored.
  4. Create and update responses, plus get_media_buys, must read back the
    effective cap exactly.
  5. bidding: null on update clears the authored cap. Omission leaves it
    unchanged.
  6. Provider-specific native fields may be returned under ext as diagnostic
    metadata, but ext must not be required to express the canonical policy.

Backward compatibility with #1163

bid_price remains accepted during migration and normalizes through the same
canonical path:

Legacy input Canonical interpretation
bid_price: X with selected PricingOption.max_bid: true bidding.max_bid: X
bid_price: X with absent/false PricingOption.max_bid Existing exact/manual bid semantics; not a bid cap

If both legacy bid_price and the new explicit cap are supplied for one
effective package, reject the request as ambiguous rather than defining
precedence. There must not be two independent cap values or two execution
paths.

PricingOption.max_bid remains useful as the seller's capability declaration;
the new field is the buyer's explicit instruction.

Acceptance criteria

  • The schema has a canonical buyer-authored per-auction ceiling on create and
    update and an exact corresponding read shape.
  • The distinction among per-auction cap, exact/manual bid, cost-per target, and
    ROAS target is normative.
  • Legacy bid_price + PricingOption.max_bid has a deterministic normalization
    rule and cannot create dual semantics.
  • Unsupported or conflicting inputs are rejected before mutation.
  • Examples show a cap composed with an OptimizationGoal, including a
    cost_per goal, without changing the meaning of either field.

Placement follow-up

This issue intentionally asks for the smallest missing canonical primitive.
Some providers place bidding policy above a package (for example campaign
budget optimization or portfolio strategies). A broader media-buy/package
inheritance model can build on this type, but should not block making the
per-auction cap unambiguous at the package level where it is already supported
by bid_price + PricingOption.max_bid today.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    claude-triagedIssue has been triaged by the Claude Code triage routine. Remove to re-triage.media-buyIssue concerns the media-buy protocol domainrfcProtocol change — auto-adds to roadmap boardspec / protocol

    Type

    No type

    Projects

    Status
    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions