Guides

Common OpenRTB bid request mistakes

Ten bid request mistakes that show up in production traffic again and again, each with the exact field path and the fix per the OpenRTB 2.6 spec. Most of them do not throw errors anywhere; they just cost bids from whichever partners enforce the rule.

1. Missing the imp array

A bid request requires exactly two things at the top level: id and imp, an array with at least one Imp object. Requests with an empty imp array, or with a single impression object instead of an array, are invalid. Each Imp also needs its own id, unique within the request, and at least one of banner, video, audio, or native to say what is for sale. Full field list in the bid request reference.

2. Sending at as a string

at is the auction type: an integer, where 1 = First Price, 2 = Second Price Plus, and values of 500 or greater are exchange-specific. It defaults to 2 when omitted, so leaving it out is fine. Sending "at": "2" is not: a typed parser rejects the whole request. The same string-vs-integer bug appears on test, tmax, and the flag fields; JSON has real numbers, use them.

3. Still using imp.video.placement instead of plcmt

The 2.6-202303 update deprecated imp.video.placement in favor of imp.video.plcmt, with new values from the AdCOM 1.0 list Plcmt Subtypes - Video (1 = Instream, 2 = Accompanying Content, 3 = Interstitial, 4 = No Content / Standalone). The two lists do not map one to one, so this is a re-declaration, not a rename. Buyers increasingly key instream classification off plcmt. Details in placement vs plcmt.

4. GDPR flag left at regs.ext.gdpr

OpenRTB 2.6 promoted the GDPR applicability flag from the extension into the core Regs object: it is regs.gdpr (integer, 0 = no, 1 = yes, omitted = unknown). The companion consent string moved too, from user.ext.consent to user.consent. Requests built on 2.5-era code keep writing regs.ext.gdpr, and 2.6-native consumers never see it. The privacy signals reference covers GDPR, us_privacy, and the GPP fields (regs.gpp and regs.gpp_sid, added in 2.6-202211).

5. schain under source.ext.schain instead of source.schain

OpenRTB 2.6 pulled the SupplyChain object into the core spec: it lives at source.schain and is marked recommended. Under 2.5 it traveled as source.ext.schain per the separate SupplyChain object spec. Writing the old path on a 2.6 request, or duplicating the object at both paths with divergent nodes, breaks sellers.json validation downstream. See the supply chain reference.

6. Both site and app present

The spec is blunt: a bid request must not contain more than one of a site, app, or dooh object. They answer the same question (what kind of inventory is this) and a request carrying two of them is ambiguous, so many bidders drop it outright. This usually comes from a request builder that merges publisher defaults with placement data. Pick the one that matches the inventory and omit the others.

7. Assuming bidfloorcur follows the request currency

imp.bidfloor is the minimum CPM, and imp.bidfloorcur is its currency, defaulting to "USD". It does not inherit from cur or from anything else. A floor of 120 that you meant as Japanese yen but sent without "bidfloorcur": "JPY" reads as a 120 USD floor and kills every bid. Deals carry their own independent bidfloor and bidfloorcur on each entry in imp.pmp.deals, with the same USD default. More in the floors reference.

8. Misreading imp.secure

imp.secure is an integer flag: 1 means the impression requires HTTPS creative assets and markup, 0 means non-secure is acceptable. Two mistakes recur. First, sending true instead of 1; it is an integer, not a boolean. Second, assuming omission implies secure: per the spec, if the field is omitted the secure state is unknown and non-secure HTTP support can be assumed. Publishers on HTTPS pages (which is nearly everyone) should send "secure": 1 explicitly.

9. Misunderstanding tmax

tmax is the maximum time in milliseconds the exchange allows for bids to be received, including internet latency. It is set by the exchange, describes the whole round trip, and supersedes any prior guidance. It is not the bidder's compute budget: a bidder that spends tmax milliseconds thinking has already timed out, because the network ate part of the budget. Bidders should also not overwrite it when forwarding a request downstream except to decrease it. More in the tmax reference.

10. Malformed category values in cat and bcat

Content and advertiser categories (site.cat, app.cat, bcat, and its allowlist counterpart acat) are arrays of IAB Tech Lab taxonomy IDs, and the taxonomy in use is declared by the adjacent cattax field, which defaults to 1 (Content Category Taxonomy 1.0, the familiar "IAB1-1" style IDs). The common failures: free-text values like "News", lowercase "iab1", or Content Taxonomy 2.x/3.x IDs sent while cattax is absent and therefore still means taxonomy 1.0. Whatever taxonomy you use, say so in cattax and keep the IDs from that list.

Why these persist

None of these mistakes produce an error message. A request with regs.ext.gdpr or a string at still gets accepted by lenient parsers, so the sender sees traffic flowing and assumes all is well, while stricter partners silently no-bid. Half of the list exists because fields moved between 2.5 and 2.6; the 2.5 vs 2.6 guide maps those migrations in one place.

Validate it

Every mistake on this page is a stable rtblint finding: the moved GDPR flag triggers openrtb.field.moved, the string at triggers a type mismatch, the deprecated placement triggers openrtb.field.deprecated, and site plus app is flagged as mutually exclusive. Paste a request into the bid request tester to check yours client-side, or run the same rules in CI with the CLI.