---
title: "OpenRTB bid request fields"
description: "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."
type: "reference"
audience: [network-manager, admin]
keywords: [OpenRTB, bid request, BidRequest, imp, banner, video, native, site, app, device, user, regs, gdpr, user.consent, us_privacy, gpp, gpp_sid, coppa, dnt, lmt, ifa, geo, content.genre, maxduration, pmp, deals, bcat, schain, pchain, tmax, at, auction type, privateBrowsing, inventory masking, tagid, rwdd, ssai]
---

Fetch the complete documentation index at: https://epom.com/ad-server/docs/llms.txt. Use this file to discover all available pages before exploring further. Append .md to any documentation page URL to get its markdown version.

# OpenRTB bid request fields

A detail page of the [OpenRTB integration reference](https://epom.com/ad-server/docs/reference/openrtb-integration-reference.md), 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](https://epom.com/ad-server/docs/reference/openrtb-integration-reference-bid-response.md); the
inbound direction on [Epom as a seller: the /hb/bid endpoint](https://epom.com/ad-server/docs/reference/openrtb-integration-reference-seller.md).

## 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](#request-device) | Yes |
| `user` | See [user](#request-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](https://epom.com/ad-server/docs/guides/inventory/supply-chain-and-sellers-json.md#where-each-value-is-used) | 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`](#privacy-signals-regs-and-userconsent) | Only with *Pass privacy signals to DSPs* on |
| `test` | `0` | DoubleImpact only |
| `ext` | `ext.sub = placementId` | ExoClick only |

> **Corner case:** `at` is **not** always first price. It is `2` (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's `FORCE_FIRST_PRICE` parameter is explicitly set to
> `false`. Note the inverted sense of that parameter: **absent** and **`true`** both mean first
> price; only an explicit `false` opens the door to second price. See
> [First vs second price](https://epom.com/ad-server/docs/concepts/auction-first-vs-second-price.md).

> **Corner case:** the `tmax` you 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](https://epom.com/ad-server/docs/guides/admin/gdpr-tcf-consent.md#pass-privacy-signals-to-dsps))
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
> deprecates `banner.w`/`h` in favour of `format[]`. A 2.6 partner receives `w`/`h` and no
> `format`.

> **Corner case:** `banner.btype` is **never populated**. Only `battr` carries the placement's
> blocking. `banner.mimes`, `banner.expdir` and `banner.api` are 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](https://epom.com/ad-server/docs/guides/inventory/placement-video.md#tell-epom-which-stitcher-calls-the-tag) |

`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:** `boxingallowed` defaults to `1` and 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](https://epom.com/ad-server/docs/reference/openrtb-integration-reference.md#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](#request-pmpdeals) |
| `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](https://epom.com/ad-server/docs/guides/inventory/pop-under-direct-link.md#buying-pops-from-an-openrtb-partner) |

`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](https://epom.com/ad-server/docs/guides/inventory/placement-openrtb.md).

### 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`/`lon` are **not GPS-only**. They are filled from IP geolocation on an
> ordinary request, with `geo.type = IP`. Coordinates passed as request parameters are a second
> source and flip `geo.type` to `GPS` — on an API request, and on a VAST tag that carries
> `latitude` and `longitude` (a stitcher filling them from the player), unless the request is
> anonymised under the GDPR policy. Read `geo.type` if the distinction matters to you — a
> precise-looking lat/lon may be city-level IP data.

> **Corner case:** the whole `geo` object is **omitted** when neither an alpha-3 country nor GPS
> parameters could be resolved. `geo.region`, `metro`, `city` and `zip` are never sent.

> **Corner case:** Epom sends `device.dnt` and `device.lmt` only when the ad request carried a
> `dnt` or `lmt` parameter (CTV tags behind a stitcher do). Otherwise a DSP filtering on them sees
> them absent (treated as unknown), not as `0`. Epom forwards `lmt = 1` as 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`/`h` never appear. `ppi`, `pxratio`, `sua` and `mccmnc` do 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](https://epom.com/ad-server/docs/guides/admin/gdpr-tcf-consent.md#what-no-consent-actually-costs) — reaches
> the DSP with `ip` ending in `.0` (an `ipv6` keeps only its /48), `ua` set to `GDPR`, no
> `os`/`osv`/`devicetype`, and no `ifa`, `ext.ifa_type` or GPS coordinates. Its `user` has no `id`,
> no `buyeruid` and no `customdata`.

## 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](#privacy-signals-regs-and-userconsent) |
| `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.privateBrowsing` is 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 the
> `dnt`/`lmt` convention with an integer `1`/`0` and 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.consent` and no `regs`** at 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 as `user.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](https://epom.com/ad-server/docs/guides/inventory/supply-chain-and-sellers-json.md#where-each-value-is-used).

> **Note:** even **unmasked**, `site.name`, `site.domain` and `site.id` describe the **live page
> domain**, derived from the ancestor-origin or referer — not the Epom site entity's name. `site.id`
> is a hash of that domain. Do not expect it to match anything in the Epom UI. Only `http(s)`
> addresses count: an origin or referer such as `about:blank`, `data:…` or `android-app://…` is
> ignored, and when neither is usable the site's configured URL supplies the domain instead.

## Related

- [OpenRTB integration reference](https://epom.com/ad-server/docs/reference/openrtb-integration-reference.md) — the two directions, currency and the bid floor
- [OpenRTB bid response, auction and macros](https://epom.com/ad-server/docs/reference/openrtb-integration-reference-bid-response.md)
- [Epom as a seller: the /hb/bid endpoint](https://epom.com/ad-server/docs/reference/openrtb-integration-reference-seller.md)
- [Configure DSP partners](https://epom.com/ad-server/docs/guides/admin/dsp-partners.md)
- [OpenRTB (DSP) banners](https://epom.com/ad-server/docs/guides/advertising/banner-openrtb-dsp.md)
- [GDPR / TCF consent](https://epom.com/ad-server/docs/guides/admin/gdpr-tcf-consent.md)
- [PMP deals](https://epom.com/ad-server/docs/guides/advertising/pmp-deals.md)