OpenRTB protocol
OpenRTB bid request: objects and required fields
An OpenRTB bid request is one JSON object with exactly two required fields: id and a non-empty imp array. Everything else, from site to regs, is recommended or optional. This page walks every major object in the 2.6 request model and flags the fields that break auctions when they are wrong.
Top-level BidRequest fields
The unnamed outer JSON object is BidRequest. Per the spec, attributes are required only if their omission would break the protocol, and an omitted attribute without a default means unknown. The fields you will actually handle:
id(string, required): request ID assigned by the exchange.imp(object array, required): one entry per impression offered. Must not be empty.at(integer, default 2): auction type. 1 is first price, 2 is second price plus. Values 500+ are exchange-specific.tmax(integer): maximum milliseconds the exchange waits for bids, including internet latency. See tmax and timeouts.cur(string array): allowed bid currencies as ISO-4217 alpha codes.test(integer, default 0): 1 marks the auction as non-billable test traffic.bcat/badv/bapp: blocked advertiser categories (percattax), advertiser domains, and app store IDs. Only one ofacatorbcatshould be present.source,regs,ext: supply chain provenance, regulatory signals, and exchange extensions, covered below.
A minimal valid 2.6 banner request
{
"id": "80ce30c53c16e6ede735f123ef6e3236",
"at": 1,
"tmax": 120,
"cur": ["USD"],
"imp": [{
"id": "1",
"bidfloor": 0.5,
"secure": 1,
"banner": { "w": 300, "h": 250, "pos": 1 }
}],
"site": {
"id": "102855",
"domain": "example.com",
"page": "https://example.com/article/1234",
"publisher": { "id": "8953" }
},
"device": { "ua": "Mozilla/5.0 ...", "ip": "203.0.113.7" },
"user": { "id": "55816b39711f9b5acf3b90e313ed29e5" },
"source": { "tid": "6dd7ea62-9b6b-4dc2-b0a6-1a0d21b0f962" },
"regs": { "gdpr": 0 }
}Strictly, only id, imp, and imp[0].id plus the banner object are required here. The rest is what a real exchange sends so bidders can price the impression.
Imp: the impression on offer
Each Imp needs an id unique within the request; the bid response references it through bid.impid. The presence of a banner, video, audio, or native subordinate declares the media type. A publisher may include several of them in one Imp, but any given bid must conform to exactly one of the offered types.
bidfloor(float, default 0): minimum bid as CPM, inbidfloorcur(default USD). See bid floors.secure(integer): 1 means creative assets must be HTTPS. If omitted, non-secure HTTP support can be assumed.rwdd(integer, default 0): 1 marks a rewarded placement, new in 2.6.exp(integer): advisory seconds that may elapse between auction and impression.
Banner
w and h give one exact size in device-independent pixels. The recommended format array of Format objects lists all permitted sizes and is the modern replacement for the removed wmax / hmax fields. pos takes an AdCOM placement position value, api lists supported frameworks (MRAID, OMID), and battr blocks creative attributes. All of these enumerations live in AdCOM 1.0 lists since 2.6.
Video
mimes is the only required Video field, an array like ["video/mp4"]. plcmt carries the placement type; placement was deprecated in the 2.6-202303 update and rtblint flags it with openrtb.field.deprecated. minduration (default 0) and maxduration are recommended and mutually exclusive with rqddurs. protocols lists supported VAST versions, startdelay distinguishes pre-, mid-, and post-roll, and skip flags a skippable player. The 2.6 pod fields, podid, podseq, and slotinpod, structure CTV ad breaks; see OpenRTB for CTV.
Audio
Audio mirrors Video: mimes is required, minduration, maxduration, and protocols are recommended, and the same pod trio (podid, podseq, slotinpod) applies. Audio-specific fields include feed (feed type) and stitched (1 if the ad is server-stitched into the content stream).
Native
The request field is required and is a JSON-encoded string, not a nested object: the Native Ad request payload is serialized and escaped inside the OpenRTB JSON. Sending it as an object is one of the most common integration bugs. ver names the Native Ads spec version in use. Details on the payload itself: native ads in OpenRTB.
Site vs App
A request must not contain more than one of site, app, or dooh. Site carries id (recommended), domain, page, a publisher object, and content. App swaps page for bundle (the app store ID) and storeurl. At minimum, provide a site ID or page URL; bidders that cannot tell where the impression runs rarely bid well.
Device
ua is the raw user agent; sua is the structured UserAgent object added for a post-User-Agent-reduction world, and is the preferred source when both are present. ip (or ipv6) and the recommended geo object locate the device. ifa is the OS-provided advertising ID in the clear, devicetype is an AdCOM device type value, and the recommended lmt flag signals limit ad tracking (1 means tracking must be limited).
User
id is the exchange-specific user ID and buyeruid the bidder-specific one from cookie sync. eids is a first-class array of extended identifier objects for third-party identity providers. consent holds the TCF consent string when GDPR applies; like regs.gdpr, it moved out of ext into the core object in 2.6.
Source and Regs
source.fd says who makes the final sale decision (0 exchange, 1 upstream), source.tid is the transaction ID shared by every participant, and source.schain is the SupplyChain object, promoted from source.ext.schain in 2.6. rtblint reports the old path with openrtb.field.moved. See supply chain in OpenRTB.
regs carries coppa, gdpr (top-level since 2.6), us_privacy, and the GPP pair gpp / gpp_sid added in 2.6-202211. Full walkthrough: privacy signals in OpenRTB.
Required vs recommended vs optional
| Field | Status | What trips people up |
|---|---|---|
id | Required | Request ID assigned by the exchange. |
imp | Required, 1+ entries | An empty array breaks the protocol. |
imp.id | Required | Unique within the request; bids reference it via impid. |
video.mimes | Required (when video present) | The only required field inside Video. |
native.request | Required (when native present) | A JSON-encoded string, not a nested object. |
site / app / dooh | Recommended, at most one | Sending two of them is a spec violation. |
at | Optional, default 2 | 1 = first price, 2 = second price plus. |
imp.bidfloor | Optional, default 0 | CPM in bidfloorcur, which defaults to USD. |
source.tid | Recommended | Transaction ID common across all participants. |
device.geo, device.lmt | Recommended | Optional by type, elevated for business importance. |
regs.gdpr | Optional | Top-level Regs field in 2.6; previously regs.ext.gdpr. |
FAQ
Which fields are required in an OpenRTB bid request?
Only two top-level fields are technically required: id and imp with at least one entry. Each Imp needs its own id plus at least one of banner, video, audio, or native. Within those, video.mimes and native.request are required. Everything else is recommended or optional, though exchanges and bidders often enforce more.
Can a bid request contain both site and app?
No. The OpenRTB 2.6 spec states a bid request must not contain more than one of a Site, App, or DOOH object. Site describes browser inventory, App describes application inventory, and DOOH describes digital out-of-home inventory.
Can one Imp offer multiple media types?
Yes. A publisher can include banner, video, audio, and native objects in the same Imp at its discretion. Any given bid, however, must conform to exactly one of the offered types.
Is video.placement still valid in OpenRTB 2.6?
It is deprecated. The 2.6-202303 update deprecated video.placement in favor of video.plcmt, which uses the Plcmt Subtypes - Video list in AdCOM 1.0. Many exchanges still read both during the transition, but new integrations should send plcmt.
Validate it
Paste any request into the bid request tester to check it against a tracked OpenRTB 2.6 snapshot: missing required fields, unknown fields (openrtb.field.undefined), deprecated and moved paths, and type mismatches, each with a JSON path. The same engine runs in the Rust CLI for CI. For the other half of the transaction, see the bid response.