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
bidding.max_bid is a hard per-auction ceiling, not an average cost target,
realized clearing price, or guarantee of delivery.
- 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.
- 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.
- Create and update responses, plus
get_media_buys, must read back the
effective cap exactly.
bidding: null on update clears the authored cap. Omission leaves it
unchanged.
- 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
Problem
AdCP already represents outcome optimization cleanly through
optimization_goals:target.kind: cost_perexpresses a cost-per-result target; andtarget.kind: per_ad_spendexpresses a return-on-ad-spend target.AdCP also has an existing representation for a per-auction ceiling: when the
selected
PricingOptionadvertisesmax_bid: true, packagebid_priceis thebuyer'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, whosemeaning changes based on seller metadata:
PricingOption.max_bid: true, it is a hard auction ceiling;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:
The exact field name and whether this should be a reusable
BiddingPolicytype 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_perandOptimizationGoal.target.per_ad_spendunchanged. They describe the outcomeobjective; the auction ceiling is an optional execution constraint that may be
composed with the primary optimization goal.
Semantics
bidding.max_bidis a hard per-auction ceiling, not an average cost target,realized clearing price, or guarantee of delivery.
valid only for an auction-priced option that supports buyer-specified bid
ceilings.
mutation. Unsupported caps fail closed; they must not be converted to
cost_peror ignored.get_media_buys, must read back theeffective cap exactly.
bidding: nullon update clears the authored cap. Omission leaves itunchanged.
extas diagnosticmetadata, but
extmust not be required to express the canonical policy.Backward compatibility with #1163
bid_priceremains accepted during migration and normalizes through the samecanonical path:
bid_price: Xwith selectedPricingOption.max_bid: truebidding.max_bid: Xbid_price: Xwith absent/falsePricingOption.max_bidIf both legacy
bid_priceand the new explicit cap are supplied for oneeffective package, reject the request as ambiguous rather than defining
precedence. There must not be two independent cap values or two execution
paths.
PricingOption.max_bidremains useful as the seller's capability declaration;the new field is the buyer's explicit instruction.
Acceptance criteria
update and an exact corresponding read shape.
ROAS target is normative.
bid_price + PricingOption.max_bidhas a deterministic normalizationrule and cannot create dual semantics.
OptimizationGoal, including acost_pergoal, 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_bidtoday.Related
PricingOption.max_bidand the legacy ceilinginterpretation of package
bid_price.