OpenRTB basics and the bid request
The protocol, the auction, and the structure of a bid request: required fields, objects, and the conventions every integration relies on.
OpenRTB is the IAB Tech Lab standard that defines the JSON messages ad exchanges and bidders use to buy and sell impressions in real time. An exchange sends a BidRequest describing the impression, site or app, device, user, and applicable regulations; each bidder answers with a BidResponse carrying a CPM price and creative markup, all within a deadline set by tmax. Nearly every programmatic integration between an SSP or exchange and a DSP runs on OpenRTB 2.x, with 2.6 as the current version. OpenRTB 3.0 is a separate, non-backwards-compatible spec with much lower adoption.
OpenRTB docs · OpenRTB versions
A user loads a page or app, the publisher's ad system asks an exchange to sell the impression, and the exchange sends an OpenRTB BidRequest over HTTP POST to many bidders at once. Each bidder evaluates the request and returns a BidResponse with a price and creative before the tmax deadline; late or empty responses are treated as no-bids. The exchange picks a winner according to the auction type in the at field (default 2, second price plus), fires the winner's win notice (nurl) and billing notice (burl) at the appropriate events, and the creative renders. The whole loop typically completes in a few hundred milliseconds.
How tmax works · Bid request reference
OpenRTB is a wire protocol; Prebid.js is a piece of software. OpenRTB defines the JSON bid request and response format that servers exchange, while Prebid.js is Prebid.org's open-source header bidding wrapper that runs in the browser and collects bids from demand partners before the ad server call. They are complementary, not competing: Prebid Server and many Prebid adapters speak OpenRTB 2.x to demand partners under the hood. If you are a publisher wiring up demand on a page, you use Prebid; if you are building an exchange or DSP endpoint, you implement OpenRTB.
OpenRTB docs
From the protocol's point of view there are only two roles: the supply side sends OpenRTB bid requests and runs the auction, and the demand side receives them and bids. An SSP represents publishers, a DSP represents buyers, and an ad exchange is the marketplace connecting them; in practice modern SSPs and exchanges are the same system, so the terms blur. In OpenRTB terms, the exchange assigns BidRequest.id, sets the auction rules (at, tmax, cur), fans the request out to DSPs, and sends win notices; the DSP parses the request, prices the impression, and returns a BidResponse.
A bid request is the JSON message an exchange sends to bidders to offer one or more impressions for sale. The top-level BidRequest object carries a unique id, an imp array describing each ad slot, one of site, app, or dooh for the inventory context, plus device, user, source, and regs objects with device details, user identifiers, supply chain data, and privacy signals. It is sent over HTTP POST with Content-Type application/json. Bidders must answer within the tmax deadline or the exchange treats them as passing on the auction.
Bid request reference · How to validate a bid request
A bid response is the JSON message a bidder returns with its bids. BidResponse.id must echo the request's id, and bids are grouped in seatbid objects, each holding one or more bid objects with the impid they target, a price in CPM, and creative markup in adm (or a hosted creative reference). The cur field states the bid currency and defaults to USD. To decline, a bidder either returns an empty HTTP 204 response or a BidResponse containing only an nbr no-bid reason code.
Bid response reference · No-bid reason codes
Only two: id, the exchange's unique ID for the request, and imp, an array containing at least one Imp object, each with its own required id. Everything else is optional or merely recommended (site or app, device, and user are recommended). Omitted fields fall back to spec defaults: at defaults to 2 (second price plus), test to 0 (live traffic), allimps to 0, and imp.bidfloor to 0 in USD. A request that validates is not automatically biddable; most DSPs also expect device, inventory, and user data before they will spend.
Bid request reference · Validate a request in the browser
Check BidRequest.at: 1 means first price, 2 means second price plus, and 2 is the default when the field is omitted. Values of 500 and above are exchange-specific auction types that must be documented by the exchange. Private marketplace deals can override this per deal via pmp.deals[].at, where 3 means the value in the deal's bidfloor is the agreed fixed price. Note that most exchanges moved to first-price auctions years ago, so an omitted at (implying second price) often does not reflect how the exchange actually clears; when in doubt, confirm with the exchange.
OpenRTB enum reference
The Imp object describes a single ad placement being auctioned, and the imp array can hold several of them; a single bid request may offer every ad position on a page, for example. Each Imp has a required id (typically 1, 2, 3...) that bids reference via impid. The presence of a banner, video, audio, or native object inside the Imp declares which formats are offered, and a publisher may offer several at once as long as each bid conforms to one of them. Other key fields: bidfloor and bidfloorcur for the price floor, tagid for the placement identifier, and instl for interstitials.
Bid request reference · Bid floors explained
Include a site object for website inventory or an app object for non-browser application inventory; OpenRTB 2.6 adds a third option, dooh, for digital out-of-home screens. A bid request must not contain more than one of site, app, or dooh. Neither object has strictly required fields, but a site should carry at least an id or page URL and an app at least an id or bundle, since buyers key brand safety and ads.txt or app-ads.txt checks off site.domain and app.bundle. Requests carrying both site and app are a common integration bug and get rejected by many DSPs.
Common OpenRTB mistakes · Bid request reference
Set BidRequest.test to 1. The field is an integer with a default of 0, where 0 means live mode and 1 means test mode: auctions run end to end but are not billable. It lets you exercise a full integration (parsing, bidding, win notices) without spending money, and bidders typically route test requests around production pacing and budgets. Coordinate with your partner first, since handling of test traffic varies by exchange. Validating request structure before sending test traffic catches most integration issues earlier; rtblint checks a 2.6 request in the browser or from the CLI.
Browser tester · rtblint CLI
Three fields control currency, all using ISO 4217 alpha codes. BidRequest.cur is an array of currencies the exchange accepts for bids, recommended only when the exchange accepts more than one. imp.bidfloorcur states the currency of that impression's floor and defaults to USD; it does not inherit from anywhere, and per-deal floors in pmp.deals[].bidfloorcur likewise default to USD rather than inheriting from the Imp. On the response side, BidResponse.cur declares the bid currency and also defaults to USD. Mismatched floor and bid currencies are a classic source of silently lost auctions.
Bid floors explained · Common OpenRTB mistakes
They are the publisher's blocklists, applied request-wide. bcat blocks advertiser categories, expressed in the taxonomy named by BidRequest.cattax (default 1, IAB Content Category Taxonomy 1.0). badv blocks advertisers by domain, for example ford.com. bapp blocks apps by their app store IDs: bundle or package names for Google Play, numeric IDs for the Apple App Store. OpenRTB 2.6 also adds acat, an allowlist of advertiser categories; a request should contain acat or bcat, not both. Bidders that return creatives violating these lists get their bids filtered or their traffic clawed back.
Bid request reference
cattax is an integer telling bidders which category taxonomy the category arrays use, per the AdCOM Category Taxonomies list; it defaults to 1, meaning IAB Content Category Taxonomy 1.0. The cat, sectioncat, and pagecat arrays on site and app describe the inventory's content categories, and BidRequest.cattax scopes bcat and acat. The gotcha: if you populate categories from Content Taxonomy 2.x or 3.x, you must send the matching cattax value, otherwise bidders will interpret your IDs against the 1.0 taxonomy and misclassify the inventory.
OpenRTB enum reference
Device describes the hardware and software the impression will be delivered to, and it drives targeting, fraud detection, and identity. The fields buyers look at first: ua, the raw browser User-Agent string; sua, the structured user agent built from User-Agent Client Hints, which bidders should prefer over ua when both are present since ua may be frozen or reduced; ip for geo and fraud checks; devicetype, an AdCOM enum distinguishing phone, tablet, connected TV, and so on; ifa, the OS advertising identifier in the clear; and lmt, the limit ad tracking flag. The hashed ID fields (didsha1, dpidmd5, etc.) are deprecated as of 2.6.
Bid request reference · OpenRTB enum reference
The user object describes the human on the other side of the impression: user.id (the exchange's identifier), buyeruid (the bidder's mapped identifier from a cookie sync), eids for standardized identity providers, consent for the TCF consent string when GDPR applies, plus geo, data segments, and keywords. In OpenRTB 2.6 the demographic fields yob and gender are deprecated. Everything in this object is gated by the privacy signals in regs, so read the two together.
Privacy signals in OpenRTB
ext is a placeholder object for exchange-specific extensions, and by convention it may appear on any OpenRTB object. The spec deliberately leaves its structure undefined; an exchange using extensions is responsible for documenting them to its bidders. IAB Tech Lab hosts well-known community extensions in its GitHub repository, and successful extensions often get promoted into the core spec later: regs.ext.gdpr, user.ext.consent, and source.ext.schain in 2.5 all became first-class fields (regs.gdpr, user.consent, source.schain) in 2.6. Validators can only check documented fields, so undocumented ext payloads are effectively opaque.
OpenRTB 2.5 vs 2.6
wseat is an allowlist and bseat a blocklist of buyer seats (advertisers or agencies) permitted to bid on the impressions in the request. At most one of the two may appear in the same request, and omitting both means no seat restrictions. Seat IDs are not standardized; the exchange and bidder must agree on them in advance. Bidders declare which seat a bid belongs to via seatbid.seat in the response, which is how the exchange enforces the restriction. Per-deal seat allowlists also exist at pmp.deals[].wseat.
Bid request reference
allimps is a flag saying whether the impressions in the request represent every impression available in the context, such as all placements on a page or all video spots in a pod, to support roadblocking. It defaults to 0, meaning no or unknown; 1 means the offered impressions are all that exist. A buyer who wants exclusive presence can only trust a roadblock buy when allimps is 1, usually combined with a multi-impression request and the group flag on seatbid, which lets a seat insist on winning all impressions or none.
Bid request reference
There is no fixed size: a bid request is usually a few kilobytes of JSON, growing with eids arrays, supply chains, and multi-format impressions, and the spec does not set a limit. Volume depends entirely on how much of an exchange's traffic you take; production DSP endpoints commonly handle very large request rates, which is why the spec's stated best practices are gzip compression (negotiated via Accept-Encoding) and HTTP persistent connections to cut connection overhead. Whatever the volume, each response must arrive within tmax, which includes network latency in both directions.
How tmax works
OpenRTB payloads are JSON sent over HTTP POST with Content-Type application/json, and the response must use the same representation as the request. Standard JSON rules apply: no comments, no trailing commas, string keys in double quotes; hand-edited requests with comment lines are a frequent cause of HTTP 400 rejections. The spec gives the absence of an attribute formal meaning, usually that the value is unknown, so omit fields rather than sending null. Exchanges may agree on binary alternatives like Protobuf or Avro with an appropriate Content-Type, and gzip compression is a documented best practice.
Browser tester · Common OpenRTB mistakes
IAB Tech Lab maintains OpenRTB. The 2.x specification lives in the InteractiveAdvertisingBureau/openrtb2.x repository on GitHub, and OpenRTB 3.0 plus the shared AdCOM object model live in the InteractiveAdvertisingBureau/openrtb and AdCOM repositories. Since release 2.6-202211, the version number only increments on breaking changes; non-breaking updates ship as dated snapshots of 2.6 (2.6-202303 replaced video.placement with plcmt, for example), with release notes on the repository's Releases page. rtblint is an independent validator and is not affiliated with IAB Tech Lab.
OpenRTB versions
Target OpenRTB 2.6 at its latest dated snapshot. It is the current 2.x spec and carries the modern signals buyers expect: GPP privacy strings (regs.gpp, added 2.6-202211), first-class gdpr and consent fields, source.schain, video plcmt, and CTV pod bidding. OpenRTB 3.0 is a redesigned, non-backwards-compatible protocol built on AdCOM; it has real merits but limited ecosystem adoption, so 2.6 remains the practical choice for exchange-to-DSP integrations. rtblint validates requests across 2.0–3.0 catalogs with the deepest rule coverage on 2.6 snapshots.
OpenRTB 2.6 vs 3.0 · OpenRTB versions
Debugging, no-bids, and validation
Why requests get dropped, why bids lose, what the no-bid codes mean, and how to validate payloads before they cost revenue.
Work the path from the exchange inward: confirm the endpoint the exchange has on file matches what you deployed (scheme, host, path), that your endpoint returns a well-formed response or HTTP 204 quickly enough to stay under the exchange's timeout, and that traffic filters on the exchange side (geo, media type, QPS caps, pretargeting) are not excluding you. Exchanges routinely throttle or suspend bidders whose error or timeout rate climbs, so a bidder that once received traffic and stopped usually failed health checks. Ask the exchange for its bidder health report; most expose error and timeout rates per endpoint.
Start by separating deliberate no-bids from failures. If your engine chooses not to bid, that shows up as HTTP 204 or a response with a no-bid reason code in BidResponse.nbr. If you are trying to bid and nothing goes through, the usual causes are malformed responses (impid not matching an imp.id, price as a string, missing adm and nurl), bids below imp.bidfloor, bids in a currency the request's cur array does not allow, or creatives that have not cleared the exchange's audit. Compare your internal bid count against the exchange's received-bid count first; a gap means responses are being rejected in transit or on parse.
OpenRTB no-bid reason codes · Bid response reference
Ask for loss reasons via the loss notice URL. Set bid.lurl in your response with the ${AUCTION_LOSS} macro; when the exchange discards or outbids you it fires the URL with a loss reason code substituted in (0 means bid won, 100 below auction floor, 102 lost to a higher bid, and the 200 range covers creative filtering). Not every exchange supports lurl, but where it exists it is the only per-bid answer to why you lost. Aggregate the codes: a spike in 100 means a floor problem, a spike in the 200s means creative policy.
OpenRTB substitution macros · Bid response reference
If the bidder returns a BidResponse with nbr set, the code tells you where to look: nbr 2 is Invalid Request, which means the bidder could not parse or accept your bid request, so run the request through a validator before anything else. nbr 6 Unsupported Device, 7 Blocked Publisher, and 8 Unmatched User point at targeting and identity rather than payload structure. If the bidder just returns 204 with no nbr, you get no signal from the wire; reproduce the request in a validator and check for missing required fields, moved paths, and type mismatches, which are the classic silent killers.
No-bid reason codes table · Bid request tester
HTTP 204 No Content is the spec's preferred way for a bidder to say it has no bid: no body, cheapest possible round trip. The alternative is HTTP 200 with a BidResponse that carries no bids and, optionally, a no-bid reason code in nbr; that costs a body but tells the exchange why. An empty seatbid array amounts to the same no-bid outcome. Use 204 for routine passes and nbr when you want the exchange to see the reason, for example during an integration test where you are diagnosing systematic no-bids.
OpenRTB no-bid reason codes
Paste it into a validator that checks against the actual spec catalog, not just JSON syntax. rtblint validates a bid request against a chosen OpenRTB version snapshot and reports missing required fields, unknown and deprecated fields, moved paths (placement to plcmt, regs.ext.gdpr to regs.gdpr), type mismatches, and out-of-range enum values, each with a stable rule ID and the exact JSON path. The browser tester runs client-side so the payload never leaves your machine; the CLI (rtblint validate request.json) runs the same rules from a terminal or script.
Bid request tester · Validation walkthrough
Keep your bid request fixtures in the repo and lint them on every build. With rtblint: cargo install rtblint, then rtblint validate fixture.json --version 2.6-202505 --format json in a CI step; the exit code gates the build and the JSON report carries rule IDs you can allow or deny with jq. Pin the version flag so a spec snapshot upgrade is a deliberate diff instead of a surprise. This catches drift when a partner moves to a newer 2.6 snapshot and a field your golden files still use has been deprecated or moved.
OpenRTB validation in CI · CLI reference
Set test to 1 on the bid request. The spec defines test as a flag where 1 means the auction is not billable; both sides can exercise the full request-response path, including creative return, without money moving. Beyond the flag, most exchanges offer a sandbox or staging endpoint with synthetic traffic, and you can replay captured production requests against your own bidder offline. Validate the payloads themselves before any live test so you are not burning integration time on structural mistakes a linter would catch.
Bid request reference
The usual suspects, in order: JSON that does not parse (trailing commas, unescaped quotes, truncation), a missing top-level id or imp array, an empty imp array, integers sent as strings (at, tmax, w, h, the flag fields), both site and app present, and fields that moved in 2.6 still sitting at their 2.5 paths. Strictly typed parsers reject the whole request on the first type error, and the failure is usually silent from your side. A validator pinpoints the exact path instead of leaving you to diff a 4 KB payload by eye.
Common OpenRTB mistakes · Bid request tester
Check the contract fields first: BidResponse.id must echo the request id, every bid.impid must exactly match an imp.id from the request, price must be a JSON number (CPM), and the bid must carry markup in adm or a win-notice URL in nurl. After structure, the common filters are currency (cur must be one the request allows), bids under the floor, missing adomain or crid where the exchange requires them for audit, and creative attributes colliding with battr. Exchanges rarely explain a dropped response inline, so compare your sent-bid logs against the exchange's reported received bids, then work the list.
Bid response reference
Measure against tmax, not against your server clock. BidRequest.tmax is the exchange's total budget in milliseconds including network latency, so your compute window is tmax minus the round trip to the exchange. Instrument the full path: TLS handshake reuse, DNS, queue time before your handler, and response serialization all count. If your p99 response time sits near tmax, you are timing out at the tail even when the median looks healthy. Exchanges report timeout rate per bidder; a rising timeout rate with a flat error rate almost always means infrastructure latency, not payload problems.
tmax and timeouts
Different counting points. If the DSP counts wins (nurl fired) while the publisher side counts rendered or billable impressions (burl, which the spec ties to the billing event), every won-but-never-rendered impression becomes a discrepancy. Add timeout losses after win, creative wrappers that fail to load, and timezone boundaries on reporting, and 5 to 10 percent gaps are common. The spec's answer is burl: bill on the billing notice, not the win notice. When reconciling, first agree on which URL each side counts and in which timezone the day is cut.
Win, billing, and loss notices · OpenRTB substitution macros
Sample and log raw request bodies at your edge (before any normalization), strip or hash user identifiers, and store them as fixtures. Replaying them serves two purposes: regression-testing your own parser and bidding logic against real traffic shapes, and validating the payloads against the spec to catch drift when a partner changes what they send. A nightly job that runs the latest sample through rtblint validate --stdin with --format json will surface a partner's new or moved fields the day they appear instead of the week revenue dips.
OpenRTB validation in CI
seatbid is the array grouping bids by bidder seat: each SeatBid carries an optional seat identifier and a bid array with at least one bid. A BidResponse with no seatbid, or with SeatBid objects whose bid arrays are empty, is a no-bid as far as the auction is concerned. If your engine intends to bid but seatbid comes out empty, look at your response assembly: bids filtered late (floor, budget, creative approval) after the response shell was built is the classic cause. If you mean to no-bid, prefer HTTP 204 or set nbr so the intent is explicit.
Bid response reference
First confirm you actually won: nurl fires on win, and depending on the exchange that can be the auction win or the final render decision. Then check the URL itself: it must be reachable from the exchange's servers (not localhost, not behind auth), fast, and idempotent. If the URL contains substitution macros, malformed macro syntax means the exchange substitutes nothing or drops the call. Also remember the spec allows returning markup via the nurl response body instead of adm; if you rely on that, a broken nurl means no ad served at all. Log query parameters server-side and compare against ${AUCTION_PRICE} expectations.
OpenRTB substitution macros
Most exchanges review creatives before letting them win, either pre-bid (submit for approval) or post-bid (first bids lose while the creative is in review). That is why new campaigns often show bids with no wins for the first hours. Your bid must carry the fields the audit keys on: adomain (advertiser domain), crid (creative ID), and often cat and attr. Loss reason codes in the 200 range signal creative filtering specifically: 200 is a generic creative filter, and the list continues through categories like blocked category and blocked attribute. If wins stop after a creative change, assume re-audit.
Bid response reference
Video adds media constraints on top of the usual auction filters, and any mismatch disqualifies the bid: your creative's MIME type must be in imp.video.mimes, the VAST version you return must map to a value in protocols, duration must sit between minduration and maxduration (or match rqddurs exactly when present), and dimensions should fit w and h. On CTV, buyers and exchanges increasingly key on plcmt; a creative strategy built for plcmt 1 instream will not clear an outstream placement. Compare each constraint in the request against what your ad server actually returned, field by field.
CTV in OpenRTB · Protocols and plcmt enums
Look at the x-openrtb-version HTTP header on their requests; the spec has exchanges declare the version there (for example x-openrtb-version: 2.6). Beyond the header, the payload itself tells you: regs.gpp means at least the 2.6-202211 snapshot, plcmt on video means 2.6-202303 or later, and gdpr still living at regs.ext.gdpr suggests a 2.5-era integration regardless of what the docs claim. Partner documentation lags reality often enough that fingerprinting the actual traffic is the reliable method.
OpenRTB versions and snapshots
Usually tolerated, occasionally fatal, always worth knowing about. The spec's extension convention is that custom data belongs inside ext objects; fields invented at other paths are undefined behavior. Lenient parsers skip them, strict ones reject the request, and either way you are shipping bytes that no partner reads. Unknown fields are also the fingerprint of drift: a typo (bidflor), a field at its pre-2.6 path, or a partner-specific field leaking into traffic for everyone. rtblint flags every field not in the catalog for the version you target, with the exact path.
What rtblint checks
Run the validator with machine-readable output and filter on rule IDs. rtblint validate request.json --format json emits findings each carrying a stable rule ID (for example openrtb.field.moved or openrtb.field.deprecated) and a JSON path; pipe that through jq to fail the build on the classes you care about while tolerating others. Teams typically hard-fail on errors (required fields, types, undefined fields) and warn on deprecations until a migration lands. Because the IDs are stable across releases, the gate does not rot when message wording changes.
OpenRTB validation in CI · Rule catalog
Options are thinner than you would expect for a protocol this central. Generic JSON Schema validation catches types and required fields if you maintain a schema, but the IAB does not publish an official one per snapshot, and schemas cannot express moved or deprecated paths across versions. Exchange-specific request debuggers exist inside partner consoles but only cover that partner's dialect. rtblint is an open-source validator built specifically for this: version-aware across OpenRTB 2.0 through 3.0 catalogs with rule depth on the 2.6 snapshots, running in the browser (client-side WASM) and as a Rust CLI for pipelines.
Bid request tester · CLI reference
Video, CTV, native, floors, deals, and macros
Media-type specifics: the video object and ad pods, the native request string, bid floors and PMP deals, and the substitution macros in win and loss notices.
Add an imp.video object to the impression; its presence is what marks the impression as video. mimes is the only required field (e.g. video/mp4), but the spec recommends minduration, maxduration, protocols, startdelay, and player w and h in device-independent pixels. minduration and maxduration are mutually exclusive with rqddurs: send a range or an exact-duration list, never both. Add plcmt for the placement subtype and skip if the player allows skipping. The same imp may also carry banner, audio, or native objects if the publisher accepts multiple formats.
Bid request structure · Validate a bid request
imp.video.plcmt declares the video placement subtype using AdCOM's Plcmt Subtypes list, which follows the updated IAB digital video guidelines. Values: 1 = instream (sound on by default or clear user intent, video is the focus of the visit), 2 = accompanying content (player sits alongside text or graphical content and starts playback only on entering the viewport), 3 = interstitial (ad without video content that takes over the viewport), 4 = no content or standalone (slideshows, native feeds, in-content, sticky players). It was added in OpenRTB 2.6-202303 and replaces the deprecated placement field; the two enums do not map 1:1.
placement vs plcmt · OpenRTB enums
Send imp.video.plcmt = 1 (instream) for CTV. Pre-roll, mid-roll, and post-roll ads inside requested streaming video content are instream by definition, and CTV playback is sound-on full-screen video, so it meets the instream bar without qualification. Pair it with startdelay to distinguish pre-roll from mid-roll and post-roll, and podid plus slotinpod when the break is an ad pod. Keep sending placement = 1 too while partners finish migrating off the deprecated field.
CTV in OpenRTB · placement vs plcmt
Send both during the transition, but treat plcmt as authoritative. placement was deprecated in the OpenRTB 2.6-202303 release in favor of plcmt, whose values follow the updated IAB digital video guidelines. The two enums differ in meaning (old placement 1 in-stream is stricter than it sounds under the new definitions), so there is no mechanical 1:1 mapping; classify inventory against the plcmt definitions rather than translating old values. Buyers that only read placement will still work while you dual-send, and you can drop placement once your demand partners confirm they consume plcmt.
placement vs plcmt · Common OpenRTB mistakes
Each slot in the pod is its own imp, and a shared imp.video.podid ties them together. podseq gives the pod's position in the content stream, and slotinpod indicates whether the seller can guarantee a specific position in the pod (a bid can also set slotinpod to claim one). For structured pods, each imp carries its own duration constraints. For dynamic pods, where the slot count is not predetermined, send maxseq (maximum ads in the pod) and poddur (total seconds to fill for the whole break). rqddurs lists exact acceptable creative durations for live TV and is mutually exclusive with minduration and maxduration. All of these arrived in OpenRTB 2.6.
CTV in OpenRTB · OpenRTB 2.5 vs 2.6
Set imp.ssai, an integer added in OpenRTB 2.6 with default 0. Values: 0 = status unknown, 1 = all client-side (no server-side stitching), 2 = assets stitched server-side but tracking pixels fired client-side, 3 = all server-side (stitching and tracking). Buyers use it to judge tracker reliability and IVT risk, so send the honest value rather than leaving 0 on stitched CTV streams. Value 2 is the common CTV middle ground: the ad is in the stream but measurement still runs on the device.
CTV in OpenRTB
Set imp.rwdd = 1. The field was added in OpenRTB 2.6, is an integer with default 0, and means the user receives a reward for viewing the ad: an extra life in a game, a free article, or an ad-free music session, typically granted after the video completes. Before 2.6 this signal lived in assorted imp.ext extensions, so if you support older partners you may need to dual-write. rwdd sits on the impression, not on the video object, so it applies to any media type offered in that imp.
Bid request structure · OpenRTB 2.5 vs 2.6
Set imp.video.skip = 1 to say the player allows skipping, then qualify it with skipmin and skipafter (both default 0). skipmin means only videos longer than that many seconds can be skipped, and skipafter is how many seconds must play before the skip control appears. So skip=1, skipmin=15, skipafter=5 reads: ads over 15 seconds are skippable after 5 seconds. These describe player capability only. If a bidder returns a creative that is itself skippable, the bid should list creative attribute 16 in bid.attr.
Bid request structure · OpenRTB enums
imp.video.protocols uses AdCOM's Creative Subtypes Audio/Video list. The mapping: 1 = VAST 1.0, 2 = VAST 2.0, 3 = VAST 3.0, 4/5/6 = VAST 1.0/2.0/3.0 Wrapper, 7 = VAST 4.0, 8 = VAST 4.0 Wrapper, 9 = DAAST 1.0, 10 = DAAST 1.0 Wrapper, 11 = VAST 4.1, 12 = VAST 4.1 Wrapper, 13 = VAST 4.2, 14 = VAST 4.2 Wrapper, 15 = VAST 4.3, 16 = VAST 4.3 Wrapper. A common mistake is advertising inline support (e.g. 3, 7) without the wrapper values, which blocks any bidder returning wrapper tags. If you accept a version, you almost always want its wrapper value too.
OpenRTB enums · Common OpenRTB mistakes
imp.audio mirrors imp.video for the core fields (mimes required, minduration, maxduration, protocols, startdelay, full pod support with podid, poddur, rqddurs) but drops the visual ones: no w, h, plcmt, linearity, skip, or playbackmethod. In their place audio adds feed (feed type from AdCOM, e.g. music service vs podcast), stitched (1 if the ad is stitched into the audio stream), and nvol (volume normalization mode). Audio creatives are described by DAAST or, since VAST 4.1, VAST itself; the protocols list covers both. A bid on an audio imp sets mtype = 3.
Bid request structure
Add imp.native with two fields that matter: request and ver. request is required and holds the Native Ads API request (assets, event trackers, context) as a JSON-encoded string, not a nested object; forgetting to stringify it is the classic native integration bug. ver is recommended and should state the Dynamic Native Ads API version the payload complies with, 1.2 being the current standard. Since Native 1.1 the payload's root object is the request itself; only Native 1.0 wrapped it in a native key. api and battr on imp.native carry the usual framework and blocked-attribute lists.
Native in OpenRTB · Validate a bid request
Because the Native Ads API is a separate specification that versions independently of OpenRTB, the request payload is carried as an opaque JSON-encoded string. OpenRTB deliberately does not parse or validate it; the string can evolve under the Dynamic Native Ads API without requiring a new OpenRTB release. The practical consequence is double encoding: your native request object must be serialized to a string, then embedded in the bid request JSON, with quotes escaped accordingly. Parsers that expect an object here will reject valid requests, and senders that inline a raw object are out of spec even though some exchanges tolerate it.
Native in OpenRTB · Common OpenRTB mistakes
Set imp.bidfloor, a float expressed as CPM, with imp.bidfloorcur naming the currency as an ISO-4217 code (default USD). The floor is scoped to the impression: each imp in a multi-imp request carries its own floor, and there is no request-level floor field. bidfloor defaults to 0, meaning no floor. bidfloorcur sets the default currency for all floors specified within that Imp object, but note that Deal.bidfloorcur does not inherit from it; a deal's currency is either explicit or USD. Bids below the floor are eligible for rejection with loss reason 100 (bid below auction floor).
Floors in OpenRTB
The usual cause is a currency mismatch: imp.bidfloorcur defaults to USD, so a floor of 200 intended as JPY but sent without bidfloorcur reads as a 200 USD CPM and nothing clears it. The inverse also happens when a bidder prices in its own currency and compares against the raw floor number without converting. Check three things: that bidfloorcur is set whenever the floor is not USD, that the bidder converts before comparing, and that deal floors carry their own bidfloorcur (Deal.bidfloorcur does not inherit from the imp, it defaults to USD independently). Loss notices with ${AUCTION_LOSS} = 100 confirm floor rejections.
Floors in OpenRTB · No-bid and loss reasons
The seller attaches imp.pmp to the impression, containing private_auction and a deals array of Deal objects. private_auction = 0 means open bids are still accepted alongside deal bids; 1 restricts the auction to the listed deals and their terms. Each Deal has a required id plus optional bidfloor, bidfloorcur, at (auction type override), wseat (allowed buyer seats), wadomain (allowed advertiser domains), and since 2.6 a guar flag for guaranteed deals. A bidder responding against a deal must echo the deal id in bid.dealid; a bid without dealid on a private_auction = 1 impression is invalid.
Bid request structure · Bid response structure
Deal.at = 3 means the value in Deal.bidfloor is the agreed fixed deal price, not a floor to bid above. It overrides the request-level auction type for that deal: 1 = first price, 2 = second price plus, 3 = fixed price. In a fixed-price deal the bidder bids the deal price and clears at it; there is no price competition against that value. Exchanges may define additional auction types above 500. Note the field reuses bidfloor for the price, so a validator or bidder that treats every bidfloor as a minimum will misprice at=3 deals.
Floors in OpenRTB
durfloors lets sellers price video and audio by creative duration: an array of DurFloors objects on imp.video, imp.audio, or Deal, each with mindur or maxdur (at least one is required, an unbounded end is omitted) and a bidfloor in CPM. Example: 1-15s at 5.00, 16-30s at 10.00, 31s+ at 20.00. Creatives whose duration falls outside every defined range fall back to the imp-level bidfloor. Ranges are not guaranteed to be non-overlapping; buyer and seller coordinate which applies. For dynamic ad pods there is also mincpmpersec, a floor expressed as CPM per second of the bid's duration.
Floors in OpenRTB · OpenRTB 2.5 vs 2.6
The exchange replaces ${AUCTION_PRICE} with the clearing price, in the same currency and units as the bid and reflecting any discount, wherever the macro appears: in the nurl, burl, and lurl notice URLs and inside the adm markup itself. Compliant exchanges must support substitution in both URLs and markup for win and billing notifications. In loss notices, exchange policy may withhold clearing prices, in which case ${AUCTION_PRICE} is replaced with an empty string rather than left intact. Values can be encoded by mutual agreement using the :X suffix (e.g. ${AUCTION_PRICE:B64}) when the price travels through markup into the browser.
Substitution macros
Three notice URLs on the Bid object, each fired by the exchange for a different event. nurl is the win notice, called when the bid wins the auction; winning is not necessarily a delivered, viewed, or billable ad, and the nurl response body can optionally serve the ad markup. burl is the billing notice, called when the winning bid becomes billable under exchange policy, typically on delivery or viewability. lurl is the loss notice, called when the bid is known to have lost, usually carrying ${AUCTION_LOSS} and possibly a redacted ${AUCTION_PRICE}. Count spend on burl, not nurl; the gap between the two is exactly the won-but-never-rendered case.
Bid response structure · Substitution macros
nurl fires on auction win; burl fires later, when the win becomes billable under the exchange's business policy, which the spec describes as typically delivered or viewed. The two can be minutes apart or the burl may never fire at all if the ad loses downstream or fails to render, so bidders should reconcile spend against burl. For VAST video the spec's best practice is that the VAST impression event is the official billable signal, and an exchange adhering to that policy should fire burl at the same time as the VAST impression, while warning that firing both invites small discrepancies.
Bid response structure
${AUCTION_MIN_TO_WIN}, added in OpenRTB 2.6, is replaced with the minimum bid that would have tied the decisive competing price, in the bid's own currency and units, and is the standard bid-shading feedback signal. If your bid lost, it is the price needed to tie the winning bid; if you won, it is the price needed to tie the next-closest bid, or the floor if yours was the only bid. Exchanges may substitute an empty string when price was not the deciding factor, when the bid never entered the auction, or when privacy policy withholds price data, so parsers must tolerate a blank value.
Substitution macros · OpenRTB 2.5 vs 2.6
bid.mtype declares the type of the creative markup so the exchange can associate it with the right sub-object of the impression: 1 = banner, 2 = video, 3 = audio, 4 = native. It was added in OpenRTB 2.6 to remove guesswork on multi-format impressions, where one imp offers banner, video, audio, or native and the exchange previously had to sniff the adm contents to tell which the bid was for. Always set it; on a multi-format imp a missing mtype forces the exchange to infer, and misclassification breaks rendering and reporting.
Bid response structure · OpenRTB 2.5 vs 2.6
Put the VAST XML document directly in bid.adm as a JSON-escaped string and set mtype = 2. The alternative is to omit adm and return the VAST as the response body of the win notice: the exchange calls bid.nurl and the body must contain the markup and nothing else. If both adm and the nurl return provide markup, adm takes precedence. The exchange applies substitution macros such as ${AUCTION_PRICE} inside the markup either way. Inline adm is preferred in practice since it saves a round trip and lets the exchange scan the creative before rendering.
Bid response structure · Substitution macros
eventtrackers is the Native 1.2 mechanism for declaring and returning ad tracking, replacing the older imptrackers and jstracker fields, which the spec marks as to be deprecated. In the request, the eventtrackers array states which events and methods the publisher supports: event types include 1 = impression, 2 = viewable-mrc50 (50% in view for 1 second), 3 = viewable-mrc100, 4 = viewable-video50, with 500+ reserved for exchange-specific types; methods are 1 = img pixel and 2 = js tag. In the response, each EventTracker object carries the event, the single method chosen, and the tracking URL. Bidders should send eventtrackers and keep imptrackers only for legacy partners.
Native in OpenRTB
Privacy, identity, supply chain, and versions
Consent and regulatory signals with their exact paths, schain, ads.txt and sellers.json, and how the 2.x line and 3.0 relate.
Set regs.gdpr to 1 when the request is subject to GDPR and put the TCF consent string in user.consent. Both are first-class fields in OpenRTB 2.6; on 2.5 and earlier the same signals travel as regs.ext.gdpr and user.ext.consent. regs.gdpr is an integer flag (0 = no, 1 = yes, omitted = unknown), and user.consent carries the IAB Transparency and Consent Framework string. Sending a consent string without the gdpr flag, or the flag without the string, is a common integration bug worth validating for.
Privacy signals in OpenRTB · OpenRTB 2.5 vs 2.6
Put the Global Privacy Platform consent string in regs.gpp and list the applicable section IDs in regs.gpp_sid. Both fields were added in the OpenRTB 2.6-202211 update. regs.gpp_sid is an integer array; it generally contains exactly one value, though edge cases can carry more. GPP sections 3 (header) and 4 (signal integrity) do not need to be listed. On versions before 2.6-202211 there is no first-class home for GPP, so partners typically agree on an ext placement.
Privacy signals in OpenRTB · OpenRTB version snapshots
Send GPP (regs.gpp plus regs.gpp_sid) where your partners support it; the US Privacy signal was deprecated by IAB Tech Lab on January 31, 2024 in favor of GPP. In practice many exchanges and bidders still read regs.us_privacy, so during the transition it is common to send both, with the GPP string treated as authoritative when the relevant US section is present. Follow each partner's integration guidance rather than dropping us_privacy unilaterally, and avoid sending contradictory values in the two fields.
Privacy signals in OpenRTB
Put the US Privacy string in regs.us_privacy, which is a first-class field in OpenRTB 2.6; on 2.5 and earlier it travels as regs.ext.us_privacy per the IAB extension. The value is the four-character USP string (for example 1YNN) covering spec version, explicit notice, opt-out of sale, and LSPA coverage. Note that the US Privacy spec itself is deprecated in favor of GPP, so newer integrations express the same signal through regs.gpp with the appropriate US section ID.
Privacy signals in OpenRTB
Set regs.coppa to 1 when the request is subject to the COPPA regulations established by the US FTC, and 0 when it is not. The field is an integer flag on the Regs object and has been first-class since OpenRTB 2.2, so it works the same across 2.x versions. When coppa=1, buyers are expected to suppress behavioral targeting and treat user identifiers accordingly; sellers should also avoid sending identifying fields that contradict the flag.
Privacy signals in OpenRTB
The DSA transparency extension carries EU Digital Services Act ad-transparency data through OpenRTB 2.x ext blocks. On the request, regs.ext.dsa holds dsarequired (0-3, whether a DSA object is required in the response), pubrender (whether the publisher renders the transparency info), datatopub, and a transparency array of domain plus dsaparams objects. On the response, the bid's ext.dsa holds behalf (on whose behalf the ad shows), paid (who paid), adrender, and its own transparency array. It is an IAB Tech Lab community extension, not part of the core 2.6 spec.
Privacy signals in OpenRTB
device.dnt is the browser Do Not Track header flag, while device.lmt is the OS-level Limit Ad Tracking signal from mobile platforms such as iOS and Android. For both, 0 means tracking is unrestricted and 1 means tracking must be limited. When lmt=1, buyers must limit tracking per the platform's commercial guidelines: no profiling or retargeting off the device ID, and the ifa is typically absent or zeroed anyway. dnt matters for web inventory, lmt for app inventory; requests should not carry a device identifier usage that contradicts either flag.
Privacy signals in OpenRTB
When the user denies tracking, the OS advertising API returns an all-zero identifier, so device.ifa arrives as 00000000-0000-0000-0000-000000000000 or is omitted entirely, and device.lmt should be 1. Never treat a zeroed ifa as a real ID for frequency capping, matching, or attribution; filter it out. The 2.6 spec defines ifa as the ID sanctioned for advertiser use in the clear, derived from the device OS advertising API, so once the OS stops vending it there is no compliant substitute in that field.
Privacy signals in OpenRTB
user.eids is an array of EID objects, each carrying one or more identifiers from a single ID source or technology provider. Each EID has a source (the canonical domain of the ID provider) and a uids array; each UID has an id string and an atype (agent type, from the AdCOM Agent Types list, strongly recommended since many DSPs require it for resolution). EID and UID became first-class objects in OpenRTB 2.6 (sections 3.2.27 and 3.2.28); before that the same structure traveled under user.ext.eids. Vendor IDs like UID2 or RampID all use this same envelope, distinguished by the source domain.
Privacy signals in OpenRTB · Bid request reference
user.id is the exchange's own identifier for the user (typically from the exchange's cookie), while user.buyeruid is the buyer's identifier for that same user, as mapped by the exchange for that specific bidder. buyeruid is populated from a prior cookie sync: the exchange looks up its cookie ID in its match table and inserts the DSP's corresponding ID before sending the request. A DSP can act on buyeruid directly for targeting and frequency capping; user.id is only meaningful to the exchange unless you have built your own mapping.
Bid request reference
Cookie matching (ID syncing) is how an exchange and a DSP link their separate cookie IDs for the same browser so bid requests can carry a buyer-usable ID. One party hosts a match table; most commonly the exchange does. A sync pixel fired on the other party's domain redirects with the partner's user ID in the URL, letting the table owner store the pairing. On subsequent requests the exchange looks up its cookie ID and places the DSP's ID in user.buyeruid. OpenRTB 2.6 documents this flow in its cookie-based ID syncing appendix, including consent gating: sync endpoints are expected to honor GDPR, GPP, and US privacy parameters.
Bid request reference · Privacy signals in OpenRTB
Put a SupplyChain object at source.schain in OpenRTB 2.6; on 2.5 it lives at source.ext.schain. The SupplyChain object requires three fields: complete (1 if the chain traces all the way back to the inventory owner, else 0), nodes (an ordered array of SupplyChainNode objects, first node closest to the publisher, last node the entity sending the request), and ver (the schain spec version string, currently 1.0). Each node requires asi (the advertising system's canonical domain) and sid (the seller's account ID in that system), and should carry hp. Every intermediary appends its own node and propagates the rest unchanged.
Supply chain in OpenRTB
complete=1 asserts that the nodes array contains every entity in the payment chain back to the owner of the site, app, or other medium; complete=0 means the chain starts at the first known node and earlier hops are missing. Buyers commonly discount or reject inventory with incomplete chains. hp on each node indicates whether that node is in the flow of payment: when 1, the system in asi pays the seller in sid, who pays the previous node. For schain version 1.0 the spec says hp should always be 1, and intermediaries must propagate it downstream unchanged.
Supply chain in OpenRTB
They are the verification layer for the path schain describes. Each schain node's asi is the advertising system's domain, the same value used to identify systems in the publisher's ads.txt or app-ads.txt file, and each sid is the seller account ID that should appear as a seller_id in that system's sellers.json. A buyer can walk the chain node by node: confirm the first node's asi/sid pair is authorized in the publisher's ads.txt, then resolve every sid against the corresponding sellers.json to see who is actually being paid. A chain that fails those lookups is a spoofing or misdeclaration signal.
Supply chain in OpenRTB
source.tid is a transaction ID that must be common across all participants in the bid request, so multiple exchanges auctioning the same impression (header bidding, for example) can be correlated and deduplicated. source.fd declares who makes the final impression sale decision: 0 means the exchange itself, 1 means an upstream source such as a header bidding wrapper, another exchange, or an ad server mixing direct and programmatic demand. Both are recommended fields on the Source object, which also hosts pchain and schain.
Bid request reference
Move the promoted ext fields to their first-class paths, switch enum references to AdCOM, and adopt the new video fields. Concretely: regs.ext.gdpr becomes regs.gdpr, user.ext.consent becomes user.consent, source.ext.schain becomes source.schain, and regs.ext.us_privacy becomes regs.us_privacy. Enumerated lists were removed from the spec body and now point to AdCOM 1.0. From 2.6-202303, imp.video.plcmt replaces the deprecated placement field with different enum values and no one-to-one mapping. Also note deprecations: user.yob, user.gender, video/audio sequence, and the hashed device ID fields. Validate against your target 2.6 snapshot to catch stragglers.
OpenRTB 2.5 vs 2.6 · placement vs plcmt
OpenRTB 2.6 added CTV pod bidding (podid, poddur, podseq, maxseq, rqddurs, slotinpod, mincpmpersec on video and audio), made SupplyChain, EID/UID, gdpr, and consent first-class objects and fields, and moved all enumerated lists out of the spec into AdCOM 1.0. It also added bid.mtype, imp.rwdd, imp.ssai, structured UserAgent and BrandVersion objects, cattax for taxonomy versioning, and the AUCTION_MIN_TO_WIN macro, while deprecating user.yob, user.gender, the hashed device IDs, and video/audio sequence. Later dated updates added more: GPP fields and DOOH objects in 2.6-202211, plcmt in 2.6-202303.
OpenRTB 2.5 vs 2.6 · OpenRTB version snapshots
For most integrations, not yet: OpenRTB 3.0 has displaced very little 2.x traffic, and the 2.x line remains under active development, with IAB Tech Lab shipping regular 2.6 updates. 3.0 is a ground-up layered redesign: the transaction layer (auction mechanics) is separated from the domain layer, which is defined by AdCOM, and it adds a signed supply chain trust layer. It is deliberately not backwards compatible with 2.x, so adopting it means a parallel integration, not an upgrade. It is worth understanding, and worth building against if a major partner requires it, but 2.6 is where the ecosystem transacts today.
OpenRTB 2.6 vs 3.0
Since 2.6, IAB Tech Lab ships the spec as dated releases (2.6-202204, 2.6-202210, 2.6-202211, 2.6-202303, and onward through 2.6-202505) instead of new minor version numbers. They matter because fields appear, move, and get deprecated between snapshots: regs.gpp and gpp_sid arrived in 2.6-202211, and imp.video.plcmt replaced placement in 2.6-202303. Saying a partner is on 2.6 is therefore not precise enough to validate against. rtblint tracks each snapshot as a separate rule catalog so you can pin the exact revision your integration targets.
OpenRTB version snapshots · Browser tester
OpenRTB 2.5 and 2.6 carry the overwhelming majority of real-time bidding traffic; there are no reliable public numbers splitting the two, but 2.6 adoption keeps growing as CTV pod bidding and first-class privacy fields pull integrations forward, while plenty of production endpoints still speak 2.4 or 2.5. OpenRTB 3.0 remains a small fraction of traffic despite being final since 2018. Practically, build and validate against 2.6 (ideally a specific dated snapshot), and expect to keep accepting 2.5-shaped requests, with the promoted fields still in ext, for a long time.
OpenRTB version snapshots
Within the 2.x line, largely yes: revisions are mostly additive, so a 2.4 request is generally still parseable by a 2.6 endpoint. The exceptions are field promotions (gdpr, consent, schain, us_privacy moved from ext to first-class paths in 2.6), the plcmt replacement of video placement in 2.6-202303 (different enum values, no direct mapping), and removals of long-deprecated attributes like banner wmax/hmax. OpenRTB 3.0 is explicitly not backwards compatible with 2.x: different object model, different layering, separate integration. So treat 2.x upgrades as migrations of specific fields, and 3.0 as a new protocol.
OpenRTB 2.6 vs 3.0 · placement vs plcmt
AdCOM (Advertising Common Object Model) is the IAB Tech Lab spec that defines the domain objects of an ad transaction: media (the ad and its assets), placement (where it can run), and context (site, app, user, device, regs). OpenRTB 3.0 is built on it directly, keeping only auction mechanics in OpenRTB itself and delegating everything domain-shaped to AdCOM. It also matters for 2.x work: OpenRTB 2.6 removed its own enumerated lists section, so values like category taxonomies, connection types, and agent types are now defined by the AdCOM 1.0 lists that 2.6 field descriptions point to.
OpenRTB enums reference · OpenRTB 2.6 vs 3.0
The authoritative sources are IAB Tech Lab's GitHub repositories, no PDF hunting required. OpenRTB 2.6 lives in the openrtb2.x repo as 2.6.md, with dated releases on that repo's Releases page. OpenRTB 3.0 and the community extensions (schain examples, DSA transparency, and others) live in the openrtb repo, and the AdCOM 1.0 domain spec, including all the enum lists 2.6 references, is in the AdCOM repo. All under the InteractiveAdvertisingBureau organization. For a field-by-field reference with validation context, the rtblint docs cover the bid request and response objects directly.
OpenRTB docs · Bid request reference