FAQ

OpenRTB FAQ

93 questions ad ops and integration engineers actually ask about OpenRTB, answered directly with the exact field, version, and default. Every answer is grounded in the IAB Tech Lab specification; the longer treatments live in the docs and guides.

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.

What is OpenRTB?

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.

How does a real-time bidding auction work end to end?

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.

What's the difference between OpenRTB and Prebid.js?

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.

Is header bidding the same as OpenRTB?

No. Header bidding is an auction architecture: the publisher offers each impression to multiple demand sources before (or alongside) the ad server call instead of waterfalling through them one at a time. OpenRTB is the message format those demand calls are usually made in. OpenRTB even models the header bidding case explicitly: the source object carries fd (whether the exchange or an upstream entity makes the final decision) and tid (a transaction ID shared across all participants selling the same impression), which exist precisely because header bidding means the exchange receiving the request may not control the final sale.

What's the difference between an SSP, a DSP, and an ad exchange?

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.

What is a bid request in programmatic advertising?

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.

What is a bid response in OpenRTB?

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.

Which fields are required in an OpenRTB bid request?

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.

How do I tell if an auction is first-price or second-price from the bid request?

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.

What is the imp object in an OpenRTB bid request?

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.

How do I signal web vs in-app inventory in a bid request?

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.

How do I send test traffic in OpenRTB?

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.

How do currencies work in an OpenRTB bid request?

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.

What do bcat, badv, and bapp do in a bid request?

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.

What is cattax and how does it relate to cat?

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.

What is the device object for and which fields matter?

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.

What is the user object in an OpenRTB bid request?

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.

What is the ext field in OpenRTB and how do extensions work?

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.

What do wseat and bseat do in a bid request?

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.

What does allimps mean in an OpenRTB bid request?

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.

How large is a typical bid request and what volume should I expect?

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.

What format and encoding rules apply to OpenRTB payloads?

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.

Who maintains OpenRTB and where does the spec live?

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.

What OpenRTB version should a new integration target in 2026?

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.

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.

Why is my bidder not receiving bid requests?

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.

Why is my bid rate low even though I receive requests?

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.

How do I find out why my bids are losing auctions?

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.

How do I debug a systematic OpenRTB no-bid?

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.

What does HTTP 204 mean in OpenRTB, and how is it different from nbr?

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.

How do I validate an OpenRTB bid request?

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.

How do I validate OpenRTB payloads in CI?

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.

How do I test an OpenRTB integration without spending money?

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.

Why did the exchange or bidder reject my bid request as invalid?

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.

Why is my bid response being dropped by the exchange?

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.

How do I debug RTB timeouts?

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.

What causes impression count discrepancies between DSP and SSP?

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.

How do I capture and replay bid requests for QA?

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.

What is a seatbid, and why is my response's seatbid empty?

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.

Why is my win notice (nurl) not firing?

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.

What is creative audit in RTB, and why are my creatives filtered?

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.

Why does my video bid never win?

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.

How do I check which OpenRTB version a partner is running?

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.

What is the x-openrtb-version HTTP header?

It is the custom HTTP header OpenRTB defines for version signaling on the wire: the sender of a bid request declares the protocol version it speaks, for example x-openrtb-version: 2.6. The body should also be sent with Content-Type: application/json. The header names the version line, not the dated snapshot, so 2.6 traffic from 2022 and 2025 both say 2.6 even though the field sets differ; that finer distinction is exactly why validating against a specific snapshot matters.

Are unknown or extra fields in a bid request a problem?

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.

How do I gate CI on specific OpenRTB validation rules?

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.

What tools exist to validate OpenRTB payloads?

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.

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.

How do I build an OpenRTB video bid request?

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.

What is plcmt in OpenRTB video?

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.

What plcmt value should I send for CTV inventory?

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.

Should I send placement or plcmt in a video bid request?

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.

How does ad pod bidding work in OpenRTB 2.6?

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.

How do I signal server-side ad insertion (SSAI) in a bid request?

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.

How do I signal rewarded inventory 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.

How do I signal skippable video in a bid request?

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.

What VAST versions do OpenRTB protocols values map to?

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.

How does audio differ from video in OpenRTB?

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.

How do I send a native bid request in OpenRTB?

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.

Why is imp.native.request a string instead of a JSON object?

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.

How do I set a bid floor in OpenRTB?

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).

Why are all my bids coming back below floor?

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.

How do PMP deals work in OpenRTB?

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.

What does at=3 mean on an OpenRTB deal?

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.

What are duration floors (durfloors) in OpenRTB 2.6?

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.

How does the ${AUCTION_PRICE} macro work?

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.

What's the difference between nurl, burl, and lurl?

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.

When is burl fired versus nurl?

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.

What is the ${AUCTION_MIN_TO_WIN} macro?

${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.

What is mtype in an OpenRTB bid response?

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.

How do I return VAST in an OpenRTB bid response?

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.

What are eventtrackers in Native 1.2?

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.

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.

How do I pass GDPR consent in an OpenRTB bid request?

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.

How do I pass a GPP string in OpenRTB?

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.

GPP vs us_privacy: which should I send?

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.

How do I signal CCPA 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.

How do I signal COPPA in an OpenRTB request?

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.

What is the DSA extension 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.

What is the difference between device.lmt and device.dnt?

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.

What happens to ifa when the user limits ad tracking (ATT)?

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.

What is user.eids in OpenRTB and how do extended IDs travel?

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.

What's the difference between user.id and user.buyeruid?

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.

How does cookie matching work in OpenRTB?

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.

How do I implement schain 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.

What does schain complete=1 mean, and what is hp?

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.

How do ads.txt and sellers.json relate to schain?

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.

What are source.tid and source.fd 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.

How do I migrate from OpenRTB 2.5 to 2.6?

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.

What changed in OpenRTB 2.6?

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.

Is OpenRTB 3.0 worth adopting, and who actually uses it?

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.

What are the OpenRTB 2.6 dated snapshots and why do they matter?

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.

Which OpenRTB version is most used?

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.

Is OpenRTB backwards compatible between versions?

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.

What is AdCOM?

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.

Where do I download the OpenRTB spec?

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.

Still stuck?

If the question is about a specific payload, paste it into the bid request tester: it validates against the exact OpenRTB 2.6 snapshot you pick, client-side, and each finding names the rule and the JSON path. The same checks run from the CLI in CI.