Repository: prebid/prebid-server
Pull request #1394 (2020-07-13 15:52:34)

Refactor rate converter separating scheduler from converter logic to improve testability

stale[bot]: This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.


Patch:
diff --git a/config/config.go b/config/config.go
index 9663b021..7fc77855 100755
--- a/config/config.go
+++ b/config/config.go
@@ -156,6 +156,7 @@ type GDPR struct {
 	Timeouts                GDPRTimeouts `mapstructure:"timeouts_ms"`
 	NonStandardPublishers   []string     `mapstructure:"non_standard_publishers,flow"`
 	NonStandardPublisherMap map[string]int
+	TCF1                    TCF1 `mapstructure:"tcf1"`
 	TCF2                    TCF2 `mapstructure:"tcf2"`
 	AMPException            bool `mapstructure:"amp_exception"`
 }
@@ -180,6 +181,12 @@ func (t *GDPRTimeouts) ActiveTimeout() time.Duration {
 	return time.Duration(t.ActiveVendorlistFetch) * time.Millisecond
 }
 
+// TCF1 defines the TCF1 specific configurations for GDPR
+type TCF1 struct {
+	FetchGVL        bool   `mapstructure:"fetch_gvl"`
+	FallbackGVLPath string `mapstructure:"fallback_gvl_path"`
+}
+
 // TCF2 defines the TCF2 specific configurations for GDPR
 type TCF2 struct {
 	Enabled             bool                 `mapstructure:"enabled"`
@@ -885,6 +892,8 @@ func SetupViper(v *viper.Viper, filename string) {
 	v.SetDefault("gdpr.timeouts_ms.init_vendorlist_fetches", 0)
 	v.SetDefault("gdpr.timeouts_ms.active_vendorlist_fetch", 0)
 	v.SetDefault("gdpr.non_standard_publishers", []string{""})
+	v.SetDefault("gdpr.tcf1.fetch_gvl", true)
+	v.SetDefault("gdpr.tcf1.fallback_gvl_path", "./static/tcf1/fallback_gvl.json")
 	v.SetDefault("gdpr.tcf2.enabled", true)
 	v.SetDefault("gdpr.tcf2.purpose1.enabled", true)
 	v.SetDefault("gdpr.tcf2.purpose2.enabled", true)
diff --git a/currencies/converter_info.go b/currencies/converter_info.go
index 6f4fda64..abbcde29 100644
--- a/currencies/converter_info.go
+++ b/currencies/converter_info.go
@@ -5,18 +5,16 @@ import "time"
 // ConverterInfo holds information about converter setup
 type ConverterInfo interface {
 	Source() string
-	FetchingInterval() time.Duration
 	LastUpdated() time.Time
 	Rates() *map[string]map[string]float64
 	AdditionalInfo() interface{}
 }
 
 type converterInfo struct {
-	source           string
-	fetchingInterval time.Duration
-	lastUpdated      time.Time
-	rates            *map[string]map[string]float64
-	additionalInfo   interface{}
+	source         string
+	lastUpdated    time.Time
+	rates          *map[string]map[string]float64
+	additionalInfo interface{}
 }
 
 // Source returns converter's URL source
@@ -24,11 +22,6 @@ func (ci converterInfo) Source() string {
 	return ci.source
 }
 
-// FetchingInterval returns converter's fetching interval in nanoseconds
-func (ci converterInfo) FetchingInterval() time.Duration {
-	return ci.fetchingInterval
-}
-
 // LastUpdated returns converter's last updated time
 func (ci converterInfo) LastUpdated() time.Time {
 	return ci.lastUpdated
diff --git a/currencies/rate_converter.go b/currencies/rate_converter.go
index d22f347b..f9ae6743 100644
--- a/currencies/rate_converter.go
+++ b/currencies/rate_converter.go
@@ -2,83 +2,43 @@ package currencies
 
 import (
 	"encoding/json"
+	"fmt"
 	"io/ioutil"
 	"net/http"
 	"sync/atomic"
 	"time"
 
 	"github.com/golang/glog"
+	"github.com/prebid/prebid-server/errortypes"
+	"github.com/prebid/prebid-server/util/timeutil"
 )
 
 // RateConverter holds the currencies conversion rates dictionary
 type RateConverter struct {
 	httpClient          httpClient
-	done                chan bool
-	updateNotifier      chan<- int
-	fetchingInterval    time.Duration
 	staleRatesThreshold time.Duration
 	syncSourceURL       string
 	rates               atomic.Value // Should only hold Rates struct
 	lastUpdated         atomic.Value // Should only hold time.Time
 	constantRates       Conversions
+	time                timeutil.Time
 }
 
 // NewRateConverter returns a new RateConverter
 func NewRateConverter(
 	httpClient httpClient,
 	syncSourceURL string,
-	fetchingInterval time.Duration,
 	staleRatesThreshold time.Duration,
 ) *RateConverter {
-	return NewRateConverterWithNotifier(
-		httpClient,
-		syncSourceURL,
-		fetchingInterval,
-		staleRatesThreshold,
-		nil, // no notifier channel specified, won't send any notifications
-	)
-}
-
-// NewRateConverterDefault returns a RateConverter with default values.
-// By default there will be no currencies conversions done.
-// `currencies.ConstantRate` will be used.
-func NewRateConverterDefault() *RateConverter {
-	return NewRateConverter(&http.Client{}, "", time.Duration(0), time.Duration(0))
-}
-
-// NewRateConverterWithNotifier returns a new RateConverter
-// it allow to pass an update chan in which the number of ticks will be passed after each tick
-// allowing clients to listen on updates
-// Do not use this method
-func NewRateConverterWithNotifier(
-	httpClient httpClient,
-	syncSourceURL string,
-	fetchingInterval time.Duration,
-	staleRatesThreshold time.Duration,
-	updateNotifier chan<- int,
-) *RateConverter {
-	rc := &RateConverter{
+	return &RateConverter{
 		httpClient:          httpClient,
-		done:                make(chan bool),
-		updateNotifier:      updateNotifier,
-		fetchingInterval:    fetchingInterval,
 		staleRatesThreshold: staleRatesThreshold,
 		syncSourceURL:       syncSourceURL,
 		rates:               atomic.Value{},
 		lastUpdated:         atomic.Value{},
 		constantRates:       NewConstantRates(),
+		time:                &timeutil.RealTime{},
 	}
-
-	// In case host do not want to support currency lookup
-	// we just stop here and do nothing
-	if rc.fetchingInterval == time.Duration(0) {
-		return rc
-	}
-
-	rc.Update()                   // Make sure to populate data before to return the converter
-	go rc.startPeriodicFetching() // Start periodic ticking
-
-	return rc
 }
 
 // fetch allows to retrieve the currencies rates from the syncSourceURL provided
@@ -93,6 +53,11 @@ func (rc *RateConverter) fetch() (*Rates, error) {
 		return nil, err
 	}
 
+	if response.StatusCode >= 400 {
+		message := fmt.Sprintf("The currency rates request failed with status code %d", response.StatusCode)
+		return nil, &errortypes.BadServerResponse{Message: message}
+	}
+
 	defer response.Body.Close()
 
 	bytesJSON, err := ioutil.ReadAll(response.Body)
@@ -110,14 +75,14 @@ func (rc *RateConverter) fetch() (*Rates, error) {
 }
 
 // Update updates the internal currencies rates from remote sources
-func (rc *RateConverter) Update() error {
+func (rc *RateConverter) update() error {
 	rates, err := rc.fetch()
 	if err == nil {
 		rc.rates.Store(rates)
-		rc.lastUpdated.Store(time.Now())
+		rc.lastUpdated.Store(rc.time.Now())
 	} else {
-		if rc.CheckStaleRates() {
-			rc.ClearRates()
+		if rc.checkStaleRates() {
+			rc.clearRates()
 			glog.Errorf("Error updating conversion rates, falling back to constant rates: %v", err)
 		} else {
 			glog.Errorf("Error updating conversion rates: %v", err)
@@ -127,37 +92,8 @@ func (rc *RateConverter) Update() error {
 	return err
 }
 
-// startPeriodicFetching starts the periodic fetching at the given interval
-// triggers a first fetch when called before the first tick happen in order to initialize currencies rates map
-// returns a chan in which the number of data updates everytime a new update was done
-func (rc *RateConverter) startPeriodicFetching() {
-
-	ticker := time.NewTicker(rc.fetchingInterval)
-	updatesTicksCount := 0
-
-	for {
-		select {
-		case <-ticker.C:
-			// Retries are handled by clients directly.
-			rc.Update()
-			updatesTicksCount++
-			if rc.updateNotifier != nil {
-				rc.updateNotifier <- updatesTicksCount
-			}
-		case <-rc.done:
-			if ticker != nil {
-				ticker.Stop()
-				ticker = nil
-			}
-			return
-		}
-	}
-}
-
-// StopPeriodicFetching stops the periodic fetching while keeping the latest currencies rates map
-func (rc *RateConverter) StopPeriodicFetching() {
-	rc.done <- true
-	close(rc.done)
+func (rc *RateConverter) Run() error {
+	return rc.update()
 }
 
 // LastUpdated returns time when currencies rates were updated
@@ -178,18 +114,19 @@ func (rc *RateConverter) Rates() Conversions {
 	return rc.constantRates
 }
 
-// ClearRates sets the rates to nil
-func (rc *RateConverter) ClearRates() {
+// clearRates sets the rates to nil
+func (rc *RateConverter) clearRates() {
 	// atomic.Value field rates must be of type *Rates so we cast nil to that type
 	rc.rates.Store((*Rates)(nil))
 }
 
-// CheckStaleRates checks if loaded third party conversion rates are stale
-func (rc *RateConverter) CheckStaleRates() bool {
+// checkStaleRates checks if loaded third party conversion rates are stale
+func (rc *RateConverter) checkStaleRates() bool {
 	if rc.staleRatesThreshold <= 0 {
 		return false
 	}
-	currentTime := time.Now().UTC()
+
+	currentTime := rc.time.Now().UTC()
 	if lastUpdated := rc.lastUpdated.Load(); lastUpdated != nil {
 		delta := currentTime.Sub(lastUpdated.(time.Time).UTC())
 		if delta.Seconds() > rc.staleRatesThreshold.Seconds() {
@@ -202,14 +139,11 @@ func (rc *RateConverter) CheckStaleRates() bool {
 // GetInfo returns setup information about the converter
 func (rc *RateConverter) GetInfo() ConverterInfo {
 	var rates *map[string]map[string]float64
-	if rc.Rates() != nil {
-		rates = rc.Rates().GetRates()
-	}
+	rates = rc.Rates().GetRates()
 	return converterInfo{
-		source:           rc.syncSourceURL,
-		fetchingInterval: rc.fetchingInterval,
-		lastUpdated:      rc.LastUpdated(),
-		rates:            rates,
+		source:      rc.syncSourceURL,
+		lastUpdated: rc.LastUpdated(),
+		rates:       rates,
 	}
 }
 
diff --git a/endpoints/currency_rates.go b/endpoints/currency_rates.go
index 745dbe3e..90650cc2 100644
--- a/endpoints/currency_rates.go
+++ b/endpoints/currency_rates.go
@@ -24,7 +24,7 @@ type rateConverter interface {
 }
 
 // newCurrencyRatesInfo creates a new CurrencyRatesInfo instance.
-func newCurrencyRatesInfo(rateConverter rateConverter) currencyRatesInfo {
+func newCurrencyRatesInfo(rateConverter rateConverter, fetchingInterval time.Duration) currencyRatesInfo {
 
 	currencyRatesInfo := currencyRatesInfo{
 		Active: false,
@@ -44,7 +44,6 @@ func newCurrencyRatesInfo(rateConverter rateConverter) currencyRatesInfo {
 	source := infos.Source()
 	currencyRatesInfo.Source = &source
 
-	fetchingInterval := infos.FetchingInterval()
 	currencyRatesInfo.FetchingInterval = &fetchingInterval
 
 	lastUpdated := infos.LastUpdated()
@@ -57,8 +56,8 @@ func newCurrencyRatesInfo(rateConverter rateConverter) currencyRatesInfo {
 }
 
 // NewCurrencyRatesEndpoint returns current currency rates applied by the PBS server.
-func NewCurrencyRatesEndpoint(rateConverter rateConverter) http.HandlerFunc {
-	currencyRateInfo := newCurrencyRatesInfo(rateConverter)
+func NewCurrencyRatesEndpoint(rateConverter rateConverter, fetchingInterval time.Duration) http.HandlerFunc {
+	currencyRateInfo := newCurrencyRatesInfo(rateConverter, fetchingInterval)
 
 	return func(w http.ResponseWriter, _ *http.Request) {
 		jsonOutput, err := json.Marshal(currencyRateInfo)
diff --git a/endpoints/openrtb2/video_auction.go b/endpoints/openrtb2/video_auction.go
index 18678be5..49ba2876 100644
--- a/endpoints/openrtb2/video_auction.go
+++ b/endpoints/openrtb2/video_auction.go
@@ -470,7 +470,7 @@ func buildVideoResponse(bidresponse *openrtb.BidResponse, podErrors []PodError)
 			if err := json.Unmarshal(bid.Ext, &tempRespBidExt); err != nil {
 				return nil, err
 			}
-			if tempRespBidExt.Prebid.Targeting[string(openrtb_ext.HbVastCacheKey)] == "" {
+			if tempRespBidExt.Prebid.Targeting[formatTargetingKey(openrtb_ext.HbVastCacheKey, seatBid.Seat)] == "" {
 				continue
 			}
 
@@ -479,9 +479,9 @@ func buildVideoResponse(bidresponse *openrtb.BidResponse, podErrors []PodError)
 			podId, _ := strconv.ParseInt(podNum, 0, 64)
 
 			videoTargeting := openrtb_ext.VideoTargeting{
-				HbPb:       tempRespBidExt.Prebid.Targeting[string(openrtb_ext.HbpbConstantKey)],
-				HbPbCatDur: tempRespBidExt.Prebid.Targeting[string(openrtb_ext.HbCategoryDurationKey)],
-				HbCacheID:  tempRespBidExt.Prebid.Targeting[string(openrtb_ext.HbVastCacheKey)],
+				HbPb:       tempRespBidExt.Prebid.Targeting[formatTargetingKey(openrtb_ext.HbpbConstantKey, seatBid.Seat)],
+				HbPbCatDur: tempRespBidExt.Prebid.Targeting[formatTargetingKey(openrtb_ext.HbCategoryDurationKey, seatBid.Seat)],
+				HbCacheID:  tempRespBidExt.Prebid.Targeting[formatTargetingKey(openrtb_ext.HbVastCacheKey, seatBid.Seat)],
 			}
 
 			adPod := findAdPod(podId, adPods)
@@ -519,6 +519,14 @@ func buildVideoResponse(bidresponse *openrtb.BidResponse, podErrors []PodError)
 	return &openrtb_ext.BidResponseVideo{AdPods: adPods}, nil
 }
 
+func formatTargetingKey(key openrtb_ext.TargetingKey, bidderName string) string {
+	fullKey := fmt.Sprintf("%s_%s", string(key), bidderName)
+	if len(fullKey) > exchange.MaxKeyLength {
+		return string(fullKey[0:exchange.MaxKeyLength])
+	}
+	return fullKey
+}
+
 func findAdPod(podInd int64, pods []*openrtb_ext.AdPod) *openrtb_ext.AdPod {
 	for _, pod := range pods {
 		if pod.PodId == podInd {
@@ -623,9 +631,9 @@ func createBidExtension(videoRequest *openrtb_ext.BidRequestVideo) ([]byte, erro
 
 	targeting := openrtb_ext.ExtRequestTargeting{
 		PriceGranularity:     priceGranularity,
-		IncludeWinners:       true,
 		IncludeBrandCategory: inclBrandCat,
 		DurationRangeSec:     durationRangeSec,
+		IncludeBidderKeys:    true,
 	}
 
 	vastXml := openrtb_ext.ExtRequestPrebidCacheVAST{}
diff --git a/exchange/auction.go b/exchange/auction.go
index dcb73708..45e14225 100644
--- a/exchange/auction.go
+++ b/exchange/auction.go
@@ -130,7 +130,7 @@ func (a *auction) doCache(ctx context.Context, cache prebid_cache_client.Client,
 			var customCacheKey string
 			var catDur string
 			useCustomCacheKey := false
-			if competitiveExclusion && isOverallWinner {
+			if competitiveExclusion && isOverallWinner || includeBidderKeys {
 				// set custom cache key for winning bid when competitive exclusion applies
 				catDur = bidCategory[topBidPerBidder.bid.ID]
 				if len(catDur) > 0 {
diff --git a/exchange/exchange.go b/exchange/exchange.go
index ad591f57..57e13644 100644
--- a/exchange/exchange.go
+++ b/exchange/exchange.go
@@ -10,6 +10,7 @@ import (
 	"net/http"
 	"runtime/debug"
 	"sort"
+	"strconv"
 	"strings"
 	"time"
 
@@ -484,6 +485,7 @@ func applyCategoryMapping(ctx context.Context, requestExt *openrtb_ext.ExtReques
 		bidderName openrtb_ext.BidderName
 		bidIndex   int
 		bidID      string
+		bidPrice   string
 	}
 
 	dedupe := make(map[string]bidDedupe)
@@ -580,15 +582,34 @@ func applyCategoryMapping(ctx context.Context, requestExt *openrtb_ext.ExtReques
 			}
 
 			var categoryDuration string
+			var dupeKey string
 			if brandCatExt.WithCategory {
 				categoryDuration = fmt.Sprintf("%s_%s_%ds", pb, category, newDur)
+				dupeKey = category
 			} else {
 				categoryDuration = fmt.Sprintf("%s_%ds", pb, newDur)
+				dupeKey = categoryDuration
 			}
 
-			if dupe, ok := dedupe[categoryDuration]; ok {
-				// 50% chance for either bid with duplicate categoryDuration values to be kept
-				if rand.Intn(100) < 50 {
+			if dupe, ok := dedupe[dupeKey]; ok {
+
+				dupeBidPrice, err := strconv.ParseFloat(dupe.bidPrice, 64)
+				if err != nil {
+					dupeBidPrice = 0
+				}
+				currBidPrice, err := strconv.ParseFloat(pb, 64)
+				if err != nil {
+					currBidPrice = 0
+				}
+				if dupeBidPrice == currBidPrice {
+					if rand.Intn(100) < 50 {
+						dupeBidPrice = -1
+					} else {
+						currBidPrice = -1
+					}
+				}
+
+				if dupeBidPrice < currBidPrice {
 					if dupe.bidderName == bidderName {
 						// An older bid from the current bidder
 						bidsToRemove = append(bidsToRemove, dupe.bidIndex)
@@ -612,7 +633,7 @@ func applyCategoryMapping(ctx context.Context, requestExt *openrtb_ext.ExtReques
 				}
 			}
 			res[bidID] = categoryDuration
-			dedupe[categoryDuration] = bidDedupe{bidderName: bidderName, bidIndex: bidInd, bidID: bidID}
+			dedupe[dupeKey] = bidDedupe{bidderName: bidderName, bidIndex: bidInd, bidID: bidID, bidPrice: pb}
 		}
 
 		if len(bidsToRemove) > 0 {
diff --git a/exchange/targeting.go b/exchange/targeting.go
index dca57653..47bfeb65 100644
--- a/exchange/targeting.go
+++ b/exchange/targeting.go
@@ -7,7 +7,7 @@ import (
 	"github.com/prebid/prebid-server/openrtb_ext"
 )
 
-const maxKeyLength = 20
+const MaxKeyLength = 20
 
 // targetData tracks information about the winning Bid in each Imp.
 //
@@ -83,7 +83,7 @@ func (targData *targetData) setTargeting(auc *auction, isApp bool, categoryMappi
 
 func (targData *targetData) addKeys(keys map[string]string, key openrtb_ext.TargetingKey, value string, bidderName openrtb_ext.BidderName, overallWinner bool) {
 	if targData.includeBidderKeys {
-		keys[key.BidderKey(bidderName, maxKeyLength)] = value
+		keys[key.BidderKey(bidderName, MaxKeyLength)] = value
 	}
 	if targData.includeWinners && overallWinner {
 		keys[string(key)] = value
diff --git a/exchange/utils.go b/exchange/utils.go
index 97fae7b7..2131aac5 100644
--- a/exchange/utils.go
+++ b/exchange/utils.go
@@ -107,12 +107,14 @@ func cleanOpenRTBRequests(ctx context.Context,
 			coreBidder := resolveBidder(bidder.String(), aliases)
 
 			var publisherID = labels.PubID
-			ok, geo, err := gDPR.PersonalInfoAllowed(ctx, coreBidder, publisherID, consent)
+			ok, geo, id, err := gDPR.PersonalInfoAllowed(ctx, coreBidder, publisherID, consent)
 			privacyEnforcement.GDPR = !ok && err == nil
 			privacyEnforcement.GDPRGeo = !geo && err == nil
+			privacyEnforcement.GDPRID = !id && err == nil
 		} else {
 			privacyEnforcement.GDPR = false
 			privacyEnforcement.GDPRGeo = false
+			privacyEnforcement.GDPRID = false
 		}
 
 		privacyEnforcement.Apply(bidReq, ampGDPRException)
diff --git a/gdpr/gdpr.go b/gdpr/gdpr.go
index 0dfa12f5..04db8cb9 100644
--- a/gdpr/gdpr.go
+++ b/gdpr/gdpr.go
@@ -23,7 +23,7 @@ type Permissions interface {
 	// Determines whether or not to send PI information to a bidder, or mask it out.
 	//
 	// If the consent string was nonsensical, the returned error will be an ErrorMalformedConsent.
-	PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, bool, error)
+	PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, bool, bool, error)
 
 	// Exposes the AMP execption flag
 	AMPException() bool
diff --git a/gdpr/impl.go b/gdpr/impl.go
index 60db804a..2deddc7b 100644
--- a/gdpr/impl.go
+++ b/gdpr/impl.go
@@ -42,10 +42,10 @@ func (p *permissionsImpl) BidderSyncAllowed(ctx context.Context, bidder openrtb_
 	return false, nil
 }
 
-func (p *permissionsImpl) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, bool, error) {
+func (p *permissionsImpl) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, bool, bool, error) {
 	_, ok := p.cfg.NonStandardPublisherMap[PublisherID]
 	if ok {
-		return true, true, nil
+		return true, true, true, nil
 	}
 
 	id, ok := p.vendorIDs[bidder]
@@ -54,10 +54,10 @@ func (p *permissionsImpl) PersonalInfoAllowed(ctx context.Context, bidder openrt
 	}
 
 	if consent == "" {
-		return p.cfg.UsersyncIfAmbiguous, p.cfg.UsersyncIfAmbiguous, nil
+		return p.cfg.UsersyncIfAmbiguous, p.cfg.UsersyncIfAmbiguous, p.cfg.UsersyncIfAmbiguous, nil
 	}
 
-	return false, false, nil
+	return false, false, false, nil
 }
 
 func (p *permissionsImpl) AMPException() bool {
@@ -98,19 +98,19 @@ func (p *permissionsImpl) allowSync(ctx context.Context, vendorID uint16, consen
 	return false, nil
 }
 
-func (p *permissionsImpl) allowPI(ctx context.Context, vendorID uint16, consent string) (bool, bool, error) {
+func (p *permissionsImpl) allowPI(ctx context.Context, vendorID uint16, consent string) (bool, bool, bool, error) {
 	// If we're not given a consent string, respect the preferences in the app config.
 	if consent == "" {
-		return p.cfg.UsersyncIfAmbiguous, p.cfg.UsersyncIfAmbiguous, nil
+		return p.cfg.UsersyncIfAmbiguous, p.cfg.UsersyncIfAmbiguous, p.cfg.UsersyncIfAmbiguous, nil
 	}
 
 	parsedConsent, vendor, err := p.parseVendor(ctx, vendorID, consent)
 	if err != nil {
-		return false, false, err
+		return false, false, false, err
 	}
 
 	if vendor == nil {
-		return false, false, nil
+		return false, false, false, nil
 	}
 
 	if parsedConsent.Version() == 2 {
@@ -118,21 +118,22 @@ func (p *permissionsImpl) allowPI(ctx context.Context, vendorID uint16, consent
 			return p.allowPITCF2(parsedConsent, vendor, vendorID)
 		}
 		if (vendor.Purpose(consentconstants.InfoStorageAccess) || vendor.LegitimateInterest(consentconstants.InfoStorageAccess)) && parsedConsent.PurposeAllowed(consentconstants.InfoStorageAccess) && (vendor.Purpose(consentconstants.PersonalizationProfile) || vendor.LegitimateInterest(consentconstants.PersonalizationProfile)) && parsedConsent.PurposeAllowed(consentconstants.PersonalizationProfile) && parsedConsent.VendorConsent(vendorID) {
-			return true, true, nil
+			return true, true, true, nil
 		}
 	} else {
 		if (vendor.Purpose(tcf1constants.InfoStorageAccess) || vendor.LegitimateInterest(tcf1constants.InfoStorageAccess)) && parsedConsent.PurposeAllowed(tcf1constants.InfoStorageAccess) && (vendor.Purpose(tcf1constants.AdSelectionDeliveryReporting) || vendor.LegitimateInterest(tcf1constants.AdSelectionDeliveryReporting)) && parsedConsent.PurposeAllowed(tcf1constants.AdSelectionDeliveryReporting) && parsedConsent.VendorConsent(vendorID) {
-			return true, true, nil
+			return true, true, true, nil
 		}
 	}
-	return false, false, nil
+	return false, false, false, nil
 }
 
-func (p *permissionsImpl) allowPITCF2(parsedConsent api.VendorConsents, vendor api.Vendor, vendorID uint16) (allowPI bool, allowGeo bool, err error) {
+func (p *permissionsImpl) allowPITCF2(parsedConsent api.VendorConsents, vendor api.Vendor, vendorID uint16) (allowPI bool, allowGeo bool, allowID bool, err error) {
 	consent, ok := parsedConsent.(tcf2.ConsentMetadata)
 	err = nil
 	allowPI = false
 	allowGeo = false
+	allowID = false
 	if !ok {
 		err = fmt.Errorf("Unable to access TCF2 parsed consent")
 		return
@@ -142,6 +143,12 @@ func (p *permissionsImpl) allowPITCF2(parsedConsent api.VendorConsents, vendor a
 	} else {
 		allowGeo = true
 	}
+	for i := 2; i <= 10; i++ {
+		if p.checkPurpose(consent, vendor, vendorID, tcf1constants.Purpose(i)) {
+			allowID = true
+			break
+		}
+	}
 	// Set to true so any purpose check can flip it to false
 	allowPI = true
 	if p.cfg.TCF2.Purpose1.Enabled {
@@ -214,8 +221,8 @@ func (a AlwaysAllow) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.B
 	return true, nil
 }
 
-func (a AlwaysAllow) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, bool, error) {
-	return true, true, nil
+func (a AlwaysAllow) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, bool, bool, error) {
+	return true, true, true, nil
 }
 
 func (a AlwaysAllow) AMPException() bool {
diff --git a/gdpr/vendorlist-fetching.go b/gdpr/vendorlist-fetching.go
index 987622a6..a0a73c93 100644
--- a/gdpr/vendorlist-fetching.go
+++ b/gdpr/vendorlist-fetching.go
@@ -27,8 +27,20 @@ type saveVendors func(uint16, api.VendorList)
 // Nothing in this file is exported. Public APIs can be found in gdpr.go
 
 func newVendorListFetcher(initCtx context.Context, cfg config.GDPR, client *http.Client, urlMaker func(uint16, uint8) string, TCFVer uint8) func(ctx context.Context, id uint16) (vendorlist.VendorList, error) {
+	var fallbackVL api.VendorList = nil
+
+	if TCFVer == tCF1 && len(cfg.TCF1.FallbackGVLPath) > 0 {
+		fallbackVL = loadFallbackGVL(cfg.TCF1.FallbackGVLPath)
+	}
+
+	// If we are not going to try fetching the GVL dynamically, we have a simple fetcher
+	if !cfg.TCF1.FetchGVL && TCFVer == tCF1 && fallbackVL != nil {
+		return func(ctx context.Context, id uint16) (vendorlist.VendorList, error) {
+			return fallbackVL, nil
+		}
+	}
 	// These save and load functions can be used to store & retrieve lists from our cache.
-	save, load := newVendorListCache()
+	save, load := newVendorListCache(fallbackVL)
 
 	withTimeout, cancel := context.WithTimeout(initCtx, cfg.Timeouts.InitTimeout())
 	defer cancel()
@@ -46,6 +58,9 @@ func newVendorListFetcher(initCtx context.Context, cfg config.GDPR, client *http
 		if list != nil {
 			return list, nil
 		}
+		if fallbackVL != nil {
+			return fallbackVL, nil
+		}
 		return nil, fmt.Errorf("gdpr vendor list version %d does not exist, or has not been loaded yet. Try again in a few minutes", id)
 	}
 }
@@ -132,7 +147,7 @@ func saveOne(ctx context.Context, client *http.Client, url string, saver saveVen
 	return newList.Version()
 }
 
-func newVendorListCache() (save func(id uint16, list api.VendorList), load func(id uint16) api.VendorList) {
+func newVendorListCache(fallbackVL api.VendorList) (save func(id uint16, list api.VendorList), load func(id uint16) api.VendorList) {
 	cache := &sync.Map{}
 
 	save = func(id uint16, list api.VendorList) {
@@ -143,7 +158,19 @@ func newVendorListCache() (save func(id uint16, list api.VendorList), load func(
 		if ok {
 			return list.(vendorlist.VendorList)
 		}
-		return nil
+		return fallbackVL
 	}
 	return
 }
+
+func loadFallbackGVL(fallbackGVLPath string) vendorlist.VendorList {
+	fallbackVLbody, err := ioutil.ReadFile(fallbackGVLPath)
+	if err != nil {
+		glog.Fatalf("Error reading from file %s: %v", fallbackGVLPath, err)
+	}
+	fallbackVL, err := vendorlist.ParseEagerly(fallbackVLbody)
+	if err != nil {
+		glog.Fatalf("Error processing default GVL from %s: %v", fallbackGVLPath, err)
+	}
+	return fallbackVL
+}
diff --git a/go.mod b/go.mod
index a5b5a161..108e383e 100644
--- a/go.mod
+++ b/go.mod
@@ -34,7 +34,7 @@ require (
 	github.com/onsi/ginkgo v1.10.1 // indirect
 	github.com/onsi/gomega v1.7.0 // indirect
 	github.com/pelletier/go-toml v1.2.0 // indirect
-	github.com/prebid/go-gdpr v0.8.2
+	github.com/prebid/go-gdpr v0.8.3
 	github.com/prebid/prebid-cache v0.0.0-20200218152159-6d6d678c1caf
 	github.com/prometheus/client_golang v0.0.0-20180623155954-77e8f2ddcfed
 	github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910
diff --git a/go.sum b/go.sum
index 1ddab713..6da3f889 100644
--- a/go.sum
+++ b/go.sum
@@ -12,6 +12,7 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLM
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
 github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
+github.com/buger/jsonparser v0.0.0-20180318095312-2cac668e8456/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
 github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44 h1:y853v6rXx+zefEcjET3JuKAqvhj+FKflQijjeaSv2iA=
 github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
 github.com/cespare/xxhash v1.0.0 h1:naDmySfoNg0nKS62/ujM6e71ZgM2AoVdaqGwMG0w18A=
@@ -77,8 +78,8 @@ github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/prebid/go-gdpr v0.8.2 h1:mN2jKYZZpJkCYFQB/nDTJoPpuGYblOYP2UUzOzRggII=
-github.com/prebid/go-gdpr v0.8.2/go.mod h1:FPY0uxSrl9/Mz237LnPo3ge4aCG0wQ9FWf2b4WhwNn0=
+github.com/prebid/go-gdpr v0.8.3 h1:rjCZNV0AdKygiGHpVhNB42usjEpTN3qidXUPB1yarb0=
+github.com/prebid/go-gdpr v0.8.3/go.mod h1:TGzgqQDGKOVUkbqmY25K4uvcwMywSddXEaY4zUFiVBQ=
 github.com/prebid/prebid-cache v0.0.0-20200218152159-6d6d678c1caf h1:CcE+KN1tCtWKsUFH5IzdQxHIgP609VSIVe5Hywg2phs=
 github.com/prebid/prebid-cache v0.0.0-20200218152159-6d6d678c1caf/go.mod h1:k5xrl5ZpnumN1S2x8w8cMiFYsgRuVyAeFJz+BkSi+98=
 github.com/prometheus/client_golang v0.0.0-20180623155954-77e8f2ddcfed h1:0dloFFFNNDG7c+8qtkYw2FdADrWy9s5cI8wHp6tK3Mg=
diff --git a/main.go b/main.go
index 9a835f42..035d386e 100644
--- a/main.go
+++ b/main.go
@@ -11,6 +11,7 @@ import (
 	pbc "github.com/prebid/prebid-server/prebid_cache_client"
 	"github.com/prebid/prebid-server/router"
 	"github.com/prebid/prebid-server/server"
+	"github.com/prebid/prebid-server/util/task"
 
 	"github.com/golang/glog"
 	"github.com/spf13/viper"
@@ -53,8 +54,10 @@ func loadConfig() (*config.Configuration, error) {
 func serve(revision string, cfg *config.Configuration) error {
 	fetchingInterval := time.Duration(cfg.CurrencyConverter.FetchIntervalSeconds) * time.Second
 	staleRatesThreshold := time.Duration(cfg.CurrencyConverter.StaleRatesSeconds) * time.Second
-	currencyConverter := currencies.NewRateConverter(&http.Client{}, cfg.CurrencyConverter.FetchURL,
-		fetchingInterval, staleRatesThreshold)
+	currencyConverter := currencies.NewRateConverter(&http.Client{}, cfg.CurrencyConverter.FetchURL, staleRatesThreshold)
+	
+	currencyConverterTickerTask := task.NewTickerTask(fetchingInterval, currencyConverter)
+	currencyConverterTickerTask.Start()
 
 	r, err := router.New(cfg, currencyConverter)
 	if err != nil {
@@ -64,7 +67,7 @@ func serve(revision string, cfg *config.Configuration) error {
 	pbc.InitPrebidCache(cfg.CacheURL.GetBaseURL())
 
 	corsRouter := router.SupportCORS(r)
-	server.Listen(cfg, router.NoCache{Handler: corsRouter}, router.Admin(revision, currencyConverter), r.MetricsEngine)
+	server.Listen(cfg, router.NoCache{Handler: corsRouter}, router.Admin(revision, currencyConverter, fetchingInterval), r.MetricsEngine)
 
 	r.Shutdown()
 	return nil
diff --git a/privacy/enforcement.go b/privacy/enforcement.go
index 8a5d201f..9c23c320 100644
--- a/privacy/enforcement.go
+++ b/privacy/enforcement.go
@@ -10,12 +10,13 @@ type Enforcement struct {
 	COPPA   bool
 	GDPR    bool
 	GDPRGeo bool
+	GDPRID  bool
 	LMT     bool
 }
 
 // Any returns true if at least one privacy policy requires enforcement.
 func (e Enforcement) Any() bool {
-	return e.CCPA || e.COPPA || e.GDPR || e.GDPRGeo || e.LMT
+	return e.CCPA || e.COPPA || e.GDPR || e.GDPRGeo || e.GDPRID || e.LMT
 }
 
 // Apply cleans personally identifiable information from an OpenRTB bid request.
@@ -64,7 +65,7 @@ func (e Enforcement) getUserScrubStrategy(ampGDPRException bool) ScrubStrategyUs
 	}
 
 	// If no user scrubbing is needed, then return none, else scrub ID (COPPA checked above)
-	if e.CCPA || e.GDPR || e.LMT {
+	if e.CCPA || e.GDPRID || e.LMT {
 		return ScrubStrategyUserID
 	}
 
diff --git a/router/admin.go b/router/admin.go
index 83c4701b..fe268c48 100644
--- a/router/admin.go
+++ b/router/admin.go
@@ -3,12 +3,13 @@ package router
 import (
 	"net/http"
 	"net/http/pprof"
+	"time"
 
 	"github.com/prebid/prebid-server/currencies"
 	"github.com/prebid/prebid-server/endpoints"
 )
 
-func Admin(revision string, rateConverter *currencies.RateConverter) *http.ServeMux {
+func Admin(revision string, rateConverter *currencies.RateConverter, rateConverterFetchingInterval time.Duration) *http.ServeMux {
 	// Add endpoints to the admin server
 	// Making sure to add pprof routes
 	mux := http.NewServeMux()
@@ -19,7 +20,7 @@ func Admin(revision string, rateConverter *currencies.RateConverter) *http.Serve
 	mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
 	mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
 	// Register prebid-server defined admin handlers
-	mux.HandleFunc("/currency/rates", endpoints.NewCurrencyRatesEndpoint(rateConverter))
+	mux.HandleFunc("/currency/rates", endpoints.NewCurrencyRatesEndpoint(rateConverter, rateConverterFetchingInterval))
 	mux.HandleFunc("/version", endpoints.NewVersionEndpoint(revision))
 	return mux
 }
diff --git a/static/tcf1/fallback_gvl.json b/static/tcf1/fallback_gvl.json
new file mode 100644
index 00000000..86895a52
--- /dev/null
+++ b/static/tcf1/fallback_gvl.json
@@ -0,0 +1,1 @@
+{"vendorListVersion":214,"lastUpdated":"2020-08-06T16:00:35Z","purposes":[{"id":1,"name":"Information storage and access","description":"The storage of information, or access to information that is already stored, on your device such as advertising identifiers, device identifiers, cookies, and similar technologies."},{"id":2,"name":"Personalisation","description":"The collection and processing of information about your use of this service to subsequently personalise advertising and/or content for you in other contexts, such as on other websites or apps, over time. Typically, the content of the site or app is used to make inferences about your interests, which inform future selection of advertising and/or content."},{"id":3,"name":"Ad selection, delivery, reporting","description":"The collection of information, and combination with previously collected information, to select and deliver advertisements for you, and to measure the delivery and effectiveness of such advertisements. This includes using previously collected information about your interests to select ads, processing data about what advertisements were shown, how often they were shown, when and where they were shown, and whether you took any action related to the advertisement, including for example clicking an ad or making a purchase. This does not include personalisation, which is the collection and processing of information about your use of this service to subsequently personalise advertising and/or content for you in other contexts, such as websites or apps, over time."},{"id":4,"name":"Content selection, delivery, reporting","description":"The collection of information, and combination with previously collected information, to select and deliver content for you, and to measure the delivery and effectiveness of such content. This includes using previously collected information about your interests to select content, processing data about what content was shown, how often or how long it was shown, when and where it was shown, and whether the you took any action related to the content, including for example clicking on content. This does not include personalisation, which is the collection and processing of information about your use of this service to subsequently personalise content and/or advertising for you in other contexts, such as websites or apps, over time."},{"id":5,"name":"Measurement","description":"The collection of information about your use of the content, and combination with previously collected information, used to measure, understand, and report on your usage of the service. This does not include personalisation, the collection of information about your use of this service to subsequently personalise content and/or advertising for you in other contexts, i.e. on other service, such as websites or apps, over time."}],"features":[{"id":1,"name":"Matching Data to Offline Sources","description":"Combining data from offline sources that were initially collected in other contexts."},{"id":2,"name":"Linking Devices","description":"Allow processing of a user's data to connect such user across multiple devices."},{"id":3,"name":"Precise Geographic Location Data","description":"Allow processing of a user's precise geographic location data in support of a purpose for which that certain third party has consent."}],"vendors":[{"id":8,"name":"Emerse Sverige AB","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[1,2],"policyUrl":"https://www.emerse.com/privacy-policy/"},{"id":9,"name":"AdMaxim Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://www.admaxim.com/admaxim-privacy-policy/","deletedDate":"2020-06-17T00:00:00Z"},{"id":12,"name":"BeeswaxIO Corporation","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.beeswax.com/privacy/"},{"id":28,"name":"TripleLift, Inc.","purposeIds":[1],"legIntPurposeIds":[3],"featureIds":[3],"policyUrl":"https://triplelift.com/privacy/"},{"id":27,"name":"ADventori SAS","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.adventori.com/with-us/legal-notice/"},{"id":25,"name":"Verizon Media EMEA Limited","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[1,2,3],"policyUrl":"https://www.verizonmedia.com/policies/ie/en/verizonmedia/privacy/index.html"},{"id":26,"name":"Venatus Media Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.venatusmedia.com/privacy/"},{"id":1,"name":"Exponential Interactive, Inc d/b/a VDX.tv","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://vdx.tv/privacy/"},{"id":6,"name":"AdSpirit GmbH","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.adspirit.de/privacy"},{"id":30,"name":"BidTheatre AB","purposeIds":[1,2],"legIntPurposeIds":[3],"featureIds":[2,3],"policyUrl":"https://www.bidtheatre.com/privacy-policy"},{"id":24,"name":"Epsilon","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.conversantmedia.eu/legal/privacy-policy"},{"id":29,"name":"Etarget SE","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://www.etarget.sk/privacy.php","deletedDate":"2020-06-01T00:00:00Z"},{"id":39,"name":"ADITION technologies AG","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.adition.com/datenschutz"},{"id":11,"name":"Quantcast International Limited","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2],"policyUrl":"https://www.quantcast.com/privacy/"},{"id":15,"name":"Adikteev","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.adikteev.com/privacy-policy-eng/"},{"id":4,"name":"Roq.ad Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.roq.ad/privacy-policy"},{"id":7,"name":"Vibrant Media Limited","purposeIds":[2,3,4,5],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://www.vibrantmedia.com/en/privacy-policy/"},{"id":2,"name":"Captify Technologies Limited","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[2],"policyUrl":"http://www.captify.co.uk/privacy-policy/"},{"id":37,"name":"NEURAL.ONE","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://web.neural.one/privacy-policy/"},{"id":13,"name":"Sovrn Holdings Inc","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.sovrn.com/sovrn-privacy/"},{"id":34,"name":"NEORY GmbH","purposeIds":[1,2,4,5],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://www.neory.com/privacy.html"},{"id":32,"name":"Xandr, Inc.","purposeIds":[1],"legIntPurposeIds":[3],"featureIds":[2,3],"policyUrl":"https://www.xandr.com/privacy/platform-privacy-policy/"},{"id":10,"name":"Index Exchange, Inc. ","purposeIds":[1],"legIntPurposeIds":[3],"featureIds":[2,3],"policyUrl":"https://www.indexexchange.com/privacy"},{"id":57,"name":"ADARA MEDIA UNLIMITED","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://adara.com/privacy-promise/"},{"id":63,"name":"Avocet Systems Limited","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[1,2,3],"policyUrl":"https://avocet.io/privacy-portal"},{"id":51,"name":"xAd, Inc. dba GroundTruth","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.groundtruth.com/privacy-policy/"},{"id":49,"name":"TRADELAB","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[1,2,3],"policyUrl":"https://tradelab.com/en/privacy/"},{"id":45,"name":"Smart Adserver","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[3],"policyUrl":"https://smartadserver.com/end-user-privacy-policy/"},{"id":52,"name":"The Rubicon Project, Inc. ","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[3],"policyUrl":"http://www.rubiconproject.com/rubicon-project-yield-optimization-privacy-policy/"},{"id":71,"name":"Roku Advertising Services","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://docs.roku.com/published/userprivacypolicy/en/us"},{"id":79,"name":"MediaMath, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"http://www.mediamath.com/privacy-policy/"},{"id":91,"name":"Criteo SA","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.criteo.com/privacy/"},{"id":85,"name":"Crimtan Holdings Limited","purposeIds":[2],"legIntPurposeIds":[1,3,5],"featureIds":[1,3],"policyUrl":"https://crimtan.com/privacy/"},{"id":16,"name":"RTB House S.A.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.rtbhouse.com/privacy-center/services-privacy-policy/"},{"id":86,"name":"Scene Stealer Limited","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[3],"policyUrl":"http://scenestealer.tv/privacy-policy/"},{"id":94,"name":"Blis Media Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.blis.com/privacy/"},{"id":73,"name":"Simplifi Holdings Inc.","purposeIds":[2,3,4,5],"legIntPurposeIds":[1],"featureIds":[2,3],"policyUrl":"https://simpli.fi/site-privacy-policy/"},{"id":33,"name":"ShareThis, Inc","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[2],"policyUrl":"https://sharethis.com/privacy/"},{"id":20,"name":"N Technologies Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[2,3],"policyUrl":"https://n.rich/privacy-notice"},{"id":55,"name":"Madison Logic, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.madisonlogic.com/privacy/"},{"id":53,"name":"Sirdata","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.sirdata.com/privacy/"},{"id":69,"name":"OpenX","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.openx.com/legal/privacy-policy/"},{"id":98,"name":"GroupM UK Limited","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[1,2,3],"policyUrl":"https://www.groupm.com/privacy-notice"},{"id":62,"name":"Justpremium BV","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://justpremium.com/privacy-policy/"},{"id":19,"name":"Intent Media, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[2],"policyUrl":"https://intentmedia.com/privacy-policy/","deletedDate":"2020-07-17T00:00:00Z"},{"id":43,"name":"Vdopia DBA Chocolate Platform","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://chocolateplatform.com/privacy-policy/","deletedDate":"2019-08-06T00:00:00Z"},{"id":36,"name":"RhythmOne DBA Unruly Group Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.rhythmone.com/privacy-policy"},{"id":80,"name":"Sharethrough, Inc","purposeIds":[3,5],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://platform-cdn.sharethrough.com/privacy-policy"},{"id":81,"name":"PulsePoint, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.pulsepoint.com/privacy-policy/website","deletedDate":"2020-07-06T00:00:00Z"},{"id":23,"name":"Amobee, Inc. ","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.amobee.com/trust/privacy-guidelines"},{"id":35,"name":"Purch Group, Inc.","purposeIds":[1],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"http://www.purch.com/privacy-policy/","deletedDate":"2019-05-30T00:00:00Z"},{"id":3,"name":"affilinet","purposeIds":[2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.affili.net/de/footeritem/datenschutz","deletedDate":"2019-06-21T00:00:00Z"},{"id":74,"name":"Admotion SRL","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.admotion.com/policy/","deletedDate":"2019-07-24T00:00:00Z"},{"id":191,"name":"realzeit GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://realzeitmedia.com/privacy.html","deletedDate":"2019-04-29T00:00:00Z"},{"id":197,"name":"Switch Concepts Limited","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.switchconcepts.com/privacy-policy","deletedDate":"2019-07-26T00:00:00Z"},{"id":390,"name":"Parsec Media Inc.","purposeIds":[1,3],"legIntPurposeIds":[5],"featureIds":[1,3],"policyUrl":"www.parsec.media/privacy-policy","deletedDate":"2019-06-27T00:00:00Z"},{"id":459,"name":"uppr GmbH","purposeIds":[],"legIntPurposeIds":[1,3,5],"featureIds":[],"policyUrl":"https://netzwerk.uppr.de/privacy-policy.do","deletedDate":"2019-06-17T00:00:00Z"},{"id":221,"name":"LEMO MEDIA GROUP LIMITED","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://www.lemomedia.com/terms.pdf","deletedDate":"2019-06-28T00:00:00Z"},{"id":478,"name":"RevLifter Ltd","purposeIds":[1],"legIntPurposeIds":[2],"featureIds":[],"policyUrl":"https://www.revlifter.com/privacy-policy","deletedDate":"2019-07-15T00:00:00Z"},{"id":500,"name":"Turbo","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"http://www.turboadv.com/white-rabbit-privacy-policy/","deletedDate":"2019-07-12T00:00:00Z"},{"id":68,"name":"Sizmek by Amazon","purposeIds":[1],"legIntPurposeIds":[2,3,5],"featureIds":[2],"policyUrl":"https://www.sizmek.com/privacy-policy/"},{"id":75,"name":"M32 Connect Inc","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://m32.media/privacy-cookie-policy/"},{"id":17,"name":"Greenhouse Group BV (with its trademark LemonPI)","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.lemonpi.io/privacy-policy/","deletedDate":"2019-08-06T00:00:00Z"},{"id":61,"name":"GumGum, Inc.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://gumgum.com/privacy-policy"},{"id":40,"name":"Active Agent (ADITION technologies AG)","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"http://www.active-agent.com/de/unternehmen/datenschutzerklaerung/"},{"id":76,"name":"PubMatic, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[],"policyUrl":"https://pubmatic.com/privacy-policy/"},{"id":89,"name":"Tapad, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,5],"featureIds":[2],"policyUrl":"https://www.tapad.com/eu-privacy-policy"},{"id":46,"name":"Skimbit Ltd","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[2],"policyUrl":"https://skimlinks.com/pages/privacy-policy"},{"id":66,"name":"adsquare GmbH","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.adsquare.com/privacy"},{"id":105,"name":"Impression Desk Technologies Limited","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://impressiondesk.com/privacy-policy/","deletedDate":"2019-08-06T00:00:00Z"},{"id":41,"name":"Adverline","purposeIds":[2],"legIntPurposeIds":[1,3],"featureIds":[],"policyUrl":"https://www.adverline.com/privacy/"},{"id":82,"name":"Smaato, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.smaato.com/privacy/"},{"id":60,"name":"Rakuten Marketing LLC","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[1,2],"policyUrl":"https://rakutenadvertising.com/legal-notices/services-privacy-policy/"},{"id":70,"name":"Yieldlab AG","purposeIds":[],"legIntPurposeIds":[1,3],"featureIds":[3],"policyUrl":"http://www.yieldlab.de/meta-navigation/datenschutz/"},{"id":50,"name":"Adform","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2],"policyUrl":"https://site.adform.com/privacy-center/platform-privacy/product-and-services-privacy-policy/"},{"id":48,"name":"NetSuccess, s.r.o.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.inres.sk/pp/"},{"id":100,"name":"Fifty Technology Limited","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://fifty.io/privacy-policy.php"},{"id":21,"name":"The Trade Desk","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[1,2,3],"policyUrl":"https://www.thetradedesk.com/general/privacy-policy"},{"id":110,"name":"Dynata LLC","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.opinionoutpost.co.uk/en-gb/policies/privacy"},{"id":42,"name":"Taboola Europe Limited","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2],"policyUrl":"https://www.taboola.com/privacy-policy"},{"id":112,"name":"Maytrics GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://maytrics.com/privacy.php","deletedDate":"2019-09-17T00:00:00Z"},{"id":77,"name":"comScore, Inc.","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.scorecardresearch.com/privacy.aspx?newlanguage=1"},{"id":109,"name":"LoopMe Limited","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://loopme.com/privacy-policy/"},{"id":120,"name":"Eyeota Pte Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://www.eyeota.com/privacy-center"},{"id":93,"name":"Adloox SA","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"http://adloox.com/disclaimer"},{"id":132,"name":"Teads ","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[],"policyUrl":"https://www.teads.com/privacy-policy/"},{"id":22,"name":"admetrics GmbH","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://admetrics.io/en/privacy_policy/"},{"id":102,"name":"Telaria SAS","purposeIds":[1,2],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://telaria.com/privacy-policy/"},{"id":108,"name":"Rich Audience Technologies SL","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://richaudience.com/privacy/"},{"id":18,"name":"Widespace AB","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.widespace.com/legal/privacy-policy-notice/"},{"id":122,"name":"Avid Media Ltd","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.avidglobalmedia.eu/privacy-policy.html"},{"id":97,"name":"LiveRamp, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.liveramp.com/service-privacy-policy/"},{"id":138,"name":"ConnectAd Realtime GmbH","purposeIds":[1,2],"legIntPurposeIds":[3,4],"featureIds":[],"policyUrl":"http://connectadrealtime.com/privacy/"},{"id":72,"name":"Nano Interactive GmbH","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.nanointeractive.com/privacy"},{"id":127,"name":"PIXIMEDIA SAS","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[3],"policyUrl":"https://piximedia.com/privacy/"},{"id":136,"name":"Str\u00f6er SSP GmbH (SSP)","purposeIds":[],"legIntPurposeIds":[1,2,3,5],"featureIds":[2,3],"policyUrl":"https://www.stroeer.de/fileadmin/de/Konvergenz_und_Konzepte/Daten_und_Technologien/Stroeer_SSP/Downloads/Datenschutz_Stroeer_SSP.pdf"},{"id":111,"name":"Showheroes SE","purposeIds":[2,3],"legIntPurposeIds":[1,4,5],"featureIds":[],"policyUrl":"https://showheroes.com/privacy/"},{"id":56,"name":"Confiant Inc.","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.confiant.com/privacy","deletedDate":"2020-05-18T00:00:00Z"},{"id":124,"name":"Teemo SA","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://teemo.co/fr/confidentialite/"},{"id":154,"name":"YOC AG","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[3],"policyUrl":"https://yoc.com/privacy/"},{"id":38,"name":"Beemray Oy","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.beemray.com/privacy-policy/","deletedDate":"2020-06-19T00:00:00Z"},{"id":101,"name":"MiQ","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://wearemiq.com/privacy-policy/"},{"id":149,"name":"ADman Interactive SLU","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[2,3],"policyUrl":"https://admanmedia.com/politica.html?setLng=es"},{"id":151,"name":"Admedo Ltd","purposeIds":[],"legIntPurposeIds":[1,2,3],"featureIds":[3],"policyUrl":"https://www.admedo.com/privacy-policy","deletedDate":"2020-07-17T00:00:00Z"},{"id":153,"name":"MADVERTISE MEDIA","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://madvertise.com/en/gdpr/"},{"id":159,"name":"Underdog Media LLC ","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[],"policyUrl":"https://underdogmedia.com/privacy-policy/"},{"id":157,"name":"Seedtag Advertising S.L","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.seedtag.com/en/privacy-policy/"},{"id":145,"name":"Snapsort Inc., operating as Sortable","purposeIds":[],"legIntPurposeIds":[1,3,5],"featureIds":[3],"policyUrl":"https://help.sortable.com/help/privacy-policy"},{"id":131,"name":"ID5 Technology SAS","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.id5.io/privacy"},{"id":158,"name":"Reveal Mobile, Inc","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://revealmobile.com/privacy"},{"id":147,"name":"Adacado Technologies Inc. (DBA Adacado)","purposeIds":[1,2,3,4],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.adacado.com/privacy-policy-april-25-2018/"},{"id":130,"name":"NextRoll, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.nextroll.com/privacy"},{"id":129,"name":"IPONWEB GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.iponweb.com/privacy-policy/"},{"id":128,"name":"BIDSWITCH GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.bidswitch.com/privacy-policy/"},{"id":168,"name":"EASYmedia GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://login.rtbmarket.com/gdpr"},{"id":164,"name":"Outbrain UK Ltd","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://www.outbrain.com/legal/privacy#privacy-policy"},{"id":144,"name":"district m inc.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://districtm.net/en/page/platforms-data-and-privacy-policy/"},{"id":163,"name":"Bombora Inc.","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[1,2],"policyUrl":"https://bombora.com/privacy"},{"id":173,"name":"Yieldmo, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.yieldmo.com/privacy/"},{"id":88,"name":"TreSensa, Inc.","purposeIds":[1,3],"legIntPurposeIds":[2,5],"featureIds":[1],"policyUrl":"https://www.tresensa.com/eu-privacy"},{"id":78,"name":"Flashtalking, Inc.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.flashtalking.com/privacypolicy/"},{"id":59,"name":"Sift Media, Inc","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.sift.co/privacy"},{"id":114,"name":"Sublime","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"http://ayads.co/privacy.php"},{"id":175,"name":"FORTVISION","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://fortvision.com/POC/index.html","deletedDate":"2019-08-09T00:00:00Z"},{"id":133,"name":"digitalAudience","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://digitalaudience.io/legal/privacy-cookies/"},{"id":14,"name":"Adkernel LLC","purposeIds":[1,4],"legIntPurposeIds":[2,3,5],"featureIds":[1,3],"policyUrl":"http://adkernel.com/privacy-policy/"},{"id":180,"name":"Thirdpresence Oy","purposeIds":[1],"legIntPurposeIds":[3],"featureIds":[3],"policyUrl":"http://www.thirdpresence.com/privacy","deletedDate":"2019-10-02T00:00:00Z"},{"id":183,"name":"EMX Digital LLC","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[2,3],"policyUrl":"https://emxdigital.com/privacy/"},{"id":58,"name":"33Across","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://www.33across.com/privacy-policy"},{"id":140,"name":"Platform161","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://platform161.com/cookie-and-privacy-policy/"},{"id":90,"name":"Teroa S.A.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[],"policyUrl":"https://www.e-planning.net/en/privacy.html"},{"id":141,"name":"1020, Inc. dba Placecast and Ericsson Emodo","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[3],"policyUrl":"https://www.emodoinc.com/privacy-policy/"},{"id":142,"name":"Media.net Advertising FZ-LLC","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[3],"policyUrl":"https://www.media.net/en/privacy-policy"},{"id":209,"name":"Delta Projects AB","purposeIds":[1],"legIntPurposeIds":[2,3,5],"featureIds":[3],"policyUrl":"https://deltaprojects.com/data-collection-policy"},{"id":195,"name":"advanced store GmbH","purposeIds":[2,3],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"http://www.advanced-store.com/de/datenschutz/"},{"id":190,"name":"video intelligence AG","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.vi.ai/privacy-policy/"},{"id":84,"name":"Semasio GmbH","purposeIds":[],"legIntPurposeIds":[1,2,4,5],"featureIds":[],"policyUrl":"http://www.semasio.com/privacy-policy/"},{"id":65,"name":"Location Sciences AI Ltd","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.locationsciences.ai/privacy-policy/"},{"id":210,"name":"Zemanta, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,5],"featureIds":[1],"policyUrl":"http://www.zemanta.com/legal/privacy"},{"id":200,"name":"Tapjoy, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://www.tapjoy.com/legal/#privacy-policy"},{"id":188,"name":"Sellpoints Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://retargeter.com/service-privacy-policy/","deletedDate":"2019-09-17T00:00:00Z"},{"id":217,"name":"2KDirect, Inc. (dba iPromote)","purposeIds":[],"legIntPurposeIds":[1,3],"featureIds":[],"policyUrl":"https://www.ipromote.com/privacy-policy/"},{"id":156,"name":"Centro, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1],"policyUrl":"https://www.centro.net/privacy-policy/"},{"id":194,"name":"Rezonence Limited","purposeIds":[3,5],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://rezonence.com/privacy-policy/"},{"id":226,"name":"Publicis Media GmbH","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1],"policyUrl":"https://www.publicismedia.de/datenschutz/"},{"id":198,"name":"SYNC","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://redirect.sync.tv/privacy/","deletedDate":"2019-09-12T00:00:00Z"},{"id":227,"name":"ORTEC B.V.","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.ortecadscience.com/privacy-policy/"},{"id":225,"name":"Ligatus GmbH","purposeIds":[],"legIntPurposeIds":[1,2,3,5],"featureIds":[3],"policyUrl":"https://www.ligatus.com/en/privacy-policy","deletedDate":"2020-06-19T00:00:00Z"},{"id":205,"name":"Adssets AB","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"http://adssets.com/policy/"},{"id":179,"name":"Collective Europe Ltd.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.collectiveuk.com/privacy.html"},{"id":31,"name":"Ogury Ltd.","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[2,3],"policyUrl":"https://www.ogury.com/privacy-policy/"},{"id":92,"name":"1plusX AG","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.1plusx.com/privacy-policy/"},{"id":155,"name":"AntVoice","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.antvoice.com/en/privacypolicy/"},{"id":115,"name":"smartclip Europe GmbH","purposeIds":[1],"legIntPurposeIds":[2,3,5],"featureIds":[2],"policyUrl":"https://privacy-portal.smartclip.net/"},{"id":126,"name":"DoubleVerify Inc.\u200b","purposeIds":[5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.doubleverify.com/privacy/"},{"id":193,"name":"Mediasmart Mobile S.L.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://mediasmart.io/privacy/"},{"id":245,"name":"IgnitionOne","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2],"policyUrl":"https://www.ignitionone.com/privacy-policy/","deletedDate":"2020-06-30T00:00:00Z"},{"id":213,"name":"emetriq GmbH","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2],"policyUrl":"https://www.emetriq.com/datenschutz/"},{"id":244,"name":"Temelio","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://temelio.com/vie-privee"},{"id":224,"name":"adrule mobile GmbH","purposeIds":[2,4],"legIntPurposeIds":[1,3,5],"featureIds":[3],"policyUrl":"https://www.adrule.net/de/datenschutz/"},{"id":174,"name":"A Million Ads Ltd","purposeIds":[2],"legIntPurposeIds":[1,3],"featureIds":[],"policyUrl":"https://www.amillionads.com/privacy-policy"},{"id":192,"name":"remerge GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://remerge.io/privacy-policy.html"},{"id":232,"name":"Rockerbox, Inc","purposeIds":[],"legIntPurposeIds":[1,3,5],"featureIds":[3],"policyUrl":"http://rockerbox.com/privacy","deletedDate":"2020-07-17T00:00:00Z"},{"id":256,"name":"Bounce Exchange, Inc","purposeIds":[1],"legIntPurposeIds":[2,4,5],"featureIds":[1,2],"policyUrl":"https://www.bouncex.com/privacy/"},{"id":234,"name":"ZBO Media","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[1,2],"policyUrl":"https://zbo.media/mentions-legales/politique-de-confidentialite-service-publicitaire/"},{"id":246,"name":"Smartology Limited","purposeIds":[3],"legIntPurposeIds":[4,5],"featureIds":[],"policyUrl":"https://www.smartology.net/privacy-policy/"},{"id":241,"name":"OneTag Limited","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[1,2,3],"policyUrl":"https://www.onetag.com/privacy/"},{"id":254,"name":"LiquidM Technology GmbH","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://liquidm.com/privacy-policy/"},{"id":215,"name":"ARMIS SAS","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://armis.tech/en/armis-personal-data-privacy-policy/"},{"id":167,"name":"Audiens S.r.l.","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.audiens.com/privacy"},{"id":240,"name":"7Hops.com Inc. (ZergNet)","purposeIds":[],"legIntPurposeIds":[1,4,5],"featureIds":[],"policyUrl":"https://zergnet.com/privacy"},{"id":235,"name":"Bucksense Inc","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[2,3],"policyUrl":"http://www.bucksense.com/platform-privacy-policy/"},{"id":185,"name":"Bidtellect, Inc","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.bidtellect.com/privacy-policy/"},{"id":258,"name":"Adello Group AG","purposeIds":[],"legIntPurposeIds":[1,2,3,5],"featureIds":[3],"policyUrl":"https://www.adello.com/privacy-policy/"},{"id":169,"name":"RTK.IO, Inc","purposeIds":[1,4],"legIntPurposeIds":[2,3,5],"featureIds":[1,3],"policyUrl":"http://www.rtk.io/privacy.html","deletedDate":"2020-07-17T00:00:00Z"},{"id":208,"name":"Spotad","purposeIds":[2,3],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.spotad.co/privacy-policy/","deletedDate":"2020-07-06T00:00:00Z"},{"id":211,"name":"AdTheorent, Inc","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://adtheorent.com/privacy-policy"},{"id":229,"name":"Digitize New Media Ltd","purposeIds":[2,4],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.digitize.ie/online-privacy","deletedDate":"2020-07-17T00:00:00Z"},{"id":273,"name":"Bannerflow AB","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"https://www.bannerflow.com/privacy "},{"id":104,"name":"Sonobi, Inc","purposeIds":[1,2],"legIntPurposeIds":[3],"featureIds":[1,2,3],"policyUrl":"http://sonobi.com/privacy-policy/"},{"id":162,"name":"Unruly Group Ltd","purposeIds":[1],"legIntPurposeIds":[2,3,5],"featureIds":[1,2],"policyUrl":"https://unruly.co/privacy/"},{"id":249,"name":"Spolecznosci Sp. z o.o. Sp. k.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.spolecznosci.pl/polityka-prywatnosci"},{"id":125,"name":"Research Now Group, Inc","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.valuedopinions.co.uk/privacy","deletedDate":"2019-09-17T00:00:00Z"},{"id":170,"name":"Goodway Group, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://goodwaygroup.com/privacy-policy/"},{"id":160,"name":"Netsprint SA","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://netsprint.eu/privacy.html"},{"id":189,"name":"Intowow Innovation Ltd.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://www.intowow.com/privacy/","deletedDate":"2019-08-12T00:00:00Z"},{"id":279,"name":"Mirando GmbH &amp; Co KG","purposeIds":[],"legIntPurposeIds":[1],"featureIds":[3],"policyUrl":"https://wwwmirando.de/datenschutz/"},{"id":269,"name":"Sanoma Media Finland","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://sanoma.fi/tietoa-meista/tietosuoja/","deletedDate":"2019-08-07T00:00:00Z"},{"id":276,"name":"Viralize SRL","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"https://viralize.com/privacy-policy"},{"id":87,"name":"Genius Sports Media Limited","purposeIds":[2,4],"legIntPurposeIds":[1,3,5],"featureIds":[2,3],"policyUrl":"https://www.geniussports.com/privacy-policy"},{"id":182,"name":"Collective, Inc. dba Visto","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.vistohub.com/privacy-policy/","deletedDate":"2019-07-26T00:00:00Z"},{"id":255,"name":"Onnetwork Sp. z o.o.","purposeIds":[2,3,5],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://www.onnetwork.tv/pp_services.php"},{"id":203,"name":"Revcontent, LLC","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://intercom.help/revcontent2/en/articles/2290675-revcontent-s-privacy-policy"},{"id":260,"name":"RockYou, Inc.","purposeIds":[3],"legIntPurposeIds":[1,2,5],"featureIds":[3],"policyUrl":"https://rockyou.com/privacy-policy/","deletedDate":"2019-08-09T00:00:00Z"},{"id":237,"name":"LKQD, a division of Nexstar Digital, LLC.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[2,3],"policyUrl":"http://www.lkqd.com/privacy-policy/","deletedDate":"2019-09-12T00:00:00Z"},{"id":274,"name":"Golden Bees","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.goldenbees.fr/en/privacy-charter/"},{"id":280,"name":"Spot.IM LTD","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://www.spot.im/privacy/"},{"id":239,"name":"Triton Digital Canada Inc.","purposeIds":[],"legIntPurposeIds":[1,3,4,5],"featureIds":[],"policyUrl":"https://www.tritondigital.com/privacy-policies"},{"id":177,"name":"plista GmbH","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.plista.com/about/privacy/"},{"id":201,"name":"TimeOne","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[1,2,3],"policyUrl":"https://privacy.timeonegroup.com/en/","deletedDate":"2020-05-15T00:00:00Z"},{"id":150,"name":"Inskin Media LTD","purposeIds":[2,3,5],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"http://www.inskinmedia.com/privacy-policy.html"},{"id":252,"name":"Jaduda GmbH","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[3],"policyUrl":"https://www.jadudamobile.com/datenschutzerklaerung/"},{"id":248,"name":"Converge-Digital","purposeIds":[1],"legIntPurposeIds":[3,4,5],"featureIds":[3],"policyUrl":"https://converge-digital.com/privacy-policy/"},{"id":161,"name":"Smadex SL","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://smadex.com/end-user-privacy-policy/"},{"id":285,"name":"Comcast International France SAS","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.freewheel.com/privacy-policy"},{"id":228,"name":"McCann Discipline LTD","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.primis.tech/privacy-policy/"},{"id":299,"name":"AdClear GmbH","purposeIds":[1,5],"legIntPurposeIds":[2,3,4],"featureIds":[1,2],"policyUrl":"https://www.adclear.de/datenschutzerklaerung/"},{"id":277,"name":"Codewise VL Sp. z o.o. Sp. k","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://voluumdsp.com/end-user-privacy-policy/"},{"id":259,"name":"ADYOULIKE SA","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"https://www.adyoulike.com/privacy_policy.php"},{"id":272,"name":"A.Mob","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.we-are-adot.com/privacy-policy/"},{"id":230,"name":"Steel House, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://steelhouse.com/privacy-policy/"},{"id":253,"name":"Improve Digital BV","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.improvedigital.com/platform-privacy-policy"},{"id":304,"name":"On Device Research Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://s.on-device.com/privacyPolicy"},{"id":314,"name":"Keymantics","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.keymantics.com/assets/privacy-policy.pdf"},{"id":257,"name":"R-TARGET","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"http://www.r-target.com/privacy","deletedDate":"2019-08-26T00:00:00Z"},{"id":317,"name":"mainADV Srl","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"http://www.mainad.com/privacy-policy/"},{"id":278,"name":"Integral Ad Science, Inc.","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://integralads.com/privacy-policy/"},{"id":291,"name":"Qwertize","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"https://www.qwertize.com/en/privacy","deletedDate":"2019-08-26T00:00:00Z"},{"id":295,"name":"Sojern, Inc.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.sojern.com/privacy/product-privacy-policy/"},{"id":315,"name":"Celtra, Inc.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.celtra.com/privacy-policy/"},{"id":165,"name":"SpotX, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1],"policyUrl":"https://www.spotx.tv/privacy-policy/"},{"id":47,"name":"ADMAN - Phaistos Networks, S.A.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.adman.gr/privacy"},{"id":134,"name":"SMARTSTREAM.TV GmbH","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[2],"policyUrl":"https://www.smartstream.tv/en/productprivacy"},{"id":325,"name":"Knorex","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.knorex.com/privacy"},{"id":316,"name":"Gamned","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.gamned.com/privacy-policy/"},{"id":318,"name":"Accorp Sp. z o.o.","purposeIds":[2,3,4],"legIntPurposeIds":[1,5],"featureIds":[],"policyUrl":"http://www.instytut-pollster.pl/privacy-policy/"},{"id":199,"name":"ADUX","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.adux.com/donnees-personelles/"},{"id":236,"name":"PowerLinks Media Limited","purposeIds":[1,2,5],"legIntPurposeIds":[3,4],"featureIds":[3],"policyUrl":"https://www.powerlinks.com/privacy-policy/"},{"id":294,"name":"Jivox Corporation","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.jivox.com/privacy"},{"id":143,"name":"Connatix Native Exchange Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://connatix.com/privacy-policy/"},{"id":297,"name":"Polar Mobile Group Inc.","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[],"policyUrl":"https://privacy.polar.me"},{"id":319,"name":"Clipcentric, Inc.","purposeIds":[1,2],"legIntPurposeIds":[3],"featureIds":[3],"policyUrl":"https://clipcentric.com/privacy.bhtml"},{"id":290,"name":"Readpeak Oy","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://readpeak.com/privacy-policy/"},{"id":323,"name":"DAZN Media Services Limited","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2],"policyUrl":"https://www.goal.com/en-gb/legal/privacy-policy"},{"id":119,"name":"Fusio by S4M","purposeIds":[1,2,5],"legIntPurposeIds":[3],"featureIds":[1,3],"policyUrl":"http://www.s4m.io/privacy-policy/"},{"id":302,"name":"Mobile Professionals BV","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://mobpro.com/privacy.html"},{"id":212,"name":"usemax advertisement (Emego GmbH)","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"http://www.usemax.de/?l=privacy"},{"id":264,"name":"Adobe Advertising Cloud","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.adobe.com/privacy/experience-cloud.html"},{"id":44,"name":"The ADEX GmbH","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://theadex.com/privacy-opt-out/"},{"id":282,"name":"Welect GmbH","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.welect.de/datenschutz"},{"id":238,"name":"StackAdapt","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.stackadapt.com/privacy"},{"id":284,"name":"WEBORAMA","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2],"policyUrl":"https://weborama.com/privacy_en/"},{"id":148,"name":"Liveintent Inc.","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://liveintent.com/services-privacy-policy/"},{"id":64,"name":"DigiTrust / IAB Tech Lab","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.digitru.st/privacy-policy/"},{"id":301,"name":"zeotap GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://zeotap.com/privacy_policy"},{"id":275,"name":"TabMo SAS","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"http://static.tabmo.io.s3.amazonaws.com/privacy-policy/index.html"},{"id":310,"name":"Adevinta Spain S.L.U.","purposeIds":[],"legIntPurposeIds":[4],"featureIds":[],"policyUrl":"https://www.adevinta.com/about/privacy/"},{"id":139,"name":"Permodo GmbH","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://permodo.com/de/privacy.html"},{"id":326,"name":"AdTiming Technology Company Limited","purposeIds":[3,5],"legIntPurposeIds":[1,2,4],"featureIds":[],"policyUrl":"http://www.adtiming.com/en/privacypolicy.html"},{"id":262,"name":"Fyber ","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.fyber.com/legal/privacy-policy/"},{"id":331,"name":"ad6media","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[2,3],"policyUrl":"https://www.ad6media.fr/privacy"},{"id":345,"name":"The Kantar Group Limited","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.kantar.com/cookies-policies"},{"id":308,"name":"Rockabox Media Ltd","purposeIds":[1],"legIntPurposeIds":[2,3],"featureIds":[],"policyUrl":"http://scoota.com/privacy-policy"},{"id":270,"name":"Marfeel Solutions, SL","purposeIds":[],"legIntPurposeIds":[2],"featureIds":[],"policyUrl":"https://www.marfeel.com/privacy-policy/"},{"id":333,"name":"InMobi Pte Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.inmobi.com/privacy-policy-for-eea"},{"id":202,"name":"Telaria, Inc","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://telaria.com/privacy-policy/"},{"id":328,"name":"Gemius SA","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.gemius.com/cookie-policy.html"},{"id":281,"name":"Wizaly","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.wizaly.com/terms-of-use#privacy-policy"},{"id":354,"name":"Apester Ltd","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[],"policyUrl":"https://apester.com/privacy-policy/"},{"id":320,"name":"Adelphic LLC","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://adelphic.com/platform/privacy/"},{"id":359,"name":"AerServ LLC","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[1,2,3],"policyUrl":"https://www.inmobi.com/privacy-policy-for-eea"},{"id":265,"name":"Instinctive, Inc.","purposeIds":[2,3,4],"legIntPurposeIds":[1,5],"featureIds":[],"policyUrl":"https://instinctive.io/privacy"},{"id":349,"name":"Optomaton UG","purposeIds":[1,2,3,4],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://optomaton.com/privacy.html"},{"id":288,"name":"Video Media Groep B.V.","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[],"policyUrl":"http://www.videomediagroup.com/wp-content/uploads/2016/01/Privacy-policy-VMG.pdf","deletedDate":"2019-09-17T00:00:00Z"},{"id":266,"name":"Digilant Spain, SLU","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://www.digilant.com/es/politica-privacidad/"},{"id":339,"name":"Vuble","purposeIds":[2,3,4,5],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://www.vuble.tv/us/privacy","deletedDate":"2019-08-26T00:00:00Z"},{"id":303,"name":"Orion Semantics","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://static.orion-semantics.com/privacy.html"},{"id":261,"name":"Signal Digital Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.signal.co/privacy-policy/"},{"id":83,"name":"Visarity Technologies GmbH","purposeIds":[5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://primo.design/docs/PrivacyPolicyPrimo.html"},{"id":343,"name":"DIGITEKA Technologies","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[3],"policyUrl":"https://www.ultimedia.com/POLICY.html"},{"id":330,"name":"Linicom","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[],"policyUrl":"https://www.linicom.com/privacy/","deletedDate":"2020-06-08T00:00:00Z"},{"id":231,"name":"AcuityAds Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://privacy.acuityads.com/corporate-privacy-policy.html"},{"id":216,"name":"Mindlytix SAS","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"http://mindlytix.com/privacy/"},{"id":360,"name":"Permutive Technologies, Inc.","purposeIds":[],"legIntPurposeIds":[1],"featureIds":[1,2],"policyUrl":"https://permutive.com/privacy","deletedDate":"2020-03-31T00:00:00Z"},{"id":361,"name":"Permutive","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://permutive.com/privacy"},{"id":311,"name":"Mobfox US LLC","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.mobfox.com/privacy-policy/"},{"id":358,"name":"MGID Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.mgid.com/privacy-policy"},{"id":152,"name":"Meetrics GmbH","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.meetrics.com/en/data-privacy/"},{"id":251,"name":"Yieldlove GmbH","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[],"policyUrl":"http://www.yieldlove.com/cookie-policy"},{"id":344,"name":"My6sense Inc.","purposeIds":[1,3,5],"legIntPurposeIds":[2,4],"featureIds":[],"policyUrl":"https://my6sense.com/privacy-policy/","deletedDate":"2020-07-17T00:00:00Z"},{"id":347,"name":"Ezoic Inc.","purposeIds":[2,4,5],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://www.ezoic.com/terms/"},{"id":218,"name":"Bigabid Media ltd","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[3],"policyUrl":"https://www.bigabid.com/privacy-policy"},{"id":350,"name":"Free Stream Media Corp. dba Samba TV","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://samba.tv/legal/privacy-policy/"},{"id":351,"name":"Samba TV UK Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://samba.tv/legal/privacy-policy/"},{"id":341,"name":"Somo Audience Corp","purposeIds":[1,2],"legIntPurposeIds":[3],"featureIds":[1,2,3],"policyUrl":"https://somoaudience.com/legal/","deletedDate":"2020-07-06T00:00:00Z"},{"id":380,"name":"Vidoomy Media SL","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[2,3],"policyUrl":"http://vidoomy.com/privacy-policy.html"},{"id":378,"name":"communicationAds GmbH &amp; Co. KG","purposeIds":[],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://www.communicationads.net/aboutus/privacy/"},{"id":369,"name":"Getintent USA, inc.","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://getintent.com/privacy/","deletedDate":"2020-07-17T00:00:00Z"},{"id":184,"name":"mediarithmics SAS","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.mediarithmics.com/en-us/content/privacy-policy"},{"id":368,"name":"VECTAURY","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.vectaury.io/en/personal-data"},{"id":373,"name":"Nielsen Marketing Cloud","purposeIds":[1,2],"legIntPurposeIds":[5],"featureIds":[1,2],"policyUrl":"http://www.nielsen.com/us/en/privacy-statement/exelate-privacy-policy.html"},{"id":214,"name":"Digital Control GmbH &amp; Co. KG","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://advolution.de/privacy.php","deletedDate":"2020-05-06T00:00:00Z"},{"id":388,"name":"numberly","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://numberly.com/en/privacy/"},{"id":250,"name":"Qriously Ltd","purposeIds":[2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.brandwatch.com/legal/qriously-privacy-notice/"},{"id":223,"name":"Audience Trading Platform Ltd.","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[2],"policyUrl":"https://atp.io/privacy-policy"},{"id":384,"name":"Pixalate, Inc.","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[],"policyUrl":"http://pixalate.com/privacypolicy/","deletedDate":"2019-11-08T00:00:00Z"},{"id":387,"name":"Triapodi Ltd.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://appreciate.mobi/page.html#/end-user-privacy-policy"},{"id":312,"name":"Exactag GmbH","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.exactag.com/en/data-privacy/"},{"id":178,"name":"Hybrid Theory","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://hybridtheory.com/privacy-policy/"},{"id":377,"name":"AddApptr GmbH","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.addapptr.com/data-privacy"},{"id":382,"name":"The Reach Group GmbH","purposeIds":[1,2,4,5],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://trg.de/en/privacy-statement/"},{"id":206,"name":"Hybrid Adtech GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://hybrid.ai/data_protection_policy"},{"id":403,"name":"Mobusi Mobile Advertising S.L.","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.mobusi.com/privacy.en.html","deletedDate":"2020-07-17T00:00:00Z"},{"id":385,"name":"Oracle Data Cloud","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[2],"policyUrl":"https://www.oracle.com/legal/privacy/marketing-cloud-data-cloud-privacy-policy.html"},{"id":404,"name":"Duplo Media AS","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[],"policyUrl":"https://www.easy-ads.com/privacypolicy.htm"},{"id":242,"name":"twiago GmbH","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.twiago.com/datenschutz/"},{"id":376,"name":"Pocketmath Pte Ltd","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.pocketmath.com/privacy-policy"},{"id":402,"name":"Effiliation","purposeIds":[2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://inter.effiliation.com/politique-confidentialite.html"},{"id":413,"name":"Eulerian Technologies","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.eulerian.com/en/privacy/"},{"id":400,"name":"Whenever Media Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.whenevermedia.com/privacy-policy","deletedDate":"2019-07-29T00:00:00Z"},{"id":171,"name":"Webedia","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.webedia-group.com/site/privacy-policy","deletedDate":"2020-07-01T00:00:00Z"},{"id":398,"name":"Yormedia Solutions Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://www.yormedia.com/privacy-and-cookies-notice/","deletedDate":"2019-08-06T00:00:00Z"},{"id":415,"name":"Seenthis AB","purposeIds":[],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://seenthis.co/privacy-notice-2018-04-18.pdf"},{"id":263,"name":"Nativo, Inc.","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.nativo.com/interest-based-ads"},{"id":329,"name":"Browsi Mobile Ltd","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://gobrowsi.com/browsi-privacy-policy/"},{"id":389,"name":"Bidmanagement GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.adspert.net/en/privacy/","deletedDate":"2020-07-01T00:00:00Z"},{"id":337,"name":"SheMedia, LLC","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.shemedia.com/ad-services-privacy-policy"},{"id":422,"name":"Brand Metrics Sweden AB","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://collector.brandmetrics.com/brandmetrics_privacypolicy.pdf"},{"id":421,"name":"LeftsnRight, Inc. dba LIQWID","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://liqwid.solutions/privacy-policy","deletedDate":"2020-06-30T00:00:00Z"},{"id":426,"name":"TradeTracker","purposeIds":[],"legIntPurposeIds":[1,3,5],"featureIds":[2],"policyUrl":"https://tradetracker.com/privacy-policy/","deletedDate":"2019-08-21T00:00:00Z"},{"id":394,"name":"AudienceProject Aps","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[1,2],"policyUrl":"https://privacy.audienceproject.com"},{"id":287,"name":"Avazu Inc.","purposeIds":[],"legIntPurposeIds":[1,3,4],"featureIds":[3],"policyUrl":"http://avazuinc.com/opt-out/","deletedDate":"2020-08-03T00:00:00Z"},{"id":243,"name":"Cloud Technologies S.A.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.cloudtechnologies.pl/en/internet-advertising-privacy-policy"},{"id":113,"name":"iotec global Ltd.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.iotecglobal.com/privacy-policy/","deletedDate":"2020-07-06T00:00:00Z"},{"id":338,"name":"dunnhumby Germany GmbH","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.sociomantic.com/privacy/en/","deletedDate":"2020-07-17T00:00:00Z"},{"id":405,"name":"IgnitionAi Ltd","purposeIds":[],"legIntPurposeIds":[1,3,4,5],"featureIds":[2],"policyUrl":"https://www.isitelab.io/default.aspx","deletedDate":"2020-07-03T00:00:00Z"},{"id":416,"name":"Commanders Act","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.commandersact.com/en/privacy/"},{"id":434,"name":"DynAdmic","purposeIds":[1,3],"legIntPurposeIds":[2,4],"featureIds":[1,3],"policyUrl":"http://eu.dynadmic.com/privacy-policy/"},{"id":435,"name":"SINGLESPOT SAS ","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"https://www.singlespot.com/privacy_policy?locale=fr"},{"id":409,"name":"Arrivalist Co.","purposeIds":[],"legIntPurposeIds":[1,3,5],"featureIds":[1,2],"policyUrl":"https://www.arrivalist.com/privacy"},{"id":321,"name":"Ziff Davis LLC","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.ziffdavis.com/privacy-policy"},{"id":436,"name":"INVIBES GROUP","purposeIds":[2,3,4],"legIntPurposeIds":[1,5],"featureIds":[1,2,3],"policyUrl":"http://www.invibes.com/terms"},{"id":442,"name":"R-Advertising","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.tradedoubler.com/en/privacy-policy/","deletedDate":"2019-08-20T00:00:00Z"},{"id":362,"name":"Myntelligence S.r.l.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://myntelligence.com/privacy-page/"},{"id":418,"name":"PROXISTORE","purposeIds":[1],"legIntPurposeIds":[3],"featureIds":[3],"policyUrl":"https://www.proxistore.com/common/en/cgv"},{"id":449,"name":"Mobile Journey B.V.","purposeIds":[],"legIntPurposeIds":[1,3,5],"featureIds":[3],"policyUrl":"https://mobilejourney.com/Privacy-Policy","deletedDate":"2019-09-05T00:00:00Z"},{"id":443,"name":"Tradedoubler AB","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[2],"policyUrl":"https://www.tradedoubler.com/en/privacy-policy/","deletedDate":"2019-08-13T00:00:00Z"},{"id":429,"name":"Signals","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://signalsdata.com/platform-cookie-policy/"},{"id":335,"name":"Beachfront Media LLC","purposeIds":[],"legIntPurposeIds":[1,3,4,5],"featureIds":[],"policyUrl":"http://beachfront.com/privacy-policy/"},{"id":407,"name":"Publishers Internationale Pty Ltd","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.pi-rate.com.au/privacy.html","deletedDate":"2019-11-08T00:00:00Z"},{"id":427,"name":"Proxi.cloud Sp. z o.o.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://proxi.cloud/info/privacy-policy/"},{"id":374,"name":"Bmind a Sales Maker Company, S.L.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.bmind.es/legal-notice/"},{"id":438,"name":"INVIDI technologies AB","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"http://www.invidi.com/wp-content/uploads/2020/02/ad-tech-services-privacy-policy.pdf"},{"id":450,"name":"Neodata Group srl","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.neodatagroup.com/en/security-policy"},{"id":452,"name":"Innovid Inc.","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[2],"policyUrl":"http://www.innovid.com/privacy-policy"},{"id":444,"name":"Playbuzz Ltd (aka EX.CO)","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[],"policyUrl":"https://ex.co/privacy-policy/"},{"id":412,"name":"Cxense ASA","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.cxense.com/about-us/privacy-policy"},{"id":454,"name":"Adimo","purposeIds":[1],"legIntPurposeIds":[5],"featureIds":[3],"policyUrl":"https://adimo.co/privacy-policy/","deletedDate":"2019-09-12T00:00:00Z"},{"id":455,"name":"GDMServices, Inc. d/b/a FiksuDSP","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://fiksu.com/privacy-policy/"},{"id":298,"name":"Cuebiq Inc.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.cuebiq.com/privacypolicy/","deletedDate":"2019-08-30T00:00:00Z"},{"id":423,"name":"travel audience GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://travelaudience.com/product-privacy-policy/"},{"id":397,"name":"Demandbase, Inc. ","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.demandbase.com/privacy-policy/"},{"id":381,"name":"Solocal","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://frontend.adhslx.com/privacy.html?"},{"id":425,"name":"ADRINO Sp. z o.o.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"http://www.adrino.pl/ciasteczkowa-polityka/","deletedDate":"2019-09-05T00:00:00Z"},{"id":365,"name":"Forensiq LLC","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[1,3],"policyUrl":"https://impact.com/privacy-policy/"},{"id":447,"name":"Adludio Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.adludio.com/privacy-policy/"},{"id":410,"name":"Adtelligent Inc.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://adtelligent.com/privacy-policy/"},{"id":137,"name":"Str\u00f6er SSP GmbH (DSP)","purposeIds":[],"legIntPurposeIds":[1,2,3],"featureIds":[],"policyUrl":"https://www.stroeer.de/fileadmin/de/Konvergenz_und_Konzepte/Daten_und_Technologien/Stroeer_SSP/Downloads/Datenschutz_Stroeer_SSP.pdf"},{"id":395,"name":"PREX Programmatic Exchange GmbH&amp;Co KG","purposeIds":[],"legIntPurposeIds":[1,2,3,5],"featureIds":[],"policyUrl":"http://www.programmatic-exchange.com/privacy","deletedDate":"2020-07-03T00:00:00Z"},{"id":462,"name":"Bidstack Limited","purposeIds":[1,2,5],"legIntPurposeIds":[3,4],"featureIds":[2],"policyUrl":"https://www.bidstack.com/privacy-policy/"},{"id":466,"name":"TACTIC\u2122 Real-Time Marketing AS","purposeIds":[],"legIntPurposeIds":[4,5],"featureIds":[],"policyUrl":"https://tacticrealtime.com/privacy/"},{"id":340,"name":"Yieldr UK","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"https://www.yieldr.com/privacy"},{"id":336,"name":"Telecoming S.A.","purposeIds":[3,4],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"http://www.telecoming.com/privacy-policy/"},{"id":430,"name":"Ad Unity Ltd","purposeIds":[],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"http://www.adunity.com/privacy-policy.html","deletedDate":"2019-08-13T00:00:00Z"},{"id":346,"name":"Cybba, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://cybba.com/about/legal/data-processing-agreement/","deletedDate":"2020-08-03T00:00:00Z"},{"id":469,"name":"Zeta Global","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://zetaglobal.com/privacy-policy/"},{"id":440,"name":"DEFINE MEDIA GMBH","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"http://www.definemedia.de/datenschutz-conative/"},{"id":375,"name":"Affle International","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://affle.com/privacy-policy "},{"id":196,"name":"AdElement Media Solutions Pvt Ltd","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[3],"policyUrl":"http://adelement.com/privacy-policy.html"},{"id":268,"name":"Social Tokens Ltd. ","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://woobi.com/privacy/","deletedDate":"2019-10-02T00:00:00Z"},{"id":475,"name":"TAPTAP Digital SL","purposeIds":[1,2,3],"legIntPurposeIds":[4,5],"featureIds":[1,2,3],"policyUrl":"http://www.taptapnetworks.com/privacy_policy/"},{"id":474,"name":"hbfsTech","purposeIds":[],"legIntPurposeIds":[1,2],"featureIds":[],"policyUrl":"http://www.hbfstech.com/fr/privacy.html"},{"id":448,"name":"Targetspot Belgium SPRL","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://marketing.targetspot.com/Targetspot/Legal/TargetSpot%20Privacy%20Policy%20-%20June%202018.pdf"},{"id":428,"name":"Internet BillBoard a.s.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"http://www.ibillboard.com/en/privacy-information/"},{"id":461,"name":"B2B Media Group EMEA GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.selfcampaign.com/static/privacy","deletedDate":"2019-08-14T00:00:00Z"},{"id":476,"name":"HIRO Media Ltd","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1],"policyUrl":"http://hiro-media.com/privacy.php"},{"id":480,"name":"pilotx.tv","purposeIds":[2,3],"legIntPurposeIds":[1,4,5],"featureIds":[1,2,3],"policyUrl":"https://pilotx.tv/privacy/"},{"id":366,"name":"CerebroAd.com s.r.o.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.cerebroad.com/privacy-policy","deletedDate":"2019-10-02T00:00:00Z"},{"id":392,"name":"Str\u00f6er Mobile Performance GmbH","purposeIds":[2,3,4],"legIntPurposeIds":[1,5],"featureIds":[3],"policyUrl":"https://stroeermobileperformance.com/?dl=privacy"},{"id":357,"name":"Totaljobs Group Ltd ","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.totaljobs.com/privacy-policy"},{"id":486,"name":"Madington","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[],"policyUrl":"https://delivered-by-madington.com/dat-privacy-policy/"},{"id":468,"name":"NeuStar, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,5],"featureIds":[1,2],"policyUrl":"https://www.home.neustar/privacy"},{"id":458,"name":"AdColony, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1],"policyUrl":"adcolony.com/privacy-policy/"},{"id":489,"name":"YellowHammer Media Group","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.yhmg.com/privacy-policy/","deletedDate":"2019-11-27T00:00:00Z"},{"id":293,"name":"SpringServe, LLC","purposeIds":[],"legIntPurposeIds":[1,3],"featureIds":[],"policyUrl":"https://springserve.com/privacy-policy/"},{"id":484,"name":"STRIATUM SAS","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://adledge.com/data-privacy/"},{"id":493,"name":"Carbon (AI) Limited","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[2],"policyUrl":"https://carbonrmp.com/privacy.html"},{"id":495,"name":"Arcspire Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://public.arcspire.io/privacy.pdf"},{"id":496,"name":"Automattic Inc.","purposeIds":[1,2,3,4],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://en.blog.wordpress.com/2017/12/04/updated-privacy-policy/"},{"id":424,"name":"KUPONA GmbH","purposeIds":[],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://www.kupona.de/dsgvo/"},{"id":408,"name":"Fidelity Media","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[3],"policyUrl":"https://fidelity-media.com/privacy-policy/"},{"id":473,"name":"Sub2 Technologies Ltd","purposeIds":[3,4,5],"legIntPurposeIds":[1,2],"featureIds":[],"policyUrl":"http://www.sub2tech.com/privacy-policy/"},{"id":467,"name":"Haensel AMS GmbH","purposeIds":[3,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://haensel-ams.com/data-privacy/"},{"id":490,"name":"PLAYGROUND XYZ EMEA LTD","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://playground.xyz/privacy"},{"id":464,"name":"Oracle AddThis","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[2],"policyUrl":"http://www.addthis.com/privacy/privacy-policy/","deletedDate":"2020-02-12T00:00:00Z"},{"id":491,"name":"Triboo Data Analytics","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.shinystat.com/it/informativa_privacy_generale.html"},{"id":499,"name":"PurposeLab, LLC","purposeIds":[5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://purposelab.com/privacy/","deletedDate":"2019-10-02T00:00:00Z"},{"id":502,"name":"NEXD","purposeIds":[5],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://nexd.com/privacy-policy"},{"id":465,"name":"Schibsted Product and Tech UK","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://www.schibsted.com/","deletedDate":"2019-07-26T00:00:00Z"},{"id":497,"name":"Little Big Data sp.z.o.o.","purposeIds":[1,2,4],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://dtxngr.com/legal/"},{"id":492,"name":"LotaData, Inc.","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[1],"policyUrl":"https://lotadata.com/privacy_policy","deletedDate":"2019-10-02T00:00:00Z"},{"id":512,"name":"PubNative GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://pubnative.net/privacy-notice/"},{"id":471,"name":"FlexOffers.com, LLC","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.flexoffers.com/privacy-policy/","deletedDate":"2019-11-18T00:00:00Z"},{"id":494,"name":"Cablato Limited","purposeIds":[1,2,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://cablato.com/privacy","deletedDate":"2019-10-02T00:00:00Z"},{"id":516,"name":"Pexi B.V.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://pexi.nl/privacy-policy/"},{"id":507,"name":"AdsWizz Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,5],"featureIds":[1,2,3],"policyUrl":"https://www.adswizz.com/our-privacy-policy/"},{"id":482,"name":"UberMedia, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://ubermedia.com/summary-of-privacy-policy/"},{"id":505,"name":"Shopalyst Inc","purposeIds":[1,2],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.shortlyst.com/eu/privacy_terms.html"},{"id":517,"name":"SunMedia ","purposeIds":[1,2],"legIntPurposeIds":[3],"featureIds":[2],"policyUrl":"https://www.sunmedia.tv/en/cookies"},{"id":518,"name":"Accelerize Inc.","purposeIds":[1],"legIntPurposeIds":[5],"featureIds":[2,3],"policyUrl":"https://getcake.com/privacy-policy/","deletedDate":"2020-07-17T00:00:00Z"},{"id":511,"name":"Admixer EU GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://admixer.com/privacy/"},{"id":479,"name":"INFINIA MOBILE S.L.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.infiniamobile.com/privacy_policy"},{"id":513,"name":"Shopstyle","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.shopstyle.co.uk/privacy","deletedDate":"2019-10-02T00:00:00Z"},{"id":509,"name":"ATG Ad Tech Group GmbH","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://ad-tech-group.com/privacy-policy/"},{"id":521,"name":"netzeffekt GmbH","purposeIds":[1],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"https://www.netzeffekt.de/en/imprint"},{"id":487,"name":"nugg.ad GmbH","purposeIds":[],"legIntPurposeIds":[1,2,3,5],"featureIds":[1],"policyUrl":"https://www.nugg.ad/en/privacy/general-information.html","deletedDate":"2019-10-03T00:00:00Z"},{"id":515,"name":"ZighZag","purposeIds":[1,3],"legIntPurposeIds":[5],"featureIds":[1,2],"policyUrl":"https://zighzag.com/privacy"},{"id":520,"name":"ChannelSight ","purposeIds":[1],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.channelsight.com/privacypolicy/"},{"id":524,"name":"The Ozone Project Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://ozoneproject.com/privacy-policy"},{"id":529,"name":"Fidzup","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.fidzup.com/en/privacy/","deletedDate":"2019-11-18T00:00:00Z"},{"id":528,"name":"Kayzen","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://kayzen.io/data-privacy-policy"},{"id":527,"name":"Jampp LTD","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://jampp.com/privacy.html"},{"id":506,"name":"salesforce.com, inc.","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.salesforce.com/company/privacy/"},{"id":534,"name":"SmartyAds Inc.","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://smartyads.com/privacy-policy"},{"id":535,"name":"INNITY","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.innity.com/privacy-policy.php"},{"id":514,"name":"Uprival LLC","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://uprival.com/privacy-policy/","deletedDate":"2020-01-21T00:00:00Z"},{"id":522,"name":"Tealium Inc.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://tealium.com/privacy-policy/"},{"id":530,"name":"Near Pte Ltd","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://near.co/privacy"},{"id":539,"name":"AdDefend GmbH","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://www.addefend.com/en/privacy-policy/"},{"id":501,"name":"Alliance Gravity Data Media","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.alliancegravity.com/politiquedeprotectiondesdonneespersonnelles"},{"id":519,"name":"Chargeads","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.chargeplatform.com/privacy"},{"id":523,"name":"X-Mode Social, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://xmode.io/privacy-policy.html"},{"id":537,"name":"RUN, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"http://www.runads.com/privacy-policy"},{"id":531,"name":"Smartclip Hispania SL","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://rgpd-smartclip.com/"},{"id":536,"name":"GlobalWebIndex","purposeIds":[1],"legIntPurposeIds":[3,5],"featureIds":[1,2],"policyUrl":"http://legal.trendstream.net/non-panellist_privacy_policy"},{"id":542,"name":"Densou Trading Desk ApS","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://densou.dk/Policy.html","deletedDate":"2020-01-21T00:00:00Z"},{"id":525,"name":"PUB OCEAN LIMITED","purposeIds":[1],"legIntPurposeIds":[5],"featureIds":[3],"policyUrl":"https://rta.pubocean.com/privacy-policy/","deletedDate":"2019-10-03T00:00:00Z"},{"id":544,"name":"Kochava Inc.","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[1,2],"policyUrl":"https://www.kochava.com/support-privacy/"},{"id":543,"name":"PaperG, Inc. dba Thunder Industries","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[],"policyUrl":"https://www.makethunder.com/privacy"},{"id":334,"name":"Cydersoft","purposeIds":[],"legIntPurposeIds":[1,2,3,4],"featureIds":[2,3],"policyUrl":"http://www.videmob.com/privacy.html"},{"id":551,"name":"Illuma Technology Limited","purposeIds":[1],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://www.weareilluma.com/endddd","deletedDate":"2019-11-14T00:00:00Z"},{"id":540,"name":"Tunnl BV","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://tunnl.com/privacy.html","deletedDate":"2019-12-20T00:00:00Z"},{"id":547,"name":"Video Reach","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.videoreach.de/about/privacy-policy/","deletedDate":"2020-01-21T00:00:00Z"},{"id":546,"name":"Smart Traffik","purposeIds":[1],"legIntPurposeIds":[5],"featureIds":[1,2,3],"policyUrl":"https://okube-attribution.com/politique-de-confidentialite/"},{"id":541,"name":"DeepIntent, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[3],"policyUrl":"https://www.deepintent.com/privacypolicy"},{"id":545,"name":"Reignn Platform Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://reignn.com/user-privacy-policy"},{"id":439,"name":"Bit Q Holdings Limited","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.rippll.com/privacy"},{"id":553,"name":"Adhese","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://adhese.com/privacy-and-cookie-policy"},{"id":556,"name":"adhood.com","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://v3.adhood.com/en/site/politikavekurallar/gizlilik.php?lang=en"},{"id":550,"name":"Happydemics","purposeIds":[5],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"https://www.iubenda.com/privacy-policy/69056167/full-legal"},{"id":560,"name":"Leiki Ltd.","purposeIds":[1,2,3],"legIntPurposeIds":[4],"featureIds":[],"policyUrl":"http://www.leiki.com/privacy","deletedDate":"2020-01-07T00:00:00Z"},{"id":554,"name":"RMSi Radio Marketing Service interactive GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.rms.de/datenschutz/"},{"id":498,"name":"Dr. Banner","purposeIds":[1],"legIntPurposeIds":[3],"featureIds":[3],"policyUrl":"https://drbanner.com/privacypolicy_en/"},{"id":565,"name":"Adobe Audience Manager","purposeIds":[1,2,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.adobe.com/privacy/policy.html"},{"id":118,"name":"Drawbridge, Inc.","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://www.drawbridge.com/privacy/","deletedDate":"2020-03-06T00:00:00Z"},{"id":572,"name":"CHEQ AI TECHNOLOGIES LTD.","purposeIds":[1],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"http://www.cheq.ai/privacy"},{"id":571,"name":"ViewPay","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://viewpay.tv/mentions-legales/"},{"id":568,"name":"Jointag S.r.l.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.jointag.com/privacy/kariboo/publisher/third/"},{"id":570,"name":"Czech Publisher Exchange z.s.p.o.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.cpex.cz/pro-uzivatele/ochrana-soukromi/"},{"id":559,"name":"Otto (GmbH &amp; Co KG)","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[2],"policyUrl":"https://www.otto.de/shoppages/service/datenschutz"},{"id":548,"name":"LBC France","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.leboncoin.fr/dc/cookies","deletedDate":"2020-04-23T00:00:00Z"},{"id":569,"name":"Kairos Fire","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"https://www.kairosfire.com/privacy"},{"id":577,"name":"Neustar on behalf of The Procter & Gamble Company","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.pg.com/privacy/english/privacy_statement.shtml"},{"id":590,"name":"Sourcepoint Technologies, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://www.sourcepoint.com/privacy-policy"},{"id":587,"name":"Localsensor B.V.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[],"policyUrl":"https://www.localsensor.com/privacy.html"},{"id":578,"name":"MAIRDUMONT NETLETIX GmbH&amp;Co. KG","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://mairdumont-netletix.com/datenschutz"},{"id":580,"name":"Goldbach Group AG","purposeIds":[],"legIntPurposeIds":[1,2,3,5],"featureIds":[1,2,3],"policyUrl":"https://goldbach.com/ch/de/datenschutz"},{"id":593,"name":"Programatica de publicidad S.L.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://datmean.com/politica-privacidad/"},{"id":574,"name":"Realeyes OU","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://realview.realeyesit.com/privacy"},{"id":581,"name":"Mobilewalla, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.mobilewalla.com/business-services-privacy-policy"},{"id":598,"name":"audio content &amp; control GmbH","purposeIds":[1],"legIntPurposeIds":[3,4,5],"featureIds":[3],"policyUrl":"https://www.audio-cc.com/audiocc_privacy_policy.pdf"},{"id":596,"name":"InsurAds Technologies SA.","purposeIds":[3],"legIntPurposeIds":[5],"featureIds":[3],"policyUrl":"https://www.insurads.com/privacy.html"},{"id":576,"name":"StartApp Inc.","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[3],"policyUrl":"https://www.startapp.com/policy/privacy-policy/","deletedDate":"2020-04-23T00:00:00Z"},{"id":592,"name":"Colpirio.com","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://privacy-policy.colpirio.com/en/","deletedDate":"2020-03-18T00:00:00Z"},{"id":549,"name":"Bandsintown Amplified LLC","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://corp.bandsintown.com/privacy"},{"id":597,"name":"Better Banners A/S","purposeIds":[],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"https://betterbanners.com/en/privacy"},{"id":601,"name":"WebAds B.V","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://privacy.webads.eu/"},{"id":599,"name":"Maximus Live LLC","purposeIds":[],"legIntPurposeIds":[3,4,5],"featureIds":[3],"policyUrl":"https://maximusx.com/privacy-policy/"},{"id":604,"name":"Join","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.teamjoin.fr/privacy.html","deletedDate":"2020-04-23T00:00:00Z"},{"id":606,"name":"Impactify ","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[],"policyUrl":"https://impactify.io/privacy-policy/"},{"id":608,"name":"News and Media Holding, a.s.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.newsandmedia.sk/gdpr/"},{"id":602,"name":"Online Solution Int Limited","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[1,2],"policyUrl":"https://adsafety.net/privacy.html"},{"id":612,"name":"Adnami Aps","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.adnami.io/privacy","deletedDate":"2020-03-17T00:00:00Z"},{"id":591,"name":"Consumable, Inc.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://consumable.com/privacy-policy.html"},{"id":614,"name":"Market Resource Partners LLC","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.mrpfd.com/privacy-policy/"},{"id":615,"name":"Adsolutions BV","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.adsolutions.com/privacy-policy/"},{"id":607,"name":"ucfunnel Co., Ltd.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.ucfunnel.com/privacy-policy"},{"id":609,"name":"Predicio","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.predic.io/privacy"},{"id":617,"name":"Onfocus (Adagio)","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://adagio.io/privacy"},{"id":620,"name":"Blue","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[2,3],"policyUrl":"http://www.getblue.io/privacy/"},{"id":610,"name":"Azerion Holding B.V.","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[2,3],"policyUrl":"https://azerion.com/business/privacy.html"},{"id":621,"name":"Seznam.cz, a.s.","purposeIds":[2,3,4],"legIntPurposeIds":[1,5],"featureIds":[2,3],"policyUrl":"https://www.seznam.cz/ochranaudaju"},{"id":624,"name":"Norstat AS","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.norstatpanel.com/en/data-protection"},{"id":623,"name":"Adprime Media Inc. ","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://adprimehealth.com/privacy/","deletedDate":"2020-06-17T00:00:00Z"},{"id":95,"name":"Lotame Solutions, inc","purposeIds":[],"legIntPurposeIds":[1,2],"featureIds":[2],"policyUrl":"https://www.lotame.com/about-lotame/privacy/lotame-corporate-websites-privacy-policy/"},{"id":618,"name":"BEINTOO SPA","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://www.beintoo.com/privacy-cookie-policy/"},{"id":619,"name":"Capitaldata","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.capitaldata.fr/privacy"},{"id":625,"name":"BILENDI SA","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.maximiles.com/privacy-policy"},{"id":628,"name":": Tappx","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.tappx.com/en/privacy-policy/"},{"id":626,"name":"Hivestack Inc.","purposeIds":[],"legIntPurposeIds":[1],"featureIds":[3],"policyUrl":"https://hivestack.com/privacy-policy"},{"id":631,"name":"Relay42 Netherlands B.V.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://relay42.com/privacy"},{"id":627,"name":"D-Edge","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.d-edge.com/privacy-policy/","deletedDate":"2020-07-06T00:00:00Z"},{"id":644,"name":"Gamoshi LTD","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.gamoshi.com/privacy-policy"},{"id":639,"name":"Smile Wanted Group","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.smilewanted.com/privacy.php"},{"id":635,"name":"WebMediaRM","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.webmediarm.com/vie_privee_et_opposition_en.php"},{"id":579,"name":"Ve Global","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.ve.com/privacy-policy"},{"id":645,"name":"Noster Finance S.L.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.finect.com/terminos-legales/politica-de-cookies"},{"id":653,"name":"Smartme Analytics","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[1],"policyUrl":"http://smartmeapp.com/info/smartme/aviso_legal.php","deletedDate":"2020-07-03T00:00:00Z"},{"id":613,"name":"Adserve.zone / Artworx AS","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://adserve.zone/adserveprivacypolicy.html"},{"id":573,"name":"Dailymotion SA","purposeIds":[2,3,4,5],"legIntPurposeIds":[1],"featureIds":[2],"policyUrl":"https://www.dailymotion.com/legal/privacy"},{"id":652,"name":"Skaze","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[1,2,3],"policyUrl":"http://www.skaze.fr/rgpd/"},{"id":646,"name":"Notify","purposeIds":[1,2],"legIntPurposeIds":[5],"featureIds":[1],"policyUrl":"https://notify-group.com/en/mentions-legales/"},{"id":648,"name":"TrueData Solutions, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.truedata.co/privacy-policy/"},{"id":647,"name":"Axel Springer Teaser Ad GmbH","purposeIds":[2],"legIntPurposeIds":[1,3,5],"featureIds":[],"policyUrl":"https://www.adup-tech.com/privacy"},{"id":654,"name":"GRAPHINIUM","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.graphinium.com/privacy/"},{"id":659,"name":"Research and Analysis of Media in Sweden AB","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www2.rampanel.com/privacy-policy/"},{"id":656,"name":"Think Clever Media","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.contentignite.com/privacy-policy/"},{"id":504,"name":"Alive & Kicking Global Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.mcsaatchiplc.com/legal/privacy-cookies","deletedDate":"2020-07-27T00:00:00Z"},{"id":657,"name":"GP One GmbH","purposeIds":[],"legIntPurposeIds":[1,3,5],"featureIds":[3],"policyUrl":"https://www.gsi-one.org/de/privacy-policy.html"},{"id":655,"name":"Sportradar AG","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.sportradar.com/about-us/privacy/"},{"id":662,"name":"SoundCast","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://soundcast.fm/en/data-privacy"},{"id":665,"name":"Digital East GmbH","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.digitaleast.mobi/en/legal/privacy-policy/"},{"id":650,"name":"Telefonica Investigaci\u00f3n y Desarrollo S.A.U","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.cognitivemarketing.tid.es/"},{"id":666,"name":"BeOp","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[],"policyUrl":"https://beop.io/privacy"},{"id":663,"name":"Mobsuccess","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://www.mobsuccess.com/en/privacy"},{"id":658,"name":"BLIINK SAS","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://bliink.io/privacy-policy"},{"id":667,"name":"Liftoff Mobile, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[3],"policyUrl":"https://liftoff.io/privacy-policy/"},{"id":668,"name":"WhatRocks Inc. ","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.whatrocks.co/en/privacy-policy "},{"id":670,"name":"Timehop, Inc.","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.timehop.com/privacy"},{"id":674,"name":"Duration Media, LLC.","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.durationmedia.net/privacy-policy"},{"id":675,"name":"Instreamatic inc.","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://instreamatic.com/privacy-policy/"},{"id":676,"name":"BusinessClick","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.businessclick.com/documents/RegulaminProgramuBusinessClick-2019.pdf"},{"id":677,"name":"Intercept Interactive Inc. dba Undertone","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.undertone.com/privacy/"},{"id":660,"name":"Schibsted Norge AS","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[2,3],"policyUrl":"https://static.vg.no/privacy/","deletedDate":"2019-09-16T00:00:00Z"},{"id":673,"name":"TTNET AS","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://www.programattik.com/en/privacy-policy.aspx"},{"id":664,"name":"adMarketplace, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"https://www.admarketplace.com/privacy-policy/"},{"id":671,"name":"Mediaforce LTD","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"http://casino.mindthebet.co.uk/themes/mindthebetv2-casino/privacy.php"},{"id":561,"name":"AuDigent","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://audigent.com/platform-privacy-policy"},{"id":682,"name":"Radio Net Media Limited","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[1,2,3],"policyUrl":"https://www.adtonos.com/service-privacy-policy/"},{"id":684,"name":"Blue Billywig BV","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.bluebillywig.com/privacy-statement/"},{"id":686,"name":"The MediaGrid Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.themediagrid.com/privacy-policy/"},{"id":685,"name":"Arkeero","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://arkeero.com/privacy-2/"},{"id":687,"name":"MISSENA","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://missena.com/confidentialite/"},{"id":690,"name":"Go.pl sp. z o.o.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://go.pl/polityka-prywatnosci/"},{"id":691,"name":"Lifesight Pte. Ltd.","purposeIds":[1,2,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.lifesight.io/privacy-policy/"},{"id":697,"name":"ADWAYS SAS","purposeIds":[],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://www.adways.com/confidentialite/?lang=en"},{"id":681,"name":"MyTraffic","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.mytraffic.io/en/privacy"},{"id":649,"name":"adality GmbH","purposeIds":[],"legIntPurposeIds":[1,2],"featureIds":[1],"policyUrl":"https://adality.de/en/privacy/"},{"id":712,"name":"Inspired Mobile Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://byinspired.com/privacypolicy.pdf"},{"id":688,"name":"Effinity","purposeIds":[],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://www.effiliation.com/politique-de-confidentialite/"},{"id":702,"name":"Kwanko","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.kwanko.com/fr/rgpd/"},{"id":715,"name":"BidBerry SRL","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://www.bidberrymedia.com/privacy-policy/"},{"id":713,"name":"Dataseat Ltd","purposeIds":[2,5],"legIntPurposeIds":[1,3,4],"featureIds":[],"policyUrl":"https://dataseat.com/privacy-policy"},{"id":716,"name":"OnAudience Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.onaudience.com/internet-advertising-privacy-policy"},{"id":708,"name":"Dugout Limited ","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://dugout.com/privacy-policy"},{"id":717,"name":"Audience Network","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://www.en.audiencenetwork.pl/internet-advertising-privacy-policy"},{"id":718,"name":"AppConsent Xchange","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://appconsent.io/en/privacy-policy"},{"id":720,"name":"AAX LLC","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[3],"policyUrl":"https://aax.media/privacy/"},{"id":678,"name":"Axonix LTD","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://axonix.com/privacy-cookie-policy/"},{"id":719,"name":"Online Advertising Network Sp. z o.o.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.oan.pl/en/privacy-policy"},{"id":707,"name":"Dentsu Aegis Network Italia SpA","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.dentsuaegisnetwork.com/it/it/policies/info-cookie"},{"id":721,"name":"Beaconspark Ltd","purposeIds":[1,2,3],"legIntPurposeIds":[4,5],"featureIds":[1],"policyUrl":"https://www.engageya.com/privacy"},{"id":724,"name":"Between Exchange","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[2,3],"policyUrl":"https://en.betweenx.com/pdata.pdf"},{"id":728,"name":"Appier PTE Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.appier.com/privacy-policy/"},{"id":729,"name":"Cavai AS & UK ","purposeIds":[],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://cav.ai/privacy-policy/"},{"id":723,"name":"Adzymic Pte Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://www.adzymic.co/privacy"},{"id":737,"name":"Monet Engine Inc","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://appmonet.com/privacy-policy/"},{"id":740,"name":"6Sense Insights, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2],"policyUrl":"https://6sense.com/privacy-policy/"},{"id":744,"name":"Vidazoo Ltd","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[2],"policyUrl":"https://vidazoo.gitbook.io/vidazoo-legal/privacy-policy"},{"id":731,"name":"GeistM Technologies LTD","purposeIds":[],"legIntPurposeIds":[3,4,5],"featureIds":[],"policyUrl":"https://www.geistm.com/privacy"},{"id":741,"name":"Brand Advance Limited","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.wearebrandadvance.com/website-privacy-policy"},{"id":734,"name":"Cint AB","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://www.cint.com/participant-privacy-notice"},{"id":709,"name":"NC Audience Exchange, LLC (NewsIQ)","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[1,2],"policyUrl":"https://www.ncaudienceexchange.com/privacy/"},{"id":739,"name":"Blingby LLC","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://blingby.com/privacy"},{"id":732,"name":"Performax.cz, s.r.o.","purposeIds":[2,4,5],"legIntPurposeIds":[1,3],"featureIds":[2,3],"policyUrl":"https://reg.tiscali.cz/privacy-policy"},{"id":736,"name":"BidMachine Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://explorestack.com/privacy-policy/"},{"id":738,"name":"adbility media GmbH","purposeIds":[2,3],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.adbility-media.com/datenschutzerklaerung/"},{"id":742,"name":"Audiencerate LTD","purposeIds":[],"legIntPurposeIds":[1,2,5],"featureIds":[],"policyUrl":"https://www.audiencerate.com/privacy/"},{"id":743,"name":"MOVIads Sp. z o.o. Sp. k.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://moviads.pl/polityka-prywatnosci/"},{"id":746,"name":"Adxperience SAS","purposeIds":[2],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"https://adxperience.com/privacy-policy/"},{"id":747,"name":"Kairion GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://kairion.de/datenschutzbestimmungen/"},{"id":748,"name":"AUDIOMOB LTD","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.audiomob.io/privacy"},{"id":749,"name":"Good-Loop Ltd","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[],"policyUrl":"https://doc.good-loop.com/policy/privacy-policy.html"},{"id":754,"name":"DistroScale, Inc.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.distroscale.com/privacy-policy/"},{"id":756,"name":"Fandom, Inc.","purposeIds":[3],"legIntPurposeIds":[1,2,4,5],"featureIds":[],"policyUrl":"https://www.fandom.com/privacy-policy"},{"id":758,"name":"GfK Netherlands B.V.","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://gfkpanel.nl/privacy"},{"id":759,"name":"RevJet","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.revjet.com/privacy"},{"id":760,"name":"VEXPRO TECHNOLOGIES LTD","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://onedash.com/privacy-policy.html"},{"id":761,"name":"Digiseg ApS","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://digiseg.io/privacy-center/"},{"id":763,"name":"Delidatax SL","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.delidatax.net/privacy.htm"},{"id":764,"name":"Lucidity","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://golucidity.com/privacy-policy/"},{"id":765,"name":"Grabit Interactive Media Inc dba KERV Interctive","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://kervit.com/privacy-policy/"},{"id":766,"name":"ADCELL | Firstlead GmbH","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://www.adcell.de/agb#sector_6"},{"id":768,"name":"Global Media & Entertainment Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://global.com/privacy-policy/"},{"id":770,"name":"MARKETPERF CORP","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[2,3],"policyUrl":"https://www.marketperf.com/assets/images/app/marketperf/pdf/privacy-policy.pdf"},{"id":773,"name":"360e-com Sp. z o.o.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.clickonometrics.com/optout/"},{"id":775,"name":"SelectMedia International LTD","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.selectmedia.asia/terms-and-privacy/"},{"id":778,"name":"Discover-Tech ltd","purposeIds":[2,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://discover-tech.io/dsp-privacy-policy/"},{"id":779,"name":"Adtarget Medya A.S.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://adtarget.com.tr/adtarget-privacy-policy-2020.pdf"},{"id":780,"name":"Aniview LTD","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.aniview.com/privacy-policy/"},{"id":781,"name":"FeedAd GmbH","purposeIds":[2,3,4],"legIntPurposeIds":[1,5],"featureIds":[2,3],"policyUrl":"https://feedad.com/privacy/"},{"id":784,"name":"Nubo LTD","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[],"policyUrl":"https://www.recod3.com/privacypolicy.php"},{"id":786,"name":"TargetVideo GmbH","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://www.target-video.com/datenschutz/"},{"id":798,"name":"Adverticum cPlc.","purposeIds":[2,3,4],"legIntPurposeIds":[1,5],"featureIds":[],"policyUrl":"https://adverticum.net/english/privacy-and-data-processing-information/"},{"id":803,"name":"Click Tech Limited","purposeIds":[1],"legIntPurposeIds":[2,3],"featureIds":[1],"policyUrl":"https://en.yeahmobi.com/html/privacypolicy/"}]}
\ No newline at end of file
diff --git a/util/task/ticker_task.go b/util/task/ticker_task.go
new file mode 100644
index 00000000..a8d523b7
--- /dev/null
+++ b/util/task/ticker_task.go
@@ -0,0 +1,53 @@
+package task
+
+import (
+	"time"
+)
+
+type Runner interface {
+	Run() error
+}
+
+type TickerTask struct {
+	interval time.Duration
+	runner   Runner
+	done     chan struct{}
+}
+
+func NewTickerTask(interval time.Duration, runner Runner) *TickerTask {
+	return &TickerTask{
+		interval: interval,
+		runner:   runner,
+		done:     make(chan struct{}),
+	}
+}
+
+// Start runs the task immediately and then schedules the task to run periodically
+// if a positive fetching interval has been specified.
+func (t *TickerTask) Start() {
+	t.runner.Run()
+
+	if t.interval > 0 {
+		go t.runRecurring()
+	}
+}
+
+// Stop stops the periodic task but the task runner maintains state
+func (t *TickerTask) Stop() {
+	close(t.done)
+}
+
+// run creates a ticker that ticks at the specified interval. On each tick,
+// the task is executed
+func (t *TickerTask) runRecurring() {
+	ticker := time.NewTicker(t.interval)
+
+	for {
+		select {
+		case <-ticker.C:
+			t.runner.Run()
+		case <-t.done:
+			return
+		}
+	}
+}
diff --git a/util/timeutil/time.go b/util/timeutil/time.go
new file mode 100644
index 00000000..e8eaae7d
--- /dev/null
+++ b/util/timeutil/time.go
@@ -0,0 +1,16 @@
+package timeutil
+
+import (
+	"time"
+)
+
+type Time interface {
+	Now() time.Time
+}
+
+// RealTime wraps the time package for testability
+type RealTime struct{}
+
+func (c *RealTime) Now() time.Time {
+	return time.Now()
+}


Test patch:
diff --git a/currencies/rate_converter_test.go b/currencies/rate_converter_test.go
index d717d1a3..7beb9523 100644
--- a/currencies/rate_converter_test.go
+++ b/currencies/rate_converter_test.go
@@ -1,4 +1,4 @@
-package currencies_test
+package currencies
 
 import (
 	"io/ioutil"
@@ -9,7 +9,7 @@ import (
 	"testing"
 	"time"
 
-	"github.com/prebid/prebid-server/currencies"
+	"github.com/prebid/prebid-server/util/task"
 	"github.com/stretchr/testify/assert"
 )
 
@@ -27,423 +27,148 @@ func getMockRates() []byte {
 	}`)
 }
 
-func TestFetch_Success(t *testing.T) {
-
-	// Setup:
-	calledURLs := []string{}
-	mockedHttpServer := httptest.NewServer(http.HandlerFunc(
-		func(rw http.ResponseWriter, req *http.Request) {
-			calledURLs = append(calledURLs, req.RequestURI)
-			rw.WriteHeader(http.StatusOK)
-			rw.Write([]byte(getMockRates()))
-		}),
-	)
-
-	defer mockedHttpServer.Close()
-
-	expectedRates := &currencies.Rates{
-		DataAsOf: time.Date(2018, time.September, 12, 0, 0, 0, 0, time.UTC),
-		Conversions: map[string]map[string]float64{
-			"USD": {
-				"GBP": 0.77208,
-			},
-			"GBP": {
-				"USD": 1.2952,
-			},
-		},
-	}
-
-	// Execute:
-	beforeExecution := time.Now()
-	currencyConverter := currencies.NewRateConverter(
-		&http.Client{},
-		mockedHttpServer.URL,
-		time.Duration(24)*time.Hour,
-		time.Duration(24)*time.Hour,
-	)
-
-	// Verify:
-	assert.Equal(t, 1, len(calledURLs), "sync URL should have been called %d times but was %d", 1, len(calledURLs))
-	assert.NotEqual(t, currencyConverter.LastUpdated(), (time.Time{}), "LastUpdated() should return a time set")
-	assert.True(t, currencyConverter.LastUpdated().After(beforeExecution), "LastUpdated() should be after last update")
-	rates := currencyConverter.Rates()
-	assert.NotNil(t, rates, "Rates() should not return nil")
-	assert.Equal(t, expectedRates, rates, "Rates() doesn't return expected rates")
-	assert.NotNil(t, currencyConverter.GetInfo(), "GetInfo() should not return nil")
-}
-
-func TestFetch_Fail404(t *testing.T) {
-
-	// Setup:
-	calledURLs := []string{}
-	mockedHttpServer := httptest.NewServer(http.HandlerFunc(
-		func(rw http.ResponseWriter, req *http.Request) {
-			calledURLs = append(calledURLs, req.RequestURI)
-			rw.WriteHeader(http.StatusNotFound)
-		}),
-	)
-
-	defer mockedHttpServer.Close()
-
-	// Execute:
-	currencyConverter := currencies.NewRateConverter(
-		&http.Client{},
-		mockedHttpServer.URL,
-		time.Duration(24)*time.Hour,
-		time.Duration(24)*time.Hour,
-	)
-
-	// Verify:
-	assert.Equal(t, 1, len(calledURLs), "sync URL should have been called %d times but was %d", 1, len(calledURLs))
-	assert.Equal(t, currencyConverter.LastUpdated(), (time.Time{}), "LastUpdated() shouldn't return a time set")
-	assert.Equal(t, currencyConverter.Rates(), &currencies.ConstantRates{}, "Rates() should return constant rates")
-	assert.NotNil(t, currencyConverter.GetInfo(), "GetInfo() should not return nil")
+// FakeTime implements the Time interface
+type FakeTime struct {
+	time time.Time
 }
 
-func TestFetch_FailErrorHttpClient(t *testing.T) {
-
-	// Setup:
-	calledURLs := []string{}
-	mockedHttpServer := httptest.NewServer(http.HandlerFunc(
-		func(rw http.ResponseWriter, req *http.Request) {
-			calledURLs = append(calledURLs, req.RequestURI)
-			rw.WriteHeader(http.StatusBadRequest)
-		}),
-	)
-
-	defer mockedHttpServer.Close()
-
-	// Execute:
-	currencyConverter := currencies.NewRateConverter(
-		&http.Client{},
-		mockedHttpServer.URL,
-		time.Duration(24)*time.Hour,
-		time.Duration(24)*time.Hour,
-	)
-
-	// Verify:
-	assert.Equal(t, 1, len(calledURLs), "sync URL should have been called %d times but was %d", 1, len(calledURLs))
-	assert.Equal(t, currencyConverter.LastUpdated(), (time.Time{}), "LastUpdated() shouldn't return a time set")
-	assert.Equal(t, currencyConverter.Rates(), &currencies.ConstantRates{}, "Rates() should return constant rates")
-	assert.NotNil(t, currencyConverter.GetInfo(), "GetInfo() should not return nil")
+func (mc *FakeTime) Now() time.Time {
+	return mc.time
 }
 
-func TestFetch_FailBadSyncURL(t *testing.T) {
-
-	// Setup:
-
-	// Execute:
-	currencyConverter := currencies.NewRateConverter(
-		&http.Client{},
-		"justaweirdurl",
-		time.Duration(24)*time.Hour,
-		time.Duration(24)*time.Hour,
-	)
-
-	// Verify:
-	assert.Equal(t, currencyConverter.LastUpdated(), (time.Time{}), "LastUpdated() shouldn't return a time set")
-	assert.Equal(t, currencyConverter.Rates(), &currencies.ConstantRates{}, "Rates() should return constant rates")
-	assert.NotNil(t, currencyConverter.GetInfo(), "GetInfo() should not return nil")
-}
-
-func TestFetch_FailBadJSON(t *testing.T) {
-
-	// Setup:
-	calledURLs := []string{}
-	mockedHttpServer := httptest.NewServer(http.HandlerFunc(
-		func(rw http.ResponseWriter, req *http.Request) {
-			calledURLs = append(calledURLs, req.RequestURI)
-			rw.WriteHeader(http.StatusOK)
-			rw.Write([]byte(
-				`{
-					"dataAsOf":"2018-09-12",
-					"conversions":{
-						"USD":{
-							"GBP":0.77208
-						},
-						"GBP":{
-							"USD":1.2952
-						},
-						"badJsonHere"
-					}
-				}`,
-			))
-		}),
-	)
-
-	defer mockedHttpServer.Close()
-
-	// Execute:
-	currencyConverter := currencies.NewRateConverter(
-		&http.Client{},
-		mockedHttpServer.URL,
-		time.Duration(24)*time.Hour,
-		time.Duration(24)*time.Hour,
-	)
-
-	// Verify:
-	assert.Equal(t, 1, len(calledURLs), "sync URL should have been called %d times but was %d", 1, len(calledURLs))
-	assert.Equal(t, currencyConverter.LastUpdated(), (time.Time{}), "LastUpdated() shouldn't return a time set")
-	assert.Equal(t, currencyConverter.Rates(), &currencies.ConstantRates{}, "Rates() should return constant rates")
-	assert.NotNil(t, currencyConverter.GetInfo(), "GetInfo() should not return nil")
-}
-
-func TestFetch_InvalidRemoteResponseContent(t *testing.T) {
-
-	// Setup:
-	calledURLs := []string{}
-	mockedHttpServer := httptest.NewServer(http.HandlerFunc(
-		func(rw http.ResponseWriter, req *http.Request) {
-			calledURLs = append(calledURLs, req.RequestURI)
-			rw.WriteHeader(http.StatusOK)
-			rw.Write(nil)
-		}),
-	)
-
-	defer mockedHttpServer.Close()
-
-	// Execute:
-	currencyConverter := currencies.NewRateConverter(
-		&http.Client{},
-		mockedHttpServer.URL,
-		time.Duration(24)*time.Hour,
-		time.Duration(24)*time.Hour,
-	)
-
-	// Verify:
-	assert.Equal(t, 1, len(calledURLs), "sync URL should have been called %d times but was %d", 1, len(calledURLs))
-	assert.Equal(t, currencyConverter.LastUpdated(), (time.Time{}), "LastUpdated() shouldn't return a time set")
-	assert.Equal(t, currencyConverter.Rates(), &currencies.ConstantRates{}, "Rates() should return constant rates")
-	assert.NotNil(t, currencyConverter.GetInfo(), "GetInfo() should not return nil")
-}
-
-func TestInit(t *testing.T) {
-
-	// Setup:
-	mockedHttpServer := httptest.NewServer(http.HandlerFunc(
-		func(rw http.ResponseWriter, req *http.Request) {
-			rw.WriteHeader(http.StatusOK)
-			rw.Write([]byte(getMockRates()))
-		}),
-	)
-
-	// Execute:
-	expectedTicks := 5
-	ticksTimes := []time.Time{}
-	ticks := make(chan int)
-	currencyConverter := currencies.NewRateConverterWithNotifier(
-		&http.Client{},
-		mockedHttpServer.URL,
-		time.Duration(100)*time.Millisecond,
-		time.Duration(24)*time.Hour,
-		ticks,
-	)
+func TestReadWriteRates(t *testing.T) {
+	// Setup
+	mockServerHandler := func(mockResponse []byte, mockStatus int) http.Handler {
+		return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
+			rw.WriteHeader(mockStatus)
+			rw.Write([]byte(mockResponse))
+		})
+	}
 
-	// Verify:
-	expectedIntervalDuration := time.Duration(100) * time.Millisecond
-	errorMargin := 0.1 // 10% error margin
-	expectedRates := &currencies.Rates{
-		DataAsOf: time.Date(2018, time.September, 12, 0, 0, 0, 0, time.UTC),
-		Conversions: map[string]map[string]float64{
-			"USD": {
-				"GBP": 0.77208,
-			},
-			"GBP": {
-				"USD": 1.2952,
-			},
+	tests := []struct {
+		description       string
+		giveFakeTime      time.Time
+		giveMockUrl       string
+		giveMockResponse  []byte
+		giveMockStatus    int
+		wantUpdateErr     bool
+		wantConstantRates bool
+		wantLastUpdated   time.Time
+		wantDataAsOf      time.Time
+		wantConversions   map[string]map[string]float64
+	}{
+		{
+			description:      "Fetching currency rates successfully",
+			giveFakeTime:     time.Date(2018, time.September, 12, 30, 0, 0, 0, time.UTC),
+			giveMockResponse: getMockRates(),
+			giveMockStatus:   200,
+			wantLastUpdated:  time.Date(2018, time.September, 12, 30, 0, 0, 0, time.UTC),
+			wantDataAsOf:     time.Date(2018, time.September, 12, 0, 0, 0, 0, time.UTC),
+			wantConversions:  map[string]map[string]float64{"USD": {"GBP": 0.77208}, "GBP": {"USD": 1.2952}},
+		},
+		{
+			description:      "Currency rates endpoint returns empty response",
+			giveFakeTime:     time.Date(2018, time.September, 12, 30, 0, 0, 0, time.UTC),
+			giveMockResponse: []byte("{}"),
+			giveMockStatus:   200,
+			wantLastUpdated:  time.Date(2018, time.September, 12, 30, 0, 0, 0, time.UTC),
+			wantDataAsOf:     time.Time{},
+			wantConversions:  nil,
+		},
+		{
+			description:       "Currency rates endpoint returns nil response",
+			giveFakeTime:      time.Date(2018, time.September, 12, 30, 0, 0, 0, time.UTC),
+			giveMockResponse:  nil,
+			giveMockStatus:    200,
+			wantUpdateErr:     true,
+			wantConstantRates: true,
+			wantLastUpdated:   time.Time{},
+		},
+		{
+			description:       "Currency rates endpoint returns non-2xx status code",
+			giveFakeTime:      time.Date(2018, time.September, 12, 30, 0, 0, 0, time.UTC),
+			giveMockResponse:  []byte(`{"message": "Not Found"}`),
+			giveMockStatus:    404,
+			wantUpdateErr:     true,
+			wantConstantRates: true,
+			wantLastUpdated:   time.Time{},
+		},
+		{
+			description:       "Currency rates endpoint returns invalid json response",
+			giveFakeTime:      time.Date(2018, time.September, 12, 30, 0, 0, 0, time.UTC),
+			giveMockResponse:  []byte(`{"message": Invalid-JSON-No-Surrounding-Quotes}`),
+			giveMockStatus:    200,
+			wantUpdateErr:     true,
+			wantConstantRates: true,
+			wantLastUpdated:   time.Time{},
+		},
+		{
+			description:       "Currency rates endpoint url is invalid",
+			giveFakeTime:      time.Date(2018, time.September, 12, 30, 0, 0, 0, time.UTC),
+			giveMockUrl:       "invalidurl",
+			giveMockResponse:  getMockRates(),
+			giveMockStatus:    200,
+			wantUpdateErr:     true,
+			wantConstantRates: true,
+			wantLastUpdated:   time.Time{},
 		},
 	}
 
-	// At each ticks, do couple checks
-	for ticksCount := range ticks {
-		ticksTimes = append(ticksTimes, time.Now())
-		if len(ticksTimes) > 1 {
-			intervalDuration := ticksTimes[len(ticksTimes)-1].Truncate(time.Millisecond).Sub(ticksTimes[len(ticksTimes)-2].Truncate(time.Millisecond))
-			intervalDiff := float64(float64(intervalDuration.Nanoseconds()) / float64(expectedIntervalDuration.Nanoseconds()))
-			assert.False(t, intervalDiff > float64(errorMargin*100), "Interval between ticks should be: %d but was: %d", expectedIntervalDuration, intervalDuration)
-		}
-
-		assert.NotEqual(t, currencyConverter.LastUpdated(), (time.Time{}), "LastUpdated should be set")
-		assert.Equal(t, expectedRates, currencyConverter.Rates(), "Conversions.Rates weren't the expected ones")
-		assert.NotNil(t, currencyConverter.GetInfo(), "GetInfo() should not return nil")
+	for _, tt := range tests {
+		mockedHttpServer := httptest.NewServer(mockServerHandler(tt.giveMockResponse, tt.giveMockStatus))
+		defer mockedHttpServer.Close()
 
-		if ticksCount == expectedTicks {
-			currencyConverter.StopPeriodicFetching()
-			return
+		var url string
+		if len(tt.giveMockUrl) > 0 {
+			url = tt.giveMockUrl
+		} else {
+			url = mockedHttpServer.URL
 		}
-	}
-}
-
-func TestStop(t *testing.T) {
-
-	// Setup:
-	calledURLs := []string{}
-	mockedHttpServer := httptest.NewServer(http.HandlerFunc(
-		func(rw http.ResponseWriter, req *http.Request) {
-			calledURLs = append(calledURLs, req.RequestURI)
-			rw.WriteHeader(http.StatusOK)
-			rw.Write([]byte(getMockRates()))
-		}),
-	)
-
-	// Execute:
-	expectedTicks := 2
-	ticks := make(chan int)
-	currencyConverter := currencies.NewRateConverterWithNotifier(
-		&http.Client{},
-		mockedHttpServer.URL,
-		time.Duration(100)*time.Millisecond,
-		time.Duration(24)*time.Hour,
-		ticks,
-	)
-
-	// Let the currency converter fetch 5 times before stopping it
-	for ticksCount := range ticks {
-		if ticksCount == expectedTicks {
-			currencyConverter.StopPeriodicFetching()
-			break
+		currencyConverter := NewRateConverter(
+			&http.Client{},
+			url,
+			24*time.Hour,
+		)
+		currencyConverter.time = &FakeTime{time: tt.giveFakeTime}
+		err := currencyConverter.Run()
+
+		if tt.wantUpdateErr {
+			assert.NotNil(t, err)
+		} else {
+			assert.Nil(t, err)
 		}
-	}
-	lastFetched := time.Now()
-
-	// Verify:
-	// Check for the next 1 second that no fetch was triggered
-	time.Sleep(1 * time.Second)
-
-	assert.False(t, currencyConverter.LastUpdated().After(lastFetched), "LastUpdated() shouldn't be after `lastFetched` since the periodic fetching is stopped")
-}
-
-func TestInitWithZeroDuration(t *testing.T) {
-
-	// Setup:
-	calledURLs := []string{}
-	mockedHttpServer := httptest.NewServer(http.HandlerFunc(
-		func(rw http.ResponseWriter, req *http.Request) {
-			calledURLs = append(calledURLs, req.RequestURI)
-			rw.WriteHeader(http.StatusOK)
-			rw.Write([]byte(getMockRates()))
-		}),
-	)
-
-	// Execute:
-	currencyConverter := currencies.NewRateConverter(
-		&http.Client{},
-		mockedHttpServer.URL,
-		time.Duration(0),
-		time.Duration(24)*time.Hour,
-	)
-
-	// Verify:
-	// Check for the next 1 second that no fetch was triggered
-	time.Sleep(1 * time.Second)
-
-	assert.Equal(t, 0, len(calledURLs), "sync URL shouldn't have been called but was called %d times", 0, len(calledURLs))
-	assert.Equal(t, (time.Time{}), currencyConverter.LastUpdated(), "LastUpdated() shouldn't be set")
-	assert.Equal(t, currencyConverter.Rates(), &currencies.ConstantRates{}, "Rates() should return constant rates")
-	assert.NotNil(t, currencyConverter.GetInfo(), "GetInfo() should not return nil")
-}
-
-func TestRates(t *testing.T) {
-
-	// Setup:
-	testCases := []struct {
-		from         string
-		to           string
-		expectedRate float64
-		hasError     bool
-	}{
-		{from: "USD", to: "GBP", expectedRate: 0.77208, hasError: false},
-		{from: "GBP", to: "USD", expectedRate: 1.2952, hasError: false},
-		{from: "GBP", to: "EUR", expectedRate: 0, hasError: true},
-		{from: "CNY", to: "EUR", expectedRate: 0, hasError: true},
-		{from: "", to: "EUR", expectedRate: 0, hasError: true},
-		{from: "CNY", to: "", expectedRate: 0, hasError: true},
-		{from: "", to: "", expectedRate: 0, hasError: true},
-		{from: "USD", to: "USD", expectedRate: 1, hasError: false},
-	}
-
-	mockedHttpServer := httptest.NewServer(http.HandlerFunc(
-		func(rw http.ResponseWriter, req *http.Request) {
-			rw.WriteHeader(http.StatusOK)
-			rw.Write([]byte(getMockRates()))
-		}),
-	)
-
-	// Execute:
-	ticks := make(chan int)
-	currencyConverter := currencies.NewRateConverterWithNotifier(
-		&http.Client{},
-		mockedHttpServer.URL,
-		time.Duration(100)*time.Millisecond,
-		time.Duration(24)*time.Hour,
-		ticks,
-	)
-	rates := currencyConverter.Rates()
 
-	// Let the currency converter ticks 1 time before to stop it
-	select {
-	case <-ticks:
-		currencyConverter.StopPeriodicFetching()
-	}
-
-	// Verify:
-	assert.NotNil(t, rates, "rates shouldn't be nil")
-	for _, tc := range testCases {
-		rate, err := rates.GetRate(tc.from, tc.to)
-
-		if tc.hasError {
-			assert.NotNil(t, err, "err shouldn't be nil")
-			assert.Equal(t, float64(0), rate, "rate should be 0")
+		if tt.wantConstantRates {
+			assert.Equal(t, currencyConverter.Rates(), &ConstantRates{}, tt.description)
 		} else {
-			assert.Nil(t, err, "err should be nil")
-			assert.Equal(t, tc.expectedRate, rate, "rate doesn't match the expected one")
+			rates := currencyConverter.Rates().(*Rates)
+			assert.Equal(t, tt.wantConversions, (*rates).Conversions, tt.description)
+			assert.Equal(t, tt.wantDataAsOf, (*rates).DataAsOf, tt.description)
 		}
-	}
-}
-
-func TestRates_EmptyRates(t *testing.T) {
 
-	// Setup:
-	mockedHttpServer := httptest.NewServer(http.HandlerFunc(
-		func(rw http.ResponseWriter, req *http.Request) {
-			rw.WriteHeader(http.StatusOK)
-			rw.Write([]byte(""))
-		}),
-	)
-
-	// Execute:
-	// Will try to fetch directly on method call but will fail
-	currencyConverter := currencies.NewRateConverter(
-		&http.Client{},
-		mockedHttpServer.URL,
-		time.Duration(100)*time.Millisecond,
-		time.Duration(24)*time.Hour,
-	)
-	defer currencyConverter.StopPeriodicFetching()
-
-	// Verify:
-	assert.Equal(t, currencyConverter.Rates(), &currencies.ConstantRates{}, "Rates() should return constant rates")
+		lastUpdated := currencyConverter.LastUpdated()
+		assert.Equal(t, tt.wantLastUpdated, lastUpdated, tt.description)
+	}
 }
 
-func TestSelectRatesBasedOnStaleness(t *testing.T) {
-	calledURLs := []string{}
-	callCnt := 0
+func TestRateStaleness(t *testing.T) {
+	callCount := 0
 	mockedHttpServer := httptest.NewServer(http.HandlerFunc(
 		func(rw http.ResponseWriter, req *http.Request) {
-			calledURLs = append(calledURLs, req.RequestURI)
-			if callCnt == 0 || callCnt >= 5 {
+			callCount++
+			if callCount == 2 || callCount >= 5 {
 				rw.WriteHeader(http.StatusOK)
 				rw.Write([]byte(getMockRates()))
 			} else {
 				rw.WriteHeader(http.StatusNotFound)
+				rw.Write([]byte(`{"message": "Not Found"}`))
 			}
-			callCnt++
 		}),
 	)
 
 	defer mockedHttpServer.Close()
 
-	expectedRates := &currencies.Rates{
+	expectedRates := &Rates{
 		DataAsOf: time.Date(2018, time.September, 12, 0, 0, 0, 0, time.UTC),
 		Conversions: map[string]map[string]float64{
 			"USD": {
@@ -455,97 +180,83 @@ func TestSelectRatesBasedOnStaleness(t *testing.T) {
 		},
 	}
 
+	initialFakeTime := time.Date(2018, time.September, 12, 30, 0, 0, 0, time.UTC)
+	fakeTime := &FakeTime{time: initialFakeTime}
+
 	// Execute:
-	currencyConverter := currencies.NewRateConverter(
+	currencyConverter := NewRateConverter(
 		&http.Client{},
 		mockedHttpServer.URL,
-		time.Duration(100)*time.Millisecond,
-		time.Duration(200)*time.Millisecond,
+		30*time.Second, // stale rates threshold
 	)
+	currencyConverter.time = fakeTime
 
-	// Verify:
-	// Rates are valid at t=0, then invalid for 500ms before being valid again
-	assert.Equal(t, currencyConverter.Rates(), expectedRates, "Rates() should return expected rates")
+	// First Update call results in error
+	err1 := currencyConverter.Run()
+	assert.NotNil(t, err1)
 
-	time.Sleep(100 * time.Millisecond)
-	// Rates have been invalid for ~100ms, rates not stale yet
-	assert.Equal(t, currencyConverter.Rates(), expectedRates, "Rates() should return expected rates")
+	// Verify constant rates are used and last update ts is not set
+	assert.Equal(t, &ConstantRates{}, currencyConverter.Rates(), "Rates should return constant rates")
+	assert.Equal(t, time.Time{}, currencyConverter.LastUpdated(), "LastUpdated return is incorrect")
 
-	time.Sleep(200 * time.Millisecond)
-	// Rates have been invalid for ~300ms, rates are stale
-	assert.Equal(t, currencyConverter.Rates(), &currencies.ConstantRates{}, "Rates() should return constant rates")
+	// Second Update call is successful and yields valid rates
+	err2 := currencyConverter.Run()
+	assert.Nil(t, err2)
 
-	time.Sleep(300 * time.Millisecond)
-	// Rates have been valid again for ~100ms
-	assert.Equal(t, currencyConverter.Rates(), expectedRates, "Rates() should return expected rates")
-}
+	// Verify rates are valid and last update timestamp is set
+	assert.Equal(t, expectedRates, currencyConverter.Rates(), "Conversions.Rates weren't the expected ones")
+	assert.Equal(t, initialFakeTime, currencyConverter.LastUpdated(), "LastUpdated should be set")
 
-func TestUseConstantRatesUntilFetchIsSuccessful(t *testing.T) {
-	callCnt := 0
-	mockedHttpServer := httptest.NewServer(http.HandlerFunc(
-		func(rw http.ResponseWriter, req *http.Request) {
-			if callCnt >= 5 {
-				rw.WriteHeader(http.StatusOK)
-				rw.Write([]byte(getMockRates()))
-			} else {
-				rw.WriteHeader(http.StatusNotFound)
-			}
-			callCnt++
-		}),
-	)
+	// Advance time so the rates fall just short of being considered stale
+	fakeTime.time = fakeTime.time.Add(29 * time.Second)
 
-	defer mockedHttpServer.Close()
+	// Third Update call results in error but stale rate threshold has not been exceeded
+	err3 := currencyConverter.Run()
+	assert.NotNil(t, err3)
 
-	expectedRates := &currencies.Rates{
-		DataAsOf: time.Date(2018, time.September, 12, 0, 0, 0, 0, time.UTC),
-		Conversions: map[string]map[string]float64{
-			"USD": {
-				"GBP": 0.77208,
-			},
-			"GBP": {
-				"USD": 1.2952,
-			},
-		},
-	}
+	// Verify rates are valid and last update ts has not changed
+	assert.Equal(t, expectedRates, currencyConverter.Rates(), "Conversions.Rates weren't the expected ones")
+	assert.Equal(t, initialFakeTime, currencyConverter.LastUpdated(), "LastUpdated should be set")
 
-	// Execute:
-	currencyConverter := currencies.NewRateConverter(
-		&http.Client{},
-		mockedHttpServer.URL,
-		time.Duration(100)*time.Millisecond,
-		time.Duration(1)*time.Second,
-	)
+	// Advance time just past the threshold so the rates are considered stale
+	fakeTime.time = fakeTime.time.Add(2 * time.Second)
+
+	// Fourth Update call results in error and stale rate threshold has been exceeded
+	err4 := currencyConverter.Run()
+	assert.NotNil(t, err4)
 
-	// Verify:
-	// Rates are invalid at t=0 and remain invalid until 500ms have elapsed
-	assert.Equal(t, currencyConverter.Rates(), &currencies.ConstantRates{}, "Rates() should return constant rates")
+	// Verify constant rates are used and last update ts has not changed
+	assert.Equal(t, &ConstantRates{}, currencyConverter.Rates(), "Rates should return constant rates")
+	assert.Equal(t, initialFakeTime, currencyConverter.LastUpdated(), "LastUpdated return is incorrect")
 
-	time.Sleep(400 * time.Millisecond)
-	// Rates have been invalid for ~400ms
-	assert.Equal(t, currencyConverter.Rates(), &currencies.ConstantRates{}, "Rates() should return constant rates")
+	// Fifth Update call is successful and yields valid rates
+	err5 := currencyConverter.Run()
+	assert.Nil(t, err5)
 
-	time.Sleep(200 * time.Millisecond)
-	// Rates have been valid for ~100ms
-	assert.Equal(t, currencyConverter.Rates(), expectedRates, "Rates() should return expected rates")
+	// Verify rates are valid and last update ts has changed
+	thirtyOneSec := 31 * time.Second
+	assert.Equal(t, expectedRates, currencyConverter.Rates(), "Conversions.Rates weren't the expected ones")
+	assert.Equal(t, (initialFakeTime.Add(thirtyOneSec)), currencyConverter.LastUpdated(), "LastUpdated should be set")
 }
 
-func TestRatesAreNeverStale(t *testing.T) {
-	callCnt := 0
+func TestRatesAreNeverConsideredStale(t *testing.T) {
+	callCount := 0
 	mockedHttpServer := httptest.NewServer(http.HandlerFunc(
 		func(rw http.ResponseWriter, req *http.Request) {
-			if callCnt == 0 {
+			callCount++
+			if callCount == 1 {
 				rw.WriteHeader(http.StatusOK)
 				rw.Write([]byte(getMockRates()))
 			} else {
 				rw.WriteHeader(http.StatusNotFound)
+				rw.Write([]byte(`{"message": "Not Found"}`))
 			}
-			callCnt++
 		}),
 	)
 
 	defer mockedHttpServer.Close()
 
-	expectedRates := &currencies.Rates{
+	expectedRates := &Rates{
 		DataAsOf: time.Date(2018, time.September, 12, 0, 0, 0, 0, time.UTC),
 		Conversions: map[string]map[string]float64{
 			"USD": {
@@ -557,25 +268,38 @@ func TestRatesAreNeverStale(t *testing.T) {
 		},
 	}
 
+	initialFakeTime := time.Date(2018, time.September, 12, 30, 0, 0, 0, time.UTC)
+	fakeTime := &FakeTime{time: initialFakeTime}
+
 	// Execute:
-	currencyConverter := currencies.NewRateConverter(
+	currencyConverter := NewRateConverter(
 		&http.Client{},
 		mockedHttpServer.URL,
-		time.Duration(100)*time.Millisecond,
-		time.Duration(0)*time.Millisecond,
+		0*time.Millisecond, // stale rates threshold
 	)
+	currencyConverter.time = fakeTime
+
+	// First Update call is successful and yields valid rates
+	err1 := currencyConverter.Run()
+	assert.Nil(t, err1)
+
+	// Verify rates are valid and last update timestamp is correct
+	assert.Equal(t, expectedRates, currencyConverter.Rates(), "Conversions.Rates weren't the expected ones")
+	assert.Equal(t, fakeTime.time, currencyConverter.LastUpdated(), "LastUpdated should be set")
+
+	// Advance time so the current time is well past the the time the rates were last updated
+	fakeTime.time = initialFakeTime.Add(24 * time.Hour)
 
-	// Verify:
-	// Rates are valid at t=0 and are then invalid at 100ms
-	assert.Equal(t, currencyConverter.Rates(), expectedRates, "Rates() should return expected rates")
+	// Second Update call results in error but rates from a day ago are still valid
+	err2 := currencyConverter.Run()
+	assert.NotNil(t, err2)
 
-	time.Sleep(500 * time.Millisecond)
-	// Rates have been invalid for ~400ms
-	assert.Equal(t, currencyConverter.Rates(), expectedRates, "Rates() should return expected rates")
+	// Verify rates are valid and last update ts is correct
+	assert.Equal(t, expectedRates, currencyConverter.Rates(), "Conversions.Rates weren't the expected ones")
+	assert.Equal(t, initialFakeTime, currencyConverter.LastUpdated(), "LastUpdated should be set")
 }
 
 func TestRace(t *testing.T) {
-
 	// This test is checking that no race conditions appear in rate converter.
 	// It simulate multiple clients (in different goroutines) asking for updates
 	// and rates while the rate converter is also updating periodically.
@@ -599,20 +323,19 @@ func TestRace(t *testing.T) {
 	}
 
 	// Execute:
-
-	// Create a rate converter which will be fetching new values every 10 ms
-	currencyConverter := currencies.NewRateConverter(
+	// Create a rate converter which will be fetching new values every 1 ms
+	interval := 1 * time.Millisecond
+	currencyConverter := NewRateConverter(
 		mockedHttpClient,
 		"currency.fake.com",
-		time.Duration(10)*time.Millisecond,
-		time.Duration(24)*time.Hour,
+		24*time.Hour,
 	)
-	defer currencyConverter.StopPeriodicFetching()
+	ticker := task.NewTickerTask(interval, currencyConverter)
+	ticker.Start()
+	defer ticker.Stop()
 
-	// Create 50 clients asking for updates and rates conversion at random intervals
-	// from 1ms to 50ms for 10 seconds
 	var wg sync.WaitGroup
-	clientsCount := 50
+	clientsCount := 10
 	wg.Add(clientsCount)
 	dones := make([]chan bool, clientsCount)
 
@@ -623,12 +346,9 @@ func TestRace(t *testing.T) {
 			clientTicker := time.NewTicker(randomTickInterval)
 			for {
 				select {
-				case tickTime := <-clientTicker.C:
-					// Either ask for an Update() or for GetRate()
-					// based on the tick ms
-					tickMs := tickTime.UnixNano() / int64(time.Millisecond)
-					if tickMs%2 == 0 {
-						err := currencyConverter.Update()
+				case <-clientTicker.C:
+					if clientNum < 5 {
+						err := currencyConverter.Run()
 						assert.Nil(t, err)
 					} else {
 						rate, err := currencyConverter.Rates().GetRate("USD", "GBP")
@@ -643,7 +363,7 @@ func TestRace(t *testing.T) {
 		}(dones[c], c)
 	}
 
-	time.Sleep(10 * time.Second)
+	time.Sleep(100 * time.Millisecond)
 	// Sending stop signals to all clients
 	for i := range dones {
 		dones[i] <- true
diff --git a/currencies/rates_test.go b/currencies/rates_test.go
index 915b817d..5b1c4497 100644
--- a/currencies/rates_test.go
+++ b/currencies/rates_test.go
@@ -146,6 +146,7 @@ func TestGetRate(t *testing.T) {
 		{from: "", to: "EUR", expectedRate: 0, hasError: true},
 		{from: "CNY", to: "", expectedRate: 0, hasError: true},
 		{from: "", to: "", expectedRate: 0, hasError: true},
+		{from: "USD", to: "USD", expectedRate: 1, hasError: false},
 	}
 
 	for _, tc := range testCases {
diff --git a/endpoints/auction_test.go b/endpoints/auction_test.go
index 5e9e9639..028f1196 100644
--- a/endpoints/auction_test.go
+++ b/endpoints/auction_test.go
@@ -408,6 +408,7 @@ type auctionMockPermissions struct {
 	allowHostCookies bool
 	allowPI          bool
 	allowGeo         bool
+	allowID          bool
 }
 
 func (m *auctionMockPermissions) HostCookiesAllowed(ctx context.Context, consent string) (bool, error) {
@@ -418,8 +419,8 @@ func (m *auctionMockPermissions) BidderSyncAllowed(ctx context.Context, bidder o
 	return m.allowBidderSync, nil
 }
 
-func (m *auctionMockPermissions) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, bool, error) {
-	return m.allowPI, m.allowGeo, nil
+func (m *auctionMockPermissions) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, bool, bool, error) {
+	return m.allowPI, m.allowGeo, m.allowID, nil
 }
 
 func (m *auctionMockPermissions) AMPException() bool {
diff --git a/endpoints/cookie_sync_test.go b/endpoints/cookie_sync_test.go
index 824e32f1..f7974d2b 100644
--- a/endpoints/cookie_sync_test.go
+++ b/endpoints/cookie_sync_test.go
@@ -254,8 +254,8 @@ func (g *gdprPerms) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.Bi
 	return ok, nil
 }
 
-func (g *gdprPerms) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, bool, error) {
-	return true, true, nil
+func (g *gdprPerms) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, bool, bool, error) {
+	return true, true, true, nil
 }
 
 func (g *gdprPerms) AMPException() bool {
diff --git a/endpoints/currency_rates_test.go b/endpoints/currency_rates_test.go
index e0b127fc..86c4e50f 100644
--- a/endpoints/currency_rates_test.go
+++ b/endpoints/currency_rates_test.go
@@ -14,20 +14,21 @@ import (
 func TestCurrencyRatesEndpoint(t *testing.T) {
 	// Setup:
 	var testCases = []struct {
-		input        rateConverter
-		expectedBody string
-		expectedCode int
-		description  string
+		inputConverter        rateConverter
+		inputFetchingInterval time.Duration
+		expectedBody          string
+		expectedCode          int
+		description           string
 	}{
 		{
 			nil,
+			time.Duration(0),
 			`{"active": false}`,
 			http.StatusOK,
 			"case 1 - rate converter is nil",
 		},
 		{
 			newRateConverterMock(
-				5*time.Minute,
 				"https://sync.test.com",
 				time.Date(2019, 3, 2, 12, 54, 56, 651387237, time.UTC),
 				newConversionMock(&map[string]map[string]float64{
@@ -36,6 +37,7 @@ func TestCurrencyRatesEndpoint(t *testing.T) {
 					},
 				}),
 			),
+			5 * time.Minute,
 			`{
 				"active": true,
 				"source": "https://sync.test.com",
@@ -52,11 +54,11 @@ func TestCurrencyRatesEndpoint(t *testing.T) {
 		},
 		{
 			newRateConverterMock(
-				time.Duration(0),
 				"",
 				time.Time{},
 				nil,
 			),
+			time.Duration(0),
 			`{
 				"active": true,
 				"source": "",
@@ -70,12 +72,14 @@ func TestCurrencyRatesEndpoint(t *testing.T) {
 			newRateConverterMockWithInfo(
 				newUnmarshableConverterInfoMock(),
 			),
+			time.Duration(0),
 			"",
 			http.StatusInternalServerError,
 			"case 4 - invalid rates input for marshaling",
 		},
 		{
 			newRateConverterMockWithNilInfo(),
+			time.Duration(0),
 			`{
 				"active": true
 			 }`,
@@ -86,7 +90,7 @@ func TestCurrencyRatesEndpoint(t *testing.T) {
 
 	for _, tc := range testCases {
 
-		handler := NewCurrencyRatesEndpoint(tc.input)
+		handler := NewCurrencyRatesEndpoint(tc.inputConverter, tc.inputFetchingInterval)
 		w := httptest.NewRecorder()
 
 		// Execute:
@@ -117,21 +121,16 @@ func newConversionMock(rates *map[string]map[string]float64) *conversionMock {
 }
 
 type converterInfoMock struct {
-	source           string
-	fetchingInterval time.Duration
-	lastUpdated      time.Time
-	rates            *map[string]map[string]float64
-	additionalInfo   interface{}
+	source         string
+	lastUpdated    time.Time
+	rates          *map[string]map[string]float64
+	additionalInfo interface{}
 }
 
 func (m converterInfoMock) Source() string {
 	return m.source
 }
 
-func (m converterInfoMock) FetchingInterval() time.Duration {
-	return m.fetchingInterval
-}
-
 func (m converterInfoMock) LastUpdated() time.Time {
 	return m.lastUpdated
 }
@@ -150,10 +149,6 @@ func (m unmarshableConverterInfoMock) Source() string {
 	return ""
 }
 
-func (m unmarshableConverterInfoMock) FetchingInterval() time.Duration {
-	return time.Duration(0)
-}
-
 func (m unmarshableConverterInfoMock) LastUpdated() time.Time {
 	return time.Time{}
 }
@@ -172,7 +167,6 @@ func newUnmarshableConverterInfoMock() unmarshableConverterInfoMock {
 }
 
 type rateConverterMock struct {
-	fetchingInterval    time.Duration
 	syncSourceURL       string
 	rates               *conversionMock
 	lastUpdated         time.Time
@@ -197,23 +191,20 @@ func (m rateConverterMock) GetInfo() currencies.ConverterInfo {
 		rates = m.rates.GetRates()
 	}
 	return converterInfoMock{
-		source:           m.syncSourceURL,
-		fetchingInterval: m.fetchingInterval,
-		lastUpdated:      m.lastUpdated,
-		rates:            rates,
+		source:      m.syncSourceURL,
+		lastUpdated: m.lastUpdated,
+		rates:       rates,
 	}
 }
 
 func newRateConverterMock(
-	fetchingInterval time.Duration,
 	syncSourceURL string,
 	lastUpdated time.Time,
 	rates *conversionMock) rateConverterMock {
 	return rateConverterMock{
-		fetchingInterval: fetchingInterval,
-		syncSourceURL:    syncSourceURL,
-		rates:            rates,
-		lastUpdated:      lastUpdated,
+		syncSourceURL: syncSourceURL,
+		rates:         rates,
+		lastUpdated:   lastUpdated,
 	}
 }
 
diff --git a/endpoints/openrtb2/auction_benchmark_test.go b/endpoints/openrtb2/auction_benchmark_test.go
index 93d7575e..fba0daec 100644
--- a/endpoints/openrtb2/auction_benchmark_test.go
+++ b/endpoints/openrtb2/auction_benchmark_test.go
@@ -5,6 +5,7 @@ import (
 	"net/http/httptest"
 	"strings"
 	"testing"
+	"time"
 
 	"github.com/prebid/prebid-server/adapters"
 	"github.com/prebid/prebid-server/currencies"
@@ -77,7 +78,7 @@ func BenchmarkOpenrtbEndpoint(b *testing.B) {
 			theMetrics,
 			infos,
 			gdpr.AlwaysAllow{},
-			currencies.NewRateConverterDefault(),
+			currencies.NewRateConverter(&http.Client{}, "", time.Duration(0)),
 		),
 		paramValidator,
 		empty_fetcher.EmptyFetcher{},
diff --git a/endpoints/openrtb2/video_auction_test.go b/endpoints/openrtb2/video_auction_test.go
index f29ac3bf..b15c6a7b 100644
--- a/endpoints/openrtb2/video_auction_test.go
+++ b/endpoints/openrtb2/video_auction_test.go
@@ -81,6 +81,10 @@ func TestVideoEndpointImpressionsDuration(t *testing.T) {
 		t.Fatalf("The request never made it into the Exchange.")
 	}
 
+	var extData openrtb_ext.ExtRequest
+	json.Unmarshal(ex.lastRequest.Ext, &extData)
+	assert.True(t, extData.Prebid.Targeting.IncludeBidderKeys, "Request ext incorrect: IncludeBidderKeys should be true ")
+
 	assert.Len(t, ex.lastRequest.Imp, 22, "Incorrect number of impressions in request")
 	assert.Equal(t, ex.lastRequest.Imp[0].ID, "1_0", "Incorrect impression id in request")
 	assert.Equal(t, ex.lastRequest.Imp[0].Video.MaxDuration, int64(15), "Incorrect impression max duration in request")
@@ -643,9 +647,9 @@ func TestVideoBuildVideoResponseMissedCacheForOneBid(t *testing.T) {
 	bid2 := openrtb.Bid{}
 	bid3 := openrtb.Bid{}
 
-	extBid1 := []byte(`{"prebid":{"targeting":{"hb_bidder":"appnexus","hb_pb":"17.00","hb_pb_cat_dur":"17.00_123_30s","hb_size":"1x1","hb_uuid":"837ea3b7-5598-4958-8c45-8e9ef2bf7cc1"}}}`)
-	extBid2 := []byte(`{"prebid":{"targeting":{"hb_bidder":"appnexus","hb_pb":"17.00","hb_pb_cat_dur":"17.00_456_30s","hb_size":"1x1","hb_uuid":"837ea3b7-5598-4958-8c45-8e9ef2bf7cc1"}}}`)
-	extBid3 := []byte(`{"prebid":{"targeting":{"hb_bidder":"appnexus","hb_pb":"17.00","hb_pb_cat_dur":"17.00_406_30s","hb_size":"1x1"}}}`)
+	extBid1 := []byte(`{"prebid":{"targeting":{"hb_bidder_appnexus":"appnexus","hb_pb_appnexus":"17.00","hb_pb_cat_dur_appnex":"17.00_123_30s","hb_size":"1x1","hb_uuid_appnexus":"837ea3b7-5598-4958-8c45-8e9ef2bf7cc1"}}}`)
+	extBid2 := []byte(`{"prebid":{"targeting":{"hb_bidder_appnexus":"appnexus","hb_pb_appnexus":"17.00","hb_pb_cat_dur_appnex":"17.00_456_30s","hb_size":"1x1","hb_uuid_appnexus":"837ea3b7-5598-4958-8c45-8e9ef2bf7cc1"}}}`)
+	extBid3 := []byte(`{"prebid":{"targeting":{"hb_bidder_appnexus":"appnexus","hb_pb_appnexus":"17.00","hb_pb_cat_dur_appnex":"17.00_406_30s","hb_size":"1x1"}}}`)
 
 	bid1.Ext = extBid1
 	bids = append(bids, bid1)
@@ -657,6 +661,7 @@ func TestVideoBuildVideoResponseMissedCacheForOneBid(t *testing.T) {
 	bids = append(bids, bid3)
 
 	seatBid.Bid = bids
+	seatBid.Seat = "appnexus"
 	seatBids = append(seatBids, seatBid)
 	openRtbBidResp.SeatBid = seatBids
 
@@ -713,8 +718,8 @@ func TestVideoBuildVideoResponsePodErrors(t *testing.T) {
 	bid1 := openrtb.Bid{}
 	bid2 := openrtb.Bid{}
 
-	extBid1 := []byte(`{"prebid":{"targeting":{"hb_bidder":"appnexus","hb_pb":"17.00","hb_pb_cat_dur":"17.00_123_30s","hb_size":"1x1","hb_uuid":"837ea3b7-5598-4958-8c45-8e9ef2bf7cc1"}}}`)
-	extBid2 := []byte(`{"prebid":{"targeting":{"hb_bidder":"appnexus","hb_pb":"17.00","hb_pb_cat_dur":"17.00_456_30s","hb_size":"1x1","hb_uuid":"837ea3b7-5598-4958-8c45-8e9ef2bf7cc1"}}}`)
+	extBid1 := []byte(`{"prebid":{"targeting":{"hb_bidder_appnexus":"appnexus","hb_pb_appnexus":"17.00","hb_pb_cat_dur_appnex":"17.00_123_30s","hb_size":"1x1","hb_uuid_appnexus":"837ea3b7-5598-4958-8c45-8e9ef2bf7cc1"}}}`)
+	extBid2 := []byte(`{"prebid":{"targeting":{"hb_bidder_appnexus":"appnexus","hb_pb_appnexus":"17.00","hb_pb_cat_dur_appnex":"17.00_456_30s","hb_size":"1x1","hb_uuid_appnexus":"837ea3b7-5598-4958-8c45-8e9ef2bf7cc1"}}}`)
 
 	bid1.Ext = extBid1
 	bids = append(bids, bid1)
@@ -723,6 +728,7 @@ func TestVideoBuildVideoResponsePodErrors(t *testing.T) {
 	bids = append(bids, bid2)
 
 	seatBid.Bid = bids
+	seatBid.Seat = "appnexus"
 	seatBids = append(seatBids, seatBid)
 	openRtbBidResp.SeatBid = seatBids
 
@@ -1107,6 +1113,16 @@ func TestCCPA(t *testing.T) {
 	}
 }
 
+func TestFormatTargetingKey(t *testing.T) {
+	res := formatTargetingKey(openrtb_ext.HbCategoryDurationKey, "appnexus")
+	assert.Equal(t, "hb_pb_cat_dur_appnex", res, "Tergeting key constructed incorrectly")
+}
+
+func TestFormatTargetingKeyLongKey(t *testing.T) {
+	res := formatTargetingKey(openrtb_ext.HbpbConstantKey, "20.00")
+	assert.Equal(t, "hb_pb_20.00", res, "Tergeting key constructed incorrectly")
+}
+
 func mockDepsWithMetrics(t *testing.T, ex *mockExchangeVideo) (*endpointDeps, *pbsmetrics.Metrics, *mockAnalyticsModule) {
 	theMetrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{})
 	mockModule := &mockAnalyticsModule{}
@@ -1205,9 +1221,10 @@ func (m *mockExchangeVideo) HoldAuction(ctx context.Context, bidRequest *openrtb
 	if debugLog != nil && debugLog.Enabled {
 		m.cache.called = true
 	}
-	ext := []byte(`{"prebid":{"targeting":{"hb_bidder":"appnexus","hb_pb":"20.00","hb_pb_cat_dur":"20.00_395_30s","hb_size":"1x1", "hb_uuid":"837ea3b7-5598-4958-8c45-8e9ef2bf7cc1"},"type":"video"},"bidder":{"appnexus":{"brand_id":1,"auction_id":7840037870526938650,"bidder_id":2,"bid_ad_type":1,"creative_info":{"video":{"duration":30,"mimes":["video\/mp4"]}}}}}`)
+	ext := []byte(`{"prebid":{"targeting":{"hb_bidder_appnexus":"appnexus","hb_pb_appnexus":"20.00","hb_pb_cat_dur_appnex":"20.00_395_30s","hb_size":"1x1", "hb_uuid_appnexus":"837ea3b7-5598-4958-8c45-8e9ef2bf7cc1"},"type":"video"},"bidder":{"appnexus":{"brand_id":1,"auction_id":7840037870526938650,"bidder_id":2,"bid_ad_type":1,"creative_info":{"video":{"duration":30,"mimes":["video\/mp4"]}}}}}`)
 	return &openrtb.BidResponse{
 		SeatBid: []openrtb.SeatBid{{
+			Seat: "appnexus",
 			Bid: []openrtb.Bid{
 				{ID: "01", ImpID: "1_0", Ext: ext},
 				{ID: "02", ImpID: "1_1", Ext: ext},
diff --git a/endpoints/setuid_test.go b/endpoints/setuid_test.go
index 3f47b257..e63944e2 100644
--- a/endpoints/setuid_test.go
+++ b/endpoints/setuid_test.go
@@ -437,8 +437,8 @@ func (g *mockPermsSetUID) BidderSyncAllowed(ctx context.Context, bidder openrtb_
 	return false, nil
 }
 
-func (g *mockPermsSetUID) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, bool, error) {
-	return g.allowPI, g.allowPI, nil
+func (g *mockPermsSetUID) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, bool, bool, error) {
+	return g.allowPI, g.allowPI, g.allowPI, nil
 }
 
 func (g *mockPermsSetUID) AMPException() bool {
diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go
index 7ae96c09..4f207cf5 100644
--- a/exchange/bidder_test.go
+++ b/exchange/bidder_test.go
@@ -75,7 +75,7 @@ func TestSingleBidder(t *testing.T) {
 		bidResponse: mockBidderResponse,
 	}
 	bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus)
-	currencyConverter := currencies.NewRateConverterDefault()
+	currencyConverter := currencies.NewRateConverter(&http.Client{}, "", time.Duration(0))
 	seatBid, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, "test", bidAdjustment, currencyConverter.Rates(), &adapters.ExtraRequestInfo{})
 
 	// Make sure the goodSingleBidder was called with the expected arguments.
@@ -163,7 +163,7 @@ func TestMultiBidder(t *testing.T) {
 		bidResponse: mockBidderResponse,
 	}
 	bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus)
-	currencyConverter := currencies.NewRateConverterDefault()
+	currencyConverter := currencies.NewRateConverter(&http.Client{}, "", time.Duration(0))
 	seatBid, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, "test", 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{})
 
 	if seatBid == nil {
@@ -528,9 +528,11 @@ func TestMultiCurrencies(t *testing.T) {
 		currencyConverter := currencies.NewRateConverter(
 			&http.Client{},
 			mockedHTTPServer.URL,
-			time.Duration(10)*time.Second,
 			time.Duration(24)*time.Hour,
 		)
+		time.Sleep(time.Duration(500) * time.Millisecond)
+		currencyConverter.Run()
+
 		seatBid, errs := bidder.requestBid(
 			context.Background(),
 			&openrtb.BidRequest{},
@@ -674,7 +676,7 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) {
 
 		// Execute:
 		bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus)
-		currencyConverter := currencies.NewRateConverterDefault()
+		currencyConverter := currencies.NewRateConverter(&http.Client{}, "", time.Duration(0))
 		seatBid, errs := bidder.requestBid(
 			context.Background(),
 			&openrtb.BidRequest{},
@@ -843,7 +845,6 @@ func TestMultiCurrencies_RequestCurrencyPick(t *testing.T) {
 		currencyConverter := currencies.NewRateConverter(
 			&http.Client{},
 			mockedHTTPServer.URL,
-			time.Duration(10)*time.Second,
 			time.Duration(24)*time.Hour,
 		)
 		seatBid, errs := bidder.requestBid(
@@ -1020,7 +1021,7 @@ func TestMobileNativeTypes(t *testing.T) {
 			bidResponse: tc.mockBidderResponse,
 		}
 		bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus)
-		currencyConverter := currencies.NewRateConverterDefault()
+		currencyConverter := currencies.NewRateConverter(&http.Client{}, "", time.Duration(0))
 
 		seatBids, _ := bidder.requestBid(
 			context.Background(),
@@ -1041,7 +1042,7 @@ func TestMobileNativeTypes(t *testing.T) {
 
 func TestErrorReporting(t *testing.T) {
 	bidder := adaptBidder(&bidRejector{}, nil, &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus)
-	currencyConverter := currencies.NewRateConverterDefault()
+	currencyConverter := currencies.NewRateConverter(&http.Client{}, "", time.Duration(0))
 	bids, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, "test", 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{})
 	if bids != nil {
 		t.Errorf("There should be no seatbid if no http requests are returned.")
@@ -1224,7 +1225,8 @@ func TestCallRecordAdapterConnections(t *testing.T) {
 
 	// Run requestBid using an http.Client with a mock handler
 	bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, metrics, openrtb_ext.BidderAppnexus)
-	_, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, "test", bidAdjustment, currencies.NewRateConverterDefault().Rates(), &adapters.ExtraRequestInfo{})
+	currencyConverter := currencies.NewRateConverter(&http.Client{}, "", time.Duration(0))
+	_, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, "test", bidAdjustment, currencyConverter.Rates(), &adapters.ExtraRequestInfo{})
 
 	// Assert no errors
 	assert.Equal(t, 0, len(errs), "bidder.requestBid returned errors %v \n", errs)
diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go
index 7da7b62e..545f04fd 100644
--- a/exchange/exchange_test.go
+++ b/exchange/exchange_test.go
@@ -50,7 +50,8 @@ func TestNewExchange(t *testing.T) {
 		Adapters: blankAdapterConfig(openrtb_ext.BidderList()),
 	}
 
-	e := NewExchange(server.Client(), nil, cfg, pbsmetrics.NewMetrics(metrics.NewRegistry(), knownAdapters, config.DisabledMetrics{}), adapters.ParseBidderInfos(cfg.Adapters, "../static/bidder-info", openrtb_ext.BidderList()), gdpr.AlwaysAllow{}, currencies.NewRateConverterDefault()).(*exchange)
+	currencyConverter := currencies.NewRateConverter(&http.Client{}, "", time.Duration(0))
+	e := NewExchange(server.Client(), nil, cfg, pbsmetrics.NewMetrics(metrics.NewRegistry(), knownAdapters, config.DisabledMetrics{}), adapters.ParseBidderInfos(cfg.Adapters, "../static/bidder-info", openrtb_ext.BidderList()), gdpr.AlwaysAllow{}, currencyConverter).(*exchange)
 	for _, bidderName := range knownAdapters {
 		if _, ok := e.adapterMap[bidderName]; !ok {
 			t.Errorf("NewExchange produced an Exchange without bidder %s", bidderName)
@@ -87,7 +88,8 @@ func TestCharacterEscape(t *testing.T) {
 	server := httptest.NewServer(http.HandlerFunc(handlerNoBidServer))
 	defer server.Close()
 
-	e := NewExchange(server.Client(), nil, cfg, pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}), adapters.ParseBidderInfos(cfg.Adapters, "../static/bidder-info", openrtb_ext.BidderList()), gdpr.AlwaysAllow{}, currencies.NewRateConverterDefault()).(*exchange)
+	currencyConverter := currencies.NewRateConverter(&http.Client{}, "", time.Duration(0))
+	e := NewExchange(server.Client(), nil, cfg, pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}), adapters.ParseBidderInfos(cfg.Adapters, "../static/bidder-info", openrtb_ext.BidderList()), gdpr.AlwaysAllow{}, currencyConverter).(*exchange)
 
 	/* 	3) Build all the parameters e.buildBidResponse(ctx.Background(), liveA... ) needs */
 	//liveAdapters []openrtb_ext.BidderName,
@@ -230,7 +232,7 @@ func TestDebugBehaviour(t *testing.T) {
 	e.cache = &wellBehavedCache{}
 	e.me = &metricsConf.DummyMetricsEngine{}
 	e.gDPR = gdpr.AlwaysAllow{}
-	e.currencyConverter = currencies.NewRateConverterDefault()
+	e.currencyConverter = currencies.NewRateConverter(&http.Client{}, "", time.Duration(0))
 
 	// Run tests
 	for _, test := range testCases {
@@ -299,7 +301,8 @@ func TestGetBidCacheInfo(t *testing.T) {
 	server := httptest.NewServer(http.HandlerFunc(handlerNoBidServer))
 	defer server.Close()
 
-	e := NewExchange(server.Client(), pbc.NewClient(&http.Client{}, &cfg.CacheURL, &cfg.ExtCacheURL, testEngine), cfg, pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}), adapters.ParseBidderInfos(cfg.Adapters, "../static/bidder-info", openrtb_ext.BidderList()), gdpr.AlwaysAllow{}, currencies.NewRateConverterDefault()).(*exchange)
+	currencyConverter := currencies.NewRateConverter(&http.Client{}, "", time.Duration(0))
+	e := NewExchange(server.Client(), pbc.NewClient(&http.Client{}, &cfg.CacheURL, &cfg.ExtCacheURL, testEngine), cfg, pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}), adapters.ParseBidderInfos(cfg.Adapters, "../static/bidder-info", openrtb_ext.BidderList()), gdpr.AlwaysAllow{}, currencyConverter).(*exchange)
 
 	/* 	3) Build all the parameters e.buildBidResponse(ctx.Background(), liveA... ) needs */
 	liveAdapters := []openrtb_ext.BidderName{bidderName}
@@ -449,7 +452,8 @@ func TestBidResponseCurrency(t *testing.T) {
 	server := httptest.NewServer(http.HandlerFunc(handlerNoBidServer))
 	defer server.Close()
 
-	e := NewExchange(server.Client(), nil, cfg, pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}), adapters.ParseBidderInfos(cfg.Adapters, "../static/bidder-info", openrtb_ext.BidderList()), gdpr.AlwaysAllow{}, currencies.NewRateConverterDefault()).(*exchange)
+	currencyConverter := currencies.NewRateConverter(&http.Client{}, "", time.Duration(0))
+	e := NewExchange(server.Client(), nil, cfg, pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}), adapters.ParseBidderInfos(cfg.Adapters, "../static/bidder-info", openrtb_ext.BidderList()), gdpr.AlwaysAllow{}, currencyConverter).(*exchange)
 
 	liveAdapters := make([]openrtb_ext.BidderName, 1)
 	liveAdapters[0] = "appnexus"
@@ -616,7 +620,8 @@ func TestRaceIntegration(t *testing.T) {
 		t.Errorf("Failed to create a category Fetcher: %v", error)
 	}
 	theMetrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{})
-	ex := NewExchange(server.Client(), &wellBehavedCache{}, cfg, theMetrics, adapters.ParseBidderInfos(cfg.Adapters, "../static/bidder-info", openrtb_ext.BidderList()), gdpr.AlwaysAllow{}, currencies.NewRateConverterDefault())
+	currencyConverter := currencies.NewRateConverter(&http.Client{}, "", time.Duration(0))
+	ex := NewExchange(server.Client(), &wellBehavedCache{}, cfg, theMetrics, adapters.ParseBidderInfos(cfg.Adapters, "../static/bidder-info", openrtb_ext.BidderList()), gdpr.AlwaysAllow{}, currencyConverter)
 	_, err := ex.HoldAuction(context.Background(), newRaceCheckingRequest(t), &emptyUsersync{}, pbsmetrics.Labels{}, &categoriesFetcher, nil)
 	if err != nil {
 		t.Errorf("HoldAuction returned unexpected error: %v", err)
@@ -700,7 +705,8 @@ func TestPanicRecovery(t *testing.T) {
 	}
 
 	theMetrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{})
-	e := NewExchange(&http.Client{}, nil, cfg, theMetrics, adapters.ParseBidderInfos(cfg.Adapters, "../static/bidder-info", openrtb_ext.BidderList()), gdpr.AlwaysAllow{}, currencies.NewRateConverterDefault()).(*exchange)
+	currencyConverter := currencies.NewRateConverter(&http.Client{}, "", time.Duration(0))
+	e := NewExchange(&http.Client{}, nil, cfg, theMetrics, adapters.ParseBidderInfos(cfg.Adapters, "../static/bidder-info", openrtb_ext.BidderList()), gdpr.AlwaysAllow{}, currencyConverter).(*exchange)
 	chBids := make(chan *bidResponseWrapper, 1)
 	panicker := func(aName openrtb_ext.BidderName, coreBidder openrtb_ext.BidderName, request *openrtb.BidRequest, bidlabels *pbsmetrics.AdapterLabels, conversions currencies.Conversions) {
 		panic("panic!")
@@ -765,7 +771,8 @@ func TestPanicRecoveryHighLevel(t *testing.T) {
 			Endpoint: server.URL,
 		}
 	}
-	e := NewExchange(server.Client(), &mockCache{}, cfg, pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}), adapters.ParseBidderInfos(cfg.Adapters, "../static/bidder-info", openrtb_ext.BidderList()), gdpr.AlwaysAllow{}, currencies.NewRateConverterDefault()).(*exchange)
+	currencyConverter := currencies.NewRateConverter(&http.Client{}, "", time.Duration(0))
+	e := NewExchange(server.Client(), &mockCache{}, cfg, pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}), adapters.ParseBidderInfos(cfg.Adapters, "../static/bidder-info", openrtb_ext.BidderList()), gdpr.AlwaysAllow{}, currencyConverter).(*exchange)
 
 	e.adapterMap[openrtb_ext.BidderBeachfront] = panicingAdapter{}
 	e.adapterMap[openrtb_ext.BidderAppnexus] = panicingAdapter{}
@@ -1025,7 +1032,7 @@ func newExchangeForTests(t *testing.T, filename string, expectations map[string]
 		cache:               &wellBehavedCache{},
 		cacheTime:           0,
 		gDPR:                gdpr.AlwaysAllow{},
-		currencyConverter:   currencies.NewRateConverterDefault(),
+		currencyConverter:   currencies.NewRateConverter(&http.Client{}, "", time.Duration(0)),
 		UsersyncIfAmbiguous: false,
 		privacyConfig:       privacyConfig,
 	}
@@ -1352,19 +1359,22 @@ func TestCategoryDedupe(t *testing.T) {
 	cats4 := []string{"IAB1-2000"}
 	bid1 := openrtb.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: cats1, W: 1, H: 1}
 	bid2 := openrtb.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 15.0000, Cat: cats2, W: 1, H: 1}
-	bid3 := openrtb.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 10.0000, Cat: cats1, W: 1, H: 1}
+	bid3 := openrtb.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 20.0000, Cat: cats1, W: 1, H: 1}
 	bid4 := openrtb.Bid{ID: "bid_id4", ImpID: "imp_id4", Price: 20.0000, Cat: cats4, W: 1, H: 1}
+	bid5 := openrtb.Bid{ID: "bid_id5", ImpID: "imp_id5", Price: 20.0000, Cat: cats1, W: 1, H: 1}
 
 	bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, 0}
 	bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 50}, 0}
 	bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, 0}
 	bid1_4 := pbsOrtbBid{&bid4, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, 0}
+	bid1_5 := pbsOrtbBid{&bid5, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, 0}
 
 	selectedBids := make(map[string]int)
 	expectedCategories := map[string]string{
 		"bid_id1": "10.00_Electronics_30s",
 		"bid_id2": "14.00_Sports_50s",
-		"bid_id3": "10.00_Electronics_30s",
+		"bid_id3": "20.00_Electronics_30s",
+		"bid_id5": "20.00_Electronics_30s",
 	}
 
 	numIterations := 10
@@ -1378,6 +1388,7 @@ func TestCategoryDedupe(t *testing.T) {
 			&bid1_2,
 			&bid1_3,
 			&bid1_4,
+			&bid1_5,
 		}
 
 		seatBid := pbsOrtbSeatBid{innerBids, "USD", nil, nil}
@@ -1388,7 +1399,7 @@ func TestCategoryDedupe(t *testing.T) {
 		bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &requestExt, adapterBids, categoriesFetcher, targData)
 
 		assert.Equal(t, nil, err, "Category mapping error should be empty")
-		assert.Equal(t, 2, len(rejections), "There should be 2 bid rejection messages")
+		assert.Equal(t, 3, len(rejections), "There should be 2 bid rejection messages")
 		assert.Regexpf(t, regexp.MustCompile(`bid rejected \[bid ID: bid_id(1|3)\] reason: Bid was deduplicated`), rejections[0], "Rejection message did not match expected")
 		assert.Equal(t, "bid rejected [bid ID: bid_id4] reason: Category mapping file for primary ad server: 'freewheel', publisher: '' not found", rejections[1], "Rejection message did not match expected")
 		assert.Equal(t, 2, len(adapterBids[bidderName1].bids), "Bidders number doesn't match")
@@ -1401,8 +1412,90 @@ func TestCategoryDedupe(t *testing.T) {
 	}
 
 	assert.Equal(t, numIterations, selectedBids["bid_id2"], "Bid 2 did not make it through every time")
-	assert.NotEqual(t, numIterations, selectedBids["bid_id1"], "Bid 1 made it through every time")
-	assert.NotEqual(t, numIterations, selectedBids["bid_id3"], "Bid 3 made it through every time")
+	assert.Equal(t, 0, selectedBids["bid_id1"], "Bid 1 should be rejected on every iteration due to lower price")
+	assert.NotEqual(t, 0, selectedBids["bid_id3"], "Bid 3 should be accepted at least once")
+	assert.NotEqual(t, 0, selectedBids["bid_id5"], "Bid 5 should be accepted at least once")
+}
+
+func TestNoCategoryDedupe(t *testing.T) {
+
+	categoriesFetcher, error := newCategoryFetcher("./test/category-mapping")
+	if error != nil {
+		t.Errorf("Failed to create a category Fetcher: %v", error)
+	}
+
+	requestExt := newExtRequestNoBrandCat()
+
+	targData := &targetData{
+		priceGranularity: requestExt.Prebid.Targeting.PriceGranularity,
+		includeWinners:   true,
+	}
+
+	adapterBids := make(map[openrtb_ext.BidderName]*pbsOrtbSeatBid)
+
+	cats1 := []string{"IAB1-3"}
+	cats2 := []string{"IAB1-4"}
+	cats4 := []string{"IAB1-2000"}
+	bid1 := openrtb.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 14.0000, Cat: cats1, W: 1, H: 1}
+	bid2 := openrtb.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 14.0000, Cat: cats2, W: 1, H: 1}
+	bid3 := openrtb.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 20.0000, Cat: cats1, W: 1, H: 1}
+	bid4 := openrtb.Bid{ID: "bid_id4", ImpID: "imp_id4", Price: 20.0000, Cat: cats4, W: 1, H: 1}
+	bid5 := openrtb.Bid{ID: "bid_id5", ImpID: "imp_id5", Price: 10.0000, Cat: cats1, W: 1, H: 1}
+
+	bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, 0}
+	bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, 0}
+	bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, 0}
+	bid1_4 := pbsOrtbBid{&bid4, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, 0}
+	bid1_5 := pbsOrtbBid{&bid5, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, 0}
+
+	selectedBids := make(map[string]int)
+	expectedCategories := map[string]string{
+		"bid_id1": "14.00_30s",
+		"bid_id2": "14.00_30s",
+		"bid_id3": "20.00_30s",
+		"bid_id4": "20.00_30s",
+		"bid_id5": "10.00_30s",
+	}
+
+	numIterations := 10
+
+	// Run the function many times, this should be enough for the 50% chance of which bid to remove to remove bid1 sometimes
+	// and bid3 others. It's conceivably possible (but highly unlikely) that the same bid get chosen every single time, but
+	// if you notice false fails from this test increase numIterations to make it even less likely to happen.
+	for i := 0; i < numIterations; i++ {
+		innerBids := []*pbsOrtbBid{
+			&bid1_1,
+			&bid1_2,
+			&bid1_3,
+			&bid1_4,
+			&bid1_5,
+		}
+
+		seatBid := pbsOrtbSeatBid{innerBids, "USD", nil, nil}
+		bidderName1 := openrtb_ext.BidderName("appnexus")
+
+		adapterBids[bidderName1] = &seatBid
+
+		bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &requestExt, adapterBids, categoriesFetcher, targData)
+
+		assert.Equal(t, nil, err, "Category mapping error should be empty")
+		assert.Equal(t, 2, len(rejections), "There should be 2 bid rejection messages")
+		assert.Regexpf(t, regexp.MustCompile(`bid rejected \[bid ID: bid_id(1|2)\] reason: Bid was deduplicated`), rejections[0], "Rejection message did not match expected")
+		assert.Regexpf(t, regexp.MustCompile(`bid rejected \[bid ID: bid_id(3|4)\] reason: Bid was deduplicated`), rejections[1], "Rejection message did not match expected")
+		assert.Equal(t, 3, len(adapterBids[bidderName1].bids), "Bidders number doesn't match")
+		assert.Equal(t, 3, len(bidCategory), "Bidders category mapping doesn't match")
+
+		for bidId, bidCat := range bidCategory {
+			assert.Equal(t, expectedCategories[bidId], bidCat, "Category mapping doesn't match")
+			selectedBids[bidId]++
+		}
+	}
+	assert.Equal(t, numIterations, selectedBids["bid_id5"], "Bid 5 did not make it through every time")
+	assert.NotEqual(t, 0, selectedBids["bid_id1"], "Bid 1 should be selected at least once")
+	assert.NotEqual(t, 0, selectedBids["bid_id2"], "Bid 2 should be selected at least once")
+	assert.NotEqual(t, 0, selectedBids["bid_id1"], "Bid 3 should be selected at least once")
+	assert.NotEqual(t, 0, selectedBids["bid_id4"], "Bid 4 should be selected at least once")
+
 }
 
 func TestBidRejectionErrors(t *testing.T) {
diff --git a/exchange/impcustomcachekeytest/multiImpVideo.json b/exchange/impcustomcachekeytest/multiImpVideo.json
new file mode 100644
index 00000000..7c1d7af0
--- /dev/null
+++ b/exchange/impcustomcachekeytest/multiImpVideo.json
@@ -0,0 +1,101 @@
+{
+	"bidRequest": {
+		"imp": [{
+					"id": "1_0"
+				},
+				{
+					"id": "1_1"
+				}
+		]
+	},
+	"pbsBids": [{
+		"bid": {
+			"id": "apn_1_0",
+			"impid": "1_0",
+			"price": 12.00,
+			"nurl": "http://apn_1_0.com",
+			"cat": ["12.00_sports_30s"]
+		},
+		"bidType": "video",
+		"bidder": "appnexus"
+	}, {
+		"bid": {
+			"id": "spotx_1_0",
+			"impid": "1_0",
+			"price": 20.00,
+			"nurl": "http://spotx_1_0.com",
+			"cat": ["20_news_30s"]
+		},
+		"bidType": "video",
+		"bidder": "spotx"
+	}, {
+		"bid": {
+			"id": "apn_1_1",
+			"impid": "1_1",
+			"price": 18.00,
+			"nurl": "http://apn_1_1.com",
+			"cat": ["18_furniture_30s"]
+		},
+		"bidType": "video",
+		"bidder": "appnexus"
+	}, {
+		"bid": {
+			"id": "spotx_1_1",
+			"impid": "1_1",
+			"price": 17.00,
+			"nurl": "http://spotx_1_1.com",
+			"cat": ["17_auto_30s"]
+		},
+		"bidType": "video",
+		"bidder": "spotx"
+	}, {
+		"bid": {
+			"id": "rubicon_1_1",
+			"impid": "1_1",
+			"price": 17.50,
+			"nurl": "http://rubicon_1_1.com",
+			"cat": ["17_music_30s"]
+		},
+		"bidType": "video",
+		"bidder": "rubicon"
+	}],
+	"expectedCacheables": [{
+			"Type": "xml",
+			"TTLSeconds": 3660,
+			"Data": "\u003cVAST version=\"3.0\"\u003e\u003cAd\u003e\u003cWrapper\u003e\u003cAdSystem\u003eprebid.org wrapper\u003c/AdSystem\u003e\u003cVASTAdTagURI\u003e\u003c![CDATA[http://apn_1_0.com]]\u003e\u003c/VASTAdTagURI\u003e\u003cImpression\u003e\u003c/Impression\u003e\u003cCreatives\u003e\u003c/Creatives\u003e\u003c/Wrapper\u003e\u003c/Ad\u003e\u003c/VAST\u003e",
+			"Key": "12.00_sports_30s"
+		}, {
+			"Type": "xml",
+			"TTLSeconds": 3660,
+			"Data": "\u003cVAST version=\"3.0\"\u003e\u003cAd\u003e\u003cWrapper\u003e\u003cAdSystem\u003eprebid.org wrapper\u003c/AdSystem\u003e\u003cVASTAdTagURI\u003e\u003c![CDATA[http://spotx_1_0.com]]\u003e\u003c/VASTAdTagURI\u003e\u003cImpression\u003e\u003c/Impression\u003e\u003cCreatives\u003e\u003c/Creatives\u003e\u003c/Wrapper\u003e\u003c/Ad\u003e\u003c/VAST\u003e",
+			"Key": "20_news_30s"
+		}, {
+			"Type": "xml",
+			"TTLSeconds": 3660,
+			"Data": "\u003cVAST version=\"3.0\"\u003e\u003cAd\u003e\u003cWrapper\u003e\u003cAdSystem\u003eprebid.org wrapper\u003c/AdSystem\u003e\u003cVASTAdTagURI\u003e\u003c![CDATA[http://apn_1_1.com]]\u003e\u003c/VASTAdTagURI\u003e\u003cImpression\u003e\u003c/Impression\u003e\u003cCreatives\u003e\u003c/Creatives\u003e\u003c/Wrapper\u003e\u003c/Ad\u003e\u003c/VAST\u003e",
+			"Key": "18_furniture_30s"
+		},
+		{
+			"Type": "xml",
+			"TTLSeconds": 3660,
+			"Data": "\u003cVAST version=\"3.0\"\u003e\u003cAd\u003e\u003cWrapper\u003e\u003cAdSystem\u003eprebid.org wrapper\u003c/AdSystem\u003e\u003cVASTAdTagURI\u003e\u003c![CDATA[http://spotx_1_1.com]]\u003e\u003c/VASTAdTagURI\u003e\u003cImpression\u003e\u003c/Impression\u003e\u003cCreatives\u003e\u003c/Creatives\u003e\u003c/Wrapper\u003e\u003c/Ad\u003e\u003c/VAST\u003e",
+			"Key": "17_auto_30s"
+		},
+		{
+			"Type": "xml",
+			"TTLSeconds": 3660,
+			"Data": "\u003cVAST version=\"3.0\"\u003e\u003cAd\u003e\u003cWrapper\u003e\u003cAdSystem\u003eprebid.org wrapper\u003c/AdSystem\u003e\u003cVASTAdTagURI\u003e\u003c![CDATA[http://rubicon_1_1.com]]\u003e\u003c/VASTAdTagURI\u003e\u003cImpression\u003e\u003c/Impression\u003e\u003cCreatives\u003e\u003c/Creatives\u003e\u003c/Wrapper\u003e\u003c/Ad\u003e\u003c/VAST\u003e",
+			"Key": "17_music_30s"
+		}
+	],
+	"defaultTTLs": {
+		"banner": 300,
+		"video": 3600,
+		"audio": 1800,
+		"native": 300
+	},
+	"targetDataIncludeWinners": false,
+	"targetDataIncludeBidderKeys": true,
+	"targetDataIncludeCacheBids": false,
+	"targetDataIncludeCacheVast": true
+}
diff --git a/exchange/impcustomcachekeytest/multiImpVideoNoIncludeBidderKeys.json b/exchange/impcustomcachekeytest/multiImpVideoNoIncludeBidderKeys.json
new file mode 100644
index 00000000..0b8ee141
--- /dev/null
+++ b/exchange/impcustomcachekeytest/multiImpVideoNoIncludeBidderKeys.json
@@ -0,0 +1,86 @@
+{
+	"bidRequest": {
+		"imp": [{
+					"id": "1_0"
+				},
+				{
+					"id": "1_1"
+				}
+		]
+	},
+	"pbsBids": [{
+		"bid": {
+			"id": "apn_1_0",
+			"impid": "1_0",
+			"price": 12.00,
+			"nurl": "http://apn_1_0.com",
+			"cat": ["12.00_sports_30s"]
+		},
+		"bidType": "video",
+		"bidder": "appnexus"
+	}, {
+		"bid": {
+			"id": "spotx_1_0",
+			"impid": "1_0",
+			"price": 20.00,
+			"nurl": "http://spotx_1_0.com",
+			"cat": ["20_news_30s"]
+		},
+		"bidType": "video",
+		"bidder": "spotx"
+	}, {
+		"bid": {
+			"id": "apn_1_1",
+			"impid": "1_1",
+			"price": 18.00,
+			"nurl": "http://apn_1_1.com",
+			"cat": ["18_furniture_30s"]
+		},
+		"bidType": "video",
+		"bidder": "appnexus"
+	}, {
+		"bid": {
+			"id": "spotx_1_1",
+			"impid": "1_1",
+			"price": 17.00,
+			"nurl": "http://spotx_1_1.com",
+			"cat": ["17_auto_30s"]
+		},
+		"bidType": "video",
+		"bidder": "spotx"
+	}, {
+		"bid": {
+			"id": "rubicon_1_1",
+			"impid": "1_1",
+			"price": 17.50,
+			"nurl": "http://rubicon_1_1.com",
+			"cat": ["17_music_30s"]
+		},
+		"bidType": "video",
+		"bidder": "rubicon"
+	}],
+	"expectedCacheables": [
+		{
+			"Type": "xml",
+			"TTLSeconds": 3660,
+			"Data": "\u003cVAST version=\"3.0\"\u003e\u003cAd\u003e\u003cWrapper\u003e\u003cAdSystem\u003eprebid.org wrapper\u003c/AdSystem\u003e\u003cVASTAdTagURI\u003e\u003c![CDATA[http://spotx_1_0.com]]\u003e\u003c/VASTAdTagURI\u003e\u003cImpression\u003e\u003c/Impression\u003e\u003cCreatives\u003e\u003c/Creatives\u003e\u003c/Wrapper\u003e\u003c/Ad\u003e\u003c/VAST\u003e",
+			"Key": "20_news_30s"
+		},
+		{
+			"Type": "xml",
+			"TTLSeconds": 3660,
+			"Data": "\u003cVAST version=\"3.0\"\u003e\u003cAd\u003e\u003cWrapper\u003e\u003cAdSystem\u003eprebid.org wrapper\u003c/AdSystem\u003e\u003cVASTAdTagURI\u003e\u003c![CDATA[http://apn_1_1.com]]\u003e\u003c/VASTAdTagURI\u003e\u003cImpression\u003e\u003c/Impression\u003e\u003cCreatives\u003e\u003c/Creatives\u003e\u003c/Wrapper\u003e\u003c/Ad\u003e\u003c/VAST\u003e",
+			"Key": "18_furniture_30s"
+		}
+	],
+	"defaultTTLs": {
+		"banner": 300,
+		"video": 3600,
+		"audio": 1800,
+		"native": 300
+	},
+	"targetDataIncludeWinners": true,
+	"targetDataIncludeBidderKeys": false,
+	"targetDataIncludeCacheBids": false,
+	"targetDataIncludeCacheVast": true
+}
diff --git a/exchange/legacy_test.go b/exchange/legacy_test.go
index 3ca804a1..61414c0e 100644
--- a/exchange/legacy_test.go
+++ b/exchange/legacy_test.go
@@ -4,8 +4,10 @@ import (
 	"context"
 	"encoding/json"
 	"errors"
+	"net/http"
 	"reflect"
 	"testing"
+	"time"
 
 	"github.com/buger/jsonparser"
 	"github.com/evanphx/json-patch"
@@ -58,7 +60,7 @@ func TestSiteVideo(t *testing.T) {
 	mockAdapter := mockLegacyAdapter{}
 
 	exchangeBidder := adaptLegacyAdapter(&mockAdapter)
-	currencyConverter := currencies.NewRateConverterDefault()
+	currencyConverter := currencies.NewRateConverter(&http.Client{}, "", time.Duration(0))
 	_, errs := exchangeBidder.requestBid(context.Background(), ortbRequest, openrtb_ext.BidderRubicon, 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{})
 	if len(errs) > 0 {
 		t.Errorf("Unexpected error requesting bids: %v", errs)
@@ -92,7 +94,7 @@ func TestAppBanner(t *testing.T) {
 	mockAdapter := mockLegacyAdapter{}
 
 	exchangeBidder := adaptLegacyAdapter(&mockAdapter)
-	currencyConverter := currencies.NewRateConverterDefault()
+	currencyConverter := currencies.NewRateConverter(&http.Client{}, "", time.Duration(0))
 	_, errs := exchangeBidder.requestBid(context.Background(), ortbRequest, openrtb_ext.BidderRubicon, 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{})
 	if len(errs) > 0 {
 		t.Errorf("Unexpected error requesting bids: %v", errs)
@@ -138,7 +140,7 @@ func TestBidTransforms(t *testing.T) {
 	}
 
 	exchangeBidder := adaptLegacyAdapter(&mockAdapter)
-	currencyConverter := currencies.NewRateConverterDefault()
+	currencyConverter := currencies.NewRateConverter(&http.Client{}, "", time.Duration(0))
 	seatBid, errs := exchangeBidder.requestBid(context.Background(), newAppOrtbRequest(), openrtb_ext.BidderRubicon, bidAdjustment, currencyConverter.Rates(), &adapters.ExtraRequestInfo{})
 	if len(errs) != 1 {
 		t.Fatalf("Bad error count. Expected 1, got %d", len(errs))
@@ -287,7 +289,7 @@ func TestErrorResponse(t *testing.T) {
 	}
 
 	exchangeBidder := adaptLegacyAdapter(&mockAdapter)
-	currencyConverter := currencies.NewRateConverterDefault()
+	currencyConverter := currencies.NewRateConverter(&http.Client{}, "", time.Duration(0))
 	_, errs := exchangeBidder.requestBid(context.Background(), ortbRequest, openrtb_ext.BidderRubicon, 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{})
 	if len(errs) != 1 {
 		t.Fatalf("Bad error count. Expected 1, got %d", len(errs))
@@ -326,7 +328,7 @@ func TestWithTargeting(t *testing.T) {
 		}},
 	}
 	exchangeBidder := adaptLegacyAdapter(&mockAdapter)
-	currencyConverter := currencies.NewRateConverterDefault()
+	currencyConverter := currencies.NewRateConverter(&http.Client{}, "", time.Duration(0))
 	bid, errs := exchangeBidder.requestBid(context.Background(), ortbRequest, openrtb_ext.BidderFacebook, 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{})
 	if len(errs) != 0 {
 		t.Fatalf("This should not produce errors. Got %v", errs)
diff --git a/exchange/targeting_test.go b/exchange/targeting_test.go
index 11b60db0..e596e5aa 100644
--- a/exchange/targeting_test.go
+++ b/exchange/targeting_test.go
@@ -50,13 +50,13 @@ func TestTargetingCache(t *testing.T) {
 
 	// Make sure that the cache keys exist on the bids where they're expected to
 	assertKeyExists(t, bids["winning-bid"], string(openrtb_ext.HbCacheKey), true)
-	assertKeyExists(t, bids["winning-bid"], openrtb_ext.HbCacheKey.BidderKey(openrtb_ext.BidderAppnexus, maxKeyLength), true)
+	assertKeyExists(t, bids["winning-bid"], openrtb_ext.HbCacheKey.BidderKey(openrtb_ext.BidderAppnexus, MaxKeyLength), true)
 
 	assertKeyExists(t, bids["contending-bid"], string(openrtb_ext.HbCacheKey), false)
-	assertKeyExists(t, bids["contending-bid"], openrtb_ext.HbCacheKey.BidderKey(openrtb_ext.BidderRubicon, maxKeyLength), true)
+	assertKeyExists(t, bids["contending-bid"], openrtb_ext.HbCacheKey.BidderKey(openrtb_ext.BidderRubicon, MaxKeyLength), true)
 
 	assertKeyExists(t, bids["losing-bid"], string(openrtb_ext.HbCacheKey), false)
-	assertKeyExists(t, bids["losing-bid"], openrtb_ext.HbCacheKey.BidderKey(openrtb_ext.BidderAppnexus, maxKeyLength), false)
+	assertKeyExists(t, bids["losing-bid"], openrtb_ext.HbCacheKey.BidderKey(openrtb_ext.BidderAppnexus, MaxKeyLength), false)
 
 	//assert hb_cache_host was included
 	assert.Contains(t, string(bids["winning-bid"].Ext), string(openrtb_ext.HbConstantCacheHostKey))
@@ -88,7 +88,7 @@ func runTargetingAuction(t *testing.T, mockBids map[openrtb_ext.BidderName][]*op
 		cache:               &wellBehavedCache{},
 		cacheTime:           time.Duration(0),
 		gDPR:                gdpr.AlwaysAllow{},
-		currencyConverter:   currencies.NewRateConverterDefault(),
+		currencyConverter:   currencies.NewRateConverter(&http.Client{}, "", time.Duration(0)),
 		UsersyncIfAmbiguous: false,
 	}
 
diff --git a/exchange/utils_test.go b/exchange/utils_test.go
index 3b919d3d..528e875a 100644
--- a/exchange/utils_test.go
+++ b/exchange/utils_test.go
@@ -28,8 +28,8 @@ func (p *permissionsMock) BidderSyncAllowed(ctx context.Context, bidder openrtb_
 	return true, nil
 }
 
-func (p *permissionsMock) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, bool, error) {
-	return p.personalInfoAllowed, p.personalInfoAllowed, nil
+func (p *permissionsMock) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, bool, bool, error) {
+	return p.personalInfoAllowed, p.personalInfoAllowed, p.personalInfoAllowed, nil
 }
 
 func (p *permissionsMock) AMPException() bool {
diff --git a/gdpr/impl_test.go b/gdpr/impl_test.go
index 05b2fb6d..053e8753 100644
--- a/gdpr/impl_test.go
+++ b/gdpr/impl_test.go
@@ -207,17 +207,17 @@ func TestAllowPersonalInfo(t *testing.T) {
 	}
 
 	// PI needs both purposes to succeed
-	allowPI, _, err := perms.PersonalInfoAllowed(context.Background(), openrtb_ext.BidderAppnexus, "", "BOS2bx5OS2bx5ABABBAAABoAAAABBwAA")
+	allowPI, _, _, err := perms.PersonalInfoAllowed(context.Background(), openrtb_ext.BidderAppnexus, "", "BOS2bx5OS2bx5ABABBAAABoAAAABBwAA")
 	assertNilErr(t, err)
 	assertBoolsEqual(t, false, allowPI)
 
-	allowPI, _, err = perms.PersonalInfoAllowed(context.Background(), openrtb_ext.BidderPubmatic, "", "BOS2bx5OS2bx5ABABBAAABoAAAABBwAA")
+	allowPI, _, _, err = perms.PersonalInfoAllowed(context.Background(), openrtb_ext.BidderPubmatic, "", "BOS2bx5OS2bx5ABABBAAABoAAAABBwAA")
 	assertNilErr(t, err)
 	assertBoolsEqual(t, true, allowPI)
 
 	// Assert that an item that otherwise would not be allowed PI access, gets approved because it is found in the GDPR.NonStandardPublishers array
 	perms.cfg.NonStandardPublisherMap = map[string]int{"appNexusAppID": 1}
-	allowPI, _, err = perms.PersonalInfoAllowed(context.Background(), openrtb_ext.BidderAppnexus, "appNexusAppID", "BOS2bx5OS2bx5ABABBAAABoAAAABBwAA")
+	allowPI, _, _, err = perms.PersonalInfoAllowed(context.Background(), openrtb_ext.BidderAppnexus, "appNexusAppID", "BOS2bx5OS2bx5ABABBAAABoAAAABBwAA")
 	assertNilErr(t, err)
 	assertBoolsEqual(t, true, allowPI)
 }
@@ -257,6 +257,7 @@ type tcf2TestDef struct {
 	consent     string
 	allowPI     bool
 	allowGeo    bool
+	allowID     bool
 }
 
 func TestAllowPersonalInfoTCF2(t *testing.T) {
@@ -285,6 +286,7 @@ func TestAllowPersonalInfoTCF2(t *testing.T) {
 			consent:     "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA",
 			allowPI:     false,
 			allowGeo:    false,
+			allowID:     false,
 		},
 		{
 			description: "Pubmatic vendor test, flex purposes claimed",
@@ -292,6 +294,7 @@ func TestAllowPersonalInfoTCF2(t *testing.T) {
 			consent:     "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA",
 			allowPI:     true,
 			allowGeo:    true,
+			allowID:     true,
 		},
 		{
 			description: "Rubicon vendor test, Specific purposes/LIs claimed, no geo claimed",
@@ -299,14 +302,16 @@ func TestAllowPersonalInfoTCF2(t *testing.T) {
 			consent:     "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA",
 			allowPI:     true,
 			allowGeo:    false,
+			allowID:     true,
 		},
 	}
 
 	for _, td := range testDefs {
-		allowPI, allowGeo, err := perms.PersonalInfoAllowed(context.Background(), td.bidder, "", td.consent)
+		allowPI, allowGeo, allowID, err := perms.PersonalInfoAllowed(context.Background(), td.bidder, "", td.consent)
 		assert.NoErrorf(t, err, "Error processing PersonalInfoAllowed for %s", td.description)
 		assert.EqualValuesf(t, td.allowPI, allowPI, "AllowPI failure on %s", td.description)
 		assert.EqualValuesf(t, td.allowGeo, allowGeo, "AllowGeo failure on %s", td.description)
+		assert.EqualValuesf(t, td.allowID, allowID, "AllowGeo failure on %s", td.description)
 	}
 }
 
@@ -328,10 +333,11 @@ func TestAllowPersonalInfoWhitelistTCF2(t *testing.T) {
 	}
 	// Assert that an item that otherwise would not be allowed PI access, gets approved because it is found in the GDPR.NonStandardPublishers array
 	perms.cfg.NonStandardPublisherMap = map[string]int{"appNexusAppID": 1}
-	allowPI, allowGeo, err := perms.PersonalInfoAllowed(context.Background(), openrtb_ext.BidderAppnexus, "appNexusAppID", "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA")
+	allowPI, allowGeo, allowID, err := perms.PersonalInfoAllowed(context.Background(), openrtb_ext.BidderAppnexus, "appNexusAppID", "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA")
 	assert.NoErrorf(t, err, "Error processing PersonalInfoAllowed")
 	assert.EqualValuesf(t, true, allowPI, "AllowPI failure")
 	assert.EqualValuesf(t, true, allowGeo, "AllowGeo failure")
+	assert.EqualValuesf(t, true, allowID, "AllowID failure")
 
 }
 
@@ -361,6 +367,7 @@ func TestAllowPersonalInfoTCF2PubRestrict(t *testing.T) {
 			consent:     "COwAdDhOwAdDhN4ABAENAPCgAAQAAv___wAAAFP_AAp_4AI6ACACAA",
 			allowPI:     false,
 			allowGeo:    false,
+			allowID:     false,
 		},
 		{
 			description: "Pubmatic vendor test, flex purposes claimed",
@@ -368,6 +375,7 @@ func TestAllowPersonalInfoTCF2PubRestrict(t *testing.T) {
 			consent:     "COwAdDhOwAdDhN4ABAENAPCgAAQAAv___wAAAFP_AAp_4AI6ACACAA",
 			allowPI:     false,
 			allowGeo:    false,
+			allowID:     false,
 		},
 		{
 			description: "Rubicon vendor test, Specific purposes/LIs claimed, no geo claimed",
@@ -375,14 +383,16 @@ func TestAllowPersonalInfoTCF2PubRestrict(t *testing.T) {
 			consent:     "COwAdDhOwAdDhN4ABAENAPCgAAQAAv___wAAAFP_AAp_4AI6ACACAA",
 			allowPI:     false,
 			allowGeo:    false,
+			allowID:     true,
 		},
 	}
 
 	for _, td := range testDefs {
-		allowPI, allowGeo, err := perms.PersonalInfoAllowed(context.Background(), td.bidder, "", td.consent)
+		allowPI, allowGeo, allowID, err := perms.PersonalInfoAllowed(context.Background(), td.bidder, "", td.consent)
 		assert.NoErrorf(t, err, "Error processing PersonalInfoAllowed for %s", td.description)
 		assert.EqualValuesf(t, td.allowPI, allowPI, "AllowPI failure on %s", td.description)
 		assert.EqualValuesf(t, td.allowGeo, allowGeo, "AllowGeo failure on %s", td.description)
+		assert.EqualValuesf(t, td.allowID, allowID, "AllowPI failure on %s", td.description)
 	}
 }
 
@@ -413,6 +423,7 @@ func TestAllowPersonalInfoTCF2PurposeOneTrue(t *testing.T) {
 			consent:     "COzqiL3OzqiL3NIAAAENAiCMAP_AAH_AAIAAAQEX2S5MAICL7JcmAAA",
 			allowPI:     false,
 			allowGeo:    false,
+			allowID:     false,
 		},
 		{
 			description: "Pubmatic vendor test, flex purposes claimed",
@@ -420,6 +431,7 @@ func TestAllowPersonalInfoTCF2PurposeOneTrue(t *testing.T) {
 			consent:     "COzqiL3OzqiL3NIAAAENAiCMAP_AAH_AAIAAAQEX2S5MAICL7JcmAAA",
 			allowPI:     true,
 			allowGeo:    true,
+			allowID:     true,
 		},
 		{
 			description: "Rubicon vendor test, Specific purposes/LIs claimed, no geo claimed",
@@ -427,14 +439,16 @@ func TestAllowPersonalInfoTCF2PurposeOneTrue(t *testing.T) {
 			consent:     "COzqiL3OzqiL3NIAAAENAiCMAP_AAH_AAIAAAQEX2S5MAICL7JcmAAA",
 			allowPI:     true,
 			allowGeo:    false,
+			allowID:     true,
 		},
 	}
 
 	for _, td := range testDefs {
-		allowPI, allowGeo, err := perms.PersonalInfoAllowed(context.Background(), td.bidder, "", td.consent)
+		allowPI, allowGeo, allowID, err := perms.PersonalInfoAllowed(context.Background(), td.bidder, "", td.consent)
 		assert.NoErrorf(t, err, "Error processing PersonalInfoAllowed for %s", td.description)
 		assert.EqualValuesf(t, td.allowPI, allowPI, "AllowPI failure on %s", td.description)
 		assert.EqualValuesf(t, td.allowGeo, allowGeo, "AllowGeo failure on %s", td.description)
+		assert.EqualValuesf(t, td.allowID, allowID, "AllowID failure on %s", td.description)
 	}
 }
 
@@ -458,6 +472,7 @@ func TestAllowPersonalInfoTCF2PurposeOneFalse(t *testing.T) {
 	perms.cfg.TCF2.PurposeOneTreatment.AccessAllowed = false
 
 	// COzqiL3OzqiL3NIAAAENAiCMAP_AAH_AAIAAAQEX2S5MAICL7JcmAAA Purpose one flag set
+	// Purpose one treatment will fail PI, but allow passing the IDs.
 	testDefs := []tcf2TestDef{
 		{
 			description: "Appnexus vendor test, insufficient purposes claimed",
@@ -465,6 +480,7 @@ func TestAllowPersonalInfoTCF2PurposeOneFalse(t *testing.T) {
 			consent:     "COzqiL3OzqiL3NIAAAENAiCMAP_AAH_AAIAAAQEX2S5MAICL7JcmAAA",
 			allowPI:     false,
 			allowGeo:    false,
+			allowID:     false,
 		},
 		{
 			description: "Pubmatic vendor test, flex purposes claimed",
@@ -472,6 +488,7 @@ func TestAllowPersonalInfoTCF2PurposeOneFalse(t *testing.T) {
 			consent:     "COzqiL3OzqiL3NIAAAENAiCMAP_AAH_AAIAAAQEX2S5MAICL7JcmAAA",
 			allowPI:     false,
 			allowGeo:    true,
+			allowID:     true,
 		},
 		{
 			description: "Rubicon vendor test, Specific purposes/LIs claimed, no geo claimed",
@@ -479,14 +496,16 @@ func TestAllowPersonalInfoTCF2PurposeOneFalse(t *testing.T) {
 			consent:     "COzqiL3OzqiL3NIAAAENAiCMAP_AAH_AAIAAAQEX2S5MAICL7JcmAAA",
 			allowPI:     false,
 			allowGeo:    false,
+			allowID:     true,
 		},
 	}
 
 	for _, td := range testDefs {
-		allowPI, allowGeo, err := perms.PersonalInfoAllowed(context.Background(), td.bidder, "", td.consent)
+		allowPI, allowGeo, allowID, err := perms.PersonalInfoAllowed(context.Background(), td.bidder, "", td.consent)
 		assert.NoErrorf(t, err, "Error processing PersonalInfoAllowed for %s", td.description)
 		assert.EqualValuesf(t, td.allowPI, allowPI, "AllowPI failure on %s", td.description)
 		assert.EqualValuesf(t, td.allowGeo, allowGeo, "AllowGeo failure on %s", td.description)
+		assert.EqualValuesf(t, td.allowID, allowID, "AllowID failure on %s", td.description)
 	}
 }
 
diff --git a/gdpr/vendorlist-fetching_test.go b/gdpr/vendorlist-fetching_test.go
index 824f9178..c989ef4c 100644
--- a/gdpr/vendorlist-fetching_test.go
+++ b/gdpr/vendorlist-fetching_test.go
@@ -9,6 +9,8 @@ import (
 	"testing"
 	"time"
 
+	"github.com/stretchr/testify/assert"
+
 	"github.com/prebid/prebid-server/config"
 )
 
@@ -139,6 +141,101 @@ func TestVendorListMaker(t *testing.T) {
 	assertStringsEqual(t, "https://vendorlist.consensu.org/v2/archives/vendor-list-v7.json", vendorListURLMaker(7, 2))
 }
 
+func TestDefaultVendorList(t *testing.T) {
+	firstVendorList := mockVendorListData(t, 1, map[uint16]*purposes{
+		32: {
+			purposes: []int{1, 2},
+		},
+	})
+	secondVendorList := mockVendorListData(t, 2, map[uint16]*purposes{
+		12: {
+			purposes: []int{2},
+		},
+	})
+	server := httptest.NewServer(http.HandlerFunc(mockServer(2, map[int]string{
+		1: firstVendorList,
+		2: secondVendorList,
+	})))
+	defer server.Close()
+
+	testcfg := testConfig()
+	testcfg.TCF1.FetchGVL = true
+	testcfg.TCF1.FallbackGVLPath = "../static/tcf1/fallback_gvl.json"
+	fetcher := newVendorListFetcher(context.Background(), testcfg, server.Client(), testURLMaker(server), 1)
+
+	list, err := fetcher(context.Background(), 12)
+	assert.NoError(t, err, "Error with fetching default vendorlist: %v", err)
+	assert.Equal(t, uint16(214), list.Version(), "Expected to fetch default version 214, got %d", list.Version())
+
+	// Testing that we got the default vendorlist data, and not the version off the server.
+	vendor := list.Vendor(12)
+	assert.Equal(t, true, vendor.Purpose(1))
+	assert.Equal(t, false, vendor.Purpose(2))
+}
+
+func TestDefaultVendorListPassthrough(t *testing.T) {
+	firstVendorList := mockVendorListData(t, 1, map[uint16]*purposes{
+		32: {
+			purposes: []int{1, 2},
+		},
+	})
+	secondVendorList := mockVendorListData(t, 2, map[uint16]*purposes{
+		12: {
+			purposes: []int{2},
+		},
+	})
+	server := httptest.NewServer(http.HandlerFunc(mockServer(2, map[int]string{
+		1: firstVendorList,
+		2: secondVendorList,
+	})))
+	defer server.Close()
+
+	testcfg := testConfig()
+	testcfg.TCF1.FetchGVL = true
+	testcfg.TCF1.FallbackGVLPath = "../static/tcf1/fallback_gvl.json"
+	fetcher := newVendorListFetcher(context.Background(), testcfg, server.Client(), testURLMaker(server), 1)
+	list, err := fetcher(context.Background(), 2)
+	assert.NoError(t, err, "Error with fetching existing vendorlist: %v", err)
+	assert.Equal(t, uint16(2), list.Version(), "Expected to fetch mock list version 2, got version %d", list.Version())
+
+	// Testing that we got the testing vendorlist data, and not the default.
+	vendor := list.Vendor(12)
+	assert.Equal(t, false, vendor.Purpose(1))
+	assert.Equal(t, true, vendor.Purpose(2))
+}
+
+func TestDefaultVendorListNoFetch(t *testing.T) {
+	firstVendorList := mockVendorListData(t, 1, map[uint16]*purposes{
+		32: {
+			purposes: []int{1, 2},
+		},
+	})
+	secondVendorList := mockVendorListData(t, 2, map[uint16]*purposes{
+		12: {
+			purposes: []int{2},
+		},
+	})
+	server := httptest.NewServer(http.HandlerFunc(mockServer(1, map[int]string{
+		1: firstVendorList,
+		2: secondVendorList,
+	})))
+	defer server.Close()
+
+	testcfg := testConfig()
+	testcfg.TCF1.FetchGVL = false
+	testcfg.TCF1.FallbackGVLPath = "../static/tcf1/fallback_gvl.json"
+	fetcher := newVendorListFetcher(context.Background(), testcfg, server.Client(), testURLMaker(server), 1)
+	list, err := fetcher(context.Background(), 2)
+	assert.NoError(t, err, "Error with fetching default vendorlist: %v", err)
+	assert.Equal(t, uint16(214), list.Version(), "Expected to fetch default version 214, got %d", list.Version())
+
+	// Testing that we got the default vendorlist data, and not the version off the server.
+	vendor := list.Vendor(12)
+	assert.Equal(t, true, vendor.Purpose(1))
+	assert.Equal(t, false, vendor.Purpose(2))
+
+}
+
 // mockServer returns a handler which returns the given response for each global vendor list version.
 // The latestVersion param can be used to mock "updates" which occur after PBS has been turned on.
 // For example, if latestVersion is 3, but the responses map has data at "4", the server will return
diff --git a/privacy/enforcement_test.go b/privacy/enforcement_test.go
index 968c6354..ef02e281 100644
--- a/privacy/enforcement_test.go
+++ b/privacy/enforcement_test.go
@@ -21,6 +21,7 @@ func TestAny(t *testing.T) {
 				COPPA:   false,
 				GDPR:    false,
 				GDPRGeo: false,
+				GDPRID:  false,
 				LMT:     false,
 			},
 			expected: false,
@@ -32,6 +33,7 @@ func TestAny(t *testing.T) {
 				COPPA:   true,
 				GDPR:    true,
 				GDPRGeo: true,
+				GDPRID:  true,
 				LMT:     true,
 			},
 			expected: true,
@@ -43,6 +45,7 @@ func TestAny(t *testing.T) {
 				COPPA:   true,
 				GDPR:    false,
 				GDPRGeo: false,
+				GDPRID:  false,
 				LMT:     true,
 			},
 			expected: true,
@@ -72,6 +75,7 @@ func TestApply(t *testing.T) {
 				COPPA:   true,
 				GDPR:    true,
 				GDPRGeo: true,
+				GDPRID:  true,
 				LMT:     true,
 			},
 			ampGDPRException:   false,
@@ -87,6 +91,7 @@ func TestApply(t *testing.T) {
 				COPPA:   false,
 				GDPR:    false,
 				GDPRGeo: false,
+				GDPRID:  false,
 				LMT:     false,
 			},
 			ampGDPRException:   false,
@@ -102,6 +107,7 @@ func TestApply(t *testing.T) {
 				COPPA:   true,
 				GDPR:    false,
 				GDPRGeo: false,
+				GDPRID:  false,
 				LMT:     false,
 			},
 			ampGDPRException:   false,
@@ -117,6 +123,7 @@ func TestApply(t *testing.T) {
 				COPPA:   false,
 				GDPR:    true,
 				GDPRGeo: true,
+				GDPRID:  true,
 				LMT:     false,
 			},
 			ampGDPRException:   false,
@@ -132,6 +139,7 @@ func TestApply(t *testing.T) {
 				COPPA:   false,
 				GDPR:    true,
 				GDPRGeo: true,
+				GDPRID:  true,
 				LMT:     false,
 			},
 			ampGDPRException:   true,
@@ -147,6 +155,7 @@ func TestApply(t *testing.T) {
 				COPPA:   false,
 				GDPR:    false,
 				GDPRGeo: false,
+				GDPRID:  false,
 				LMT:     false,
 			},
 			ampGDPRException:   true,
@@ -162,6 +171,7 @@ func TestApply(t *testing.T) {
 				COPPA:   true,
 				GDPR:    true,
 				GDPRGeo: true,
+				GDPRID:  true,
 				LMT:     false,
 			},
 			ampGDPRException:   true,
@@ -177,6 +187,7 @@ func TestApply(t *testing.T) {
 				COPPA:   false,
 				GDPR:    true,
 				GDPRGeo: false,
+				GDPRID:  true,
 				LMT:     false,
 			},
 			ampGDPRException:   false,
@@ -192,6 +203,7 @@ func TestApply(t *testing.T) {
 				COPPA:   false,
 				GDPR:    false,
 				GDPRGeo: true,
+				GDPRID:  false,
 				LMT:     false,
 			},
 			ampGDPRException:   false,
@@ -200,6 +212,22 @@ func TestApply(t *testing.T) {
 			expectedUser:       ScrubStrategyUserNone,
 			expectedUserGeo:    ScrubStrategyGeoReducedPrecision,
 		},
+		{
+			description: "GDPR Only, ID exception",
+			enforcement: Enforcement{
+				CCPA:    false,
+				COPPA:   false,
+				GDPR:    true,
+				GDPRGeo: true,
+				GDPRID:  false,
+				LMT:     false,
+			},
+			ampGDPRException:   false,
+			expectedDeviceIPv6: ScrubStrategyIPV6Lowest16,
+			expectedDeviceGeo:  ScrubStrategyGeoReducedPrecision,
+			expectedUser:       ScrubStrategyUserNone,
+			expectedUserGeo:    ScrubStrategyGeoReducedPrecision,
+		},
 		{
 			description: "LMT Only",
 			enforcement: Enforcement{
@@ -207,6 +235,7 @@ func TestApply(t *testing.T) {
 				COPPA:   false,
 				GDPR:    false,
 				GDPRGeo: false,
+				GDPRID:  false,
 				LMT:     true,
 			},
 			ampGDPRException:   false,
@@ -222,6 +251,7 @@ func TestApply(t *testing.T) {
 				COPPA:   false,
 				GDPR:    false,
 				GDPRGeo: false,
+				GDPRID:  false,
 				LMT:     true,
 			},
 			ampGDPRException:   true,
@@ -258,10 +288,12 @@ func TestApplyNoneApplicable(t *testing.T) {
 	m := &mockScrubber{}
 
 	enforcement := Enforcement{
-		CCPA:  false,
-		COPPA: false,
-		GDPR:  false,
-		LMT:   false,
+		CCPA:    false,
+		COPPA:   false,
+		GDPR:    false,
+		GDPRGeo: false,
+		GDPRID:  false,
+		LMT:     false,
 	}
 	enforcement.apply(req, false, m)
 
diff --git a/util/task/ticker_task_test.go b/util/task/ticker_task_test.go
new file mode 100644
index 00000000..27551c9a
--- /dev/null
+++ b/util/task/ticker_task_test.go
@@ -0,0 +1,63 @@
+package task_test
+
+import (
+	"testing"
+	"time"
+
+	"github.com/prebid/prebid-server/util/task"
+	"github.com/stretchr/testify/assert"
+)
+
+type MockRunner struct {
+	RunCount int
+}
+
+func (mcc *MockRunner) Run() error {
+	mcc.RunCount++
+	return nil
+}
+
+func TestStartWithSingleRun(t *testing.T) {
+	// Setup:
+	runner := &MockRunner{RunCount: 0}
+	interval := 0 * time.Millisecond
+	ticker := task.NewTickerTask(interval, runner)
+
+	// Execute:
+	ticker.Start()
+	time.Sleep(10 * time.Millisecond)
+
+	// Verify:
+	assert.Equal(t, runner.RunCount, 1, "runner should have run one time")
+}
+
+func TestStartWithPeriodicRun(t *testing.T) {
+	// Setup:
+	runner := &MockRunner{RunCount: 0}
+	interval := 10 * time.Millisecond
+	ticker := task.NewTickerTask(interval, runner)
+
+	// Execute:
+	ticker.Start()
+	time.Sleep(25 * time.Millisecond)
+	ticker.Stop()
+
+	// Verify:
+	assert.Equal(t, runner.RunCount, 3, "runner should have run three times")
+}
+
+func TestStop(t *testing.T) {
+	// Setup:
+	runner := &MockRunner{RunCount: 0}
+	interval := 10 * time.Millisecond
+	ticker := task.NewTickerTask(interval, runner)
+
+	// Execute:
+	ticker.Start()
+	time.Sleep(25 * time.Millisecond)
+	ticker.Stop()
+	time.Sleep(25 * time.Millisecond) // wait in case stop failed so additional runs can happen
+
+	// Verify:
+	assert.Equal(t, runner.RunCount, 3, "runner should have run three times")
+}