OpenRTB for ad ops and product managers
You do not need to write a bidder to read a bid request. Most revenue questions in programmatic, such as why a partner stopped bidding, why a deal does not deliver, or why two reports disagree, come down to a handful of fields. This page explains those fields in plain terms, the signals buyers filter on, and how to check a payload yourself before escalating.
What ad ops owns
Engineers build the integration, but most of the values inside it come from settings that ad ops and product teams control.
- Configuration that becomes fields. Floors, deals, blocked advertisers and categories, ad unit sizes and formats, and the auction type.
- Inventory declarations. ads.txt, app-ads.txt, and sellers.json entries, which buyers compare with what each request says.
- Partner setup. Which OpenRTB version each partner speaks, which extra fields it needs, and when test traffic is switched to live.
- Reconciliation. Which notice each side counts as an impression, and in which time zone.
- Triage. Reading a captured request, a response, or a reason code to decide whether a problem is configuration, demand, or code.
Reading a bid request in two minutes
A bid request is one JSON object that a seller sends to buyers for each ad opportunity. It always has the same parts: the auction rules at the top, the impressions for sale in imp, where the ad runs (site, app, or dooh), the device and user, the regulatory signals in regs, and the sales path in source. Here is a connected TV request for a live sports stream:
{
"id": "ctv-8f21c4a0",
"at": 1,
"tmax": 300,
"cur": ["USD"],
"badv": ["rivalbet.example"],
"imp": [
{
"id": "1",
"bidfloor": 18.00,
"bidfloorcur": "USD",
"video": {
"mimes": ["video/mp4"],
"plcmt": 1,
"startdelay": -1,
"minduration": 15,
"maxduration": 30,
"protocols": [7, 8],
"w": 1920,
"h": 1080
},
"pmp": {
"deals": [{ "id": "pg-sports-q4", "bidfloor": 32.00, "at": 3, "wseat": ["agency-311"] }]
}
}
],
"app": {
"bundle": "com.example.sportsnow",
"storeurl": "https://play.google.com/store/apps/details?id=com.example.sportsnow",
"publisher": { "id": "pub-1180" },
"content": { "genre": "Sports", "livestream": 1, "language": "en" }
},
"device": {
"devicetype": 3,
"ip": "198.51.100.77",
"os": "Android",
"ifa": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"lmt": 0
},
"regs": { "coppa": 0 },
"source": {
"schain": {
"ver": "1.0",
"complete": 1,
"nodes": [{ "asi": "ssp.example", "sid": "pub-1180", "hp": 1 }]
}
}
}| What you see | What it means |
|---|---|
"at": 1 | First-price auction: the winner pays what it bid. |
"tmax": 300 | Buyers have 300 milliseconds to answer, network time included. |
"bidfloor": 18.00 | Open market bids under 18 dollars CPM are rejected. |
"plcmt": 1, "startdelay": -1 | Instream video, played in a mid-roll break. |
The deal with "at": 3 | A fixed-price deal at 32 dollars CPM, open only to seat agency-311. |
"devicetype": 3 | Connected TV. |
"livestream": 1 | The content is live, not on demand. |
"complete": 1 | The seller claims the sales path below is the whole path. |
A field that is missing is not the same as zero. Many fields have a default, so leaving out at means second price, and leaving out bidfloorcur means US dollars. The spec reading guide explains defaults in more depth.
The fields that move revenue
| Field | Plain meaning | Why revenue depends on it |
|---|---|---|
imp.bidfloor, imp.bidfloorcur | Minimum price per thousand impressions, and its currency | Set too high, bids disappear; a currency mix-up silently changes the floor. |
imp.pmp.deals | The deals offered on this impression, each with its own floor and buyers | A deal ID that does not match the buyer's booking never delivers. |
at | First price (1) or second price (2) | Buyers shade their bids differently in each; the wrong value misleads them. |
imp.banner.format, imp.video.maxduration | The sizes and ad lengths the slot accepts | Every extra size or length that fits brings in more eligible demand. |
imp.video.plcmt | The kind of video placement: instream, interstitial, and so on | Buyers target and exclude by placement, so a wrong value hides inventory. |
device.devicetype | Phone, tablet, computer, connected TV, and others | CTV campaigns look for 3 (Connected TV) or 7 (Set Top Box). |
app.bundle, site.domain | Which app or website the ad runs in | Buyer allow and block lists are keyed on these values. |
user.eids | Identity provider IDs for this user | Without a usable ID, fewer buyers bid and those who do bid lower. |
tmax | How long buyers have to answer | Too short, and distant buyers time out instead of bidding. |
source.schain | Every company in the sales path | Buyers discount or skip paths they cannot verify. |
The floors and deals rows are covered in depth by the floors reference and the deals guide.
Signals buyers filter on before they price
A buyer runs gates before it thinks about price. When a gate fails, a well-behaved buyer answers with a no-bid reason code in BidResponse.nbr, which makes the gate visible to you.
| Gate | What the buyer checks | No-bid reason when it fails |
|---|---|---|
| Valid request | The payload parses and follows the spec | 2 Invalid Request |
| Real traffic | User agent, IP address, and behavior look human | 3 Known Web Crawler, 4 Suspected Non-Human Traffic, 5 Cloud, Data Center, or Proxy IP |
| Supported device | The buyer runs campaigns for this device or OS | 6 Unsupported Device |
| Allowed seller | The site, app, or publisher is not on the buyer's block list | 7 Blocked Publisher or Site |
| Known user | A user ID the buyer can match | 8 Unmatched User |
| Authorized seller | The seller appears in the publisher's ads.txt or app-ads.txt | 11 Ads.txt Authorization Unavailable, 12 Ads.txt Authorization Violation |
| Verifiable path | The supply chain is complete and every node is acceptable | 16 Incomplete SupplyChain, 17 Blocked SupplyChain Node |
| Enough time | The time budget covers the buyer's own work | 15 Insufficient Auction Time |
Privacy signals act as a gate too. If regs.gdpr is 1 and the consent string is missing, or a regs.gpp string signals an opt-out, many buyers bid without personal data or not at all. All codes are in no-bid reason codes.
Reading what comes back
Three kinds of feedback reach ad ops. No-bid reasons come from buyers, as above. Loss reasons come from the exchange to each buyer that lost, and buy-side teams use them to tell pricing problems (100 Below Auction Floor, 102 Lost to Higher Bid) from creative problems (the 200 range). Notices count the money: the win notice nurl fires when a bid wins, and the billing notice burl fires when the impression becomes billable.
Most discrepancies between a buyer's report and a seller's report trace back to those two notices. If one side counts wins and the other counts billable impressions, every won ad that never rendered becomes a gap. Agree with each partner which notice defines an impression, and in which time zone the reporting day ends. The loss reason codes guide covers the buyer side of this feedback.
Symptoms and where to look first
| Symptom | Check first | Usually means |
|---|---|---|
| A deal has requests but no bids | Deal ID, deal floor and its currency, the seat list | The deal setup does not match the buyer's booking |
| Bid rate fell right after a release | A captured request in a validator; nbr 2 counts | The request became malformed |
| A buyer bids in testing but not live | Consent fields, ads.txt lines, supply chain | A gate that test traffic skipped |
| CTV inventory earns mobile prices | devicetype, plcmt, the app bundle | The inventory is classified as something else |
| Many losses at 100 Below Auction Floor | Floor level and currency | Floors are above what buyers value, or in the wrong currency |
| Partner reports far more wins than billed impressions | Which notice each side counts | Wins counted on nurl, billing on burl |
| High timeout rate for one partner | tmax and the partner's server region | The time budget is too thin for that route |
For the full fill-rate funnel, see why is my fill rate low.
Questions to settle with every partner
- Which OpenRTB version and dated release do you send or expect?
- Which extension fields under
extdo you require, and where are they documented? - Will you return no-bid reason codes during testing?
- What event fires the billing notice, and do you send loss notices?
- Which currencies do you accept, and which currency are floors in?
- How are deal IDs and seat IDs formatted, and who creates them?
- Which time zone do your reports use?
The answers belong in the integration notes for that partner. Many exchanges publish their own requirements, and the exchange notes collect the documented ones.
Checking a payload yourself
Ask engineering or your partner for a captured request, taken after the seller has added everything it adds. A sample from documentation shows what was intended, not what was sent. Read it against the tables above, then run it through a validator to catch wrong types, unknown fields, and outdated paths. For live checks that cost nothing, a request with test set to 1 runs a full auction that neither side bills.
To see a complete CTV pod request, open the CTV video pod sample in the tester, then paste your captured request in its place.
Questions people ask
Which OpenRTB fields affect revenue the most?
Floors and their currency (imp.bidfloor and imp.bidfloorcur), deal terms in imp.pmp.deals, the auction type in at, the formats on offer (banner sizes, video durations, and plcmt), device.devicetype, the inventory identity in site or app, user identifiers such as user.eids, the tmax time budget, and the supply chain in source.schain. Each one decides how many buyers can bid and what they are willing to pay.
Why do DSP and SSP impression counts differ?
Usually because the two sides count different events. A win notice (nurl) fires when a bid wins the auction, while the billing notice (burl) fires only when the impression becomes billable, typically on render. If one side counts wins and the other counts billable impressions, every won but unrendered ad becomes a gap. Agree that both sides count burl, and agree on the time zone that cuts the reporting day.
How can ad ops find out why a buyer is not bidding?
Ask the buyer to return no-bid reason codes in nbr during testing. Code 2 means the request was invalid, 8 means no usable user ID, 11 and 12 point at ads.txt, and 16 and 17 point at the supply chain. Then check the captured request yourself against the spec and the buyer's documented requirements before changing floors or targeting.