Why is my fill rate low?
Short answer
Low fill means bidders are getting your requests and passing, so the question is which filter they hit. Check in this order: floors set above the market clearing price; missing or malformed required fields, which get a request dropped before it is ever priced; stale media-type signalling, above all video.placement without video.plcmt; privacy signals that are absent or self-contradictory, which make buyers drop EEA traffic; a tmax too tight for the path; and blocked categories, advertisers, or seats. Validate one real request before reading any dashboard.
Split the funnel before changing anything
"Low fill" describes an outcome, not a cause. Four different failures produce the same number, and each has a different fix:
- The request never reaches bidders, or reaches them malformed and gets discarded before decisioning.
- Bidders receive it and pass. They responded in time and chose not to bid.
- Bidders bid and lose, to a floor, to a deal, or to another buyer.
- A bid wins and the impression never renders, so it is never counted.
Bid rate separates stage one from stage two. No-bid reason codes separate two from three. Discrepancy between your win count and the buyer's impression count points at four. Everything below assumes you have split the funnel first.
The order to check things
| Check | Symptom | Where to look |
|---|---|---|
| Payload validity | One partner reports errors while your logs look clean | Validate the exact bytes you send, after serialization and compression, in the tester |
| Required fields | Systematic no-bids across every partner | id, imp[], an imp.id per impression, a media type object on every impression |
| Media-type signalling | Video and CTV fill far below display | video.plcmt, mimes, protocols, minduration and maxduration, pod fields |
| Privacy signals | Fill collapses on EEA or specific US state traffic | regs.gdpr with user.consent, regs.gpp with regs.gpp_sid, regs.coppa |
| Floors | Healthy bid rate, low win rate, bids clustered under the floor | imp.bidfloor, bidfloorcur, deal floors, durfloors on video and audio |
| Timeouts | Responses arrive but arrive late | tmax against real round-trip latency per bidder |
| Blocklists | Specific advertisers or categories never appear | bcat, badv, bapp, wseat, bseat |
| Supply chain | One path fills, an identical path does not | source.schain versus ads.txt and sellers.json |
The failures that hide as demand problems
Some breakage produces no error anywhere. OpenRTB parsers are required to ignore fields they do not recognise, so a field in the wrong place is not rejected: it is silently dropped, and the request continues without the signal. Three examples that cost real fill:
- Privacy in the old path.
regs.ext.gdpranduser.ext.consentwere promoted to first-class fields in 2.6. A buyer reading only the new paths sees consent as absent and stops bidding, while your logs show a perfectly well-formed request. - Deprecated video placement.
video.placementwithoutvideo.plcmt. Covered in placement versus plcmt. - Type drift.
"at": "1"as a string instead of an integer, a boolean where the spec says integer, a single object where an array is required. Strict parsers reject the request; lenient ones coerce it unpredictably.
These are exactly what a validator finds in seconds and what dashboards never show. The catalogue is in common OpenRTB mistakes.
Ask the bidders why
A no-bid can carry a reason. BidResponse.nbr holds a code such as 2 (invalid request), 6 (unsupported device), 8 (unmatched user), 12 (ads.txt authorization violation), or 16 (incomplete supply chain), and the full table is in no-bid reason codes. Not every bidder populates it, but the ones that do turn a guessing exercise into a lookup. A concentration of code 2 across partners is a payload problem, not a pricing one.
A ten-minute diagnostic
- Capture one real bid request for an affected placement, exactly as sent.
- Run it through the tester against the OpenRTB version your partners actually use, and fix every error before reading anything else.
- Compare the request against a known-good example of the same media type, field by field, looking for what is absent rather than what is wrong.
- Segment fill by bidder, by media type, by geo, and by device to localise the drop.
- Only then move floors, and move them one variable at a time.
- Put the fixed request in CI as a fixture so the regression cannot come back: validation in CI.