OpenRTB bid request fields
Every field of the bid request Epom sends a DSP partner — the top-level BidRequest, the privacy signals, imp by placement type, site and app, device, user, pmp.deals and inventory masking — with the corner cases a buyer trips over.
A detail page of the OpenRTB integration reference, which has the two directions, the currency rules and the bid floor chain. This page is the outbound bid request field by field: what Epom sends a DSP partner, when, and what it never sends. The response Epom reads back, the auction and the macros are on OpenRTB bid response, auction and macros; the inbound direction on Epom as a seller: the /hb/bid endpoint.
Request: top-level BidRequest
Eight fields ship on every request; five more are conditional.
| Field | What Epom sends | Always? |
|---|---|---|
id | Generated UUID per request | Yes |
imp[] | Exactly one impression | Yes |
site or app | app when the site’s inventory type is APP or the placement is an application placement; otherwise site. Sending neither or both is rejected before the request leaves. | Yes (one of) |
device | See device | Yes |
user | See user | Yes |
at (auction type) | 1 (first price) or 2 (second price) — see below | Yes |
tmax | The partner’s configured Timeout (ms), minus the time already spent on this auction batch; on a header-bidding bid, never more than what is left of that bid’s own budget | Yes |
cur | ["USD"], or the partner’s currency | Yes |
bcat | Blocked IAB categories — union of blocked categories on site + zone + placement | When non-empty |
source | pchain (asi:sid of your own node), plus schain — in source.schain on 2.6 and in source.ext.schain on every earlier version (2.5 by the IAB SupplyChain spec; 2.2–2.4 have no standard place, so the chain goes where 2.5 puts it). Your node’s sid is the site’s Seller ID once sellers.json is published and one is set, otherwise the generated publisher id. On a site whose seller type is Intermediary or Both, a chain passed on the tag’s schain parameter comes first, with your node appended (or put in place of a copy of it the chain already ends with) and its complete flag kept; with no chain on the tag your node is alone, complete: 0 on an Intermediary site and 1 otherwise — see Supply chain and sellers.json | OpenRTB 2.5 and 2.6 partners only, when the DSP partner’s Send supply chain is on — a banner’s Send supply chain (banner override) wins either way (On sends it, Off suppresses it) |
regs | The privacy signals of the ad request — see Privacy signals: regs and user.consent | Only with Pass privacy signals to DSPs on |
test | 0 | DoubleImpact only |
ext | ext.sub = placementId | ExoClick only |
Corner case:
atis not always first price. It is2(second price) when the banner has Always 2nd price set and it is the only programmatic candidate, and also when several programmatic banners compete and the partner’sFORCE_FIRST_PRICEparameter is explicitly set tofalse. Note the inverted sense of that parameter: absent andtrueboth mean first price; only an explicitfalseopens the door to second price. See First vs second price.
Corner case: the
tmaxyou receive is the Timeout configured on the partner minus the time the current auction batch has already used, so it is usually smaller than the configured value. If 10 ms or less remains, the request is abandoned and never sent at all.
Not sent: badv, bapp, wseat and allimps.
Privacy signals: regs and user.consent
The network’s Pass privacy signals to DSPs setting (Admin → System preferences, on the GDPR card — see GDPR / TCF consent) decides whether a bid request carries the privacy signals of the ad request. It is off by default; off, none of the fields below is written. On, where they go depends on the partner’s OpenRTB version:
| Ad request carried | OpenRTB 2.6 partner | Earlier versions |
|---|---|---|
gdpr — stated, or inferred from the country | regs.gdpr | regs.ext.gdpr |
gdprConsentIab | user.consent | user.ext.consent |
usPrivacy | regs.us_privacy | regs.ext.us_privacy |
gpp, gppSid | regs.gpp, regs.gpp_sid | regs.ext.gpp, regs.ext.gpp_sid |
coppa | regs.coppa | regs.coppa |
regs.gdpr is always written, 0 or 1; every other field only when the request carried a
well-formed value — a stitcher’s macro sent back unfilled ({{US_PRIVACY}}) counts as none. The
signals are forwarded, not enforced: whether the request is served, and whether the bid request is
anonymised, is still the GDPR policy’s decision, and a US opt-out or a COPPA flag does not by itself
strip anything from the request.
Request: imp by placement type
Every request carries exactly one impression object, typed to the placement.
Display (imp.banner)
| Field | Value |
|---|---|
banner.id | The placement id |
banner.w / banner.h | The banner’s own size, or the placement’s size when Use placement size is on |
banner.format[] | A single {w,h} format — 2.5 partners only |
banner.pos | The placement’s ad position |
banner.battr | Blocked creative attributes from the placement |
Corner case:
format[]is sent to 2.5 partners only, and never to 2.6 partners — which is the reverse of what the spec’s own direction of travel suggests, since 2.6 is the version that deprecatesbanner.w/hin favour offormat[]. A 2.6 partner receivesw/hand noformat.
Corner case:
banner.btypeis never populated. Onlybattrcarries the placement’s blocking.banner.mimes,banner.expdirandbanner.apiare likewise never sent.
Corner case: on pop placements, several partner types receive no banner object at all (it is set to null), and DoubleImpact receives an empty one.
Video (imp.video)
| Field | Value |
|---|---|
video.linearity | Linear / non-linear, from the banner’s In-Stream video type |
video.mimes[] | The banner’s Mime types list, or Epom’s default video mime set when empty |
video.minduration / video.maxduration | The placement’s min/max ad duration. A maxDuration on the ad request (a player’s own limit, filled in by a stitcher) narrows maxduration when it is shorter, never widens it, and minduration is lowered to match when it would exceed it |
video.protocols[] | A fixed list — see below |
video.skip / video.skipmin / video.skipafter | From the placement’s skip settings |
video.playbackmethod[] / video.api[] | The placement’s RTB playback methods and API frameworks, when non-empty |
video.w / video.h | The placement ad-unit size, when set |
video.battr | Blocked video attributes from the placement |
video.minbitrate / video.maxbitrate | From the VAST request, when present |
video.pos / video.sequence / video.maxextended / video.boxingallowed | Always sent: 0 / 1 / 0 / 1 by default |
imp.ssai | 3 (stitched and tracked server-side) when the placement’s Server-side ad insertion (SSAI) setting names a stitcher; omitted otherwise — see Video placements |
video.protocols[] is a hard-coded six-element list that does not vary by placement, partner or
protocol version:
| Protocol | Id |
|---|---|
| VAST 2.0 | 2 |
| VAST 3.0 | 3 |
| VAST 2.0 wrapper | 5 |
| VAST 3.0 wrapper | 6 |
| VAST 4.0 | 7 |
| VAST 4.0 wrapper | 8 |
Corner case: VAST 1.0 (1, 4) and VAST 4.1 / 4.2 / 4.3 (11–16) are all modelled in Epom’s enum but are deliberately never advertised. A DSP that only bids on 4.2 will never see a matching protocol, and there is no setting that adds one.
Corner case:
boxingalloweddefaults to1and therefore ships on every video impression, not only when a VAST request supplies it. The VAST request only overrides the default.
Native (imp.native)
| Field | Value |
|---|---|
native.ver | The partner’s native protocol version (default 1.1) |
native.request | The native request spec built from the placement, optionally wrapped per the partner’s native request wrap setting |
native.battr | Blocked native attributes from the placement |
native.api and native.ext are never sent.
Shared imp fields (all types)
| Field | Value |
|---|---|
imp.id | The request’s transaction id — not the placement id |
imp.tagid | The placement key, or a custom OpenRTB tag id resolved from the placement (macros expanded) |
imp.bidfloor | The calculated floor — see The bid floor |
imp.bidfloorcur | USD, or the partner’s currency |
imp.instl | 1 for interstitial and rewarded placements, or when the partner forces interstitial |
imp.rwdd | 1 when the placement’s Application ad category is Rewarded video; omitted otherwise. A rewarded placement carries imp.instl = 1 as well, because it is full screen too — rwdd is the only field that tells the two apart, so a buyer that wants rewarded inventory (or wants to avoid it) reads this one |
imp.secure | 1 on HTTPS requests |
imp.pmp | Present only when the campaign has PMP deals |
imp.ext | Partner-specific (e.g. TrafficHunt subid, Epom WL DSP partnerRef); TwinRed always gets adm_on_resp: 1. On an always-redirect placement a pop is type: "pop" for Custom partners, and for TwinRed the impression is sent without an imp.banner object — see pops |
imp.displaymanager, imp.displaymanagerver, imp.exp and imp.clickbrowser do not exist on
Epom’s model and are never sent.
Request: site and app — page URL, domain and publisher
The request carries site for web traffic and app for app inventory (see the top-level table).
| Field | site (web) | app |
|---|---|---|
domain / name | Host of the page that called the tag: the page origin (the tag’s o parameter or the Origin header), else the Referer host, else the URL configured on the site | app.name is the request’s appName when it carries one, else the site’s name; app.domain only when the SDK request passes one |
page | The full page URL — the tag’s requestUrl, else the Referer — when it belongs to that origin | — |
ref | The document referrer (the tag’s requestRef), when known | — |
id | A numeric hash of the domain; a custom parameter ortb.site.id (cp.ortb.site.id in the tag) replaces it | The site id |
bundle / storeurl / ver | — | The site’s Bundle setting; the SDK request’s bundle (or the WebView’s X-Requested-With package), storeurl and ver override it |
publisher.id | A generated publisher id per site — never the account; the site’s Seller ID once sellers.json is published and one is set | Same |
cat / sectioncat / pagecat | IAB categories of the site, the zone and the placement | Same |
privacypolicy | The site’s privacy-policy flag | — |
content.genre / contentrating / livestream / language | From the request’s contentGenre, contentRating, contentLivestream (1 / 0) and contentLanguage — the show a stitcher describes; each only when the request carried it | Same, under app.content |
The buyer sees a Google domain (imasdk.googleapis.com) instead of the site
Inside Google’s IMA player the tag is requested from Google’s SDK frame. Epom drops imasdk.googleapis.com from the
origin and referrer and falls back to the site URL configured in Epom, so set the site’s URL. A
page that sends no Referer (a strict referrer policy, some players) also leaves page empty —
pass the page URL in the tag’s requestUrl parameter (data-page-url on the async tag), which
replaces the Referer, and requestRef for the document referrer.
Readable source id or zone name in the partner’s stats
imp.tagid carries the placement key by default. To show the buyer a readable id (for example sports-daily-top), set the placement’s Custom tag ID on
its RTB configuration card — see OpenRTB placement settings.
Is pre-roll, mid-roll or post-roll sent to the DSP?
No. Outbound video requests carry no video.startdelay, so a DSP cannot tell a pre-roll from a mid-roll or post-roll.
Request: device
| Field | Value | Notes |
|---|---|---|
ua | User agent | Always |
ip / ipv6 | Client IP | One or the other by address family; shortened on a GDPR-anonymised request — see below |
js | Always 1 | Always |
connectiontype | Mapped to the OpenRTB connection-type enum | When known |
carrier | Mobile carrier | When known |
language | Browser language | When known |
os / osv | Operating system + version | When detected |
devicetype | PHONE / TABLET / CONNECTED_TV / PC | Derived from OS device format |
make / model / hwv | Vendor / family / model | From device detection |
ifa | Advertising ID | The advertising ID, from the request’s ifa parameter or the Android/iOS advertising-ID identification. The SHA1 platform-ID form device.dpidsha1 is no longer sent since release 154. An all-zero ID, an unsubstituted macro ([IFA]) or a placeholder such as null / unknown is treated as no ID |
lmt | Limit ad tracking, 1 / 0 | From the request’s lmt parameter; omitted when absent |
dnt | Do not track, 1 / 0 | From the request’s dnt parameter; omitted when absent |
ext.ifa_type | Source of ifa — rida, tifa, aaid, sessionid, … | From the request’s ifa_type parameter, lower-cased; sent only alongside an ifa |
geo | See below | May be absent entirely |
ext.timezone / ext.timezoneCountry / ext.releaseYear | Non-standard extensions | Timezone name, its primary country (alpha-3), device release year |
device.geo carries country (ISO alpha-3), type, utcoffset, and lat/lon.
Corner case:
geo.lat/lonare not GPS-only. They are filled from IP geolocation on an ordinary request, withgeo.type = IP. Coordinates passed as request parameters are a second source and flipgeo.typetoGPS— on an API request, and on a VAST tag that carrieslatitudeandlongitude(a stitcher filling them from the player), unless the request is anonymised under the GDPR policy. Readgeo.typeif the distinction matters to you — a precise-looking lat/lon may be city-level IP data.
Corner case: the whole
geoobject is omitted when neither an alpha-3 country nor GPS parameters could be resolved.geo.region,metro,cityandzipare never sent.
Corner case: Epom sends
device.dntanddevice.lmtonly when the ad request carried adntorlmtparameter (CTV tags behind a stitcher do). Otherwise a DSP filtering on them sees them absent (treated as unknown), not as0. Epom forwardslmt = 1as it is and does not withhold the ID: a device that limits tracking already zeroes or session-scopes it.
Corner case: screen width and height are not forwarded. Epom collects them (they reach the ad log and the creative-scan context) but drops them before the bid request, so
device.w/hnever appear.ppi,pxratio,suaandmccmncdo not exist on the model.
Corner case: a request the GDPR policy anonymises — no consent under Do not use any cookies or server-side processing, see GDPR / TCF consent — reaches the DSP with
ipending in.0(anipv6keeps only its /48),uaset toGDPR, noos/osv/devicetype, and noifa,ext.ifa_typeor GPS coordinates. Itsuserhas noid, nobuyeruidand nocustomdata.
Request: user
| Field | Value | Notes |
|---|---|---|
id | Epom’s user id (UID) | Always |
consent | The IAB TCF consent string | OpenRTB 2.6 partners, with Pass privacy signals to DSPs on; earlier versions get ext.consent — see Privacy signals |
buyeruid | The partner’s synced user id | Only when user sync is enabled on the partner and its sync cookie is present |
customdata | Partner-specific custom data | Keyed by the partner shortname |
keywords | The request’s keywords custom parameter | |
gender / yob / data[] | DMP segments | Only when DMP-to-bid-request enrichment is enabled |
ext.privateBrowsing | 1 (incognito) / 0 (normal) | Omitted when the state is unknown |
ext.xrw | The in-app WebView package name | From the X-Requested-With header |
ext.channel | The request channel | |
ext.category / ext.income | DMP-derived audience attributes | Only with DMP enrichment |
ext.<custom-param> | Any custom parameter on the request | Macros in the values are resolved before sending |
Note:
user.ext.privateBrowsingis a proprietary Epom extension, not an OpenRTB field. No standard field for private-browsing state exists in 2.5, 2.6 or 3.0, so Epom follows thednt/lmtconvention with an integer1/0and omits the key when the state is unknown. Do not expect other exchanges to send it.
Corner case: with Pass privacy signals to DSPs off — the default — the bid request carries no
user.consentand noregsat all, though Epom still uses the consent string to gate its own serving and to fill consent macros in markup. A DSP that needs the string then gets it only through the$$GDPR$$/$$GDPR_CONSENT_IAB$$macros in the partner’s endpoint URL, or as a custom parameter, which arrives asuser.ext.<your-param-name>.
Request: pmp.deals
Present only when the winning banner’s campaign has PMP deals enabled and at least one deal
matches. Set on imp.pmp:
| Field | Value |
|---|---|
pmp.private_auction | 1 when the campaign marks the auction private, else 0 |
pmp.deals[].id | The deal id |
pmp.deals[].bidfloor | The deal’s floor, converted like imp.bidfloor |
pmp.deals[].bidfloorcur | USD, or the partner’s currency |
pmp.deals[].wseat[] | The deal’s buyer (seat) id — always a single-element array |
pmp.deals[].at | The deal’s auction type |
Deals are matched by the request country first, then any global (country-agnostic) deals are
appended, de-duplicated. A private auction (private_auction = 1) with an empty deal list is
rejected before the request is sent — no HTTP call is made. A non-private auction with an empty
deal list simply drops the pmp object and sends the request without it.
Inventory masking
If the banner’s mask-inventory setting (or the site’s) applies, site.name becomes Site_<id>
and site.domain / site.page / site.ref are cleared. With field-hashing enabled, name and
domain are sent as MD5 hashes instead. Publishers are never sent as the raw account: site.publisher.id (or
app.publisher.id), schain.sid and the pchain suffix carry one ID — the generated publisher id until
sellers.json is published, and from then on the site’s Seller ID where one is set — see
Supply chain and sellers.json.
Note: even unmasked,
site.name,site.domainandsite.iddescribe the live page domain, derived from the ancestor-origin or referer — not the Epom site entity’s name.site.idis a hash of that domain. Do not expect it to match anything in the Epom UI. Onlyhttp(s)addresses count: an origin or referer such asabout:blank,data:…orandroid-app://…is ignored, and when neither is usable the site’s configured URL supplies the domain instead.
Related
- OpenRTB integration reference — the two directions, currency and the bid floor
- OpenRTB bid response, auction and macros
- Epom as a seller: the /hb/bid endpoint
- Configure DSP partners
- OpenRTB (DSP) banners
- GDPR / TCF consent
- PMP deals