Blog · Protocol
OpenRTB 2.5 vs 2.6 is a version string that says 2.6 and objects that still live under ext.
OpenRTB 2.5 vs 2.6 is close enough that a request still parses, and different enough that a 2.6 reader never sees the GDPR flag, the consent string, or the supply chain. The version string moved. The objects stayed under ext.
The structures are close, which is why the no-bid is quiet
A valid 2.5 request is structurally close to a valid 2.6 request. That sentence is why exchanges bump a constant to 2.6 and leave the builder alone. Lenient partners still bid. Strict partners read regs.gdpr, user.consent, source.schain, and user.eids, find them absent, and no-bid.nbr does not name the path you forgot to move.
In 2.5 those signals lived in extensions: regs.ext.gdpr, user.ext.consent, source.ext.schain, user.ext.eids. 2.6 promoted them. Writing only the old path, or writing both paths with different nodes, is openrtb.field.moved when you validate against a 2.6 snapshot. A 2.5 pin still treats the extension as the place the object lived. The same file is a different finding under the two versions. The field table is the 2.5 vs 2.6 guide. This is the request that survives the version bump.
2.6 also removed fields that 2.5 had already deprecated: banner.wmax, hmax, wmin, hmin, video.protocol, and content.videoquality. A builder that still emits them is not sending a 2.6 request with extra hints. Those keys are gone. Deprecated in 2.6, and still commonly populated: video.sequence, audio.sequence, the hashed device ids (didsha1, didmd5, dpidsha1, dpidmd5, macsha1, macmd5), user.yob, user.gender, and bid.api.
2.6 is a series of dated snapshots, not one document from April 2022
Since 2.6-202211 the version number stays put unless a change is breaking. New fields arrive as dated updates. plcmt and GPP are 2.6 fields that are not in the original April 2022 text. A partner who says they are on 2.6 has not named a snapshot. regs.gpp and regs.gpp_sid arrived in 2.6-202211. video.plcmt arrived in 2.6-202303 and deprecated placement. The enums do not map one to one. Copying the old integer into plcmt classifies the impression as a different subtype.
Later updates kept moving the same minor version. 2.6-202309 added durfloors. 2.6-202402 added poddedupe. 2.6-202409 added inserter, matcher, and mm on EID. 2.6-202501 added gtax and genres. 2.6-202505 added cids on Data and corrected genres to a string array. Pin --version 2.6-202505, or whichever snapshot you actually speak. Without a pin, the CLI uses the latest tracked 2.6 snapshot, and a Tuesday release can flag a field your Monday fixture never had.
Validating that file as 2.5 hides the move. Validating it as 2.6-202211 sees GPP and still does not require plcmt, because plcmt is not in that snapshot. Validating it as 2.6-202505 sees the placement deprecation. Three greens are three different contracts. Put the snapshot in the CI step next to the file.
What a 2.6 reader does not inherit from the extension
| 2.5 path still in the builder | 2.6 path a current reader uses |
|---|---|
regs.ext.gdpr | regs.gdpr (0, 1, or omitted for unknown) |
user.ext.consent | user.consent |
source.ext.schain | source.schain |
user.ext.eids | user.eids |
video.placement | video.plcmt from 2.6-202303, new enum |
GDPR omitted means unknown, the same as a flag that never left ext from the reader's point of view. Consent omitted means the TCF string was not on the core object. A supply chain that exists only under ext is invisible to a sellers.json check that reads source.schain. Duplicate the object at both paths only while you are sure the nodes match. Divergent nodes are worse than a single old path, because each reader trusts a different chain.
cattax declares the taxonomy. It defaults to 1, Content Category Taxonomy 1.0. Taxonomy 2 or 3 ids with cattax omitted are the wrong list on a 2.6 request and were simply assumed on 2.5. device.sua is the structured user agent beside device.ua. imp.rwdd and imp.ssai are core, where 2.5 used exchange extensions. Pod fields (podid, podseq, slotinpod, maxseq, poddur, mincpmpersec, rqddurs) are how 2.6 describes a CTV break that 2.5 could only express as one impression per slot.
Pin the snapshot on the builder output, not on a repaired sample
- uses: aleksUIX/rtblint@v0.13.3
with:
path: fixtures/bid-request.json
spec-version: 2.6-202505
dialect: spec-jsonGenerate that fixture from the builder. A file someone cleaned during the migration review, with regs.gdpr typed in by hand, stays green while production still writes regs.ext.gdpr. Exit 0 means no error-severity finding. Warnings do not fail the job. Exit 1 is a spec error. Exit 2 is usage or I/O. Spec JSON wants integer flags. regs.gdpr: true is protobuf JSON. The dialect flag is part of the version question, because a bool GDPR flag fails Spec JSON even after you moved it out of ext.
Keep a second fixture that must fail on 2.6-202505: regs.ext.gdpr present and regs.gdpr absent, banner.wmax still set, video.placement set and plcmt absent. If that file exits 0, you are pinned to the wrong snapshot or you are not running the file the builder emits. The CI guide is the action contract. The failure mode is a green job on a sample the builder does not return.
The bid request tester runs the same engine. Set the snapshot and Spec JSON before you read a pass. Payloads you paste may be stored with device ids, IPs, and consent strings stripped. A capture that still has device.ifa belongs on the CLI, which does not send the file. RTBlint is not an IAB validator. A pass means the artifact matches the snapshot you selected. It does not mean the bidder filled.
Enums left the OpenRTB document, and the bid gained a type
2.6 deleted its own Section 5. Device types, API frameworks, creative attributes, and placement subtypes now live in AdCOM 1.0, so a new enum value can ship without a new OpenRTB minor version. No-bid and loss reason codes live in the OpenRTB 3.0 document. A validator still aimed at the 2.5 tables will accept a placement integer 2.6 later deprecated, and will reject a plcmt value that only exists on the AdCOM list. Point enum checks at AdCOM when the snapshot is 2.6.
mtype on the bid tells the exchange which markup came back. 2.5 inferred that from which object you filled. A response checked only against a 2.5 fixture will not require mtype. kwarray is a string array beside the old comma-separated keywords string, on Site, App, Content, and User. BCP 47 language sits on langb. None of those appear because you changed the version constant. They appear because the builder writes them.
Pod bidding is the CTV case of the same gap. 2.5 had one impression per slot and no pod. 2.6 groups impressions with podid, places the pod with podseq, and uses slotinpod when the seller can promise first or last. A dynamic pod sells time: poddur, maxseq, mincpmpersec, and rqddurs. durfloors is the 2.6-202309 addition, a floor by duration range. An exchange extension that used to carry the break is not these fields. A bidder that only reads the extension will price a pod as a single impression.
What to move before you call the exchange 2.6
- Move
regs.ext.gdprtoregs.gdpr,user.ext.consenttouser.consent,source.ext.schaintosource.schain, anduser.ext.eidstouser.eids. - Name the snapshot. Add
regs.gppandregs.gpp_sidwhen you claim 2.6-202211 or later. Addplcmtwhen you claim 2.6-202303 or later, from the AdCOM list, not by copyingplacement. - Stop sending
banner.wmax,hmax,wmin,hmin,video.protocol, andcontent.videoquality. - Stop populating hashed device ids,
user.yob, anduser.genderon new integrations. - Declare
cattaxwhen category ids are not taxonomy 1.0. - For CTV, send the pod fields instead of an exchange extension that 2.5 required and 2.6 replaced.
- Pin
spec-versionin CI on the builder output. A repaired sample is a different program.