Repository: prebid/prebid-server
Pull request #1570 (2020-11-09 19:27:12)

Refactored HoldAuction Arguments

laurb9: Nice, and long overdue :) This should fix the signature and help avoid having to update dozens of unit tests with new parameters, which I'd have to do for #1015 . This object can be passed down to the various MakeBid* methods.

PBS-Java has a similar object called [AuctionContext](https://github.com/rubicon-project/prebid-server-java/blob/b7698bef12612de7c360734196d2f84c4882e93e/src/main/java/org/prebid/server/auction/model/AuctionContext.java#L21) whose naming I find more intuitive save for the fact that `context` has a specific meaning in Go.

SyntaxNode: > Nice, and long overdue :) This should fix the signature and help avoid having to update dozens of unit tests with new parameters, which I'd have to do for #1015 . This object can be passed down to the various MakeBid* methods.
> 
> PBS-Java has a similar object called [AuctionContext](https://github.com/rubicon-project/prebid-server-java/blob/b7698bef12612de7c360734196d2f84c4882e93e/src/main/java/org/prebid/server/auction/model/AuctionContext.java#L21) whose naming I find more intuitive save for the fact that `context` has a specific meaning in Go.

Indeed. We're in the process of refactoring the entire auction code path. I'd like to wait until we're happy with the restructured code before making changes to the adapter `MakeBid` interface. That's very expensive to change.

I would expect the name of the `AuctionRequest` to evolve over time. You're right. The term 'context' has other meaning in Go's HTTP handlers. I also think the term 'context' is too generic and equivalent to 'stuff' or 'thing'. Make sense in some cases, but I'd like to be more specific with this object.

Patch:
diff --git a/adapters/adform/adform.go b/adapters/adform/adform.go
index 5881f4ab..d7e5deda 100644
--- a/adapters/adform/adform.go
+++ b/adapters/adform/adform.go
@@ -79,6 +79,7 @@ type adformBid struct {
 	Height       uint64  `json:"height,omitempty"`
 	DealId       string  `json:"deal_id,omitempty"`
 	CreativeId   string  `json:"win_crid,omitempty"`
+	VastContent  string  `json:"vast_content,omitempty"`
 }
 
 const priceTypeGross = "gross"
@@ -241,7 +242,8 @@ func toPBSBidSlice(adformBids []*adformBid, r *adformRequest) pbs.PBSBidSlice {
 	bids := make(pbs.PBSBidSlice, 0)
 
 	for i, bid := range adformBids {
-		if bid.Banner == "" || bid.ResponseType != "banner" {
+		adm, bidType := getAdAndType(bid)
+		if adm == "" {
 			continue
 		}
 		pbsBid := pbs.PBSBid{
@@ -249,12 +251,12 @@ func toPBSBidSlice(adformBids []*adformBid, r *adformRequest) pbs.PBSBidSlice {
 			AdUnitCode:        r.adUnits[i].adUnitCode,
 			BidderCode:        r.bidderCode,
 			Price:             bid.Price,
-			Adm:               bid.Banner,
+			Adm:               adm,
 			Width:             bid.Width,
 			Height:            bid.Height,
 			DealId:            bid.DealId,
 			Creative_id:       bid.CreativeId,
-			CreativeMediaType: string(openrtb_ext.BidTypeBanner),
+			CreativeMediaType: string(bidType),
 		}
 
 		bids = append(bids, &pbsBid)
@@ -632,21 +634,23 @@ func toOpenRtbBidResponse(adformBids []*adformBid, r *openrtb.BidRequest) *adapt
 	}
 
 	for i, bid := range adformBids {
-		if bid.Banner == "" || bid.ResponseType != "banner" {
+		adm, bidType := getAdAndType(bid)
+		if adm == "" {
 			continue
 		}
+
 		openRtbBid := openrtb.Bid{
 			ID:     r.Imp[i].ID,
 			ImpID:  r.Imp[i].ID,
 			Price:  bid.Price,
-			AdM:    bid.Banner,
+			AdM:    adm,
 			W:      bid.Width,
 			H:      bid.Height,
 			DealID: bid.DealId,
 			CrID:   bid.CreativeId,
 		}
 
-		bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{Bid: &openRtbBid, BidType: openrtb_ext.BidTypeBanner})
+		bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{Bid: &openRtbBid, BidType: bidType})
 		currency = bid.Currency
 	}
 
@@ -654,3 +658,12 @@ func toOpenRtbBidResponse(adformBids []*adformBid, r *openrtb.BidRequest) *adapt
 
 	return bidResponse
 }
+
+func getAdAndType(bid *adformBid) (string, openrtb_ext.BidType) {
+	if bid.ResponseType == "banner" {
+		return bid.Banner, openrtb_ext.BidTypeBanner
+	} else if bid.ResponseType == "vast_content" {
+		return bid.VastContent, openrtb_ext.BidTypeVideo
+	}
+	return "", ""
+}
diff --git a/config/accounts.go b/config/accounts.go
index 5ec818b8..0faf5f5f 100644
--- a/config/accounts.go
+++ b/config/accounts.go
@@ -17,45 +17,63 @@ type Account struct {
 	Disabled      bool        `mapstructure:"disabled" json:"disabled"`
 	CacheTTL      DefaultTTLs `mapstructure:"cache_ttl" json:"cache_ttl"`
 	EventsEnabled bool        `mapstructure:"events_enabled" json:"events_enabled"`
+	CCPA          AccountCCPA `mapstructure:"ccpa" json:"ccpa"`
 	GDPR          AccountGDPR `mapstructure:"gdpr" json:"gdpr"`
 }
 
+// AccountCCPA represents account-specific CCPA configuration
+type AccountCCPA struct {
+	Enabled            *bool              `mapstructure:"enabled" json:"enabled,omitempty"`
+	IntegrationEnabled AccountIntegration `mapstructure:"integration_enabled" json:"integration_enabled"`
+}
+
+// EnabledForIntegrationType indicates whether CCPA is turned on at the account level for the specified integration type
+// by using the integration type setting if defined or the general CCPA setting if defined; otherwise it returns nil
+func (a *AccountCCPA) EnabledForIntegrationType(integrationType IntegrationType) *bool {
+	if integrationEnabled := a.IntegrationEnabled.GetByIntegrationType(integrationType); integrationEnabled != nil {
+		return integrationEnabled
+	}
+	return a.Enabled
+}
+
 // AccountGDPR represents account-specific GDPR configuration
 type AccountGDPR struct {
-	Enabled            *bool                  `mapstructure:"enabled" json:"enabled,omitempty"`
-	IntegrationEnabled AccountGDPRIntegration `mapstructure:"integration_enabled" json:"integration_enabled"`
+	Enabled            *bool              `mapstructure:"enabled" json:"enabled,omitempty"`
+	IntegrationEnabled AccountIntegration `mapstructure:"integration_enabled" json:"integration_enabled"`
 }
 
 // EnabledForIntegrationType indicates whether GDPR is turned on at the account level for the specified integration type
 // by using the integration type setting if defined or the general GDPR setting if defined; otherwise it returns nil
 func (a *AccountGDPR) EnabledForIntegrationType(integrationType IntegrationType) *bool {
-	var integrationEnabled *bool
-
-	switch integrationType {
-	case IntegrationTypeAMP:
-		integrationEnabled = a.IntegrationEnabled.AMP
-	case IntegrationTypeApp:
-		integrationEnabled = a.IntegrationEnabled.App
-	case IntegrationTypeVideo:
-		integrationEnabled = a.IntegrationEnabled.Video
-	case IntegrationTypeWeb:
-		integrationEnabled = a.IntegrationEnabled.Web
-	}
 
-	if integrationEnabled != nil {
+	if integrationEnabled := a.IntegrationEnabled.GetByIntegrationType(integrationType); integrationEnabled != nil {
 		return integrationEnabled
 	}
-	if a.Enabled != nil {
-		return a.Enabled
-	}
-
-	return nil
+	return a.Enabled
 }
 
-// AccountGDPRIntegration indicates whether GDPR is enabled for each integration type
-type AccountGDPRIntegration struct {
+// AccountIntegration indicates whether a particular privacy policy (GDPR, CCPA) is enabled for each integration type
+type AccountIntegration struct {
 	AMP   *bool `mapstructure:"amp" json:"amp,omitempty"`
 	App   *bool `mapstructure:"app" json:"app,omitempty"`
 	Video *bool `mapstructure:"video" json:"video,omitempty"`
 	Web   *bool `mapstructure:"web" json:"web,omitempty"`
 }
+
+// GetByIntegrationType looks up the account integration enabled setting for the specified integration type
+func (a *AccountIntegration) GetByIntegrationType(integrationType IntegrationType) *bool {
+	var integrationEnabled *bool
+
+	switch integrationType {
+	case IntegrationTypeAMP:
+		integrationEnabled = a.AMP
+	case IntegrationTypeApp:
+		integrationEnabled = a.App
+	case IntegrationTypeVideo:
+		integrationEnabled = a.Video
+	case IntegrationTypeWeb:
+		integrationEnabled = a.Web
+	}
+
+	return integrationEnabled
+}
diff --git a/endpoints/openrtb2/amp_auction.go b/endpoints/openrtb2/amp_auction.go
index 5536c4e6..486b5cfc 100644
--- a/endpoints/openrtb2/amp_auction.go
+++ b/endpoints/openrtb2/amp_auction.go
@@ -48,7 +48,6 @@ func NewAmpEndpoint(
 	validator openrtb_ext.BidderParamValidator,
 	requestsById stored_requests.Fetcher,
 	accounts stored_requests.AccountFetcher,
-	categories stored_requests.CategoryFetcher,
 	cfg *config.Configuration,
 	met pbsmetrics.MetricsEngine,
 	pbsAnalytics analytics.PBSAnalyticsModule,
@@ -74,7 +73,6 @@ func NewAmpEndpoint(
 		requestsById,
 		empty_fetcher.EmptyFetcher{},
 		accounts,
-		categories,
 		cfg,
 		met,
 		pbsAnalytics,
@@ -185,7 +183,15 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h
 		return
 	}
 
-	response, err := deps.ex.HoldAuction(ctx, req, usersyncs, labels, account, &deps.categories, nil)
+	auctionRequest := exchange.AuctionRequest{
+		BidRequest:   req,
+		Account:      *account,
+		UserSyncs:    usersyncs,
+		RequestType:  labels.RType,
+		LegacyLabels: labels,
+	}
+
+	response, err := deps.ex.HoldAuction(ctx, auctionRequest, nil)
 	ao.AuctionResponse = response
 
 	if err != nil {
diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go
index b02b5786..839ae975 100644
--- a/endpoints/openrtb2/auction.go
+++ b/endpoints/openrtb2/auction.go
@@ -47,7 +47,7 @@ var (
 	dntEnabled  int8   = 1
 )
 
-func NewEndpoint(ex exchange.Exchange, validator openrtb_ext.BidderParamValidator, requestsById stored_requests.Fetcher, accounts stored_requests.AccountFetcher, categories stored_requests.CategoryFetcher, cfg *config.Configuration, met pbsmetrics.MetricsEngine, pbsAnalytics analytics.PBSAnalyticsModule, disabledBidders map[string]string, defReqJSON []byte, bidderMap map[string]openrtb_ext.BidderName) (httprouter.Handle, error) {
+func NewEndpoint(ex exchange.Exchange, validator openrtb_ext.BidderParamValidator, requestsById stored_requests.Fetcher, accounts stored_requests.AccountFetcher, cfg *config.Configuration, met pbsmetrics.MetricsEngine, pbsAnalytics analytics.PBSAnalyticsModule, disabledBidders map[string]string, defReqJSON []byte, bidderMap map[string]openrtb_ext.BidderName) (httprouter.Handle, error) {
 
 	if ex == nil || validator == nil || requestsById == nil || accounts == nil || cfg == nil || met == nil {
 		return nil, errors.New("NewEndpoint requires non-nil arguments.")
@@ -66,7 +66,6 @@ func NewEndpoint(ex exchange.Exchange, validator openrtb_ext.BidderParamValidato
 		requestsById,
 		empty_fetcher.EmptyFetcher{},
 		accounts,
-		categories,
 		cfg,
 		met,
 		pbsAnalytics,
@@ -85,7 +84,6 @@ type endpointDeps struct {
 	storedReqFetcher          stored_requests.Fetcher
 	videoFetcher              stored_requests.Fetcher
 	accounts                  stored_requests.AccountFetcher
-	categories                stored_requests.CategoryFetcher
 	cfg                       *config.Configuration
 	metricsEngine             pbsmetrics.MetricsEngine
 	analytics                 analytics.PBSAnalyticsModule
@@ -164,7 +162,15 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http
 		return
 	}
 
-	response, err := deps.ex.HoldAuction(ctx, req, usersyncs, labels, account, &deps.categories, nil)
+	auctionRequest := exchange.AuctionRequest{
+		BidRequest:   req,
+		Account:      *account,
+		UserSyncs:    usersyncs,
+		RequestType:  labels.RType,
+		LegacyLabels: labels,
+	}
+
+	response, err := deps.ex.HoldAuction(ctx, auctionRequest, nil)
 	ao.Request = req
 	ao.Response = response
 	ao.Account = account
diff --git a/endpoints/openrtb2/video_auction.go b/endpoints/openrtb2/video_auction.go
index e277e362..d1fcda0a 100644
--- a/endpoints/openrtb2/video_auction.go
+++ b/endpoints/openrtb2/video_auction.go
@@ -36,7 +36,7 @@ import (
 
 var defaultRequestTimeout int64 = 5000
 
-func NewVideoEndpoint(ex exchange.Exchange, validator openrtb_ext.BidderParamValidator, requestsById stored_requests.Fetcher, videoFetcher stored_requests.Fetcher, accounts stored_requests.AccountFetcher, categories stored_requests.CategoryFetcher, cfg *config.Configuration, met pbsmetrics.MetricsEngine, pbsAnalytics analytics.PBSAnalyticsModule, disabledBidders map[string]string, defReqJSON []byte, bidderMap map[string]openrtb_ext.BidderName, cache prebid_cache_client.Client) (httprouter.Handle, error) {
+func NewVideoEndpoint(ex exchange.Exchange, validator openrtb_ext.BidderParamValidator, requestsById stored_requests.Fetcher, videoFetcher stored_requests.Fetcher, accounts stored_requests.AccountFetcher, cfg *config.Configuration, met pbsmetrics.MetricsEngine, pbsAnalytics analytics.PBSAnalyticsModule, disabledBidders map[string]string, defReqJSON []byte, bidderMap map[string]openrtb_ext.BidderName, cache prebid_cache_client.Client) (httprouter.Handle, error) {
 
 	if ex == nil || validator == nil || requestsById == nil || accounts == nil || cfg == nil || met == nil {
 		return nil, errors.New("NewVideoEndpoint requires non-nil arguments.")
@@ -57,7 +57,6 @@ func NewVideoEndpoint(ex exchange.Exchange, validator openrtb_ext.BidderParamVal
 		requestsById,
 		videoFetcher,
 		accounts,
-		categories,
 		cfg,
 		met,
 		pbsAnalytics,
@@ -261,8 +260,16 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re
 		handleError(&labels, w, acctIDErrs, &vo, &debugLog)
 		return
 	}
-	//execute auction logic
-	response, err := deps.ex.HoldAuction(ctx, bidReq, usersyncs, labels, account, &deps.categories, &debugLog)
+
+	auctionRequest := exchange.AuctionRequest{
+		BidRequest:   bidReq,
+		Account:      *account,
+		UserSyncs:    usersyncs,
+		RequestType:  labels.RType,
+		LegacyLabels: labels,
+	}
+
+	response, err := deps.ex.HoldAuction(ctx, auctionRequest, &debugLog)
 	vo.Request = bidReq
 	vo.Response = response
 	if err != nil {
diff --git a/exchange/exchange.go b/exchange/exchange.go
index 3a5f785b..9056caf8 100644
--- a/exchange/exchange.go
+++ b/exchange/exchange.go
@@ -41,13 +41,14 @@ type extCacheInstructions struct {
 // Exchange runs Auctions. Implementations must be threadsafe, and will be shared across many goroutines.
 type Exchange interface {
 	// HoldAuction executes an OpenRTB v2.5 Auction.
-	HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, usersyncs IdFetcher, labels pbsmetrics.Labels, account *config.Account, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *DebugLog) (*openrtb.BidResponse, error)
+	HoldAuction(ctx context.Context, r AuctionRequest, debugLog *DebugLog) (*openrtb.BidResponse, error)
 }
 
 // IdFetcher can find the user's ID for a specific Bidder.
 type IdFetcher interface {
 	// GetId returns the ID for the bidder. The boolean will be true if the ID exists, and false otherwise.
 	GetId(bidder openrtb_ext.BidderName) (string, bool)
+	LiveSyncCount() int
 }
 
 type exchange struct {
@@ -59,6 +60,7 @@ type exchange struct {
 	currencyConverter   *currencies.RateConverter
 	UsersyncIfAmbiguous bool
 	privacyConfig       config.Privacy
+	categoriesFetcher   stored_requests.CategoryFetcher
 }
 
 // Container to pass out response ext data from the GetAllBids goroutines back into the main thread
@@ -76,7 +78,7 @@ type bidResponseWrapper struct {
 	bidder       openrtb_ext.BidderName
 }
 
-func NewExchange(client *http.Client, cache prebid_cache_client.Client, cfg *config.Configuration, metricsEngine pbsmetrics.MetricsEngine, infos adapters.BidderInfos, gDPR gdpr.Permissions, currencyConverter *currencies.RateConverter) Exchange {
+func NewExchange(client *http.Client, cache prebid_cache_client.Client, cfg *config.Configuration, metricsEngine pbsmetrics.MetricsEngine, infos adapters.BidderInfos, gDPR gdpr.Permissions, currencyConverter *currencies.RateConverter, categoriesFetcher stored_requests.CategoryFetcher) Exchange {
 	e := new(exchange)
 
 	e.adapterMap = newAdapterMap(client, cfg, infos, metricsEngine)
@@ -91,13 +93,24 @@ func NewExchange(client *http.Client, cache prebid_cache_client.Client, cfg *con
 		GDPR: cfg.GDPR,
 		LMT:  cfg.LMT,
 	}
+	e.categoriesFetcher = categoriesFetcher
 	return e
 }
 
-func (e *exchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, usersyncs IdFetcher, labels pbsmetrics.Labels, account *config.Account, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *DebugLog) (*openrtb.BidResponse, error) {
+type AuctionRequest struct {
+	BidRequest  *openrtb.BidRequest
+	Account     config.Account
+	UserSyncs   IdFetcher
+	RequestType pbsmetrics.RequestType
 
+	// LegacyLabels is included here for temporary compatability with cleanOpenRTBRequests
+	// in HoldAuction until we get to factoring it away. Do not use for anything new.
+	LegacyLabels pbsmetrics.Labels
+}
+
+func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog *DebugLog) (*openrtb.BidResponse, error) {
 	var err error
-	requestExt, err := extractBidRequestExt(bidRequest)
+	requestExt, err := extractBidRequestExt(r.BidRequest)
 	if err != nil {
 		return nil, err
 	}
@@ -108,21 +121,21 @@ func (e *exchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidReque
 		_, targData.cacheHost, targData.cachePath = e.cache.GetExtCacheData()
 	}
 
-	debugInfo := getDebugInfo(bidRequest, requestExt)
+	debugInfo := getDebugInfo(r.BidRequest, requestExt)
 	if debugInfo {
 		ctx = e.makeDebugContext(ctx, debugInfo)
 	}
 
 	bidAdjustmentFactors := getExtBidAdjustmentFactors(requestExt)
 
-	recordImpMetrics(bidRequest, e.me)
+	recordImpMetrics(r.BidRequest, e.me)
 
 	// Make our best guess if GDPR applies
-	usersyncIfAmbiguous := e.parseUsersyncIfAmbiguous(bidRequest)
+	usersyncIfAmbiguous := e.parseUsersyncIfAmbiguous(r.BidRequest)
 
 	// Slice of BidRequests, each a copy of the original cleaned to only contain bidder data for the named bidder
 	blabels := make(map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels)
-	cleanRequests, aliases, privacyLabels, errs := cleanOpenRTBRequests(ctx, bidRequest, requestExt, usersyncs, blabels, labels, e.gDPR, usersyncIfAmbiguous, e.privacyConfig, account)
+	cleanRequests, aliases, privacyLabels, errs := cleanOpenRTBRequests(ctx, r.BidRequest, requestExt, r.UserSyncs, blabels, r.LegacyLabels, e.gDPR, usersyncIfAmbiguous, e.privacyConfig, &r.Account)
 
 	e.me.RecordRequestPrivacy(privacyLabels)
 
@@ -147,7 +160,7 @@ func (e *exchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidReque
 		//If includebrandcategory is present in ext then CE feature is on.
 		if requestExt.Prebid.Targeting != nil && requestExt.Prebid.Targeting.IncludeBrandCategory != nil {
 			var rejections []string
-			bidCategory, adapterBids, rejections, err = applyCategoryMapping(ctx, requestExt, adapterBids, *categoriesFetcher, targData)
+			bidCategory, adapterBids, rejections, err = applyCategoryMapping(ctx, requestExt, adapterBids, e.categoriesFetcher, targData)
 			if err != nil {
 				return nil, fmt.Errorf("Error in category mapping : %s", err.Error())
 			}
@@ -158,24 +171,24 @@ func (e *exchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidReque
 
 		if targData != nil {
 			// A non-nil auction is only needed if targeting is active. (It is used below this block to extract cache keys)
-			auc = newAuction(adapterBids, len(bidRequest.Imp), targData.preferDeals)
+			auc = newAuction(adapterBids, len(r.BidRequest.Imp), targData.preferDeals)
 			auc.setRoundedPrices(targData.priceGranularity)
 
 			if requestExt.Prebid.SupportDeals {
-				dealErrs := applyDealSupport(bidRequest, auc, bidCategory)
+				dealErrs := applyDealSupport(r.BidRequest, auc, bidCategory)
 				errs = append(errs, dealErrs...)
 			}
 
-			cacheErrs := auc.doCache(ctx, e.cache, targData, bidRequest, 60, &account.CacheTTL, bidCategory, debugLog)
+			cacheErrs := auc.doCache(ctx, e.cache, targData, r.BidRequest, 60, &r.Account.CacheTTL, bidCategory, debugLog)
 			if len(cacheErrs) > 0 {
 				errs = append(errs, cacheErrs...)
 			}
-			targData.setTargeting(auc, bidRequest.App != nil, bidCategory)
+			targData.setTargeting(auc, r.BidRequest.App != nil, bidCategory)
 
 		}
 	}
 
-	bidResponseExt := e.makeExtBidResponse(adapterBids, adapterExtra, bidRequest, debugInfo, errs)
+	bidResponseExt := e.makeExtBidResponse(adapterBids, adapterExtra, r.BidRequest, debugInfo, errs)
 
 	// Ensure caching errors are added in case auc.doCache was called and errors were returned
 	if len(cacheErrs) > 0 {
@@ -200,7 +213,7 @@ func (e *exchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidReque
 	}
 
 	// Build the response
-	return e.buildBidResponse(ctx, liveAdapters, adapterBids, bidRequest, adapterExtra, auc, bidResponseExt, cacheInstructions.returnCreative, errs)
+	return e.buildBidResponse(ctx, liveAdapters, adapterBids, r.BidRequest, adapterExtra, auc, bidResponseExt, cacheInstructions.returnCreative, errs)
 }
 
 func (e *exchange) parseUsersyncIfAmbiguous(bidRequest *openrtb.BidRequest) bool {
diff --git a/exchange/utils.go b/exchange/utils.go
index f6fcc711..6ae18a27 100644
--- a/exchange/utils.go
+++ b/exchange/utils.go
@@ -83,7 +83,7 @@ func cleanOpenRTBRequests(ctx context.Context,
 	consent := extractConsent(orig)
 	ampGDPRException := (labels.RType == pbsmetrics.ReqTypeAMP) && gDPR.AMPException()
 
-	ccpaEnforcer, err := extractCCPA(orig, privacyConfig, aliases)
+	ccpaEnforcer, err := extractCCPA(orig, privacyConfig, account, aliases, integrationTypeMap[labels.RType])
 	if err != nil {
 		errs = append(errs, err)
 		return
@@ -144,7 +144,14 @@ func gdprEnabled(account *config.Account, privacyConfig config.Privacy, integrat
 	return privacyConfig.GDPR.Enabled
 }
 
-func extractCCPA(orig *openrtb.BidRequest, privacyConfig config.Privacy, aliases map[string]string) (privacy.PolicyEnforcer, error) {
+func ccpaEnabled(account *config.Account, privacyConfig config.Privacy, requestType config.IntegrationType) bool {
+	if accountEnabled := account.CCPA.EnabledForIntegrationType(requestType); accountEnabled != nil {
+		return *accountEnabled
+	}
+	return privacyConfig.CCPA.Enforce
+}
+
+func extractCCPA(orig *openrtb.BidRequest, privacyConfig config.Privacy, account *config.Account, aliases map[string]string, requestType config.IntegrationType) (privacy.PolicyEnforcer, error) {
 	ccpaPolicy, err := ccpa.ReadFromRequest(orig)
 	if err != nil {
 		return privacy.NilPolicyEnforcer{}, err
@@ -157,7 +164,7 @@ func extractCCPA(orig *openrtb.BidRequest, privacyConfig config.Privacy, aliases
 	}
 
 	ccpaEnforcer := privacy.EnabledPolicyEnforcer{
-		Enabled:        privacyConfig.CCPA.Enforce,
+		Enabled:        ccpaEnabled(account, privacyConfig, requestType),
 		PolicyEnforcer: ccpaParsedPolicy,
 	}
 	return ccpaEnforcer, nil
diff --git a/router/router.go b/router/router.go
index 919781f7..07021481 100644
--- a/router/router.go
+++ b/router/router.go
@@ -6,13 +6,14 @@ import (
 	"database/sql"
 	"encoding/json"
 	"fmt"
-	"github.com/prebid/prebid-server/endpoints/events"
 	"io/ioutil"
 	"net/http"
 	"path/filepath"
 	"strings"
 	"time"
 
+	"github.com/prebid/prebid-server/endpoints/events"
+
 	"github.com/prebid/prebid-server/pbsmetrics"
 
 	"github.com/prebid/prebid-server/adapters"
@@ -242,21 +243,21 @@ func New(cfg *config.Configuration, rateConvertor *currencies.RateConverter) (r
 
 	exchanges = newExchangeMap(cfg)
 	cacheClient := pbc.NewClient(cacheHttpClient, &cfg.CacheURL, &cfg.ExtCacheURL, r.MetricsEngine)
-	theExchange := exchange.NewExchange(generalHttpClient, cacheClient, cfg, r.MetricsEngine, bidderInfos, gdprPerms, rateConvertor)
+	theExchange := exchange.NewExchange(generalHttpClient, cacheClient, cfg, r.MetricsEngine, bidderInfos, gdprPerms, rateConvertor, categoriesFetcher)
 
-	openrtbEndpoint, err := openrtb2.NewEndpoint(theExchange, paramsValidator, fetcher, accounts, categoriesFetcher, cfg, r.MetricsEngine, pbsAnalytics, disabledBidders, defReqJSON, activeBiddersMap)
+	openrtbEndpoint, err := openrtb2.NewEndpoint(theExchange, paramsValidator, fetcher, accounts, cfg, r.MetricsEngine, pbsAnalytics, disabledBidders, defReqJSON, activeBiddersMap)
 
 	if err != nil {
 		glog.Fatalf("Failed to create the openrtb endpoint handler. %v", err)
 	}
 
-	ampEndpoint, err := openrtb2.NewAmpEndpoint(theExchange, paramsValidator, ampFetcher, accounts, categoriesFetcher, cfg, r.MetricsEngine, pbsAnalytics, disabledBidders, defReqJSON, activeBiddersMap)
+	ampEndpoint, err := openrtb2.NewAmpEndpoint(theExchange, paramsValidator, ampFetcher, accounts, cfg, r.MetricsEngine, pbsAnalytics, disabledBidders, defReqJSON, activeBiddersMap)
 
 	if err != nil {
 		glog.Fatalf("Failed to create the amp endpoint handler. %v", err)
 	}
 
-	videoEndpoint, err := openrtb2.NewVideoEndpoint(theExchange, paramsValidator, fetcher, videoFetcher, accounts, categoriesFetcher, cfg, r.MetricsEngine, pbsAnalytics, disabledBidders, defReqJSON, activeBiddersMap, cacheClient)
+	videoEndpoint, err := openrtb2.NewVideoEndpoint(theExchange, paramsValidator, fetcher, videoFetcher, accounts, cfg, r.MetricsEngine, pbsAnalytics, disabledBidders, defReqJSON, activeBiddersMap, cacheClient)
 	if err != nil {
 		glog.Fatalf("Failed to create the video endpoint handler. %v", err)
 	}
diff --git a/static/bidder-info/adform.yaml b/static/bidder-info/adform.yaml
index 8aafd9f6..4dce10b9 100644
--- a/static/bidder-info/adform.yaml
+++ b/static/bidder-info/adform.yaml
@@ -4,6 +4,8 @@ capabilities:
   app:
     mediaTypes:
       - banner
+      - video
   site:
     mediaTypes:
       - banner
+      - video


Test patch:
diff --git a/adapters/adform/adform_test.go b/adapters/adform/adform_test.go
index f2277762..0bfb95b6 100644
--- a/adapters/adform/adform_test.go
+++ b/adapters/adform/adform_test.go
@@ -107,6 +107,16 @@ func createAdformServerResponse(testData aBidInfo) ([]byte, error) {
 			DealId:       testData.tags[2].dealId,
 			CreativeId:   testData.tags[2].creativeId,
 		},
+		{
+			ResponseType: "vast_content",
+			VastContent:  testData.tags[3].content,
+			Price:        testData.tags[3].price,
+			Currency:     "EUR",
+			Width:        testData.width,
+			Height:       testData.height,
+			DealId:       testData.tags[3].dealId,
+			CreativeId:   testData.tags[3].creativeId,
+		},
 	}
 	adformServerResponse, err := json.Marshal(bids)
 	return adformServerResponse, err
@@ -123,10 +133,21 @@ func TestAdformBasicResponse(t *testing.T) {
 	if err != nil {
 		t.Fatalf("Should not have gotten adapter error: %v", err)
 	}
-	if len(bids) != 2 {
-		t.Fatalf("Received %d bids instead of 2", len(bids))
+	if len(bids) != 3 {
+		t.Fatalf("Received %d bids instead of 3", len(bids))
+	}
+	expectedTypes := []openrtb_ext.BidType{
+		openrtb_ext.BidTypeBanner,
+		openrtb_ext.BidTypeBanner,
+		openrtb_ext.BidTypeVideo,
 	}
-	for _, bid := range bids {
+
+	for i, bid := range bids {
+
+		if bid.CreativeMediaType != string(expectedTypes[i]) {
+			t.Errorf("Expected a %s bid. Got: %s", expectedTypes[i], bid.CreativeMediaType)
+		}
+
 		matched := false
 		for _, tag := range adformTestData.tags {
 			if bid.AdUnitCode == tag.code {
@@ -224,7 +245,7 @@ func preparePrebidRequest(serverUrl string, t *testing.T) *pbs.PBSRequest {
 
 func preparePrebidRequestBody(requestData aBidInfo, t *testing.T) *bytes.Buffer {
 	prebidRequest := pbs.PBSRequest{
-		AdUnits: make([]pbs.AdUnit, 3),
+		AdUnits: make([]pbs.AdUnit, 4),
 		Device: &openrtb.Device{
 			UA:  requestData.deviceUA,
 			IP:  requestData.deviceIP,
@@ -326,6 +347,7 @@ func createTestData(secure bool) aBidInfo {
 			{mid: 32344, keyValues: "color:red,age:30-40", keyWords: "red,blue", cdims: "300x300,400x200", priceType: "gross", code: "code1", price: 1.23, content: "banner-content1", dealId: "dealId1", creativeId: "creativeId1"},
 			{mid: 32345, priceType: "net", code: "code2", minp: 23.1, cdims: "300x200"}, // no bid for ad unit
 			{mid: 32346, code: "code3", price: 1.24, content: "banner-content2", dealId: "dealId2", url: "https://adform.com?a=b"},
+			{mid: 32347, code: "code4", content: "vast-xml"},
 		},
 		secure:   secure,
 		currency: "EUR",
@@ -379,6 +401,11 @@ func createOpenRtbRequest(testData *aBidInfo) *openrtb.BidRequest {
 func TestOpenRTBStandardResponse(t *testing.T) {
 	testData := createTestData(true)
 	request := createOpenRtbRequest(&testData)
+	expectedTypes := []openrtb_ext.BidType{
+		openrtb_ext.BidTypeBanner,
+		openrtb_ext.BidTypeBanner,
+		openrtb_ext.BidTypeVideo,
+	}
 
 	responseBody, err := createAdformServerResponse(testData)
 	if err != nil {
@@ -390,16 +417,17 @@ func TestOpenRTBStandardResponse(t *testing.T) {
 	bidder := new(AdformAdapter)
 	bidResponse, errs := bidder.MakeBids(request, nil, httpResponse)
 
-	if len(bidResponse.Bids) != 2 {
-		t.Fatalf("Expected 2 bids. Got %d", len(bidResponse.Bids))
+	if len(bidResponse.Bids) != 3 {
+		t.Fatalf("Expected 3 bids. Got %d", len(bidResponse.Bids))
 	}
 	if len(errs) != 0 {
 		t.Errorf("Expected 0 errors. Got %d", len(errs))
 	}
 
-	for _, typeBid := range bidResponse.Bids {
-		if typeBid.BidType != openrtb_ext.BidTypeBanner {
-			t.Errorf("Expected a banner bid. Got: %s", bidResponse.Bids[0].BidType)
+	for i, typeBid := range bidResponse.Bids {
+
+		if typeBid.BidType != expectedTypes[i] {
+			t.Errorf("Expected a %s bid. Got: %s", expectedTypes[i], typeBid.BidType)
 		}
 		bid := typeBid.Bid
 		matched := false
@@ -561,10 +589,10 @@ func assertAdformServerRequest(testData aBidInfo, r *http.Request, isOpenRtb boo
 	var midsWithCurrency = ""
 	var queryString = ""
 	if isOpenRtb {
-		midsWithCurrency = "bWlkPTMyMzQ0JnJjdXI9RVVSJm1rdj1jb2xvcjpyZWQsYWdlOjMwLTQwJm1rdz1yZWQsYmx1ZSZjZGltcz0zMDB4MzAwLDQwMHgyMDA&bWlkPTMyMzQ1JnJjdXI9RVVSJmNkaW1zPTMwMHgyMDAmbWlucD0yMy4xMA&bWlkPTMyMzQ2JnJjdXI9RVVS"
+		midsWithCurrency = "bWlkPTMyMzQ0JnJjdXI9RVVSJm1rdj1jb2xvcjpyZWQsYWdlOjMwLTQwJm1rdz1yZWQsYmx1ZSZjZGltcz0zMDB4MzAwLDQwMHgyMDA&bWlkPTMyMzQ1JnJjdXI9RVVSJmNkaW1zPTMwMHgyMDAmbWlucD0yMy4xMA&bWlkPTMyMzQ2JnJjdXI9RVVS&bWlkPTMyMzQ3JnJjdXI9RVVS"
 		queryString = "CC=1&adid=6D92078A-8246-4BA4-AE5B-76104861E7DC&eids=eyJ0ZXN0LmNvbSI6eyJvdGhlcl91c2VyX2lkIjpbMF0sInNvbWVfdXNlcl9pZCI6WzFdfSwidGVzdDIub3JnIjp7Im90aGVyX3VzZXJfaWQiOlsyXX19&fd=1&gdpr=1&gdpr_consent=abc&ip=111.111.111.111&pt=gross&rp=4&stid=transaction-id&url=https%3A%2F%2Fadform.com%3Fa%3Db&" + midsWithCurrency
 	} else {
-		midsWithCurrency = "bWlkPTMyMzQ0JnJjdXI9VVNEJm1rdj1jb2xvcjpyZWQsYWdlOjMwLTQwJm1rdz1yZWQsYmx1ZSZjZGltcz0zMDB4MzAwLDQwMHgyMDA&bWlkPTMyMzQ1JnJjdXI9VVNEJmNkaW1zPTMwMHgyMDAmbWlucD0yMy4xMA&bWlkPTMyMzQ2JnJjdXI9VVNE" // no way to pass currency in legacy adapter
+		midsWithCurrency = "bWlkPTMyMzQ0JnJjdXI9VVNEJm1rdj1jb2xvcjpyZWQsYWdlOjMwLTQwJm1rdz1yZWQsYmx1ZSZjZGltcz0zMDB4MzAwLDQwMHgyMDA&bWlkPTMyMzQ1JnJjdXI9VVNEJmNkaW1zPTMwMHgyMDAmbWlucD0yMy4xMA&bWlkPTMyMzQ2JnJjdXI9VVNE&bWlkPTMyMzQ3JnJjdXI9VVNE" // no way to pass currency in legacy adapter
 		queryString = "CC=1&adid=6D92078A-8246-4BA4-AE5B-76104861E7DC&fd=1&gdpr=1&gdpr_consent=abc&ip=111.111.111.111&pt=gross&rp=4&stid=transaction-id&" + midsWithCurrency
 	}
 
@@ -646,7 +674,7 @@ func TestPriceTypeUrlParameterCreation(t *testing.T) {
 // Asserts that toOpenRtbBidResponse() creates a *adapters.BidderResponse with
 // the currency of the last valid []*adformBid element and the expected number of bids
 func TestToOpenRtbBidResponse(t *testing.T) {
-	expectedBids := 3
+	expectedBids := 4
 	lastCurrency, anotherCurrency, emptyCurrency := "EUR", "USD", ""
 
 	request := &openrtb.BidRequest{
@@ -672,6 +700,11 @@ func TestToOpenRtbBidResponse(t *testing.T) {
 				Ext:    json.RawMessage(`{"bidder1": { "mid": "32344" }}`),
 				Banner: &openrtb.Banner{},
 			},
+			{
+				ID:     "video-imp-no4",
+				Ext:    json.RawMessage(`{"bidder1": { "mid": "32345" }}`),
+				Banner: &openrtb.Banner{},
+			},
 		},
 		Device: &openrtb.Device{UA: "ua", IP: "ip"},
 		User:   &openrtb.User{BuyerUID: "buyerUID"},
@@ -703,6 +736,16 @@ func TestToOpenRtbBidResponse(t *testing.T) {
 			ResponseType: "banner",
 			Banner:       "banner-content4",
 			Price:        1.25,
+			Currency:     emptyCurrency,
+			Width:        300,
+			Height:       200,
+			DealId:       "dealId4",
+			CreativeId:   "creativeId4",
+		},
+		{
+			ResponseType: "vast_content",
+			VastContent:  "vast-content",
+			Price:        1.25,
 			Currency:     lastCurrency,
 			Width:        300,
 			Height:       200,
diff --git a/adapters/adform/adformtest/exemplary/multiformat-impression.json b/adapters/adform/adformtest/exemplary/multiformat-impression.json
new file mode 100644
index 00000000..efd4aca6
--- /dev/null
+++ b/adapters/adform/adformtest/exemplary/multiformat-impression.json
@@ -0,0 +1,99 @@
+{
+    "mockBidRequest": {
+      "id": "test-request-id",
+      "imp": [
+        {
+          "id": "banner-imp-id",
+          "banner": {
+            "format": [
+              {
+                "w": 300,
+                "h": 250
+              }
+            ]
+          },
+          "ext": {
+            "bidder": {
+              "mid": 12345
+            }
+          }
+        },
+        {
+            "id": "video-imp-id",
+            "video": {
+              "w": 640,
+              "h": 480
+            },
+            "ext": {
+              "bidder": {
+                "mid": 54321
+              }
+            }
+          }
+      ]
+    },
+    "httpCalls": [
+      {
+        "expectedRequest": {
+          "uri": "http://adx.adform.net/adx?CC=1&fd=1&gdpr=&gdpr_consent=&ip=&rp=4&stid=&bWlkPTEyMzQ1JnJjdXI9VVNE&bWlkPTU0MzIxJnJjdXI9VVNE"
+        },
+        "mockResponse": {
+          "status": 200,
+          "body": [
+            {
+              "response": "banner",
+              "banner": "<adm>",
+              "win_bid": 0.5,
+              "win_cur": "USD",
+              "width": 300,
+              "height": 250,
+              "deal_id": null,
+              "win_crid": "20078830"
+            },
+            {
+                "response": "vast_content",
+                "vast_content": "<vast>",
+                "win_bid": 0.7,
+                "win_cur": "USD",
+                "width": 640,
+                "height": 480,
+                "deal_id": "DID-123-22",
+                "win_crid": "20078831"
+              }
+          ]
+        }
+      }
+    ],
+    "expectedBidResponses": [
+      {
+        "currency": "USD",
+        "bids": [
+          {
+            "bid": {
+              "id": "banner-imp-id",
+              "impid": "banner-imp-id",
+              "price": 0.5,
+              "adm": "<adm>",
+              "crid": "20078830",
+              "w": 300,
+              "h": 250
+            },
+            "type": "banner"
+          },
+          {
+            "bid": {
+              "id": "video-imp-id",
+              "impid": "video-imp-id",
+              "price": 0.7,
+              "adm": "<vast>",
+              "crid": "20078831",
+              "dealid": "DID-123-22",
+              "w": 640,
+              "h": 480
+            },
+            "type": "video"
+          }
+        ]
+      }
+    ]
+  }
diff --git a/adapters/adform/adformtest/exemplary/single-banner-impression.json b/adapters/adform/adformtest/exemplary/single-banner-impression.json
new file mode 100644
index 00000000..fd7f3cde
--- /dev/null
+++ b/adapters/adform/adformtest/exemplary/single-banner-impression.json
@@ -0,0 +1,64 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "mid": 12345
+          }
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "http://adx.adform.net/adx?CC=1&fd=1&gdpr=&gdpr_consent=&ip=&rp=4&stid=&bWlkPTEyMzQ1JnJjdXI9VVNE"
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": [
+          {
+            "response": "banner",
+            "banner": "<adm>",
+            "win_bid": 0.5,
+            "win_cur": "USD",
+            "width": 300,
+            "height": 250,
+            "deal_id": null,
+            "win_crid": "20078830"
+          }
+        ]
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "test-imp-id",
+            "impid": "test-imp-id",
+            "price": 0.5,
+            "adm": "<adm>",
+            "crid": "20078830",
+            "w": 300,
+            "h": 250
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/adform/adformtest/exemplary/single-video-impression.json b/adapters/adform/adformtest/exemplary/single-video-impression.json
new file mode 100644
index 00000000..e22977e6
--- /dev/null
+++ b/adapters/adform/adformtest/exemplary/single-video-impression.json
@@ -0,0 +1,60 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "video": {
+          "w": 640,
+          "h": 480
+        },
+        "ext": {
+          "bidder": {
+            "mid": 54321
+          }
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "http://adx.adform.net/adx?CC=1&fd=1&gdpr=&gdpr_consent=&ip=&rp=4&stid=&bWlkPTU0MzIxJnJjdXI9VVNE"
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": [
+          {
+            "response": "vast_content",
+            "vast_content": "<vast>",
+            "win_bid": 0.5,
+            "win_cur": "USD",
+            "width": 640,
+            "height": 480,
+            "deal_id": null,
+            "win_crid": "20078830"
+          }
+        ]
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "test-imp-id",
+            "impid": "test-imp-id",
+            "price": 0.5,
+            "adm": "<vast>",
+            "crid": "20078830",
+            "w": 640,
+            "h": 480
+          },
+          "type": "video"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/adform/adformtest/params/race/video.json b/adapters/adform/adformtest/params/race/video.json
new file mode 100644
index 00000000..51f8f1b9
--- /dev/null
+++ b/adapters/adform/adformtest/params/race/video.json
@@ -0,0 +1,3 @@
+{
+    "mid": "858300"
+}
diff --git a/adapters/beachfront/beachfronttest/exemplary/minimal-banner.json b/adapters/beachfront/beachfronttest/exemplary/minimal-banner.json
index 51ce4e92..6672e2af 100644
--- a/adapters/beachfront/beachfronttest/exemplary/minimal-banner.json
+++ b/adapters/beachfront/beachfronttest/exemplary/minimal-banner.json
@@ -24,7 +24,6 @@
       }
     ]
   },
-
   "httpCalls": [
     {
       "expectedRequest": {
@@ -57,38 +56,40 @@
           "ua": "",
           "adapterName": "BF_PREBID_S2S",
           "adapterVersion": "0.9.0",
-          "user": {
-          }
+          "user": {}
         }
       },
       "mockResponse": {
         "status": 200,
         "body": [
           {
-            "crid":"crid_1",
-            "price":2.942808,
-            "w":300,
-            "h":250,
-            "slot":"div-gpt-ad-1460505748561-0",
-            "adm":"<div id=\"44861168\"><script>!function(){console.log\"Hello, ad.\";}();<\/script><\/div>"
+            "crid": "crid_1",
+            "price": 2.942808,
+            "w": 300,
+            "h": 250,
+            "slot": "div-gpt-ad-1460505748561-0",
+            "adm": "<div id=\"44861168\"><script>!function(){console.log\"Hello, ad.\";}();<\/script><\/div>"
           }
         ]
       }
     }
   ],
-
-  "expectedBids": [
+  "expectedBidResponses": [
     {
-      "bid": {
-        "id": "12345678",
-        "impid": "test-slot",
-        "price": 2,
-        "adm": "\u003cdiv id=\"12345678\" style=\"width:300;height:250;background-color:black;color:white;\"\u003e300x250\u003c\\div\u003e",
-        "crid": "crid_1",
-        "w": 300,
-        "h": 250
-      },
-      "type": "banner"
+      "bids": [
+        {
+          "bid": {
+            "id": "div-gpt-ad-1460505748561-0Banner",
+            "impid": "div-gpt-ad-1460505748561-0",
+            "price": 2.942808,
+            "adm": "<div id=\"44861168\"><script>!function(){console.log\"Hello, ad.\";}();<\/script><\/div>",
+            "crid": "crid_1",
+            "w": 300,
+            "h": 250
+          },
+          "type": "banner"
+        }
+      ]
     }
   ]
 }
diff --git a/adapters/beachfront/beachfronttest/exemplary/simple-adm-video.json b/adapters/beachfront/beachfronttest/exemplary/simple-adm-video.json
index 0ff7790b..cb1f4c20 100644
--- a/adapters/beachfront/beachfronttest/exemplary/simple-adm-video.json
+++ b/adapters/beachfront/beachfronttest/exemplary/simple-adm-video.json
@@ -1,34 +1,33 @@
 {
-  "mockBidRequest":
-  {
-    "id":"61b87329-8790-47b7-90dd-c53ae7ce1723",
-    "tmax":1000,
-    "source":{
-      "tid":"61b87329-8790-47b7-90dd-c53ae7ce1723"
+  "mockBidRequest": {
+    "id": "61b87329-8790-47b7-90dd-c53ae7ce1723",
+    "tmax": 1000,
+    "source": {
+      "tid": "61b87329-8790-47b7-90dd-c53ae7ce1723"
     },
-    "imp":[
+    "imp": [
       {
-        "id":"video1",
-        "ext":{
-          "bidder":{
-            "videoResponseType" : "adm",
-            "bidfloor":0.01,
-            "appId":"11bc5dd5-7421-4dd8-c926-40fa653bec76"
+        "id": "video1",
+        "ext": {
+          "bidder": {
+            "videoResponseType": "adm",
+            "bidfloor": 0.01,
+            "appId": "11bc5dd5-7421-4dd8-c926-40fa653bec76"
           }
         },
-        "video":{
-          "mimes":[
+        "video": {
+          "mimes": [
             "video/mp4"
           ],
-          "context":"instream",
-          "w":300,
-          "h":250
+          "context": "instream",
+          "w": 300,
+          "h": 250
         }
       }
     ],
-    "site":{
-      "publisher":{
-        "id":"publisher1"
+    "site": {
+      "publisher": {
+        "id": "publisher1"
       },
       "page": "http://example.com/whatever/something.html"
     },
@@ -42,7 +41,6 @@
       "ua": "Opera/9.80 (X11; Linux i686; Ubuntu/14.10) Presto/2.12.388 Version/12.16"
     }
   },
-
   "httpCalls": [
     {
       "expectedRequest": {
@@ -63,8 +61,8 @@
             }
           ],
           "site": {
-            "publisher":{
-              "id":"publisher1"
+            "publisher": {
+              "id": "publisher1"
             },
             "page": "http://example.com/whatever/something.html",
             "domain": "example.com"
@@ -75,10 +73,9 @@
             "ip": "192.168.255.255"
           },
           "source": {
-           "tid": "61b87329-8790-47b7-90dd-c53ae7ce1723"
+            "tid": "61b87329-8790-47b7-90dd-c53ae7ce1723"
           },
           "tmax": 1000,
-
           "cur": [
             "USD"
           ],
@@ -87,12 +84,12 @@
             "buyeruid": "some-buyer"
           }
         }
-        },
+      },
       "mockResponse": {
-        "status" : 200,
+        "status": 200,
         "body": {
-          "id":"61b87329-8790-47b7-90dd-c53ae7ce1723",
-          "seatBid":[
+          "id": "61b87329-8790-47b7-90dd-c53ae7ce1723",
+          "seatBid": [
             {
               "bid": [
                 {
@@ -122,31 +119,32 @@
       }
     }
   ],
-
-  "expectedBids": [
+  "expectedBidResponses": [
     {
-
-      "bid":[
+      "bids": [
         {
-          "id": "video1AdmVideo",
-          "impid": "video1",
-          "price": 20,
-          "adm": "<?xml version=\"1.0\" encoding=\"utf-8\"?><VAST version=\"2.0\"><Ad id=\"VPAID\"><InLine><AdSystem version=\"2.0\">IO</AdSystem><AdTitle>Neptune</AdTitle><Impression></Impression><Creatives><Creative sequence=\"1\"><Linear><Duration>00:00:15</Duration><MediaFiles><MediaFile delivery=\"progressive\" width=\"640\" height=\"480\" scalable=\"1\" type=\"application/javascript\" apiFramework=\"VPAID\"><![CDATA[http://player-cdn.beachfrontmedia.com/players/neptune/3/17/5/neptune-player.js]]></MediaFile></MediaFiles><AdParameters><![CDATA[{\"dsp\":\"5d8393417cbc8e5aa4fe2202,0.01\",\"inventory_id\":\"bid:95d219a2-6025-44ba-bff8-1dca987beaae:11bc5dd5-7421-4dd8-c926-40fa653bec76:${AUCTION_PRICE}:20.0\",\"request_endpoint\":\"//useast.bfmio.com/getmu\",\"inventory_type\":\"pre\"}]]></AdParameters></Linear></Creative></Creatives></InLine></Ad></VAST>",
-          "adid": "1088",
-          "adomain": [
-            "beachfront.io"
-          ],
-          "cid": "277",
-          "crid": "532",
-          "w": 300,
-          "h": 250,
-          "ext": {
-            "prebid": {
-              "type": "video"
+          "bid": {
+            "id": "video1AdmVideo",
+            "impid": "video1",
+            "price": 20,
+            "adm": "<?xml version=\"1.0\" encoding=\"utf-8\"?><VAST version=\"2.0\"><Ad id=\"VPAID\"><InLine><AdSystem version=\"2.0\">IO</AdSystem><AdTitle>Neptune</AdTitle><Impression></Impression><Creatives><Creative sequence=\"1\"><Linear><Duration>00:00:15</Duration><MediaFiles><MediaFile delivery=\"progressive\" width=\"640\" height=\"480\" scalable=\"1\" type=\"application/javascript\" apiFramework=\"VPAID\"><![CDATA[http://player-cdn.beachfrontmedia.com/players/neptune/3/17/5/neptune-player.js]]></MediaFile></MediaFiles><AdParameters><![CDATA[{\"dsp\":\"5d8393417cbc8e5aa4fe2202,0.01\",\"inventory_id\":\"bid:95d219a2-6025-44ba-bff8-1dca987beaae:11bc5dd5-7421-4dd8-c926-40fa653bec76:${AUCTION_PRICE}:20.0\",\"request_endpoint\":\"//useast.bfmio.com/getmu\",\"inventory_type\":\"pre\"}]]></AdParameters></Linear></Creative></Creatives></InLine></Ad></VAST>",
+            "adid": "1088",
+            "adomain": [
+              "beachfront.io"
+            ],
+            "cid": "277",
+            "crid": "532",
+            "w": 300,
+            "h": 250,
+            "ext": {
+              "prebid": {
+                "type": "video"
+              }
             }
-          }
+          },
+          "type": "video"
         }
       ]
     }
   ]
-}
\ No newline at end of file
+}
diff --git a/adapters/beachfront/beachfronttest/exemplary/simple-mix.json b/adapters/beachfront/beachfronttest/exemplary/simple-mix.json
index eb5d9b07..cc2b4158 100644
--- a/adapters/beachfront/beachfronttest/exemplary/simple-mix.json
+++ b/adapters/beachfront/beachfronttest/exemplary/simple-mix.json
@@ -1,25 +1,23 @@
 {
-  "mockBidRequest":
-  {
-    "id":"61b87329-8790-47b7-90dd-c53ae7ce1723",
-    "tmax":1000,
-    "source":{
-      "tid":"61b87329-8790-47b7-90dd-c53ae7ce1723"
+  "mockBidRequest": {
+    "id": "61b87329-8790-47b7-90dd-c53ae7ce1723",
+    "tmax": 1000,
+    "source": {
+      "tid": "61b87329-8790-47b7-90dd-c53ae7ce1723"
     },
-    "imp":[
+    "imp": [
       {
-        "id":"mix1",
-        "ext":{
-          "bidder":{
-            "videoResponseType" : "adm",
-            "bidfloor":0.41,
+        "id": "mix1",
+        "ext": {
+          "bidder": {
+            "videoResponseType": "adm",
+            "bidfloor": 0.41,
             "appIds": {
               "banner": "487cf244-5e95-43cb-e7b9-bb540873ca7e",
               "video": "11bc5dd5-7421-4dd8-c926-40fa653bec76"
             }
           }
         },
-
         "banner": {
           "format": [
             {
@@ -28,18 +26,17 @@
             }
           ]
         },
-
-        "video":{
-          "mimes":[
+        "video": {
+          "mimes": [
             "video/mp4"
           ],
-          "context":"instream",
-          "w":300,
-          "h":250
+          "context": "instream",
+          "w": 300,
+          "h": 250
         }
       }
     ],
-    "site":{
+    "site": {
       "page": "http://example.com/somepage.html"
     },
     "user": {
@@ -51,7 +48,6 @@
       "ua": "Opera/9.80 (X11; Linux i686; Ubuntu/14.10) Presto/2.12.388 Version/12.16"
     }
   },
-
   "httpCalls": [
     {
       "expectedRequest": {
@@ -89,24 +85,22 @@
           "ip": "192.168.255.255",
           "requestId": "61b87329-8790-47b7-90dd-c53ae7ce1723"
         }
-      } ,
+      },
       "mockResponse": {
-        "status" : 200,
-        "body" :
-          [
-            {
-              "crid":"crid_1",
-              "price":9.5019655,
-              "w":300,
-              "h":250,
-              "slot":"mix1",
-              "adm":"<div id=\"44861168\"><script>!function(){console.log\"Hello, ad.\";}();<\/script><\/div>"
-            }
-          ]
+        "status": 200,
+        "body": [
+          {
+            "crid": "crid_1",
+            "price": 9.5019655,
+            "w": 300,
+            "h": 250,
+            "slot": "mix1",
+            "adm": "<div id=\"44861168\"><script>!function(){console.log\"Hello, ad.\";}();<\/script><\/div>"
+          }
+        ]
       }
-
-
-    }, {
+    },
+    {
       "expectedRequest": {
         "uri": "https://reachms.bfmio.com/bid.json?exchange_id=11bc5dd5-7421-4dd8-c926-40fa653bec76",
         "body": {
@@ -119,7 +113,6 @@
                 "mimes": [
                   "video/mp4"
                 ]
-
               },
               "bidfloor": 0.41,
               "secure": 0,
@@ -149,7 +142,7 @@
         }
       },
       "mockResponse": {
-        "status" : 200,
+        "status": 200,
         "body": {
           "id": "61b87329-8790-47b7-90dd-c53ae7ce1723",
           "seatbid": [
@@ -179,40 +172,41 @@
       }
     }
   ],
-
-  "expectedBids": [
+  "expectedBidResponses": [
     {
-
-      "bid":[
+      "bids": [
         {
-          "id":"61b87329-8790-47b7-90dd-c53ae7ce1723",
-          "impid":"video1",
-          "price":9.605201,
-          "nurl":"https://evt.bfmio.com/getmu?aid=bid:0d32aea6-d199-4803-a188-bd7cf7be1e12:11bc5dd5-7421-4dd8-c926-40fa653bec76:0.01:0.01&v=1&dsp=5afda03c7c2d2bfaee55a3f0,0.01&i_type=pre",
-          "crid":"0d32aea6-d199-4803-a188-bd7cf7be1e12",
-          "w":1500,
-          "h":1280,
-          "ext":{
-            "prebid":{
-              "type":"video"
-            },
-            "bidder":{
-
-            }
-          }
-        }, {
-          "id": "61b87329-8790-47b7-90dd-c53ae7ce1723",
-          "impid": "mix1",
-          "price": 9.5019655,
-          "adm": "<div id=\"44861168\"><script>!function(){console.log\"Hello, ad.\";}();<\/script><\/div>",
-          "crid": "crid_1",
-          "w": 300,
-          "h": 250,
-          "ext": {
-            "prebid": {
-              "type": "banner"
-            }
-          }
+          "bid": {
+            "id": "mix1Banner",
+            "impid": "mix1",
+            "price": 9.5019655,
+            "adm": "<div id=\"44861168\"><script>!function(){console.log\"Hello, ad.\";}();<\/script><\/div>",
+            "crid": "crid_1",
+            "w": 300,
+            "h": 250
+          },
+          "type": "banner"
+        }
+      ]
+    },
+    {
+      "bids": [
+        {
+          "bid": {
+            "id": "mix1AdmVideo",
+            "impid": "mix1",
+            "price": 20,
+            "adm": "<?xml version=\"1.0\" encoding=\"utf-8\"?><VAST version=\"2.0\"><Ad id=\"VPAID\"><InLine><AdSystem version=\"2.0\">IO</AdSystem><AdTitle>Neptune</AdTitle><Impression></Impression><Creatives><Creative sequence=\"1\"><Linear><Duration>00:00:15</Duration><MediaFiles><MediaFile delivery=\"progressive\" width=\"640\" height=\"480\" scalable=\"1\" type=\"application/javascript\" apiFramework=\"VPAID\"><![CDATA[http://player-cdn.beachfrontmedia.com/players/neptune/3/17/5/neptune-player.js]]></MediaFile></MediaFiles><AdParameters><![CDATA[{\"dsp\":\"5d839458f73decdc1572b7f6,0.41\",\"inventory_id\":\"bid:26826cf7-f8bb-4e47-a636-769d83dd9b4f:11bc5dd5-7421-4dd8-c926-40fa653bec76:${AUCTION_PRICE}:20.0\",\"request_endpoint\":\"//useast.bfmio.com/getmu\",\"inventory_type\":\"pre\"}]]></AdParameters></Linear></Creative></Creatives></InLine></Ad></VAST>",
+            "adid": "1088",
+            "adomain": [
+              "beachfront.io"
+            ],
+            "cid": "277",
+            "crid": "532",
+            "w": 300,
+            "h": 250
+          },
+          "type": "video"
         }
       ]
     }
diff --git a/adapters/beachfront/beachfronttest/exemplary/simple-nurl-video.json b/adapters/beachfront/beachfronttest/exemplary/simple-nurl-video.json
index cc3bfe34..0f96d9f2 100644
--- a/adapters/beachfront/beachfronttest/exemplary/simple-nurl-video.json
+++ b/adapters/beachfront/beachfronttest/exemplary/simple-nurl-video.json
@@ -1,33 +1,32 @@
 {
-  "mockBidRequest":
-  {
-    "id":"61b87329-8790-47b7-90dd-c53ae7ce1723",
-    "tmax":1000,
-    "source":{
-      "tid":"61b87329-8790-47b7-90dd-c53ae7ce1723"
+  "mockBidRequest": {
+    "id": "61b87329-8790-47b7-90dd-c53ae7ce1723",
+    "tmax": 1000,
+    "source": {
+      "tid": "61b87329-8790-47b7-90dd-c53ae7ce1723"
     },
-    "imp":[
+    "imp": [
       {
-        "id":"video1",
-        "ext":{
-          "bidder":{
-            "bidfloor":0.01,
-            "appId":"11bc5dd5-7421-4dd8-c926-40fa653bec76"
+        "id": "video1",
+        "ext": {
+          "bidder": {
+            "bidfloor": 0.01,
+            "appId": "11bc5dd5-7421-4dd8-c926-40fa653bec76"
           }
         },
-        "video":{
-          "mimes":[
+        "video": {
+          "mimes": [
             "video/mp4"
           ],
-          "context":"instream",
-          "w":300,
-          "h":250
+          "context": "instream",
+          "w": 300,
+          "h": 250
         }
       }
     ],
-    "site":{
-      "publisher":{
-        "id":"publisher1"
+    "site": {
+      "publisher": {
+        "id": "publisher1"
       },
       "page": "http://example.com/whatever/something.html"
     },
@@ -41,7 +40,6 @@
       "ua": "Opera/9.80 (X11; Linux i686; Ubuntu/14.10) Presto/2.12.388 Version/12.16"
     }
   },
-
   "httpCalls": [
     {
       "expectedRequest": {
@@ -63,8 +61,8 @@
             }
           ],
           "site": {
-            "publisher":{
-              "id":"publisher1"
+            "publisher": {
+              "id": "publisher1"
             },
             "page": "http://example.com/whatever/something.html",
             "domain": "example.com"
@@ -75,10 +73,9 @@
             "ip": "192.168.255.255"
           },
           "source": {
-           "tid": "61b87329-8790-47b7-90dd-c53ae7ce1723"
+            "tid": "61b87329-8790-47b7-90dd-c53ae7ce1723"
           },
           "tmax": 1000,
-
           "cur": [
             "USD"
           ],
@@ -89,58 +86,57 @@
         }
       },
       "mockResponse": {
-        "status" : 200,
+        "status": 200,
         "body": {
-          "id":"61b87329-8790-47b7-90dd-c53ae7ce1723",
-          "seatBid":[
-              {
-                "bid": [
-                  {
-                    "id": "video1NurlVideo",
-                    "impid": "video1",
-                    "price": 20,
-                    "nurl": "https://useast.bfmio.com/getBids?aid=bid:70b99087-1b92-4e81-bc42-05c940fd6014:11bc5dd5-7421-4dd8-c926-40fa653bec76:20.0:20.0&v=1&dsp=5d8391a85f35945a70c9ddf0,0.01&i_type=pre",
-                    "crid": "70b99087-1b92-4e81-bc42-05c940fd6014",
-                    "w": 300,
-                    "h": 250,
-                    "ext": {
-                      "prebid": {
-                        "type": "video"
-                      },
-                      "bidder": {}
-                    }
+          "id": "61b87329-8790-47b7-90dd-c53ae7ce1723",
+          "seatBid": [
+            {
+              "bid": [
+                {
+                  "id": "video1NurlVideo",
+                  "impid": "video1",
+                  "price": 20,
+                  "nurl": "https://useast.bfmio.com/getBids?aid=bid:70b99087-1b92-4e81-bc42-05c940fd6014:11bc5dd5-7421-4dd8-c926-40fa653bec76:20.0:20.0&v=1&dsp=5d8391a85f35945a70c9ddf0,0.01&i_type=pre",
+                  "crid": "70b99087-1b92-4e81-bc42-05c940fd6014",
+                  "w": 300,
+                  "h": 250,
+                  "ext": {
+                    "prebid": {
+                      "type": "video"
+                    },
+                    "bidder": {}
                   }
-                ],
-                "seat": "beachfront"
-              }
-            ]
+                }
+              ],
+              "seat": "beachfront"
+            }
+          ]
         }
       }
     }
   ],
-
-  "expectedBids": [
+  "expectedBidResponses": [
     {
-
-      "bid":[
+      "bids": [
         {
-          "id":"61b87329-8790-47b7-90dd-c53ae7ce1723",
-          "impid":"video1",
-          "price":9.605201,
-          "nurl":"https://evt.bfmio.com/getmu?aid=bid:0d32aea6-d199-4803-a188-bd7cf7be1e12:11bc5dd5-7421-4dd8-c926-40fa653bec76:0.01:0.01&v=1&dsp=5afda03c7c2d2bfaee55a3f0,0.01&i_type=pre",
-          "crid":"0d32aea6-d199-4803-a188-bd7cf7be1e12",
-          "w":1500,
-          "h":1280,
-          "ext":{
-            "prebid":{
-              "type":"video"
-            },
-            "bidder":{
-
+          "bid": {
+            "id": "video1NurlVideo",
+            "impid": "video1",
+            "price": 20,
+            "nurl": "https://useast.bfmio.com/getBids?aid=bid:70b99087-1b92-4e81-bc42-05c940fd6014:11bc5dd5-7421-4dd8-c926-40fa653bec76:20.0:20.0&v=1&dsp=5d8391a85f35945a70c9ddf0,0.01&i_type=pre",
+            "crid": "70b99087-1b92-4e81-bc42-05c940fd6014",
+            "w": 300,
+            "h": 250,
+            "ext": {
+              "prebid": {
+                "type": "video"
+              },
+              "bidder": {}
             }
-          }
+          },
+          "type": "video"
         }
       ]
     }
   ]
-}
\ No newline at end of file
+}
diff --git a/adapters/beachfront/beachfronttest/supplemental/minimal-banner-empty_array-200.json b/adapters/beachfront/beachfronttest/supplemental/minimal-banner-empty_array-200.json
index 7bdbc73c..01207f22 100644
--- a/adapters/beachfront/beachfronttest/supplemental/minimal-banner-empty_array-200.json
+++ b/adapters/beachfront/beachfronttest/supplemental/minimal-banner-empty_array-200.json
@@ -68,5 +68,5 @@
     }
   ],
 
-  "expectedBids": []
+  "expectedBidResponses": []
 }
diff --git a/adapters/beachfront/beachfronttest/supplemental/minimal-mobile-video.json b/adapters/beachfront/beachfronttest/supplemental/minimal-mobile-video.json
index af8b1885..5556f6e7 100644
--- a/adapters/beachfront/beachfronttest/supplemental/minimal-mobile-video.json
+++ b/adapters/beachfront/beachfronttest/supplemental/minimal-mobile-video.json
@@ -2,34 +2,33 @@
   "mockBidRequest": {
     "id": "some_test_ad",
     "app": {
-      "bundle":"_org.prebid.mobile.demoapp",
+      "bundle": "_org.prebid.mobile.demoapp",
       "id": "some-mobile-app"
     },
     "imp": [
       {
-        "id":"video1",
-        "ext":{
-          "bidder":{
-            "bidfloor":0.01,
-            "appId":"11bc5dd5-7421-4dd8-c926-40fa653bec76",
-            "videoResponseType" : "nurl"
+        "id": "video1",
+        "ext": {
+          "bidder": {
+            "bidfloor": 0.01,
+            "appId": "11bc5dd5-7421-4dd8-c926-40fa653bec76",
+            "videoResponseType": "nurl"
           }
         },
-        "video":{
-          "mimes":[
+        "video": {
+          "mimes": [
             "video/mp4"
           ],
-          "w":300,
-          "h":250
+          "w": 300,
+          "h": 250
         }
       }
     ],
-    "device":{
+    "device": {
       "devicetype": 1,
-      "ifa":"bb956625-df13-4568-b60b-eb5cbf76e986"
+      "ifa": "bb956625-df13-4568-b60b-eb5cbf76e986"
     }
   },
-
   "httpCalls": [
     {
       "expectedRequest": {
@@ -48,7 +47,7 @@
               "id": "video1",
               "secure": 0,
               "video": {
-                "mimes":[
+                "mimes": [
                   "video/mp4"
                 ],
                 "h": 250,
@@ -67,71 +66,54 @@
       "mockResponse": {
         "status": 200,
         "body": {
-          "id":"61b87329-8790-47b7-90dd-c53ae7ce1723",
-          "seatBid":[
+          "id": "61b87329-8790-47b7-90dd-c53ae7ce1723",
+          "seatBid": [
             {
-              "bid":[
+              "bid": [
                 {
-                  "w":0,
-                  "h":0,
+                  "w": 0,
+                  "h": 0,
                   "id": "0",
-                  "impid":"",
-                  "price":0.01,
-                  "adid":"",
-                  "nurl":"https://evt.bfmio.com/getmu?aid=bid:0d32aea6-d199-4803-a188-bd7cf7be1e12:11bc5dd5-7421-4dd8-c926-40fa653bec76:0.01:0.01&v=1&dsp=5afda03c7c2d2bfaee55a3f0,0.01&i_type=pre",
-                  "adm":"",
-                  "adomain":[
-
-                  ],
-                  "iurl":"",
-                  "cid":"",
-                  "crid":"",
-                  "attr":[
-
-                  ],
-                  "ext":{
-
-                  },
-                  "cat":[
-
-                  ]
+                  "impid": "",
+                  "price": 0.01,
+                  "adid": "",
+                  "nurl": "https://evt.bfmio.com/getmu?aid=bid:0d32aea6-d199-4803-a188-bd7cf7be1e12:11bc5dd5-7421-4dd8-c926-40fa653bec76:0.01:0.01&v=1&dsp=5afda03c7c2d2bfaee55a3f0,0.01&i_type=pre",
+                  "adm": "",
+                  "adomain": [],
+                  "iurl": "",
+                  "cid": "",
+                  "crid": "",
+                  "attr": [],
+                  "ext": {},
+                  "cat": []
                 }
               ],
-              "seat":"",
-              "group":0,
-              "ext":{
-
-              }
+              "seat": "",
+              "group": 0,
+              "ext": {}
             }
           ]
         }
       }
     }
   ],
-
-  "expectedBids": [
+  "expectedBidResponses": [
     {
-
-      "bid":[
+      "bids": [
         {
-          "id":"61b87329-8790-47b7-90dd-c53ae7ce1723",
-          "impid":"video1",
-          "price":9.605201,
-          "nurl":"https://evt.bfmio.com/getmu?aid=bid:0d32aea6-d199-4803-a188-bd7cf7be1e12:11bc5dd5-7421-4dd8-c926-40fa653bec76:0.01:0.01&v=1&dsp=5afda03c7c2d2bfaee55a3f0,0.01&i_type=pre",
-          "crid":"0d32aea6-d199-4803-a188-bd7cf7be1e12",
-          "w":1500,
-          "h":1280,
-          "ext":{
-            "prebid":{
-              "type":"video"
-            },
-            "bidder":{
-
-            }
-          }
+          "bid": {
+            "id": "video1NurlVideo",
+            "impid": "video1",
+            "price": 0.01,
+            "nurl": "https://evt.bfmio.com/getmu?aid=bid:0d32aea6-d199-4803-a188-bd7cf7be1e12:11bc5dd5-7421-4dd8-c926-40fa653bec76:0.01:0.01&v=1&dsp=5afda03c7c2d2bfaee55a3f0,0.01&i_type=pre",
+            "crid": "0d32aea6-d199-4803-a188-bd7cf7be1e12",
+            "w": 300,
+            "h": 250,
+            "ext":{}
+          },
+          "type": "video"
         }
       ]
     }
   ]
-
 }
diff --git a/adapters/beachfront/beachfronttest/supplemental/minimal-site-banner.json b/adapters/beachfront/beachfronttest/supplemental/minimal-site-banner.json
index 27b24357..a12f7fd9 100644
--- a/adapters/beachfront/beachfronttest/supplemental/minimal-site-banner.json
+++ b/adapters/beachfront/beachfronttest/supplemental/minimal-site-banner.json
@@ -1,94 +1,95 @@
 {
-    "mockBidRequest": {
-      "id": "some_test_ad",
-      "site": {
-        "page": "test.opposingviews.com"
-      },
-      "imp": [
-        {
-          "bidfloor": 0.02,
-          "banner": {
-            "format": [
-              {
-                "w": 300,
-                "h": 250
-              }
-            ]
-          },
-          "ext": {
-            "bidder": {
-              "bidfloor": 0.02,
-              "appId": "3b16770b-17af-4d22-daff-9606bdf2c9c3"
-            }
-          }
-        }
-      ]
+  "mockBidRequest": {
+    "id": "some_test_ad",
+    "site": {
+      "page": "test.opposingviews.com"
     },
-  
-    "httpCalls": [
+    "imp": [
       {
-        "expectedRequest": {
-          "uri": "https://display.bfmio.com/prebid_display",
-          "body": {
-            "slots": [
-              {
-                "slot": "",
-                "id": "3b16770b-17af-4d22-daff-9606bdf2c9c3",
-                "bidfloor": 0.02,
-                "sizes": [
-                  {
-                    "w": 300,
-                    "h": 250
-                  }
-                ]
-              }
-            ],
-            "domain": "test.opposingviews.com",
-            "page": "test.opposingviews.com",
-            "requestId": "some_test_ad",
-            "referrer": "",
-            "search": "",
-            "secure": 0,
-            "isMobile": 0,
-            "ip": "",
-            "deviceModel": "",
-            "deviceOs": "",
-            "dnt": 0,
-            "ua": "",
-            "adapterName": "BF_PREBID_S2S",
-            "adapterVersion": "0.9.0",
-            "user": {
-            }
-          }
-        },
-        "mockResponse": {
-          "status": 200,
-          "body": [
+        "bidfloor": 0.02,
+        "banner": {
+          "format": [
             {
-              "crid":"crid_1",
-              "price":2.942808,
-              "w":300,
-              "h":250,
-              "slot":"div-gpt-ad-1460505748561-0",
-              "adm":"<div id=\"44861168\"><script>!function(){console.log\"Hello, ad.\";}();<\/script><\/div>"
+              "w": 300,
+              "h": 250
             }
           ]
-        }
-      }
-    ],
-  
-    "expectedBids": [
-      {
-        "bid": {
-          "id": "12345678",
-          "impid": "test-slot",
-          "price": 2,
-          "adm": "\u003cdiv id=\"12345678\" style=\"width:300;height:250;background-color:black;color:white;\"\u003e300x250\u003c\\div\u003e",
-          "crid": "crid_1",
-          "w": 300,
-          "h": 250
         },
-        "type": "banner"
+        "ext": {
+          "bidder": {
+            "bidfloor": 0.02,
+            "appId": "3b16770b-17af-4d22-daff-9606bdf2c9c3"
+          }
+        }
       }
     ]
-  }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://display.bfmio.com/prebid_display",
+        "body": {
+          "slots": [
+            {
+              "slot": "",
+              "id": "3b16770b-17af-4d22-daff-9606bdf2c9c3",
+              "bidfloor": 0.02,
+              "sizes": [
+                {
+                  "w": 300,
+                  "h": 250
+                }
+              ]
+            }
+          ],
+          "domain": "test.opposingviews.com",
+          "page": "test.opposingviews.com",
+          "requestId": "some_test_ad",
+          "referrer": "",
+          "search": "",
+          "secure": 0,
+          "isMobile": 0,
+          "ip": "",
+          "deviceModel": "",
+          "deviceOs": "",
+          "dnt": 0,
+          "ua": "",
+          "adapterName": "BF_PREBID_S2S",
+          "adapterVersion": "0.9.0",
+          "user": {}
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": [
+          {
+            "crid": "crid_1",
+            "price": 2.942808,
+            "w": 300,
+            "h": 250,
+            "slot": "div-gpt-ad-1460505748561-0",
+            "adm": "<div id=\"44861168\"><script>!function(){console.log\"Hello, ad.\";}();<\/script><\/div>"
+          }
+        ]
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "bids": [
+        {
+          "bid": {
+            "id": "div-gpt-ad-1460505748561-0Banner",
+            "impid": "div-gpt-ad-1460505748561-0",
+            "price": 2.942808,
+            "adm": "<div id=\"44861168\"><script>!function(){console.log\"Hello, ad.\";}();<\/script><\/div>",
+            "crid": "crid_1",
+            "w": 300,
+            "h": 250
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/beachfront/beachfronttest/supplemental/mobile-banner.json b/adapters/beachfront/beachfronttest/supplemental/mobile-banner.json
index ea38d7ad..17cc7d32 100644
--- a/adapters/beachfront/beachfronttest/supplemental/mobile-banner.json
+++ b/adapters/beachfront/beachfronttest/supplemental/mobile-banner.json
@@ -109,22 +109,20 @@
     }
   ],
 
-  "expectedBids": [
+  "expectedBidResponses": [
     {
-      "Bid": {
-        "id": "763e3312-19d5-4b07-a61d-890147e863a1",
-        "impid": "div-gpt-ad-1460505748561-0",
-        "price": 2.942808,
-        "adm": "\u003cdiv id=\"12345678\" style=\"width:300;height:250;background-color:black;color:white;\"\u003e300x250\u003c\\div\u003e",
-        "crid": "crid_1",
-        "w": 300,
-        "h": 250
-      },
-      "ext": {
-        "prebid": {
-          "type": "banner"
-        }
-      }
+      "bids": [{
+        "bid": {
+          "id": "div-gpt-ad-1460505748561-0Banner",
+          "impid": "div-gpt-ad-1460505748561-0",
+          "price": 2.942808,
+          "adm":"<div id=\"44861168\"><script>!function(){console.log\"Hello, ad.\";}();<\/script><\/div>",
+          "crid": "crid_1",
+          "w": 300,
+          "h": 250
+        },
+        "type": "banner"
+      }]
     }
   ]
 }
diff --git a/adapters/beachfront/beachfronttest/supplemental/multi-banner.json b/adapters/beachfront/beachfronttest/supplemental/multi-banner.json
index 46699511..1d355dba 100644
--- a/adapters/beachfront/beachfronttest/supplemental/multi-banner.json
+++ b/adapters/beachfront/beachfronttest/supplemental/multi-banner.json
@@ -119,18 +119,20 @@
     }
   ],
 
-  "expectedBids": [
+  "expectedBidResponses": [
     {
-      "Bid": {
-        "id": "12345678",
-        "impid": "test-slot",
-        "price": 2,
-        "adm": "\u003cdiv id=\"12345678\" style=\"width:300;height:250;background-color:black;color:white;\"\u003e300x250\u003c\\div\u003e",
-        "crid": "crid_1",
-        "w": 300,
-        "h": 250
-      },
-      "type": "banner"
+      "bids": [{
+        "bid": {
+          "id": "div-gpt-ad-1460505748561-0Banner",
+          "impid": "div-gpt-ad-1460505748561-0",
+          "price": 2.942808,
+          "adm":"<div id=\"44861168\"><script>!function(){console.log\"Hello, ad.\";}();<\/script><\/div>",
+          "crid": "crid_1",
+          "w": 300,
+          "h": 250
+        },
+        "type": "banner"
+      }]
     }
   ]
 }
diff --git a/adapters/beachfront/beachfronttest/supplemental/multi-video.json b/adapters/beachfront/beachfronttest/supplemental/multi-video.json
index 05f02a4d..8ccd0a8d 100644
--- a/adapters/beachfront/beachfronttest/supplemental/multi-video.json
+++ b/adapters/beachfront/beachfronttest/supplemental/multi-video.json
@@ -1,35 +1,34 @@
-{ "mockBidRequest":
-  {
-    "id":"61b87329-8790-47b7-90dd-c53ae7ce1723",
-    "tmax":1000,
-    "source":{
-      "tid":"61b87329-8790-47b7-90dd-c53ae7ce1723"
+{
+  "mockBidRequest": {
+    "id": "61b87329-8790-47b7-90dd-c53ae7ce1723",
+    "tmax": 1000,
+    "source": {
+      "tid": "61b87329-8790-47b7-90dd-c53ae7ce1723"
     },
-    "imp":[
+    "imp": [
       {
-        "id":"multivideo",
-        "ext":{
-          "bidder":{
-            "bidfloor":3.01,
+        "id": "multivideo",
+        "ext": {
+          "bidder": {
+            "bidfloor": 3.01,
             "videoResponseType": "both",
-            "appId":"11bc5dd5-7421-4dd8-c926-40fa653bec76"
+            "appId": "11bc5dd5-7421-4dd8-c926-40fa653bec76"
           }
         },
-        "video":{
-          "mimes":[
-            "video/mp4", "video/webm"
+        "video": {
+          "mimes": [
+            "video/mp4",
+            "video/webm"
           ],
-          "context":"instream",
-          "w":600,
-          "h":450
+          "context": "instream",
+          "w": 600,
+          "h": 450
         }
       }
-
-
     ],
-    "site":{
-      "publisher":{
-        "id":"publisher1"
+    "site": {
+      "publisher": {
+        "id": "publisher1"
       },
       "page": "http://example.com/whatever"
     },
@@ -42,16 +41,15 @@
       "ua": "Opera/9.80 (X11; Linux i686; Ubuntu/14.10) Presto/2.12.388 Version/12.16"
     }
   },
-
   "httpCalls": [
-      {
+    {
       "expectedRequest": {
         "uri": "https://reachms.bfmio.com/bid.json?exchange_id=11bc5dd5-7421-4dd8-c926-40fa653bec76",
         "body": {
           "id": "61b87329-8790-47b7-90dd-c53ae7ce1723",
           "imp": [
             {
-              "id":"multivideo",
+              "id": "multivideo",
               "secure": 0,
               "video": {
                 "mimes": [
@@ -118,132 +116,120 @@
           "cur": "USD"
         }
       }
-    }, {
-
+    },
+    {
       "expectedRequest": {
         "uri": "https://reachms.bfmio.com/bid.json?exchange_id=11bc5dd5-7421-4dd8-c926-40fa653bec76&prebidserver",
         "body": {
-          "isPrebid":true,
-          "id":"61b87329-8790-47b7-90dd-c53ae7ce1723",
-          "imp":[
+          "isPrebid": true,
+          "id": "61b87329-8790-47b7-90dd-c53ae7ce1723",
+          "imp": [
             {
-              "id":"multivideo",
+              "id": "multivideo",
               "secure": 0,
-              "video":{
-                "mimes":[
+              "video": {
+                "mimes": [
                   "video/mp4",
                   "video/webm"
                 ],
-                "w":600,
-                "h":450
+                "w": 600,
+                "h": 450
               },
-              "bidfloor":3.01
+              "bidfloor": 3.01
             }
           ],
-          "site":{
-            "domain":"example.com",
-            "page":"http://example.com/whatever",
-            "publisher":{
-              "id":"publisher1"
+          "site": {
+            "domain": "example.com",
+            "page": "http://example.com/whatever",
+            "publisher": {
+              "id": "publisher1"
             }
           },
-          "device":{
-            "ua":"Opera/9.80 (X11; Linux i686; Ubuntu/14.10) Presto/2.12.388 Version/12.16",
-            "ip":"192.168.255.255",
-            "devicetype":2
+          "device": {
+            "ua": "Opera/9.80 (X11; Linux i686; Ubuntu/14.10) Presto/2.12.388 Version/12.16",
+            "ip": "192.168.255.255",
+            "devicetype": 2
           },
-          "user":{
-            "id":"some-user",
-            "buyeruid":"some-buyer"
+          "user": {
+            "id": "some-user",
+            "buyeruid": "some-buyer"
           },
-          "tmax":1000,
-          "cur":[
+          "tmax": 1000,
+          "cur": [
             "USD"
           ],
-          "source":{
-            "tid":"61b87329-8790-47b7-90dd-c53ae7ce1723"
+          "source": {
+            "tid": "61b87329-8790-47b7-90dd-c53ae7ce1723"
           }
         }
       },
-
       "mockResponse": {
         "status": 200,
         "body": {
-          "id":"61b87329-8790-47b7-90dd-c53ae7ce1723",
-          "seatBid":[
+          "id": "61b87329-8790-47b7-90dd-c53ae7ce1723",
+          "seatBid": [
             {
-              "bid":[
+              "bid": [
                 {
-                  "id":"multivideo",
-                  "price":20.0,
-                  "nurl":"https://useast.bfmio.com/getBids?aid=bid:b489a745-e577-4fcb-815d-0b6a1e052ec7:11bc5dd5-7421-4dd8-c926-40fa653bec76:20.0:20.0&v=1&dsp=5d8231a48d308d0a1f9b4526,0.61&i_type=pre",
-                  "adomain":[
-
-                  ],
-                  "cat":[
-
-                  ],
-                  "attr":[
-
-                  ],
-                  "w":0,
-                  "h":0,
-                  "ext":{
-
-                  }
+                  "id": "multivideo",
+                  "price": 20.0,
+                  "nurl": "https://useast.bfmio.com/getBids?aid=bid:b489a745-e577-4fcb-815d-0b6a1e052ec7:11bc5dd5-7421-4dd8-c926-40fa653bec76:20.0:20.0&v=1&dsp=5d8231a48d308d0a1f9b4526,0.61&i_type=pre",
+                  "adomain": [],
+                  "cat": [],
+                  "attr": [],
+                  "w": 0,
+                  "h": 0,
+                  "ext": {}
                 }
               ],
-              "group":0,
-              "ext":{
-
-              }
+              "group": 0,
+              "ext": {}
             }
           ]
         }
       }
     }
   ],
-
-  "expectedBids": [
+  "expectedBidResponses": [
     {
-
-      "bid": [
+      "bids": [
         {
-          "id": "multivideoNurlVideo",
-          "impid": "multivideo",
-          "price": 20,
-          "nurl": "https://useast.bfmio.com/getBids?aid=bid:6daefa55-0e8b-4366-b238-bb2de01b0a7c:11bc5dd5-7421-4dd8-c926-40fa653bec76:20.0:20.0&v=1&dsp=5d8395cb13fe4ef822af7293,3.01&i_type=pre",
-          "crid": "6daefa55-0e8b-4366-b238-bb2de01b0a7c",
-          "w": 600,
-          "h": 450,
-          "ext": {
-            "prebid": {
-              "type": "video"
-            },
-            "bidder": {}
-          }
-        },
+          "bid": {
+            "id": "multivideoAdmVideo",
+            "impid": "multivideo",
+            "price": 20,
+            "adm": "<?xml version=\"1.0\" encoding=\"utf-8\"?><VAST version=\"2.0\"><Ad id=\"VPAID\"><InLine><AdSystem version=\"2.0\">IO</AdSystem><AdTitle>Neptune</AdTitle><Impression></Impression><Creatives><Creative sequence=\"1\"><Linear><Duration>00:00:15</Duration><MediaFiles><MediaFile delivery=\"progressive\" width=\"640\" height=\"480\" scalable=\"1\" type=\"application/javascript\" apiFramework=\"VPAID\"><![CDATA[http://player-cdn.beachfrontmedia.com/players/neptune/3/17/5/neptune-player.js]]></MediaFile></MediaFiles><AdParameters><![CDATA[{\"dsp\":\"5d8231a4254b2eca804965a3,3.01\",\"inventory_id\":\"bid:1680831c-83a2-42a3-a790-a683d780749a:11bc5dd5-7421-4dd8-c926-40fa653bec76:${AUCTION_PRICE}:20.0\",\"request_endpoint\":\"//useast.bfmio.com/getmu\",\"inventory_type\":\"pre\"}]]></AdParameters></Linear></Creative></Creatives></InLine></Ad></VAST>",
+            "adid": "1088",
+            "adomain": [
+              "beachfront.io"
+            ],
+            "cid": "277",
+            "crid": "532",
+            "w": 600,
+            "h": 450
+          },
+          "type": "video"
+        }
+      ],
+      "seat": "beachfront"
+    },
+    {
+      "bids": [
         {
-          "id": "multivideoAdmVideo",
-          "impid": "multivideo",
-          "price": 20,
-          "adm": "<?xml version=\"1.0\" encoding=\"utf-8\"?><VAST version=\"2.0\"><Ad id=\"VPAID\"><InLine><AdSystem version=\"2.0\">IO</AdSystem><AdTitle>Neptune</AdTitle><Impression></Impression><Creatives><Creative sequence=\"1\"><Linear><Duration>00:00:15</Duration><MediaFiles><MediaFile delivery=\"progressive\" width=\"640\" height=\"480\" scalable=\"1\" type=\"application/javascript\" apiFramework=\"VPAID\"><![CDATA[http://player-cdn.beachfrontmedia.com/players/neptune/3/17/5/neptune-player.js]]></MediaFile></MediaFiles><AdParameters><![CDATA[{\"dsp\":\"5d8395cb9107b5524e1fd6be,3.01\",\"inventory_id\":\"bid:f3ff87ed-7d92-48d5-8610-40023c318afc:11bc5dd5-7421-4dd8-c926-40fa653bec76:${AUCTION_PRICE}:20.0\",\"request_endpoint\":\"//useast.bfmio.com/getmu\",\"inventory_type\":\"pre\"}]]></AdParameters></Linear></Creative></Creatives></InLine></Ad></VAST>",
-          "adid": "1088",
-          "adomain": [
-            "beachfront.io"
-          ],
-          "cid": "277",
-          "crid": "532",
-          "w": 600,
-          "h": 450,
-          "ext": {
-            "prebid": {
-              "type": "video"
-            }
-          }
+          "bid": {
+            "id": "multivideoNurlVideo",
+            "impid": "multivideo",
+            "price": 20,
+            "nurl": "https://useast.bfmio.com/getBids?aid=bid:b489a745-e577-4fcb-815d-0b6a1e052ec7:11bc5dd5-7421-4dd8-c926-40fa653bec76:20.0:20.0&v=1&dsp=5d8231a48d308d0a1f9b4526,0.61&i_type=pre",
+            "crid": "b489a745-e577-4fcb-815d-0b6a1e052ec7",
+            "w": 600,
+            "h": 450,
+            "ext": {}
+          },
+          "type": "video"
         }
       ],
       "seat": "beachfront"
     }
   ]
-}
\ No newline at end of file
+}
diff --git a/adapters/beachfront/beachfronttest/supplemental/unmarshal-error-banner.json b/adapters/beachfront/beachfronttest/supplemental/unmarshal-error-banner.json
index 8a9e6b05..fc1bb910 100644
--- a/adapters/beachfront/beachfronttest/supplemental/unmarshal-error-banner.json
+++ b/adapters/beachfront/beachfronttest/supplemental/unmarshal-error-banner.json
@@ -32,4 +32,4 @@
       "comparison": "literal"
     }
   ]
-}
\ No newline at end of file
+}
diff --git a/adapters/beachfront/beachfronttest/supplemental/unmarshal-error-but-another-good-video.json b/adapters/beachfront/beachfronttest/supplemental/unmarshal-error-but-another-good-video.json
index dec0b51b..963a2adc 100644
--- a/adapters/beachfront/beachfronttest/supplemental/unmarshal-error-but-another-good-video.json
+++ b/adapters/beachfront/beachfronttest/supplemental/unmarshal-error-but-another-good-video.json
@@ -169,4 +169,4 @@
       "comparison": "literal"
     }
   ]
-}
\ No newline at end of file
+}
diff --git a/adapters/beachfront/beachfronttest/supplemental/unmarshal-error-video.json b/adapters/beachfront/beachfronttest/supplemental/unmarshal-error-video.json
index a2f028ec..3082ff20 100644
--- a/adapters/beachfront/beachfronttest/supplemental/unmarshal-error-video.json
+++ b/adapters/beachfront/beachfronttest/supplemental/unmarshal-error-video.json
@@ -38,4 +38,4 @@
       "comparison": "literal"
     }
   ]
-}
\ No newline at end of file
+}
diff --git a/config/accounts_test.go b/config/accounts_test.go
index ca7e8938..bfca27d8 100644
--- a/config/accounts_test.go
+++ b/config/accounts_test.go
@@ -10,36 +10,12 @@ func TestAccountGDPREnabledForIntegrationType(t *testing.T) {
 	trueValue, falseValue := true, false
 
 	tests := []struct {
-		description          string
-		giveIntegrationType  IntegrationType
-		giveGDPREnabled      *bool
-		giveAMPGDPREnabled   *bool
-		giveAppGDPREnabled   *bool
-		giveVideoGDPREnabled *bool
-		giveWebGDPREnabled   *bool
-		wantEnabled          *bool
+		description         string
+		giveIntegrationType IntegrationType
+		giveGDPREnabled     *bool
+		giveWebGDPREnabled  *bool
+		wantEnabled         *bool
 	}{
-		{
-			description:         "GDPR AMP integration enabled, general GDPR disabled",
-			giveIntegrationType: IntegrationTypeAMP,
-			giveGDPREnabled:     &falseValue,
-			giveAMPGDPREnabled:  &trueValue,
-			wantEnabled:         &trueValue,
-		},
-		{
-			description:         "GDPR App integration enabled, general GDPR disabled",
-			giveIntegrationType: IntegrationTypeApp,
-			giveGDPREnabled:     &falseValue,
-			giveAppGDPREnabled:  &trueValue,
-			wantEnabled:         &trueValue,
-		},
-		{
-			description:          "GDPR Video integration enabled, general GDPR disabled",
-			giveIntegrationType:  IntegrationTypeVideo,
-			giveGDPREnabled:      &falseValue,
-			giveVideoGDPREnabled: &trueValue,
-			wantEnabled:          &trueValue,
-		},
 		{
 			description:         "GDPR Web integration enabled, general GDPR disabled",
 			giveIntegrationType: IntegrationTypeWeb,
@@ -47,13 +23,6 @@ func TestAccountGDPREnabledForIntegrationType(t *testing.T) {
 			giveWebGDPREnabled:  &trueValue,
 			wantEnabled:         &trueValue,
 		},
-		{
-			description:         "Web integration enabled, general GDPR unspecified",
-			giveIntegrationType: IntegrationTypeWeb,
-			giveGDPREnabled:     nil,
-			giveWebGDPREnabled:  &trueValue,
-			wantEnabled:         &trueValue,
-		},
 		{
 			description:         "GDPR Web integration disabled, general GDPR enabled",
 			giveIntegrationType: IntegrationTypeWeb,
@@ -61,13 +30,6 @@ func TestAccountGDPREnabledForIntegrationType(t *testing.T) {
 			giveWebGDPREnabled:  &falseValue,
 			wantEnabled:         &falseValue,
 		},
-		{
-			description:         "GDPR Web integration disabled, general GDPR unspecified",
-			giveIntegrationType: IntegrationTypeWeb,
-			giveGDPREnabled:     nil,
-			giveWebGDPREnabled:  &falseValue,
-			wantEnabled:         &falseValue,
-		},
 		{
 			description:         "GDPR Web integration unspecified, general GDPR disabled",
 			giveIntegrationType: IntegrationTypeWeb,
@@ -95,11 +57,8 @@ func TestAccountGDPREnabledForIntegrationType(t *testing.T) {
 		account := Account{
 			GDPR: AccountGDPR{
 				Enabled: tt.giveGDPREnabled,
-				IntegrationEnabled: AccountGDPRIntegration{
-					AMP:   tt.giveAMPGDPREnabled,
-					App:   tt.giveAppGDPREnabled,
-					Video: tt.giveVideoGDPREnabled,
-					Web:   tt.giveWebGDPREnabled,
+				IntegrationEnabled: AccountIntegration{
+					Web: tt.giveWebGDPREnabled,
 				},
 			},
 		}
@@ -114,3 +73,171 @@ func TestAccountGDPREnabledForIntegrationType(t *testing.T) {
 		}
 	}
 }
+
+func TestAccountCCPAEnabledForIntegrationType(t *testing.T) {
+	trueValue, falseValue := true, false
+
+	tests := []struct {
+		description         string
+		giveIntegrationType IntegrationType
+		giveCCPAEnabled     *bool
+		giveWebCCPAEnabled  *bool
+		wantEnabled         *bool
+	}{
+		{
+			description:         "CCPA Web integration enabled, general CCPA disabled",
+			giveIntegrationType: IntegrationTypeWeb,
+			giveCCPAEnabled:     &falseValue,
+			giveWebCCPAEnabled:  &trueValue,
+			wantEnabled:         &trueValue,
+		},
+		{
+			description:         "CCPA Web integration disabled, general CCPA enabled",
+			giveIntegrationType: IntegrationTypeWeb,
+			giveCCPAEnabled:     &trueValue,
+			giveWebCCPAEnabled:  &falseValue,
+			wantEnabled:         &falseValue,
+		},
+		{
+			description:         "CCPA Web integration unspecified, general CCPA disabled",
+			giveIntegrationType: IntegrationTypeWeb,
+			giveCCPAEnabled:     &falseValue,
+			giveWebCCPAEnabled:  nil,
+			wantEnabled:         &falseValue,
+		},
+		{
+			description:         "CCPA Web integration unspecified, general CCPA enabled",
+			giveIntegrationType: IntegrationTypeWeb,
+			giveCCPAEnabled:     &trueValue,
+			giveWebCCPAEnabled:  nil,
+			wantEnabled:         &trueValue,
+		},
+		{
+			description:         "CCPA Web integration unspecified, general CCPA unspecified",
+			giveIntegrationType: IntegrationTypeWeb,
+			giveCCPAEnabled:     nil,
+			giveWebCCPAEnabled:  nil,
+			wantEnabled:         nil,
+		},
+	}
+
+	for _, tt := range tests {
+		account := Account{
+			CCPA: AccountCCPA{
+				Enabled: tt.giveCCPAEnabled,
+				IntegrationEnabled: AccountIntegration{
+					Web: tt.giveWebCCPAEnabled,
+				},
+			},
+		}
+
+		enabled := account.CCPA.EnabledForIntegrationType(tt.giveIntegrationType)
+
+		if tt.wantEnabled == nil {
+			assert.Nil(t, enabled, tt.description)
+		} else {
+			assert.NotNil(t, enabled, tt.description)
+			assert.Equal(t, *tt.wantEnabled, *enabled, tt.description)
+		}
+	}
+}
+
+func TestAccountIntegrationGetByIntegrationType(t *testing.T) {
+	trueValue, falseValue := true, false
+
+	tests := []struct {
+		description         string
+		giveAMPEnabled      *bool
+		giveAppEnabled      *bool
+		giveVideoEnabled    *bool
+		giveWebEnabled      *bool
+		giveIntegrationType IntegrationType
+		wantEnabled         *bool
+	}{
+		{
+			description:         "AMP integration setting unspecified, returns nil",
+			giveIntegrationType: IntegrationTypeAMP,
+			wantEnabled:         nil,
+		},
+		{
+			description:         "AMP integration disabled, returns false",
+			giveAMPEnabled:      &falseValue,
+			giveIntegrationType: IntegrationTypeAMP,
+			wantEnabled:         &falseValue,
+		},
+		{
+			description:         "AMP integration enabled, returns true",
+			giveAMPEnabled:      &trueValue,
+			giveIntegrationType: IntegrationTypeAMP,
+			wantEnabled:         &trueValue,
+		},
+		{
+			description:         "App integration setting unspecified, returns nil",
+			giveIntegrationType: IntegrationTypeApp,
+			wantEnabled:         nil,
+		},
+		{
+			description:         "App integration disabled, returns false",
+			giveAppEnabled:      &falseValue,
+			giveIntegrationType: IntegrationTypeApp,
+			wantEnabled:         &falseValue,
+		},
+		{
+			description:         "App integration enabled, returns true",
+			giveAppEnabled:      &trueValue,
+			giveIntegrationType: IntegrationTypeApp,
+			wantEnabled:         &trueValue,
+		},
+		{
+			description:         "Video integration setting unspecified, returns nil",
+			giveIntegrationType: IntegrationTypeVideo,
+			wantEnabled:         nil,
+		},
+		{
+			description:         "Video integration disabled, returns false",
+			giveVideoEnabled:    &falseValue,
+			giveIntegrationType: IntegrationTypeVideo,
+			wantEnabled:         &falseValue,
+		},
+		{
+			description:         "Video integration enabled, returns true",
+			giveVideoEnabled:    &trueValue,
+			giveIntegrationType: IntegrationTypeVideo,
+			wantEnabled:         &trueValue,
+		},
+		{
+			description:         "Web integration setting unspecified, returns nil",
+			giveIntegrationType: IntegrationTypeWeb,
+			wantEnabled:         nil,
+		},
+		{
+			description:         "Web integration disabled, returns false",
+			giveWebEnabled:      &falseValue,
+			giveIntegrationType: IntegrationTypeWeb,
+			wantEnabled:         &falseValue,
+		},
+		{
+			description:         "Web integration enabled, returns true",
+			giveWebEnabled:      &trueValue,
+			giveIntegrationType: IntegrationTypeWeb,
+			wantEnabled:         &trueValue,
+		},
+	}
+
+	for _, tt := range tests {
+		accountIntegration := AccountIntegration{
+			AMP:   tt.giveAMPEnabled,
+			App:   tt.giveAppEnabled,
+			Video: tt.giveVideoEnabled,
+			Web:   tt.giveWebEnabled,
+		}
+
+		result := accountIntegration.GetByIntegrationType(tt.giveIntegrationType)
+		if tt.wantEnabled == nil {
+			assert.Nil(t, result, tt.description)
+		} else {
+			assert.NotNil(t, result, tt.description)
+			assert.Equal(t, *tt.wantEnabled, *result, tt.description)
+		}
+	}
+}
diff --git a/endpoints/openrtb2/amp_auction_test.go b/endpoints/openrtb2/amp_auction_test.go
index b8dd75b2..80a48754 100644
--- a/endpoints/openrtb2/amp_auction_test.go
+++ b/endpoints/openrtb2/amp_auction_test.go
@@ -12,7 +12,6 @@ import (
 	"testing"
 
 	"github.com/prebid/prebid-server/analytics"
-	"github.com/prebid/prebid-server/stored_requests"
 	"github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher"
 
 	"github.com/mxmCherry/openrtb"
@@ -48,7 +47,6 @@ func TestGoodAmpRequests(t *testing.T) {
 		newParamsValidator(t),
 		&mockAmpStoredReqFetcher{goodRequests},
 		empty_fetcher.EmptyFetcher{},
-		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: maxSize},
 		theMetrics,
 		analyticsConf.NewPBSAnalytics(&config.Analytics{}),
@@ -102,7 +100,6 @@ func TestAMPPageInfo(t *testing.T) {
 		newParamsValidator(t),
 		&mockAmpStoredReqFetcher{stored},
 		empty_fetcher.EmptyFetcher{},
-		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: maxSize},
 		theMetrics,
 		analyticsConf.NewPBSAnalytics(&config.Analytics{}),
@@ -208,7 +205,6 @@ func TestGDPRConsent(t *testing.T) {
 			newParamsValidator(t),
 			&mockAmpStoredReqFetcher{stored},
 			empty_fetcher.EmptyFetcher{},
-			empty_fetcher.EmptyFetcher{},
 			&config.Configuration{MaxRequestSize: maxSize},
 			metrics,
 			analyticsConf.NewPBSAnalytics(&config.Analytics{}),
@@ -362,7 +358,6 @@ func TestCCPAConsent(t *testing.T) {
 			newParamsValidator(t),
 			&mockAmpStoredReqFetcher{stored},
 			empty_fetcher.EmptyFetcher{},
-			empty_fetcher.EmptyFetcher{},
 			&config.Configuration{MaxRequestSize: maxSize},
 			metrics,
 			analyticsConf.NewPBSAnalytics(&config.Analytics{}),
@@ -422,7 +417,6 @@ func TestNoConsent(t *testing.T) {
 		newParamsValidator(t),
 		&mockAmpStoredReqFetcher{stored},
 		empty_fetcher.EmptyFetcher{},
-		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: maxSize},
 		metrics,
 		analyticsConf.NewPBSAnalytics(&config.Analytics{}),
@@ -469,7 +463,6 @@ func TestInvalidConsent(t *testing.T) {
 		newParamsValidator(t),
 		&mockAmpStoredReqFetcher{stored},
 		empty_fetcher.EmptyFetcher{},
-		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: maxSize},
 		metrics,
 		analyticsConf.NewPBSAnalytics(&config.Analytics{}),
@@ -554,7 +547,6 @@ func TestNewAndLegacyConsentBothProvided(t *testing.T) {
 			newParamsValidator(t),
 			&mockAmpStoredReqFetcher{stored},
 			empty_fetcher.EmptyFetcher{},
-			empty_fetcher.EmptyFetcher{},
 			&config.Configuration{MaxRequestSize: maxSize},
 			metrics,
 			analyticsConf.NewPBSAnalytics(&config.Analytics{}),
@@ -607,7 +599,6 @@ func TestAMPSiteExt(t *testing.T) {
 		newParamsValidator(t),
 		&mockAmpStoredReqFetcher{stored},
 		empty_fetcher.EmptyFetcher{},
-		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: maxSize},
 		theMetrics,
 		analyticsConf.NewPBSAnalytics(&config.Analytics{}),
@@ -648,7 +639,6 @@ func TestAmpBadRequests(t *testing.T) {
 		newParamsValidator(t),
 		&mockAmpStoredReqFetcher{badRequests},
 		empty_fetcher.EmptyFetcher{},
-		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: maxSize},
 		theMetrics,
 		analyticsConf.NewPBSAnalytics(&config.Analytics{}),
@@ -680,7 +670,6 @@ func TestAmpDebug(t *testing.T) {
 		newParamsValidator(t),
 		&mockAmpStoredReqFetcher{requests},
 		empty_fetcher.EmptyFetcher{},
-		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: maxSize},
 		theMetrics,
 		analyticsConf.NewPBSAnalytics(&config.Analytics{}),
@@ -754,7 +743,6 @@ func TestQueryParamOverrides(t *testing.T) {
 		newParamsValidator(t),
 		&mockAmpStoredReqFetcher{requests},
 		empty_fetcher.EmptyFetcher{},
-		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: maxSize},
 		theMetrics,
 		analyticsConf.NewPBSAnalytics(&config.Analytics{}),
@@ -907,7 +895,6 @@ func (s formatOverrideSpec) execute(t *testing.T) {
 		newParamsValidator(t),
 		&mockAmpStoredReqFetcher{requests},
 		empty_fetcher.EmptyFetcher{},
-		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: maxSize},
 		theMetrics,
 		analyticsConf.NewPBSAnalytics(&config.Analytics{}),
@@ -965,8 +952,8 @@ var expectedErrorsFromHoldAuction map[openrtb_ext.BidderName][]openrtb_ext.ExtBi
 	},
 }
 
-func (m *mockAmpExchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, ids exchange.IdFetcher, labels pbsmetrics.Labels, account *config.Account, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) {
-	m.lastRequest = bidRequest
+func (m *mockAmpExchange) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) {
+	m.lastRequest = r.BidRequest
 
 	response := &openrtb.BidResponse{
 		SeatBid: []openrtb.SeatBid{{
@@ -978,8 +965,8 @@ func (m *mockAmpExchange) HoldAuction(ctx context.Context, bidRequest *openrtb.B
 		Ext: json.RawMessage(`{ "errors": {"openx":[ { "code": 1, "message": "The request exceeded the timeout allocated" } ] } }`),
 	}
 
-	if bidRequest.Test == 1 {
-		resolvedRequest, err := json.Marshal(bidRequest)
+	if r.BidRequest.Test == 1 {
+		resolvedRequest, err := json.Marshal(r.BidRequest)
 		if err != nil {
 			resolvedRequest = json.RawMessage("{}")
 		}
@@ -1275,7 +1262,6 @@ func TestBuildAmpObject(t *testing.T) {
 			newParamsValidator(t),
 			mockAmpFetcher,
 			empty_fetcher.EmptyFetcher{},
-			empty_fetcher.EmptyFetcher{},
 			&config.Configuration{MaxRequestSize: maxSize},
 			pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}),
 			logger,
diff --git a/endpoints/openrtb2/auction_benchmark_test.go b/endpoints/openrtb2/auction_benchmark_test.go
index 09af23af..c5b7eb2b 100644
--- a/endpoints/openrtb2/auction_benchmark_test.go
+++ b/endpoints/openrtb2/auction_benchmark_test.go
@@ -79,11 +79,11 @@ func BenchmarkOpenrtbEndpoint(b *testing.B) {
 			infos,
 			gdpr.AlwaysAllow{},
 			currencies.NewRateConverter(&http.Client{}, "", time.Duration(0)),
+			empty_fetcher.EmptyFetcher{},
 		),
 		paramValidator,
 		empty_fetcher.EmptyFetcher{},
 		empty_fetcher.EmptyFetcher{},
-		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: maxSize},
 		theMetrics,
 		analyticsConf.NewPBSAnalytics(&config.Analytics{}),
diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go
index 02a41f52..9cdeff7b 100644
--- a/endpoints/openrtb2/auction_test.go
+++ b/endpoints/openrtb2/auction_test.go
@@ -406,8 +406,8 @@ func TestExplicitUserId(t *testing.T) {
 	})
 	// NewMetrics() will create a new go_metrics MetricsEngine, bypassing the need for a crafted configuration set to support it.
 	// As a side effect this gives us some coverage of the go_metrics piece of the metrics engine.
-	metrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{})
-	endpoint, _ := NewEndpoint(ex, newParamsValidator(t), empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, cfg, metrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BidderMap)
+	theMetrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{})
+	endpoint, _ := NewEndpoint(ex, newParamsValidator(t), empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, cfg, theMetrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BidderMap)
 
 	endpoint(httptest.NewRecorder(), request, nil)
 
@@ -437,7 +437,6 @@ func doRequest(t *testing.T, test testCase) (int, string) {
 		newParamsValidator(t),
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
-		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{
 			MaxRequestSize:     maxSize,
 			BlacklistedApps:    test.Config.BlacklistedApps,
@@ -504,7 +503,7 @@ func doBadAliasRequest(t *testing.T, filename string, expectMsg string) {
 	// NewMetrics() will create a new go_metrics MetricsEngine, bypassing the need for a crafted configuration set to support it.
 	// As a side effect this gives us some coverage of the go_metrics piece of the metrics engine.
 	metrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{})
-	endpoint, _ := NewEndpoint(&nobidExchange{}, newParamsValidator(t), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, metrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), disabledBidders, aliasJSON, bidderMap)
+	endpoint, _ := NewEndpoint(&nobidExchange{}, newParamsValidator(t), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, metrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), disabledBidders, aliasJSON, bidderMap)
 
 	request := httptest.NewRequest("POST", "/openrtb2/auction", bytes.NewReader(testBidRequest))
 	recorder := httptest.NewRecorder()
@@ -545,7 +544,7 @@ func TestNilExchange(t *testing.T) {
 	// NewMetrics() will create a new go_metrics MetricsEngine, bypassing the need for a crafted configuration set to support it.
 	// As a side effect this gives us some coverage of the go_metrics piece of the metrics engine.
 	metrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{})
-	_, err := NewEndpoint(nil, newParamsValidator(t), empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, metrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BidderMap)
+	_, err := NewEndpoint(nil, newParamsValidator(t), empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, metrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BidderMap)
 	if err == nil {
 		t.Errorf("NewEndpoint should return an error when given a nil Exchange.")
 	}
@@ -556,7 +555,7 @@ func TestNilValidator(t *testing.T) {
 	// NewMetrics() will create a new go_metrics MetricsEngine, bypassing the need for a crafted configuration set to support it.
 	// As a side effect this gives us some coverage of the go_metrics piece of the metrics engine.
 	metrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{})
-	_, err := NewEndpoint(&nobidExchange{}, nil, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, metrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BidderMap)
+	_, err := NewEndpoint(&nobidExchange{}, nil, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, metrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BidderMap)
 	if err == nil {
 		t.Errorf("NewEndpoint should return an error when given a nil BidderParamValidator.")
 	}
@@ -567,7 +566,7 @@ func TestExchangeError(t *testing.T) {
 	// NewMetrics() will create a new go_metrics MetricsEngine, bypassing the need for a crafted configuration set to support it.
 	// As a side effect this gives us some coverage of the go_metrics piece of the metrics engine.
 	metrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{})
-	endpoint, _ := NewEndpoint(&brokenExchange{}, newParamsValidator(t), empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, metrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BidderMap)
+	endpoint, _ := NewEndpoint(&brokenExchange{}, newParamsValidator(t), empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, metrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BidderMap)
 	request := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, "site.json")))
 	recorder := httptest.NewRecorder()
 	endpoint(recorder, request, nil)
@@ -679,7 +678,7 @@ func TestImplicitIPsEndToEnd(t *testing.T) {
 				IPv6PrivateNetworksParsed: test.privateNetworksIPv6,
 			},
 		}
-		endpoint, _ := NewEndpoint(exchange, newParamsValidator(t), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, cfg, metrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BidderMap)
+		endpoint, _ := NewEndpoint(exchange, newParamsValidator(t), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, cfg, metrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BidderMap)
 
 		httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, test.reqJSONFile)))
 		httpReq.Header.Set("X-Forwarded-For", test.xForwardedForHeader)
@@ -864,7 +863,7 @@ func TestImplicitDNTEndToEnd(t *testing.T) {
 	metrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{})
 	for _, test := range testCases {
 		exchange := &nobidExchange{}
-		endpoint, _ := NewEndpoint(exchange, newParamsValidator(t), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, metrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BidderMap)
+		endpoint, _ := NewEndpoint(exchange, newParamsValidator(t), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, metrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BidderMap)
 
 		httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, test.reqJSONFile)))
 		httpReq.Header.Set("DNT", test.dntHeader)
@@ -924,7 +923,6 @@ func TestStoredRequests(t *testing.T) {
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		empty_fetcher.EmptyFetcher{},
-		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: maxSize},
 		metrics,
 		analyticsConf.NewPBSAnalytics(&config.Analytics{}),
@@ -964,7 +962,6 @@ func TestOversizedRequest(t *testing.T) {
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		empty_fetcher.EmptyFetcher{},
-		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: int64(len(reqBody) - 1)},
 		pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}),
 		analyticsConf.NewPBSAnalytics(&config.Analytics{}),
@@ -1000,7 +997,6 @@ func TestRequestSizeEdgeCase(t *testing.T) {
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		empty_fetcher.EmptyFetcher{},
-		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: int64(len(reqBody))},
 		pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}),
 		analyticsConf.NewPBSAnalytics(&config.Analytics{}),
@@ -1034,7 +1030,6 @@ func TestNoEncoding(t *testing.T) {
 		newParamsValidator(t),
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
-		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: maxSize},
 		pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}),
 		analyticsConf.NewPBSAnalytics(&config.Analytics{}),
@@ -1110,7 +1105,6 @@ func TestContentType(t *testing.T) {
 		newParamsValidator(t),
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
-		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: maxSize},
 		pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}),
 		analyticsConf.NewPBSAnalytics(&config.Analytics{}),
@@ -1200,7 +1194,6 @@ func TestValidateImpExt(t *testing.T) {
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		empty_fetcher.EmptyFetcher{},
-		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: int64(8096)},
 		pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}),
 		analyticsConf.NewPBSAnalytics(&config.Analytics{}),
@@ -1245,7 +1238,6 @@ func TestCurrencyTrunc(t *testing.T) {
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		empty_fetcher.EmptyFetcher{},
-		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{},
 		pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}),
 		analyticsConf.NewPBSAnalytics(&config.Analytics{}),
@@ -1290,7 +1282,6 @@ func TestCCPAInvalid(t *testing.T) {
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		empty_fetcher.EmptyFetcher{},
-		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{},
 		pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}),
 		analyticsConf.NewPBSAnalytics(&config.Analytics{}),
@@ -1339,7 +1330,6 @@ func TestNoSaleInvalid(t *testing.T) {
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		empty_fetcher.EmptyFetcher{},
-		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{},
 		pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}),
 		analyticsConf.NewPBSAnalytics(&config.Analytics{}),
@@ -1391,7 +1381,6 @@ func TestValidateSourceTID(t *testing.T) {
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		empty_fetcher.EmptyFetcher{},
-		empty_fetcher.EmptyFetcher{},
 		cfg,
 		pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}),
 		analyticsConf.NewPBSAnalytics(&config.Analytics{}),
@@ -1436,7 +1425,6 @@ func TestSChainInvalid(t *testing.T) {
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		empty_fetcher.EmptyFetcher{},
-		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{},
 		pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}),
 		analyticsConf.NewPBSAnalytics(&config.Analytics{}),
@@ -1657,10 +1645,10 @@ type nobidExchange struct {
 	gotRequest *openrtb.BidRequest
 }
 
-func (e *nobidExchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, ids exchange.IdFetcher, labels pbsmetrics.Labels, account *config.Account, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) {
-	e.gotRequest = bidRequest
+func (e *nobidExchange) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) {
+	e.gotRequest = r.BidRequest
 	return &openrtb.BidResponse{
-		ID:    bidRequest.ID,
+		ID:    r.BidRequest.ID,
 		BidID: "test bid id",
 		NBR:   openrtb.NoBidReasonCodeUnknownError.Ptr(),
 	}, nil
@@ -1672,15 +1660,15 @@ type mockBidExchange struct {
 
 // mockBidExchange is a well-behaved exchange that lists the bidders found in every bidRequest.Imp[i].Ext
 // into the bidResponse.Ext to assert the bidder adapters that were not filtered out in the validation process
-func (e *mockBidExchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, ids exchange.IdFetcher, labels pbsmetrics.Labels, account *config.Account, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) {
+func (e *mockBidExchange) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) {
 	bidResponse := &openrtb.BidResponse{
-		ID:    bidRequest.ID,
+		ID:    r.BidRequest.ID,
 		BidID: "test bid id",
 		NBR:   openrtb.NoBidReasonCodeUnknownError.Ptr(),
 	}
-	if len(bidRequest.Imp) > 0 {
+	if len(r.BidRequest.Imp) > 0 {
 		var SeatBidMap = make(map[string]openrtb.SeatBid, 0)
-		for _, imp := range bidRequest.Imp {
+		for _, imp := range r.BidRequest.Imp {
 			var bidderExts map[string]json.RawMessage
 			if err := json.Unmarshal(imp.Ext, &bidderExts); err != nil {
 				return nil, err
@@ -1704,7 +1692,7 @@ func (e *mockBidExchange) HoldAuction(ctx context.Context, bidRequest *openrtb.B
 
 type brokenExchange struct{}
 
-func (e *brokenExchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, ids exchange.IdFetcher, labels pbsmetrics.Labels, account *config.Account, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) {
+func (e *brokenExchange) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) {
 	return nil, errors.New("Critical, unrecoverable error.")
 }
 
@@ -2070,8 +2058,8 @@ type mockExchange struct {
 	lastRequest *openrtb.BidRequest
 }
 
-func (m *mockExchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, ids exchange.IdFetcher, labels pbsmetrics.Labels, account *config.Account, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) {
-	m.lastRequest = bidRequest
+func (m *mockExchange) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) {
+	m.lastRequest = r.BidRequest
 	return &openrtb.BidResponse{
 		SeatBid: []openrtb.SeatBid{{
 			Bid: []openrtb.Bid{{
diff --git a/endpoints/openrtb2/video_auction_test.go b/endpoints/openrtb2/video_auction_test.go
index 66a3bd67..2b6c478e 100644
--- a/endpoints/openrtb2/video_auction_test.go
+++ b/endpoints/openrtb2/video_auction_test.go
@@ -20,7 +20,6 @@ import (
 	"github.com/prebid/prebid-server/openrtb_ext"
 	"github.com/prebid/prebid-server/pbsmetrics"
 	"github.com/prebid/prebid-server/prebid_cache_client"
-	"github.com/prebid/prebid-server/stored_requests"
 	"github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher"
 	metrics "github.com/rcrowley/go-metrics"
 	"github.com/stretchr/testify/assert"
@@ -1210,7 +1209,6 @@ func mockDepsWithMetrics(t *testing.T, ex *mockExchangeVideo) (*endpointDeps, *p
 		&mockVideoStoredReqFetcher{},
 		&mockVideoStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
-		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: maxSize},
 		theMetrics,
 		mockModule,
@@ -1255,7 +1253,6 @@ func mockDeps(t *testing.T, ex *mockExchangeVideo) *endpointDeps {
 		&mockVideoStoredReqFetcher{},
 		&mockVideoStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
-		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: maxSize},
 		theMetrics,
 		analyticsConf.NewPBSAnalytics(&config.Analytics{}),
@@ -1279,7 +1276,6 @@ func mockDepsAppendBidderNames(t *testing.T, ex *mockExchangeAppendBidderNames)
 		&mockVideoStoredReqFetcher{},
 		&mockVideoStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
-		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: maxSize},
 		theMetrics,
 		analyticsConf.NewPBSAnalytics(&config.Analytics{}),
@@ -1303,7 +1299,6 @@ func mockDepsNoBids(t *testing.T, ex *mockExchangeVideoNoBids) *endpointDeps {
 		&mockVideoStoredReqFetcher{},
 		&mockVideoStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
-		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: maxSize},
 		theMetrics,
 		analyticsConf.NewPBSAnalytics(&config.Analytics{}),
@@ -1346,8 +1341,8 @@ type mockExchangeVideo struct {
 	cache       *mockCacheClient
 }
 
-func (m *mockExchangeVideo) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, ids exchange.IdFetcher, labels pbsmetrics.Labels, account *config.Account, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) {
-	m.lastRequest = bidRequest
+func (m *mockExchangeVideo) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) {
+	m.lastRequest = r.BidRequest
 	if debugLog != nil && debugLog.Enabled {
 		m.cache.called = true
 	}
@@ -1382,8 +1377,8 @@ type mockExchangeAppendBidderNames struct {
 	cache       *mockCacheClient
 }
 
-func (m *mockExchangeAppendBidderNames) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, ids exchange.IdFetcher, labels pbsmetrics.Labels, account *config.Account, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) {
-	m.lastRequest = bidRequest
+func (m *mockExchangeAppendBidderNames) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) {
+	m.lastRequest = r.BidRequest
 	if debugLog != nil && debugLog.Enabled {
 		m.cache.called = true
 	}
@@ -1418,8 +1413,8 @@ type mockExchangeVideoNoBids struct {
 	cache       *mockCacheClient
 }
 
-func (m *mockExchangeVideoNoBids) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, ids exchange.IdFetcher, labels pbsmetrics.Labels, account *config.Account, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) {
-	m.lastRequest = bidRequest
+func (m *mockExchangeVideoNoBids) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) {
+	m.lastRequest = r.BidRequest
 	return &openrtb.BidResponse{
 		SeatBid: []openrtb.SeatBid{{}},
 	}, nil
diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go
index bd2fa114..d22acf0f 100644
--- a/exchange/exchange_test.go
+++ b/exchange/exchange_test.go
@@ -54,7 +54,7 @@ func TestNewExchange(t *testing.T) {
 	}
 
 	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)
+	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, nilCategoryFetcher{}).(*exchange)
 	for _, bidderName := range knownAdapters {
 		if _, ok := e.adapterMap[bidderName]; !ok {
 			t.Errorf("NewExchange produced an Exchange without bidder %s", bidderName)
@@ -92,7 +92,7 @@ func TestCharacterEscape(t *testing.T) {
 	defer server.Close()
 
 	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)
+	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, nilCategoryFetcher{}).(*exchange)
 
 	/* 	3) Build all the parameters e.buildBidResponse(ctx.Background(), liveA... ) needs */
 	//liveAdapters []openrtb_ext.BidderName,
@@ -236,6 +236,7 @@ func TestDebugBehaviour(t *testing.T) {
 	e.me = &metricsConf.DummyMetricsEngine{}
 	e.gDPR = gdpr.AlwaysAllow{}
 	e.currencyConverter = currencies.NewRateConverter(&http.Client{}, "", time.Duration(0))
+	e.categoriesFetcher = categoriesFetcher
 
 	// Run tests
 	for _, test := range testCases {
@@ -247,8 +248,14 @@ func TestDebugBehaviour(t *testing.T) {
 			bidRequest.Ext = nil
 		}
 
+		auctionRequest := AuctionRequest{
+			BidRequest: bidRequest,
+			Account:    config.Account{},
+			UserSyncs:  &emptyUsersync{},
+		}
+
 		// Run test
-		outBidResponse, err := e.HoldAuction(context.Background(), bidRequest, &emptyUsersync{}, pbsmetrics.Labels{}, &config.Account{}, &categoriesFetcher, nil)
+		outBidResponse, err := e.HoldAuction(context.Background(), auctionRequest, nil)
 
 		// Assert no HoldAuction error
 		assert.NoErrorf(t, err, "%s. ex.HoldAuction returned an error: %v \n", test.desc, err)
@@ -418,6 +425,7 @@ func TestReturnCreativeEndToEnd(t *testing.T) {
 	e.me = &metricsConf.DummyMetricsEngine{}
 	e.gDPR = gdpr.AlwaysAllow{}
 	e.currencyConverter = currencies.NewRateConverter(&http.Client{}, "", time.Duration(0))
+	e.categoriesFetcher = categoriesFetcher
 
 	// Define mock incoming bid requeset
 	mockBidRequest := &openrtb.BidRequest{
@@ -435,8 +443,14 @@ func TestReturnCreativeEndToEnd(t *testing.T) {
 		for _, test := range testGroup.testCases {
 			mockBidRequest.Ext = test.inExt
 
+			auctionRequest := AuctionRequest{
+				BidRequest: mockBidRequest,
+				Account:    config.Account{},
+				UserSyncs:  &emptyUsersync{},
+			}
+
 			// Run test
-			outBidResponse, err := e.HoldAuction(context.Background(), mockBidRequest, &emptyUsersync{}, pbsmetrics.Labels{}, &config.Account{}, &categoriesFetcher, nil)
+			outBidResponse, err := e.HoldAuction(context.Background(), auctionRequest, nil)
 
 			// Assert return error, if any
 			if testGroup.expectError {
@@ -494,7 +508,7 @@ func TestGetBidCacheInfoEndToEnd(t *testing.T) {
 	defer server.Close()
 
 	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)
+	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, nilCategoryFetcher{}).(*exchange)
 
 	/* 	3) Build all the parameters e.buildBidResponse(ctx.Background(), liveA... ) needs */
 	liveAdapters := []openrtb_ext.BidderName{bidderName}
@@ -838,7 +852,7 @@ func TestBidResponseCurrency(t *testing.T) {
 	defer server.Close()
 
 	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)
+	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, nilCategoryFetcher{}).(*exchange)
 
 	liveAdapters := make([]openrtb_ext.BidderName, 1)
 	liveAdapters[0] = "appnexus"
@@ -1010,8 +1024,15 @@ func TestRaceIntegration(t *testing.T) {
 	}
 	theMetrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{})
 	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{}, &config.Account{}, &categoriesFetcher, nil)
+
+	auctionRequest := AuctionRequest{
+		BidRequest: newRaceCheckingRequest(t),
+		Account:    config.Account{},
+		UserSyncs:  &emptyUsersync{},
+	}
+
+	ex := NewExchange(server.Client(), &wellBehavedCache{}, cfg, theMetrics, adapters.ParseBidderInfos(cfg.Adapters, "../static/bidder-info", openrtb_ext.BidderList()), gdpr.AlwaysAllow{}, currencyConverter, categoriesFetcher)
+	_, err := ex.HoldAuction(context.Background(), auctionRequest, nil)
 	if err != nil {
 		t.Errorf("HoldAuction returned unexpected error: %v", err)
 	}
@@ -1095,7 +1116,7 @@ func TestPanicRecovery(t *testing.T) {
 
 	theMetrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{})
 	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)
+	e := NewExchange(&http.Client{}, nil, cfg, theMetrics, adapters.ParseBidderInfos(cfg.Adapters, "../static/bidder-info", openrtb_ext.BidderList()), gdpr.AlwaysAllow{}, currencyConverter, nilCategoryFetcher{}).(*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!")
@@ -1160,8 +1181,12 @@ func TestPanicRecoveryHighLevel(t *testing.T) {
 			Endpoint: server.URL,
 		}
 	}
+	categoriesFetcher, error := newCategoryFetcher("./test/category-mapping")
+	if error != nil {
+		t.Errorf("Failed to create a category Fetcher: %v", error)
+	}
 	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 := 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, categoriesFetcher).(*exchange)
 
 	e.adapterMap[openrtb_ext.BidderBeachfront] = panicingAdapter{}
 	e.adapterMap[openrtb_ext.BidderAppnexus] = panicingAdapter{}
@@ -1194,11 +1219,13 @@ func TestPanicRecoveryHighLevel(t *testing.T) {
 		}},
 	}
 
-	categoriesFetcher, error := newCategoryFetcher("./test/category-mapping")
-	if error != nil {
-		t.Errorf("Failed to create a category Fetcher: %v", error)
+	auctionRequest := AuctionRequest{
+		BidRequest: request,
+		Account:    config.Account{},
+		UserSyncs:  &emptyUsersync{},
 	}
-	_, err := e.HoldAuction(context.Background(), request, &emptyUsersync{}, pbsmetrics.Labels{}, &config.Account{}, &categoriesFetcher, nil)
+
+	_, err := e.HoldAuction(context.Background(), auctionRequest, nil)
 	if err != nil {
 		t.Errorf("HoldAuction returned unexpected error: %v", err)
 	}
@@ -1313,16 +1340,19 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) {
 
 	ex := newExchangeForTests(t, filename, spec.OutgoingRequests, aliases, privacyConfig)
 	biddersInAuction := findBiddersInAuction(t, filename, &spec.IncomingRequest.OrtbRequest)
-	categoriesFetcher, error := newCategoryFetcher("./test/category-mapping")
-	if error != nil {
-		t.Errorf("Failed to create a category Fetcher: %v", error)
-	}
 	debugLog := &DebugLog{}
 	if spec.DebugLog != nil {
 		*debugLog = *spec.DebugLog
 		debugLog.Regexp = regexp.MustCompile(`[<>]`)
 	}
-	bid, err := ex.HoldAuction(context.Background(), &spec.IncomingRequest.OrtbRequest, mockIdFetcher(spec.IncomingRequest.Usersyncs), pbsmetrics.Labels{}, &config.Account{}, &categoriesFetcher, debugLog)
+
+	auctionRequest := AuctionRequest{
+		BidRequest: &spec.IncomingRequest.OrtbRequest,
+		Account:    config.Account{},
+		UserSyncs:  mockIdFetcher(spec.IncomingRequest.Usersyncs),
+	}
+
+	bid, err := ex.HoldAuction(context.Background(), auctionRequest, debugLog)
 	responseTimes := extractResponseTimes(t, filename, bid)
 	for _, bidderName := range biddersInAuction {
 		if _, ok := responseTimes[bidderName]; !ok {
@@ -1426,6 +1456,11 @@ func newExchangeForTests(t *testing.T, filename string, expectations map[string]
 		}
 	}
 
+	categoriesFetcher, error := newCategoryFetcher("./test/category-mapping")
+	if error != nil {
+		t.Fatalf("Failed to create a category Fetcher: %v", error)
+	}
+
 	return &exchange{
 		adapterMap:          adapters,
 		me:                  metricsConf.NewMetricsEngine(&config.Configuration{}, openrtb_ext.BidderList()),
@@ -1435,6 +1470,7 @@ func newExchangeForTests(t *testing.T, filename string, expectations map[string]
 		currencyConverter:   currencies.NewRateConverter(&http.Client{}, "", time.Duration(0)),
 		UsersyncIfAmbiguous: privacyConfig.GDPR.UsersyncIfAmbiguous,
 		privacyConfig:       privacyConfig,
+		categoriesFetcher:   categoriesFetcher,
 	}
 }
 
@@ -2503,6 +2539,10 @@ func (f mockIdFetcher) GetId(bidder openrtb_ext.BidderName) (id string, ok bool)
 	return
 }
 
+func (f mockIdFetcher) LiveSyncCount() int {
+	return len(f)
+}
+
 type validatingBidder struct {
 	t          *testing.T
 	fileName   string
@@ -2673,6 +2713,10 @@ func (e *emptyUsersync) GetId(bidder openrtb_ext.BidderName) (string, bool) {
 	return "", false
 }
 
+func (e *emptyUsersync) LiveSyncCount() int {
+	return 0
+}
+
 type mockUsersync struct {
 	syncs map[string]string
 }
@@ -2682,8 +2726,18 @@ func (e *mockUsersync) GetId(bidder openrtb_ext.BidderName) (id string, exists b
 	return
 }
 
+func (e *mockUsersync) LiveSyncCount() int {
+	return len(e.syncs)
+}
+
 type panicingAdapter struct{}
 
 func (panicingAdapter) requestBid(ctx context.Context, request *openrtb.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currencies.Conversions, reqInfo *adapters.ExtraRequestInfo) (posb *pbsOrtbSeatBid, errs []error) {
 	panic("Panic! Panic! The world is ending!")
 }
+
+type nilCategoryFetcher struct{}
+
+func (nilCategoryFetcher) FetchCategories(ctx context.Context, primaryAdServer, publisherId, iabCategory string) (string, error) {
+	return "", nil
+}
diff --git a/exchange/targeting_test.go b/exchange/targeting_test.go
index aaa75411..23a24fac 100644
--- a/exchange/targeting_test.go
+++ b/exchange/targeting_test.go
@@ -13,7 +13,6 @@ import (
 
 	"github.com/prebid/prebid-server/gdpr"
 
-	"github.com/prebid/prebid-server/pbsmetrics"
 	metricsConf "github.com/prebid/prebid-server/pbsmetrics/config"
 	metricsConfig "github.com/prebid/prebid-server/pbsmetrics/config"
 
@@ -82,6 +81,11 @@ func runTargetingAuction(t *testing.T, mockBids map[openrtb_ext.BidderName][]*op
 	server := httptest.NewServer(http.HandlerFunc(mockServer))
 	defer server.Close()
 
+	categoriesFetcher, error := newCategoryFetcher("./test/category-mapping")
+	if error != nil {
+		t.Errorf("Failed to create a category Fetcher: %v", error)
+	}
+
 	ex := &exchange{
 		adapterMap:          buildAdapterMap(mockBids, server.URL, server.Client()),
 		me:                  &metricsConf.DummyMetricsEngine{},
@@ -90,6 +94,7 @@ func runTargetingAuction(t *testing.T, mockBids map[openrtb_ext.BidderName][]*op
 		gDPR:                gdpr.AlwaysAllow{},
 		currencyConverter:   currencies.NewRateConverter(&http.Client{}, "", time.Duration(0)),
 		UsersyncIfAmbiguous: false,
+		categoriesFetcher:   categoriesFetcher,
 	}
 
 	imps := buildImps(t, mockBids)
@@ -104,11 +109,13 @@ func runTargetingAuction(t *testing.T, mockBids map[openrtb_ext.BidderName][]*op
 		req.Site = &openrtb.Site{}
 	}
 
-	categoriesFetcher, error := newCategoryFetcher("./test/category-mapping")
-	if error != nil {
-		t.Errorf("Failed to create a category Fetcher: %v", error)
+	auctionRequest := AuctionRequest{
+		BidRequest: req,
+		Account:    config.Account{},
+		UserSyncs:  &emptyUsersync{},
 	}
-	bidResp, err := ex.HoldAuction(context.Background(), req, &mockFetcher{}, pbsmetrics.Labels{}, &config.Account{}, &categoriesFetcher, nil)
+
+	bidResp, err := ex.HoldAuction(context.Background(), auctionRequest, nil)
 
 	if err != nil {
 		t.Fatalf("Unexpected errors running auction: %v", err)
@@ -238,12 +245,6 @@ func (m *mockTargetingBidder) MakeBids(internalRequest *openrtb.BidRequest, exte
 	return bidResponse, nil
 }
 
-type mockFetcher struct{}
-
-func (f *mockFetcher) GetId(bidder openrtb_ext.BidderName) (string, bool) {
-	return "", false
-}
-
 func mockServer(w http.ResponseWriter, req *http.Request) {
 	w.Write([]byte("{}"))
 }
diff --git a/exchange/utils_test.go b/exchange/utils_test.go
index 14293ae2..e2aa72e0 100644
--- a/exchange/utils_test.go
+++ b/exchange/utils_test.go
@@ -93,72 +93,114 @@ func TestCleanOpenRTBRequests(t *testing.T) {
 }
 
 func TestCleanOpenRTBRequestsCCPA(t *testing.T) {
+	trueValue, falseValue := true, false
+
 	testCases := []struct {
 		description         string
 		reqExt              json.RawMessage
 		ccpaConsent         string
-		enforceCCPA         bool
+		ccpaHostEnabled     bool
+		ccpaAccountEnabled  *bool
 		expectDataScrub     bool
 		expectPrivacyLabels pbsmetrics.PrivacyLabels
 	}{
 		{
-			description:     "Feature Flag Enabled - Opt Out",
-			ccpaConsent:     "1-Y-",
-			enforceCCPA:     true,
-			expectDataScrub: true,
+			description:        "Feature Flags Enabled - Opt Out",
+			ccpaConsent:        "1-Y-",
+			ccpaHostEnabled:    true,
+			ccpaAccountEnabled: &trueValue,
+			expectDataScrub:    true,
 			expectPrivacyLabels: pbsmetrics.PrivacyLabels{
 				CCPAProvided: true,
 				CCPAEnforced: true,
 			},
 		},
 		{
-			description:     "Feature Flag Enabled - Opt In",
-			ccpaConsent:     "1-N-",
-			enforceCCPA:     true,
-			expectDataScrub: false,
+			description:        "Feature Flags Enabled - Opt In",
+			ccpaConsent:        "1-N-",
+			ccpaHostEnabled:    true,
+			ccpaAccountEnabled: &trueValue,
+			expectDataScrub:    false,
 			expectPrivacyLabels: pbsmetrics.PrivacyLabels{
 				CCPAProvided: true,
 				CCPAEnforced: false,
 			},
 		},
 		{
-			description:     "Feature Flag Enabled - No Sale Star - Doesn't Scrub",
-			reqExt:          json.RawMessage(`{"prebid":{"nosale":["*"]}}`),
-			ccpaConsent:     "1-Y-",
-			enforceCCPA:     true,
-			expectDataScrub: false,
+			description:        "Feature Flags Enabled - No Sale Star - Doesn't Scrub",
+			reqExt:             json.RawMessage(`{"prebid":{"nosale":["*"]}}`),
+			ccpaConsent:        "1-Y-",
+			ccpaHostEnabled:    true,
+			ccpaAccountEnabled: &trueValue,
+			expectDataScrub:    false,
 			expectPrivacyLabels: pbsmetrics.PrivacyLabels{
 				CCPAProvided: true,
 				CCPAEnforced: false,
 			},
 		},
 		{
-			description:     "Feature Flag Enabled - No Sale Specific Bidder - Doesn't Scrub",
-			reqExt:          json.RawMessage(`{"prebid":{"nosale":["appnexus"]}}`),
-			ccpaConsent:     "1-Y-",
-			enforceCCPA:     true,
-			expectDataScrub: false,
+			description:        "Feature Flags Enabled - No Sale Specific Bidder - Doesn't Scrub",
+			reqExt:             json.RawMessage(`{"prebid":{"nosale":["appnexus"]}}`),
+			ccpaConsent:        "1-Y-",
+			ccpaHostEnabled:    true,
+			ccpaAccountEnabled: &trueValue,
+			expectDataScrub:    false,
 			expectPrivacyLabels: pbsmetrics.PrivacyLabels{
 				CCPAProvided: true,
 				CCPAEnforced: true,
 			},
 		},
 		{
-			description:     "Feature Flag Enabled - No Sale Different Bidder - Scrubs",
-			reqExt:          json.RawMessage(`{"prebid":{"nosale":["rubicon"]}}`),
-			ccpaConsent:     "1-Y-",
-			enforceCCPA:     true,
-			expectDataScrub: true,
+			description:        "Feature Flags Enabled - No Sale Different Bidder - Scrubs",
+			reqExt:             json.RawMessage(`{"prebid":{"nosale":["rubicon"]}}`),
+			ccpaConsent:        "1-Y-",
+			ccpaHostEnabled:    true,
+			ccpaAccountEnabled: &trueValue,
+			expectDataScrub:    true,
 			expectPrivacyLabels: pbsmetrics.PrivacyLabels{
 				CCPAProvided: true,
 				CCPAEnforced: true,
 			},
 		},
 		{
-			description:     "Feature Flag Disabled",
-			ccpaConsent:     "1-Y-",
-			enforceCCPA:     false,
-			expectDataScrub: false,
+			description:        "Feature flags Account CCPA enabled, host CCPA disregarded - Opt Out",
+			ccpaConsent:        "1-Y-",
+			ccpaHostEnabled:    false,
+			ccpaAccountEnabled: &trueValue,
+			expectDataScrub:    true,
+			expectPrivacyLabels: pbsmetrics.PrivacyLabels{
+				CCPAProvided: true,
+				CCPAEnforced: true,
+			},
+		},
+		{
+			description:        "Feature flags Account CCPA disabled, host CCPA disregarded",
+			ccpaConsent:        "1-Y-",
+			ccpaHostEnabled:    true,
+			ccpaAccountEnabled: &falseValue,
+			expectDataScrub:    false,
+			expectPrivacyLabels: pbsmetrics.PrivacyLabels{
+				CCPAProvided: true,
+				CCPAEnforced: false,
+			},
+		},
+		{
+			description:        "Feature flags Account CCPA not specified, host CCPA enabled - Opt Out",
+			ccpaConsent:        "1-Y-",
+			ccpaHostEnabled:    true,
+			ccpaAccountEnabled: nil,
+			expectDataScrub:    true,
+			expectPrivacyLabels: pbsmetrics.PrivacyLabels{
+				CCPAProvided: true,
+				CCPAEnforced: true,
+			},
+		},
+		{
+			description:        "Feature flags Account CCPA not specified, host CCPA disabled",
+			ccpaConsent:        "1-Y-",
+			ccpaHostEnabled:    false,
+			ccpaAccountEnabled: nil,
+			expectDataScrub:    false,
 			expectPrivacyLabels: pbsmetrics.PrivacyLabels{
 				CCPAProvided: true,
 				CCPAEnforced: false,
@@ -175,11 +217,27 @@ func TestCleanOpenRTBRequestsCCPA(t *testing.T) {
 
 		privacyConfig := config.Privacy{
 			CCPA: config.CCPA{
-				Enforce: test.enforceCCPA,
+				Enforce: test.ccpaHostEnabled,
 			},
 		}
 
-		results, _, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), req, nil, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{personalInfoAllowed: true}, true, privacyConfig, &config.Account{})
+		accountConfig := config.Account{
+			CCPA: config.AccountCCPA{
+				Enabled: test.ccpaAccountEnabled,
+			},
+		}
+
+		results, _, privacyLabels, errs := cleanOpenRTBRequests(
+			context.Background(),
+			req,
+			nil,
+			&emptyUsersync{},
+			map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{},
+			pbsmetrics.Labels{},
+			&permissionsMock{personalInfoAllowed: true},
+			true,
+			privacyConfig,
+			&accountConfig)
 		result := results["appnexus"]
 
 		assert.Nil(t, errs)
@@ -968,7 +1026,6 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) {
 		gdpr                string
 		gdprConsent         string
 		gdprScrub           bool
-		enforceGDPR         bool
 		expectPrivacyLabels pbsmetrics.PrivacyLabels
 	}{
 		{