The Price Floors feature provides an open framework for Publishers to configure Prebid price floors on their own or to work with advanced vendors who can provide optimized floors.
A ‘floor’ is defined as the lowest price a bid that will be accepted for each Prebid auction. It’s a way for publishers to signal to bidders the price to beat, thereby protecting the value of their inventory. Proper floors are dynamic and determined based on detailed factors like mediaType, adSlot, size, and other factors.
The Prebid Server version of this feature is similar to the Prebid.js Price Floors Module with a few differences. Here are the advantages to having this feature on the server-side:
Here are the differences between Prebid.js and Prebid Server floors:
The syntax of the floors schema is so similar between Prebid.js and Prebid Server that floor providers should not need to change how they generate floors data.
The PBS floors feature isn’t a formal PBS module. Yeah, that bums us out too. Turns out that floors just don’t fit the module architecture model. Specifically, modules don’t support the feature where bid adapters need access to floor data.
Notes:
Here’s the high level picture of what’s happening in Prebid Server to support floors:
imp.bidfloor
field, which is sent to each bid adapter in the auction.Vendor services generally calculate floors with optimization algorithms fed by an analytics adapter. Floors data is expected to be updated hourly, or perhaps once per day. It is best practice is to avoid a “hardcode-and-forget” method of setting floors. If a publisher doesn’t want to utilize a floors service, they should commit to periodically reviewing manually-defined numbers.
Drilling down one level into some detail, the PBS Floors feature has several main parts that work together:
The following sections go through each of the floor components.
The Prebid Server host company and publisher may optionally decide to work with an external vendor to provide optimized floors.
Setting up the service is straightforward:
That’s it. Here’s how fetching works:
Floor signaling is the process that determines which floor value to send to each bid adapter. Here’s what it does for each auction:
ext.prebid.floors
.The default way that bid adapters receive the floor is just the OpenRTB standard bidfloor
(and bidfloorcur
).
But floors may actually be richer than a single value. For instance, in a multi-format impression,
there could be a floor for banner and a separate floor for video. Unfortunately, OpenRTB 2.5/2.6 don’t support bidfloor at the mediaType level, so most bidders don’t support that distinction either. However,
Prebid Server offers a way for bid adapters to distinguish between
mediatypes, sizes, and other details if they support it. See the Bid Adapter Floor Interface section below.
The enforcement stage of the Floors feature validates bid responses from the adapters.
Prebid Server floors data must always be formatted using the Schema 2 format supported by Prebid.js. There a few fields supported by Prebid Server not supported in Prebid.js, but it’s completely backwards-compatible – PBS will accept and process any Prebid.js floors data formatted in Schema 2.
As described in the Signaling section above, floor data may be defined in several ways. Here’s the order of priority:
Here’s an example of floors data coming in on the request:
{
...
"ext": {
"prebid": {
"floors": {
"floorMin": 1.00,
"floorMinCur": "USD",
...
"data": {
"modelGroups": [{
...
}]
}
}
}
}
}
If you’re a vendor looking to provide dynamic floors data via Prebid Server, it’s possible to use the same system you’re using for Prebid.js. Or you may want to take advantage of the additional dimensions available.
You’ll need to work with the Prebid Server host company on how to get the analytics. It may be necessary to build a server-side analytics adapter in order to get data about which models were used, skipRate effectiveness, etc.
At this time, floors analytics data (e.g. skipped) is not passed back to the client. The expectation is that Publishers will use this floors feature mainly for mobile app and AMP scenarios. It’s assumed that Web sites running Prebid.js will utilize the client-side module and analytics. If the community requires client-side analytics for floors, please open an issue with as much detail as you can about the requirements.
Note: when producing a floors file, be aware that the entire contents are mered under the ‘data’ object of Schema 2. i.e. the file should not contain the ‘data’ object, just attributes of the ‘data’ object.
To understand how floor rules look and operate, see the rules selection process in the Prebid.js floors module doc.
These are the fields that can be used in defining the rules. The ones highlighed in bold are supported only in Prebid Server, not in Prebid.js.
Dimension | Type | Example | How it works |
---|---|---|---|
siteDomain | string | “level4.level3.example.com” | This is the full site domain. The value in the floor rule is compared to ORTB {site,app,dooh}.domain |
pubDomain | string | “example.com” or “example.co.uk” | This is the publisher’s base domain. It’s compared to {site,app,dooh}.publisher.domain |
domain | string | “example.com” | This is the robust way to check either the full domain or the base domain. It’s compared against {site,app,dooh}.domain and {site,app,dooh}.publisher.domain. If any of them match this part of the rule matches. |
bundle | string | “org.prebid.drprebid” | This value in the rule is compared to ORTB app.bundle |
channel | string | “app” | This rule value is compared against ORTB ext.prebid.channel.name |
mediaType | string | “video” | If more than one of the following ORTB objects exists, only the “*” rule value will match: imp.banner, imp.video, imp.native, imp.audio. Otherwise:
|
size | string | “300x250” |
|
gptSlot | string | “/111/homepage” | if imp.ext.data.adserver.name==”gam” then compare the rule value against imp.ext.data.adserver.adslot. Otherwise compare the rule value against imp.ext.data.pbadslot |
bidder | string | “bidderA” | Compare the rule value against imp.ext.prebid.bidder.BIDDER (Requires PBS-Java 3.5+) |
adUnitCode | string | “/111/homepage” | (PBS-Java 1.101+) Compares the rule value against ORTB fields in this order:
|
pbAdSlot | string | “/111/homepage#div1” | Deprecated - we suggest using adUnitCode instead. Compare the rule value against imp.ext.data.pbadslot |
country | string | “USA” | Compare the rule value against device.geo.country (ISO-3166-1-alpha-3) |
deviceType | string | “desktop”, “phone”, “tablet” | This is a very simple device-type algorithm:
|
Note that these schema dimensions are coded into the floors feature. If you need another attribute to break out rules, please submit a code pull request with an enhancement.
Here’s an example of some rules using PBS-specific schema dimensions:
{
...
"data": {
"floorProvider": "providerA",
"currency": "USD",
"modelGroups": [{
"modelWeight": 50,
"modelVersion": "111111",
"schema": {
"fields": ["country", "mediaType", "deviceType"],
"delimiter": "|"
},
"values": {
"usa|banner|tablet": 0.50, // banners on tablets from the US
"can|video-outstream|desktop": 0.75, // outstream on Canadian desktops
...
},
"default": 0.01
}, {
....
]}
}
...
The floors feature gives publishers and host companies a fair amount of control over how it operates.
Here are the configurable items:
Config | Type | Default | Description |
---|---|---|---|
enabled | boolean | true | Master switch for turning off the floors feature for this account. |
enforce-floors-rate | integer | 100 | Default value for the enforceRate attribute. |
adjust-for-bid-adjustment | boolean | true | Default value for the enforcement.bidAdjustment attribute. |
enforce-deal-floors | boolean | false | Default value for the enforcement.floorDeals attribute. |
fetch.enabled | boolean | false | Turns on the polling of an external dynamic floor data source. |
fetch.url | string | - | URL for the external dynamic floor data source. |
fetch.timeout-ms | integer | 3000 | How long to wait for the dynamic floor data source. |
fetch.max-file-size-kb | integer | 100 | How big can the rule data get before being rejected. Helps protect memory problems. |
fetch.max-rules | integer | 1000 | How many rules is too many. Helps protect processing time. |
fetch.max-age-sec | integer | 86400 | How long is dynamically fetched data considered usable? |
fetch.period-sec | integer | 3600 | How often between attempts to poll for updated data? |
use-dynamic-data | boolean | true | Can be used as an emergency override to start ignoring dynamic floors data if something goes wrong. |
The precise details of configuration may differ for PBS-Java vs PBS-Go. See the configuration document for your platform.
Most bid adapters will not need to do anything special to obtain floors. They can just read imp.bidfloor and imp.bidfloorcur, converting currency as needed.
However, there are some use cases where advanced adapters might want to get more granular access to floors data:
Let’s look at the first use case in more detail. When the Floors Signaling component sees multiple media types in a single impression, it will always choose a “*“-valued rule for mediaType. Say we have this rule scenario:
{
"data": {
"currency": "USD",
"modelGroups": [{
...
"schema": {
"fields": ["country", "mediaType"],
"delimiter": "|"
},
"values": {
"usa|banner": 0.50,
"usa|video-outstream": 0.75,
"usa|video-instream": 0.99,
"usa|*": 0.99,
...
},
"default": 0.01
}, {
...
}]
}
}
And say this is the request:
{
"imp": {
"banner": {
...
},
"video": {
"placement": 1,
...
}
}
}
The Floors Signaling component sees that both banner and video are present, so will only match the “*” rule for mediaType. In other words, it will set imp.bidfloor to 0.99 in this scenario. But if a bidder only supports banner, the 0.99 floor is higher than necessary. It would be easier to meet the 0.50 floor for banners.
To address this, a special floor function enables adapters to retrieve more granular floor values for each impression in the auction. Due to the complexity of the rule system, deriving the correct floor would be a difficult task without this function.
See the developer bid adapter documentation for details:
To get floors information, developers of an analytics adapter will need to pull data from both the bidrequest object and bidresponse object.
In most scenarios, floor data is all in the bidrequest object, specifically in the ext.prebid.floors object. See Schema 2 for the available fields.
Here are some scenarios where looking at the bid response level data is used:
Check the bid response object for this data:
Field | Type | Description |
---|---|---|
bidAdjustment | boolean | Used to record if the bid floor was CPM adjusted based on the bidAdjustmentFactor provided in the bidRequest |
floorCurrency | string | Currency of the floor matched. Only specified if the bidder calls getFloor. |
floorRule | string | The matching rule for the given bidResponse. Only needed if the bidder calls getFloor(). |
floorRuleValue | float | Rule floor selected. This is to differentiate between the floor bound to the selected rule and the OpenRTB bidfloor (if available). Only needed if the bidder calls getFloor(). |
floorValue | float | The value of the floor enforced for this bidder. This will be the greater of the OpenRTB bidfloor and floorRuleValue. Only needed if the bidder calls getFloor or if the floor was adjusted due to bidCpmAdjustments. |
See the developer’s guide to building an analytics adapter for more details, and see existing code as an example.