Repository: prebid/prebid-server
Pull request #1489 (2020-09-10 17:04:37)

New: InMobi Prebid Server Adapter
Adding an adapter for InMobi.

The corresponding docs PR is here:
https://github.com/prebid/prebid.github.io/pull/2326


Patch:
diff --git a/account/account.go b/account/account.go
new file mode 100644
index 00000000..2f27b61e
--- /dev/null
+++ b/account/account.go
@@ -0,0 +1,69 @@
+package account
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+
+	jsonpatch "github.com/evanphx/json-patch"
+	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/errortypes"
+	"github.com/prebid/prebid-server/pbsmetrics"
+	"github.com/prebid/prebid-server/stored_requests"
+)
+
+// GetAccount looks up the config.Account object referenced by the given accountID, with access rules applied
+func GetAccount(ctx context.Context, cfg *config.Configuration, fetcher stored_requests.AccountFetcher, accountID string) (account *config.Account, errs []error) {
+	// Check BlacklistedAcctMap until we have deprecated it
+	if _, found := cfg.BlacklistedAcctMap[accountID]; found {
+		return nil, []error{&errortypes.BlacklistedAcct{
+			Message: fmt.Sprintf("Prebid-server has disabled Account ID: %s, please reach out to the prebid server host.", accountID),
+		}}
+	}
+	if cfg.AccountRequired && accountID == pbsmetrics.PublisherUnknown {
+		return nil, []error{&errortypes.AcctRequired{
+			Message: fmt.Sprintf("Prebid-server has been configured to discard requests without a valid Account ID. Please reach out to the prebid server host."),
+		}}
+	}
+	if accountJSON, accErrs := fetcher.FetchAccount(ctx, accountID); len(accErrs) > 0 || accountJSON == nil {
+		// accountID does not reference a valid account
+		for _, e := range accErrs {
+			if _, ok := e.(stored_requests.NotFoundError); !ok {
+				errs = append(errs, e)
+			}
+		}
+		if cfg.AccountRequired && cfg.AccountDefaults.Disabled {
+			errs = append(errs, &errortypes.AcctRequired{
+				Message: fmt.Sprintf("Prebid-server could not verify the Account ID. Please reach out to the prebid server host."),
+			})
+			return nil, errs
+		}
+		// Make a copy of AccountDefaults instead of taking a reference,
+		// to preserve original accountID in case is needed to check NonStandardPublisherMap
+		pubAccount := cfg.AccountDefaults
+		pubAccount.ID = accountID
+		account = &pubAccount
+	} else {
+		// accountID resolved to a valid account, merge with AccountDefaults for a complete config
+		account = &config.Account{}
+		completeJSON, err := jsonpatch.MergePatch(cfg.AccountDefaultsJSON(), accountJSON)
+		if err == nil {
+			err = json.Unmarshal(completeJSON, account)
+		}
+		if err != nil {
+			errs = append(errs, err)
+			return nil, errs
+		}
+		// Fill in ID if needed, so it can be left out of account definition
+		if len(account.ID) == 0 {
+			account.ID = accountID
+		}
+	}
+	if account.Disabled {
+		errs = append(errs, &errortypes.BlacklistedAcct{
+			Message: fmt.Sprintf("Prebid-server has disabled Account ID: %s, please reach out to the prebid server host.", accountID),
+		})
+		return nil, errs
+	}
+	return account, nil
+}
diff --git a/adapters/colossus/colossus.go b/adapters/colossus/colossus.go
new file mode 100644
index 00000000..89cd49d2
--- /dev/null
+++ b/adapters/colossus/colossus.go
@@ -0,0 +1,137 @@
+package colossus
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+
+	"github.com/buger/jsonparser"
+	"github.com/mxmCherry/openrtb"
+	"github.com/prebid/prebid-server/adapters"
+	"github.com/prebid/prebid-server/errortypes"
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+type ColossusAdapter struct {
+	URI string
+}
+
+// NewColossusBidder Initializes the Bidder
+func NewColossusBidder(endpoint string) *ColossusAdapter {
+	return &ColossusAdapter{
+		URI: endpoint,
+	}
+}
+
+// MakeRequests create bid request for colossus demand
+func (a *ColossusAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+	var errs []error
+	var err error
+	var tagID string
+
+	var adapterRequests []*adapters.RequestData
+
+	reqCopy := *request
+	for _, imp := range request.Imp {
+		reqCopy.Imp = []openrtb.Imp{imp}
+
+		tagID, err = jsonparser.GetString(reqCopy.Imp[0].Ext, "bidder", "TagID")
+		if err != nil {
+			errs = append(errs, err)
+			continue
+		}
+
+		reqCopy.Imp[0].TagID = tagID
+
+		adapterReq, errors := a.makeRequest(&reqCopy)
+		if adapterReq != nil {
+			adapterRequests = append(adapterRequests, adapterReq)
+		}
+		errs = append(errs, errors...)
+	}
+	return adapterRequests, errs
+}
+
+func (a *ColossusAdapter) makeRequest(request *openrtb.BidRequest) (*adapters.RequestData, []error) {
+
+	var errs []error
+
+	reqJSON, err := json.Marshal(request)
+
+	if err != nil {
+		errs = append(errs, err)
+		return nil, errs
+	}
+
+	headers := http.Header{}
+	headers.Add("Content-Type", "application/json;charset=utf-8")
+	headers.Add("Accept", "application/json")
+	return &adapters.RequestData{
+		Method:  "POST",
+		Uri:     a.URI,
+		Body:    reqJSON,
+		Headers: headers,
+	}, errs
+}
+
+// MakeBids makes the bids
+func (a *ColossusAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+	var errs []error
+
+	if response.StatusCode == http.StatusNoContent {
+		return nil, nil
+	}
+
+	if response.StatusCode == http.StatusNotFound {
+		return nil, []error{&errortypes.BadServerResponse{
+			Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode),
+		}}
+	}
+
+	if response.StatusCode != http.StatusOK {
+		return nil, []error{&errortypes.BadServerResponse{
+			Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode),
+		}}
+	}
+
+	var bidResp openrtb.BidResponse
+
+	if err := json.Unmarshal(response.Body, &bidResp); err != nil {
+		return nil, []error{err}
+	}
+
+	bidResponse := adapters.NewBidderResponseWithBidsCapacity(1)
+
+	for _, sb := range bidResp.SeatBid {
+		for i := range sb.Bid {
+			bidType, err := getMediaTypeForImp(sb.Bid[i].ImpID, internalRequest.Imp)
+			if err != nil {
+				errs = append(errs, err)
+			} else {
+				b := &adapters.TypedBid{
+					Bid:     &sb.Bid[i],
+					BidType: bidType,
+				}
+				bidResponse.Bids = append(bidResponse.Bids, b)
+			}
+		}
+	}
+	return bidResponse, errs
+}
+
+func getMediaTypeForImp(impID string, imps []openrtb.Imp) (openrtb_ext.BidType, error) {
+	mediaType := openrtb_ext.BidTypeBanner
+	for _, imp := range imps {
+		if imp.ID == impID {
+			if imp.Banner == nil && imp.Video != nil {
+				mediaType = openrtb_ext.BidTypeVideo
+			}
+			return mediaType, nil
+		}
+	}
+
+	// This shouldnt happen. Lets handle it just incase by returning an error.
+	return "", &errortypes.BadInput{
+		Message: fmt.Sprintf("Failed to find impression \"%s\" ", impID),
+	}
+}
diff --git a/adapters/colossus/usersync.go b/adapters/colossus/usersync.go
new file mode 100644
index 00000000..a4e82ee3
--- /dev/null
+++ b/adapters/colossus/usersync.go
@@ -0,0 +1,13 @@
+package colossus
+
+import (
+	"text/template"
+
+	"github.com/prebid/prebid-server/adapters"
+	"github.com/prebid/prebid-server/usersync"
+)
+
+// NewColossusSyncer returns colossus syncer
+func NewColossusSyncer(temp *template.Template) usersync.Usersyncer {
+	return adapters.NewSyncer("colossus", 0, temp, adapters.SyncTypeRedirect)
+}
diff --git a/adapters/inmobi/inmobi.go b/adapters/inmobi/inmobi.go
new file mode 100644
index 00000000..4d46ffb8
--- /dev/null
+++ b/adapters/inmobi/inmobi.go
@@ -0,0 +1,127 @@
+package inmobi
+
+import (
+	"encoding/json"
+	"fmt"
+	"github.com/mxmCherry/openrtb"
+	"github.com/prebid/prebid-server/adapters"
+	"github.com/prebid/prebid-server/errortypes"
+	"github.com/prebid/prebid-server/openrtb_ext"
+	"net/http"
+)
+
+type InMobiAdapter struct {
+	endPoint string
+}
+
+func NewInMobiAdapter(endpoint string) *InMobiAdapter {
+	return &InMobiAdapter{
+		endPoint: endpoint,
+	}
+}
+
+func (a *InMobiAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+	var errs []error
+
+	if len(request.Imp) == 0 {
+		return nil, []error{&errortypes.BadInput{
+			Message: "No impression in the request",
+		}}
+	}
+
+	if err := preprocess(&request.Imp[0]); err != nil {
+		errs = append(errs, err)
+		return nil, errs
+	}
+
+	reqJson, err := json.Marshal(request)
+	if err != nil {
+		errs = append(errs, err)
+		return nil, errs
+	}
+
+	headers := http.Header{}
+	headers.Add("Content-Type", "application/json;charset=utf-8")
+	headers.Add("Accept", "application/json")
+
+	return []*adapters.RequestData{{
+		Method:  "POST",
+		Uri:     a.endPoint,
+		Body:    reqJson,
+		Headers: headers,
+	}}, errs
+}
+
+func (a *InMobiAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+	if response.StatusCode == http.StatusNoContent {
+		return nil, nil
+	}
+
+	if response.StatusCode != http.StatusOK {
+		return nil, []error{&errortypes.BadServerResponse{
+			Message: fmt.Sprintf("Unexpected http status code: %d", response.StatusCode),
+		}}
+	}
+
+	var serverBidResponse openrtb.BidResponse
+	if err := json.Unmarshal(response.Body, &serverBidResponse); err != nil {
+		return nil, []error{err}
+	}
+
+	bidResponse := adapters.NewBidderResponseWithBidsCapacity(1)
+
+	for _, sb := range serverBidResponse.SeatBid {
+		for i := range sb.Bid {
+			mediaType := getMediaTypeForImp(sb.Bid[i].ImpID, internalRequest.Imp)
+			bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
+				Bid:     &sb.Bid[i],
+				BidType: mediaType,
+			})
+		}
+	}
+
+	return bidResponse, nil
+}
+
+func preprocess(imp *openrtb.Imp) error {
+	var bidderExt adapters.ExtImpBidder
+	if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
+		return &errortypes.BadInput{
+			Message: err.Error(),
+		}
+	}
+
+	var inMobiExt openrtb_ext.ExtImpInMobi
+	if err := json.Unmarshal(bidderExt.Bidder, &inMobiExt); err != nil {
+		return &errortypes.BadInput{Message: "bad InMobi bidder ext"}
+	}
+
+	if len(inMobiExt.Plc) == 0 {
+		return &errortypes.BadInput{Message: "'plc' is a required attribute for InMobi's bidder ext"}
+	}
+
+	if imp.Banner != nil {
+		banner := *imp.Banner
+		imp.Banner = &banner
+		if (banner.W == nil || banner.H == nil || *banner.W == 0 || *banner.H == 0) && len(banner.Format) > 0 {
+			format := banner.Format[0]
+			banner.W = &format.W
+			banner.H = &format.H
+		}
+	}
+
+	return nil
+}
+
+func getMediaTypeForImp(impId string, imps []openrtb.Imp) openrtb_ext.BidType {
+	mediaType := openrtb_ext.BidTypeBanner
+	for _, imp := range imps {
+		if imp.ID == impId {
+			if imp.Video != nil {
+				mediaType = openrtb_ext.BidTypeVideo
+			}
+			break
+		}
+	}
+	return mediaType
+}
diff --git a/adapters/rubicon/rubicon.go b/adapters/rubicon/rubicon.go
index 56ae7b2f..7d6e0e12 100644
--- a/adapters/rubicon/rubicon.go
+++ b/adapters/rubicon/rubicon.go
@@ -236,6 +236,7 @@ var rubiSizeMap = map[rubiSize]int{
 	{w: 800, h: 250}:   125,
 	{w: 200, h: 600}:   126,
 	{w: 640, h: 320}:   156,
+	{w: 640, h: 360}:   198,
 }
 
 // defines the contract for bidrequest.user.ext.eids[i].ext
diff --git a/config/config.go b/config/config.go
index 59ba55eb..53daf117 100755
--- a/config/config.go
+++ b/config/config.go
@@ -693,6 +693,7 @@ func (cfg *Configuration) setDerivedDefaults() {
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderBeachfront, "https://sync.bfmio.com/sync_s2s?gdpr={{.GDPR}}&us_privacy={{.USPrivacy}}&url="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dbeachfront%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5Bio_cid%5D")
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderBeintoo, "https://ib.beintoo.com/um?ssp=pbs&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dbeintoo%26uid%3D%24UID")
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderBrightroll, "https://pr-bh.ybp.yahoo.com/sync/appnexusprebidserver/?gdpr={{.GDPR}}&euconsent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&url="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dbrightroll%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID")
+	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderColossus, "https://sync.colossusssp.com/pbs.gif?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dcolossus%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5BUID%5D")
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderConsumable, "https://e.serverbid.com/udb/9969/match?gdpr={{.GDPR}}&euconsent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dconsumable%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D")
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderConversant, "https://prebid-match.dotomi.com/match/bounce/current?version=1&networkId=72582&rurl="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dconversant%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D")
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderCpmstar, "https://server.cpmstar.com/usersync.aspx?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dcpmstar%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID")
@@ -911,6 +912,7 @@ func SetupViper(v *viper.Viper, filename string) {
 	v.SetDefault("adapters.beachfront.extra_info", "{\"video_endpoint\":\"https://reachms.bfmio.com/bid.json?exchange_id\"}")
 	v.SetDefault("adapters.beintoo.endpoint", "https://ib.beintoo.com/um")
 	v.SetDefault("adapters.brightroll.endpoint", "http://east-bid.ybp.yahoo.com/bid/appnexuspbs")
+	v.SetDefault("adapters.colossus.endpoint", "http://colossusssp.com/?c=o&m=rtb")
 	v.SetDefault("adapters.consumable.endpoint", "https://e.serverbid.com/api/v2")
 	v.SetDefault("adapters.conversant.endpoint", "http://api.hb.ad.cpe.dotomi.com/cvx/server/hb/ortb/25")
 	v.SetDefault("adapters.cpmstar.endpoint", "https://server.cpmstar.com/openrtbbidrq.aspx")
@@ -924,6 +926,7 @@ func SetupViper(v *viper.Viper, filename string) {
 	v.SetDefault("adapters.grid.endpoint", "http://grid.bidswitch.net/sp_bid?sp=prebid")
 	v.SetDefault("adapters.gumgum.endpoint", "https://g2.gumgum.com/providers/prbds2s/bid")
 	v.SetDefault("adapters.improvedigital.endpoint", "http://ad.360yield.com/pbs")
+	v.SetDefault("adapters.inmobi.endpoint", "https://api.w.inmobi.com/showad/openrtb/bidder/prebid")
 	v.SetDefault("adapters.ix.endpoint", "http://appnexus-us-east.lb.indexww.com/transbidder?p=184932")
 	v.SetDefault("adapters.kidoz.endpoint", "http://prebid-adapter.kidoz.net/openrtb2/auction?src=prebid-server")
 	v.SetDefault("adapters.kubient.endpoint", "https://kssp.kbntx.ch/prebid")
diff --git a/endpoints/openrtb2/amp_auction.go b/endpoints/openrtb2/amp_auction.go
index 54f47069..1e92569e 100644
--- a/endpoints/openrtb2/amp_auction.go
+++ b/endpoints/openrtb2/amp_auction.go
@@ -16,6 +16,7 @@ import (
 	"github.com/golang/glog"
 	"github.com/julienschmidt/httprouter"
 	"github.com/mxmCherry/openrtb"
+	accountService "github.com/prebid/prebid-server/account"
 	"github.com/prebid/prebid-server/analytics"
 	"github.com/prebid/prebid-server/config"
 	"github.com/prebid/prebid-server/errortypes"
@@ -158,7 +159,7 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h
 	}
 	labels.PubID = getAccountID(req.Site.Publisher)
 	// Look up account now that we have resolved the pubID value
-	account, acctIDErrs := deps.getAccount(ctx, labels.PubID)
+	account, acctIDErrs := accountService.GetAccount(ctx, deps.cfg, deps.accounts, labels.PubID)
 	if len(acctIDErrs) > 0 {
 		errL = append(errL, acctIDErrs...)
 		httpStatus := http.StatusBadRequest
diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go
index bc0cd900..d6cbc228 100644
--- a/endpoints/openrtb2/auction.go
+++ b/endpoints/openrtb2/auction.go
@@ -22,6 +22,7 @@ import (
 	"github.com/mxmCherry/openrtb"
 	"github.com/mxmCherry/openrtb/native"
 	nativeRequests "github.com/mxmCherry/openrtb/native/request"
+	accountService "github.com/prebid/prebid-server/account"
 	"github.com/prebid/prebid-server/analytics"
 	"github.com/prebid/prebid-server/config"
 	"github.com/prebid/prebid-server/errortypes"
@@ -156,7 +157,7 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http
 	}
 
 	// Look up account now that we have resolved the pubID value
-	account, acctIDErrs := deps.getAccount(ctx, labels.PubID)
+	account, acctIDErrs := accountService.GetAccount(ctx, deps.cfg, deps.accounts, labels.PubID)
 	if len(acctIDErrs) > 0 {
 		errL = append(errL, acctIDErrs...)
 		writeError(errL, w, &labels)
@@ -765,8 +766,8 @@ func (deps *endpointDeps) validateImpExt(imp *openrtb.Imp, aliases map[string]st
 	}
 
 	// Also accept bidder exts within imp[...].ext.prebid.bidder
-	// NOTE: This is not part of the official API, we are not expecting clients
-	// migrate from imp[...].ext.${BIDDER} to imp[...].ext.prebid.bidder.${BIDDER}
+	// NOTE: This is not part of the official API yet, so we are not expecting clients
+	// to migrate from imp[...].ext.${BIDDER} to imp[...].ext.prebid.bidder.${BIDDER}
 	// at this time
 	// https://github.com/prebid/prebid-server/pull/846#issuecomment-476352224
 	if rawPrebidExt, ok := bidderExts[openrtb_ext.PrebidExtKey]; ok {
@@ -785,7 +786,7 @@ func (deps *endpointDeps) validateImpExt(imp *openrtb.Imp, aliases map[string]st
 	/* Process all the bidder exts in the request */
 	disabledBidders := []string{}
 	for bidder, ext := range bidderExts {
-		if bidder != openrtb_ext.PrebidExtKey {
+		if isBidderToValidate(bidder) {
 			coreBidder := bidder
 			if tmp, isAlias := aliases[bidder]; isAlias {
 				coreBidder = tmp
@@ -820,12 +821,20 @@ func (deps *endpointDeps) validateImpExt(imp *openrtb.Imp, aliases map[string]st
 	// TODO #713 Fix this here
 	if len(bidderExts) < 1 {
 		errL = append(errL, fmt.Errorf("request.imp[%d].ext must contain at least one bidder", impIndex))
-		return errL
 	}
 
 	return errL
 }
 
+func isBidderToValidate(bidder string) bool {
+	// PrebidExtKey is a special case for the prebid config section and is not considered a bidder.
+
+	// FirstPartyDataContextExtKey is a special case for the first party data context section
+	// and is not considered a bidder.
+
+	return bidder != openrtb_ext.PrebidExtKey && bidder != openrtb_ext.FirstPartyDataContextExtKey
+}
+
 func (deps *endpointDeps) parseBidExt(ext json.RawMessage) (*openrtb_ext.ExtRequest, error) {
 	if len(ext) < 1 {
 		return nil, nil
@@ -1309,56 +1318,3 @@ func getAccountID(pub *openrtb.Publisher) string {
 	}
 	return pbsmetrics.PublisherUnknown
 }
-
-func (deps *endpointDeps) getAccount(ctx context.Context, pubID string) (account *config.Account, errs []error) {
-	// Check BlacklistedAcctMap until we have deprecated it
-	if _, found := deps.cfg.BlacklistedAcctMap[pubID]; found {
-		return nil, []error{&errortypes.BlacklistedAcct{
-			Message: fmt.Sprintf("Prebid-server has disabled Account ID: %s, please reach out to the prebid server host.", pubID),
-		}}
-	}
-	if deps.cfg.AccountRequired && pubID == pbsmetrics.PublisherUnknown {
-		return nil, []error{&errortypes.AcctRequired{
-			Message: fmt.Sprintf("Prebid-server has been configured to discard requests without a valid Account ID. Please reach out to the prebid server host."),
-		}}
-	}
-	if accountJSON, accErrs := deps.accounts.FetchAccount(ctx, pubID); len(accErrs) > 0 || accountJSON == nil {
-		// pubID does not reference a valid account
-		if len(accErrs) > 0 {
-			errs = append(errs, errs...)
-		}
-		if deps.cfg.AccountRequired && deps.cfg.AccountDefaults.Disabled {
-			errs = append(errs, &errortypes.AcctRequired{
-				Message: fmt.Sprintf("Prebid-server has been configured to discard requests without a valid Account ID. Please reach out to the prebid server host."),
-			})
-			return nil, errs
-		}
-		// Make a copy of AccountDefaults instead of taking a reference,
-		// to preserve original pubID in case is needed to check NonStandardPublisherMap
-		pubAccount := deps.cfg.AccountDefaults
-		pubAccount.ID = pubID
-		account = &pubAccount
-	} else {
-		// pubID resolved to a valid account, merge with AccountDefaults for a complete config
-		account = &config.Account{}
-		completeJSON, err := jsonpatch.MergePatch(deps.cfg.AccountDefaultsJSON(), accountJSON)
-		if err == nil {
-			err = json.Unmarshal(completeJSON, account)
-		}
-		if err != nil {
-			errs = append(errs, err)
-			return nil, errs
-		}
-		// Fill in ID if needed, so it can be left out of account definition
-		if len(account.ID) == 0 {
-			account.ID = pubID
-		}
-	}
-	if account.Disabled {
-		errs = append(errs, &errortypes.BlacklistedAcct{
-			Message: fmt.Sprintf("Prebid-server has disabled Account ID: %s, please reach out to the prebid server host.", pubID),
-		})
-		return nil, errs
-	}
-	return
-}
diff --git a/endpoints/openrtb2/sample-requests/first-party-data/valid-context-allowed-with-ext-bidder.json b/endpoints/openrtb2/sample-requests/first-party-data/valid-context-allowed-with-ext-bidder.json
new file mode 100644
index 00000000..aa205fc5
--- /dev/null
+++ b/endpoints/openrtb2/sample-requests/first-party-data/valid-context-allowed-with-ext-bidder.json
@@ -0,0 +1,32 @@
+{
+    "description": "The imp.ext.context field is valid for First Party Data and should be exempted from bidder name validation.",
+
+    "requestPayload": {
+        "id": "some-request-id",
+        "site": {
+            "page": "test.somepage.com"
+        },
+        "imp": [{
+            "id": "some-imp-id",
+            "banner": {
+                "format": [{
+                    "w": 600,
+                    "h": 500
+                }, {
+                    "w": 300,
+                    "h": 600
+                }]
+            },
+            "ext": {
+                "appnexus": {
+                    "placementId": 12883451
+                },
+                "context": {
+                    "data": {
+                        "keywords": "prebid server example"
+                    }
+                }
+            }
+        }]
+    }
+}
\ No newline at end of file
diff --git a/endpoints/openrtb2/sample-requests/first-party-data/valid-context-allowed-with-prebid-bidder.json b/endpoints/openrtb2/sample-requests/first-party-data/valid-context-allowed-with-prebid-bidder.json
new file mode 100644
index 00000000..1616e84b
--- /dev/null
+++ b/endpoints/openrtb2/sample-requests/first-party-data/valid-context-allowed-with-prebid-bidder.json
@@ -0,0 +1,36 @@
+{
+    "description": "The imp.ext.context field is valid for First Party Data and should be exempted from bidder name validation.",
+
+    "requestPayload": {
+        "id": "some-request-id",
+        "site": {
+            "page": "test.somepage.com"
+        },
+        "imp": [{
+            "id": "some-imp-id",
+            "banner": {
+                "format": [{
+                    "w": 600,
+                    "h": 500
+                }, {
+                    "w": 300,
+                    "h": 600
+                }]
+            },
+            "ext": {
+                "prebid": {
+                    "bidder": {
+                        "appnexus": {
+                            "placementId": 12883451
+                        }
+                    }
+                },
+                "context": {
+                    "data": {
+                        "keywords": "prebid server example"
+                    }
+                }
+            }
+        }]
+    }
+}
\ No newline at end of file
diff --git a/endpoints/openrtb2/video_auction.go b/endpoints/openrtb2/video_auction.go
index f5494751..ab5634c7 100644
--- a/endpoints/openrtb2/video_auction.go
+++ b/endpoints/openrtb2/video_auction.go
@@ -23,6 +23,7 @@ import (
 	"github.com/golang/glog"
 	"github.com/julienschmidt/httprouter"
 	"github.com/mxmCherry/openrtb"
+	accountService "github.com/prebid/prebid-server/account"
 	"github.com/prebid/prebid-server/analytics"
 	"github.com/prebid/prebid-server/config"
 	"github.com/prebid/prebid-server/exchange"
@@ -255,7 +256,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re
 	}
 
 	// Look up account now that we have resolved the pubID value
-	account, acctIDErrs := deps.getAccount(ctx, labels.PubID)
+	account, acctIDErrs := accountService.GetAccount(ctx, deps.cfg, deps.accounts, labels.PubID)
 	if len(acctIDErrs) > 0 {
 		handleError(&labels, w, acctIDErrs, &vo, &debugLog)
 		return
diff --git a/exchange/adapter_map.go b/exchange/adapter_map.go
index a160e87a..d4281689 100755
--- a/exchange/adapter_map.go
+++ b/exchange/adapter_map.go
@@ -31,6 +31,7 @@ import (
 	"github.com/prebid/prebid-server/adapters/beachfront"
 	"github.com/prebid/prebid-server/adapters/beintoo"
 	"github.com/prebid/prebid-server/adapters/brightroll"
+	"github.com/prebid/prebid-server/adapters/colossus"
 	"github.com/prebid/prebid-server/adapters/consumable"
 	"github.com/prebid/prebid-server/adapters/conversant"
 	"github.com/prebid/prebid-server/adapters/cpmstar"
@@ -44,6 +45,7 @@ import (
 	"github.com/prebid/prebid-server/adapters/grid"
 	"github.com/prebid/prebid-server/adapters/gumgum"
 	"github.com/prebid/prebid-server/adapters/improvedigital"
+	"github.com/prebid/prebid-server/adapters/inmobi"
 	"github.com/prebid/prebid-server/adapters/ix"
 	"github.com/prebid/prebid-server/adapters/kidoz"
 	"github.com/prebid/prebid-server/adapters/kubient"
@@ -118,6 +120,7 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter
 		openrtb_ext.BidderBeachfront:   beachfront.NewBeachfrontBidder(cfg.Adapters[string(openrtb_ext.BidderBeachfront)].Endpoint, cfg.Adapters[string(openrtb_ext.BidderBeachfront)].ExtraAdapterInfo),
 		openrtb_ext.BidderBeintoo:      beintoo.NewBeintooBidder(cfg.Adapters[string(openrtb_ext.BidderBeintoo)].Endpoint),
 		openrtb_ext.BidderBrightroll:   brightroll.NewBrightrollBidder(cfg.Adapters[string(openrtb_ext.BidderBrightroll)].Endpoint, cfg.Adapters[string(openrtb_ext.BidderBrightroll)].ExtraAdapterInfo),
+		openrtb_ext.BidderColossus:     colossus.NewColossusBidder(cfg.Adapters[string(openrtb_ext.BidderColossus)].Endpoint),
 		openrtb_ext.BidderConsumable:   consumable.NewConsumableBidder(cfg.Adapters[string(openrtb_ext.BidderConsumable)].Endpoint),
 		openrtb_ext.BidderCpmstar:      cpmstar.NewCpmstarBidder(cfg.Adapters[string(openrtb_ext.BidderCpmstar)].Endpoint),
 		openrtb_ext.BidderDatablocks:   datablocks.NewDatablocksBidder(cfg.Adapters[string(openrtb_ext.BidderDatablocks)].Endpoint),
@@ -133,6 +136,7 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter
 		openrtb_ext.BidderGrid:            grid.NewGridBidder(cfg.Adapters[string(openrtb_ext.BidderGrid)].Endpoint),
 		openrtb_ext.BidderGumGum:          gumgum.NewGumGumBidder(cfg.Adapters[string(openrtb_ext.BidderGumGum)].Endpoint),
 		openrtb_ext.BidderImprovedigital:  improvedigital.NewImprovedigitalBidder(cfg.Adapters[string(openrtb_ext.BidderImprovedigital)].Endpoint),
+		openrtb_ext.BidderInMobi:          inmobi.NewInMobiAdapter(cfg.Adapters[string(openrtb_ext.BidderInMobi)].Endpoint),
 		openrtb_ext.BidderKidoz:           kidoz.NewKidozBidder(cfg.Adapters[string(openrtb_ext.BidderKidoz)].Endpoint),
 		openrtb_ext.BidderKubient:         kubient.NewKubientBidder(cfg.Adapters[string(openrtb_ext.BidderKubient)].Endpoint),
 		openrtb_ext.BidderLockerDome:      lockerdome.NewLockerDomeBidder(cfg.Adapters[string(openrtb_ext.BidderLockerDome)].Endpoint),
diff --git a/exchange/utils.go b/exchange/utils.go
index 2e9e4dc8..22b28adc 100644
--- a/exchange/utils.go
+++ b/exchange/utils.go
@@ -107,12 +107,10 @@ func cleanOpenRTBRequests(ctx context.Context,
 			coreBidder := resolveBidder(bidder.String(), aliases)
 
 			var publisherID = labels.PubID
-			ok, geo, id, err := gDPR.PersonalInfoAllowed(ctx, coreBidder, publisherID, consent)
-			privacyEnforcement.GDPR = !ok && err == nil
+			_, geo, id, err := gDPR.PersonalInfoAllowed(ctx, coreBidder, publisherID, consent)
 			privacyEnforcement.GDPRGeo = !geo && err == nil
 			privacyEnforcement.GDPRID = !id && err == nil
 		} else {
-			privacyEnforcement.GDPR = false
 			privacyEnforcement.GDPRGeo = false
 			privacyEnforcement.GDPRID = false
 		}
@@ -273,13 +271,18 @@ func splitImps(imps []openrtb.Imp) (map[string][]openrtb.Imp, []error) {
 		imp := imps[i]
 		impExt := impExts[i]
 
+		var firstPartyDataContext json.RawMessage
+		if context, exists := impExt[openrtb_ext.FirstPartyDataContextExtKey]; exists {
+			firstPartyDataContext = context
+		}
+
 		rawPrebidExt, ok := impExt[openrtb_ext.PrebidExtKey]
 
 		if ok {
 			var prebidExt openrtb_ext.ExtImpPrebid
 
 			if err := json.Unmarshal(rawPrebidExt, &prebidExt); err == nil && prebidExt.Bidder != nil {
-				if errs := sanitizedImpCopy(&imp, prebidExt.Bidder, rawPrebidExt, &splitImps); errs != nil {
+				if errs := sanitizedImpCopy(&imp, prebidExt.Bidder, rawPrebidExt, firstPartyDataContext, &splitImps); errs != nil {
 					errList = append(errList, errs...)
 				}
 
@@ -287,7 +290,7 @@ func splitImps(imps []openrtb.Imp) (map[string][]openrtb.Imp, []error) {
 			}
 		}
 
-		if errs := sanitizedImpCopy(&imp, impExt, rawPrebidExt, &splitImps); errs != nil {
+		if errs := sanitizedImpCopy(&imp, impExt, rawPrebidExt, firstPartyDataContext, &splitImps); errs != nil {
 			errList = append(errList, errs...)
 		}
 	}
@@ -295,35 +298,38 @@ func splitImps(imps []openrtb.Imp) (map[string][]openrtb.Imp, []error) {
 	return splitImps, nil
 }
 
-// sanitizedImpCopy returns a copy of imp with its ext filtered so that only "prebid" and bidder params exist.
+// sanitizedImpCopy returns a copy of imp with its ext filtered so that only "prebid", "context", and bidder params exist.
 // It will not mutate the input imp.
 // This function will write the new imps to the output map passed in
 func sanitizedImpCopy(imp *openrtb.Imp,
 	bidderExts map[string]json.RawMessage,
 	rawPrebidExt json.RawMessage,
+	firstPartyDataContext json.RawMessage,
 	out *map[string][]openrtb.Imp) []error {
 
 	var prebidExt map[string]json.RawMessage
 	var errs []error
 
-	// We don't want to include other demand partners' bidder params
-	// in the sanitized imp
 	if err := json.Unmarshal(rawPrebidExt, &prebidExt); err == nil {
-		delete(prebidExt, "bidder")
-
-		var err error
-		if rawPrebidExt, err = json.Marshal(prebidExt); err != nil {
-			errs = append(errs, err)
+		// Remove the entire bidder field. We will already have the content we need in bidderExts. We
+		// don't want to include other demand partners' bidder params in the sanitized imp.
+		if _, hasBidderField := prebidExt["bidder"]; hasBidderField {
+			delete(prebidExt, "bidder")
+
+			var err error
+			if rawPrebidExt, err = json.Marshal(prebidExt); err != nil {
+				errs = append(errs, err)
+			}
 		}
 	}
 
 	for bidder, ext := range bidderExts {
-		if bidder == openrtb_ext.PrebidExtKey {
+		if bidder == openrtb_ext.PrebidExtKey || bidder == openrtb_ext.FirstPartyDataContextExtKey {
 			continue
 		}
 
 		impCopy := *imp
-		newExt := make(map[string]json.RawMessage, 2)
+		newExt := make(map[string]json.RawMessage, 3)
 
 		newExt["bidder"] = ext
 
@@ -331,6 +337,10 @@ func sanitizedImpCopy(imp *openrtb.Imp,
 			newExt[openrtb_ext.PrebidExtKey] = rawPrebidExt
 		}
 
+		if len(firstPartyDataContext) > 0 {
+			newExt[openrtb_ext.FirstPartyDataContextExtKey] = firstPartyDataContext
+		}
+
 		rawExt, err := json.Marshal(newExt)
 		if err != nil {
 			errs = append(errs, err)
@@ -392,7 +402,7 @@ func resolveBidder(bidder string, aliases map[string]string) openrtb_ext.BidderN
 }
 
 // parseImpExts does a partial-unmarshal of the imp[].Ext field.
-// The keys in the returned map are expected to be "prebid", core BidderNames, or Aliases for this request.
+// The keys in the returned map are expected to be "prebid", "context", core BidderNames, or Aliases for this request.
 func parseImpExts(imps []openrtb.Imp) ([]map[string]json.RawMessage, error) {
 	exts := make([]map[string]json.RawMessage, len(imps))
 	// Loop over every impression in the request
diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go
index 761f53d4..dcfd663e 100755
--- a/openrtb_ext/bidders.go
+++ b/openrtb_ext/bidders.go
@@ -20,6 +20,9 @@ type BidderName string
 // BidderNameGeneral is reserved for non-bidder specific messages when using a map keyed on the bidder name.
 const BidderNameGeneral = BidderName("general")
 
+// BidderNameContext is reserved for first party data.
+const BidderNameContext = BidderName("context")
+
 // These names _must_ coincide with the bidder code in Prebid.js, if an adapter also exists in that project.
 // Please keep these (and the BidderMap) alphabetized to minimize merge conflicts among adapter submissions.
 // The bidder name 'general' is not allowed since it has special meaning in message maps.
@@ -46,6 +49,7 @@ const (
 	BidderBeachfront       BidderName = "beachfront"
 	BidderBeintoo          BidderName = "beintoo"
 	BidderBrightroll       BidderName = "brightroll"
+	BidderColossus         BidderName = "colossus"
 	BidderConsumable       BidderName = "consumable"
 	BidderConversant       BidderName = "conversant"
 	BidderCpmstar          BidderName = "cpmstar"
@@ -60,6 +64,7 @@ const (
 	BidderGrid             BidderName = "grid"
 	BidderGumGum           BidderName = "gumgum"
 	BidderImprovedigital   BidderName = "improvedigital"
+	BidderInMobi           BidderName = "inmobi"
 	BidderIx               BidderName = "ix"
 	BidderKidoz            BidderName = "kidoz"
 	BidderKubient          BidderName = "kubient"
@@ -130,6 +135,7 @@ var BidderMap = map[string]BidderName{
 	"beachfront":        BidderBeachfront,
 	"beintoo":           BidderBeintoo,
 	"brightroll":        BidderBrightroll,
+	"colossus":          BidderColossus,
 	"consumable":        BidderConsumable,
 	"conversant":        BidderConversant,
 	"cpmstar":           BidderCpmstar,
@@ -144,6 +150,7 @@ var BidderMap = map[string]BidderName{
 	"grid":              BidderGrid,
 	"gumgum":            BidderGumGum,
 	"improvedigital":    BidderImprovedigital,
+	"inmobi":            BidderInMobi,
 	"ix":                BidderIx,
 	"kidoz":             BidderKidoz,
 	"kubient":           BidderKubient,
diff --git a/openrtb_ext/imp_colossus.go b/openrtb_ext/imp_colossus.go
new file mode 100644
index 00000000..89690005
--- /dev/null
+++ b/openrtb_ext/imp_colossus.go
@@ -0,0 +1,6 @@
+package openrtb_ext
+
+// ExtImpColossus defines colossus specifiec param
+type ExtImpColossus struct {
+	TagID string `json:"TagID"`
+}
diff --git a/openrtb_ext/imp_inmobi.go b/openrtb_ext/imp_inmobi.go
new file mode 100644
index 00000000..d74e3cac
--- /dev/null
+++ b/openrtb_ext/imp_inmobi.go
@@ -0,0 +1,5 @@
+package openrtb_ext
+
+type ExtImpInMobi struct {
+	Plc string `json:"plc"`
+}
diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go
index d6edf47f..42ac9d9d 100644
--- a/openrtb_ext/request.go
+++ b/openrtb_ext/request.go
@@ -5,6 +5,10 @@ import (
 	"errors"
 )
 
+// FirstPartyDataContextExtKey defines the field name within bidrequest.ext reserved
+// for first party data support.
+const FirstPartyDataContextExtKey string = "context"
+
 // ExtRequest defines the contract for bidrequest.ext
 type ExtRequest struct {
 	Prebid ExtRequestPrebid `json:"prebid"`
diff --git a/privacy/enforcement.go b/privacy/enforcement.go
index 9c23c320..3f157329 100644
--- a/privacy/enforcement.go
+++ b/privacy/enforcement.go
@@ -8,7 +8,6 @@ import (
 type Enforcement struct {
 	CCPA    bool
 	COPPA   bool
-	GDPR    bool
 	GDPRGeo bool
 	GDPRID  bool
 	LMT     bool
@@ -16,7 +15,7 @@ type Enforcement struct {
 
 // Any returns true if at least one privacy policy requires enforcement.
 func (e Enforcement) Any() bool {
-	return e.CCPA || e.COPPA || e.GDPR || e.GDPRGeo || e.GDPRID || e.LMT
+	return e.CCPA || e.COPPA || e.GDPRGeo || e.GDPRID || e.LMT
 }
 
 // Apply cleans personally identifiable information from an OpenRTB bid request.
@@ -26,17 +25,33 @@ func (e Enforcement) Apply(bidRequest *openrtb.BidRequest, ampGDPRException bool
 
 func (e Enforcement) apply(bidRequest *openrtb.BidRequest, ampGDPRException bool, scrubber Scrubber) {
 	if bidRequest != nil && e.Any() {
-		bidRequest.Device = scrubber.ScrubDevice(bidRequest.Device, e.getIPv6ScrubStrategy(), e.getGeoScrubStrategy())
+		bidRequest.Device = scrubber.ScrubDevice(bidRequest.Device, e.getDeviceIDScrubStrategy(), e.getIPv4ScrubStrategy(), e.getIPv6ScrubStrategy(), e.getGeoScrubStrategy())
 		bidRequest.User = scrubber.ScrubUser(bidRequest.User, e.getUserScrubStrategy(ampGDPRException), e.getGeoScrubStrategy())
 	}
 }
 
+func (e Enforcement) getDeviceIDScrubStrategy() ScrubStrategyDeviceID {
+	if e.COPPA || e.GDPRID || e.CCPA || e.LMT {
+		return ScrubStrategyDeviceIDAll
+	}
+
+	return ScrubStrategyDeviceIDNone
+}
+
+func (e Enforcement) getIPv4ScrubStrategy() ScrubStrategyIPV4 {
+	if e.COPPA || e.GDPRGeo || e.CCPA || e.LMT {
+		return ScrubStrategyIPV4Lowest8
+	}
+
+	return ScrubStrategyIPV4None
+}
+
 func (e Enforcement) getIPv6ScrubStrategy() ScrubStrategyIPV6 {
 	if e.COPPA {
 		return ScrubStrategyIPV6Lowest32
 	}
 
-	if e.GDPR || e.CCPA || e.LMT {
+	if e.GDPRGeo || e.CCPA || e.LMT {
 		return ScrubStrategyIPV6Lowest16
 	}
 
@@ -60,12 +75,11 @@ func (e Enforcement) getUserScrubStrategy(ampGDPRException bool) ScrubStrategyUs
 		return ScrubStrategyUserIDAndDemographic
 	}
 
-	if e.GDPR && ampGDPRException {
-		return ScrubStrategyUserNone
+	if e.CCPA || e.LMT {
+		return ScrubStrategyUserID
 	}
 
-	// If no user scrubbing is needed, then return none, else scrub ID (COPPA checked above)
-	if e.CCPA || e.GDPRID || e.LMT {
+	if e.GDPRID && !ampGDPRException {
 		return ScrubStrategyUserID
 	}
 
diff --git a/privacy/scrubber.go b/privacy/scrubber.go
index 65543683..8771c8b3 100644
--- a/privacy/scrubber.go
+++ b/privacy/scrubber.go
@@ -7,6 +7,17 @@ import (
 	"github.com/mxmCherry/openrtb"
 )
 
+// ScrubStrategyIPV4 defines the approach to scrub PII from an IPV4 address.
+type ScrubStrategyIPV4 int
+
+const (
+	// ScrubStrategyIPV4None does not remove any part of an IPV4 address.
+	ScrubStrategyIPV4None ScrubStrategyIPV4 = iota
+
+	// ScrubStrategyIPV4Lowest8 zeroes out the last 8 bits of an IPV4 address.
+	ScrubStrategyIPV4Lowest8
+)
+
 // ScrubStrategyIPV6 defines the approach to scrub PII from an IPV6 address.
 type ScrubStrategyIPV6 int
 
@@ -49,9 +60,20 @@ const (
 	ScrubStrategyUserID
 )
 
+// ScrubStrategyDeviceID defines the approach to remove hardware id and device id data.
+type ScrubStrategyDeviceID int
+
+const (
+	// ScrubStrategyDeviceIDNone does not remove hardware id and device id data.
+	ScrubStrategyDeviceIDNone ScrubStrategyDeviceID = iota
+
+	// ScrubStrategyDeviceIDAll removes all hardware and device id data (ifa, mac hashes device id hashes)
+	ScrubStrategyDeviceIDAll
+)
+
 // Scrubber removes PII from parts of an OpenRTB request.
 type Scrubber interface {
-	ScrubDevice(device *openrtb.Device, ipv6 ScrubStrategyIPV6, geo ScrubStrategyGeo) *openrtb.Device
+	ScrubDevice(device *openrtb.Device, id ScrubStrategyDeviceID, ipv4 ScrubStrategyIPV4, ipv6 ScrubStrategyIPV6, geo ScrubStrategyGeo) *openrtb.Device
 	ScrubUser(user *openrtb.User, strategy ScrubStrategyUser, geo ScrubStrategyGeo) *openrtb.User
 }
 
@@ -62,20 +84,28 @@ func NewScrubber() Scrubber {
 	return scrubber{}
 }
 
-func (scrubber) ScrubDevice(device *openrtb.Device, ipv6 ScrubStrategyIPV6, geo ScrubStrategyGeo) *openrtb.Device {
+func (scrubber) ScrubDevice(device *openrtb.Device, id ScrubStrategyDeviceID, ipv4 ScrubStrategyIPV4, ipv6 ScrubStrategyIPV6, geo ScrubStrategyGeo) *openrtb.Device {
 	if device == nil {
 		return nil
 	}
 
 	deviceCopy := *device
-	deviceCopy.DIDMD5 = ""
-	deviceCopy.DIDSHA1 = ""
-	deviceCopy.DPIDMD5 = ""
-	deviceCopy.DPIDSHA1 = ""
-	deviceCopy.IFA = ""
-	deviceCopy.MACMD5 = ""
-	deviceCopy.MACSHA1 = ""
-	deviceCopy.IP = scrubIPV4(device.IP)
+
+	switch id {
+	case ScrubStrategyDeviceIDAll:
+		deviceCopy.DIDMD5 = ""
+		deviceCopy.DIDSHA1 = ""
+		deviceCopy.DPIDMD5 = ""
+		deviceCopy.DPIDSHA1 = ""
+		deviceCopy.IFA = ""
+		deviceCopy.MACMD5 = ""
+		deviceCopy.MACSHA1 = ""
+	}
+
+	switch ipv4 {
+	case ScrubStrategyIPV4Lowest8:
+		deviceCopy.IP = scrubIPV4Lowest8(device.IP)
+	}
 
 	switch ipv6 {
 	case ScrubStrategyIPV6Lowest16:
@@ -124,7 +154,7 @@ func (scrubber) ScrubUser(user *openrtb.User, strategy ScrubStrategyUser, geo Sc
 	return &userCopy
 }
 
-func scrubIPV4(ip string) string {
+func scrubIPV4Lowest8(ip string) string {
 	i := strings.LastIndex(ip, ".")
 	if i == -1 {
 		return ""
diff --git a/static/bidder-info/colossus.yaml b/static/bidder-info/colossus.yaml
new file mode 100644
index 00000000..901c824c
--- /dev/null
+++ b/static/bidder-info/colossus.yaml
@@ -0,0 +1,11 @@
+maintainer:
+  email: "support@huddledmasses.com"
+capabilities:
+  app:
+    mediaTypes:
+      - banner
+      - video
+  site:
+    mediaTypes:
+      - banner
+      - video
diff --git a/static/bidder-info/inmobi.yaml b/static/bidder-info/inmobi.yaml
new file mode 100644
index 00000000..3f8cdd8c
--- /dev/null
+++ b/static/bidder-info/inmobi.yaml
@@ -0,0 +1,8 @@
+maintainer:
+  email: "prebid-support@inmobi.com"
+
+capabilities:
+  app:
+    mediaTypes:
+      - banner
+      - video
diff --git a/static/bidder-params/colossus.json b/static/bidder-params/colossus.json
new file mode 100644
index 00000000..f2732fa0
--- /dev/null
+++ b/static/bidder-params/colossus.json
@@ -0,0 +1,14 @@
+{
+    "$schema": "http://json-schema.org/draft-04/schema#",
+    "title": "Colossus Adapter Params",
+    "description": "A schema which validates params accepted by the Colossus adapter",
+
+    "type": "object",
+    "properties": {
+      "TagID": {
+        "type": "string",
+        "description": "An ID which identifies the colossus ad tag"
+      }
+    },
+    "required" : [ "TagID" ]
+  }
diff --git a/static/bidder-params/inmobi.json b/static/bidder-params/inmobi.json
new file mode 100644
index 00000000..631b3137
--- /dev/null
+++ b/static/bidder-params/inmobi.json
@@ -0,0 +1,13 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "title": "InMobi Adapter Params",
+  "description": "A schema which validates params accepted by the InMobi adapter",
+  "type": "object",
+  "properties": {
+    "plc": {
+      "type": ["string"],
+      "description": "An ID corresponding to the placement selling the impression"
+    }
+  },
+  "required": ["plc"]
+}
diff --git a/stored_requests/caches/memory/cache.go b/stored_requests/caches/memory/cache.go
index c5702080..aea087e6 100644
--- a/stored_requests/caches/memory/cache.go
+++ b/stored_requests/caches/memory/cache.go
@@ -7,7 +7,6 @@ import (
 
 	"github.com/coocood/freecache"
 	"github.com/golang/glog"
-	"github.com/prebid/prebid-server/config"
 	"github.com/prebid/prebid-server/stored_requests"
 )
 
@@ -17,68 +16,51 @@ import (
 // 2. The cache is too large. This will cause the least recently used items to be evicted.
 //
 // For no TTL, use ttlSeconds <= 0
-func NewCache(cfg *config.InMemoryCache) stored_requests.Cache {
-	return &cache{
-		requestDataCache: newCacheForWithLimits(cfg.RequestCacheSize, cfg.TTL, "Request"),
-		impDataCache:     newCacheForWithLimits(cfg.ImpCacheSize, cfg.TTL, "Imp"),
-	}
-}
-
-func newCacheForWithLimits(size int, ttl int, dataType string) mapLike {
+func NewCache(size int, ttl int, dataType string) stored_requests.CacheJSON {
 	if ttl > 0 && size <= 0 {
-		glog.Fatal("No in-memory caches defined with a finite TTL but unbounded size. Config validation should have caught this. Failing fast because something is buggy.")
+		glog.Fatalf("No in-memory %s caches defined with a finite TTL but unbounded size. Config validation should have caught this. Failing fast because something is buggy.", dataType)
 	}
 	if size > 0 {
 		glog.Infof("Using a Stored %s in-memory cache. Max size: %d bytes. TTL: %d seconds.", dataType, size, ttl)
-		return &pbsLRUCache{
-			Cache:      freecache.NewCache(size),
-			ttlSeconds: ttl,
+		return &cache{
+			dataType: dataType,
+			cache: &pbsLRUCache{
+				Cache:      freecache.NewCache(size),
+				ttlSeconds: ttl,
+			},
 		}
 	} else {
 		glog.Infof("Using an unbounded Stored %s in-memory cache.", dataType)
-		return &pbsSyncMap{&sync.Map{}}
+		return &cache{
+			dataType: dataType,
+			cache:    &pbsSyncMap{&sync.Map{}},
+		}
 	}
 }
 
 type cache struct {
-	requestDataCache mapLike
-	impDataCache     mapLike
+	dataType string
+	cache    mapLike
 }
 
-func (c *cache) Get(ctx context.Context, requestIDs []string, impIDs []string) (requestData map[string]json.RawMessage, impData map[string]json.RawMessage) {
-	requestData = doGet(c.requestDataCache, requestIDs)
-	impData = doGet(c.impDataCache, impIDs)
-	return
-}
-
-func doGet(cache mapLike, ids []string) (data map[string]json.RawMessage) {
+func (c *cache) Get(ctx context.Context, ids []string) (data map[string]json.RawMessage) {
 	data = make(map[string]json.RawMessage, len(ids))
 	for _, id := range ids {
-		if val, ok := cache.Get(id); ok {
+		if val, ok := c.cache.Get(id); ok {
 			data[id] = val
 		}
 	}
 	return
 }
 
-func (c *cache) Save(ctx context.Context, storedRequests map[string]json.RawMessage, storedImps map[string]json.RawMessage) {
-	c.doSave(c.requestDataCache, storedRequests)
-	c.doSave(c.impDataCache, storedImps)
-}
-
-func (c *cache) doSave(cache mapLike, values map[string]json.RawMessage) {
-	for id, data := range values {
-		cache.Set(id, data)
+func (c *cache) Save(ctx context.Context, data map[string]json.RawMessage) {
+	for id, data := range data {
+		c.cache.Set(id, data)
 	}
 }
 
-func (c *cache) Invalidate(ctx context.Context, requestIDs []string, impIDs []string) {
-	doInvalidate(c.requestDataCache, requestIDs)
-	doInvalidate(c.impDataCache, impIDs)
-}
-
-func doInvalidate(cache mapLike, ids []string) {
+func (c *cache) Invalidate(ctx context.Context, ids []string) {
 	for _, id := range ids {
-		cache.Delete(id)
+		c.cache.Delete(id)
 	}
 }
diff --git a/stored_requests/caches/nil_cache/nil_cache.go b/stored_requests/caches/nil_cache/nil_cache.go
index de29156e..d043ae55 100644
--- a/stored_requests/caches/nil_cache/nil_cache.go
+++ b/stored_requests/caches/nil_cache/nil_cache.go
@@ -8,13 +8,14 @@ import (
 // NilCache is a no-op cache which does nothing useful.
 type NilCache struct{}
 
-func (c *NilCache) Get(ctx context.Context, requestIDs []string, impIDs []string) (map[string]json.RawMessage, map[string]json.RawMessage) {
-	return nil, nil
+func (c *NilCache) Get(ctx context.Context, ids []string) map[string]json.RawMessage {
+	return make(map[string]json.RawMessage)
 }
-func (c *NilCache) Save(ctx context.Context, storedRequests map[string]json.RawMessage, storedImps map[string]json.RawMessage) {
+
+func (c *NilCache) Save(ctx context.Context, data map[string]json.RawMessage) {
 	return
 }
 
-func (c *NilCache) Invalidate(ctx context.Context, requestIDs []string, impIDs []string) {
+func (c *NilCache) Invalidate(ctx context.Context, ids []string) {
 	return
 }
diff --git a/stored_requests/config/config.go b/stored_requests/config/config.go
index e81d9667..6c663cbf 100644
--- a/stored_requests/config/config.go
+++ b/stored_requests/config/config.go
@@ -178,10 +178,13 @@ func newFetcher(cfg *config.StoredRequests, client *http.Client, db *sql.DB) (fe
 func newCache(cfg *config.StoredRequests) stored_requests.Cache {
 	if cfg.InMemoryCache.Type == "none" {
 		glog.Infof("No Stored %s cache configured. The %s Fetcher backend will be used for all data requests", cfg.DataType(), cfg.DataType())
-		return &nil_cache.NilCache{}
+		return stored_requests.Cache{&nil_cache.NilCache{}, &nil_cache.NilCache{}}
 	}
 
-	return memory.NewCache(&cfg.InMemoryCache)
+	return stored_requests.Cache{
+		Requests: memory.NewCache(cfg.InMemoryCache.RequestCacheSize, cfg.InMemoryCache.TTL, "Requests"),
+		Imps:     memory.NewCache(cfg.InMemoryCache.ImpCacheSize, cfg.InMemoryCache.TTL, "Imps"),
+	}
 }
 
 func newEventProducers(cfg *config.StoredRequests, client *http.Client, db *sql.DB, router *httprouter.Router) (eventProducers []events.EventProducer) {
diff --git a/stored_requests/events/events.go b/stored_requests/events/events.go
index ea67e8ee..ba08f13d 100644
--- a/stored_requests/events/events.go
+++ b/stored_requests/events/events.go
@@ -61,12 +61,14 @@ func (e *EventListener) Listen(cache stored_requests.Cache, events EventProducer
 	for {
 		select {
 		case save := <-events.Saves():
-			cache.Save(context.Background(), save.Requests, save.Imps)
+			cache.Requests.Save(context.Background(), save.Requests)
+			cache.Imps.Save(context.Background(), save.Imps)
 			if e.onSave != nil {
 				e.onSave()
 			}
 		case invalidation := <-events.Invalidations():
-			cache.Invalidate(context.Background(), invalidation.Requests, invalidation.Imps)
+			cache.Requests.Invalidate(context.Background(), invalidation.Requests)
+			cache.Imps.Invalidate(context.Background(), invalidation.Imps)
 			if e.onInvalidate != nil {
 				e.onInvalidate()
 			}
diff --git a/stored_requests/fetcher.go b/stored_requests/fetcher.go
index a31b9989..e9716e08 100644
--- a/stored_requests/fetcher.go
+++ b/stored_requests/fetcher.go
@@ -37,9 +37,9 @@ type CategoryFetcher interface {
 
 // AllFetcher is an interface that encapsulates both the original Fetcher and the CategoryFetcher
 type AllFetcher interface {
-	FetchRequests(ctx context.Context, requestIDs []string, impIDs []string) (requestData map[string]json.RawMessage, impData map[string]json.RawMessage, errs []error)
-	FetchAccount(ctx context.Context, accountID string) (json.RawMessage, []error)
-	FetchCategories(ctx context.Context, primaryAdServer, publisherId, iabCategory string) (string, error)
+	Fetcher
+	AccountFetcher
+	CategoryFetcher
 }
 
 // NotFoundError is an error type to flag that an ID was not found by the Fetcher.
@@ -62,7 +62,11 @@ func (e NotFoundError) Error() string {
 // Cache is an intermediate layer which can be used to create more complex Fetchers by composition.
 // Implementations must be safe for concurrent access by multiple goroutines.
 // To add a Cache layer in front of a Fetcher, see WithCache()
-type Cache interface {
+type Cache struct {
+	Requests CacheJSON
+	Imps     CacheJSON
+}
+type CacheJSON interface {
 	// Get works much like Fetcher.FetchRequests, with a few exceptions:
 	//
 	// 1. Any (actionable) errors should be logged by the implementation, rather than returned.
@@ -73,37 +77,33 @@ type Cache interface {
 	//
 	// Nil slices and empty strings are treated as "no ops". That is, a nil requestID will always produce a nil
 	// "stored request data" in the response.
-	Get(ctx context.Context, requestIDs []string, impIDs []string) (requestData map[string]json.RawMessage, impData map[string]json.RawMessage)
+	Get(ctx context.Context, ids []string) (data map[string]json.RawMessage)
 
 	// Invalidate will ensure that all values associated with the given IDs
 	// are no longer returned by the cache until new values are saved via Update
-	Invalidate(ctx context.Context, requestIDs []string, impIDs []string)
+	Invalidate(ctx context.Context, ids []string)
 
 	// Save will add or overwrite the data in the cache at the given keys
-	Save(ctx context.Context, requestData map[string]json.RawMessage, impData map[string]json.RawMessage)
+	Save(ctx context.Context, data map[string]json.RawMessage)
 }
 
 // ComposedCache creates an interface to treat a slice of caches as a single cache
-type ComposedCache []Cache
+type ComposedCache []CacheJSON
 
 // Get will attempt to Get from the caches in the order in which they are in the slice,
 // stopping as soon as a value is found (or when all caches have been exhausted)
-func (c ComposedCache) Get(ctx context.Context, requestIDs []string, impIDs []string) (requestData map[string]json.RawMessage, impData map[string]json.RawMessage) {
-	requestData = make(map[string]json.RawMessage, len(requestIDs))
-	impData = make(map[string]json.RawMessage, len(impIDs))
+func (c ComposedCache) Get(ctx context.Context, ids []string) (data map[string]json.RawMessage) {
+	data = make(map[string]json.RawMessage, len(ids))
 
-	remainingReqIDs := requestIDs
-	remainingImpIDs := impIDs
+	remainingIDs := ids
 
 	for _, cache := range c {
-		cachedReqData, cachedImpData := cache.Get(ctx, remainingReqIDs, remainingImpIDs)
-
-		requestData, remainingReqIDs = updateFromCache(requestData, remainingReqIDs, cachedReqData)
-		impData, remainingImpIDs = updateFromCache(impData, remainingImpIDs, cachedImpData)
+		cachedData := cache.Get(ctx, remainingIDs)
+		data, remainingIDs = updateFromCache(data, remainingIDs, cachedData)
 
-		// return if all ids filled
-		if len(remainingReqIDs) == 0 && len(remainingImpIDs) == 0 {
-			return
+		// finish early if all ids filled
+		if len(remainingIDs) == 0 {
+			break
 		}
 	}
 
@@ -129,16 +129,16 @@ func updateFromCache(data map[string]json.RawMessage, ids []string, newData map[
 }
 
 // Invalidate will propagate invalidations to all underlying caches
-func (c ComposedCache) Invalidate(ctx context.Context, requestIDs []string, impIDs []string) {
+func (c ComposedCache) Invalidate(ctx context.Context, ids []string) {
 	for _, cache := range c {
-		cache.Invalidate(ctx, requestIDs, impIDs)
+		cache.Invalidate(ctx, ids)
 	}
 }
 
 // Save will propagate saves to all underlying caches
-func (c ComposedCache) Save(ctx context.Context, requestData map[string]json.RawMessage, impData map[string]json.RawMessage) {
+func (c ComposedCache) Save(ctx context.Context, data map[string]json.RawMessage) {
 	for _, cache := range c {
-		cache.Save(ctx, requestData, impData)
+		cache.Save(ctx, data)
 	}
 }
 
@@ -148,7 +148,7 @@ type fetcherWithCache struct {
 	metricsEngine pbsmetrics.MetricsEngine
 }
 
-// WithCache returns a Fetcher which uses the given Cache before delegating to the original.
+// WithCache returns a Fetcher which uses the given Caches before delegating to the original.
 // This can be called multiple times to compose Cache layers onto the backing Fetcher, though
 // it is usually more desirable to first compose caches with Compose, ensuring propagation of updates
 // and invalidations through all cache layers.
@@ -161,7 +161,9 @@ func WithCache(fetcher AllFetcher, cache Cache, metricsEngine pbsmetrics.Metrics
 }
 
 func (f *fetcherWithCache) FetchRequests(ctx context.Context, requestIDs []string, impIDs []string) (requestData map[string]json.RawMessage, impData map[string]json.RawMessage, errs []error) {
-	requestData, impData = f.cache.Get(ctx, requestIDs, impIDs)
+
+	requestData = f.cache.Requests.Get(ctx, requestIDs)
+	impData = f.cache.Imps.Get(ctx, impIDs)
 
 	// Fixes #311
 	leftoverImps := findLeftovers(impIDs, impData)
@@ -178,7 +180,8 @@ func (f *fetcherWithCache) FetchRequests(ctx context.Context, requestIDs []strin
 		fetcherReqData, fetcherImpData, fetcherErrs := f.fetcher.FetchRequests(ctx, leftoverReqs, leftoverImps)
 		errs = fetcherErrs
 
-		f.cache.Save(ctx, fetcherReqData, fetcherImpData)
+		f.cache.Requests.Save(ctx, fetcherReqData)
+		f.cache.Imps.Save(ctx, fetcherImpData)
 
 		requestData = mergeData(requestData, fetcherReqData)
 		impData = mergeData(impData, fetcherImpData)
diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go
index 89540ea2..c6ae984e 100755
--- a/usersync/usersyncers/syncer.go
+++ b/usersync/usersyncers/syncer.go
@@ -23,6 +23,7 @@ import (
 	"github.com/prebid/prebid-server/adapters/beachfront"
 	"github.com/prebid/prebid-server/adapters/beintoo"
 	"github.com/prebid/prebid-server/adapters/brightroll"
+	"github.com/prebid/prebid-server/adapters/colossus"
 	"github.com/prebid/prebid-server/adapters/consumable"
 	"github.com/prebid/prebid-server/adapters/conversant"
 	"github.com/prebid/prebid-server/adapters/cpmstar"
@@ -99,6 +100,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderBeachfront, beachfront.NewBeachfrontSyncer)
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderBeintoo, beintoo.NewBeintooSyncer)
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderBrightroll, brightroll.NewBrightrollSyncer)
+	insertIntoMap(cfg, syncers, openrtb_ext.BidderColossus, colossus.NewColossusSyncer)
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderConsumable, consumable.NewConsumableSyncer)
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderConversant, conversant.NewConversantSyncer)
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderCpmstar, cpmstar.NewCpmstarSyncer)


Test patch:
diff --git a/account/account_test.go b/account/account_test.go
new file mode 100644
index 00000000..0d192f18
--- /dev/null
+++ b/account/account_test.go
@@ -0,0 +1,94 @@
+package account
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"testing"
+
+	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/errortypes"
+	"github.com/prebid/prebid-server/pbsmetrics"
+	"github.com/prebid/prebid-server/stored_requests"
+	"github.com/stretchr/testify/assert"
+)
+
+var mockAccountData = map[string]json.RawMessage{
+	"valid_acct":    json.RawMessage(`{"disabled":false}`),
+	"disabled_acct": json.RawMessage(`{"disabled":true}`),
+}
+
+type mockAccountFetcher struct {
+}
+
+func (af mockAccountFetcher) FetchAccount(ctx context.Context, accountID string) (json.RawMessage, []error) {
+	if account, ok := mockAccountData[accountID]; ok {
+		return account, nil
+	}
+	return nil, []error{stored_requests.NotFoundError{accountID, "Account"}}
+}
+
+func TestGetAccount(t *testing.T) {
+	unknown := pbsmetrics.PublisherUnknown
+	testCases := []struct {
+		accountID string
+		// account_required
+		required bool
+		// account_defaults.disabled
+		disabled bool
+		// expected error, or nil if account should be found
+		err error
+	}{
+		// Blacklisted account is always rejected even in permissive setup
+		{accountID: "bad_acct", required: false, disabled: false, err: &errortypes.BlacklistedAcct{}},
+
+		// empty pubID
+		{accountID: unknown, required: false, disabled: false, err: nil},
+		{accountID: unknown, required: true, disabled: false, err: &errortypes.AcctRequired{}},
+		{accountID: unknown, required: false, disabled: true, err: &errortypes.BlacklistedAcct{}},
+		{accountID: unknown, required: true, disabled: true, err: &errortypes.AcctRequired{}},
+
+		// pubID given but is not a valid host account (does not exist)
+		{accountID: "doesnt_exist_acct", required: false, disabled: false, err: nil},
+		{accountID: "doesnt_exist_acct", required: true, disabled: false, err: nil},
+		{accountID: "doesnt_exist_acct", required: false, disabled: true, err: &errortypes.BlacklistedAcct{}},
+		{accountID: "doesnt_exist_acct", required: true, disabled: true, err: &errortypes.AcctRequired{}},
+
+		// pubID given and matches a valid host account with Disabled: false
+		{accountID: "valid_acct", required: false, disabled: false, err: nil},
+		{accountID: "valid_acct", required: true, disabled: false, err: nil},
+		{accountID: "valid_acct", required: false, disabled: true, err: nil},
+		{accountID: "valid_acct", required: true, disabled: true, err: nil},
+
+		// pubID given and matches a host account explicitly disabled (Disabled: true on account json)
+		{accountID: "disabled_acct", required: false, disabled: false, err: &errortypes.BlacklistedAcct{}},
+		{accountID: "disabled_acct", required: true, disabled: false, err: &errortypes.BlacklistedAcct{}},
+		{accountID: "disabled_acct", required: false, disabled: true, err: &errortypes.BlacklistedAcct{}},
+		{accountID: "disabled_acct", required: true, disabled: true, err: &errortypes.BlacklistedAcct{}},
+	}
+
+	for _, test := range testCases {
+		description := fmt.Sprintf(`ID=%s/required=%t/disabled=%t`, test.accountID, test.required, test.disabled)
+		t.Run(description, func(t *testing.T) {
+			cfg := &config.Configuration{
+				BlacklistedAcctMap: map[string]bool{"bad_acct": true},
+				AccountRequired:    test.required,
+				AccountDefaults:    config.Account{Disabled: test.disabled},
+			}
+			fetcher := &mockAccountFetcher{}
+			assert.NoError(t, cfg.MarshalAccountDefaults())
+
+			account, errors := GetAccount(context.Background(), cfg, fetcher, test.accountID)
+
+			if test.err == nil {
+				assert.Empty(t, errors)
+				assert.Equal(t, test.accountID, account.ID, "account.ID must match requested ID")
+				assert.Equal(t, false, account.Disabled, "returned account must not be disabled")
+			} else {
+				assert.NotEmpty(t, errors, "expected errors but got success")
+				assert.Nil(t, account, "return account must be nil on error")
+				assert.IsType(t, test.err, errors[0], "error is of unexpected type")
+			}
+		})
+	}
+}
diff --git a/adapters/colossus/colossus_test.go b/adapters/colossus/colossus_test.go
new file mode 100644
index 00000000..f4fd12f3
--- /dev/null
+++ b/adapters/colossus/colossus_test.go
@@ -0,0 +1,12 @@
+package colossus
+
+import (
+	"testing"
+
+	"github.com/prebid/prebid-server/adapters/adapterstest"
+)
+
+func TestJsonSamples(t *testing.T) {
+	colossusAdapter := NewColossusBidder("http://example.com/?c=o&m=rtb")
+	adapterstest.RunJSONBidderTest(t, "colossustest", colossusAdapter)
+}
diff --git a/adapters/colossus/colossustest/exemplary/simple-banner.json b/adapters/colossus/colossustest/exemplary/simple-banner.json
new file mode 100644
index 00000000..1adc7010
--- /dev/null
+++ b/adapters/colossus/colossustest/exemplary/simple-banner.json
@@ -0,0 +1,132 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+        {
+            "id": "test-imp-id",
+            "banner": {
+                "format": [
+                    {
+                        "w": 300,
+                        "h": 250
+                    },
+                    {
+                        "w": 300,
+                        "h": 600
+                    }
+                ]
+            },
+            "tagid": "61317",
+            "ext": {
+                "bidder": {
+                    "TagID": "61317"
+                }
+            }
+        }
+    ],
+    "app": {
+        "id": "1",
+        "bundle": "com.wls.testwlsapplication"
+    },
+    "device": {
+        "ip": "123.123.123.123",
+        "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx"
+    }
+},
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "http://example.com/?c=o&m=rtb",
+        "body": {
+          "id": "test-request-id",
+          "imp": [
+              {
+                  "id": "test-imp-id",
+                  "banner": {
+                      "format": [
+                          {
+                              "w": 300,
+                              "h": 250
+                          },
+                          {
+                              "w": 300,
+                              "h": 600
+                          }
+                      ]
+                  },
+                  "tagid": "61317",
+                  "ext": {
+                      "bidder": {
+                          "TagID": "61317"
+                      }
+                  }
+              }
+          ],
+          "app": {
+              "id": "1",
+              "bundle": "com.wls.testwlsapplication"
+          },
+          "device": {
+              "ip": "123.123.123.123",
+              "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx"
+          }
+      }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "seatbid": [
+              {
+                  "bid": [
+                      {
+                          "id": "test_bid_id",
+                          "impid": "test-imp-id",
+                          "price": 0.27543,
+                          "adm": "<iframe id=\"adm-banner-16\" width=\"300\" height=\"250\" frameborder=\"0\" marginheight=\"0\" marginwidth=\"0\" style=\"{overflow:hidden}\" src=\"https://colossusssp.com/?c=o&m=adm&k=882b2510ed6d6c94fa69c99aa522a708\"></iframe>",
+                          "cid": "test_cid",
+                          "crid": "test_crid",
+                          "dealid": "test_dealid",
+                          "w": 300,
+                          "h": 250,
+                          "ext": {
+                              "prebid": {
+                                  "type": "banner"
+                              }
+                          }
+                      }
+                  ]
+              }
+          ]
+        }
+      }
+    }
+  ],
+
+  "expectedBidResponses": [
+    {
+      "bids":[
+        {
+          "bid":  {
+            "id": "test_bid_id",
+            "impid": "test-imp-id",
+            "price": 0.27543,
+            "adm": "<iframe id=\"adm-banner-16\" width=\"300\" height=\"250\" frameborder=\"0\" marginheight=\"0\" marginwidth=\"0\" style=\"{overflow:hidden}\" src=\"https://colossusssp.com/?c=o&m=adm&k=882b2510ed6d6c94fa69c99aa522a708\"></iframe>",
+            "cid": "test_cid",
+            "crid": "test_crid",
+            "dealid": "test_dealid",
+            "w": 300,
+            "h": 250,
+            "ext": {
+              "prebid": {
+                "type": "banner"
+              }
+            }
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/colossus/colossustest/exemplary/simple-video.json b/adapters/colossus/colossustest/exemplary/simple-video.json
new file mode 100644
index 00000000..78516fce
--- /dev/null
+++ b/adapters/colossus/colossustest/exemplary/simple-video.json
@@ -0,0 +1,119 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "device": {
+      "ip": "123.123.123.123",
+      "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx"
+    },
+    "app": {
+      "id": "1",
+      "bundle": "com.wls.testwlsapplication"
+    },
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "video": {
+          "mimes": ["video/mp4"],
+          "protocols": [2, 5],
+          "w": 1024,
+          "h": 576
+        },
+        "ext": {
+          "bidder": {
+            "TagID": "61318"
+          }
+        }
+      }
+    ]
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "http://example.com/?c=o&m=rtb",
+        "body": {
+          "id": "test-request-id",
+          "device": {
+            "ip": "123.123.123.123",
+            "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx"
+          },
+          "app": {
+            "id": "1",
+            "bundle": "com.wls.testwlsapplication"
+          },
+          "imp": [
+            {
+              "id": "test-imp-id",
+              "video": {
+                "mimes": ["video/mp4"],
+                "protocols": [2, 5],
+                "w": 1024,
+                "h": 576
+              },
+              "tagid": "61318",
+              "ext": {
+                "bidder": {
+                  "TagID": "61318"
+                }
+              }
+            }
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "seatbid": [
+              {
+                  "bid": [
+                      {
+                          "id": "test_bid_id",
+                          "impid": "test-imp-id",
+                          "price": 0.27543,
+                          "adm": "<VAST version=\"3.0\"><Ad id=\"20001\" sequence=\"1\"><InLine><AdSystem version=\"4.0\"><![CDATA[iabtechlab]]></AdSystem><AdTitle><![CDATA[Inline Simple Ad]]></AdTitle><Impression><![CDATA[]]></Impression><Creatives><Creative id=\"5480\" sequence=\"1\"><Linear><Duration>00:01:00</Duration><MediaFiles><MediaFile id=\"5246\" delivery=\"progressive\" type=\"video/mp4\" bitrate=\"600\" width=\"640\" height=\"360\" minBitrate=\"500\" maxBitrate=\"700\" scalable=\"1\" maintainAspectRatio=\"1\" codec=\"0\"><![CDATA[https://s0.2mdn.net/4253510/google_ddm_animation_480P.mp4]]></MediaFile></MediaFiles><VideoClicks><ClickThrough id=\"blog\"><![CDATA[https://example.com]]></ClickThrough></VideoClicks></Linear></Creative></Creatives></InLine></Ad></VAST>",
+                          "cid": "test_cid",
+                          "crid": "test_crid",
+                          "dealid": "test_dealid",
+                          "ext": {
+                              "prebid": {
+                                  "type": "video"
+                              }
+                          }
+                      }
+                  ],
+                  "seat": "colossus"
+              }
+          ],
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+
+
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "test_bid_id",
+                          "impid": "test-imp-id",
+                          "price": 0.27543,
+                          "adm": "<VAST version=\"3.0\"><Ad id=\"20001\" sequence=\"1\"><InLine><AdSystem version=\"4.0\"><![CDATA[iabtechlab]]></AdSystem><AdTitle><![CDATA[Inline Simple Ad]]></AdTitle><Impression><![CDATA[]]></Impression><Creatives><Creative id=\"5480\" sequence=\"1\"><Linear><Duration>00:01:00</Duration><MediaFiles><MediaFile id=\"5246\" delivery=\"progressive\" type=\"video/mp4\" bitrate=\"600\" width=\"640\" height=\"360\" minBitrate=\"500\" maxBitrate=\"700\" scalable=\"1\" maintainAspectRatio=\"1\" codec=\"0\"><![CDATA[https://s0.2mdn.net/4253510/google_ddm_animation_480P.mp4]]></MediaFile></MediaFiles><VideoClicks><ClickThrough id=\"blog\"><![CDATA[https://example.com]]></ClickThrough></VideoClicks></Linear></Creative></Creatives></InLine></Ad></VAST>",
+                          "cid": "test_cid",
+                          "crid": "test_crid",
+                          "dealid": "test_dealid",
+                          "ext": {
+                              "prebid": {
+                                  "type": "video"
+                              }
+                          }
+          },
+          "type": "video"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/colossus/colossustest/exemplary/simple-web-banner.json b/adapters/colossus/colossustest/exemplary/simple-web-banner.json
new file mode 100644
index 00000000..37baf3d9
--- /dev/null
+++ b/adapters/colossus/colossustest/exemplary/simple-web-banner.json
@@ -0,0 +1,133 @@
+{
+    "mockBidRequest": {
+      "id": "test-request-id",
+      "imp": [
+          {
+              "id": "test-imp-id",
+              "banner": {
+                  "format": [
+                      {
+                          "w": 300,
+                          "h": 250
+                      },
+                      {
+                          "w": 300,
+                          "h": 600
+                      }
+                  ]
+              },
+              "tagid": "1",
+              "ext": {
+                  "bidder": {
+                      "TagID": "1"
+                  }
+              }
+          }
+      ],
+      "site": {
+          "id": "1",
+          "domain": "test.com"
+      },
+      "device": {
+          "ip": "123.123.123.123"
+      }
+  },
+  
+    "httpCalls": [
+      {
+        "expectedRequest": {
+          "uri": "http://example.com/?c=o&m=rtb",
+          "body": {
+            "id": "test-request-id",
+            "imp": [
+                {
+                    "id": "test-imp-id",
+                    "banner": {
+                        "format": [
+                            {
+                                "w": 300,
+                                "h": 250
+                            },
+                            {
+                                "w": 300,
+                                "h": 600
+                            }
+                        ]
+                    },
+                    "tagid": "1",
+                    "ext": {
+                        "bidder": {
+                            "TagID": "1"
+                        }
+                    }
+                }
+            ],
+            "site": {
+                "id": "1",
+                "domain": "test.com"
+            },
+            "device": {
+                "ip": "123.123.123.123"
+            }
+        }
+        },
+        "mockResponse": {
+          "status": 200,
+          "body": {
+            "id": "test-request-id",
+            "seatbid": [
+                {
+                    "bid": [
+                        {
+                            "id": "test_bid_id",
+                            "impid": "test-imp-id",
+                            "price": 0.27543,
+                            "adm": "<iframe id=\"adm-banner-1\" width=\"468\" height=\"60\" frameborder=\"0\" marginheight=\"0\" marginwidth=\"0\" style=\"{overflow:hidden}\" src=\"https://colossusssp.com/?c=o&m=adm&k=bc2d316f39931a07d9a8dd249bf85fc0\"></iframe>",
+                            "cid": "test_cid",
+                            "crid": "test_crid",
+                            "dealid": "test_dealid",
+                            "w": 468,
+                            "h": 60,
+                            "ext": {
+                                "prebid": {
+                                    "type": "banner"
+                                }
+                            }
+                        }
+                    ],
+                    "seat": "colossus"
+                }
+            ],
+            "cur": "USD"
+          }
+        }
+      }
+    ],
+  
+    "expectedBidResponses": [
+      {
+        "bids":[
+          {
+            "bid":  {
+                "id": "test_bid_id",
+                "impid": "test-imp-id",
+                "price": 0.27543,
+                "adm": "<iframe id=\"adm-banner-1\" width=\"468\" height=\"60\" frameborder=\"0\" marginheight=\"0\" marginwidth=\"0\" style=\"{overflow:hidden}\" src=\"https://colossusssp.com/?c=o&m=adm&k=bc2d316f39931a07d9a8dd249bf85fc0\"></iframe>",
+                "cid": "test_cid",
+                "crid": "test_crid",
+                "dealid": "test_dealid",
+                "w": 468,
+                "h": 60,
+                "ext": {
+                    "prebid": {
+                        "type": "banner"
+                    }
+                }
+            },
+            "type": "banner"
+          }
+        ]
+      }
+    ]
+  }
+  
\ No newline at end of file
diff --git a/adapters/colossus/colossustest/params/banner.json b/adapters/colossus/colossustest/params/banner.json
new file mode 100644
index 00000000..7c2643d4
--- /dev/null
+++ b/adapters/colossus/colossustest/params/banner.json
@@ -0,0 +1,3 @@
+{
+    "TagID": "61317"
+}
diff --git a/adapters/colossus/colossustest/params/race/banner.json b/adapters/colossus/colossustest/params/race/banner.json
new file mode 100644
index 00000000..7c2643d4
--- /dev/null
+++ b/adapters/colossus/colossustest/params/race/banner.json
@@ -0,0 +1,3 @@
+{
+    "TagID": "61317"
+}
diff --git a/adapters/colossus/colossustest/params/race/video.json b/adapters/colossus/colossustest/params/race/video.json
new file mode 100644
index 00000000..56f865c7
--- /dev/null
+++ b/adapters/colossus/colossustest/params/race/video.json
@@ -0,0 +1,3 @@
+{
+    "TagID": "61318"
+}
diff --git a/adapters/colossus/colossustest/params/video.json b/adapters/colossus/colossustest/params/video.json
new file mode 100644
index 00000000..56f865c7
--- /dev/null
+++ b/adapters/colossus/colossustest/params/video.json
@@ -0,0 +1,3 @@
+{
+    "TagID": "61318"
+}
diff --git a/adapters/colossus/colossustest/supplemental/bad-imp-ext.json b/adapters/colossus/colossustest/supplemental/bad-imp-ext.json
new file mode 100644
index 00000000..13656337
--- /dev/null
+++ b/adapters/colossus/colossustest/supplemental/bad-imp-ext.json
@@ -0,0 +1,42 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+        {
+            "id": "test-imp-id",
+            "banner": {
+                "format": [
+                    {
+                        "w": 300,
+                        "h": 250
+                    },
+                    {
+                        "w": 300,
+                        "h": 600
+                    }
+                ]
+            },
+            "tagid": "61317",
+            "ext": {
+                "colossus": {
+                    "TagID": "61317"
+                }
+            }
+        }
+    ],
+    "app": {
+        "id": "1",
+        "bundle": "com.wls.testwlsapplication"
+    },
+    "device": {
+        "ip": "123.123.123.123",
+        "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx"
+    }
+},
+"expectedMakeRequestsErrors": [
+  {
+    "value": "Key path not found",
+    "comparison": "literal"
+  }
+]
+}
diff --git a/adapters/colossus/colossustest/supplemental/bad_response.json b/adapters/colossus/colossustest/supplemental/bad_response.json
new file mode 100644
index 00000000..c69b00c8
--- /dev/null
+++ b/adapters/colossus/colossustest/supplemental/bad_response.json
@@ -0,0 +1,85 @@
+{
+	"mockBidRequest": {
+        "id": "test-request-id",
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 300,
+                            "h": 250
+                        },
+                        {
+                            "w": 300,
+                            "h": 600
+                        }
+                    ]
+                },
+                "tagid": "17",
+                "ext": {
+                    "bidder": {
+                        "TagID": "17"
+                    }
+                }
+            }
+        ],
+        "app": {
+            "id": "1",
+            "bundle": "com.wls.testwlsapplication"
+        },
+        "device": {
+            "ip": "123.123.123.123",
+            "ifa": "sdjfksdf-dfsds-dsdg-dsgg"
+        }
+    },
+	"httpCalls": [{
+		"expectedRequest": {
+			"uri": "http://example.com/?c=o&m=rtb",
+			"body": {
+                "id": "test-request-id",
+                "imp": [
+                    {
+                        "id": "test-imp-id",
+                        "banner": {
+                            "format": [
+                                {
+                                    "w": 300,
+                                    "h": 250
+                                },
+                                {
+                                    "w": 300,
+                                    "h": 600
+                                }
+                            ]
+                        },
+                        "tagid": "17",
+                        "ext": {
+                            "bidder": {
+                                "TagID": "17"
+                            }
+                        }
+                    }
+                ],
+                "app": {
+                    "id": "1",
+                    "bundle": "com.wls.testwlsapplication"
+                },
+                "device": {
+                    "ip": "123.123.123.123",
+                    "ifa": "sdjfksdf-dfsds-dsdg-dsgg"
+                }
+            }
+		},
+		"mockResponse": {
+			"status": 200,
+			"body": ""
+        }
+    }],
+    "expectedMakeBidsErrors": [
+        {
+          "value": "json: cannot unmarshal string into Go value of type openrtb.BidResponse",
+          "comparison": "literal"
+        }
+    ]
+}
diff --git a/adapters/colossus/colossustest/supplemental/bad_status_code.json b/adapters/colossus/colossustest/supplemental/bad_status_code.json
new file mode 100644
index 00000000..f5b6a574
--- /dev/null
+++ b/adapters/colossus/colossustest/supplemental/bad_status_code.json
@@ -0,0 +1,79 @@
+{
+	"mockBidRequest": {
+        "id": "test-request-id",
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 300,
+                            "h": 250
+                        },
+                        {
+                            "w": 300,
+                            "h": 600
+                        }
+                    ]
+                },
+                "tagid": "100000000",
+                "ext": {
+                    "bidder": {
+                        "TagID": "100000000"
+                    }
+                }
+            }
+        ],
+        "app": {
+            "id": "1",
+            "bundle": "com.wls.testwlsapplication"
+        },
+        "device": {}
+    },
+	"httpCalls": [{
+		"expectedRequest": {
+			"uri": "http://example.com/?c=o&m=rtb",
+			"body": {
+                "id": "test-request-id",
+                "imp": [
+                    {
+                        "id": "test-imp-id",
+                        "banner": {
+                            "format": [
+                                {
+                                    "w": 300,
+                                    "h": 250
+                                },
+                                {
+                                    "w": 300,
+                                    "h": 600
+                                }
+                            ]
+                        },
+                        "tagid": "100000000",
+                        "ext": {
+                            "bidder": {
+                                "TagID": "100000000"
+                            }
+                        }
+                    }
+                ],
+                "app": {
+                    "id": "1",
+                    "bundle": "com.wls.testwlsapplication"
+                },
+                "device": {}
+            }
+		},
+		"mockResponse": {
+			"status": 400,
+			"body": {}
+		}
+    }],
+    "expectedMakeBidsErrors": [
+        {
+          "value": "Unexpected status code: 400. Run with request.debug = 1 for more info",
+          "comparison": "literal"
+        }
+    ]
+}
diff --git a/adapters/colossus/colossustest/supplemental/empty_imp_ext.json b/adapters/colossus/colossustest/supplemental/empty_imp_ext.json
new file mode 100644
index 00000000..00e1cf60
--- /dev/null
+++ b/adapters/colossus/colossustest/supplemental/empty_imp_ext.json
@@ -0,0 +1,38 @@
+{
+    "mockBidRequest": {
+      "id": "test-request-id",
+      "imp": [
+          {
+              "id": "test-imp-id",
+              "banner": {
+                  "format": [
+                      {
+                          "w": 300,
+                          "h": 250
+                      },
+                      {
+                          "w": 300,
+                          "h": 600
+                      }
+                  ]
+              },
+              "tagid": "61317",
+              "ext": {}
+          }
+      ],
+      "app": {
+          "id": "1",
+          "bundle": "com.wls.testwlsapplication"
+      },
+      "device": {
+          "ip": "123.123.123.123",
+          "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx"
+      }
+  },
+  "expectedMakeRequestsErrors": [
+    {
+      "value": "Key path not found",
+      "comparison": "literal"
+    }
+  ]
+}  
\ No newline at end of file
diff --git a/adapters/colossus/colossustest/supplemental/imp_ext_empty_object.json b/adapters/colossus/colossustest/supplemental/imp_ext_empty_object.json
new file mode 100644
index 00000000..e9c1f257
--- /dev/null
+++ b/adapters/colossus/colossustest/supplemental/imp_ext_empty_object.json
@@ -0,0 +1,39 @@
+{
+    "mockBidRequest": {
+      "id": "test-request-id",
+      "imp": [
+          {
+              "id": "test-imp-id",
+              "banner": {
+                  "format": [
+                      {
+                          "w": 300,
+                          "h": 250
+                      },
+                      {
+                          "w": 300,
+                          "h": 600
+                      }
+                  ]
+              },
+              "tagid": "61317",
+              "ext": {}
+          }
+      ],
+      "app": {
+          "id": "1",
+          "bundle": "com.wls.testwlsapplication"
+      },
+      "device": {
+          "ip": "123.123.123.123",
+          "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx"
+      }
+  },
+  "expectedMakeRequestsErrors": [
+    {
+      "value": "Key path not found",
+      "comparison": "literal"
+    }
+  ]
+  }
+  
\ No newline at end of file
diff --git a/adapters/colossus/colossustest/supplemental/imp_ext_string.json b/adapters/colossus/colossustest/supplemental/imp_ext_string.json
new file mode 100644
index 00000000..362a8fa4
--- /dev/null
+++ b/adapters/colossus/colossustest/supplemental/imp_ext_string.json
@@ -0,0 +1,39 @@
+{
+    "mockBidRequest": {
+      "id": "test-request-id",
+      "imp": [
+          {
+              "id": "test-imp-id",
+              "banner": {
+                  "format": [
+                      {
+                          "w": 300,
+                          "h": 250
+                      },
+                      {
+                          "w": 300,
+                          "h": 600
+                      }
+                  ]
+              },
+              "tagid": "61317",
+              "ext": ""
+          }
+      ],
+      "app": {
+          "id": "1",
+          "bundle": "com.wls.testwlsapplication"
+      },
+      "device": {
+          "ip": "123.123.123.123",
+          "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx"
+      }
+  },
+  "expectedMakeRequestsErrors": [
+    {
+      "value": "Key path not found",
+      "comparison": "literal"
+    }
+  ]
+  }
+  
\ No newline at end of file
diff --git a/adapters/colossus/colossustest/supplemental/status-204.json b/adapters/colossus/colossustest/supplemental/status-204.json
new file mode 100644
index 00000000..73f8bc71
--- /dev/null
+++ b/adapters/colossus/colossustest/supplemental/status-204.json
@@ -0,0 +1,79 @@
+{
+	"mockBidRequest": {
+        "id": "test-request-id",
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 300,
+                            "h": 250
+                        },
+                        {
+                            "w": 300,
+                            "h": 600
+                        }
+                    ]
+                },
+                "tagid": "17",
+                "ext": {
+                    "bidder": {
+                        "TagID": "17"
+                    }
+                }
+            }
+        ],
+        "app": {
+            "id": "1",
+            "bundle": "com.wls.testwlsapplication"
+        },
+        "device": {
+            "ip": "123.123.123.123",
+            "ifa": "sdjfksdf-dfsds-dsdg-dsgg"
+        }
+    },
+    "httpCalls": [{
+        "expectedRequest": {
+            "uri": "http://example.com/?c=o&m=rtb",
+			"body": {
+                "id": "test-request-id",
+                "imp": [
+                    {
+                        "id": "test-imp-id",
+                        "banner": {
+                            "format": [
+                                {
+                                    "w": 300,
+                                    "h": 250
+                                },
+                                {
+                                    "w": 300,
+                                    "h": 600
+                                }
+                            ]
+                        },
+                        "tagid": "17",
+                        "ext": {
+                            "bidder": {
+                                "TagID": "17"
+                            }
+                        }
+                    }
+                ],
+                "app": {
+                    "id": "1",
+                    "bundle": "com.wls.testwlsapplication"
+                },
+                "device": {
+                    "ip": "123.123.123.123",
+                    "ifa": "sdjfksdf-dfsds-dsdg-dsgg"
+                }
+            }
+		},
+		"mockResponse": {
+			"status": 204,
+			"body": {}
+		}
+	}]
+}
diff --git a/adapters/colossus/colossustest/supplemental/status-404.json b/adapters/colossus/colossustest/supplemental/status-404.json
new file mode 100644
index 00000000..676eb8bb
--- /dev/null
+++ b/adapters/colossus/colossustest/supplemental/status-404.json
@@ -0,0 +1,85 @@
+{
+	"mockBidRequest": {
+        "id": "test-request-id",
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 300,
+                            "h": 250
+                        },
+                        {
+                            "w": 300,
+                            "h": 600
+                        }
+                    ]
+                },
+                "tagid": "100000000",
+                "ext": {
+                    "bidder": {
+                        "TagID": "100000000"
+                    }
+                }
+            }
+        ],
+        "app": {
+            "id": "1",
+            "bundle": "com.wls.testwlsapplication"
+        },
+        "device": {
+            "ip": "123.123.123.123",
+            "ifa": "sdjfksdf-dfsds-dsdg-dsgg"
+        }
+    },
+    "httpCalls": [{
+        "expectedRequest": {
+            "uri": "http://example.com/?c=o&m=rtb",
+            "body": {
+                "id": "test-request-id",
+                "imp": [
+                    {
+                        "id": "test-imp-id",
+                        "banner": {
+                            "format": [
+                                {
+                                    "w": 300,
+                                    "h": 250
+                                },
+                                {
+                                    "w": 300,
+                                    "h": 600
+                                }
+                            ]
+                        },
+                        "tagid": "100000000",
+                        "ext": {
+                            "bidder": {
+                                "TagID": "100000000"
+                            }
+                        }
+                    }
+                ],
+                "app": {
+                    "id": "1",
+                    "bundle": "com.wls.testwlsapplication"
+                },
+                "device": {
+                    "ip": "123.123.123.123",
+                    "ifa": "sdjfksdf-dfsds-dsdg-dsgg"
+                }
+            }
+		},
+		"mockResponse": {
+			"status": 404,
+			"body": {}
+		}
+    }],
+    "expectedMakeBidsErrors": [
+        {
+          "value": "Unexpected status code: 404. Run with request.debug = 1 for more info",
+          "comparison": "literal"
+        }
+    ]
+}
diff --git a/adapters/colossus/colossustest/supplemental/string_imp_ext.json b/adapters/colossus/colossustest/supplemental/string_imp_ext.json
new file mode 100644
index 00000000..362a8fa4
--- /dev/null
+++ b/adapters/colossus/colossustest/supplemental/string_imp_ext.json
@@ -0,0 +1,39 @@
+{
+    "mockBidRequest": {
+      "id": "test-request-id",
+      "imp": [
+          {
+              "id": "test-imp-id",
+              "banner": {
+                  "format": [
+                      {
+                          "w": 300,
+                          "h": 250
+                      },
+                      {
+                          "w": 300,
+                          "h": 600
+                      }
+                  ]
+              },
+              "tagid": "61317",
+              "ext": ""
+          }
+      ],
+      "app": {
+          "id": "1",
+          "bundle": "com.wls.testwlsapplication"
+      },
+      "device": {
+          "ip": "123.123.123.123",
+          "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx"
+      }
+  },
+  "expectedMakeRequestsErrors": [
+    {
+      "value": "Key path not found",
+      "comparison": "literal"
+    }
+  ]
+  }
+  
\ No newline at end of file
diff --git a/adapters/colossus/params_test.go b/adapters/colossus/params_test.go
new file mode 100644
index 00000000..2883de2f
--- /dev/null
+++ b/adapters/colossus/params_test.go
@@ -0,0 +1,46 @@
+package colossus
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+// TestValidParams makes sure that the colossus schema accepts all imp.ext fields which we intend to support.
+func TestValidParams(t *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+	if err != nil {
+		t.Fatalf("Failed to fetch the json-schemas. %v", err)
+	}
+
+	for _, validParam := range validParams {
+		if err := validator.Validate(openrtb_ext.BidderColossus, json.RawMessage(validParam)); err != nil {
+			t.Errorf("Schema rejected colossus params: %s", validParam)
+		}
+	}
+}
+
+// TestInvalidParams makes sure that the colossus schema rejects all the imp.ext fields we don't support.
+func TestInvalidParams(t *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+	if err != nil {
+		t.Fatalf("Failed to fetch the json-schemas. %v", err)
+	}
+
+	for _, invalidParam := range invalidParams {
+		if err := validator.Validate(openrtb_ext.BidderColossus, json.RawMessage(invalidParam)); err == nil {
+			t.Errorf("Schema allowed unexpected params: %s", invalidParam)
+		}
+	}
+}
+
+var validParams = []string{
+	`{"TagID": "61317"}`,
+}
+
+var invalidParams = []string{
+	`{"id": "123"}`,
+	`{"tagid": "123"}`,
+	`{"TagID": 16}`,
+}
diff --git a/adapters/colossus/usersync_test.go b/adapters/colossus/usersync_test.go
new file mode 100644
index 00000000..79d5483d
--- /dev/null
+++ b/adapters/colossus/usersync_test.go
@@ -0,0 +1,35 @@
+package colossus
+
+import (
+	"testing"
+	"text/template"
+
+	"github.com/prebid/prebid-server/privacy"
+	"github.com/prebid/prebid-server/privacy/ccpa"
+	"github.com/prebid/prebid-server/privacy/gdpr"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestColossusSyncer(t *testing.T) {
+	syncURL := "https://sync.colossusssp.com/pbs.gif?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir=http%3A%2F%2Flocalhost%3A8000%2Fsetuid%3Fbidder%3Dcolossus%26uid%3D%5BUID%5D"
+	syncURLTemplate := template.Must(
+		template.New("sync-template").Parse(syncURL),
+	)
+
+	syncer := NewColossusSyncer(syncURLTemplate)
+	syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{
+		GDPR: gdpr.Policy{
+			Signal:  "0",
+			Consent: "A",
+		},
+		CCPA: ccpa.Policy{
+			Value: "1-YY",
+		},
+	})
+
+	assert.NoError(t, err)
+	assert.Equal(t, "https://sync.colossusssp.com/pbs.gif?gdpr=0&gdpr_consent=A&us_privacy=1-YY&redir=http%3A%2F%2Flocalhost%3A8000%2Fsetuid%3Fbidder%3Dcolossus%26uid%3D%5BUID%5D", syncInfo.URL)
+	assert.Equal(t, "redirect", syncInfo.Type)
+	assert.EqualValues(t, 0, syncer.GDPRVendorID())
+	assert.Equal(t, false, syncInfo.SupportCORS)
+}
diff --git a/adapters/inmobi/inmobi_test.go b/adapters/inmobi/inmobi_test.go
new file mode 100644
index 00000000..6aa58d97
--- /dev/null
+++ b/adapters/inmobi/inmobi_test.go
@@ -0,0 +1,10 @@
+package inmobi
+
+import (
+	"github.com/prebid/prebid-server/adapters/adapterstest"
+	"testing"
+)
+
+func TestJsonSamples(t *testing.T) {
+	adapterstest.RunJSONBidderTest(t, "inmobitest", NewInMobiAdapter("https://api.w.inmobi.com/showad/openrtb/bidder/prebid"))
+}
diff --git a/adapters/inmobi/inmobitest/exemplary/simple-banner.json b/adapters/inmobi/inmobitest/exemplary/simple-banner.json
new file mode 100644
index 00000000..4345ef8f
--- /dev/null
+++ b/adapters/inmobi/inmobitest/exemplary/simple-banner.json
@@ -0,0 +1,107 @@
+{
+  "mockBidRequest": {
+    "app": {
+      "bundle": "com.example.app"
+    },
+    "id": "req-id",
+    "device": {
+      "ifa": "9d8fe0a9-c0dd-4482-b16b-5709b00c608d",
+      "ip": "1.1.1.1",
+      "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36"
+    },
+    "imp": [
+      {
+        "ext": {
+          "bidder": {
+            "plc": "1596825400965"
+          }
+        },
+        "banner": {
+          "w": 320,
+          "h": 50
+        },
+        "id": "imp-id"
+      }
+    ]
+  },
+  "httpCalls": [{
+    "expectedRequest": {
+      "uri": "https://api.w.inmobi.com/showad/openrtb/bidder/prebid",
+      "body": {
+        "app": {
+          "bundle": "com.example.app"
+        },
+        "id": "req-id",
+        "device": {
+          "ifa": "9d8fe0a9-c0dd-4482-b16b-5709b00c608d",
+          "ip": "1.1.1.1",
+          "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36"
+        },
+        "imp": [
+          {
+            "ext": {
+              "bidder": {
+                "plc": "1596825400965"
+              }
+            },
+            "banner": {
+              "w": 320,
+              "h": 50
+            },
+            "id": "imp-id"
+          }
+        ]
+      }
+    },
+    "mockResponse": {
+      "status": 200,
+      "body": {
+        "id": "req-id",
+        "seatbid": [
+          {
+            "bid": [
+              {
+                "ext": {
+                  "prebid": {
+                    "meta": {
+                      "networkName": "inmobi"
+                    }
+                  }
+                },
+                "nurl": "https://some.event.url/params",
+                "crid": "123456789",
+                "adomain": [],
+                "price": 2.0,
+                "id": "1234",
+                "adm": "bannerhtml",
+                "impid": "imp-id"
+              }
+            ]
+          }
+        ]
+      }
+    }
+  }],
+
+  "expectedBidResponses": [{
+    "currency": "USD",
+    "bids": [{
+      "bid": {
+        "id": "1234",
+        "impid": "imp-id",
+        "price": 2.0,
+        "adm": "bannerhtml",
+        "crid": "123456789",
+        "nurl": "https://some.event.url/params",
+        "ext": {
+          "prebid": {
+            "meta": {
+              "networkName": "inmobi"
+            }
+          }
+        }
+      },
+      "type": "banner"
+    }]
+  }]
+}
diff --git a/adapters/inmobi/inmobitest/exemplary/simple-video.json b/adapters/inmobi/inmobitest/exemplary/simple-video.json
new file mode 100644
index 00000000..20b3c0cc
--- /dev/null
+++ b/adapters/inmobi/inmobitest/exemplary/simple-video.json
@@ -0,0 +1,109 @@
+{
+  "mockBidRequest": {
+    "app": {
+      "bundle": "com.example.app"
+    },
+    "id": "req-id",
+    "device": {
+      "ifa": "9d8fe0a9-c0dd-4482-b16b-5709b00c608d",
+      "ip": "1.1.1.1",
+      "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36"
+    },
+    "imp": [
+      {
+        "ext": {
+          "bidder": {
+            "plc": "1598991608990"
+          }
+        },
+        "video": {
+          "w": 640,
+          "h": 360,
+          "mimes": ["video/mp4"]
+        },
+        "id": "imp-id"
+      }
+    ]
+  },
+  "httpCalls": [{
+    "expectedRequest": {
+      "uri": "https://api.w.inmobi.com/showad/openrtb/bidder/prebid",
+      "body": {
+        "app": {
+          "bundle": "com.example.app"
+        },
+        "id": "req-id",
+        "device": {
+          "ifa": "9d8fe0a9-c0dd-4482-b16b-5709b00c608d",
+          "ip": "1.1.1.1",
+          "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36"
+        },
+        "imp": [
+          {
+            "ext": {
+              "bidder": {
+                "plc": "1598991608990"
+              }
+            },
+            "video": {
+              "w": 640,
+              "h": 360,
+              "mimes": ["video/mp4"]
+            },
+            "id": "imp-id"
+          }
+        ]
+      }
+    },
+    "mockResponse": {
+      "status": 200,
+      "body": {
+        "id": "req-id",
+        "seatbid": [
+          {
+            "bid": [
+              {
+                "ext": {
+                  "prebid": {
+                    "meta": {
+                      "networkName": "inmobi"
+                    }
+                  }
+                },
+                "nurl": "https://some.event.url/params",
+                "crid": "123456789",
+                "adomain": [],
+                "price": 2.0,
+                "id": "1234",
+                "adm": "<?xml version=\"1.0\" encoding=\"UTF-8\"?> <VAST version=\"3.0\"></VAST>",
+                "impid": "imp-id"
+              }
+            ]
+          }
+        ]
+      }
+    }
+  }],
+
+  "expectedBidResponses": [{
+    "currency": "USD",
+    "bids": [{
+      "bid": {
+        "id": "1234",
+        "impid": "imp-id",
+        "price": 2.0,
+        "adm": "<?xml version=\"1.0\" encoding=\"UTF-8\"?> <VAST version=\"3.0\"></VAST>",
+        "crid": "123456789",
+        "nurl": "https://some.event.url/params",
+        "ext": {
+          "prebid": {
+            "meta": {
+              "networkName": "inmobi"
+            }
+          }
+        }
+      },
+      "type": "video"
+    }]
+  }]
+}
diff --git a/adapters/inmobi/inmobitest/params/race/banner.json b/adapters/inmobi/inmobitest/params/race/banner.json
new file mode 100644
index 00000000..7791393f
--- /dev/null
+++ b/adapters/inmobi/inmobitest/params/race/banner.json
@@ -0,0 +1,3 @@
+{
+  "plc": "1596825400965"
+}
diff --git a/adapters/inmobi/inmobitest/params/race/video.json b/adapters/inmobi/inmobitest/params/race/video.json
new file mode 100644
index 00000000..74a44b6e
--- /dev/null
+++ b/adapters/inmobi/inmobitest/params/race/video.json
@@ -0,0 +1,3 @@
+{
+  "plc": "1598991608990"
+}
diff --git a/adapters/inmobi/inmobitest/supplemental/204.json b/adapters/inmobi/inmobitest/supplemental/204.json
new file mode 100644
index 00000000..c8117636
--- /dev/null
+++ b/adapters/inmobi/inmobitest/supplemental/204.json
@@ -0,0 +1,61 @@
+{
+  "mockBidRequest": {
+    "app": {
+      "bundle": "com.example.app"
+    },
+    "id": "req-id",
+    "device": {
+      "ifa": "9d8fe0a9-c0dd-4482-b16b-5709b00c608d",
+      "ip": "1.1.1.1",
+      "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36"
+    },
+    "imp": [
+      {
+        "ext": {
+          "bidder": {
+            "plc": "1596825400965"
+          }
+        },
+        "banner": {
+          "w": 320,
+          "h": 50
+        },
+        "id": "imp-id"
+      }
+    ]
+  },
+  "httpCalls": [{
+    "expectedRequest": {
+      "uri": "https://api.w.inmobi.com/showad/openrtb/bidder/prebid",
+      "body": {
+        "app": {
+          "bundle": "com.example.app"
+        },
+        "id": "req-id",
+        "device": {
+          "ifa": "9d8fe0a9-c0dd-4482-b16b-5709b00c608d",
+          "ip": "1.1.1.1",
+          "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36"
+        },
+        "imp": [
+          {
+            "ext": {
+              "bidder": {
+                "plc": "1596825400965"
+              }
+            },
+            "banner": {
+              "w": 320,
+              "h": 50
+            },
+            "id": "imp-id"
+          }
+        ]
+      }
+    },
+    "mockResponse": {
+      "status": 204,
+      "body": {}
+    }
+  }]
+}
diff --git a/adapters/inmobi/inmobitest/supplemental/400.json b/adapters/inmobi/inmobitest/supplemental/400.json
new file mode 100644
index 00000000..2df5c85a
--- /dev/null
+++ b/adapters/inmobi/inmobitest/supplemental/400.json
@@ -0,0 +1,67 @@
+{
+  "mockBidRequest": {
+    "app": {
+      "bundle": "com.example.app"
+    },
+    "id": "req-id",
+    "device": {
+      "ifa": "9d8fe0a9-c0dd-4482-b16b-5709b00c608d",
+      "ip": "1.1.1.1",
+      "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36"
+    },
+    "imp": [
+      {
+        "ext": {
+          "bidder": {
+            "plc": "1596825400965"
+          }
+        },
+        "banner": {
+          "w": 320,
+          "h": 50
+        },
+        "id": "imp-id"
+      }
+    ]
+  },
+  "httpCalls": [{
+    "expectedRequest": {
+      "uri": "https://api.w.inmobi.com/showad/openrtb/bidder/prebid",
+      "body": {
+        "app": {
+          "bundle": "com.example.app"
+        },
+        "id": "req-id",
+        "device": {
+          "ifa": "9d8fe0a9-c0dd-4482-b16b-5709b00c608d",
+          "ip": "1.1.1.1",
+          "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36"
+        },
+        "imp": [
+          {
+            "ext": {
+              "bidder": {
+                "plc": "1596825400965"
+              }
+            },
+            "banner": {
+              "w": 320,
+              "h": 50
+            },
+            "id": "imp-id"
+          }
+        ]
+      }
+    },
+    "mockResponse": {
+      "status": 400,
+      "body": {}
+    }
+  }],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "Unexpected http status code: 400",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/inmobi/inmobitest/supplemental/banner-format-coersion.json b/adapters/inmobi/inmobitest/supplemental/banner-format-coersion.json
new file mode 100644
index 00000000..514f8681
--- /dev/null
+++ b/adapters/inmobi/inmobitest/supplemental/banner-format-coersion.json
@@ -0,0 +1,113 @@
+{
+  "mockBidRequest": {
+    "app": {
+      "bundle": "com.example.app"
+    },
+    "id": "req-id",
+    "device": {
+      "ifa": "device-ifa",
+      "ip": "1.1.1.1",
+      "ua": "device-ua"
+    },
+    "imp": [
+      {
+        "ext": {
+          "bidder": {
+            "plc": "1596825400965"
+          }
+        },
+        "banner": {
+          "format": [{
+            "w": 320,
+            "h": 50
+          }]
+        },
+        "id": "imp-id"
+      }
+    ]
+  },
+  "httpCalls": [{
+    "expectedRequest": {
+      "uri": "https://api.w.inmobi.com/showad/openrtb/bidder/prebid",
+      "body": {
+        "app": {
+          "bundle": "com.example.app"
+        },
+        "id": "req-id",
+        "device": {
+          "ifa": "device-ifa",
+          "ip": "1.1.1.1",
+          "ua": "device-ua"
+        },
+        "imp": [
+          {
+            "ext": {
+              "bidder": {
+                "plc": "1596825400965"
+              }
+            },
+            "banner": {
+              "format": [{
+                "w": 320,
+                "h": 50
+              }],
+              "w": 320,
+              "h": 50
+            },
+            "id": "imp-id"
+          }
+        ]
+      }
+    },
+    "mockResponse": {
+      "status": 200,
+      "body": {
+        "id": "req-id",
+        "seatbid": [
+          {
+            "bid": [
+              {
+                "ext": {
+                  "prebid": {
+                    "meta": {
+                      "networkName": "inmobi"
+                    }
+                  }
+                },
+                "nurl": "https://some.event.url/params",
+                "crid": "123456789",
+                "adomain": [],
+                "price": 2.0,
+                "id": "1234",
+                "adm": "bannerhtml",
+                "impid": "imp-id"
+              }
+            ]
+          }
+        ]
+      }
+    }
+  }],
+
+  "expectedBidResponses": [{
+    "currency": "USD",
+    "bids": [{
+      "bid": {
+        "id": "1234",
+        "impid": "imp-id",
+        "price": 2.0,
+        "adm": "bannerhtml",
+        "crid": "123456789",
+        "nurl": "https://some.event.url/params",
+        "ext": {
+          "prebid": {
+            "meta": {
+              "networkName": "inmobi"
+            }
+          }
+        }
+      },
+      "type": "banner"
+    }]
+  }]
+}
diff --git a/adapters/inmobi/inmobitest/supplemental/ext-unmarshal-err.json b/adapters/inmobi/inmobitest/supplemental/ext-unmarshal-err.json
new file mode 100644
index 00000000..957a2f6f
--- /dev/null
+++ b/adapters/inmobi/inmobitest/supplemental/ext-unmarshal-err.json
@@ -0,0 +1,28 @@
+{
+  "mockBidRequest": {
+    "id": "req-id",
+    "imp": [
+      {
+        "banner": {
+          "format": [
+            {
+              "w": 320,
+              "h": 50
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "plc": true
+          }
+        }
+      }
+    ]
+  },
+  "expectedMakeRequestsErrors": [
+    {
+      "value": "bad InMobi bidder ext",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/inmobi/inmobitest/supplemental/missing-plc-error.json b/adapters/inmobi/inmobitest/supplemental/missing-plc-error.json
new file mode 100644
index 00000000..52697cf9
--- /dev/null
+++ b/adapters/inmobi/inmobitest/supplemental/missing-plc-error.json
@@ -0,0 +1,28 @@
+{
+  "mockBidRequest": {
+    "id": "req-id",
+    "imp": [
+      {
+        "banner": {
+          "format": [
+            {
+              "w": 320,
+              "h": 50
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "a": 1
+          }
+        }
+      }
+    ]
+  },
+  "expectedMakeRequestsErrors": [
+    {
+      "value": "'plc' is a required attribute for InMobi's bidder ext",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/inmobi/inmobitest/supplemental/no-imp-error.json b/adapters/inmobi/inmobitest/supplemental/no-imp-error.json
new file mode 100644
index 00000000..6c6c3634
--- /dev/null
+++ b/adapters/inmobi/inmobitest/supplemental/no-imp-error.json
@@ -0,0 +1,13 @@
+{
+  "mockBidRequest": {
+    "id": "req-id",
+    "imp": [
+    ]
+  },
+  "expectedMakeRequestsErrors": [
+    {
+      "value": "No impression in the request",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go
index 53fea2e0..925cffce 100644
--- a/endpoints/openrtb2/auction_test.go
+++ b/endpoints/openrtb2/auction_test.go
@@ -141,6 +141,16 @@ func TestGoodRequests(t *testing.T) {
 	supplementary.assert(t)
 }
 
+func TestFirstPartyDataRequests(t *testing.T) {
+	validRequests := &getResponseFromDirectory{
+		dir:           "sample-requests/first-party-data",
+		payloadGetter: getRequestPayload,
+		messageGetter: nilReturner,
+		expectedCode:  http.StatusOK,
+	}
+	validRequests.assert(t)
+}
+
 // TestGoodNativeRequests makes sure we return 200s on well-formed Native requests.
 func TestGoodNativeRequests(t *testing.T) {
 	tests := &getResponseFromDirectory{
@@ -1127,10 +1137,73 @@ func TestDisabledBidder(t *testing.T) {
 	}
 }
 
-func TestValidateImpExtDisabledBidder(t *testing.T) {
-	imp := &openrtb.Imp{
-		Ext: json.RawMessage(`{"appnexus":{"placement_id":555},"unknownbidder":{"foo":"bar"}}`),
+func TestValidateImpExt(t *testing.T) {
+	testCases := []struct {
+		description    string
+		impExt         json.RawMessage
+		expectedImpExt string
+		expectedErrs   []error
+	}{
+		{
+			description:    "Empty",
+			impExt:         nil,
+			expectedImpExt: "",
+			expectedErrs:   []error{errors.New("request.imp[0].ext is required")},
+		},
+		{
+			description:    "Valid Bidder",
+			impExt:         json.RawMessage(`{"appnexus":{"placement_id":555}}`),
+			expectedImpExt: `{"appnexus":{"placement_id":555}}`,
+			expectedErrs:   []error{},
+		},
+		{
+			description:    "Valid Bidder + Disabled Bidder",
+			impExt:         json.RawMessage(`{"appnexus":{"placement_id":555},"unknownbidder":{"foo":"bar"}}`),
+			expectedImpExt: `{"appnexus":{"placement_id":555}}`,
+			expectedErrs:   []error{&errortypes.BidderTemporarilyDisabled{Message: "The bidder 'unknownbidder' has been disabled."}},
+		},
+		{
+			description:    "Valid Bidder + Disabled Bidder + First Party Data Context",
+			impExt:         json.RawMessage(`{"appnexus":{"placement_id":555},"unknownbidder":{"foo":"bar"},"context":{"data":{"keywords":"prebid server example"}}}`),
+			expectedImpExt: `{"appnexus":{"placement_id":555},"context":{"data":{"keywords":"prebid server example"}}}`,
+			expectedErrs:   []error{&errortypes.BidderTemporarilyDisabled{Message: "The bidder 'unknownbidder' has been disabled."}},
+		},
+		{
+			description:    "Valid Bidder + First Party Data Context",
+			impExt:         json.RawMessage(`{"appnexus":{"placement_id":555},"context":{"data":{"keywords":"prebid server example"}}}`),
+			expectedImpExt: `{"appnexus":{"placement_id":555},"context":{"data":{"keywords":"prebid server example"}}}`,
+			expectedErrs:   []error{},
+		},
+		{
+			description:    "Valid Prebid Ext Bidder",
+			impExt:         json.RawMessage(`{"prebid":{"bidder":{"appnexus":{"placement_id":555}}}}`),
+			expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id":555}}}}`,
+			expectedErrs:   []error{},
+			// request.imp[x].ext.prebid.bidder.{biddername} is only promoted/copied to request.ext.{biddername} if there is at least one disabled bidder.
+		},
+		{
+			description:    "Valid Prebid Ext Bidder + First Party Data Context",
+			impExt:         json.RawMessage(`{"prebid":{"bidder":{"appnexus":{"placement_id":555}}} ,"context":{"data":{"keywords":"prebid server example"}}}`),
+			expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id":555}}},"context":{"data":{"keywords":"prebid server example"}}}`,
+			expectedErrs:   []error{},
+			// request.imp[x].ext.prebid.bidder.{biddername} is only promoted/copied to request.ext.{biddername} if there is at least one disabled bidder.
+		},
+		{
+			description:    "Valid Prebid Ext Bidder + Disabled Bidder",
+			impExt:         json.RawMessage(`{"prebid":{"bidder":{"appnexus":{"placement_id":555},"unknownbidder":{"foo":"bar"}}}}`),
+			expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id": 555},"unknownbidder":{"foo":"bar"}}},"appnexus":{"placement_id":555}}`,
+			expectedErrs:   []error{&errortypes.BidderTemporarilyDisabled{Message: "The bidder 'unknownbidder' has been disabled."}},
+			// request.imp[x].ext.prebid.bidder.{biddername} disabled bidders are not removed. if there is a disabled bidder, the valid ones are promoted/copied to request.ext.{biddername}.
+		},
+		{
+			description:    "Valid Prebid Ext Bidder + Disabled Bidder + First Party Data Context",
+			impExt:         json.RawMessage(`{"prebid":{"bidder":{"appnexus":{"placement_id":555},"unknownbidder":{"foo":"bar"}}},"context":{"data":{"keywords":"prebid server example"}}}`),
+			expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id": 555},"unknownbidder":{"foo":"bar"}}},"appnexus":{"placement_id":555},"context":{"data":{"keywords":"prebid server example"}}}`,
+			expectedErrs:   []error{&errortypes.BidderTemporarilyDisabled{Message: "The bidder 'unknownbidder' has been disabled."}},
+			// request.imp[x].ext.prebid.bidder.{biddername} disabled bidders are not removed. if there is a disabled bidder, the valid ones are promoted/copied to request.ext.{biddername}.
+		},
 	}
+
 	deps := &endpointDeps{
 		&nobidExchange{},
 		newParamsValidator(t),
@@ -1149,9 +1222,19 @@ func TestValidateImpExtDisabledBidder(t *testing.T) {
 		nil,
 		hardcodedResponseIPValidator{response: true},
 	}
-	errs := deps.validateImpExt(imp, nil, 0)
-	assert.JSONEq(t, `{"appnexus":{"placement_id":555}}`, string(imp.Ext))
-	assert.Equal(t, []error{&errortypes.BidderTemporarilyDisabled{Message: "The bidder 'unknownbidder' has been disabled."}}, errs)
+
+	for _, test := range testCases {
+		imp := &openrtb.Imp{Ext: test.impExt}
+
+		errs := deps.validateImpExt(imp, nil, 0)
+
+		if len(test.expectedImpExt) > 0 {
+			assert.JSONEq(t, test.expectedImpExt, string(imp.Ext))
+		} else {
+			assert.Empty(t, imp.Ext)
+		}
+		assert.Equal(t, test.expectedErrs, errs)
+	}
 }
 
 func validRequest(t *testing.T, filename string) string {
@@ -1901,8 +1984,7 @@ func (cf mockStoredReqFetcher) FetchRequests(ctx context.Context, requestIDs []s
 }
 
 var mockAccountData = map[string]json.RawMessage{
-	"valid_acct":    json.RawMessage(`{"disabled":false}`),
-	"disabled_acct": json.RawMessage(`{"disabled":true}`),
+	"valid_acct": json.RawMessage(`{"disabled":false}`),
 }
 
 type mockAccountFetcher struct {
@@ -1975,70 +2057,3 @@ type hardcodedResponseIPValidator struct {
 func (v hardcodedResponseIPValidator) IsValid(net.IP, iputil.IPVersion) bool {
 	return v.response
 }
-
-func TestGetAccount(t *testing.T) {
-	unknown := pbsmetrics.PublisherUnknown
-	testCases := []struct {
-		accountID string
-		// account_required
-		required bool
-		// account_defaults.disabled
-		disabled bool
-		// expected error, or nil if account should be found
-		err error
-	}{
-		// Blacklisted account is always rejected even in permissive setup
-		{accountID: "bad_acct", required: false, disabled: false, err: &errortypes.BlacklistedAcct{}},
-
-		// empty pubID
-		{accountID: unknown, required: false, disabled: false, err: nil},
-		{accountID: unknown, required: true, disabled: false, err: &errortypes.AcctRequired{}},
-		{accountID: unknown, required: false, disabled: true, err: &errortypes.BlacklistedAcct{}},
-		{accountID: unknown, required: true, disabled: true, err: &errortypes.AcctRequired{}},
-
-		// pubID given but is not a valid host account (does not exist)
-		{accountID: "doesnt_exist_acct", required: false, disabled: false, err: nil},
-		{accountID: "doesnt_exist_acct", required: true, disabled: false, err: nil},
-		{accountID: "doesnt_exist_acct", required: false, disabled: true, err: &errortypes.BlacklistedAcct{}},
-		{accountID: "doesnt_exist_acct", required: true, disabled: true, err: &errortypes.AcctRequired{}},
-
-		// pubID given and matches a valid host account with Disabled: false
-		{accountID: "valid_acct", required: false, disabled: false, err: nil},
-		{accountID: "valid_acct", required: true, disabled: false, err: nil},
-		{accountID: "valid_acct", required: false, disabled: true, err: nil},
-		{accountID: "valid_acct", required: true, disabled: true, err: nil},
-
-		// pubID given and matches a host account explicitly disabled (Disabled: true on account json)
-		{accountID: "disabled_acct", required: false, disabled: false, err: &errortypes.BlacklistedAcct{}},
-		{accountID: "disabled_acct", required: true, disabled: false, err: &errortypes.BlacklistedAcct{}},
-		{accountID: "disabled_acct", required: false, disabled: true, err: &errortypes.BlacklistedAcct{}},
-		{accountID: "disabled_acct", required: true, disabled: true, err: &errortypes.BlacklistedAcct{}},
-	}
-
-	for _, test := range testCases {
-		description := fmt.Sprintf(`ID=%s/required=%t/disabled=%t`, test.accountID, test.required, test.disabled)
-		t.Run(description, func(t *testing.T) {
-			deps := &endpointDeps{
-				cfg: &config.Configuration{
-					BlacklistedAcctMap: map[string]bool{"bad_acct": true},
-					AccountRequired:    test.required,
-					AccountDefaults:    config.Account{Disabled: test.disabled},
-				},
-				accounts: &mockAccountFetcher{},
-			}
-			assert.NoError(t, deps.cfg.MarshalAccountDefaults())
-
-			account, errors := deps.getAccount(context.Background(), test.accountID)
-
-			if test.err == nil {
-				assert.Empty(t, errors)
-				assert.Equal(t, test.accountID, account.ID, "account.ID must match requested ID")
-				assert.Equal(t, false, account.Disabled, "returned account must not be disabled")
-			} else {
-				assert.NotEmpty(t, errors, "expected errors but got success")
-				assert.Nil(t, account, "return account must be nil on error")
-				assert.IsType(t, test.err, errors[0], "error is of unexpected type")
-			}
-		})
-	}
-}
diff --git a/exchange/exchangetest/firstpartydata-imp-ext-multiple-bidders.json b/exchange/exchangetest/firstpartydata-imp-ext-multiple-bidders.json
new file mode 100644
index 00000000..8004c3c2
--- /dev/null
+++ b/exchange/exchangetest/firstpartydata-imp-ext-multiple-bidders.json
@@ -0,0 +1,173 @@
+{
+    "incomingRequest": {
+        "ortbRequest": {
+            "id": "some-request-id",
+            "site": {
+                "page": "test.somepage.com"
+            },
+            "imp": [{
+                "id": "some-imp-id",
+                "banner": {
+                    "format": [{
+                        "w": 600,
+                        "h": 500
+                    }, {
+                        "w": 300,
+                        "h": 600
+                    }]
+                },
+                "ext": {
+                    "appnexus": {
+                        "placementId": 1
+                    },
+                    "rubicon": {
+                        "accountId": 1,
+                        "siteId": 2,
+                        "zoneId": 3
+                    },
+                    "context": {
+                        "data": {
+                            "keywords": "prebid server example"
+                        }
+                    }
+                }
+            }]
+        }
+    },
+    "outgoingRequests": {
+        "appnexus": {
+            "expectRequest": {
+                "ortbRequest": {
+                    "id": "some-request-id",
+                    "site": {
+                        "page": "test.somepage.com"
+                    },
+                    "imp": [{
+                        "id": "some-imp-id",
+                        "banner": {
+                            "format": [{
+                                "w": 600,
+                                "h": 500
+                            }, {
+                                "w": 300,
+                                "h": 600
+                            }]
+                        },
+                        "ext": {
+                            "bidder": {
+                                "placementId": 1
+                            },
+                            "context": {
+                                "data": {
+                                    "keywords": "prebid server example"
+                                }
+                            }
+                        }
+                    }]
+                },
+                "bidAdjustment": 1.0
+            },
+            "mockResponse": {
+                "pbsSeatBid": {
+                    "pbsBids": [{
+                        "ortbBid": {
+                            "id": "apn-bid",
+                            "impid": "some-imp-id",
+                            "price": 0.3,
+                            "w": 200,
+                            "h": 500,
+                            "crid": "creative-1"
+                        },
+                        "bidType": "banner"
+                    }]
+                }
+            }
+        },
+        "rubicon": {
+            "expectRequest": {
+                "ortbRequest": {
+                    "id": "some-request-id",
+                    "site": {
+                        "page": "test.somepage.com"
+                    },
+                    "imp": [{
+                        "id": "some-imp-id",
+                        "banner": {
+                            "format": [{
+                                "w": 600,
+                                "h": 500
+                            }, {
+                                "w": 300,
+                                "h": 600
+                            }]
+                        },
+                        "ext": {
+                            "bidder": {
+                                "accountId": 1,
+                                "siteId": 2,
+                                "zoneId": 3
+                            },
+                            "context": {
+                                "data": {
+                                    "keywords": "prebid server example"
+                                }
+                            }
+                        }
+                    }]
+                },
+                "bidAdjustment": 1.0
+            },
+            "mockResponse": {
+                "pbsSeatBid": {
+                    "pbsBids": [{
+                        "ortbBid": {
+                            "id": "rubi-bid",
+                            "impid": "some-imp-id",
+                            "price": 0.4,
+                            "w": 200,
+                            "h": 500,
+                            "crid": "creative-2"
+                        },
+                        "bidType": "banner"
+                    }]
+                }
+            }
+        }
+    },
+    "response": {
+        "bids": {
+            "id": "some-request-id",
+            "seatbid": [{
+                "seat": "appnexus",
+                "bid": [{
+                    "id": "apn-bid",
+                    "impid": "some-imp-id",
+                    "price": 0.3,
+                    "w": 200,
+                    "h": 500,
+                    "crid": "creative-1",
+                    "ext": {
+                        "prebid": {
+                            "type": "banner"
+                        }
+                    }
+                }]
+            }, {
+                "seat": "rubicon",
+                "bid": [{
+                    "id": "rubi-bid",
+                    "impid": "some-imp-id",
+                    "price": 0.4,
+                    "w": 200,
+                    "h": 500,
+                    "crid": "creative-2",
+                    "ext": {
+                        "prebid": {
+                            "type": "banner"
+                        }
+                    }
+                }]
+            }]
+        }
+    }
+}
\ No newline at end of file
diff --git a/exchange/exchangetest/firstpartydata-imp-ext-multiple-prebid-bidders.json b/exchange/exchangetest/firstpartydata-imp-ext-multiple-prebid-bidders.json
new file mode 100644
index 00000000..d62afccf
--- /dev/null
+++ b/exchange/exchangetest/firstpartydata-imp-ext-multiple-prebid-bidders.json
@@ -0,0 +1,179 @@
+{
+    "incomingRequest": {
+        "ortbRequest": {
+            "id": "some-request-id",
+            "site": {
+                "page": "test.somepage.com"
+            },
+            "imp": [{
+                "id": "some-imp-id",
+                "banner": {
+                    "format": [{
+                        "w": 600,
+                        "h": 500
+                    }, {
+                        "w": 300,
+                        "h": 600
+                    }]
+                },
+                "ext": {
+                    "prebid": {
+                        "bidder": {
+                            "appnexus": {
+                                "placementId": 1
+                            },
+                            "rubicon": {
+                                "accountId": 1,
+                                "siteId": 2,
+                                "zoneId": 3
+                            }
+                        }
+                    },
+                    "context": {
+                        "data": {
+                            "keywords": "prebid server example"
+                        }
+                    }
+                }
+            }]
+        }
+    },
+    "outgoingRequests": {
+        "appnexus": {
+            "expectRequest": {
+                "ortbRequest": {
+                    "id": "some-request-id",
+                    "site": {
+                        "page": "test.somepage.com"
+                    },
+                    "imp": [{
+                        "id": "some-imp-id",
+                        "banner": {
+                            "format": [{
+                                "w": 600,
+                                "h": 500
+                            }, {
+                                "w": 300,
+                                "h": 600
+                            }]
+                        },
+                        "ext": {
+                            "bidder": {
+                                "placementId": 1
+                            },
+                            "prebid": {},
+                            "context": {
+                                "data": {
+                                    "keywords": "prebid server example"
+                                }
+                            }
+                        }
+                    }]
+                },
+                "bidAdjustment": 1.0
+            },
+            "mockResponse": {
+                "pbsSeatBid": {
+                    "pbsBids": [{
+                        "ortbBid": {
+                            "id": "apn-bid",
+                            "impid": "some-imp-id",
+                            "price": 0.3,
+                            "w": 200,
+                            "h": 500,
+                            "crid": "creative-1"
+                        },
+                        "bidType": "banner"
+                    }]
+                }
+            }
+        },
+        "rubicon": {
+            "expectRequest": {
+                "ortbRequest": {
+                    "id": "some-request-id",
+                    "site": {
+                        "page": "test.somepage.com"
+                    },
+                    "imp": [{
+                        "id": "some-imp-id",
+                        "banner": {
+                            "format": [{
+                                "w": 600,
+                                "h": 500
+                            }, {
+                                "w": 300,
+                                "h": 600
+                            }]
+                        },
+                        "ext": {
+                            "bidder": {
+                                "accountId": 1,
+                                "siteId": 2,
+                                "zoneId": 3
+                            },
+                            "prebid": {},
+                            "context": {
+                                "data": {
+                                    "keywords": "prebid server example"
+                                }
+                            }
+                        }
+                    }]
+                },
+                "bidAdjustment": 1.0
+            },
+            "mockResponse": {
+                "pbsSeatBid": {
+                    "pbsBids": [{
+                        "ortbBid": {
+                            "id": "rubi-bid",
+                            "impid": "some-imp-id",
+                            "price": 0.4,
+                            "w": 200,
+                            "h": 500,
+                            "crid": "creative-2"
+                        },
+                        "bidType": "banner"
+                    }]
+                }
+            }
+        }
+    },
+    "response": {
+        "bids": {
+            "id": "some-request-id",
+            "seatbid": [{
+                "seat": "appnexus",
+                "bid": [{
+                    "id": "apn-bid",
+                    "impid": "some-imp-id",
+                    "price": 0.3,
+                    "w": 200,
+                    "h": 500,
+                    "crid": "creative-1",
+                    "ext": {
+                        "prebid": {
+                            "type": "banner"
+                        }
+                    }
+                }]
+            }, {
+                "seat": "rubicon",
+                "bid": [{
+                    "id": "rubi-bid",
+                    "impid": "some-imp-id",
+                    "price": 0.4,
+                    "w": 200,
+                    "h": 500,
+                    "crid": "creative-2",
+                    "ext": {
+                        "prebid": {
+                            "type": "banner"
+                        }
+                    }
+                }]
+            }]
+        }
+    }
+}
\ No newline at end of file
diff --git a/exchange/exchangetest/firstpartydata-imp-ext-one-bidder.json b/exchange/exchangetest/firstpartydata-imp-ext-one-bidder.json
new file mode 100644
index 00000000..6f0bab95
--- /dev/null
+++ b/exchange/exchangetest/firstpartydata-imp-ext-one-bidder.json
@@ -0,0 +1,103 @@
+{
+    "incomingRequest": {
+        "ortbRequest": {
+            "id": "some-request-id",
+            "site": {
+                "page": "test.somepage.com"
+            },
+            "imp": [{
+                "id": "some-imp-id",
+                "banner": {
+                    "format": [{
+                        "w": 600,
+                        "h": 500
+                    }, {
+                        "w": 300,
+                        "h": 600
+                    }]
+                },
+                "ext": {
+                    "appnexus": {
+                        "placementId": 1
+                    },
+                    "context": {
+                        "data": {
+                            "keywords": "prebid server example"
+                        }
+                    }
+                }
+            }]
+        }
+    },
+    "outgoingRequests": {
+        "appnexus": {
+            "expectRequest": {
+                "ortbRequest": {
+                    "id": "some-request-id",
+                    "site": {
+                        "page": "test.somepage.com"
+                    },
+                    "imp": [{
+                        "id": "some-imp-id",
+                        "banner": {
+                            "format": [{
+                                "w": 600,
+                                "h": 500
+                            }, {
+                                "w": 300,
+                                "h": 600
+                            }]
+                        },
+                        "ext": {
+                            "bidder": {
+                                "placementId": 1
+                            },
+                            "context": {
+                                "data": {
+                                    "keywords": "prebid server example"
+                                }
+                            }
+                        }
+                    }]
+                },
+                "bidAdjustment": 1.0
+            },
+            "mockResponse": {
+                "pbsSeatBid": {
+                    "pbsBids": [{
+                        "ortbBid": {
+                            "id": "apn-bid",
+                            "impid": "some-imp-id",
+                            "price": 0.3,
+                            "w": 200,
+                            "h": 500,
+                            "crid": "creative-1"
+                        },
+                        "bidType": "banner"
+                    }]
+                }
+            }
+        }
+    },
+    "response": {
+        "bids": {
+            "id": "some-request-id",
+            "seatbid": [{
+                "seat": "appnexus",
+                "bid": [{
+                    "id": "apn-bid",
+                    "impid": "some-imp-id",
+                    "price": 0.3,
+                    "w": 200,
+                    "h": 500,
+                    "crid": "creative-1",
+                    "ext": {
+                        "prebid": {
+                            "type": "banner"
+                        }
+                    }
+                }]
+            }]
+        }
+    }
+}
\ No newline at end of file
diff --git a/exchange/exchangetest/firstpartydata-imp-ext-one-prebid-bidder.json b/exchange/exchangetest/firstpartydata-imp-ext-one-prebid-bidder.json
new file mode 100644
index 00000000..1610b9ea
--- /dev/null
+++ b/exchange/exchangetest/firstpartydata-imp-ext-one-prebid-bidder.json
@@ -0,0 +1,108 @@
+{
+    "incomingRequest": {
+        "ortbRequest": {
+            "id": "some-request-id",
+            "site": {
+                "page": "test.somepage.com"
+            },
+            "imp": [{
+                "id": "some-imp-id",
+                "banner": {
+                    "format": [{
+                        "w": 600,
+                        "h": 500
+                    }, {
+                        "w": 300,
+                        "h": 600
+                    }]
+                },
+                "ext": {
+                    "prebid": {
+                        "bidder": {
+                            "appnexus": {
+                                "placementId": 1
+                            }
+                        }
+                    },
+                    "context": {
+                        "data": {
+                            "keywords": "prebid server example"
+                        }
+                    }
+                }
+            }]
+        }
+    },
+    "outgoingRequests": {
+        "appnexus": {
+            "expectRequest": {
+                "ortbRequest": {
+                    "id": "some-request-id",
+                    "site": {
+                        "page": "test.somepage.com"
+                    },
+                    "imp": [{
+                        "id": "some-imp-id",
+                        "banner": {
+                            "format": [{
+                                "w": 600,
+                                "h": 500
+                            }, {
+                                "w": 300,
+                                "h": 600
+                            }]
+                        },
+                        "ext": {
+                            "bidder": {
+                                "placementId": 1
+                            },
+                            "prebid": {},
+                            "context": {
+                                "data": {
+                                    "keywords": "prebid server example"
+                                }
+                            }
+                        }
+                    }]
+                },
+                "bidAdjustment": 1.0
+            },
+            "mockResponse": {
+                "pbsSeatBid": {
+                    "pbsBids": [{
+                        "ortbBid": {
+                            "id": "apn-bid",
+                            "impid": "some-imp-id",
+                            "price": 0.3,
+                            "w": 200,
+                            "h": 500,
+                            "crid": "creative-1"
+                        },
+                        "bidType": "banner"
+                    }]
+                }
+            }
+        }
+    },
+    "response": {
+        "bids": {
+            "id": "some-request-id",
+            "seatbid": [{
+                "seat": "appnexus",
+                "bid": [{
+                    "id": "apn-bid",
+                    "impid": "some-imp-id",
+                    "price": 0.3,
+                    "w": 200,
+                    "h": 500,
+                    "crid": "creative-1",
+                    "ext": {
+                        "prebid": {
+                            "type": "banner"
+                        }
+                    }
+                }]
+            }]
+        }
+    }
+}
\ No newline at end of file
diff --git a/openrtb_ext/bidders_test.go b/openrtb_ext/bidders_test.go
index d49b2323..9f05f526 100644
--- a/openrtb_ext/bidders_test.go
+++ b/openrtb_ext/bidders_test.go
@@ -61,3 +61,8 @@ func TestBidderListDoesNotDefineGeneral(t *testing.T) {
 	bidders := BidderList()
 	assert.NotContains(t, bidders, BidderNameGeneral)
 }
+
+func TestBidderListDoesNotDefineContext(t *testing.T) {
+	bidders := BidderList()
+	assert.NotContains(t, bidders, BidderNameContext)
+}
diff --git a/privacy/enforcement_test.go b/privacy/enforcement_test.go
index ef02e281..0cf36a61 100644
--- a/privacy/enforcement_test.go
+++ b/privacy/enforcement_test.go
@@ -19,7 +19,6 @@ func TestAny(t *testing.T) {
 			enforcement: Enforcement{
 				CCPA:    false,
 				COPPA:   false,
-				GDPR:    false,
 				GDPRGeo: false,
 				GDPRID:  false,
 				LMT:     false,
@@ -31,7 +30,6 @@ func TestAny(t *testing.T) {
 			enforcement: Enforcement{
 				CCPA:    true,
 				COPPA:   true,
-				GDPR:    true,
 				GDPRGeo: true,
 				GDPRID:  true,
 				LMT:     true,
@@ -43,7 +41,6 @@ func TestAny(t *testing.T) {
 			enforcement: Enforcement{
 				CCPA:    false,
 				COPPA:   true,
-				GDPR:    false,
 				GDPRGeo: false,
 				GDPRID:  false,
 				LMT:     true,
@@ -63,6 +60,8 @@ func TestApply(t *testing.T) {
 		description        string
 		enforcement        Enforcement
 		ampGDPRException   bool
+		expectedDeviceID   ScrubStrategyDeviceID
+		expectedDeviceIPv4 ScrubStrategyIPV4
 		expectedDeviceIPv6 ScrubStrategyIPV6
 		expectedDeviceGeo  ScrubStrategyGeo
 		expectedUser       ScrubStrategyUser
@@ -73,12 +72,12 @@ func TestApply(t *testing.T) {
 			enforcement: Enforcement{
 				CCPA:    true,
 				COPPA:   true,
-				GDPR:    true,
 				GDPRGeo: true,
 				GDPRID:  true,
 				LMT:     true,
 			},
-			ampGDPRException:   false,
+			expectedDeviceID:   ScrubStrategyDeviceIDAll,
+			expectedDeviceIPv4: ScrubStrategyIPV4Lowest8,
 			expectedDeviceIPv6: ScrubStrategyIPV6Lowest32,
 			expectedDeviceGeo:  ScrubStrategyGeoFull,
 			expectedUser:       ScrubStrategyUserIDAndDemographic,
@@ -89,12 +88,12 @@ func TestApply(t *testing.T) {
 			enforcement: Enforcement{
 				CCPA:    true,
 				COPPA:   false,
-				GDPR:    false,
 				GDPRGeo: false,
 				GDPRID:  false,
 				LMT:     false,
 			},
-			ampGDPRException:   false,
+			expectedDeviceID:   ScrubStrategyDeviceIDAll,
+			expectedDeviceIPv4: ScrubStrategyIPV4Lowest8,
 			expectedDeviceIPv6: ScrubStrategyIPV6Lowest16,
 			expectedDeviceGeo:  ScrubStrategyGeoReducedPrecision,
 			expectedUser:       ScrubStrategyUserID,
@@ -105,124 +104,97 @@ func TestApply(t *testing.T) {
 			enforcement: Enforcement{
 				CCPA:    false,
 				COPPA:   true,
-				GDPR:    false,
 				GDPRGeo: false,
 				GDPRID:  false,
 				LMT:     false,
 			},
-			ampGDPRException:   false,
+			expectedDeviceID:   ScrubStrategyDeviceIDAll,
+			expectedDeviceIPv4: ScrubStrategyIPV4Lowest8,
 			expectedDeviceIPv6: ScrubStrategyIPV6Lowest32,
 			expectedDeviceGeo:  ScrubStrategyGeoFull,
 			expectedUser:       ScrubStrategyUserIDAndDemographic,
 			expectedUserGeo:    ScrubStrategyGeoFull,
 		},
 		{
-			description: "GDPR Only",
+			description: "GDPR Only - Full",
 			enforcement: Enforcement{
 				CCPA:    false,
 				COPPA:   false,
-				GDPR:    true,
 				GDPRGeo: true,
 				GDPRID:  true,
 				LMT:     false,
 			},
 			ampGDPRException:   false,
+			expectedDeviceID:   ScrubStrategyDeviceIDAll,
+			expectedDeviceIPv4: ScrubStrategyIPV4Lowest8,
 			expectedDeviceIPv6: ScrubStrategyIPV6Lowest16,
 			expectedDeviceGeo:  ScrubStrategyGeoReducedPrecision,
 			expectedUser:       ScrubStrategyUserID,
 			expectedUserGeo:    ScrubStrategyGeoReducedPrecision,
 		},
 		{
-			description: "GDPR Only, ampGDPRException",
+			description: "GDPR Only - Full - AMP Exception",
 			enforcement: Enforcement{
 				CCPA:    false,
 				COPPA:   false,
-				GDPR:    true,
 				GDPRGeo: true,
 				GDPRID:  true,
 				LMT:     false,
 			},
 			ampGDPRException:   true,
+			expectedDeviceID:   ScrubStrategyDeviceIDAll,
+			expectedDeviceIPv4: ScrubStrategyIPV4Lowest8,
 			expectedDeviceIPv6: ScrubStrategyIPV6Lowest16,
 			expectedDeviceGeo:  ScrubStrategyGeoReducedPrecision,
 			expectedUser:       ScrubStrategyUserNone,
 			expectedUserGeo:    ScrubStrategyGeoReducedPrecision,
 		},
 		{
-			description: "CCPA Only, ampGDPRException",
-			enforcement: Enforcement{
-				CCPA:    true,
-				COPPA:   false,
-				GDPR:    false,
-				GDPRGeo: false,
-				GDPRID:  false,
-				LMT:     false,
-			},
-			ampGDPRException:   true,
-			expectedDeviceIPv6: ScrubStrategyIPV6Lowest16,
-			expectedDeviceGeo:  ScrubStrategyGeoReducedPrecision,
-			expectedUser:       ScrubStrategyUserID,
-			expectedUserGeo:    ScrubStrategyGeoReducedPrecision,
-		},
-		{
-			description: "COPPA and GDPR, ampGDPRException",
-			enforcement: Enforcement{
-				CCPA:    false,
-				COPPA:   true,
-				GDPR:    true,
-				GDPRGeo: true,
-				GDPRID:  true,
-				LMT:     false,
-			},
-			ampGDPRException:   true,
-			expectedDeviceIPv6: ScrubStrategyIPV6Lowest32,
-			expectedDeviceGeo:  ScrubStrategyGeoFull,
-			expectedUser:       ScrubStrategyUserIDAndDemographic,
-			expectedUserGeo:    ScrubStrategyGeoFull,
-		},
-		{
-			description: "GDPR Only, no Geo",
+			description: "GDPR Only - ID Only",
 			enforcement: Enforcement{
 				CCPA:    false,
 				COPPA:   false,
-				GDPR:    true,
 				GDPRGeo: false,
 				GDPRID:  true,
 				LMT:     false,
 			},
 			ampGDPRException:   false,
-			expectedDeviceIPv6: ScrubStrategyIPV6Lowest16,
+			expectedDeviceID:   ScrubStrategyDeviceIDAll,
+			expectedDeviceIPv4: ScrubStrategyIPV4None,
+			expectedDeviceIPv6: ScrubStrategyIPV6None,
 			expectedDeviceGeo:  ScrubStrategyGeoNone,
 			expectedUser:       ScrubStrategyUserID,
 			expectedUserGeo:    ScrubStrategyGeoNone,
 		},
 		{
-			description: "GDPR Only, Geo only",
+			description: "GDPR Only - ID Only - AMP Exception",
 			enforcement: Enforcement{
 				CCPA:    false,
 				COPPA:   false,
-				GDPR:    false,
-				GDPRGeo: true,
-				GDPRID:  false,
+				GDPRGeo: false,
+				GDPRID:  true,
 				LMT:     false,
 			},
-			ampGDPRException:   false,
+			ampGDPRException:   true,
+			expectedDeviceID:   ScrubStrategyDeviceIDAll,
+			expectedDeviceIPv4: ScrubStrategyIPV4None,
 			expectedDeviceIPv6: ScrubStrategyIPV6None,
-			expectedDeviceGeo:  ScrubStrategyGeoReducedPrecision,
+			expectedDeviceGeo:  ScrubStrategyGeoNone,
 			expectedUser:       ScrubStrategyUserNone,
-			expectedUserGeo:    ScrubStrategyGeoReducedPrecision,
+			expectedUserGeo:    ScrubStrategyGeoNone,
 		},
 		{
-			description: "GDPR Only, ID exception",
+			description: "GDPR Only - Geo Only",
 			enforcement: Enforcement{
 				CCPA:    false,
 				COPPA:   false,
-				GDPR:    true,
 				GDPRGeo: true,
 				GDPRID:  false,
 				LMT:     false,
 			},
 			ampGDPRException:   false,
+			expectedDeviceID:   ScrubStrategyDeviceIDNone,
+			expectedDeviceIPv4: ScrubStrategyIPV4Lowest8,
 			expectedDeviceIPv6: ScrubStrategyIPV6Lowest16,
 			expectedDeviceGeo:  ScrubStrategyGeoReducedPrecision,
 			expectedUser:       ScrubStrategyUserNone,
@@ -233,32 +205,50 @@ func TestApply(t *testing.T) {
 			enforcement: Enforcement{
 				CCPA:    false,
 				COPPA:   false,
-				GDPR:    false,
 				GDPRGeo: false,
 				GDPRID:  false,
 				LMT:     true,
 			},
-			ampGDPRException:   false,
+			expectedDeviceID:   ScrubStrategyDeviceIDAll,
+			expectedDeviceIPv4: ScrubStrategyIPV4Lowest8,
 			expectedDeviceIPv6: ScrubStrategyIPV6Lowest16,
 			expectedDeviceGeo:  ScrubStrategyGeoReducedPrecision,
 			expectedUser:       ScrubStrategyUserID,
 			expectedUserGeo:    ScrubStrategyGeoReducedPrecision,
 		},
 		{
-			description: "LMT Only, ampGDPRException",
+			description: "Interactions: COPPA Only + AMP Exception",
 			enforcement: Enforcement{
 				CCPA:    false,
-				COPPA:   false,
-				GDPR:    false,
+				COPPA:   true,
 				GDPRGeo: false,
 				GDPRID:  false,
-				LMT:     true,
+				LMT:     false,
 			},
 			ampGDPRException:   true,
-			expectedDeviceIPv6: ScrubStrategyIPV6Lowest16,
-			expectedDeviceGeo:  ScrubStrategyGeoReducedPrecision,
-			expectedUser:       ScrubStrategyUserID,
-			expectedUserGeo:    ScrubStrategyGeoReducedPrecision,
+			expectedDeviceID:   ScrubStrategyDeviceIDAll,
+			expectedDeviceIPv4: ScrubStrategyIPV4Lowest8,
+			expectedDeviceIPv6: ScrubStrategyIPV6Lowest32,
+			expectedDeviceGeo:  ScrubStrategyGeoFull,
+			expectedUser:       ScrubStrategyUserIDAndDemographic,
+			expectedUserGeo:    ScrubStrategyGeoFull,
+		},
+		{
+			description: "Interactions: COPPA + GDPR Full + AMP Exception",
+			enforcement: Enforcement{
+				CCPA:    false,
+				COPPA:   true,
+				GDPRGeo: true,
+				GDPRID:  true,
+				LMT:     false,
+			},
+			ampGDPRException:   true,
+			expectedDeviceID:   ScrubStrategyDeviceIDAll,
+			expectedDeviceIPv4: ScrubStrategyIPV4Lowest8,
+			expectedDeviceIPv6: ScrubStrategyIPV6Lowest32,
+			expectedDeviceGeo:  ScrubStrategyGeoFull,
+			expectedUser:       ScrubStrategyUserIDAndDemographic,
+			expectedUserGeo:    ScrubStrategyGeoFull,
 		},
 	}
 
@@ -271,7 +261,7 @@ func TestApply(t *testing.T) {
 		replacedUser := &openrtb.User{}
 
 		m := &mockScrubber{}
-		m.On("ScrubDevice", req.Device, test.expectedDeviceIPv6, test.expectedDeviceGeo).Return(replacedDevice).Once()
+		m.On("ScrubDevice", req.Device, test.expectedDeviceID, test.expectedDeviceIPv4, test.expectedDeviceIPv6, test.expectedDeviceGeo).Return(replacedDevice).Once()
 		m.On("ScrubUser", req.User, test.expectedUser, test.expectedUserGeo).Return(replacedUser).Once()
 
 		test.enforcement.apply(req, test.ampGDPRException, m)
@@ -290,7 +280,6 @@ func TestApplyNoneApplicable(t *testing.T) {
 	enforcement := Enforcement{
 		CCPA:    false,
 		COPPA:   false,
-		GDPR:    false,
 		GDPRGeo: false,
 		GDPRID:  false,
 		LMT:     false,
@@ -315,8 +304,8 @@ type mockScrubber struct {
 	mock.Mock
 }
 
-func (m *mockScrubber) ScrubDevice(device *openrtb.Device, ipv6 ScrubStrategyIPV6, geo ScrubStrategyGeo) *openrtb.Device {
-	args := m.Called(device, ipv6, geo)
+func (m *mockScrubber) ScrubDevice(device *openrtb.Device, id ScrubStrategyDeviceID, ipv4 ScrubStrategyIPV4, ipv6 ScrubStrategyIPV6, geo ScrubStrategyGeo) *openrtb.Device {
+	args := m.Called(device, id, ipv4, ipv6, geo)
 	return args.Get(0).(*openrtb.Device)
 }
 
diff --git a/privacy/scrubber_test.go b/privacy/scrubber_test.go
index 67241019..e0a2cb86 100644
--- a/privacy/scrubber_test.go
+++ b/privacy/scrubber_test.go
@@ -31,28 +31,21 @@ func TestScrubDevice(t *testing.T) {
 	testCases := []struct {
 		description string
 		expected    *openrtb.Device
+		id          ScrubStrategyDeviceID
+		ipv4        ScrubStrategyIPV4
 		ipv6        ScrubStrategyIPV6
 		geo         ScrubStrategyGeo
 	}{
 		{
-			description: "IPv6 Lowest 32 & Geo Full",
-			expected: &openrtb.Device{
-				DIDMD5:   "",
-				DIDSHA1:  "",
-				DPIDMD5:  "",
-				DPIDSHA1: "",
-				MACSHA1:  "",
-				MACMD5:   "",
-				IFA:      "",
-				IP:       "1.2.3.0",
-				IPv6:     "2001:0db8:0000:0000:0000:ff00:0:0",
-				Geo:      &openrtb.Geo{},
-			},
-			ipv6: ScrubStrategyIPV6Lowest32,
-			geo:  ScrubStrategyGeoFull,
+			description: "All Strageties - None",
+			expected:    device,
+			id:          ScrubStrategyDeviceIDNone,
+			ipv4:        ScrubStrategyIPV4None,
+			ipv6:        ScrubStrategyIPV6None,
+			geo:         ScrubStrategyGeoNone,
 		},
 		{
-			description: "IPv6 Lowest 16 & Geo Full",
+			description: "All Strageties - Strictest",
 			expected: &openrtb.Device{
 				DIDMD5:   "",
 				DIDSHA1:  "",
@@ -62,14 +55,16 @@ func TestScrubDevice(t *testing.T) {
 				MACMD5:   "",
 				IFA:      "",
 				IP:       "1.2.3.0",
-				IPv6:     "2001:0db8:0000:0000:0000:ff00:0042:0",
+				IPv6:     "2001:0db8:0000:0000:0000:ff00:0:0",
 				Geo:      &openrtb.Geo{},
 			},
-			ipv6: ScrubStrategyIPV6Lowest16,
+			id:   ScrubStrategyDeviceIDAll,
+			ipv4: ScrubStrategyIPV4Lowest8,
+			ipv6: ScrubStrategyIPV6Lowest32,
 			geo:  ScrubStrategyGeoFull,
 		},
 		{
-			description: "IPv6 None & Geo Full",
+			description: "Isolated - ID - All",
 			expected: &openrtb.Device{
 				DIDMD5:   "",
 				DIDSHA1:  "",
@@ -78,161 +73,126 @@ func TestScrubDevice(t *testing.T) {
 				MACSHA1:  "",
 				MACMD5:   "",
 				IFA:      "",
-				IP:       "1.2.3.0",
+				IP:       "1.2.3.4",
 				IPv6:     "2001:0db8:0000:0000:0000:ff00:0042:8329",
-				Geo:      &openrtb.Geo{},
+				Geo:      device.Geo,
 			},
+			id:   ScrubStrategyDeviceIDAll,
+			ipv4: ScrubStrategyIPV4None,
 			ipv6: ScrubStrategyIPV6None,
-			geo:  ScrubStrategyGeoFull,
+			geo:  ScrubStrategyGeoNone,
 		},
 		{
-			description: "IPv6 Lowest 32 & Geo Reduced",
+			description: "Isolated - IPv4 - Lowest 8",
 			expected: &openrtb.Device{
-				DIDMD5:   "",
-				DIDSHA1:  "",
-				DPIDMD5:  "",
-				DPIDSHA1: "",
-				MACSHA1:  "",
-				MACMD5:   "",
-				IFA:      "",
+				DIDMD5:   "anyDIDMD5",
+				DIDSHA1:  "anyDIDSHA1",
+				DPIDMD5:  "anyDPIDMD5",
+				DPIDSHA1: "anyDPIDSHA1",
+				MACSHA1:  "anyMACSHA1",
+				MACMD5:   "anyMACMD5",
+				IFA:      "anyIFA",
 				IP:       "1.2.3.0",
-				IPv6:     "2001:0db8:0000:0000:0000:ff00:0:0",
-				Geo: &openrtb.Geo{
-					Lat:   123.46,
-					Lon:   678.89,
-					Metro: "some metro",
-					City:  "some city",
-					ZIP:   "some zip",
-				},
+				IPv6:     "2001:0db8:0000:0000:0000:ff00:0042:8329",
+				Geo:      device.Geo,
 			},
-			ipv6: ScrubStrategyIPV6Lowest32,
-			geo:  ScrubStrategyGeoReducedPrecision,
+			id:   ScrubStrategyDeviceIDNone,
+			ipv4: ScrubStrategyIPV4Lowest8,
+			ipv6: ScrubStrategyIPV6None,
+			geo:  ScrubStrategyGeoNone,
 		},
 		{
-			description: "IPv6 Lowest 16 & Geo Reduced",
+			description: "Isolated - IPv6 - Lowest 16",
 			expected: &openrtb.Device{
-				DIDMD5:   "",
-				DIDSHA1:  "",
-				DPIDMD5:  "",
-				DPIDSHA1: "",
-				MACSHA1:  "",
-				MACMD5:   "",
-				IFA:      "",
-				IP:       "1.2.3.0",
+				DIDMD5:   "anyDIDMD5",
+				DIDSHA1:  "anyDIDSHA1",
+				DPIDMD5:  "anyDPIDMD5",
+				DPIDSHA1: "anyDPIDSHA1",
+				MACSHA1:  "anyMACSHA1",
+				MACMD5:   "anyMACMD5",
+				IFA:      "anyIFA",
+				IP:       "1.2.3.4",
 				IPv6:     "2001:0db8:0000:0000:0000:ff00:0042:0",
-				Geo: &openrtb.Geo{
-					Lat:   123.46,
-					Lon:   678.89,
-					Metro: "some metro",
-					City:  "some city",
-					ZIP:   "some zip",
-				},
+				Geo:      device.Geo,
 			},
+			id:   ScrubStrategyDeviceIDNone,
+			ipv4: ScrubStrategyIPV4None,
 			ipv6: ScrubStrategyIPV6Lowest16,
-			geo:  ScrubStrategyGeoReducedPrecision,
-		},
-		{
-			description: "IPv6 None & Geo Reduced",
-			expected: &openrtb.Device{
-				DIDMD5:   "",
-				DIDSHA1:  "",
-				DPIDMD5:  "",
-				DPIDSHA1: "",
-				MACSHA1:  "",
-				MACMD5:   "",
-				IFA:      "",
-				IP:       "1.2.3.0",
-				IPv6:     "2001:0db8:0000:0000:0000:ff00:0042:8329",
-				Geo: &openrtb.Geo{
-					Lat:   123.46,
-					Lon:   678.89,
-					Metro: "some metro",
-					City:  "some city",
-					ZIP:   "some zip",
-				},
-			},
-			ipv6: ScrubStrategyIPV6None,
-			geo:  ScrubStrategyGeoReducedPrecision,
+			geo:  ScrubStrategyGeoNone,
 		},
 		{
-			description: "IPv6 Lowest 32 & Geo None",
+			description: "Isolated - IPv6 - Lowest 32",
 			expected: &openrtb.Device{
-				DIDMD5:   "",
-				DIDSHA1:  "",
-				DPIDMD5:  "",
-				DPIDSHA1: "",
-				MACSHA1:  "",
-				MACMD5:   "",
-				IFA:      "",
-				IP:       "1.2.3.0",
+				DIDMD5:   "anyDIDMD5",
+				DIDSHA1:  "anyDIDSHA1",
+				DPIDMD5:  "anyDPIDMD5",
+				DPIDSHA1: "anyDPIDSHA1",
+				MACSHA1:  "anyMACSHA1",
+				MACMD5:   "anyMACMD5",
+				IFA:      "anyIFA",
+				IP:       "1.2.3.4",
 				IPv6:     "2001:0db8:0000:0000:0000:ff00:0:0",
-				Geo: &openrtb.Geo{
-					Lat:   123.456,
-					Lon:   678.89,
-					Metro: "some metro",
-					City:  "some city",
-					ZIP:   "some zip",
-				},
+				Geo:      device.Geo,
 			},
+			id:   ScrubStrategyDeviceIDNone,
+			ipv4: ScrubStrategyIPV4None,
 			ipv6: ScrubStrategyIPV6Lowest32,
 			geo:  ScrubStrategyGeoNone,
 		},
 		{
-			description: "IPv6 Lowest 16 & Geo None",
+			description: "Isolated - Geo - Reduced Precision",
 			expected: &openrtb.Device{
-				DIDMD5:   "",
-				DIDSHA1:  "",
-				DPIDMD5:  "",
-				DPIDSHA1: "",
-				MACSHA1:  "",
-				MACMD5:   "",
-				IFA:      "",
-				IP:       "1.2.3.0",
-				IPv6:     "2001:0db8:0000:0000:0000:ff00:0042:0",
+				DIDMD5:   "anyDIDMD5",
+				DIDSHA1:  "anyDIDSHA1",
+				DPIDMD5:  "anyDPIDMD5",
+				DPIDSHA1: "anyDPIDSHA1",
+				MACSHA1:  "anyMACSHA1",
+				MACMD5:   "anyMACMD5",
+				IFA:      "anyIFA",
+				IP:       "1.2.3.4",
+				IPv6:     "2001:0db8:0000:0000:0000:ff00:0042:8329",
 				Geo: &openrtb.Geo{
-					Lat:   123.456,
+					Lat:   123.46,
 					Lon:   678.89,
 					Metro: "some metro",
 					City:  "some city",
 					ZIP:   "some zip",
 				},
 			},
-			ipv6: ScrubStrategyIPV6Lowest16,
-			geo:  ScrubStrategyGeoNone,
+			id:   ScrubStrategyDeviceIDNone,
+			ipv4: ScrubStrategyIPV4None,
+			ipv6: ScrubStrategyIPV6None,
+			geo:  ScrubStrategyGeoReducedPrecision,
 		},
 		{
-			description: "IPv6 None & Geo None",
+			description: "Isolated - Geo - Full",
 			expected: &openrtb.Device{
-				DIDMD5:   "",
-				DIDSHA1:  "",
-				DPIDMD5:  "",
-				DPIDSHA1: "",
-				MACSHA1:  "",
-				MACMD5:   "",
-				IFA:      "",
-				IP:       "1.2.3.0",
+				DIDMD5:   "anyDIDMD5",
+				DIDSHA1:  "anyDIDSHA1",
+				DPIDMD5:  "anyDPIDMD5",
+				DPIDSHA1: "anyDPIDSHA1",
+				MACSHA1:  "anyMACSHA1",
+				MACMD5:   "anyMACMD5",
+				IFA:      "anyIFA",
+				IP:       "1.2.3.4",
 				IPv6:     "2001:0db8:0000:0000:0000:ff00:0042:8329",
-				Geo: &openrtb.Geo{
-					Lat:   123.456,
-					Lon:   678.89,
-					Metro: "some metro",
-					City:  "some city",
-					ZIP:   "some zip",
-				},
+				Geo:      &openrtb.Geo{},
 			},
+			id:   ScrubStrategyDeviceIDNone,
+			ipv4: ScrubStrategyIPV4None,
 			ipv6: ScrubStrategyIPV6None,
-			geo:  ScrubStrategyGeoNone,
+			geo:  ScrubStrategyGeoFull,
 		},
 	}
 
 	for _, test := range testCases {
-		result := NewScrubber().ScrubDevice(device, test.ipv6, test.geo)
+		result := NewScrubber().ScrubDevice(device, test.id, test.ipv4, test.ipv6, test.geo)
 		assert.Equal(t, test.expected, result, test.description)
 	}
 }
 
 func TestScrubDeviceNil(t *testing.T) {
-	result := NewScrubber().ScrubDevice(nil, ScrubStrategyIPV6None, ScrubStrategyGeoNone)
+	result := NewScrubber().ScrubDevice(nil, ScrubStrategyDeviceIDNone, ScrubStrategyIPV4None, ScrubStrategyIPV6None, ScrubStrategyGeoNone)
 	assert.Nil(t, result)
 }
 
@@ -458,7 +418,7 @@ func TestScrubIPV4(t *testing.T) {
 	}
 
 	for _, test := range testCases {
-		result := scrubIPV4(test.IP)
+		result := scrubIPV4Lowest8(test.IP)
 		assert.Equal(t, test.cleanedIP, result, test.description)
 	}
 }
diff --git a/stored_requests/caches/cachestest/reliable.go b/stored_requests/caches/cachestest/reliable.go
index e08a20e9..7fbaf723 100644
--- a/stored_requests/caches/cachestest/reliable.go
+++ b/stored_requests/caches/cachestest/reliable.go
@@ -11,8 +11,6 @@ import (
 const (
 	reqCacheKey = "known-req"
 	reqCacheVal = `{"req":true}`
-	impCacheKey = "known-imp"
-	impCacheVal = `{"imp":true}`
 )
 
 // AssertCacheRobustness runs tests which can be used to validate any Cache that is 100% reliable.
@@ -20,84 +18,41 @@ const (
 //
 // The cacheSupplier should be a function which returns a new Cache (with no data inside) on every call.
 // This will be called from separate Goroutines to make sure that different tests don't conflict.
-func AssertCacheRobustness(t *testing.T, cacheSupplier func() stored_requests.Cache) {
+func AssertCacheRobustness(t *testing.T, cacheSupplier func() stored_requests.CacheJSON) {
 	t.Run("TestCacheMiss", cacheMissTester(cacheSupplier()))
 	t.Run("TestCacheHit", cacheHitTester(cacheSupplier()))
-	t.Run("TestCacheMixed", cacheMixedTester(cacheSupplier()))
-	t.Run("TestCacheOverlap", cacheOverlapTester(cacheSupplier()))
 	t.Run("TestCacheSaveInvalidate", cacheSaveInvalidateTester(cacheSupplier()))
 }
 
-func cacheMissTester(cache stored_requests.Cache) func(*testing.T) {
+func cacheMissTester(cache stored_requests.CacheJSON) func(*testing.T) {
 	return func(t *testing.T) {
-		storedReqs, storedImps := cache.Get(context.Background(), []string{"unknown"}, nil)
-		assertMapLength(t, 0, storedReqs)
-		assertMapLength(t, 0, storedImps)
+		storedData := cache.Get(context.Background(), []string{"unknown"})
+		assertMapLength(t, 0, storedData)
 	}
 }
 
-func cacheHitTester(cache stored_requests.Cache) func(*testing.T) {
+func cacheHitTester(cache stored_requests.CacheJSON) func(*testing.T) {
 	return func(t *testing.T) {
 		cache.Save(context.Background(), map[string]json.RawMessage{
 			reqCacheKey: json.RawMessage(reqCacheVal),
-		}, map[string]json.RawMessage{
-			impCacheKey: json.RawMessage(impCacheVal),
 		})
-		reqData, impData := cache.Get(context.Background(), []string{reqCacheKey}, []string{impCacheKey})
-		if len(reqData) != 1 {
-			t.Errorf("The cache should have returned the data.")
-		}
+		reqData := cache.Get(context.Background(), []string{reqCacheKey})
 		assertMapLength(t, 1, reqData)
 		assertHasValue(t, reqData, reqCacheKey, reqCacheVal)
-
-		assertMapLength(t, 1, impData)
-		assertHasValue(t, impData, impCacheKey, impCacheVal)
-	}
-}
-
-func cacheMixedTester(cache stored_requests.Cache) func(*testing.T) {
-	return func(t *testing.T) {
-		cache.Save(context.Background(), map[string]json.RawMessage{
-			reqCacheKey: json.RawMessage(reqCacheVal),
-		}, nil)
-		reqData, impData := cache.Get(context.Background(), []string{reqCacheKey, "unknown-req"}, nil)
-		assertMapLength(t, 1, reqData)
-		assertHasValue(t, reqData, reqCacheKey, reqCacheVal)
-		assertMapLength(t, 0, impData)
 	}
 }
 
-func cacheOverlapTester(cache stored_requests.Cache) func(*testing.T) {
-	commonKey := "id"
+func cacheSaveInvalidateTester(cache stored_requests.CacheJSON) func(*testing.T) {
 	return func(t *testing.T) {
 		cache.Save(context.Background(), map[string]json.RawMessage{
-			commonKey: json.RawMessage(reqCacheVal),
-		}, map[string]json.RawMessage{
-			commonKey: json.RawMessage(impCacheVal),
-		})
-		reqData, impData := cache.Get(context.Background(), []string{commonKey}, []string{commonKey})
-		assertMapLength(t, 1, reqData)
-		assertHasValue(t, reqData, commonKey, reqCacheVal)
-		assertMapLength(t, 1, impData)
-		assertHasValue(t, impData, commonKey, impCacheVal)
-	}
-}
-
-func cacheSaveInvalidateTester(cache stored_requests.Cache) func(*testing.T) {
-	return func(t *testing.T) {
-		cache.Save(context.Background(), map[string]json.RawMessage{
-			reqCacheKey: json.RawMessage(reqCacheVal),
-		}, map[string]json.RawMessage{
 			reqCacheKey: json.RawMessage(reqCacheVal),
 		})
-		reqData, impData := cache.Get(context.Background(), []string{reqCacheKey}, []string{reqCacheKey})
+		reqData := cache.Get(context.Background(), []string{reqCacheKey})
 		assertMapLength(t, 1, reqData)
-		assertMapLength(t, 1, impData)
 
-		cache.Invalidate(context.Background(), []string{reqCacheKey}, []string{reqCacheKey})
-		reqData, impData = cache.Get(context.Background(), []string{reqCacheKey}, []string{reqCacheKey})
+		cache.Invalidate(context.Background(), []string{reqCacheKey})
+		reqData = cache.Get(context.Background(), []string{reqCacheKey})
 		assertMapLength(t, 0, reqData)
-		assertMapLength(t, 0, impData)
 	}
 }
 
diff --git a/stored_requests/caches/memory/cache_test.go b/stored_requests/caches/memory/cache_test.go
index ba4703ef..b89bd5af 100644
--- a/stored_requests/caches/memory/cache_test.go
+++ b/stored_requests/caches/memory/cache_test.go
@@ -7,52 +7,34 @@ import (
 	"strconv"
 	"testing"
 
-	"github.com/prebid/prebid-server/config"
 	"github.com/prebid/prebid-server/stored_requests"
 	"github.com/prebid/prebid-server/stored_requests/caches/cachestest"
 )
 
 func TestLRURobustness(t *testing.T) {
-	cachestest.AssertCacheRobustness(t, func() stored_requests.Cache {
-		return NewCache(&config.InMemoryCache{
-			RequestCacheSize: 256 * 1024,
-			ImpCacheSize:     256 * 1024,
-			TTL:              -1,
-		})
+	cachestest.AssertCacheRobustness(t, func() stored_requests.CacheJSON {
+		return NewCache(256*1024, -1, "TestData")
 	})
 }
 
 func TestUnboundedRobustness(t *testing.T) {
-	cachestest.AssertCacheRobustness(t, func() stored_requests.Cache {
-		return NewCache(&config.InMemoryCache{
-			RequestCacheSize: 0,
-			ImpCacheSize:     0,
-			TTL:              -1,
-		})
+	cachestest.AssertCacheRobustness(t, func() stored_requests.CacheJSON {
+		return NewCache(0, -1, "TestData")
 	})
 }
 
 func TestRaceLRUConcurrency(t *testing.T) {
-	cache := NewCache(&config.InMemoryCache{
-		RequestCacheSize: 256 * 1024,
-		ImpCacheSize:     256 * 1024,
-		TTL:              -1,
-	})
-
+	cache := NewCache(256*1024, -1, "TestData")
 	doRaceTest(t, cache)
 }
 
 func TestRaceUnboundedConcurrency(t *testing.T) {
-	cache := NewCache(&config.InMemoryCache{
-		RequestCacheSize: 0,
-		ImpCacheSize:     0,
-		TTL:              -1,
-	})
+	cache := NewCache(0, -1, "TestData")
 
 	doRaceTest(t, cache)
 }
 
-func doRaceTest(t *testing.T, cache stored_requests.Cache) {
+func doRaceTest(t *testing.T, cache stored_requests.CacheJSON) {
 	done := make(chan struct{})
 	sets := [][]int{rand.Perm(100), rand.Perm(100), rand.Perm(100)}
 
@@ -70,26 +52,26 @@ func doRaceTest(t *testing.T, cache stored_requests.Cache) {
 	}
 }
 
-func readLots(cache stored_requests.Cache, done chan<- struct{}, reads []int) {
+func readLots(cache stored_requests.CacheJSON, done chan<- struct{}, reads []int) {
 	var s struct{}
 	for _, i := range reads {
-		cache.Get(context.Background(), sliceForVal(i), sliceForVal(-i))
+		cache.Get(context.Background(), sliceForVal(i))
 	}
 	done <- s
 }
 
-func writeLots(cache stored_requests.Cache, done chan<- struct{}, writes []int) {
+func writeLots(cache stored_requests.CacheJSON, done chan<- struct{}, writes []int) {
 	var s struct{}
 	for _, i := range writes {
-		cache.Save(context.Background(), mapForVal(i), mapForVal(-i))
+		cache.Save(context.Background(), mapForVal(i))
 	}
 	done <- s
 }
 
-func invalidateLots(cache stored_requests.Cache, done chan<- struct{}, invalidates []int) {
+func invalidateLots(cache stored_requests.CacheJSON, done chan<- struct{}, invalidates []int) {
 	var s struct{}
 	for _, i := range invalidates {
-		cache.Invalidate(context.Background(), sliceForVal(i), sliceForVal(-i))
+		cache.Invalidate(context.Background(), sliceForVal(i))
 	}
 	done <- s
 }
diff --git a/stored_requests/config/config_test.go b/stored_requests/config/config_test.go
index 4c3943ea..af6f4a4f 100644
--- a/stored_requests/config/config_test.go
+++ b/stored_requests/config/config_test.go
@@ -102,8 +102,8 @@ func TestNewHTTPEvents(t *testing.T) {
 
 func TestNewEmptyCache(t *testing.T) {
 	cache := newCache(&config.StoredRequests{InMemoryCache: config.InMemoryCache{Type: "none"}})
-	cache.Save(context.Background(), map[string]json.RawMessage{"foo": json.RawMessage("true")}, nil)
-	reqs, _ := cache.Get(context.Background(), []string{"foo"}, nil)
+	cache.Requests.Save(context.Background(), map[string]json.RawMessage{"foo": json.RawMessage("true")})
+	reqs := cache.Requests.Get(context.Background(), []string{"foo"})
 	if len(reqs) != 0 {
 		t.Errorf("The newCache method should return an empty cache if the config asks for it.")
 	}
@@ -117,8 +117,8 @@ func TestNewInMemoryCache(t *testing.T) {
 			ImpCacheSize:     100,
 		},
 	})
-	cache.Save(context.Background(), map[string]json.RawMessage{"foo": json.RawMessage("true")}, nil)
-	reqs, _ := cache.Get(context.Background(), []string{"foo"}, nil)
+	cache.Requests.Save(context.Background(), map[string]json.RawMessage{"foo": json.RawMessage("true")})
+	reqs := cache.Requests.Get(context.Background(), []string{"foo"})
 	if len(reqs) != 1 {
 		t.Errorf("The newCache method should return an in-memory cache if the config asks for it.")
 	}
diff --git a/stored_requests/events/api/api_test.go b/stored_requests/events/api/api_test.go
index 64cf68b0..d2db4557 100644
--- a/stored_requests/events/api/api_test.go
+++ b/stored_requests/events/api/api_test.go
@@ -9,22 +9,21 @@ import (
 	"strings"
 	"testing"
 
-	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/stored_requests"
 	"github.com/prebid/prebid-server/stored_requests/caches/memory"
 	"github.com/prebid/prebid-server/stored_requests/events"
 )
 
 func TestGoodRequests(t *testing.T) {
-	cache := memory.NewCache(&config.InMemoryCache{
-		RequestCacheSize: 256 * 1024,
-		ImpCacheSize:     256 * 1024,
-		TTL:              -1,
-	})
-
+	cache := stored_requests.Cache{
+		Requests: memory.NewCache(256*1024, -1, "Requests"),
+		Imps:     memory.NewCache(256*1024, -1, "Imps"),
+	}
 	id := "1"
 	config := fmt.Sprintf(`{"id": "%s"}`, id)
 	initialValue := map[string]json.RawMessage{id: json.RawMessage(config)}
-	cache.Save(context.Background(), initialValue, initialValue)
+	cache.Requests.Save(context.Background(), initialValue)
+	cache.Imps.Save(context.Background(), initialValue)
 
 	apiEvents, endpoint := NewEventsAPI()
 
@@ -51,7 +50,8 @@ func TestGoodRequests(t *testing.T) {
 	}
 
 	<-updateOccurred
-	reqData, impData := cache.Get(context.Background(), []string{id}, []string{id})
+	reqData := cache.Requests.Get(context.Background(), []string{id})
+	impData := cache.Imps.Get(context.Background(), []string{id})
 	assertHasValue(t, reqData, id, config)
 	assertHasValue(t, impData, id, config)
 
@@ -66,18 +66,17 @@ func TestGoodRequests(t *testing.T) {
 	}
 
 	<-invalidateOccurred
-	reqData, impData = cache.Get(context.Background(), []string{id}, []string{id})
+	reqData = cache.Requests.Get(context.Background(), []string{id})
+	impData = cache.Imps.Get(context.Background(), []string{id})
 	assertMapLength(t, 0, reqData)
 	assertMapLength(t, 0, impData)
 }
 
 func TestBadRequests(t *testing.T) {
-	cache := memory.NewCache(&config.InMemoryCache{
-		RequestCacheSize: 256 * 1024,
-		ImpCacheSize:     256 * 1024,
-		TTL:              -1,
-	})
-
+	cache := stored_requests.Cache{
+		Requests: memory.NewCache(256*1024, -1, "Requests"),
+		Imps:     memory.NewCache(256*1024, -1, "Imps"),
+	}
 	apiEvents, endpoint := NewEventsAPI()
 	listener := events.SimpleEventListener()
 	go listener.Listen(cache, apiEvents)
diff --git a/stored_requests/events/events_test.go b/stored_requests/events/events_test.go
index 240a6975..74263d14 100644
--- a/stored_requests/events/events_test.go
+++ b/stored_requests/events/events_test.go
@@ -7,7 +7,7 @@ import (
 	"reflect"
 	"testing"
 
-	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/stored_requests"
 	"github.com/prebid/prebid-server/stored_requests/caches/memory"
 )
 
@@ -16,12 +16,10 @@ func TestListen(t *testing.T) {
 		saves:         make(chan Save),
 		invalidations: make(chan Invalidation),
 	}
-
-	cache := memory.NewCache(&config.InMemoryCache{
-		RequestCacheSize: 256 * 1024,
-		ImpCacheSize:     256 * 1024,
-		TTL:              -1,
-	})
+	cache := stored_requests.Cache{
+		Requests: memory.NewCache(256*1024, -1, "Requests"),
+		Imps:     memory.NewCache(256*1024, -1, "Imps"),
+	}
 
 	// create channels to synchronize
 	saveOccurred := make(chan struct{})
@@ -42,7 +40,8 @@ func TestListen(t *testing.T) {
 		Requests: data,
 		Imps:     data,
 	}
-	cache.Save(context.Background(), save.Requests, save.Imps)
+	cache.Requests.Save(context.Background(), save.Requests)
+	cache.Requests.Save(context.Background(), save.Imps)
 
 	config = fmt.Sprintf(`{"id": "%s", "updated": true}`, id)
 	data = map[string]json.RawMessage{id: json.RawMessage(config)}
@@ -54,7 +53,8 @@ func TestListen(t *testing.T) {
 	ep.saves <- save
 	<-saveOccurred
 
-	requestData, impData := cache.Get(context.Background(), idSlice, idSlice)
+	requestData := cache.Requests.Get(context.Background(), idSlice)
+	impData := cache.Imps.Get(context.Background(), idSlice)
 	if !reflect.DeepEqual(requestData, data) || !reflect.DeepEqual(impData, data) {
 		t.Error("Update failed")
 	}
@@ -67,7 +67,8 @@ func TestListen(t *testing.T) {
 	ep.invalidations <- invalidation
 	<-invalidateOccurred
 
-	requestData, impData = cache.Get(context.Background(), idSlice, idSlice)
+	requestData = cache.Requests.Get(context.Background(), idSlice)
+	impData = cache.Imps.Get(context.Background(), idSlice)
 	if len(requestData) > 0 || len(impData) > 0 {
 		t.Error("Invalidate failed")
 	}
diff --git a/stored_requests/fetcher_test.go b/stored_requests/fetcher_test.go
index 1928d116..396ba3d0 100644
--- a/stored_requests/fetcher_test.go
+++ b/stored_requests/fetcher_test.go
@@ -12,25 +12,27 @@ import (
 	"github.com/stretchr/testify/mock"
 )
 
-func setupFetcherWithCacheDeps() (*mockCache, *mockFetcher, AllFetcher, *pbsmetrics.MetricsEngineMock) {
-	cache := &mockCache{}
+func setupFetcherWithCacheDeps() (*mockCache, *mockCache, *mockFetcher, AllFetcher, *pbsmetrics.MetricsEngineMock) {
+	reqCache := &mockCache{}
+	impCache := &mockCache{}
 	metricsEngine := &pbsmetrics.MetricsEngineMock{}
 	fetcher := &mockFetcher{}
-	afetcherWithCache := WithCache(fetcher, cache, metricsEngine)
+	afetcherWithCache := WithCache(fetcher, Cache{reqCache, impCache}, metricsEngine)
 
-	return cache, fetcher, afetcherWithCache, metricsEngine
+	return reqCache, impCache, fetcher, afetcherWithCache, metricsEngine
 }
 
 func TestPerfectCache(t *testing.T) {
-	cache, fetcher, aFetcherWithCache, metricsEngine := setupFetcherWithCacheDeps()
+	reqCache, impCache, fetcher, aFetcherWithCache, metricsEngine := setupFetcherWithCacheDeps()
 	impIDs := []string{"known"}
 	reqIDs := []string{"req-id"}
 	ctx := context.Background()
 
-	cache.On("Get", ctx, reqIDs, impIDs).Return(
+	reqCache.On("Get", ctx, reqIDs).Return(
 		map[string]json.RawMessage{
 			"req-id": json.RawMessage(`{"req":true}`),
-		},
+		})
+	impCache.On("Get", ctx, impIDs).Return(
 		map[string]json.RawMessage{
 			"known": json.RawMessage(`{}`),
 		})
@@ -41,7 +43,8 @@ func TestPerfectCache(t *testing.T) {
 
 	reqData, impData, errs := aFetcherWithCache.FetchRequests(ctx, reqIDs, impIDs)
 
-	cache.AssertExpectations(t)
+	reqCache.AssertExpectations(t)
+	impCache.AssertExpectations(t)
 	fetcher.AssertExpectations(t)
 	metricsEngine.AssertExpectations(t)
 	assert.JSONEq(t, `{"req":true}`, string(reqData["req-id"]), "Fetch requests should fetch the right request data")
@@ -50,15 +53,16 @@ func TestPerfectCache(t *testing.T) {
 }
 
 func TestImperfectCache(t *testing.T) {
-	cache, fetcher, aFetcherWithCache, metricsEngine := setupFetcherWithCacheDeps()
+	reqCache, impCache, fetcher, aFetcherWithCache, metricsEngine := setupFetcherWithCacheDeps()
 	impIDs := []string{"cached", "uncached"}
 	ctx := context.Background()
 
-	cache.On("Get", ctx, []string(nil), impIDs).Return(
-		map[string]json.RawMessage{},
+	impCache.On("Get", ctx, impIDs).Return(
 		map[string]json.RawMessage{
 			"cached": json.RawMessage(`true`),
 		})
+	reqCache.On("Get", ctx, []string(nil)).Return(
+		map[string]json.RawMessage{})
 
 	fetcher.On("FetchRequests", ctx, []string{}, []string{"uncached"}).Return(
 		map[string]json.RawMessage{},
@@ -67,11 +71,11 @@ func TestImperfectCache(t *testing.T) {
 		},
 		[]error{},
 	)
-	cache.On("Save", ctx,
-		map[string]json.RawMessage{},
+	impCache.On("Save", ctx,
 		map[string]json.RawMessage{
 			"uncached": json.RawMessage(`false`),
 		})
+	reqCache.On("Save", ctx, map[string]json.RawMessage{})
 	metricsEngine.On("RecordStoredReqCacheResult", pbsmetrics.CacheHit, 0)
 	metricsEngine.On("RecordStoredReqCacheResult", pbsmetrics.CacheMiss, 0)
 	metricsEngine.On("RecordStoredImpCacheResult", pbsmetrics.CacheHit, 1)
@@ -79,7 +83,7 @@ func TestImperfectCache(t *testing.T) {
 
 	reqData, impData, errs := aFetcherWithCache.FetchRequests(ctx, nil, impIDs)
 
-	cache.AssertExpectations(t)
+	impCache.AssertExpectations(t)
 	fetcher.AssertExpectations(t)
 	metricsEngine.AssertExpectations(t)
 	assert.Len(t, reqData, 0, "Fetch requests should return nil if no request IDs were passed")
@@ -89,14 +93,15 @@ func TestImperfectCache(t *testing.T) {
 }
 
 func TestMissingData(t *testing.T) {
-	cache, fetcher, aFetcherWithCache, metricsEngine := setupFetcherWithCacheDeps()
+	reqCache, impCache, fetcher, aFetcherWithCache, metricsEngine := setupFetcherWithCacheDeps()
 	impIDs := []string{"unknown"}
 	ctx := context.Background()
 
-	cache.On("Get", ctx, []string(nil), impIDs).Return(
-		map[string]json.RawMessage{},
+	impCache.On("Get", ctx, impIDs).Return(
 		map[string]json.RawMessage{},
 	)
+	reqCache.On("Get", ctx, []string(nil)).Return(
+		map[string]json.RawMessage{})
 	fetcher.On("FetchRequests", ctx, []string{}, impIDs).Return(
 		map[string]json.RawMessage{},
 		map[string]json.RawMessage{},
@@ -104,8 +109,10 @@ func TestMissingData(t *testing.T) {
 			errors.New("Data not found"),
 		},
 	)
-	cache.On("Save", ctx,
+	impCache.On("Save", ctx,
 		map[string]json.RawMessage{},
+	)
+	reqCache.On("Save", ctx,
 		map[string]json.RawMessage{},
 	)
 	metricsEngine.On("RecordStoredReqCacheResult", pbsmetrics.CacheHit, 0)
@@ -115,7 +122,8 @@ func TestMissingData(t *testing.T) {
 
 	reqData, impData, errs := aFetcherWithCache.FetchRequests(ctx, nil, impIDs)
 
-	cache.AssertExpectations(t)
+	reqCache.AssertExpectations(t)
+	impCache.AssertExpectations(t)
 	fetcher.AssertExpectations(t)
 	metricsEngine.AssertExpectations(t)
 	assert.Len(t, errs, 1, "FetchRequests for missing data should return an error")
@@ -125,15 +133,16 @@ func TestMissingData(t *testing.T) {
 
 // Prevents #311
 func TestCacheSaves(t *testing.T) {
-	cache, fetcher, aFetcherWithCache, metricsEngine := setupFetcherWithCacheDeps()
+	reqCache, impCache, fetcher, aFetcherWithCache, metricsEngine := setupFetcherWithCacheDeps()
 	impIDs := []string{"abc", "abc"}
 	ctx := context.Background()
 
-	cache.On("Get", ctx, []string(nil), impIDs).Return(
-		map[string]json.RawMessage{},
+	impCache.On("Get", ctx, impIDs).Return(
 		map[string]json.RawMessage{
 			"abc": json.RawMessage(`{}`),
 		})
+	reqCache.On("Get", ctx, []string(nil)).Return(
+		map[string]json.RawMessage{})
 	metricsEngine.On("RecordStoredReqCacheResult", pbsmetrics.CacheHit, 0)
 	metricsEngine.On("RecordStoredReqCacheResult", pbsmetrics.CacheMiss, 0)
 	metricsEngine.On("RecordStoredImpCacheResult", pbsmetrics.CacheHit, 2)
@@ -141,7 +150,7 @@ func TestCacheSaves(t *testing.T) {
 
 	_, impData, errs := aFetcherWithCache.FetchRequests(ctx, nil, []string{"abc", "abc"})
 
-	cache.AssertExpectations(t)
+	impCache.AssertExpectations(t)
 	fetcher.AssertExpectations(t)
 	metricsEngine.AssertExpectations(t)
 	assert.Len(t, impData, 1, "FetchRequests should return data only once for duplicate requests")
@@ -154,38 +163,34 @@ func TestComposedCache(t *testing.T) {
 	c2 := &mockCache{}
 	c3 := &mockCache{}
 	c4 := &mockCache{}
-	cache := ComposedCache{c1, c2, c3, c4}
+	impCache := &mockCache{}
+	cache := Cache{
+		Requests: ComposedCache{c1, c2, c3, c4},
+		Imps:     impCache,
+	}
 	metricsEngine := &pbsmetrics.MetricsEngineMock{}
 	fetcher := &mockFetcher{}
 	aFetcherWithCache := WithCache(fetcher, cache, metricsEngine)
-	impIDs := []string{"1", "2", "3"}
 	reqIDs := []string{"1", "2", "3"}
+	impIDs := []string{}
 	ctx := context.Background()
 
-	c1.On("Get", ctx, reqIDs, impIDs).Return(
-		map[string]json.RawMessage{
-			"1": json.RawMessage(`{"id": "1"}`),
-		},
+	c1.On("Get", ctx, reqIDs).Return(
 		map[string]json.RawMessage{
 			"1": json.RawMessage(`{"id": "1"}`),
 		})
-	c2.On("Get", ctx, []string{"2", "3"}, []string{"2", "3"}).Return(
-		map[string]json.RawMessage{
-			"2": json.RawMessage(`{"id": "2"}`),
-		},
+	c2.On("Get", ctx, []string{"2", "3"}).Return(
 		map[string]json.RawMessage{
 			"2": json.RawMessage(`{"id": "2"}`),
 		})
-	c3.On("Get", ctx, []string{"3"}, []string{"3"}).Return(
-		map[string]json.RawMessage{
-			"3": json.RawMessage(`{"id": "3"}`),
-		},
+	c3.On("Get", ctx, []string{"3"}).Return(
 		map[string]json.RawMessage{
 			"3": json.RawMessage(`{"id": "3"}`),
 		})
+	impCache.On("Get", ctx, []string{}).Return(map[string]json.RawMessage{})
 	metricsEngine.On("RecordStoredReqCacheResult", pbsmetrics.CacheHit, 3)
 	metricsEngine.On("RecordStoredReqCacheResult", pbsmetrics.CacheMiss, 0)
-	metricsEngine.On("RecordStoredImpCacheResult", pbsmetrics.CacheHit, 3)
+	metricsEngine.On("RecordStoredImpCacheResult", pbsmetrics.CacheHit, 0)
 	metricsEngine.On("RecordStoredImpCacheResult", pbsmetrics.CacheMiss, 0)
 
 	reqData, impData, errs := aFetcherWithCache.FetchRequests(ctx, reqIDs, impIDs)
@@ -193,14 +198,12 @@ func TestComposedCache(t *testing.T) {
 	c1.AssertExpectations(t)
 	c2.AssertExpectations(t)
 	c3.AssertExpectations(t)
+	impCache.AssertExpectations(t)
 	fetcher.AssertExpectations(t)
 	metricsEngine.AssertExpectations(t)
 	assert.Len(t, reqData, len(reqIDs), "FetchRequests should be able to return all request data from a composed cache")
 	assert.Len(t, impData, len(impIDs), "FetchRequests should be able to return all imp data from a composed cache")
 	assert.Len(t, errs, 0, "FetchRequests shouldn't return an error when trying to use a composed cache")
-	assert.JSONEq(t, `{"id": "1"}`, string(impData["1"]), "FetchRequests should fetch the right imp data")
-	assert.JSONEq(t, `{"id": "2"}`, string(impData["2"]), "FetchRequests should fetch the right imp data")
-	assert.JSONEq(t, `{"id": "3"}`, string(impData["3"]), "FetchRequests should fetch the right imp data")
 	assert.JSONEq(t, `{"id": "1"}`, string(reqData["1"]), "FetchRequests should fetch the right req data")
 	assert.JSONEq(t, `{"id": "2"}`, string(reqData["2"]), "FetchRequests should fetch the right req data")
 	assert.JSONEq(t, `{"id": "3"}`, string(reqData["3"]), "FetchRequests should fetch the right req data")
@@ -228,15 +231,15 @@ type mockCache struct {
 	mock.Mock
 }
 
-func (c *mockCache) Get(ctx context.Context, requestIDs []string, impIDs []string) (map[string]json.RawMessage, map[string]json.RawMessage) {
-	args := c.Called(ctx, requestIDs, impIDs)
-	return args.Get(0).(map[string]json.RawMessage), args.Get(1).(map[string]json.RawMessage)
+func (c *mockCache) Get(ctx context.Context, ids []string) map[string]json.RawMessage {
+	args := c.Called(ctx, ids)
+	return args.Get(0).(map[string]json.RawMessage)
 }
 
-func (c *mockCache) Save(ctx context.Context, storedRequests map[string]json.RawMessage, storedImps map[string]json.RawMessage) {
-	c.Called(ctx, storedRequests, storedImps)
+func (c *mockCache) Save(ctx context.Context, data map[string]json.RawMessage) {
+	c.Called(ctx, data)
 }
 
-func (c *mockCache) Invalidate(ctx context.Context, requestIDs []string, impIDs []string) {
-	c.Called(ctx, requestIDs, impIDs)
+func (c *mockCache) Invalidate(ctx context.Context, ids []string) {
+	c.Called(ctx, ids)
 }
diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go
index 9197ed95..bd250489 100755
--- a/usersync/usersyncers/syncer_test.go
+++ b/usersync/usersyncers/syncer_test.go
@@ -31,6 +31,7 @@ func TestNewSyncerMap(t *testing.T) {
 			string(openrtb_ext.BidderBeachfront):       syncConfig,
 			string(openrtb_ext.BidderBeintoo):          syncConfig,
 			string(openrtb_ext.BidderBrightroll):       syncConfig,
+			string(openrtb_ext.BidderColossus):         syncConfig,
 			string(openrtb_ext.BidderConsumable):       syncConfig,
 			string(openrtb_ext.BidderConversant):       syncConfig,
 			string(openrtb_ext.BidderCpmstar):          syncConfig,
@@ -88,6 +89,7 @@ func TestNewSyncerMap(t *testing.T) {
 		openrtb_ext.BidderAdhese:       true,
 		openrtb_ext.BidderAdoppler:     true,
 		openrtb_ext.BidderApplogy:      true,
+		openrtb_ext.BidderInMobi:       true,
 		openrtb_ext.BidderKidoz:        true,
 		openrtb_ext.BidderKubient:      true,
 		openrtb_ext.BidderMobileFuse:   true,