What is bid shading and how does it work?
Short answer
Bid shading is a buy-side pricing technique that submits a bid below the buyer's true maximum, estimating the lowest price that still wins. It exists because the industry moved from second-price to first-price auctions: under first price (OpenRTB at=1) the winner pays exactly what it bid, so bidding your full valuation means overpaying on every win. OpenRTB has no bid shading field. The model lives inside the DSP and is trained on auction feedback, mainly loss notices (lurl) carrying ${AUCTION_MIN_TO_WIN} and loss reason codes.
The auction change that created it
Under a second-price auction the winner pays just above the second-highest bid, so a buyer can safely bid its true valuation: the mechanism itself removes the overpayment. When the industry converted to first price, that protection disappeared. The winner now pays exactly what it submitted, so a buyer bidding its maximum on every impression pays its maximum on every impression it wins.
In OpenRTB the auction type is declared in BidRequest.at: 1 is first price, 2 is second price plus, and 3 means the value in imp.bidfloor is the agreed deal price. Full semantics and the history of the transition are in first-price versus second-price auctions.
What a shading model is actually estimating
A shading model answers one question per impression: what is the lowest price that still wins? That requires a win probability curve, the chance of winning at each candidate price for this kind of impression on this supply path. The bidder then picks a point on the curve that maximises expected value, given how much it wants the impression.
The hard part is that auction data is censored. You observe the clearing price when you win, and by default nothing at all when you lose. That is why loss feedback matters so much: without it, a bidder only learns about the region of the price curve where it already wins.
The OpenRTB signals that feed it
| Signal | Where | What it tells a shading model |
|---|---|---|
at | Bid request | Whether shading applies at all: pointless under a true second-price auction |
imp.bidfloor, bidfloorcur | Bid request | The hard lower bound; shading below it wastes the bid |
${AUCTION_PRICE} | Win notice (nurl or burl) | The clearing price on wins, the observed half of the curve |
${AUCTION_LOSS} | Loss notice (lurl) | Why the bid lost: price (102), floor (100 or 101), filtering (2xx) |
${AUCTION_MIN_TO_WIN} | Loss notice (lurl) | The minimum price that would have won, the censored half of the curve |
Distinguishing loss reasons matters more than the raw loss count: a bid filtered for a creative attribute (loss code 210) carries no pricing information at all, and training on it teaches the model to shade down for no reason. The full tables are in loss reason codes and the macro reference.
Where shading gets contentious
Buy-side shading inside your own DSP is uncontroversial: you are pricing your own money. The disputes are about shading applied somewhere in the middle, where an intermediary reduces the bid it passes on and keeps part of the difference, or where a fee is charged on the pre-shaded price. Both are business-model questions, not protocol questions: OpenRTB carries the price that was submitted and the price that cleared, and nothing in the payload reveals a margin taken between them.
That is the practical link to supply path optimization: comparing the same source.tid across paths is how buyers detect the gap without needing anyone's cooperation.
Before you blame the model
A drop in win rate is often mistaken for shading gone wrong when the real cause is a malformed or incomplete request that never got priced properly. Check the payload first: the tester flags missing floors, non-positive tmax, currency format errors, and media-type mismatches that all depress win rate independently of price. See also why is my fill rate low.