The Price Floors Module provides an open source framework in Prebid for Publishers to configure Prebid price floors on their own or to work with a vendor who can provide floors.
A ‘floor’ is defined as the lowest CPM price a bid will need to meet 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.
The module provides several ways for Prebid floors to be defined, that are used by bidder adapters to read floors and enforced on bid responses in any supported currency. The floors utilized by the Price Floors Module are defined by one or more set of rules containing any or all of the following dimensions:
When using GPT Slot name, the GPT library is required to load first. Failing to do so may yield unexpected results and could impact revenue performance.
The entire set of floors selected by the Price Floors Module for a given auction is called a “Rule Location”. A Rule Location can be any one of:
Even though floors are defined with five pre-configured dimensions, it’s possible (in Prebid.js) to extend the list of dimensions to attributes of the page, user, auction or other data by supplying a dimension matching function. For example, a publisher can provide a matching function that returns the device type to allow the Floor module to use device type as an attribute within a prebid floor rules file.
Note that Prebid Server also supports a floors feature that is very similar to the Prebid.js module. They both share Schema 2, and there are PBS-specific notes below. The expectation with the Prebid Server floors feature is that Publishers will use it mainly for mobile app and AMP scenarios. Web sites running Prebid.js will utilize this client-side module.
Notes:
There are several places where the Floor module changes the behavior of the Prebid.js auction process. Below is a diagram describing the general flow of the client-side Price Floors Module:
As soon as the setConfig({floors}) call is initiated, the Price Floors Module will build an internal hash table for each auction derived from a Rule Location (one of Dynamic, setConfig or adUnit)
skipRate
flag is specified, there’s an A/B test in progress, so the module will decide for this request whether floors processing should be ‘skipped’ or not.How price floors are used in the page will depend on how you’re obtaining the floors and how Prebid.js is integrated into the pages. For optimal revenue performance, we strongly recommend an automated flooring method compared to manual, sporadic updates.
In this approach, the Publisher configures the floors directly into the Prebid.js AdUnits. This method can be used on simple pages or as part of a content management system that dynamically creates AdUnits.
Below are some basic principles of ad unit floor definitions:
var adUnits = [
{
code: 'test-div',
mediaTypes: {
banner: { sizes: [[300,250],[300,600]] },
video: {
context: 'outstream',
playerSize: [300,250],
...
}
},
floors: {
currency: 'USD',
schema: {
delimiter: '|',
fields: [ 'mediaType', 'size' ]
},
values: {
'banner|300x250': 1.10,
'banner|300x600': 1.35,
'video|300x250': 2.00
}
},
bids: [
...
]
}
];
When defining floors at the adUnit level, the Price Floors Module requires the floors object to be defined in setConfig, even if the definition is an empty object as shown below: javascriptpbjs.setConfig({ floors: {} });
Floor definitions are set in the “values” object containing one or more rules, where the rule is the criteria that needs to be met for that given ad unit, with an associated CPM floor. In the above example, the floors are enforced when the bid from a bidder matches the “mediaType” and “size” combination. Since many bid adapters are not able to ingest floors per size, a simpler setup can be:
floors: {
currency: 'USD',
skipRate: 5,
modelVersion: 'Sports Ad Unit Floors',
schema: {
fields: [ 'mediaType']
},
values: {
'banner': 0.8,
'video': 2.01
}
}
For more advanced publisher setups, values can accept a “*” to denote a catch-all when a bid comes back that the Price Floors Module does not have an exact match and for bid adapters who are not able to use a floor per size, the bid adapter will automatically receive the “*” rule’s floor if available. Example setup can be:
floors: {
currency: 'USD',
skipRate: 5,
modelVersion: 'Sports Ad Unit Floors',
schema: {
fields: [ 'mediaType', 'size']
},
values: {
'banner|300x250': 0.8,
'banner|300x600': 1.8,
'banner|728x90': 0.90,
'banner|*': 1.00,
'video|300x250': 2.01,
'video|*': 2.01
}
}
Alternatively, if there’s only one mediaType in the AdUnit and a single global floor, the syntax gets easier:
...
floors: {
default: 1.00 // default currency is USD
},
...
This approach is intended for scenarios where the Publisher or their Prebid managed service provider periodically appends updated floor data to the Prebid.js package. In this model, there could be more floor data present to cover AdUnits across many pages.
pbjs.setConfig({
floors: {
enforcement: {
floorDeals: false, //default to false
bidAdjustment: true
},
data: { // default if endpoint doesn't return in time
currency: 'USD',
skipRate: 5,
modelVersion: 'some setconfig model version',
schema: {
fields: [ 'gptSlot', 'mediaType' ]
},
values: {
'/1111/homepage/top-rect|banner': 0.80,
'/1111/homepage/top-rect|video': 2.20,
'/1111/homepage/top-rect|*': 1.50,
'/1111/homepage/left-nav|banner': 1.20,
'/1111/homepage/left-nav|video': 2.20,
'/1111/homepage/left-nav|*': 1.50,
... there could be hundreds of these ...
'*|banner': 0.98,
'*|video': 1.74
}
}
}
});
By defining floor data with setConfig, the Price Floors Module will map GPT ad slots to AdUnits as needed. It does this in the same way as the setTargetingForGPTAsync() function – first looking for an AdUnit.code that matches the slot name, then looking for an AdUnit.code that matches the div id of the named GPT slot.
Here’s another example that includes more fields:
pbjs.setConfig({
floors: {
data: { // default if endpoint doesn't return in time
currency: 'USD',
skipRate: 5,
modelVersion: 'some setconfig model version',
schema: {
fields: [ 'domain', 'gptSlot', 'mediaType', 'size']
},
values: {
'www.examplepub.com|/1111/homepage/top-rect|banner|300x250': 0.80,
'www.examplepub.com|/1111/homepage/top-rect|video|300x250': 2.20,
'www.examplepub.com|/1111/homepage/left-nav|banner|300x250': 1.77,
'www.examplepub.com|/1111/homepage/left-nav|video|300x250': 2.88
...
}
}
}
});
The final method of obtaining floor data allows the publisher to delay the auction for a certain time period to obtain up-to-date floor data tailored to each page or auction context. The assumed workflow is:
Here’s an example defining a simple GET endpoint:
pbjs.setConfig({
floors: {
enforcement: {
floorDeals: true, //default to false
},
auctionDelay: 100, // in milliseconds
endpoint: {
url: 'https://floorprovider.com/a1001-mysite.json'
}
}
});
The Price Floors Module is flexible to handle floors set in multiple locations. Like in the below example a publisher can configure Dynamic floors in addition to Package floors (in setConfig). While the Price Floors Module is only able to use one set of rules (either Package, adUnit or Dynamic) defined as a Floor Location, setting floors in the Package will be utilized when the Dynamic floors fail to return data or another error condition occurs with the Dynamic fetch.
pbjs.setConfig({
floors: {
enforcement: {
floorDeals: false, //default to false
},
auctionDelay: 100, // in milliseconds
endpoint: {
url: 'https://floorprovider.com/a1001-mysite.json'
},
data: { // default if endpoint doesn't return in time
floorProvider: 'floorProviderName',
currency: 'USD',
skipRate: 5,
modelVersion: ‘new model 1.0’
schema: {
fields: [ 'mediaType' ]
},
values: {
'banner': 0.80,
'video': 1.20
}
}
}
});
The examples above covered several different scenarios where floors can be applied. Below we will cover the syntax and definition of the floors data schema. As of Prebid.js version 3.24, the Price Floors Module supports a second data schema with the ability to add new schemas to future-proof the needs of additional design changes while keeping backwards compatibility.
Schema 1 restricts floors providers or publishers to applying only one data group. To test more than one floor group, floor providers or publishers are required to reset the data set with new rules after each request bids.
Note: if you’re a dynamic floor provider service, your response must be a subset that will be merged under the ‘data’ object.
Param | Type | Description | Default |
---|---|---|---|
floorMin | float | The mimimum CPM floor used by the Price Floors Module (as of 4.13). The Price Floors Module will take the greater of floorMin and the matched rule CPM when evaluating getFloor() and enforcing floors. | - |
floorProvider | string | Optional atribute (as of prebid version 4.1) used to signal to the Floor Provider’s Analytics adapter their floors are being applied. They can opt to log only floors that are applied when they are the provider. If floorProvider is supplied in both the top level of the floors object and within the data object, the data object’s configuration shall prevail. | - |
enforcement | object | Controls the enforcement behavior within the Price Floors Module. | - |
skipRate | integer | skipRate is a random function whose input value is any integer 0 through 100 to determine when to skip all floor logic, where 0 is always use floor data and 100 is always skip floor data. The use case is for publishers or floor providers to learn bid behavior when floors are applied or skipped. Analytics adapters will have access to model version (if defined) when skipped is true to signal the Price Floors Module is in floors mode. If skipRate is supplied in both the root level of the floors object and within the data object, the skipRate configuration within the data object shall prevail. | 0 |
enforcement.enforceJS | boolean | If set to true, the Price Floors Module will provide floors to bid adapters for bid request matched rules and suppress any bids not exceeding a matching floor. If set to false, the Price Floors Module will still provide floors for bid adapters, there will be no floor enforcement. | true |
enforcement.enforcePBS | boolean | If set to true, the Price Floors Module will signal to Prebid Server to pass floors to it’s bid adapters and enforce floors. If set to false, the pbjs should still pass matched bid request floor data to PBS, however no enforcement will take place. | true |
enforcement.floorDeals | boolean | Enforce floors for deal bid requests. | false |
enforcement.bidAdjustment | boolean | If true, the Price Floors Module will use the bidAdjustment function to adjust the floor per bidder. If false (or no bidAdjustment function is provided), floors will not be adjusted. Note: Setting this parameter to false may have unexpected results, such as signaling a gross floor when expecting net or vice versa. | true |
endpoint | object | Prebid.js only: controls behavior for dynamically retrieving floors. | - |
endpoint.url | string | URL of endpoint to retrieve dynamic floor data. | - |
data | object (required) | Floor data used by the Price Floors Module to pass floor data to bidders and floor enforcement. | - |
data.floorProvider | string | Optional atribute (as of prebid version 4.2) used to signal to the Floor Provider’s Analytics adapter their floors are being applied. They can opt to log only floors that are applied when they are the provider. If floorProvider is supplied in both the top level of the floors object and within the data object, the data object’s configuration shall prevail. | - |
data.currency | string | Currency of floor data. Floor Module will convert currency where necessary. See Currency section for more details. | ‘USD’ |
data.skipRate | integer | skipRate is a random function whose input value is any integer 0 through 100 to determine when to skip all floor logic, where 0 is always use floor data and 100 is always skip floor data. The use case is for publishers or floor providers to learn bid behavior when floors are applied or skipped. Analytics adapters will have access to model version (if defined) when skipped is true to signal the Price Floors Module is in floors mode. If skipRate is supplied in both the root level of the floors object and within the data object, the skipRate configuration within the data object shall prevail. | 0 |
data.floorsSchemaVersion | integer | The module supports two versions of the data schema. Version 1 allows for only one model to be applied in a given data set, whereas Version 2 allows you to sample multiple models selected by supplied weights. If no schema version is provided, the module will assume version 1 for the sake of backwards compatiblity. For schema version 2 see the next section. | 1 |
data.modelVersion | string | Used by floor providers to train on model version performance. The expectation is a floor provider’s analytics adapter will pass the model verson back for algorithm training. | - |
data.modelTimestamp | integer | Epoch timestamp associated with modelVersion. Can be used to track model creation of floor file for post auction analysis. | - |
data.schema | object | allows for flexible definition of how floor data is formatted. | - |
data.schema.delimiter | string | Character separating the floor keys. | ’|’ |
data.schema.fields | array of strings | Supported values are: gptSlot, adUnitCode, mediaType, size, domain | - |
data.values | key / values | A series of attributes representing a hash of floor data in a format defined by the schema object. | - |
data.values.”rule key” | string | Delimited field of attribute values that define a floor. | - |
data.values.”rule floor value” | float | The floor value for this key. | - |
data.default | float | Floor used if no matching rules are found. | - |
additionalSchemaFields | object | Object contain the lookup function to map custom schema.fields | - |
additionalSchemaFields.”custom key” | string | custom key name | - |
additionalSchemaFields.”key map function” | function | Function used to lookup the value for that particular custom key | - |
When you see ‘skipped’ in the floors data, it indicates the status of the skipRate
A/B test for this request. This is just a mechanism to be able to tell if the floor rules are providing value. e.g skipRate
will often start at a high value like 90%, which means “only apply floors 10% of the time”. If skipRate is 90, you would expect to see “skipped: true” 9 times out 10.
Schema 2 allows floors providers to A/B-test one or more floor groups, determined at auction time.
The following principles apply to Schema 2:
While some attributes are common in both schema versions, for completeness, all valid schema 2 attributes are provided:
Note: if you’re a dynamic floor provider service, your response must be a subset that will be merged under the ‘data’ object.
Param | Type | Description | Default |
---|---|---|---|
floorMin | float | The mimimum CPM floor used by the module (as of 4.13). The module will take the greater of floorMin and the matched rule CPM when evaluating getFloor() and enforcing floors. | - |
floorMinCur | float | Prebid Server only: the currency used for the floorMin value. | - |
floorProvider | string | Optional atribute (as of prebid version 4.1) used to signal to the Floor Provider’s Analytics adapter their floors are being applied. They can opt to log only floors that are applied when they are the provider. If floorProvider is supplied in both the top level of the floors object and within the data object, the data object’s configuration shall prevail. | - |
skipRate | integer | skipRate is a random function whose input value is any integer 0 through 100 to determine when to skip all floor logic, where 0 is always use floor data and 100 is always skip floor data. The use case is for publishers or floor providers to learn bid behavior when floors are applied or skipped. Analytics adapters will have access to model version (if defined) when skipped is true to signal the module is in floors mode. If skipRate is supplied in both the root level of the floors object and within the data object, the skipRate configuration within the data object shall prevail. | 0 |
enabled | boolean | Prebid Server only: provides a request level override to disable server-side floor activity. If there’s server-side floor config, it must also be true in order for floor activity to take place. | true |
fetchStatus | string | Prebid Server only: this is a read-only field set by the Prebid-Server floors feature to let analytics adapters know whether the floors data used was dynamically fetched. | n/a |
skipped | boolean | Prebid Server only: this is a read-only field set by the Prebid-Server floors feature to let analytics adapters know whether the ‘skipRate’ feature was invoked. i.e. this value is ‘true’ when the floor activity has been turned off by the skipRate, and ‘false’ otherwise. | n/a |
location | string | Prebid Server only: this is a read-only field set by the Prebid-Server floors feature to let analytics adapters know where the floors data came from. Possible values are: ‘request’, ‘fetch’ or ‘noData’. | n/a |
enforcement | object | Controls the enforcement behavior within the module. | - |
enforcement.enforceJS | boolean | If set to true, the module will provide floors to bid adapters for bid request matched rules and suppress any bids not exceeding a matching floor. If set to false, the module will still provide floors for bid adapters, but there will be no floor enforcement. | true |
enforcement.enforcePBS | boolean | If set to true, the module will signal to Prebid Server to pass floors to it’s bid adapters and enforce floors. If set to false, Prebid.js should still pass matched bid request floor data to Prebid Server, however no enforcement will take place. | true |
enforcement.floorDeals | boolean | Enforce floors for deal bid requests. | false |
enforcement.bidAdjustment | boolean | If true, the module will use the bidAdjustment function to adjust the floor per bidder. If false (or no bidAdjustment function is provided), floors will not be adjusted. Note: Setting this parameter to false may have unexpected results, such as signaling a gross floor when expecting net or vice versa. | true |
enforcement.enforceRate | integer | Prebid Server only: Defines a percentage for how often bid response enforcement activity should take place given that the floors feature is active. If the floors feature is skipped due to skipRate, this has no affect. For every non-skipped auction, this percent of them should be enforced: i.e. bids discarded. This feature lets publishers ease into enforcement in case bidders aren’t adhering to floor rules. | 100 |
enforcement.noFloorSignalBidders | array of strings | (PBJS 8.31+, PBS-Java 3.4+) This is an array of bidders for which to avoid sending floors. This is useful for bidders where the publishers has established different floor rules in their systems. The value can be ["*"] . |
- |
endpoint | object | Controls behavior for dynamically retrieving floors. | - |
endpoint.url | string | URL of endpoint to retrieve dynamic floor data. | - |
data | object (required) | Floor data used by the module to pass floor data to bidders and floor enforcement. | - |
data.floorProvider | string | Optional atribute (as of prebid version 4.2) used to signal to the Floor Provider’s Analytics adapter their floors are being applied. They can opt to log only floors that are applied when they are the provider. If floorProvider is supplied in both the top level of the floors object and within the data object, the data object’s configuration shall prevail. | - |
data.currency | string | Currency of floor data. The module will convert currency where necessary. See Currency section for more details. | ‘USD’ |
data.skipRate | integer | skipRate is a random function whose input value is any integer 0 through 100 to determine when to skip all floor logic, where 0 is always use floor data and 100 is always skip floor data. The use case is for publishers or floor providers to learn bid behavior when floors are applied or skipped. Analytics adapters will have access to model version (if defined) when skipped is true to signal the module is in floors mode. If skipRate is supplied in both the root level of the floors object and within the data object, the skipRate configuration within the data object shall prevail. | 0 |
data.floorsSchemaVersion | integer | The module supports two version of the data schema. Version 1 allows for only one model to be applied in a given data set, whereas Version 2 allows you to sample multiple models selected by supplied weights. If no schema version is provided, the module will assume version 1 for the sake of backwards compatiblity. | 1 |
data.modelTimestamp | int | Epoch timestamp associated with modelVersion. Can be used to track model creation of floor file for post auction analysis. | - |
data.noFloorSignalBidders | array of strings | (PBJS 8.31+, PBS-Java 3.4+) This is an array of bidders for which to avoid sending floors. This is useful for bidders where the publishers has established different floor rules in their systems. The value can be ["*"] . |
- |
data.modelGroups | array of objects | Array of model objects to be used for A/B sampling multiple models. This field is only used when data.floorsSchemaVersion = 2 | - |
data.modelGroups[].currency | string | Currency of floor data. Floor Module will convert currency where necessary. See Currency section for more details. | ‘USD’ |
data.modelGroups[].skipRate | integer | skipRate is a random function whose input value is any integer 0 through 100 to determine when to skip all floor logic, where 0 is always use floor data and 100 is always skip floor data. The use case is for publishers or floor providers to learn bid behavior when floors are applied or skipped. Analytics adapters will have access to model version (if defined) when skipped is true to signal the module is in floors mode. | 0 |
data.modelGroups[].modelVersion | string | Used by floor providers to train on model version performance. The expectation is a floor provider’s analytics adapter will pass the model verson back for algorithm training. | - |
data.modelGroups[].modelWeight | integer | Used by the module to determine when to apply the specific model. All weights will be normalized and applied at runtime. Futher clarification will be provided in examples below. | - |
data.modelGroups[].schema | object | Allows for flexible definition of how floor data is formatted. | - |
data.modelGroups[].schema.delimiter | string | Character separating the floor keys. | ’|’ |
data.modelGroups[].schema.fields | array of strings | Supported pre-defined values are: gptSlot, adUnitCode, mediaType, size | - |
data.modelGroups[].values | key / values | A series of attributes representing a hash of floor data in a format defined by the schema object. | - |
data.modelGroups[].values.”rule key” | string | Delimited field of attribute values that define a floor. | - |
data.modelGroups[].values.”rule floor value” | float | The floor value for this key. | - |
data.modelGroups[].default | float | Floor used if no matching rules are found. | - |
data.modelGroups[].noFloorSignalBidders | array of strings | (PBJS 8.31+, PBS-Java 3.4+) This is an array of bidders for which to avoid sending floors. This is useful for bidders where the publishers has established different floor rules in their systems. The value can be ["*"] . |
- |
additionalSchemaFields | object | Object contain the lookup function to map custom schema.fields. Not supported by Prebid Server. | - |
additionalSchemaFields.”custom key” | string | custom key name | - |
additionalSchemaFields.”key map function” | function | Function used to lookup the value for that particular custom key | - |
As noted for Schema 1, when you see ‘skipped’ in the floors data, it indicates the status of the skipRate
A/B test for this request. This is just a mechanism to be able to tell if the floor rules are providing value. e.g skipRate
will often start at a high value like 90%, which means “only apply floors 10% of the time”. If skipRate is 90, you would expect to see “skipped: true” 9 times out 10.
Example 1 Model weights add up to 100 and are sampled at a 25%, 25%, 50% distribution. Additionally, each model group has diffirent schema fields:
pbjs.setConfig({
floors: {
enforcement: { ... },
...
data: {
"currency": "EU",
"skipRate": 20,
"floorsSchemaVersion":2,
"modelGroups": [
{
"modelWeight":25,
"modelVersion": "Model1",
"schema": {
"fields": [ "domain", "gptSlot", "mediaType", "size" ]
},
"values": {
"www.publisher.com|/1111/homepage/top-banner|banner|728x90": 1.00,
"www.publisher.com|/1111/homepage/top-rect|banner|300x250": 1.20,
"www.publisher.com|/1111/homepage/top-rect|banner|300x600": 1.80,
...
"www.domain.com|/1111/homepage/top-banner|banner|728x90": 2.11
...
"www.publisher.com|*|*|*": 0.80,
},
"default": 0.75
},
{
"modelWeight": 25,
"modelVersion": "Model2",
"schema": {
"fields": [ "domain", "mediaType", "size" ]
},
"values": {
"www.publisher.com|banner|728x90": 1.00,
"www.publisher.com|banner|300x250": 1.20,
"www.publisher.com|banner|300x600": 1.80,
...
"www.domain.com|banner|728x90": 2.11
...
"www.publisher.com|*|*|*": 0.80,
},
"default": 0.75
},
{
"modelWeight": 50,
"modelVersion": "Model3",
"schema": {
"fields": [ "gptSlot", "mediaType", "size" ]
},
"values": {
"/1111/homepage/top-banner|banner|728x90": 1.00,
"/1111/homepage/top-rect|banner|300x250": 1.20,
"/1111/homepage/top-rect|banner|300x600": 1.80,
...
"/1111/homepage/top-banner|banner|728x90": 2.11
...
"*|banner|*": 0.80,
},
"default": 0.75
}
]
}
}
});
Example 2 Model weights do not equal 100 and are normalized. Weights will be applied in the following method: Model weight / (sum of all weights) model1 = 20 -> 20 / (20 + 50) = 29% of auctions model 1 will be applied model2 = 50 -> 50 / (20 + 50) = 71% of auctions model 2 will be applied
Additionally skipRate is supplied at model group level where model1 will skip floors 20% of times when model1 is selected, whereas model2 will skip 50% of auctions when model2 is selected.
pbjs.setConfig({
floors: {
enforcement: { ... },
...
data: {
"currency": "EU",
"floorsSchemaVersion":2,
"modelGroups": [
{
"modelWeight":25,
"skipRate": 20,
"modelVersion": "Model1",
"schema": {
"fields": [ "domain", "gptSlot", "mediaType", "size" ]
},
"values": {
"www.publisher.com|/1111/homepage/top-banner|banner|728x90": 1.00,
"www.publisher.com|/1111/homepage/top-rect|banner|300x250": 1.20,
"www.publisher.com|/1111/homepage/top-rect|banner|300x600": 1.80,
...
"www.domain.com|/1111/homepage/top-banner|banner|728x90": 2.11
...
"www.publisher.com|*|*|*": 0.80,
},
"default": 0.75
},
{
"modelWeight": 50,
"skipRate": 50,
"modelVersion": "Model2",
"schema": {
"fields": [ "gptSlot", "mediaType", "size" ]
},
"values": {
"/1111/homepage/top-banner|banner|728x90": 1.00,
"/1111/homepage/top-rect|banner|300x250": 1.20,
"/1111/homepage/top-rect|banner|300x600": 1.80,
...
"/1111/homepage/top-banner|banner|728x90": 2.11
...
"*|banner|*": 0.80,
},
"default": 0.75
}
]
}
}
});
An extension supported on Prebid Server only is the ability to define impression (adunit) level minimums for the floor. This allows publishers to put fences around the results from a dynamic floors provider.
The Prebid Server OpenRTB fields are imp.ext.prebid.floors.floorMin and floorMinCur. It’s expected that these values will be placed in the App or AMP stored request and not supplied on a web page. (Though they could be supplied as AdUnit.ortb2Imp).
Only supported in Prebid.js, not Prebid Server
Out of the box, the Price Floors Module only supports looking up floors by AdUnit, GPT Slot, MediaType, ad size, and domain. Custom schema fields can be added to support other lookup dimensions. Here are the steps:
Create a function to allow the module to understand context of a given auction. In the below example, a lookup function provides details about what deviceType this auction is for.
e.g.
function deviceTypes (bidRequest, bidResponse) {
//while bidRequest and bidResponse are not required for this function, they are available for custom attribute mapping
let deviceType = getDeviceTypeFromUserAgent(navigator.userAgent);
if(deviceType = 'mobile')
return 'mobile'
else if (deviceType = 'tablet')
return 'tablet'
else if (deviceType = 'desktop')
return 'desktop'
}
After defining a lookup function for the given context of the auction, the custom schema field(s) need to be defined in the floors.schema.fields
array. Once your custom field is defined you can assign rule values in floors.data.values
derived from these field(s). The last step would be to supply the lookup function(s) that map from each custom field to a value of the context wthin that auction by using the floors.additionalSchemaFields
attribute as seen below.
In the below example, deviceType
is a custom field not currently supported by default in the Price Floors Module whose values are one of “mobile”, “desktop” or “tablet”.
pbjs.setConfig({
floors: {
enforcement: {
floorDeals: false //default to false
},
floorMin: 0.05,
data: {
floorProvider : 'rubicon',
skipRate : 0,
currency: 'USD',
schema: {
fields: ['mediaType', 'deviceType']
},
modelVersion : 'testAddtionalFields',
values : {
'banner|mobile' : 0.10,
'banner|desktop' : .15,
'banner|tablet' : 0.16,
'banner|*' : 0.16,
'*|*' : 0.03
}
},
additionalSchemaFields : {
deviceType : deviceTypes // where deviceTypes is the function reference for your lookup function
}
}
});
As defined in the overview, a Rule Location is where a particular rule is located, either defined in (1) the Ad Unit, (2) within setConfig or (3) via a fetch from the browser. It’s likely that floor rules are set in one or more location for a given Prebid auction. During an auction, the Price Floors Module will only ever use rules from one Rule Location, decided at run-time. Each auction will be assigned an immutable set of rules from one Rule Location, even if the rules change prior to auction complete.
The module uses the below prioritization scheme on determining which Rule Location is selected at run-time:
The job of the Price Floors Module is to select a matching floor rule for enforcement given the context of each Ad Unit. With the usage of “*” values in rules definitions multiple floor rules can match for a given ad unit auction.
The module algorithm will produce a list of every possible permutation for each ad unit auction based on the defined schema types. The best matching rule for each enforced bid request and call to getFloor()
is based on specificity of values (meaning match an exact value) weighted from left to right, where the specificity of a value in the left most column would match over a rule with its “*” equivalent if “*” is supplied.
Priority order behavior where “_” is a specific value, and the “*” is a catch-all
Priority order for one column rule sets:
_
*
Priority order for two column rule set:
_ | _
_ | *
* | _
* | *
Priority order for three column rule sets:
_ | _ | _
_ | _ | *
_ | * | _
* | _ | _
_ | * | *
* | _ | *
* | * | _
* | * | *
Priority order for four column rule sets:
_ | _ | _ | _
_ | _ | _ | *
_ | _ | * | _
_ | * | _ | _
* | _ | _ | _
_ | _ | * | *
_ | * | _ | *
_ | * | * | _
* | _ | _ | *
* | _ | * | _
* | * | _ | _
_ | * | * | *
* | _ | * | *
* | * | _ | *
* | * | * | _
* | * | * | *
Below are some real example behaviors.
Domain =
Floor provider rule definition
{
"modelVersion": "Fancy Model",
"currency": "USD",
"schema": {
"fields": [ "mediaType", "size", "domain"],
},
"values": {
"banner|300x250|www.website.com": 1.01,
"banner|300x250|*": 2.01,
"banner|300x600|www.website.com": 3.01,
"banner|300x600|*": 4.01,
"banner|728x90|www.website.com": 5.01,
"banner|728x90|*": 6.01,
"banner|*|www.website.com": 7.01,
"banner|*|*": 8.01,
"*|300x250|www.website.com": 9.01,
"*|300x250|*": 10.01,
"*|300x600|www.website.com": 11.01,
"*|300x600|*": 12.01,
"*|728x90|www.website.com": 13.01,
"*|728x90|*": 14.01,
"*|*|www.website.com": 15.01,
"*|*|*": 16.01
},
"default": 0.01
}
Bidder A Bid
mediaType = banner
Size = 300x600
Domain context =
The Floor module produces an internal hash table of all possible permutations of “banner”, “300x600”, “www.website.com” and “*” with the most specific hash values up top, weighting rules priority from left column specific values to right. Each left value will weigh more than the subsequent column’s specific values. The module attempts to find the matching rule by cycling through each below possible rule (from top to bottom) against the above rule provider data set.
{
"banner|300x600|www.website.com", //Most specific possible rule match against floor provider rule set
"banner|300x600|*",
"banner|*|www.website.com",
"*|300x600|www.website.com”,
"banner|*|*",
"*|300x600|*",
"*|300x600|*",
"*|*|www.website.com",
"*|*|*"
}
Matching rule: “banner|300x600|www.website.com”
Floor enforced: 3.01
Bidder B Bid
mediaType = video
Size = 640x480
Domain context =
Price Floor internal possible permutations sorted by priority:
{
"video|640x480|www.website.com", //Fails to match due to no video specific rule
"video|640x480|*", //Fails to match due to no video specific rule
"video|*|www.website.com", //Fails match due to no video specific rule
"*|640x480|www.website.com", //Fails match due to no size 480 specific rule
"video|*|*", //Fails match due to no size video specific rule
"*|640x480|*", //Fails match due to no size 480 specific rule
"*|*|www.website.com", //Matching rule
"*|*|*"
}
Matching rule: “*|*|www.website.com”
Enforced Floor: 15.01
Bidder C Bid
mediaType = video
Size = 300x250
Domain context =
Price Floor internal possible permutations sorted by priority:
{
"video|300x250|www.website.com", //Fails to match due to no video specific rule
"video|300x250|*", //Fails to match due to no video specific rule
"video|*|www.website.com", //Fails to match due to no video specific rule
"*|300x250|www.website.com", //Matching rule
"video|*|*",
"*|300x250|*",
"*|*|www.website.com",
"*|*|*"
}
Matching Rule “*|300x250|www.website.com”
Enforced floor: 10.01
Similar data set with slightly different rules and same bids from each bidder. Matching rules will differ from example 1.
Domain =
Floor provider rule definition
{
"modelVersion": "Fancy Model",
"currency": "USD",
"schema": {
"fields": [ "mediaType", "size", "domain"],
},
"values": {
"banner|300x250|www.publisher.com": 1.01,
"banner|300x250|*": 2.01,
"banner|300x600|www.publisher.com": 3.01,
"banner|300x600|*": 4.01,
"banner|728x90|www.website.com": 5.01,
"banner|728x90|*": 6.01,
"banner|*|www.website.com": 7.01,
"banner|*|*": 8.01,
"video|*|*": 9.01,
"*|300x250|www.website.com": 9.01,
"*|300x250|*": 10.01,
"*|300x600|www.website.com": 11.01,
"*|300x600|*": 12.01,
"*|728x90|www.website.com": 13.01,
"*|728x90|*": 14.01,
"*|*|www.website.com": 15.01,
"*|*|*": 16.01
},
"defaultValue": 0.01
}
Bidder A Bid
mediaType = banner
Size = 300x600
Domain context =
{
"banner|300x600|www.website.com", // Fails due to website.com does not match with banner and 300x600
"banner|300x600|*", // Banner, 300x600 * matches first
"banner|*|www.website.com",
"*|300x600|www.website.com”,
"banner|*|*",
"*|300x600|*",
"*|300x600|*",
"*|*|www.website.com",
"*|*|*"
}
Matching rule: “banner|300x600|*”
Floor enforced: 4.01
Bidder B Bid
mediaType = video
Size = 640x480
Domain context =
Price Floor internal possible permutations sorted by priority:
{
"video|640x480|www.website.com", //Fails to match due to no video specific rule
"video|640x480|*", //Fails to match due to no video specific rule
"video|*|www.website.com", //Fails match due to no video specific rule
"*|640x480|www.website.com", //Fails match due to no size 480 specific rule
"video|*|*", //Matches existing rule
"*|640x480|*",
"*|*|www.website.com",
"*|*|*"
}
Matching rule: “video|*|*”
Enforced Floor: 9.01
Bidder C Bid
mediaType = video
Size = 300x250
Domain context =
Price Floor internal possible permutations sorted by priority:
{
"video|300x250|www.website.com", //Fails to match due to no video specific rule
"video|300x250|*", //Fails to match due to no video specific rule
"video|*|www.website.com", //Fails to match due to no video specific rule
"*|300x250|www.website.com", //Matching existing rule
"video|*|*",
"*|300x250|*",
"*|*|www.website.com",
"*|*|*"
}
Matching Rule “*|300x250|www.website.com”
Enforced floor: 10.01
Floor data can be supplied to publishers either within the setConfig as part of a Prebid.js Package if the provider is also a host provider of the Prebid library, or via a real-time Dynamic fetch, prior to the auction.
Data providers can optionally build Analytics Adapters to ingest bid data within Prebid for algorithm learning and review floor performance. Please refer to the Analytics Interface section for more details.
For Dynamic fetches, the Price Floors Module will perform a GET request to the supplied endpoint, that must return valid JSON, which will be merged into the data object in the “setConfig” Package configuration. In otherwords, the schema used for dynamic fetches is a subset of the full schema.
On rule creation, we recommend supplying various rules with catch-all (“*”) values with associated floors. This is to accommodate bid adapters who cannot retrieve floors on a per size basis, as well as using various permutations of rules with “*” values to match auctions that do not have an exact match on a specific rule. Please refer to the Rule Selection Process when determining floors as attribute order and number of “*”s may have an impact on which rule is selected.
pbjs.setConfig({
floors: {
enforcement: {
floorDeals: true
},
auctionDelay: 100,
endpoint: {
url: 'https://floorprovider.com/a1001-mysite.json'
}
}
});
In this example, the floor is determined by AdUnit code and Media Type. Note that the response does not contain the ‘data’ object because everything in the response is merged there.
{
floorProvider: 'floorProviderName',
currency: 'USD',
skipRate: 5,
modelVersion: ‘new model 1.0’
schema: {
fields: [ 'gptSlot', 'mediaType' ]
},
values: {
'/1111/homepage/top-rect|banner': 0.80,
'/1111/homepage/top-rect|video': 1.20,
'/1111/homepage/left-nav|banner': 0.90,
...
'/1111/tech/left-nav|banner': 1.50
},
default: 0.75
}
In this example, the floor is determined by Domain, GPT Slot, Media Type and Size:
{
currency: 'EU',
skipRate: 20,
modelVersion: ‘High_skip_rate’
schema: {
fields: [ 'domain', 'gptSlot', 'mediaType', 'size' ]
},
values: {
'www.publisher.com|/1111/homepage/top-banner|banner|728x90': 1.00,
'www.publisher.com|/1111/homepage/top-rect|banner|300x250': 1.20,
'www.publisher.com|/1111/homepage/top-rect|banner|300x600': 1.80,
...
'www.domain.com|/1111/homepage/top-banner|banner|728x90': 2.11
...
'www.publisher.com|*|*|*': 0.80,
},
default: 0.75
}
In this example, the floor is determined by domain, gptSlot, mediaType, and size. Note again that dynamic floor responses are merged into the ‘data’ level of the schema.
{
"currency": "USD",
"floorsSchemaVersion":2,
"skipRate": 5,
"modelGroups": [
{
"modelWeight":50,
"modelVersion": "Model1",
"schema": {
"fields": [ "domain", "gptSlot", "mediaType", "size" ]
},
"values": {
"www.publisher.com|/1111/homepage/top-banner|banner|728x90": 1.00,
"www.publisher.com|/1111/homepage/top-rect|banner|300x250": 1.20,
"www.publisher.com|/1111/homepage/top-rect|banner|300x600": 1.80,
...
"www.domain.com|/1111/homepage/top-banner|banner|728x90": 2.11
...
"www.publisher.com|*|*|*": 0.80,
},
"default": 0.15
},
{
"modelWeight": 50,
"modelVersion": "Model3",
"schema": {
"fields": [ "gptSlot", "mediaType", "size" ]
},
"values": {
"/1111/homepage/top-banner|banner|728x90": 1.00,
"/1111/homepage/top-rect|banner|300x250": 1.20,
"/1111/homepage/top-rect|banner|300x600": 1.80,
...
"/1111/homepage/top-banner|banner|728x90": 2.11
...
"*|banner|*": 0.80,
},
"default": 0.05
}
]
}
The Prebid Floors Module is capable of handling an arbitrarily large set of floor rules of any combination of supported dimensions. To reduce the need for each bid adapter to process each and every rule in the selected rule data set, an encapsulated function (getFloor) was created to allow bid adapters to query the module for a floor for each mediaType, size and currency the bid adapter needs.
If the Price Floors Module is enabled for a given auction, it will add the getFloor() function to the bidRequest object. All bid adapters are recommended to call the getFloor() to retrieve a desired floor. The job of this function is to return the floor CPM of a matched rule based on the rule selection process (written out above), using the getFloor() inputs.
Changes for bid adapters:
getFloor() takes in a single object with the following params:
if (typeof bidRequest.getFloor === 'function') {
floorInfo = bidRequest.getFloor({
currency: string,
mediaType: string,
size : [ w, h] OR "*"
});
}
Consider how floors will behave in multi-currency scenarios. A common pitfall is requesting floors without specifying currency, or specifying the wrong currency back to the bid adapter’s platform. This may lead to bidders requesting one currency and bidding in an alternate currency.
Param | Type | Description | Default |
---|---|---|---|
mediaType | string | The media type within the current bidRequest context to receive a floor from the module. It will return best matching floor. Possible values are one of “banner”, “video”, “Native” or “*” | “banner” |
size | Size array or ‘*’ (required) | The size within the current bidRequest context to receive a floor from the module. Defaults to ‘*’Array of size [w, h] for a specific size. If your bid adapter cannot handle size specific floors, use ‘*’ to retrieve catch-all size floor if defined by the publisher or floor provider | ”*” |
currency | String | The desired currency to return the floor in. Please refer to the currency section to understand how currency conversion is applied. If no currency is supplied, the floor module will assume USD. If the Floor Module cannot convert a floor to the supplied currency, bid adapters will be required to handle the supplied floor. | “USD” |
{
currency: string,
floor: float
}
Or empty object if a floor was not found for a given input
{ }
Example rules file used for getFloor()
{
"data": {
"currency": "USD",
"schema": {
"fields": [ "gptSlot", "mediaType", "size"]
},
"values": {
"/1111/homepage/top-rect|banner|300x250": 0.60,
"/1111/homepage/top-rect|banner|300x600": 1.78,
"/1111/homepage/top-rect|banner|*": 1.10,
"/1111/homepage/top-rect|video|480x600": 3.20,
"/1111/homepage/top-leaderboard|banner|728x90": 1.50
},
"default": 0.75
}
}
Example getFloor() 1
getFloor() for media type Banner for a bid request in GPT slot “/1111/homepage/top-rect” where the bid adapter does not support floors per size.
getFloor({
currency: 'USD',
mediatype: ‘banner’,
size: ‘*’
});
Response
{
currency: 'USD',
floor: 1.10
}
To aid in the accuracy of floor selection when using size ”*” in getFloor(), the Price Floors Module has built-in smart rule selection when an ad unit in the internal bidRequest to the bid adapters interface has one ad unit type and one size. In the above example, if the ad unit within the bidRequest object has an ad unit type of “banner” with only one size, say “300x250”, the module will intelligently select the rule with “banner|300x250” in it, as opposed to the “banner|*” rule producing the following response:
{
currency: 'USD',
floor: 0.60
}
Example getFloor() 2
getFloor() for media type Banner for a bid requests in GPT slot “/1111/homepage/top-rect” with size of 300x600 where bid adapter does support floors per size.
getFloor({
currency: 'USD',
size: [300,600],
mediatype: ‘banner’
});
Response
{
currency: 'USD',
floor: 1.78
}
Here are some examples of how a bid adapter may wish to configure their adapter to handle getFloor() function:
For a bid adapter who does not wish to handle making a request for each size in a given bid request they can leverage the * attribute which is meant to be a skewed average for a floor.
if (typeof bidRequest.getFloor === 'function') {
let floorInfo = bidRequest.getFloor({
currency: 'USD',
mediaType: 'banner',
size: '*'
});
data['adapter_floor'] = floorInfo.currency === 'USD' ? floorInfo.floor : undefined;
}
Floor providers rely on an analytics adapter in order to make the most informed and optimal price floor rule sets. Because of this, the Price Floors Module needs to relay important information about the flooring and decisions made in the lifecycle of an auction.
The module will do this by leveraging the already-existing implementation for analytics adapters by exposing floorData information onto the bidRequest and bidResponse objects. Thus, when an analytics adapter hooks into these objects, it will be able to pick out the price floors data and pass it along to their servers.
bidRequest: Bid Requests objects are updated to contain some basic top level information which a floor provider may need:
bidRequest.floorData. | Type | Description | example |
---|---|---|---|
fetchStatus | String | Provides details on the status of a fetch for a JSON floors file when fetches are attempted. Valid values are: ‘success’ (when fetch returns an http 200 status), ‘timeout’ (when fetch results not returned before either auction delay or prebid timeout) or ‘error’ (any http status other than 200 or other error condition). Note: if data is received successfully, but isn’t valid upon parsing, fetchStatus will be ‘success’, but the location field (below) will have a value other than ‘fetch’ because the system will fall back to another source. |
‘success’ |
floorMin | float | The mimimum CPM floor used by the module (as of 4.13). The module will take the greater of floorMin and the matched rule CPM when evaluating getFloor() and enforcing floors. Note that the currency of this floor is the same as bidResponse.floorData.floorCurrency. | 0.10 |
floorProvider | string | Optional atribute (as of prebid version 4.1) used to signal to the Floor Provider’s Analytics adapter their floors are being applied. They can opt to log only floors that are applied when they are the provider. If floorProvider is supplied in both the top level of the floors object and within the data object, the data object’s configuration shall prevail. | “rubicon” |
location | String | Where the module derived the rule set. Values are one of ‘adUnit’, ‘setConfig’, ‘fetch’ or ‘noData’. If the module code is invoked and no floors object is able to be found (either by error or other condition) the floorsModule will set location to ‘noData’. When on data is found, it is up to the analtyics adapter to decide what to log. All available values will be provided in the bidRequest object. | ‘fetch’ |
modelVersion | String | The name of the model | ‘floor-model-4.3’ |
modelWeight | integer | The weight of the model selected (for schema 2 version only) | 50 |
modelTimestamp | integer | Epoch timestamp associated with the modelVersion to be used for post auction analysis. | 1607126814 |
skipRate | integer | skipRate will be populated when a skip rate is configured in the module, even if the skipRate is evaluated to false. Skip Rate is used to determine when to skip all floors logic. | 15 |
skipped | Boolean | Whether the skipRate resolved to be true or false | true |
bidResponse: When a bid response is being processed it is important for analytics adapters to know the decision which was made and the context of the rule selection. Here is the data which is attached to each bidResponse:
bidResponse.floorData. | Type | Description | example |
---|---|---|---|
cpmAfterAdjustments | number | The bidder response CPM after any applicable adjustments (currency and / or bidCpmAdjustments) | 2.20 |
enforcements | object | The input floor enforcements object. Keys are enforceJS, enforcePBS, floorDeald, bidAdjustment | { enforceJS: true, enforcePBS: false, floorDeals: false, bidAdjustment: true } |
floorCurrency | string | Currency of the matched floor | ‘USD’ |
floorRule | String | The matching rule for the given bidResponse | ‘div-1|300x250|*’ |
floorRuleValue | float | Rule floor selected. This is to differentiate between the floor bound to the selected rule and floorMin, where floorValue will be the selected floor between the two for enforcement. | 2.33 |
floorValue | float | The value of the floor enforced. This will be the greater of floorMin and floorRuleValue. | 2.33 |
matchedFields | object | Fields of the prebid auction used to match against the floorData.schema.fields. | { mediaType: ‘banner’, Size: ‘300x250, Domain: ‘www.prebid.org’, gptSlot: ‘/12345/prebid/sports’ } |
The PrebidServerBidAdapter calls getFloor()
like any other bid adapter
and passes it to the server side as imp.bidfloor and imp.bidfloorcur.
If a publisher is defining their own floors, then all of the fields in the floors schema may be defined in the page.
Even if a publisher is using a floors provider, they may wish to provide additional data:
Here’s an example covering the first two scenarios:
pbjs.setConfig({
floors: {
enforcement: {
floorDeals: false //default to false
},
floorMin: 0.05, // global default
auctionDelay: 100, // in milliseconds
endpoint: { // where to get the dynamic floors
url: 'https://floorprovider.com/a1001-mysite.json'
},
data: {
currency: 'USD',
skipRate: 10,
modelVersion: 'some setconfig model version',
schema: {
fields: [ 'gptSlot', 'mediaType' ]
},
values: {
'*|banner': 0.98,
'*|video': 1.74
}
}
}
});
And here’s an example of imp-level floorMin, which is like a form of imp-level first party data:
pbjs.addAdUnits({
code: "test-div",
mediaTypes: {
banner: {
sizes: [[300,250]]
}
},
ortb2Imp: {
ext: {
prebid: {
data: {
floorMin: 0.25,
floorMinCur: "USD"
}
}
}
},
...
});
The Price Floors Module defaults the floor currency to USD if none is supplied in the data object of the floors configuration. For any non-USD currency support, a publisher is required to specify the desired currency. If you are working with a floor provider, please speak to them about supplying the desired currency for your integration.
Currency conversion can occur in two areas of the Floor Module code:
Currency and getFloor()
The job of the getFloor() function is to retrieve an appropriate floor for the requesting Bid Adapter, for a given auction context. If a Bid Adapter performs a getFloor() call with a currency different than the currency of the floor data, the module will attempt to perform a currency conversion, utilizing the convertCurrency function in the global Prebid object.
If a currency conversion is successful in getFloor(), the resulting floor will be returned to the requesting Bid Adapter. If the conversion failed, the module will return the original floor currency defined within the selected rule location data set.
Example Rule: currency = ‘USD’, ‘banner|300x250’: 1.00
getFloor({
currency: ‘EUR’,
mediaType: ‘banner’,
size: [300, 250]
});
If successfully returned the requested currency:
{
floor: 0.85,
currency: ‘EUR’
}
If currency conversion is unsuccessful:
{
floor:1.0,
currency: ‘USD’
}
Currency conversion can fail for the following reasons:
Currency and Floor Enforcement
Enforcement in the Price Floors Module occurs when bidders respond with a bidResponse object into the Prebid auction. The module reads the bid submitted within each valid bidResponse and its associated currency, performing currency conversion where necessary.
There exist three locations where currencies can differ within enforcement:
When a bid adapter submits a bid into the auction, the currency module will first determine if any conversion logic is necessary, afterwhich the bid is passed to the module. If currency conversion occurs at this stage, the bidResponse object will have the following attributes:
Below is a chart explaining the behavior of currency conversion, if necessary, within the module when comparing bid CPM to floor CPM for enforcement:
bid.currency | bid.originalCurrency | floor.currency | result |
---|---|---|---|
USD | USD | USD | Bid.cpm is compared to floor. If bid meets or exceeds the floor, bid.originalCpm is sent to the ad server. |
USD | USD | EUR | Bid.cpm is converted to EUR then compared with floor. If bid meets or exceeds the floor, bid.originaCpm is sent to the ad server. |
USD | EUR | EUR | bid.originalCpm is compared to floor. If bid meets or exceeds the floor, bid.Cpm is sent to the ad server. |
USD | JPY | EUR | Bid.cpm is converted to EUR then compared with floor. If bid meets or exceeds the floor, bid.Cpm is sent to the ad server. |
EUR | USD | EUR | Bid.cpm is compared to floor. If bid meets or exceeds the floor, bid.Cpm is sent to the ad server. |
USD | Undefined (currency module possibly not included) | USD | Bid.cpm is compared to floor. If bid meets or exceeds the floor, bid.originalCpm is sent to the ad server. |
USD | Undefined (currency module possibly not included) | EUR | Bid.cpm is converted to EUR then compared with floor. Bid.cpm is compared to floor. If bid meets or exceeds the floor, bid.originalCpm is sent to the ad server. |
If the currency function is unable to derive the correct cpm in any of the scenarios above where a conversion is needed, then the associated bidResponse will just pass through into the auction as if a matching floor was not found.
Partner | Contact | About |
Your Magnite account team | Magnite data-science applied to dynamic floors. (Currently only available to Demand Manager customers) | |
Reach out to OpenX at apollo@openx.com | Dynamic floor optimization and more | |
header-bidding@pubmatic.com | PubMatic’s ML powered dynamic Floor Optimization | |
assertiveyield.com | Holistic flooring covering Prebid, Amazon, GAM UPR, RTB and more | |
hello@pubx.ai | pubX is an ML-led, dynamic flooring solution covering the full programmatic revenue spectrum (the header + GAM). pubX integrates with your current set-up, so no need to replace any part of the current stack. Our team is solely focussed on delivering the best flooring technology to publishers. | |
hello@mile.tech | Boost your Prebid stack with Mile’s AI-powered dynamic flooring module that delivers an average revenue uplift of 22%. Sign-up for a free trial. |