Guides

First-price vs second-price auctions in OpenRTB

The auction type decides what a winning bidder actually pays, and it is one integer in the bid request. OpenRTB itself is neutral about which auction you run; it just carries the type and reports the clearing price. Here is what first-price and second-price mean, the field that signals them, and why almost the whole industry moved to first-price.

The two auction types

  • Second-price (second price plus). The highest bidder wins but pays just above the second-highest bid, not their own bid. Bidders can bid their true value, because they will not overpay: the price is set by the competition, not by them.
  • First-price. The highest bidder wins and pays exactly what they bid. There is no discount from the runner-up, so bidding your true value means paying your true value.

The practical difference is bidder behavior. Under second-price, the winning strategy is to bid what the impression is worth to you. Under first-price, bidders lower (or "shade") their bids toward what they think it will take to win, because every dollar bid is a dollar paid.

The field: BidRequest.at

The auction type lives in one attribute at the top of the request:

at  (integer, default 2)
  1  = First Price
  2  = Second Price Plus
  500+ = exchange-specific auction types

The default is 2, a historical artifact from when second-price was the norm. An exchange running a first-price auction must set at: 1 explicitly. A deal can also override the request-level type: the Deal object has its own at, and a value of 3 there means the price in bidfloor is the agreed, fixed deal price rather than an auction outcome. Sending at as a string, "1", is a common and silent error; it is an integer.

Why the industry moved to first-price

Second-price made sense when one exchange saw all the demand. Header bidding broke that: once an impression is auctioned in several places at once, a second-price auction in each silo produces prices that are neither comparable nor trustworthy, and sellers suspected their clearing prices were being manipulated. First-price is transparent by comparison, you win, you pay your bid, so publishers and exchanges shifted to it through 2017 to 2019. Google Ad Manager, the last major holdout, moved to a unified first-price auction in 2019. First-price is now the default reality of open-market programmatic, even though the OpenRTB field still defaults to 2.

Reading the clearing price

Whatever the auction type, the winning bidder learns the final price through substitution macros in the win, billing, and loss notice URLs:

  • ${AUCTION_PRICE}: the clearing price, in the bid's currency and units. In a first-price auction this equals the bid; in second-price it is lower.
  • ${AUCTION_MIN_TO_WIN}: the minimum bid that would have won. Added in OpenRTB 2.6, it is most useful in first-price auctions, where it helps a bidder calibrate future bid shading without the exchange revealing other bidders' prices.

Exchange policy can strip ${AUCTION_PRICE} to a zero-length string if it declines to disclose the clearing price, so a bidder should handle an empty expansion gracefully rather than assume a number is always there.

Side by side

 Second-price (at=2)First-price (at=1)
Winner paysJust above the second bidTheir own bid
Optimal strategyBid true valueShade toward expected clearing price
TransparencyPrice set by hidden competitionWin equals pay
Prevalence todayLegacy and some private dealsOpen-market default

What to check

If you run an exchange, make sure at reflects the auction you actually run; a first-price exchange still emitting the default 2 is misinforming every bidder. If you bid, confirm your response handling reads ${AUCTION_PRICE} correctly and tolerates an empty value. Validate a request or response in the tester, and see the macro reference for the full expansion table.

Sources