Repository: prebid/prebid-server
Pull request #2278 (2022-06-21 11:12:51)

New Adapter: InfyTV

mansinahar: @infytvcode Looks like there's a test failure due to URL mismatch in one of your test cases. Can you please fix this when you have a chance?

Patch:
diff --git a/adapters/bidder.go b/adapters/bidder.go
index e6022777..a54b8ff2 100644
--- a/adapters/bidder.go
+++ b/adapters/bidder.go
@@ -93,12 +93,14 @@ func NewBidderResponse() *BidderResponse {
 // TypedBid.BidType will become "response.seatbid[i].bid.ext.prebid.type" in the final OpenRTB response.
 // TypedBid.BidVideo will become "response.seatbid[i].bid.ext.prebid.video" in the final OpenRTB response.
 // TypedBid.DealPriority is optionally provided by adapters and used internally by the exchange to support deal targeted campaigns.
+// TypedBid.Seat new seat under which the bid should pe placed. Default is adapter name
 type TypedBid struct {
 	Bid          *openrtb2.Bid
 	BidMeta      *openrtb_ext.ExtBidPrebidMeta
 	BidType      openrtb_ext.BidType
 	BidVideo     *openrtb_ext.ExtBidPrebidVideo
 	DealPriority int
+	Seat         openrtb_ext.BidderName
 }
 
 // RequestData and ResponseData exist so that prebid-server core code can implement its "debug" functionality
diff --git a/adapters/infytv/infytv.go b/adapters/infytv/infytv.go
new file mode 100644
index 00000000..af41958b
--- /dev/null
+++ b/adapters/infytv/infytv.go
@@ -0,0 +1,89 @@
+package infytv
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+
+	"github.com/mxmCherry/openrtb/v15/openrtb2"
+	"github.com/prebid/prebid-server/adapters"
+	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/errortypes"
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+type adapter struct {
+	endpoint string
+}
+
+// Builder builds a new instance of the InfyTV adapter for the given bidder with the given config.
+func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) {
+	bidder := &adapter{
+		endpoint: config.Endpoint,
+	}
+	return bidder, nil
+}
+
+func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+	requestJSON, err := json.Marshal(request)
+	if err != nil {
+		return nil, []error{err}
+	}
+
+	requestData := &adapters.RequestData{
+		Method: "POST",
+		Uri:    a.endpoint,
+		Body:   requestJSON,
+	}
+
+	return []*adapters.RequestData{requestData}, nil
+}
+
+func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+
+	if responseData.StatusCode == http.StatusNoContent {
+		return nil, nil
+	}
+
+	if responseData.StatusCode == http.StatusBadRequest {
+		return nil, []error{&errortypes.BadInput{
+			Message: fmt.Sprintf("Bad Request. %s", string(responseData.Body)),
+		}}
+	}
+
+	if responseData.StatusCode == http.StatusServiceUnavailable {
+		return nil, nil
+	}
+
+	if responseData.StatusCode != http.StatusOK {
+		return nil, []error{&errortypes.BadServerResponse{
+			Message: fmt.Sprintf("Something went wrong, please contact your Account Manager. Status Code: [ %d ] ", responseData.StatusCode),
+		}}
+	}
+
+	var response openrtb2.BidResponse
+	if err := json.Unmarshal(responseData.Body, &response); err != nil {
+		return nil, []error{&errortypes.BadServerResponse{
+			Message: fmt.Sprintf("Bad response, %s", err),
+		}}
+	}
+
+	if len(response.SeatBid) == 0 {
+		return nil, []error{&errortypes.BadServerResponse{
+			Message: "Empty seatbid",
+		}}
+	}
+
+	bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp))
+	bidResponse.Currency = response.Cur
+	for _, seatBid := range response.SeatBid {
+		for i := range seatBid.Bid {
+			b := &adapters.TypedBid{
+				Bid:     &seatBid.Bid[i],
+				BidType: openrtb_ext.BidTypeVideo,
+			}
+			bidResponse.Bids = append(bidResponse.Bids, b)
+		}
+	}
+	return bidResponse, nil
+}
diff --git a/adapters/pubmatic/pubmatic.go b/adapters/pubmatic/pubmatic.go
index 00c9c906..a8cb6b7d 100644
--- a/adapters/pubmatic/pubmatic.go
+++ b/adapters/pubmatic/pubmatic.go
@@ -24,6 +24,7 @@ type PubmaticAdapter struct {
 type pubmaticBidExt struct {
 	BidType           *int                 `json:"BidType,omitempty"`
 	VideoCreativeInfo *pubmaticBidExtVideo `json:"video,omitempty"`
+	Marketplace       string               `json:"marketplace,omitempty"`
 }
 
 type pubmaticWrapperExt struct {
@@ -390,9 +391,11 @@ func (a *PubmaticAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externa
 				bid.Cat = bid.Cat[0:1]
 			}
 
+			seat := ""
 			var bidExt *pubmaticBidExt
 			bidType := openrtb_ext.BidTypeBanner
 			if err := json.Unmarshal(bid.Ext, &bidExt); err == nil && bidExt != nil {
+				seat = bidExt.Marketplace
 				if bidExt.VideoCreativeInfo != nil && bidExt.VideoCreativeInfo.Duration != nil {
 					impVideo.Duration = *bidExt.VideoCreativeInfo.Duration
 				}
@@ -402,6 +405,7 @@ func (a *PubmaticAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externa
 				Bid:      &bid,
 				BidType:  bidType,
 				BidVideo: impVideo,
+				Seat:     openrtb_ext.BidderName(seat),
 			})
 
 		}
diff --git a/adapters/unicorn/unicorn.go b/adapters/unicorn/unicorn.go
index 75de6e53..069604c7 100644
--- a/adapters/unicorn/unicorn.go
+++ b/adapters/unicorn/unicorn.go
@@ -64,14 +64,6 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapte
 		}
 	}
 
-	if len(request.Cur) == 0 {
-		request.Cur = []string{"JPY"}
-	} else if isOtherThanJPY(request.Cur) {
-		return nil, []error{&errortypes.BadInput{
-			Message: "Only JPY is supported",
-		}}
-	}
-
 	err := modifyImps(request)
 	if err != nil {
 		return nil, []error{err}
@@ -106,15 +98,6 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapte
 	return []*adapters.RequestData{requestData}, nil
 }
 
-func isOtherThanJPY(cur []string) bool {
-	for _, c := range cur {
-		if c != "JPY" {
-			return true
-		}
-	}
-	return false
-}
-
 func getHeaders(request *openrtb2.BidRequest) http.Header {
 	headers := http.Header{}
 	headers.Add("Content-Type", "application/json;charset=utf-8")
@@ -191,8 +174,9 @@ func setSourceExt() json.RawMessage {
 func setExt(request *openrtb2.BidRequest) (json.RawMessage, error) {
 	accountID, err := jsonparser.GetInt(request.Imp[0].Ext, "bidder", "accountId")
 	if err != nil {
-		accountID = 0
+		return nil, fmt.Errorf("accountId field is required")
 	}
+
 	var decodedExt *unicornExt
 	err = json.Unmarshal(request.Ext, &decodedExt)
 	if err != nil {
diff --git a/adapters/visx/visx.go b/adapters/visx/visx.go
index 53277ff8..3c55cd65 100644
--- a/adapters/visx/visx.go
+++ b/adapters/visx/visx.go
@@ -69,6 +69,16 @@ func (a *VisxAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapte
 	headers := http.Header{}
 	headers.Add("Content-Type", "application/json;charset=utf-8")
 
+	if request.Device != nil {
+		if request.Device.IP != "" {
+			headers.Add("X-Forwarded-For", request.Device.IP)
+		}
+
+		if request.Device.IPv6 != "" {
+			headers.Add("X-Forwarded-For", request.Device.IPv6)
+		}
+	}
+
 	return []*adapters.RequestData{{
 		Method:  "POST",
 		Uri:     a.endpoint,
diff --git a/config/accounts.go b/config/accounts.go
index 14e52180..8b9c0222 100644
--- a/config/accounts.go
+++ b/config/accounts.go
@@ -1,6 +1,8 @@
 package config
 
 import (
+	"fmt"
+
 	"github.com/prebid/go-gdpr/consentconstants"
 	"github.com/prebid/prebid-server/openrtb_ext"
 )
@@ -18,17 +20,18 @@ const (
 
 // Account represents a publisher account configuration
 type Account struct {
-	ID                      string      `mapstructure:"id" json:"id"`
-	Disabled                bool        `mapstructure:"disabled" json:"disabled"`
-	CacheTTL                DefaultTTLs `mapstructure:"cache_ttl" json:"cache_ttl"`
-	EventsEnabled           bool        `mapstructure:"events_enabled" json:"events_enabled"`
-	CCPA                    AccountCCPA `mapstructure:"ccpa" json:"ccpa"`
-	GDPR                    AccountGDPR `mapstructure:"gdpr" json:"gdpr"`
-	DebugAllow              bool        `mapstructure:"debug_allow" json:"debug_allow"`
-	DefaultIntegration      string      `mapstructure:"default_integration" json:"default_integration"`
-	CookieSync              CookieSync  `mapstructure:"cookie_sync" json:"cookie_sync"`
-	Events                  Events      `mapstructure:"events" json:"events"` // Don't enable this feature. It is still under developmment - https://github.com/prebid/prebid-server/issues/1725
-	TruncateTargetAttribute *int        `mapstructure:"truncate_target_attr" json:"truncate_target_attr"`
+	ID                      string               `mapstructure:"id" json:"id"`
+	Disabled                bool                 `mapstructure:"disabled" json:"disabled"`
+	CacheTTL                DefaultTTLs          `mapstructure:"cache_ttl" json:"cache_ttl"`
+	EventsEnabled           bool                 `mapstructure:"events_enabled" json:"events_enabled"`
+	CCPA                    AccountCCPA          `mapstructure:"ccpa" json:"ccpa"`
+	GDPR                    AccountGDPR          `mapstructure:"gdpr" json:"gdpr"`
+	DebugAllow              bool                 `mapstructure:"debug_allow" json:"debug_allow"`
+	DefaultIntegration      string               `mapstructure:"default_integration" json:"default_integration"`
+	CookieSync              CookieSync           `mapstructure:"cookie_sync" json:"cookie_sync"`
+	Events                  Events               `mapstructure:"events" json:"events"` // Don't enable this feature. It is still under developmment - https://github.com/prebid/prebid-server/issues/1725
+	TruncateTargetAttribute *int                 `mapstructure:"truncate_target_attr" json:"truncate_target_attr"`
+	AlternateBidderCodes    AlternateBidderCodes `mapstructure:"alternatebiddercodes" json:"alternatebiddercodes"`
 }
 
 // CookieSync represents the account-level defaults for the cookie sync endpoint.
@@ -222,3 +225,49 @@ func (a *AccountIntegration) GetByIntegrationType(integrationType IntegrationTyp
 
 	return integrationEnabled
 }
+
+type AlternateBidderCodes struct {
+	Enabled  bool                                   `mapstructure:"enabled" json:"enabled"`
+	Adapters map[string]AdapterAlternateBidderCodes `mapstructure:"adapters" json:"adapters"`
+}
+
+type AdapterAlternateBidderCodes struct {
+	AllowedBidderCodes []string `mapstructure:"allowedbiddercodes" json:"allowedbiddercodes"`
+}
+
+func (bidderCodes *AlternateBidderCodes) IsValidBidderCode(bidder, alternateBidder string) (bool, error) {
+	const ErrAlternateBidderNotDefined = "alternateBidderCodes not defined for adapter %q, rejecting bids for %q"
+
+	if alternateBidder == "" || bidder == alternateBidder {
+		return true, nil
+	}
+
+	if !bidderCodes.Enabled {
+		return false, nil
+	}
+
+	if bidderCodes.Adapters == nil {
+		return false, fmt.Errorf(ErrAlternateBidderNotDefined, bidder, alternateBidder)
+	}
+
+	adapterCfg, ok := bidderCodes.Adapters[bidder]
+	if !ok {
+		return false, fmt.Errorf(ErrAlternateBidderNotDefined, bidder, alternateBidder)
+	}
+
+	if len(adapterCfg.AllowedBidderCodes) == 0 {
+		return false, fmt.Errorf("alternateBidderCodes disabled for %q, rejecting bids for %q", bidder, alternateBidder)
+	}
+
+	if adapterCfg.AllowedBidderCodes[0] == "*" {
+		return true, nil
+	}
+
+	for _, code := range adapterCfg.AllowedBidderCodes {
+		if alternateBidder == code {
+			return true, nil
+		}
+	}
+
+	return false, fmt.Errorf("invalid biddercode %q sent by adapter %q", alternateBidder, bidder)
+}
diff --git a/config/config.go b/config/config.go
index 2f1515d4..dbe2ce4c 100644
--- a/config/config.go
+++ b/config/config.go
@@ -994,7 +994,7 @@ func SetupViper(v *viper.Viper, filename string) {
 	v.SetDefault("adapters.sa_lunamedia.endpoint", "http://balancer.lmgssp.com/pserver")
 	v.SetDefault("adapters.madvertise.endpoint", "https://mobile.mng-ads.com/bidrequest{{.ZoneID}}")
 	v.SetDefault("adapters.marsmedia.endpoint", "https://bid306.rtbsrv.com/bidder/?bid=f3xtet")
-	v.SetDefault("adapters.mediafuse.endpoint", "http://ghb.hbmp.mediafuse.com/pbs/ortb")
+	v.SetDefault("adapters.mediafuse.endpoint", "http://ib.adnxs.com/openrtb2")
 	v.SetDefault("adapters.medianet.endpoint", "https://prebid-adapter.media.net/rtb/pb/prebids2s")
 	v.SetDefault("adapters.medianet.extra_info", "https://medianet.golang.pbs.com")
 	v.SetDefault("adapters.mgid.endpoint", "https://prebid.mgid.com/prebid/")
@@ -1061,6 +1061,7 @@ func SetupViper(v *viper.Viper, filename string) {
 	v.SetDefault("adapters.yieldone.endpoint", "https://y.one.impact-ad.jp/hbs_imp")
 	v.SetDefault("adapters.yssp.disabled", true)
 	v.SetDefault("adapters.zeroclickfraud.endpoint", "http://{{.Host}}/openrtb2?sid={{.SourceId}}")
+	v.SetDefault("adapters.infytv.endpoint", "https://nxs.infy.tv/pbs/openrtb")
 
 	v.SetDefault("max_request_size", 1024*256)
 	v.SetDefault("analytics.file.filename", "")
diff --git a/endpoints/openrtb2/interstitial.go b/endpoints/openrtb2/interstitial.go
index 359bae11..4fd2a90c 100644
--- a/endpoints/openrtb2/interstitial.go
+++ b/endpoints/openrtb2/interstitial.go
@@ -20,11 +20,12 @@ func processInterstitials(req *openrtb_ext.RequestWrapper) error {
 					return nil
 				}
 				deviceExt, err := req.GetDeviceExt()
+
 				if err != nil {
 					return err
 				}
 				prebid = deviceExt.GetPrebid()
-				if prebid.Interstitial == nil {
+				if prebid == nil || prebid.Interstitial == nil {
 					// No special interstitial support requested, so bail as there is nothing to do
 					return nil
 				}
diff --git a/errortypes/code.go b/errortypes/code.go
index 869e7d54..3974e8bd 100644
--- a/errortypes/code.go
+++ b/errortypes/code.go
@@ -21,6 +21,7 @@ const (
 	AccountLevelDebugDisabledWarningCode
 	BidderLevelDebugDisabledWarningCode
 	DisabledCurrencyConversionWarningCode
+	AlternateBidderCodeWarningCode
 )
 
 // Coder provides an error or warning code with severity.
diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go
index 5db9c884..fc27b617 100755
--- a/exchange/adapter_builders.go
+++ b/exchange/adapter_builders.go
@@ -69,6 +69,7 @@ import (
 	"github.com/prebid/prebid-server/adapters/huaweiads"
 	"github.com/prebid/prebid-server/adapters/impactify"
 	"github.com/prebid/prebid-server/adapters/improvedigital"
+	"github.com/prebid/prebid-server/adapters/infytv"
 	"github.com/prebid/prebid-server/adapters/inmobi"
 	"github.com/prebid/prebid-server/adapters/interactiveoffers"
 	"github.com/prebid/prebid-server/adapters/invibes"
@@ -232,7 +233,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder {
 		openrtb_ext.BidderSaLunaMedia:       salunamedia.Builder,
 		openrtb_ext.BidderMadvertise:        madvertise.Builder,
 		openrtb_ext.BidderMarsmedia:         marsmedia.Builder,
-		openrtb_ext.BidderMediafuse:         adtelligent.Builder,
+		openrtb_ext.BidderMediafuse:         appnexus.Builder,
 		openrtb_ext.BidderMedianet:          medianet.Builder,
 		openrtb_ext.BidderMgid:              mgid.Builder,
 		openrtb_ext.BidderMobfoxpb:          mobfoxpb.Builder,
@@ -294,5 +295,6 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder {
 		openrtb_ext.BidderYieldone:          yieldone.Builder,
 		openrtb_ext.BidderYSSP:              yahoossp.Builder,
 		openrtb_ext.BidderZeroClickFraud:    zeroclickfraud.Builder,
+		openrtb_ext.BidderInfyTV:            infytv.Builder,
 	}
 }
diff --git a/exchange/bidder.go b/exchange/bidder.go
index 74a23998..2f673d2e 100644
--- a/exchange/bidder.go
+++ b/exchange/bidder.go
@@ -51,7 +51,7 @@ type AdaptedBidder interface {
 	//
 	// Any errors will be user-facing in the API.
 	// Error messages should help publishers understand what might account for "bad" bids.
-	requestBid(ctx context.Context, bidderRequest BidderRequest, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) (*pbsOrtbSeatBid, []error)
+	requestBid(ctx context.Context, bidderRequest BidderRequest, bidAdjustments map[string]float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool, alternateBidderCodes config.AlternateBidderCodes) ([]*pbsOrtbSeatBid, []error)
 }
 
 const ImpIdReqBody = "Stored bid response for impression id: "
@@ -93,6 +93,8 @@ type pbsOrtbSeatBid struct {
 	// httpCalls is the list of debugging info. It should only be populated if the request.test == 1.
 	// This will become response.ext.debug.httpcalls.{bidder} on the final Response.
 	httpCalls []*openrtb_ext.ExtHttpCall
+	// seat defines whom these extra bids belong to.
+	seat string
 }
 
 // AdaptBidder converts an adapters.Bidder into an exchange.AdaptedBidder.
@@ -134,7 +136,7 @@ type bidderAdapterConfig struct {
 	DebugInfo          config.DebugInfo
 }
 
-func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest BidderRequest, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) (*pbsOrtbSeatBid, []error) {
+func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest BidderRequest, bidAdjustments map[string]float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool, alternateBidderCodes config.AlternateBidderCodes) ([]*pbsOrtbSeatBid, []error) {
 
 	var reqData []*adapters.RequestData
 	var errs []error
@@ -193,10 +195,13 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde
 	}
 
 	defaultCurrency := "USD"
-	seatBid := &pbsOrtbSeatBid{
-		bids:      make([]*pbsOrtbBid, 0, dataLen),
-		currency:  defaultCurrency,
-		httpCalls: make([]*openrtb_ext.ExtHttpCall, 0, dataLen),
+	seatBidMap := map[openrtb_ext.BidderName]*pbsOrtbSeatBid{
+		bidderRequest.BidderName: {
+			bids:      make([]*pbsOrtbBid, 0, dataLen),
+			currency:  defaultCurrency,
+			httpCalls: make([]*openrtb_ext.ExtHttpCall, 0, dataLen),
+			seat:      string(bidderRequest.BidderName),
+		},
 	}
 
 	// If the bidder made multiple requests, we still want them to enter as many bids as possible...
@@ -209,11 +214,11 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde
 		// - account debug is allowed
 		// - bidder debug is allowed
 		if headerDebugAllowed {
-			seatBid.httpCalls = append(seatBid.httpCalls, makeExt(httpInfo))
+			seatBidMap[bidderRequest.BidderName].httpCalls = append(seatBidMap[bidderRequest.BidderName].httpCalls, makeExt(httpInfo))
 		} else {
 			if accountDebugAllowed {
 				if bidder.config.DebugInfo.Allow {
-					seatBid.httpCalls = append(seatBid.httpCalls, makeExt(httpInfo))
+					seatBidMap[bidderRequest.BidderName].httpCalls = append(seatBidMap[bidderRequest.BidderName].httpCalls, makeExt(httpInfo))
 				} else {
 					debugDisabledWarning := errortypes.Warning{
 						WarningCode: errortypes.BidderLevelDebugDisabledWarningCode,
@@ -244,7 +249,7 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde
 				var err error
 				for _, bidReqCur := range bidderRequest.BidRequest.Cur {
 					if conversionRate, err = conversions.GetRate(bidResponse.Currency, bidReqCur); err == nil {
-						seatBid.currency = bidReqCur
+						seatBidMap[bidderRequest.BidderName].currency = bidReqCur
 						break
 					}
 				}
@@ -283,12 +288,52 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde
 				if err == nil {
 					// Conversion rate found, using it for conversion
 					for i := 0; i < len(bidResponse.Bids); i++ {
+						if bidResponse.Bids[i].BidMeta == nil {
+							bidResponse.Bids[i].BidMeta = &openrtb_ext.ExtBidPrebidMeta{}
+						}
+						bidResponse.Bids[i].BidMeta.AdapterCode = bidderRequest.BidderName.String()
+
+						bidderName := bidderRequest.BidderName
+						if bidResponse.Bids[i].Seat != "" {
+							bidderName = bidResponse.Bids[i].Seat
+						}
+
+						if valid, err := alternateBidderCodes.IsValidBidderCode(bidderRequest.BidderName.String(), bidderName.String()); !valid {
+							if err != nil {
+								err = &errortypes.Warning{
+									WarningCode: errortypes.AlternateBidderCodeWarningCode,
+									Message:     err.Error(),
+								}
+								errs = append(errs, err)
+							}
+							continue
+						}
+
+						adjustmentFactor := 1.0
+						if givenAdjustment, ok := bidAdjustments[bidderName.String()]; ok {
+							adjustmentFactor = givenAdjustment
+						} else if givenAdjustment, ok := bidAdjustments[bidderRequest.BidderName.String()]; ok {
+							adjustmentFactor = givenAdjustment
+						}
+
 						originalBidCpm := 0.0
 						if bidResponse.Bids[i].Bid != nil {
 							originalBidCpm = bidResponse.Bids[i].Bid.Price
-							bidResponse.Bids[i].Bid.Price = bidResponse.Bids[i].Bid.Price * bidAdjustment * conversionRate
+							bidResponse.Bids[i].Bid.Price = bidResponse.Bids[i].Bid.Price * adjustmentFactor * conversionRate
+						}
+
+						if _, ok := seatBidMap[bidderName]; !ok {
+							// Initalize seatBidMap entry as this is first extra bid with seat bidderName
+							seatBidMap[bidderName] = &pbsOrtbSeatBid{
+								bids:     make([]*pbsOrtbBid, 0, dataLen),
+								currency: defaultCurrency,
+								// Do we need to fill httpCalls for this?. Can we refer one from adaptercode for debugging?
+								httpCalls: seatBidMap[bidderRequest.BidderName].httpCalls,
+								seat:      bidderName.String(),
+							}
 						}
-						seatBid.bids = append(seatBid.bids, &pbsOrtbBid{
+
+						seatBidMap[bidderName].bids = append(seatBidMap[bidderName].bids, &pbsOrtbBid{
 							bid:            bidResponse.Bids[i].Bid,
 							bidMeta:        bidResponse.Bids[i].BidMeta,
 							bidType:        bidResponse.Bids[i].BidType,
@@ -307,7 +352,13 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde
 			errs = append(errs, httpInfo.err)
 		}
 	}
-	return seatBid, errs
+
+	seatBids := make([]*pbsOrtbSeatBid, 0, len(seatBidMap))
+	for _, seatBid := range seatBidMap {
+		seatBids = append(seatBids, seatBid)
+	}
+
+	return seatBids, errs
 }
 
 func addNativeTypes(bid *openrtb2.Bid, request *openrtb2.BidRequest) (*nativeResponse.Response, []error) {
diff --git a/exchange/bidder_validate_bids.go b/exchange/bidder_validate_bids.go
index ab1d3904..cc312976 100644
--- a/exchange/bidder_validate_bids.go
+++ b/exchange/bidder_validate_bids.go
@@ -8,6 +8,7 @@ import (
 
 	"github.com/mxmCherry/openrtb/v15/openrtb2"
 	"github.com/prebid/prebid-server/adapters"
+	"github.com/prebid/prebid-server/config"
 	"github.com/prebid/prebid-server/currency"
 	goCurrency "golang.org/x/text/currency"
 )
@@ -27,12 +28,14 @@ type validatedBidder struct {
 	bidder AdaptedBidder
 }
 
-func (v *validatedBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) (*pbsOrtbSeatBid, []error) {
-	seatBid, errs := v.bidder.requestBid(ctx, bidderRequest, bidAdjustment, conversions, reqInfo, accountDebugAllowed, headerDebugAllowed)
-	if validationErrors := removeInvalidBids(bidderRequest.BidRequest, seatBid); len(validationErrors) > 0 {
-		errs = append(errs, validationErrors...)
+func (v *validatedBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, bidAdjustments map[string]float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool, alternateBidderCodes config.AlternateBidderCodes) ([]*pbsOrtbSeatBid, []error) {
+	seatBids, errs := v.bidder.requestBid(ctx, bidderRequest, bidAdjustments, conversions, reqInfo, accountDebugAllowed, headerDebugAllowed, alternateBidderCodes)
+	for _, seatBid := range seatBids {
+		if validationErrors := removeInvalidBids(bidderRequest.BidRequest, seatBid); len(validationErrors) > 0 {
+			errs = append(errs, validationErrors...)
+		}
 	}
-	return seatBid, errs
+	return seatBids, errs
 }
 
 // validateBids will run some validation checks on the returned bids and excise any invalid bids
diff --git a/exchange/exchange.go b/exchange/exchange.go
index edbad99d..25888071 100644
--- a/exchange/exchange.go
+++ b/exchange/exchange.go
@@ -78,9 +78,9 @@ type seatResponseExtra struct {
 }
 
 type bidResponseWrapper struct {
-	adapterBids  *pbsOrtbSeatBid
-	adapterExtra *seatResponseExtra
-	bidder       openrtb_ext.BidderName
+	adapterSeatBids []*pbsOrtbSeatBid
+	adapterExtra    *seatResponseExtra
+	bidder          openrtb_ext.BidderName
 }
 
 type BidIDGenerator interface {
@@ -273,7 +273,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog *
 	} else {
 		// List of bidders we have requests for.
 		liveAdapters = listBiddersWithRequests(bidderRequests)
-		adapterBids, adapterExtra, anyBidsReturned = e.getAllBids(auctionCtx, bidderRequests, bidAdjustmentFactors, conversions, accountDebugAllow, r.GlobalPrivacyControlHeader, debugLog.DebugOverride)
+		adapterBids, adapterExtra, anyBidsReturned = e.getAllBids(auctionCtx, bidderRequests, bidAdjustmentFactors, conversions, accountDebugAllow, r.GlobalPrivacyControlHeader, debugLog.DebugOverride, r.Account.AlternateBidderCodes)
 	}
 
 	var auc *auction
@@ -480,7 +480,8 @@ func (e *exchange) getAllBids(
 	conversions currency.Conversions,
 	accountDebugAllowed bool,
 	globalPrivacyControlHeader string,
-	headerDebugAllowed bool) (
+	headerDebugAllowed bool,
+	alternateBidderCodes config.AlternateBidderCodes) (
 	map[openrtb_ext.BidderName]*pbsOrtbSeatBid,
 	map[openrtb_ext.BidderName]*seatResponseExtra, bool) {
 	// Set up pointers to the bid results
@@ -505,39 +506,37 @@ func (e *exchange) getAllBids(
 			}()
 			start := time.Now()
 
-			adjustmentFactor := 1.0
-			if givenAdjustment, ok := bidAdjustments[string(bidderRequest.BidderName)]; ok {
-				adjustmentFactor = givenAdjustment
-			}
 			reqInfo := adapters.NewExtraRequestInfo(conversions)
 			reqInfo.PbsEntryPoint = bidderRequest.BidderLabels.RType
 			reqInfo.GlobalPrivacyControlHeader = globalPrivacyControlHeader
 
-			bids, err := e.adapterMap[bidderRequest.BidderCoreName].requestBid(ctx, bidderRequest, adjustmentFactor, conversions, &reqInfo, accountDebugAllowed, headerDebugAllowed)
+			seatBids, err := e.adapterMap[bidderRequest.BidderCoreName].requestBid(ctx, bidderRequest, bidAdjustments, conversions, &reqInfo, accountDebugAllowed, headerDebugAllowed, alternateBidderCodes)
 
 			// Add in time reporting
 			elapsed := time.Since(start)
-			brw.adapterBids = bids
+			brw.adapterSeatBids = seatBids
 			// Structure to record extra tracking data generated during bidding
 			ae := new(seatResponseExtra)
 			ae.ResponseTimeMillis = int(elapsed / time.Millisecond)
-			if bids != nil {
-				ae.HttpCalls = bids.httpCalls
+			if len(seatBids) != 0 {
+				ae.HttpCalls = seatBids[0].httpCalls
 			}
 
 			// Timing statistics
 			e.me.RecordAdapterTime(bidderRequest.BidderLabels, time.Since(start))
-			bidderRequest.BidderLabels.AdapterBids = bidsToMetric(brw.adapterBids)
+			bidderRequest.BidderLabels.AdapterBids = bidsToMetric(brw.adapterSeatBids)
 			bidderRequest.BidderLabels.AdapterErrors = errorsToMetric(err)
 			// Append any bid validation errors to the error list
 			ae.Errors = errsToBidderErrors(err)
 			ae.Warnings = errsToBidderWarnings(err)
 			brw.adapterExtra = ae
-			if bids != nil {
-				for _, bid := range bids.bids {
-					var cpm = float64(bid.bid.Price * 1000)
-					e.me.RecordAdapterPrice(bidderRequest.BidderLabels, cpm)
-					e.me.RecordAdapterBidReceived(bidderRequest.BidderLabels, bid.bidType, bid.bid.AdM != "")
+			for _, seatBid := range seatBids {
+				if seatBid != nil {
+					for _, bid := range seatBid.bids {
+						var cpm = float64(bid.bid.Price * 1000)
+						e.me.RecordAdapterPrice(bidderRequest.BidderLabels, cpm)
+						e.me.RecordAdapterBidReceived(bidderRequest.BidderLabels, bid.bidType, bid.bid.AdM != "")
+					}
 				}
 			}
 			chBids <- brw
@@ -549,8 +548,14 @@ func (e *exchange) getAllBids(
 		brw := <-chBids
 
 		//if bidder returned no bids back - remove bidder from further processing
-		if brw.adapterBids != nil && len(brw.adapterBids.bids) != 0 {
-			adapterBids[brw.bidder] = brw.adapterBids
+		for _, seatBid := range brw.adapterSeatBids {
+			if seatBid != nil && len(seatBid.bids) != 0 {
+				if _, ok := adapterBids[openrtb_ext.BidderName(seatBid.seat)]; ok {
+					adapterBids[openrtb_ext.BidderName(seatBid.seat)].bids = append(adapterBids[openrtb_ext.BidderName(seatBid.seat)].bids, seatBid.bids...)
+				} else {
+					adapterBids[openrtb_ext.BidderName(seatBid.seat)] = seatBid
+				}
+			}
 		}
 		//but we need to add all bidders data to adapterExtra to have metrics and other metadata
 		adapterExtra[brw.bidder] = brw.adapterExtra
@@ -594,11 +599,13 @@ func (e *exchange) recoverSafely(bidderRequests []BidderRequest,
 	}
 }
 
-func bidsToMetric(bids *pbsOrtbSeatBid) metrics.AdapterBid {
-	if bids == nil || len(bids.bids) == 0 {
-		return metrics.AdapterBidNone
+func bidsToMetric(seatBids []*pbsOrtbSeatBid) metrics.AdapterBid {
+	for _, seatBid := range seatBids {
+		if seatBid != nil && len(seatBid.bids) != 0 {
+			return metrics.AdapterBidPresent
+		}
 	}
-	return metrics.AdapterBidPresent
+	return metrics.AdapterBidNone
 }
 
 func errorsToMetric(errs []error) map[metrics.AdapterError]struct{} {
@@ -617,6 +624,8 @@ func errorsToMetric(errs []error) map[metrics.AdapterError]struct{} {
 			ret[metrics.AdapterErrorBadServerResponse] = s
 		case errortypes.FailedToRequestBidsErrorCode:
 			ret[metrics.AdapterErrorFailedToRequestBids] = s
+		case errortypes.AlternateBidderCodeWarningCode:
+			ret[metrics.AdapterErrorValidation] = s
 		default:
 			ret[metrics.AdapterErrorUnknown] = s
 		}
@@ -650,7 +659,7 @@ func errsToBidderWarnings(errs []error) []openrtb_ext.ExtBidderMessage {
 }
 
 // This piece takes all the bids supplied by the adapters and crafts an openRTB response to send back to the requester
-func (e *exchange) buildBidResponse(ctx context.Context, liveAdapters []openrtb_ext.BidderName, adapterBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, bidRequest *openrtb2.BidRequest, adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra, auc *auction, bidResponseExt *openrtb_ext.ExtBidResponse, returnCreative bool, impExtInfoMap map[string]ImpExtInfo, errList []error) (*openrtb2.BidResponse, error) {
+func (e *exchange) buildBidResponse(ctx context.Context, liveAdapters []openrtb_ext.BidderName, adapterSeatBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, bidRequest *openrtb2.BidRequest, adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra, auc *auction, bidResponseExt *openrtb_ext.ExtBidResponse, returnCreative bool, impExtInfoMap map[string]ImpExtInfo, errList []error) (*openrtb2.BidResponse, error) {
 	bidResponse := new(openrtb2.BidResponse)
 	var err error
 
@@ -663,12 +672,12 @@ func (e *exchange) buildBidResponse(ctx context.Context, liveAdapters []openrtb_
 	// Create the SeatBids. We use a zero sized slice so that we can append non-zero seat bids, and not include seatBid
 	// objects for seatBids without any bids. Preallocate the max possible size to avoid reallocating the array as we go.
 	seatBids := make([]openrtb2.SeatBid, 0, len(liveAdapters))
-	for _, a := range liveAdapters {
+	for a, adapterSeatBids := range adapterSeatBids {
 		//while processing every single bib, do we need to handle categories here?
-		if adapterBids[a] != nil && len(adapterBids[a].bids) > 0 {
-			sb := e.makeSeatBid(adapterBids[a], a, adapterExtra, auc, returnCreative, impExtInfoMap)
+		if adapterSeatBids != nil && len(adapterSeatBids.bids) > 0 {
+			sb := e.makeSeatBid(adapterSeatBids, a, adapterExtra, auc, returnCreative, impExtInfoMap)
 			seatBids = append(seatBids, *sb)
-			bidResponse.Cur = adapterBids[a].currency
+			bidResponse.Cur = adapterSeatBids.currency
 		}
 	}
 
@@ -1176,7 +1185,7 @@ func buildStoredAuctionResponse(storedAuctionResponses map[string]json.RawMessag
 			} else {
 				//create new seat bid and add it to live adapters
 				liveAdapters = append(liveAdapters, bidderName)
-				newSeatBid := pbsOrtbSeatBid{bidsToAdd, "", nil}
+				newSeatBid := pbsOrtbSeatBid{bidsToAdd, "", nil, ""}
 				adapterBids[bidderName] = &newSeatBid
 
 			}
diff --git a/metrics/metrics.go b/metrics/metrics.go
index 8394f419..aff134ab 100644
--- a/metrics/metrics.go
+++ b/metrics/metrics.go
@@ -238,6 +238,7 @@ const (
 	AdapterErrorBadServerResponse   AdapterError = "badserverresponse"
 	AdapterErrorTimeout             AdapterError = "timeout"
 	AdapterErrorFailedToRequestBids AdapterError = "failedtorequestbid"
+	AdapterErrorValidation          AdapterError = "validation"
 	AdapterErrorUnknown             AdapterError = "unknown_error"
 )
 
@@ -247,6 +248,7 @@ func AdapterErrors() []AdapterError {
 		AdapterErrorBadServerResponse,
 		AdapterErrorTimeout,
 		AdapterErrorFailedToRequestBids,
+		AdapterErrorValidation,
 		AdapterErrorUnknown,
 	}
 }
diff --git a/openrtb_ext/bid.go b/openrtb_ext/bid.go
index 1b72b25b..b0cacf85 100644
--- a/openrtb_ext/bid.go
+++ b/openrtb_ext/bid.go
@@ -53,6 +53,7 @@ type ExtBidPrebidMeta struct {
 	NetworkName          string          `json:"networkName,omitempty"`
 	PrimaryCategoryID    string          `json:"primaryCatId,omitempty"`
 	SecondaryCategoryIDs []string        `json:"secondaryCatIds,omitempty"`
+	AdapterCode          string          `json:"adaptercode,omitempty"`
 }
 
 // ExtBidPrebidVideo defines the contract for bidresponse.seatbid.bid[i].ext.prebid.video
diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go
index 0b044da9..cc01f89a 100644
--- a/openrtb_ext/bidders.go
+++ b/openrtb_ext/bidders.go
@@ -224,6 +224,7 @@ const (
 	BidderYieldone          BidderName = "yieldone"
 	BidderYSSP              BidderName = "yssp"
 	BidderZeroClickFraud    BidderName = "zeroclickfraud"
+	BidderInfyTV            BidderName = "infytv"
 )
 
 // CoreBidderNames returns a slice of all core bidders.
@@ -377,6 +378,7 @@ func CoreBidderNames() []BidderName {
 		BidderYieldone,
 		BidderYSSP,
 		BidderZeroClickFraud,
+		BidderInfyTV,
 	}
 }
 
diff --git a/openrtb_ext/imp_infytv.go b/openrtb_ext/imp_infytv.go
new file mode 100644
index 00000000..b7307c42
--- /dev/null
+++ b/openrtb_ext/imp_infytv.go
@@ -0,0 +1,6 @@
+package openrtb_ext
+
+type ImpExtInfytv struct {
+	PlacementID string `json:"placementId"`
+	PublisherID string `json:"publisherId"`
+}
diff --git a/openrtb_ext/imp_unicorn.go b/openrtb_ext/imp_unicorn.go
index ad75414c..d8b7cf07 100644
--- a/openrtb_ext/imp_unicorn.go
+++ b/openrtb_ext/imp_unicorn.go
@@ -5,5 +5,5 @@ type ExtImpUnicorn struct {
 	PlacementID string `json:"placementId,omitempty"`
 	PublisherID int    `json:"publisherId,omitempty"`
 	MediaID     string `json:"mediaId"`
-	AccountID   int    `json:"accountId"`
+	AccountID   int    `json:"accountId,omitempty"`
 }
diff --git a/static/bidder-info/infytv.yaml b/static/bidder-info/infytv.yaml
new file mode 100644
index 00000000..02a045ee
--- /dev/null
+++ b/static/bidder-info/infytv.yaml
@@ -0,0 +1,9 @@
+maintainer:
+  email: "tech+hb@infy.tv"
+capabilities:
+  app:
+    mediaTypes:
+      - video
+  site:
+    mediaTypes:
+      - video
diff --git a/static/bidder-info/mediafuse.yaml b/static/bidder-info/mediafuse.yaml
index 3c69bfea..ca6bbb4b 100644
--- a/static/bidder-info/mediafuse.yaml
+++ b/static/bidder-info/mediafuse.yaml
@@ -1,17 +1,15 @@
 maintainer:
   email: "support@mediafuse.com"
-gvlVendorID: 411
 capabilities:
   app:
     mediaTypes:
       - banner
       - video
+      - native
   site:
     mediaTypes:
       - banner
       - video
+      - native
 userSync:
-  # mediafuse supports user syncing, but requires configuration by the host. contact this
-  # bidder directly at the email address in this file to ask about enabling user sync.
-  supports:
-    - iframe
\ No newline at end of file
+  key: "adnxs"
\ No newline at end of file
diff --git a/static/bidder-params/infytv.json b/static/bidder-params/infytv.json
new file mode 100644
index 00000000..22f9b317
--- /dev/null
+++ b/static/bidder-params/infytv.json
@@ -0,0 +1,19 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "title": "InfyTV Adapter Params",
+  "description": "A schema which validates params accepted by the InfyTV adapter",
+  "type": "object",
+  "properties": {
+    "publisherId": {
+      "type": "string",
+      "description": "publisher ID"
+    },
+    "placementId": {
+      "type": "string",
+      "description": "Placement ID"
+    }
+  },
+  "required": [
+    "publisherId"
+  ]
+}
\ No newline at end of file
diff --git a/static/bidder-params/mediafuse.json b/static/bidder-params/mediafuse.json
index 4f38f89d..8d577240 100644
--- a/static/bidder-params/mediafuse.json
+++ b/static/bidder-params/mediafuse.json
@@ -5,22 +5,105 @@
 
   "type": "object",
   "properties": {
-    "placementId": {
+    "placement_id": {
       "type": "integer",
       "description": "An ID which identifies this placement of the impression"
     },
-    "siteId": {
+    "placementId": {
       "type": "integer",
-      "description": "An ID which identifies the site selling the impression"
+      "description": "Deprecated, use placement_id instead."
     },
-    "aid": {
-      "type": "integer",
-      "description": "An ID which identifies the channel"
+    "inv_code": {
+      "type": "string",
+      "description": "A code identifying the inventory of this placement."
+    },
+    "invCode": {
+      "type": "string",
+      "description": "Deprecated, use inv_code instead."
+    },
+    "member": {
+      "type": "string",
+      "description": "An ID which identifies the member selling the impression."
+    },
+    "keywords": {
+      "type": "array",
+      "minItems": 1,
+      "items": {
+        "type": "object",
+        "description": "A key with one or more values associated with it. These are used in buy-side segment targeting.",
+        "properties": {
+          "key": {
+            "type": "string"
+          },
+          "value": {
+            "type": "array",
+            "minItems": 1,
+            "items": {
+              "type": "string"
+            }
+          }
+        },
+        "required": ["key"]
+      }
+    },
+    "traffic_source_code": {
+      "type": "string",
+      "description": "Specifies the third-party source of this impression."
     },
-    "bidFloor": {
+    "trafficSourceCode": {
+      "type": "string",
+      "description": "Deprecated, use traffic_source_code instead."
+    },
+    "reserve": {
       "type": "number",
-      "description": "BidFloor, US Dollars"
+      "description": "The minimium acceptable bid, in CPM, using US Dollars"
+    },
+    "position": {
+      "type": "string",
+      "enum": ["above", "below"],
+      "description": "Specifies the ad unit as above or below the fold"
+    },
+    "use_pmt_rule": {
+      "type": "boolean",
+      "description": "Boolean to signal Mediafuse to apply the relevant payment rule"
+    },
+    "generate_ad_pod_id": {
+      "type": "boolean",
+      "description": "Boolean to signal Mediafuse to add ad pod id to each request"
+    },
+    "private_sizes": {
+      "type": "array",
+      "items": {
+        "type": "object",
+        "properties": {
+          "w": {
+            "type": "integer"
+          },
+          "h": {
+            "type": "integer"
+          }
+        },
+        "required": ["w", "h"]
+      },
+      "description": "Private sizes (ex: [{\"w\": 300, \"h\": 250},{...}]), experimental, may not be supported."
     }
   },
-  "required": ["aid"]
-}
+
+  "oneOf": [{
+    "oneOf": [{
+      "required": ["placementId"]
+    }, {
+      "required": ["placement_id"]
+    }]
+  }, {
+    "oneOf": [{
+      "required": ["invCode", "member"]
+    }, {
+      "required": ["inv_code", "member"]
+    }]
+  }],
+
+  "not": {
+    "required": ["placementId", "invCode", "member"]
+  }
+}
\ No newline at end of file


Test patch:
diff --git a/adapters/adapterstest/test_json.go b/adapters/adapterstest/test_json.go
index 6d2533df..be3e9e40 100644
--- a/adapters/adapterstest/test_json.go
+++ b/adapters/adapterstest/test_json.go
@@ -207,6 +207,7 @@ type expectedBidResponse struct {
 type expectedBid struct {
 	Bid  json.RawMessage `json:"bid"`
 	Type string          `json:"type"`
+	Seat string          `json:"seat"`
 }
 
 // ---------------------------------------
@@ -314,6 +315,7 @@ func diffBids(t *testing.T, description string, actual *adapters.TypedBid, expec
 		return
 	}
 
+	assert.Equal(t, string(expected.Seat), string(actual.Seat), fmt.Sprintf(`%s.seat "%s" does not match expected "%s."`, description, string(actual.Seat), string(expected.Seat)))
 	assert.Equal(t, string(expected.Type), string(actual.BidType), fmt.Sprintf(`%s.type "%s" does not match expected "%s."`, description, string(actual.BidType), string(expected.Type)))
 	assert.NoError(t, diffOrtbBids(fmt.Sprintf("%s.bid", description), actual.Bid, expected.Bid))
 }
diff --git a/adapters/infytv/infytv_test.go b/adapters/infytv/infytv_test.go
new file mode 100644
index 00000000..8ee2fe0e
--- /dev/null
+++ b/adapters/infytv/infytv_test.go
@@ -0,0 +1,18 @@
+package infytv
+
+import (
+	"testing"
+
+	"github.com/prebid/prebid-server/adapters/adapterstest"
+	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/openrtb_ext"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestJsonSamples(t *testing.T) {
+	bidder, buildErr := Builder(openrtb_ext.BidderEVolution, config.Adapter{
+		Endpoint: "https://test.infy.tv/pbs/openrtb"})
+
+	assert.NoError(t, buildErr)
+	adapterstest.RunJSONBidderTest(t, "infytvtest", bidder)
+}
diff --git a/adapters/infytv/infytvtest/exemplary/app.json b/adapters/infytv/infytvtest/exemplary/app.json
new file mode 100644
index 00000000..d4426bf7
--- /dev/null
+++ b/adapters/infytv/infytvtest/exemplary/app.json
@@ -0,0 +1,258 @@
+{
+  "mockBidRequest": {
+    "id": "6d773e60ab245243a04217d5f7e9e786",
+    "imp": [
+      {
+        "id": "bfb2dd46a5e63a0a84711c20fd8ce9e1",
+        "video": {
+          "mimes": [
+            "video/mp4",
+            "video/ogg",
+            "video/webm"
+          ],
+          "minduration": 3,
+          "maxduration": 3000,
+          "protocols": [
+            2,
+            3,
+            5,
+            6,
+            7,
+            8
+          ],
+          "w": 480,
+          "h": 320,
+          "linearity": 1,
+          "playbackmethod": [
+            2
+          ],
+          "pos": 0
+        },
+        "bidfloor": 0.40404,
+        "bidfloorcur": "USD",
+        "secure": 1,
+        "exp": 3600,
+        "ext": {
+          "bidder": {
+            "publisherId": "IY1014",
+            "placementId": "1999"
+          }
+        }
+      }
+    ],
+    "app": {
+      "id": "AA88S0101",
+      "name": "Crunchyroll",
+      "bundle": "com.adrise.crunchyroll",
+      "storeurl": "https://channelstore.roku.com/details/d11368f0934f695ff350af56600d8ccb",
+      "cat": [
+        "IAB1"
+      ],
+      "privacypolicy": 1,
+      "publisher": {
+        "id": "IY1002",
+        "name": "The View Point",
+        "domain": "krushmedia.com"
+      },
+      "content": {
+        "language": "en"
+      }
+    },
+    "device": {
+      "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36",
+      "geo": {
+        "lat": 20.6668,
+        "lon": -103.3918,
+        "accuracy": 5,
+        "country": "MEX",
+        "region": "JAL",
+        "zip": "45186"
+      },
+      "dnt": 0,
+      "lmt": 0,
+      "ip": "187.152.119.145",
+      "devicetype": 2,
+      "os": "Windows",
+      "osv": "10",
+      "carrier": "Telmex"
+    },
+    "test": 1,
+    "at": 1,
+    "tmax": 500,
+    "cur": [
+      "USD"
+    ],
+    "source": {
+      "tid": "42da74bc-88c5-40ea-8187-9070a34e8e10"
+    },
+    "regs": {
+      "ext": {
+        "gdpr": 0
+      }
+    }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://test.infy.tv/pbs/openrtb",
+        "body": {
+          "id": "6d773e60ab245243a04217d5f7e9e786",
+          "imp": [
+            {
+              "id": "bfb2dd46a5e63a0a84711c20fd8ce9e1",
+              "video": {
+                "mimes": [
+                  "video/mp4",
+                  "video/ogg",
+                  "video/webm"
+                ],
+                "minduration": 3,
+                "maxduration": 3000,
+                "protocols": [
+                  2,
+                  3,
+                  5,
+                  6,
+                  7,
+                  8
+                ],
+                "w": 480,
+                "h": 320,
+                "linearity": 1,
+                "playbackmethod": [
+                  2
+                ],
+                "pos": 0
+              },
+              "bidfloor": 0.40404,
+              "bidfloorcur": "USD",
+              "secure": 1,
+              "exp": 3600,
+              "ext": {
+                "bidder": {
+                  "publisherId": "IY1014",
+                  "placementId": "1999"
+                }
+              }
+            }
+          ],
+          "app": {
+            "id": "AA88S0101",
+            "name": "Crunchyroll",
+            "bundle": "com.adrise.crunchyroll",
+            "storeurl": "https://channelstore.roku.com/details/d11368f0934f695ff350af56600d8ccb",
+            "cat": [
+              "IAB1"
+            ],
+            "privacypolicy": 1,
+            "publisher": {
+              "id": "IY1002",
+              "name": "The View Point",
+              "domain": "krushmedia.com"
+            },
+            "content": {
+              "language": "en"
+            }
+          },
+          "device": {
+            "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36",
+            "geo": {
+              "lat": 20.6668,
+              "lon": -103.3918,
+              "accuracy": 5,
+              "country": "MEX",
+              "region": "JAL",
+              "zip": "45186"
+            },
+            "dnt": 0,
+            "lmt": 0,
+            "ip": "187.152.119.145",
+            "devicetype": 2,
+            "os": "Windows",
+            "osv": "10",
+            "carrier": "Telmex"
+          },
+          "test": 1,
+          "at": 1,
+          "tmax": 500,
+          "cur": [
+            "USD"
+          ],
+          "source": {
+            "tid": "42da74bc-88c5-40ea-8187-9070a34e8e10"
+          },
+          "regs": {
+            "ext": {
+              "gdpr": 0
+            }
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "6d773e60ab245243a04217d5f7e9e786",
+          "bidid": "ccbd63285c0e7b69602d90319bda6be4",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "7f6c6d6ba432059a5305815a8d740283",
+                  "impid": "bfb2dd46a5e63a0a84711c20fd8ce9e1",
+                  "price": 0.1,
+                  "nurl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=nurl&auctionId=ccbd63285c0e7b69602d90319bda6be4&price=${AUCTION_PRICE}",
+                  "burl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=burl&auctionId=ccbd63285c0e7b69602d90319bda6be4&price=${AUCTION_PRICE}",
+                  "adm": "<VAST version=\"2.0\"><Ad id=\"20001\" sequence=\"1\" conditionalAd=\"false\"><InLine><AdSystem version=\"4.0\"><![CDATA[iabtechlab]]></AdSystem><AdTitle><![CDATA[Inline Simple Ad]]></AdTitle><Impression><![CDATA[https://s.funnel.e-volution.ai/2/623416/analytics.gif?dt=6234161617121529371000&di=kiwilimon.com&ui=&ap=&sr=-1&pp=866d6b3a4a483ed4a454a1acaa588852&ti=6d773e60ab245243a04217d5f7e9e786&md=2&de=2&si=1c94d85ea1a55ebf3325a82935240614&dm=480x320&gt=MX&ac=&pc=&cr=&pv=56d8a5f0-3872-4fa9-9f03-85cd17df2f1f&c1=1&c2=-1&cb=16220688009256611]]></Impression><Impression><![CDATA[https://s.funnel.e-volution.ai/2/623416/analytics.js?dt=6234161607645611134000&di=kiwilimon.com&ui=&ap=&sr=-1&pp=866d6b3a4a483ed4a454a1acaa588852&ti=6d773e60ab245243a04217d5f7e9e786&md=2&de=2&si=1c94d85ea1a55ebf3325a82935240614&dm=480x320&gt=MX&ac=&pc=&cr=&pv=56d8a5f0-3872-4fa9-9f03-85cd17df2f1f&c1=1&c2=-1&cb=16220688009256611]]></Impression><Impression><![CDATA[https://adrta.com/i?clid=vrl&paid=bidlab&dvid=v&avid=&caid=&plid=&publisherId=866d6b3a4a483ed4a454a1acaa588852&siteId=1c94d85ea1a55ebf3325a82935240614&priceBid=10.01000&pricePaid=&lineItemId=1&kv1=0x0&kv2=https%3A%2F%2Fkiwilimon.com%2F&kv3=&kv4=187.152.119.145&kv5=1622068800924671&kv7=-1&kv9=00%3A00%3A16&kv11=&kv12=&kv15=MEX&kv16=20.6668&kv17=-103.3918&kv18=&kv19=&kv23=Telmex&kv24=&kv25=&kv26=windows&kv27=Mozilla%2F5.0%20(Windows%20NT%2010.0%3B%20Win64%3B%20x64)%20AppleWebKit%2F537.36%20(KHTML%2C%20like%20Gecko)%20Chrome%2F88.0.4324.190%20Safari%2F537.36&kv28=&cb=1622068800924671&kv29=[ERRORCODE]&kv30=[CONTENTPLAYHEAD]_[ADPLAYHEAD]&kv33=[ASSETURI]&kv34=[VASTVERSIONS]&kv35=[IFATYPE]&kv36=[IFA]&kv37=[CLIENTUA]&kv38=[SERVERUA]&kv39=[DEVICEUA]&kv40=[DEVICEIP]&kv41=[LATLONG]&kv42=[DOMAIN]&kv43=[PAGEURL]&kv44=[VIDEO_TYPE]&kv45=[PLAYERSIZE]&kv46=[REGULATIONS]&kv47=[ADTYPE]&kv48=[TRANSACTIONID]&kv49=[BREAKPOSITION]&kv50=[APPNAME]&kv51=[PLACEMENTTYPE]]]></Impression><Impression><![CDATA[https://us-east-edge1.e-volution.ai/?c=rtb&m=sync&gdpr=0&gdpr_consent=&ccpa_consent=]]></Impression><Impression><![CDATA[https://us-east-edge1.e-volution.ai/?c=rtb&m=i&key=ccbd63285c0e7b69602d90319bda6be4&cp=${AUCTION_PRICE}]]></Impression><Creatives><Creative id=\"5480\" sequence=\"1\" adId=\"2447226\"><UniversalAdId idRegistry=\"Ad-ID\" idValue=\"8465\">8465</UniversalAdId><Linear><TrackingEvents></TrackingEvents><Duration>00:00:16</Duration><MediaFiles><MediaFile id=\"5241\" delivery=\"progressive\" type=\"video/mp4\" bitrate=\"2000\" width=\"1280\" height=\"720\" minBitrate=\"1500\" maxBitrate=\"2500\" scalable=\"1\" maintainAspectRatio=\"1\" codec=\"0\"><![CDATA[https://iab-publicfiles.s3.amazonaws.com/vast/VAST-4.0-Short-Intro.mp4]]></MediaFile><MediaFile id=\"5244\" delivery=\"progressive\" type=\"video/mp4\" bitrate=\"1000\" width=\"854\" height=\"480\" minBitrate=\"700\" maxBitrate=\"1500\" scalable=\"1\" maintainAspectRatio=\"1\" codec=\"0\"><![CDATA[https://iab-publicfiles.s3.amazonaws.com/vast/VAST-4.0-Short-Intro-mid-resolution.mp4]]></MediaFile><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://iab-publicfiles.s3.amazonaws.com/vast/VAST-4.0-Short-Intro-low-resolution.mp4]]></MediaFile></MediaFiles><VideoClicks><ClickThrough id=\"blog\"><![CDATA[https://iabtechlab.com]]></ClickThrough></VideoClicks></Linear></Creative></Creatives><Error><![CDATA[https://us-east-edge1.e-volution.ai/?c=rtb&m=nurl&auctionId=ccbd63285c0e7b69602d90319bda6be4&price=${AUCTION_PRICE}&ecode=[ERRORCODE]]]></Error></InLine></Ad></VAST>",
+                  "adomain": [
+                    "test.com"
+                  ],
+                  "cat": [
+                    "IAB24"
+                  ],
+                  "cid": "1IP31",
+                  "crid": "1KS13",
+                  "w": 0,
+                  "h": 0,
+                  "ext": {
+                    "mediaType": "video"
+                  }
+                }
+              ],
+              "seat": "1"
+            }
+          ],
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "7f6c6d6ba432059a5305815a8d740283",
+            "impid": "bfb2dd46a5e63a0a84711c20fd8ce9e1",
+            "price": 0.1,
+            "nurl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=nurl&auctionId=ccbd63285c0e7b69602d90319bda6be4&price=${AUCTION_PRICE}",
+            "burl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=burl&auctionId=ccbd63285c0e7b69602d90319bda6be4&price=${AUCTION_PRICE}",
+            "adm": "<VAST version=\"2.0\"><Ad id=\"20001\" sequence=\"1\" conditionalAd=\"false\"><InLine><AdSystem version=\"4.0\"><![CDATA[iabtechlab]]></AdSystem><AdTitle><![CDATA[Inline Simple Ad]]></AdTitle><Impression><![CDATA[https://s.funnel.e-volution.ai/2/623416/analytics.gif?dt=6234161617121529371000&di=kiwilimon.com&ui=&ap=&sr=-1&pp=866d6b3a4a483ed4a454a1acaa588852&ti=6d773e60ab245243a04217d5f7e9e786&md=2&de=2&si=1c94d85ea1a55ebf3325a82935240614&dm=480x320&gt=MX&ac=&pc=&cr=&pv=56d8a5f0-3872-4fa9-9f03-85cd17df2f1f&c1=1&c2=-1&cb=16220688009256611]]></Impression><Impression><![CDATA[https://s.funnel.e-volution.ai/2/623416/analytics.js?dt=6234161607645611134000&di=kiwilimon.com&ui=&ap=&sr=-1&pp=866d6b3a4a483ed4a454a1acaa588852&ti=6d773e60ab245243a04217d5f7e9e786&md=2&de=2&si=1c94d85ea1a55ebf3325a82935240614&dm=480x320&gt=MX&ac=&pc=&cr=&pv=56d8a5f0-3872-4fa9-9f03-85cd17df2f1f&c1=1&c2=-1&cb=16220688009256611]]></Impression><Impression><![CDATA[https://adrta.com/i?clid=vrl&paid=bidlab&dvid=v&avid=&caid=&plid=&publisherId=866d6b3a4a483ed4a454a1acaa588852&siteId=1c94d85ea1a55ebf3325a82935240614&priceBid=10.01000&pricePaid=&lineItemId=1&kv1=0x0&kv2=https%3A%2F%2Fkiwilimon.com%2F&kv3=&kv4=187.152.119.145&kv5=1622068800924671&kv7=-1&kv9=00%3A00%3A16&kv11=&kv12=&kv15=MEX&kv16=20.6668&kv17=-103.3918&kv18=&kv19=&kv23=Telmex&kv24=&kv25=&kv26=windows&kv27=Mozilla%2F5.0%20(Windows%20NT%2010.0%3B%20Win64%3B%20x64)%20AppleWebKit%2F537.36%20(KHTML%2C%20like%20Gecko)%20Chrome%2F88.0.4324.190%20Safari%2F537.36&kv28=&cb=1622068800924671&kv29=[ERRORCODE]&kv30=[CONTENTPLAYHEAD]_[ADPLAYHEAD]&kv33=[ASSETURI]&kv34=[VASTVERSIONS]&kv35=[IFATYPE]&kv36=[IFA]&kv37=[CLIENTUA]&kv38=[SERVERUA]&kv39=[DEVICEUA]&kv40=[DEVICEIP]&kv41=[LATLONG]&kv42=[DOMAIN]&kv43=[PAGEURL]&kv44=[VIDEO_TYPE]&kv45=[PLAYERSIZE]&kv46=[REGULATIONS]&kv47=[ADTYPE]&kv48=[TRANSACTIONID]&kv49=[BREAKPOSITION]&kv50=[APPNAME]&kv51=[PLACEMENTTYPE]]]></Impression><Impression><![CDATA[https://us-east-edge1.e-volution.ai/?c=rtb&m=sync&gdpr=0&gdpr_consent=&ccpa_consent=]]></Impression><Impression><![CDATA[https://us-east-edge1.e-volution.ai/?c=rtb&m=i&key=ccbd63285c0e7b69602d90319bda6be4&cp=${AUCTION_PRICE}]]></Impression><Creatives><Creative id=\"5480\" sequence=\"1\" adId=\"2447226\"><UniversalAdId idRegistry=\"Ad-ID\" idValue=\"8465\">8465</UniversalAdId><Linear><TrackingEvents></TrackingEvents><Duration>00:00:16</Duration><MediaFiles><MediaFile id=\"5241\" delivery=\"progressive\" type=\"video/mp4\" bitrate=\"2000\" width=\"1280\" height=\"720\" minBitrate=\"1500\" maxBitrate=\"2500\" scalable=\"1\" maintainAspectRatio=\"1\" codec=\"0\"><![CDATA[https://iab-publicfiles.s3.amazonaws.com/vast/VAST-4.0-Short-Intro.mp4]]></MediaFile><MediaFile id=\"5244\" delivery=\"progressive\" type=\"video/mp4\" bitrate=\"1000\" width=\"854\" height=\"480\" minBitrate=\"700\" maxBitrate=\"1500\" scalable=\"1\" maintainAspectRatio=\"1\" codec=\"0\"><![CDATA[https://iab-publicfiles.s3.amazonaws.com/vast/VAST-4.0-Short-Intro-mid-resolution.mp4]]></MediaFile><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://iab-publicfiles.s3.amazonaws.com/vast/VAST-4.0-Short-Intro-low-resolution.mp4]]></MediaFile></MediaFiles><VideoClicks><ClickThrough id=\"blog\"><![CDATA[https://iabtechlab.com]]></ClickThrough></VideoClicks></Linear></Creative></Creatives><Error><![CDATA[https://us-east-edge1.e-volution.ai/?c=rtb&m=nurl&auctionId=ccbd63285c0e7b69602d90319bda6be4&price=${AUCTION_PRICE}&ecode=[ERRORCODE]]]></Error></InLine></Ad></VAST>",
+            "adomain": [
+              "test.com"
+            ],
+            "cat": [
+              "IAB24"
+            ],
+            "cid": "1IP31",
+            "crid": "1KS13",
+            "ext": {
+              "mediaType": "video"
+            }
+          },
+          "type": "video"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/infytv/infytvtest/exemplary/video.json b/adapters/infytv/infytvtest/exemplary/video.json
new file mode 100644
index 00000000..7eb9c800
--- /dev/null
+++ b/adapters/infytv/infytvtest/exemplary/video.json
@@ -0,0 +1,280 @@
+{
+  "mockBidRequest": {
+    "id": "6d773e60ab245243a04217d5f7e9e786",
+    "imp": [
+      {
+        "id": "bfb2dd46a5e63a0a84711c20fd8ce9e1",
+        "video": {
+          "mimes": [
+            "video/mp4",
+            "video/ogg",
+            "video/webm"
+          ],
+          "minduration": 3,
+          "maxduration": 3000,
+          "protocols": [
+            2,
+            3,
+            5,
+            6,
+            7,
+            8
+          ],
+          "w": 480,
+          "h": 320,
+          "linearity": 1,
+          "playbackmethod": [
+            2
+          ],
+          "pos": 0
+        },
+        "bidfloor": 0.40404,
+        "bidfloorcur": "USD",
+        "secure": 1,
+        "exp": 3600,
+        "ext": {
+          "bidder": {
+            "publisherId": "IY1014",
+            "placementId": "1999"
+          }
+        }
+      }
+    ],
+    "site": {
+      "id": "1c94d85ea1a55ebf3325a82935240614",
+      "domain": "kiwilimon.com",
+      "cat": [
+        "IAB1",
+        "IAB2",
+        "IAB3",
+        "IAB4",
+        "IAB5",
+        "IAB6",
+        "IAB7",
+        "IAB8",
+        "IAB9",
+        "IAB10",
+        "IAB11",
+        "IAB13",
+        "IAB14",
+        "IAB15",
+        "IAB17",
+        "IAB18"
+      ],
+      "page": "https://kiwilimon.com/",
+      "publisher": {
+        "id": "866d6b3a4a483ed4a454a1acaa588852"
+      },
+      "ext": {
+        "amp": 0
+      }
+    },
+    "device": {
+      "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36",
+      "geo": {
+        "lat": 20.6668,
+        "lon": -103.3918,
+        "accuracy": 5,
+        "country": "MEX",
+        "region": "JAL",
+        "zip": "45186"
+      },
+      "dnt": 0,
+      "lmt": 0,
+      "ip": "187.152.119.145",
+      "devicetype": 2,
+      "os": "Windows",
+      "osv": "10",
+      "carrier": "Telmex"
+    },
+    "test": 1,
+    "at": 1,
+    "tmax": 500,
+    "cur": [
+      "USD"
+    ],
+    "source": {
+      "tid": "42da74bc-88c5-40ea-8187-9070a34e8e10"
+    },
+    "regs": {
+      "ext": {
+        "gdpr": 0
+      }
+    }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://test.infy.tv/pbs/openrtb",
+        "body": {
+          "id": "6d773e60ab245243a04217d5f7e9e786",
+          "imp": [
+            {
+              "id": "bfb2dd46a5e63a0a84711c20fd8ce9e1",
+              "video": {
+                "mimes": [
+                  "video/mp4",
+                  "video/ogg",
+                  "video/webm"
+                ],
+                "minduration": 3,
+                "maxduration": 3000,
+                "protocols": [
+                  2,
+                  3,
+                  5,
+                  6,
+                  7,
+                  8
+                ],
+                "w": 480,
+                "h": 320,
+                "linearity": 1,
+                "playbackmethod": [
+                  2
+                ],
+                "pos": 0
+              },
+              "bidfloor": 0.40404,
+              "bidfloorcur": "USD",
+              "secure": 1,
+              "exp": 3600,
+              "ext": {
+                "bidder": {
+                  "publisherId": "IY1014",
+                  "placementId": "1999"
+                }
+              }
+            }
+          ],
+          "site": {
+            "id": "1c94d85ea1a55ebf3325a82935240614",
+            "domain": "kiwilimon.com",
+            "cat": [
+              "IAB1",
+              "IAB2",
+              "IAB3",
+              "IAB4",
+              "IAB5",
+              "IAB6",
+              "IAB7",
+              "IAB8",
+              "IAB9",
+              "IAB10",
+              "IAB11",
+              "IAB13",
+              "IAB14",
+              "IAB15",
+              "IAB17",
+              "IAB18"
+            ],
+            "page": "https://kiwilimon.com/",
+            "publisher": {
+              "id": "866d6b3a4a483ed4a454a1acaa588852"
+            },
+            "ext": {
+              "amp": 0
+            }
+          },
+          "device": {
+            "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36",
+            "geo": {
+              "lat": 20.6668,
+              "lon": -103.3918,
+              "accuracy": 5,
+              "country": "MEX",
+              "region": "JAL",
+              "zip": "45186"
+            },
+            "dnt": 0,
+            "lmt": 0,
+            "ip": "187.152.119.145",
+            "devicetype": 2,
+            "os": "Windows",
+            "osv": "10",
+            "carrier": "Telmex"
+          },
+          "test": 1,
+          "at": 1,
+          "tmax": 500,
+          "cur": [
+            "USD"
+          ],
+          "source": {
+            "tid": "42da74bc-88c5-40ea-8187-9070a34e8e10"
+          },
+          "regs": {
+            "ext": {
+              "gdpr": 0
+            }
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "6d773e60ab245243a04217d5f7e9e786",
+          "bidid": "ccbd63285c0e7b69602d90319bda6be4",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "7f6c6d6ba432059a5305815a8d740283",
+                  "impid": "bfb2dd46a5e63a0a84711c20fd8ce9e1",
+                  "price": 0.1,
+                  "nurl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=nurl&auctionId=ccbd63285c0e7b69602d90319bda6be4&price=${AUCTION_PRICE}",
+                  "burl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=burl&auctionId=ccbd63285c0e7b69602d90319bda6be4&price=${AUCTION_PRICE}",
+                  "adm": "<VAST version=\"2.0\"><Ad id=\"20001\" sequence=\"1\" conditionalAd=\"false\"><InLine><AdSystem version=\"4.0\"><![CDATA[iabtechlab]]></AdSystem><AdTitle><![CDATA[Inline Simple Ad]]></AdTitle><Impression><![CDATA[https://s.funnel.e-volution.ai/2/623416/analytics.gif?dt=6234161617121529371000&di=kiwilimon.com&ui=&ap=&sr=-1&pp=866d6b3a4a483ed4a454a1acaa588852&ti=6d773e60ab245243a04217d5f7e9e786&md=2&de=2&si=1c94d85ea1a55ebf3325a82935240614&dm=480x320&gt=MX&ac=&pc=&cr=&pv=56d8a5f0-3872-4fa9-9f03-85cd17df2f1f&c1=1&c2=-1&cb=16220688009256611]]></Impression><Impression><![CDATA[https://s.funnel.e-volution.ai/2/623416/analytics.js?dt=6234161607645611134000&di=kiwilimon.com&ui=&ap=&sr=-1&pp=866d6b3a4a483ed4a454a1acaa588852&ti=6d773e60ab245243a04217d5f7e9e786&md=2&de=2&si=1c94d85ea1a55ebf3325a82935240614&dm=480x320&gt=MX&ac=&pc=&cr=&pv=56d8a5f0-3872-4fa9-9f03-85cd17df2f1f&c1=1&c2=-1&cb=16220688009256611]]></Impression><Impression><![CDATA[https://adrta.com/i?clid=vrl&paid=bidlab&dvid=v&avid=&caid=&plid=&publisherId=866d6b3a4a483ed4a454a1acaa588852&siteId=1c94d85ea1a55ebf3325a82935240614&priceBid=10.01000&pricePaid=&lineItemId=1&kv1=0x0&kv2=https%3A%2F%2Fkiwilimon.com%2F&kv3=&kv4=187.152.119.145&kv5=1622068800924671&kv7=-1&kv9=00%3A00%3A16&kv11=&kv12=&kv15=MEX&kv16=20.6668&kv17=-103.3918&kv18=&kv19=&kv23=Telmex&kv24=&kv25=&kv26=windows&kv27=Mozilla%2F5.0%20(Windows%20NT%2010.0%3B%20Win64%3B%20x64)%20AppleWebKit%2F537.36%20(KHTML%2C%20like%20Gecko)%20Chrome%2F88.0.4324.190%20Safari%2F537.36&kv28=&cb=1622068800924671&kv29=[ERRORCODE]&kv30=[CONTENTPLAYHEAD]_[ADPLAYHEAD]&kv33=[ASSETURI]&kv34=[VASTVERSIONS]&kv35=[IFATYPE]&kv36=[IFA]&kv37=[CLIENTUA]&kv38=[SERVERUA]&kv39=[DEVICEUA]&kv40=[DEVICEIP]&kv41=[LATLONG]&kv42=[DOMAIN]&kv43=[PAGEURL]&kv44=[VIDEO_TYPE]&kv45=[PLAYERSIZE]&kv46=[REGULATIONS]&kv47=[ADTYPE]&kv48=[TRANSACTIONID]&kv49=[BREAKPOSITION]&kv50=[APPNAME]&kv51=[PLACEMENTTYPE]]]></Impression><Impression><![CDATA[https://us-east-edge1.e-volution.ai/?c=rtb&m=sync&gdpr=0&gdpr_consent=&ccpa_consent=]]></Impression><Impression><![CDATA[https://us-east-edge1.e-volution.ai/?c=rtb&m=i&key=ccbd63285c0e7b69602d90319bda6be4&cp=${AUCTION_PRICE}]]></Impression><Creatives><Creative id=\"5480\" sequence=\"1\" adId=\"2447226\"><UniversalAdId idRegistry=\"Ad-ID\" idValue=\"8465\">8465</UniversalAdId><Linear><TrackingEvents></TrackingEvents><Duration>00:00:16</Duration><MediaFiles><MediaFile id=\"5241\" delivery=\"progressive\" type=\"video/mp4\" bitrate=\"2000\" width=\"1280\" height=\"720\" minBitrate=\"1500\" maxBitrate=\"2500\" scalable=\"1\" maintainAspectRatio=\"1\" codec=\"0\"><![CDATA[https://iab-publicfiles.s3.amazonaws.com/vast/VAST-4.0-Short-Intro.mp4]]></MediaFile><MediaFile id=\"5244\" delivery=\"progressive\" type=\"video/mp4\" bitrate=\"1000\" width=\"854\" height=\"480\" minBitrate=\"700\" maxBitrate=\"1500\" scalable=\"1\" maintainAspectRatio=\"1\" codec=\"0\"><![CDATA[https://iab-publicfiles.s3.amazonaws.com/vast/VAST-4.0-Short-Intro-mid-resolution.mp4]]></MediaFile><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://iab-publicfiles.s3.amazonaws.com/vast/VAST-4.0-Short-Intro-low-resolution.mp4]]></MediaFile></MediaFiles><VideoClicks><ClickThrough id=\"blog\"><![CDATA[https://iabtechlab.com]]></ClickThrough></VideoClicks></Linear></Creative></Creatives><Error><![CDATA[https://us-east-edge1.e-volution.ai/?c=rtb&m=nurl&auctionId=ccbd63285c0e7b69602d90319bda6be4&price=${AUCTION_PRICE}&ecode=[ERRORCODE]]]></Error></InLine></Ad></VAST>",
+                  "adomain": [
+                    "test.com"
+                  ],
+                  "cat": [
+                    "IAB24"
+                  ],
+                  "cid": "1IP31",
+                  "crid": "1KS13",
+                  "w": 0,
+                  "h": 0,
+                  "ext": {
+                    "mediaType": "video"
+                  }
+                }
+              ],
+              "seat": "1"
+            }
+          ],
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "7f6c6d6ba432059a5305815a8d740283",
+            "impid": "bfb2dd46a5e63a0a84711c20fd8ce9e1",
+            "price": 0.1,
+            "nurl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=nurl&auctionId=ccbd63285c0e7b69602d90319bda6be4&price=${AUCTION_PRICE}",
+            "burl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=burl&auctionId=ccbd63285c0e7b69602d90319bda6be4&price=${AUCTION_PRICE}",
+            "adm": "<VAST version=\"2.0\"><Ad id=\"20001\" sequence=\"1\" conditionalAd=\"false\"><InLine><AdSystem version=\"4.0\"><![CDATA[iabtechlab]]></AdSystem><AdTitle><![CDATA[Inline Simple Ad]]></AdTitle><Impression><![CDATA[https://s.funnel.e-volution.ai/2/623416/analytics.gif?dt=6234161617121529371000&di=kiwilimon.com&ui=&ap=&sr=-1&pp=866d6b3a4a483ed4a454a1acaa588852&ti=6d773e60ab245243a04217d5f7e9e786&md=2&de=2&si=1c94d85ea1a55ebf3325a82935240614&dm=480x320&gt=MX&ac=&pc=&cr=&pv=56d8a5f0-3872-4fa9-9f03-85cd17df2f1f&c1=1&c2=-1&cb=16220688009256611]]></Impression><Impression><![CDATA[https://s.funnel.e-volution.ai/2/623416/analytics.js?dt=6234161607645611134000&di=kiwilimon.com&ui=&ap=&sr=-1&pp=866d6b3a4a483ed4a454a1acaa588852&ti=6d773e60ab245243a04217d5f7e9e786&md=2&de=2&si=1c94d85ea1a55ebf3325a82935240614&dm=480x320&gt=MX&ac=&pc=&cr=&pv=56d8a5f0-3872-4fa9-9f03-85cd17df2f1f&c1=1&c2=-1&cb=16220688009256611]]></Impression><Impression><![CDATA[https://adrta.com/i?clid=vrl&paid=bidlab&dvid=v&avid=&caid=&plid=&publisherId=866d6b3a4a483ed4a454a1acaa588852&siteId=1c94d85ea1a55ebf3325a82935240614&priceBid=10.01000&pricePaid=&lineItemId=1&kv1=0x0&kv2=https%3A%2F%2Fkiwilimon.com%2F&kv3=&kv4=187.152.119.145&kv5=1622068800924671&kv7=-1&kv9=00%3A00%3A16&kv11=&kv12=&kv15=MEX&kv16=20.6668&kv17=-103.3918&kv18=&kv19=&kv23=Telmex&kv24=&kv25=&kv26=windows&kv27=Mozilla%2F5.0%20(Windows%20NT%2010.0%3B%20Win64%3B%20x64)%20AppleWebKit%2F537.36%20(KHTML%2C%20like%20Gecko)%20Chrome%2F88.0.4324.190%20Safari%2F537.36&kv28=&cb=1622068800924671&kv29=[ERRORCODE]&kv30=[CONTENTPLAYHEAD]_[ADPLAYHEAD]&kv33=[ASSETURI]&kv34=[VASTVERSIONS]&kv35=[IFATYPE]&kv36=[IFA]&kv37=[CLIENTUA]&kv38=[SERVERUA]&kv39=[DEVICEUA]&kv40=[DEVICEIP]&kv41=[LATLONG]&kv42=[DOMAIN]&kv43=[PAGEURL]&kv44=[VIDEO_TYPE]&kv45=[PLAYERSIZE]&kv46=[REGULATIONS]&kv47=[ADTYPE]&kv48=[TRANSACTIONID]&kv49=[BREAKPOSITION]&kv50=[APPNAME]&kv51=[PLACEMENTTYPE]]]></Impression><Impression><![CDATA[https://us-east-edge1.e-volution.ai/?c=rtb&m=sync&gdpr=0&gdpr_consent=&ccpa_consent=]]></Impression><Impression><![CDATA[https://us-east-edge1.e-volution.ai/?c=rtb&m=i&key=ccbd63285c0e7b69602d90319bda6be4&cp=${AUCTION_PRICE}]]></Impression><Creatives><Creative id=\"5480\" sequence=\"1\" adId=\"2447226\"><UniversalAdId idRegistry=\"Ad-ID\" idValue=\"8465\">8465</UniversalAdId><Linear><TrackingEvents></TrackingEvents><Duration>00:00:16</Duration><MediaFiles><MediaFile id=\"5241\" delivery=\"progressive\" type=\"video/mp4\" bitrate=\"2000\" width=\"1280\" height=\"720\" minBitrate=\"1500\" maxBitrate=\"2500\" scalable=\"1\" maintainAspectRatio=\"1\" codec=\"0\"><![CDATA[https://iab-publicfiles.s3.amazonaws.com/vast/VAST-4.0-Short-Intro.mp4]]></MediaFile><MediaFile id=\"5244\" delivery=\"progressive\" type=\"video/mp4\" bitrate=\"1000\" width=\"854\" height=\"480\" minBitrate=\"700\" maxBitrate=\"1500\" scalable=\"1\" maintainAspectRatio=\"1\" codec=\"0\"><![CDATA[https://iab-publicfiles.s3.amazonaws.com/vast/VAST-4.0-Short-Intro-mid-resolution.mp4]]></MediaFile><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://iab-publicfiles.s3.amazonaws.com/vast/VAST-4.0-Short-Intro-low-resolution.mp4]]></MediaFile></MediaFiles><VideoClicks><ClickThrough id=\"blog\"><![CDATA[https://iabtechlab.com]]></ClickThrough></VideoClicks></Linear></Creative></Creatives><Error><![CDATA[https://us-east-edge1.e-volution.ai/?c=rtb&m=nurl&auctionId=ccbd63285c0e7b69602d90319bda6be4&price=${AUCTION_PRICE}&ecode=[ERRORCODE]]]></Error></InLine></Ad></VAST>",
+            "adomain": [
+              "test.com"
+            ],
+            "cat": [
+              "IAB24"
+            ],
+            "cid": "1IP31",
+            "crid": "1KS13",
+            "ext": {
+              "mediaType": "video"
+            }
+          },
+          "type": "video"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/infytv/infytvtest/supplemental/bad-response.json b/adapters/infytv/infytvtest/supplemental/bad-response.json
new file mode 100644
index 00000000..5141cdca
--- /dev/null
+++ b/adapters/infytv/infytvtest/supplemental/bad-response.json
@@ -0,0 +1,202 @@
+{
+  "mockBidRequest": {
+    "id": "6d773e60ab245243a04217d5f7e9e786",
+    "imp": [
+      {
+        "id": "bfb2dd46a5e63a0a84711c20fd8ce9e1",
+        "video": {
+          "mimes": [
+            "video/mp4",
+            "video/ogg",
+            "video/webm"
+          ],
+          "minduration": 3,
+          "maxduration": 3000,
+          "protocols": [
+            2,
+            3,
+            5,
+            6,
+            7,
+            8
+          ],
+          "w": 480,
+          "h": 320,
+          "linearity": 1,
+          "playbackmethod": [
+            2
+          ],
+          "pos": 0
+        },
+        "bidfloor": 0.40404,
+        "bidfloorcur": "USD",
+        "secure": 1,
+        "exp": 3600,
+        "ext": {
+          "bidder": {
+            "key": "test_video"
+          }
+        }
+      }
+    ],
+    "app": {
+      "id": "AA88S0101",
+      "name": "Crunchyroll",
+      "bundle": "com.adrise.crunchyroll",
+      "storeurl": "https://channelstore.roku.com/details/d11368f0934f695ff350af56600d8ccb",
+      "cat": [
+        "IAB1"
+      ],
+      "privacypolicy": 1,
+      "publisher": {
+        "id": "IY1002",
+        "name": "The View Point",
+        "domain": "krushmedia.com"
+      },
+      "content": {
+        "language": "en"
+      }
+    },
+    "device": {
+      "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36",
+      "geo": {
+        "lat": 20.6668,
+        "lon": -103.3918,
+        "accuracy": 5,
+        "country": "MEX",
+        "region": "JAL",
+        "zip": "45186"
+      },
+      "dnt": 0,
+      "lmt": 0,
+      "ip": "187.152.119.145",
+      "devicetype": 2,
+      "os": "Windows",
+      "osv": "10",
+      "carrier": "Telmex"
+    },
+    "test": 1,
+    "at": 1,
+    "tmax": 500,
+    "cur": [
+      "USD"
+    ],
+    "source": {
+      "tid": "42da74bc-88c5-40ea-8187-9070a34e8e10"
+    },
+    "regs": {
+      "ext": {
+        "gdpr": 0
+      }
+    }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://test.infy.tv/pbs/openrtb",
+        "body": {
+          "id": "6d773e60ab245243a04217d5f7e9e786",
+          "imp": [
+            {
+              "id": "bfb2dd46a5e63a0a84711c20fd8ce9e1",
+              "video": {
+                "mimes": [
+                  "video/mp4",
+                  "video/ogg",
+                  "video/webm"
+                ],
+                "minduration": 3,
+                "maxduration": 3000,
+                "protocols": [
+                  2,
+                  3,
+                  5,
+                  6,
+                  7,
+                  8
+                ],
+                "w": 480,
+                "h": 320,
+                "linearity": 1,
+                "playbackmethod": [
+                  2
+                ],
+                "pos": 0
+              },
+              "bidfloor": 0.40404,
+              "bidfloorcur": "USD",
+              "secure": 1,
+              "exp": 3600,
+              "ext": {
+                "bidder": {
+                  "key": "test_video"
+                }
+              }
+            }
+          ],
+          "app": {
+            "id": "AA88S0101",
+            "name": "Crunchyroll",
+            "bundle": "com.adrise.crunchyroll",
+            "storeurl": "https://channelstore.roku.com/details/d11368f0934f695ff350af56600d8ccb",
+            "cat": [
+              "IAB1"
+            ],
+            "privacypolicy": 1,
+            "publisher": {
+              "id": "IY1002",
+              "name": "The View Point",
+              "domain": "krushmedia.com"
+            },
+            "content": {
+              "language": "en"
+            }
+          },
+          "device": {
+            "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36",
+            "geo": {
+              "lat": 20.6668,
+              "lon": -103.3918,
+              "accuracy": 5,
+              "country": "MEX",
+              "region": "JAL",
+              "zip": "45186"
+            },
+            "dnt": 0,
+            "lmt": 0,
+            "ip": "187.152.119.145",
+            "devicetype": 2,
+            "os": "Windows",
+            "osv": "10",
+            "carrier": "Telmex"
+          },
+          "test": 1,
+          "at": 1,
+          "tmax": 500,
+          "cur": [
+            "USD"
+          ],
+          "source": {
+            "tid": "42da74bc-88c5-40ea-8187-9070a34e8e10"
+          },
+          "regs": {
+            "ext": {
+              "gdpr": 0
+            }
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": ""
+      }
+    }
+  ],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "Bad response, json: cannot unmarshal string into Go value of type openrtb2.BidResponse",
+      "comparison": "literal"
+    }
+  ],
+  "expectedBidResponses": []
+}
\ No newline at end of file
diff --git a/adapters/infytv/infytvtest/supplemental/empty-seatbid.json b/adapters/infytv/infytvtest/supplemental/empty-seatbid.json
new file mode 100644
index 00000000..0db426fc
--- /dev/null
+++ b/adapters/infytv/infytvtest/supplemental/empty-seatbid.json
@@ -0,0 +1,207 @@
+{
+  "mockBidRequest": {
+    "id": "6d773e60ab245243a04217d5f7e9e786",
+    "imp": [
+      {
+        "id": "bfb2dd46a5e63a0a84711c20fd8ce9e1",
+        "video": {
+          "mimes": [
+            "video/mp4",
+            "video/ogg",
+            "video/webm"
+          ],
+          "minduration": 3,
+          "maxduration": 3000,
+          "protocols": [
+            2,
+            3,
+            5,
+            6,
+            7,
+            8
+          ],
+          "w": 480,
+          "h": 320,
+          "linearity": 1,
+          "playbackmethod": [
+            2
+          ],
+          "pos": 0
+        },
+        "bidfloor": 0.40404,
+        "bidfloorcur": "USD",
+        "secure": 1,
+        "exp": 3600,
+        "ext": {
+          "bidder": {
+            "key": "test_video"
+          }
+        }
+      }
+    ],
+    "app": {
+      "id": "AA88S0101",
+      "name": "Crunchyroll",
+      "bundle": "com.adrise.crunchyroll",
+      "storeurl": "https://channelstore.roku.com/details/d11368f0934f695ff350af56600d8ccb",
+      "cat": [
+        "IAB1"
+      ],
+      "privacypolicy": 1,
+      "publisher": {
+        "id": "IY1002",
+        "name": "The View Point",
+        "domain": "krushmedia.com"
+      },
+      "content": {
+        "language": "en"
+      }
+    },
+    "device": {
+      "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36",
+      "geo": {
+        "lat": 20.6668,
+        "lon": -103.3918,
+        "accuracy": 5,
+        "country": "MEX",
+        "region": "JAL",
+        "zip": "45186"
+      },
+      "dnt": 0,
+      "lmt": 0,
+      "ip": "187.152.119.145",
+      "devicetype": 2,
+      "os": "Windows",
+      "osv": "10",
+      "carrier": "Telmex"
+    },
+    "test": 1,
+    "at": 1,
+    "tmax": 500,
+    "cur": [
+      "USD"
+    ],
+    "source": {
+      "tid": "42da74bc-88c5-40ea-8187-9070a34e8e10"
+    },
+    "regs": {
+      "ext": {
+        "gdpr": 0
+      }
+    }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://test.infy.tv/pbs/openrtb",
+        "body": {
+          "id": "6d773e60ab245243a04217d5f7e9e786",
+          "imp": [
+            {
+              "id": "bfb2dd46a5e63a0a84711c20fd8ce9e1",
+              "video": {
+                "mimes": [
+                  "video/mp4",
+                  "video/ogg",
+                  "video/webm"
+                ],
+                "minduration": 3,
+                "maxduration": 3000,
+                "protocols": [
+                  2,
+                  3,
+                  5,
+                  6,
+                  7,
+                  8
+                ],
+                "w": 480,
+                "h": 320,
+                "linearity": 1,
+                "playbackmethod": [
+                  2
+                ],
+                "pos": 0
+              },
+              "bidfloor": 0.40404,
+              "bidfloorcur": "USD",
+              "secure": 1,
+              "exp": 3600,
+              "ext": {
+                "bidder": {
+                  "key": "test_video"
+                }
+              }
+            }
+          ],
+          "app": {
+            "id": "AA88S0101",
+            "name": "Crunchyroll",
+            "bundle": "com.adrise.crunchyroll",
+            "storeurl": "https://channelstore.roku.com/details/d11368f0934f695ff350af56600d8ccb",
+            "cat": [
+              "IAB1"
+            ],
+            "privacypolicy": 1,
+            "publisher": {
+              "id": "IY1002",
+              "name": "The View Point",
+              "domain": "krushmedia.com"
+            },
+            "content": {
+              "language": "en"
+            }
+          },
+          "device": {
+            "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36",
+            "geo": {
+              "lat": 20.6668,
+              "lon": -103.3918,
+              "accuracy": 5,
+              "country": "MEX",
+              "region": "JAL",
+              "zip": "45186"
+            },
+            "dnt": 0,
+            "lmt": 0,
+            "ip": "187.152.119.145",
+            "devicetype": 2,
+            "os": "Windows",
+            "osv": "10",
+            "carrier": "Telmex"
+          },
+          "test": 1,
+          "at": 1,
+          "tmax": 500,
+          "cur": [
+            "USD"
+          ],
+          "source": {
+            "tid": "42da74bc-88c5-40ea-8187-9070a34e8e10"
+          },
+          "regs": {
+            "ext": {
+              "gdpr": 0
+            }
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "a1580f2f-be6d-11eb-a150-d094662c1c35",
+          "bidid": "359da97d0384d8a14767029c18fd840d",
+          "seatbid": [],
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "Empty seatbid",
+      "comparison": "literal"
+    }
+  ],
+  "expectedBidResponses": []
+}
\ No newline at end of file
diff --git a/adapters/infytv/infytvtest/supplemental/status-204.json b/adapters/infytv/infytvtest/supplemental/status-204.json
new file mode 100644
index 00000000..f432e241
--- /dev/null
+++ b/adapters/infytv/infytvtest/supplemental/status-204.json
@@ -0,0 +1,196 @@
+{
+  "mockBidRequest": {
+    "id": "6d773e60ab245243a04217d5f7e9e786",
+    "imp": [
+      {
+        "id": "bfb2dd46a5e63a0a84711c20fd8ce9e1",
+        "video": {
+          "mimes": [
+            "video/mp4",
+            "video/ogg",
+            "video/webm"
+          ],
+          "minduration": 3,
+          "maxduration": 3000,
+          "protocols": [
+            2,
+            3,
+            5,
+            6,
+            7,
+            8
+          ],
+          "w": 480,
+          "h": 320,
+          "linearity": 1,
+          "playbackmethod": [
+            2
+          ],
+          "pos": 0
+        },
+        "bidfloor": 0.40404,
+        "bidfloorcur": "USD",
+        "secure": 1,
+        "exp": 3600,
+        "ext": {
+          "bidder": {
+            "key": "test_video"
+          }
+        }
+      }
+    ],
+    "app": {
+      "id": "AA88S0101",
+      "name": "Crunchyroll",
+      "bundle": "com.adrise.crunchyroll",
+      "storeurl": "https://channelstore.roku.com/details/d11368f0934f695ff350af56600d8ccb",
+      "cat": [
+        "IAB1"
+      ],
+      "privacypolicy": 1,
+      "publisher": {
+        "id": "IY1002",
+        "name": "The View Point",
+        "domain": "krushmedia.com"
+      },
+      "content": {
+        "language": "en"
+      }
+    },
+    "device": {
+      "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36",
+      "geo": {
+        "lat": 20.6668,
+        "lon": -103.3918,
+        "accuracy": 5,
+        "country": "MEX",
+        "region": "JAL",
+        "zip": "45186"
+      },
+      "dnt": 0,
+      "lmt": 0,
+      "ip": "187.152.119.145",
+      "devicetype": 2,
+      "os": "Windows",
+      "osv": "10",
+      "carrier": "Telmex"
+    },
+    "test": 1,
+    "at": 1,
+    "tmax": 500,
+    "cur": [
+      "USD"
+    ],
+    "source": {
+      "tid": "42da74bc-88c5-40ea-8187-9070a34e8e10"
+    },
+    "regs": {
+      "ext": {
+        "gdpr": 0
+      }
+    }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://test.infy.tv/pbs/openrtb",
+        "body": {
+          "id": "6d773e60ab245243a04217d5f7e9e786",
+          "imp": [
+            {
+              "id": "bfb2dd46a5e63a0a84711c20fd8ce9e1",
+              "video": {
+                "mimes": [
+                  "video/mp4",
+                  "video/ogg",
+                  "video/webm"
+                ],
+                "minduration": 3,
+                "maxduration": 3000,
+                "protocols": [
+                  2,
+                  3,
+                  5,
+                  6,
+                  7,
+                  8
+                ],
+                "w": 480,
+                "h": 320,
+                "linearity": 1,
+                "playbackmethod": [
+                  2
+                ],
+                "pos": 0
+              },
+              "bidfloor": 0.40404,
+              "bidfloorcur": "USD",
+              "secure": 1,
+              "exp": 3600,
+              "ext": {
+                "bidder": {
+                  "key": "test_video"
+                }
+              }
+            }
+          ],
+          "app": {
+            "id": "AA88S0101",
+            "name": "Crunchyroll",
+            "bundle": "com.adrise.crunchyroll",
+            "storeurl": "https://channelstore.roku.com/details/d11368f0934f695ff350af56600d8ccb",
+            "cat": [
+              "IAB1"
+            ],
+            "privacypolicy": 1,
+            "publisher": {
+              "id": "IY1002",
+              "name": "The View Point",
+              "domain": "krushmedia.com"
+            },
+            "content": {
+              "language": "en"
+            }
+          },
+          "device": {
+            "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36",
+            "geo": {
+              "lat": 20.6668,
+              "lon": -103.3918,
+              "accuracy": 5,
+              "country": "MEX",
+              "region": "JAL",
+              "zip": "45186"
+            },
+            "dnt": 0,
+            "lmt": 0,
+            "ip": "187.152.119.145",
+            "devicetype": 2,
+            "os": "Windows",
+            "osv": "10",
+            "carrier": "Telmex"
+          },
+          "test": 1,
+          "at": 1,
+          "tmax": 500,
+          "cur": [
+            "USD"
+          ],
+          "source": {
+            "tid": "42da74bc-88c5-40ea-8187-9070a34e8e10"
+          },
+          "regs": {
+            "ext": {
+              "gdpr": 0
+            }
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 204,
+        "body": {}
+      }
+    }
+  ],
+  "expectedBidResponses": []
+}
\ No newline at end of file
diff --git a/adapters/infytv/infytvtest/supplemental/status-400.json b/adapters/infytv/infytvtest/supplemental/status-400.json
new file mode 100644
index 00000000..44d55575
--- /dev/null
+++ b/adapters/infytv/infytvtest/supplemental/status-400.json
@@ -0,0 +1,202 @@
+{
+  "mockBidRequest": {
+    "id": "6d773e60ab245243a04217d5f7e9e786",
+    "imp": [
+      {
+        "id": "bfb2dd46a5e63a0a84711c20fd8ce9e1",
+        "video": {
+          "mimes": [
+            "video/mp4",
+            "video/ogg",
+            "video/webm"
+          ],
+          "minduration": 3,
+          "maxduration": 3000,
+          "protocols": [
+            2,
+            3,
+            5,
+            6,
+            7,
+            8
+          ],
+          "w": 480,
+          "h": 320,
+          "linearity": 1,
+          "playbackmethod": [
+            2
+          ],
+          "pos": 0
+        },
+        "bidfloor": 0.40404,
+        "bidfloorcur": "USD",
+        "secure": 1,
+        "exp": 3600,
+        "ext": {
+          "bidder": {
+            "key": "test_video"
+          }
+        }
+      }
+    ],
+    "app": {
+      "id": "AA88S0101",
+      "name": "Crunchyroll",
+      "bundle": "com.adrise.crunchyroll",
+      "storeurl": "https://channelstore.roku.com/details/d11368f0934f695ff350af56600d8ccb",
+      "cat": [
+        "IAB1"
+      ],
+      "privacypolicy": 1,
+      "publisher": {
+        "id": "IY1002",
+        "name": "The View Point",
+        "domain": "krushmedia.com"
+      },
+      "content": {
+        "language": "en"
+      }
+    },
+    "device": {
+      "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36",
+      "geo": {
+        "lat": 20.6668,
+        "lon": -103.3918,
+        "accuracy": 5,
+        "country": "MEX",
+        "region": "JAL",
+        "zip": "45186"
+      },
+      "dnt": 0,
+      "lmt": 0,
+      "ip": "187.152.119.145",
+      "devicetype": 2,
+      "os": "Windows",
+      "osv": "10",
+      "carrier": "Telmex"
+    },
+    "test": 1,
+    "at": 1,
+    "tmax": 500,
+    "cur": [
+      "USD"
+    ],
+    "source": {
+      "tid": "42da74bc-88c5-40ea-8187-9070a34e8e10"
+    },
+    "regs": {
+      "ext": {
+        "gdpr": 0
+      }
+    }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://test.infy.tv/pbs/openrtb",
+        "body": {
+          "id": "6d773e60ab245243a04217d5f7e9e786",
+          "imp": [
+            {
+              "id": "bfb2dd46a5e63a0a84711c20fd8ce9e1",
+              "video": {
+                "mimes": [
+                  "video/mp4",
+                  "video/ogg",
+                  "video/webm"
+                ],
+                "minduration": 3,
+                "maxduration": 3000,
+                "protocols": [
+                  2,
+                  3,
+                  5,
+                  6,
+                  7,
+                  8
+                ],
+                "w": 480,
+                "h": 320,
+                "linearity": 1,
+                "playbackmethod": [
+                  2
+                ],
+                "pos": 0
+              },
+              "bidfloor": 0.40404,
+              "bidfloorcur": "USD",
+              "secure": 1,
+              "exp": 3600,
+              "ext": {
+                "bidder": {
+                  "key": "test_video"
+                }
+              }
+            }
+          ],
+          "app": {
+            "id": "AA88S0101",
+            "name": "Crunchyroll",
+            "bundle": "com.adrise.crunchyroll",
+            "storeurl": "https://channelstore.roku.com/details/d11368f0934f695ff350af56600d8ccb",
+            "cat": [
+              "IAB1"
+            ],
+            "privacypolicy": 1,
+            "publisher": {
+              "id": "IY1002",
+              "name": "The View Point",
+              "domain": "krushmedia.com"
+            },
+            "content": {
+              "language": "en"
+            }
+          },
+          "device": {
+            "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36",
+            "geo": {
+              "lat": 20.6668,
+              "lon": -103.3918,
+              "accuracy": 5,
+              "country": "MEX",
+              "region": "JAL",
+              "zip": "45186"
+            },
+            "dnt": 0,
+            "lmt": 0,
+            "ip": "187.152.119.145",
+            "devicetype": 2,
+            "os": "Windows",
+            "osv": "10",
+            "carrier": "Telmex"
+          },
+          "test": 1,
+          "at": 1,
+          "tmax": 500,
+          "cur": [
+            "USD"
+          ],
+          "source": {
+            "tid": "42da74bc-88c5-40ea-8187-9070a34e8e10"
+          },
+          "regs": {
+            "ext": {
+              "gdpr": 0
+            }
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 400,
+        "body": "The Key has a different ad format"
+      }
+    }
+  ],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "Bad Request. \"The Key has a different ad format\"",
+      "comparison": "literal"
+    }
+  ],
+  "expectedBidResponses": []
+}
\ No newline at end of file
diff --git a/adapters/infytv/infytvtest/supplemental/status-503.json b/adapters/infytv/infytvtest/supplemental/status-503.json
new file mode 100644
index 00000000..310ad85e
--- /dev/null
+++ b/adapters/infytv/infytvtest/supplemental/status-503.json
@@ -0,0 +1,195 @@
+{
+  "mockBidRequest": {
+    "id": "6d773e60ab245243a04217d5f7e9e786",
+    "imp": [
+      {
+        "id": "bfb2dd46a5e63a0a84711c20fd8ce9e1",
+        "video": {
+          "mimes": [
+            "video/mp4",
+            "video/ogg",
+            "video/webm"
+          ],
+          "minduration": 3,
+          "maxduration": 3000,
+          "protocols": [
+            2,
+            3,
+            5,
+            6,
+            7,
+            8
+          ],
+          "w": 480,
+          "h": 320,
+          "linearity": 1,
+          "playbackmethod": [
+            2
+          ],
+          "pos": 0
+        },
+        "bidfloor": 0.40404,
+        "bidfloorcur": "USD",
+        "secure": 1,
+        "exp": 3600,
+        "ext": {
+          "bidder": {
+            "key": "test_video"
+          }
+        }
+      }
+    ],
+    "app": {
+      "id": "AA88S0101",
+      "name": "Crunchyroll",
+      "bundle": "com.adrise.crunchyroll",
+      "storeurl": "https://channelstore.roku.com/details/d11368f0934f695ff350af56600d8ccb",
+      "cat": [
+        "IAB1"
+      ],
+      "privacypolicy": 1,
+      "publisher": {
+        "id": "IY1002",
+        "name": "The View Point",
+        "domain": "krushmedia.com"
+      },
+      "content": {
+        "language": "en"
+      }
+    },
+    "device": {
+      "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36",
+      "geo": {
+        "lat": 20.6668,
+        "lon": -103.3918,
+        "accuracy": 5,
+        "country": "MEX",
+        "region": "JAL",
+        "zip": "45186"
+      },
+      "dnt": 0,
+      "lmt": 0,
+      "ip": "187.152.119.145",
+      "devicetype": 2,
+      "os": "Windows",
+      "osv": "10",
+      "carrier": "Telmex"
+    },
+    "test": 1,
+    "at": 1,
+    "tmax": 500,
+    "cur": [
+      "USD"
+    ],
+    "source": {
+      "tid": "42da74bc-88c5-40ea-8187-9070a34e8e10"
+    },
+    "regs": {
+      "ext": {
+        "gdpr": 0
+      }
+    }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://test.infy.tv/pbs/openrtb",
+        "body": {
+          "id": "6d773e60ab245243a04217d5f7e9e786",
+          "imp": [
+            {
+              "id": "bfb2dd46a5e63a0a84711c20fd8ce9e1",
+              "video": {
+                "mimes": [
+                  "video/mp4",
+                  "video/ogg",
+                  "video/webm"
+                ],
+                "minduration": 3,
+                "maxduration": 3000,
+                "protocols": [
+                  2,
+                  3,
+                  5,
+                  6,
+                  7,
+                  8
+                ],
+                "w": 480,
+                "h": 320,
+                "linearity": 1,
+                "playbackmethod": [
+                  2
+                ],
+                "pos": 0
+              },
+              "bidfloor": 0.40404,
+              "bidfloorcur": "USD",
+              "secure": 1,
+              "exp": 3600,
+              "ext": {
+                "bidder": {
+                  "key": "test_video"
+                }
+              }
+            }
+          ],
+          "app": {
+            "id": "AA88S0101",
+            "name": "Crunchyroll",
+            "bundle": "com.adrise.crunchyroll",
+            "storeurl": "https://channelstore.roku.com/details/d11368f0934f695ff350af56600d8ccb",
+            "cat": [
+              "IAB1"
+            ],
+            "privacypolicy": 1,
+            "publisher": {
+              "id": "IY1002",
+              "name": "The View Point",
+              "domain": "krushmedia.com"
+            },
+            "content": {
+              "language": "en"
+            }
+          },
+          "device": {
+            "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36",
+            "geo": {
+              "lat": 20.6668,
+              "lon": -103.3918,
+              "accuracy": 5,
+              "country": "MEX",
+              "region": "JAL",
+              "zip": "45186"
+            },
+            "dnt": 0,
+            "lmt": 0,
+            "ip": "187.152.119.145",
+            "devicetype": 2,
+            "os": "Windows",
+            "osv": "10",
+            "carrier": "Telmex"
+          },
+          "test": 1,
+          "at": 1,
+          "tmax": 500,
+          "cur": [
+            "USD"
+          ],
+          "source": {
+            "tid": "42da74bc-88c5-40ea-8187-9070a34e8e10"
+          },
+          "regs": {
+            "ext": {
+              "gdpr": 0
+            }
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 503
+      }
+    }
+  ],
+  "expectedBidResponses": []
+}
\ No newline at end of file
diff --git a/adapters/infytv/infytvtest/supplemental/unexpected-status.json b/adapters/infytv/infytvtest/supplemental/unexpected-status.json
new file mode 100644
index 00000000..bfb4b3f9
--- /dev/null
+++ b/adapters/infytv/infytvtest/supplemental/unexpected-status.json
@@ -0,0 +1,201 @@
+{
+  "mockBidRequest": {
+    "id": "6d773e60ab245243a04217d5f7e9e786",
+    "imp": [
+      {
+        "id": "bfb2dd46a5e63a0a84711c20fd8ce9e1",
+        "video": {
+          "mimes": [
+            "video/mp4",
+            "video/ogg",
+            "video/webm"
+          ],
+          "minduration": 3,
+          "maxduration": 3000,
+          "protocols": [
+            2,
+            3,
+            5,
+            6,
+            7,
+            8
+          ],
+          "w": 480,
+          "h": 320,
+          "linearity": 1,
+          "playbackmethod": [
+            2
+          ],
+          "pos": 0
+        },
+        "bidfloor": 0.40404,
+        "bidfloorcur": "USD",
+        "secure": 1,
+        "exp": 3600,
+        "ext": {
+          "bidder": {
+            "key": "test_video"
+          }
+        }
+      }
+    ],
+    "app": {
+      "id": "AA88S0101",
+      "name": "Crunchyroll",
+      "bundle": "com.adrise.crunchyroll",
+      "storeurl": "https://channelstore.roku.com/details/d11368f0934f695ff350af56600d8ccb",
+      "cat": [
+        "IAB1"
+      ],
+      "privacypolicy": 1,
+      "publisher": {
+        "id": "IY1002",
+        "name": "The View Point",
+        "domain": "krushmedia.com"
+      },
+      "content": {
+        "language": "en"
+      }
+    },
+    "device": {
+      "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36",
+      "geo": {
+        "lat": 20.6668,
+        "lon": -103.3918,
+        "accuracy": 5,
+        "country": "MEX",
+        "region": "JAL",
+        "zip": "45186"
+      },
+      "dnt": 0,
+      "lmt": 0,
+      "ip": "187.152.119.145",
+      "devicetype": 2,
+      "os": "Windows",
+      "osv": "10",
+      "carrier": "Telmex"
+    },
+    "test": 1,
+    "at": 1,
+    "tmax": 500,
+    "cur": [
+      "USD"
+    ],
+    "source": {
+      "tid": "42da74bc-88c5-40ea-8187-9070a34e8e10"
+    },
+    "regs": {
+      "ext": {
+        "gdpr": 0
+      }
+    }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://test.infy.tv/pbs/openrtb",
+        "body": {
+          "id": "6d773e60ab245243a04217d5f7e9e786",
+          "imp": [
+            {
+              "id": "bfb2dd46a5e63a0a84711c20fd8ce9e1",
+              "video": {
+                "mimes": [
+                  "video/mp4",
+                  "video/ogg",
+                  "video/webm"
+                ],
+                "minduration": 3,
+                "maxduration": 3000,
+                "protocols": [
+                  2,
+                  3,
+                  5,
+                  6,
+                  7,
+                  8
+                ],
+                "w": 480,
+                "h": 320,
+                "linearity": 1,
+                "playbackmethod": [
+                  2
+                ],
+                "pos": 0
+              },
+              "bidfloor": 0.40404,
+              "bidfloorcur": "USD",
+              "secure": 1,
+              "exp": 3600,
+              "ext": {
+                "bidder": {
+                  "key": "test_video"
+                }
+              }
+            }
+          ],
+          "app": {
+            "id": "AA88S0101",
+            "name": "Crunchyroll",
+            "bundle": "com.adrise.crunchyroll",
+            "storeurl": "https://channelstore.roku.com/details/d11368f0934f695ff350af56600d8ccb",
+            "cat": [
+              "IAB1"
+            ],
+            "privacypolicy": 1,
+            "publisher": {
+              "id": "IY1002",
+              "name": "The View Point",
+              "domain": "krushmedia.com"
+            },
+            "content": {
+              "language": "en"
+            }
+          },
+          "device": {
+            "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36",
+            "geo": {
+              "lat": 20.6668,
+              "lon": -103.3918,
+              "accuracy": 5,
+              "country": "MEX",
+              "region": "JAL",
+              "zip": "45186"
+            },
+            "dnt": 0,
+            "lmt": 0,
+            "ip": "187.152.119.145",
+            "devicetype": 2,
+            "os": "Windows",
+            "osv": "10",
+            "carrier": "Telmex"
+          },
+          "test": 1,
+          "at": 1,
+          "tmax": 500,
+          "cur": [
+            "USD"
+          ],
+          "source": {
+            "tid": "42da74bc-88c5-40ea-8187-9070a34e8e10"
+          },
+          "regs": {
+            "ext": {
+              "gdpr": 0
+            }
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 401
+      }
+    }
+  ],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "Something went wrong, please contact your Account Manager. Status Code: [ 401 ] ",
+      "comparison": "literal"
+    }
+  ],
+  "expectedBidResponses": []
+}
\ No newline at end of file
diff --git a/adapters/infytv/params_test.go b/adapters/infytv/params_test.go
new file mode 100644
index 00000000..6719b102
--- /dev/null
+++ b/adapters/infytv/params_test.go
@@ -0,0 +1,44 @@
+package infytv
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+func TestValidParams(t *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+	if err != nil {
+		t.Fatalf("Failed to fetch the json schema. %v", err)
+	}
+
+	for _, p := range validParams {
+		if err := validator.Validate(openrtb_ext.BidderInfyTV, json.RawMessage(p)); err != nil {
+			t.Errorf("Schema rejected valid params: %s", p)
+		}
+	}
+}
+
+func TestInvalidParams(t *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+	if err != nil {
+		t.Fatalf("Failed to fetch the json schema. %v", err)
+	}
+
+	for _, p := range invalidParams {
+		if err := validator.Validate(openrtb_ext.BidderInfyTV, json.RawMessage(p)); err == nil {
+			t.Errorf("Schema allowed invalid params: %s", p)
+		}
+	}
+}
+
+var validParams = []string{
+	`{"publisherId": "1598991608990"}`,
+	`{"publisherId": "1598991608990", "placementId": "9999"}`,
+}
+
+var invalidParams = []string{
+	`{"publisherId": 42}`,
+	`{"publisherId": 42, "placementId":9898}`,
+}
diff --git a/adapters/pubmatic/pubmatictest/supplemental/extra-bid.json b/adapters/pubmatic/pubmatictest/supplemental/extra-bid.json
new file mode 100644
index 00000000..a741f4a4
--- /dev/null
+++ b/adapters/pubmatic/pubmatictest/supplemental/extra-bid.json
@@ -0,0 +1,195 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "imp": [{
+            "id": "test-imp-id",
+            "banner": {
+                "format": [{
+                    "w": 300,
+                    "h": 250
+                }]
+            },
+            "ext": {
+                "bidder": {
+                    "adSlot": "AdTag_Div1@300x250",
+                    "publisherId": "999",
+                    "keywords": [{
+                        "key": "pmZoneID",
+                        "value": ["Zone1", "Zone2"]
+                      },
+                      {
+                         "key": "preference",
+                         "value": ["sports", "movies"]
+                      }
+                    ],
+                    "kadfloor": "0.12",
+                    "wrapper": {
+                        "version": 1,
+                        "profile": 5123
+                    }
+                }
+            }
+        }],
+        "device":{
+            "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36"
+        },
+        "ext": {
+          "prebid": {
+            "bidderparams": {
+              "acat": ["drg","dlu","ssr"]
+            }
+          }
+        },
+        "site": {
+          "id": "siteID",
+            "publisher": {
+              "id": "1234"
+            }
+          }
+     },
+
+     "httpCalls": [
+      {
+        "expectedRequest": {
+          "uri": "https://hbopenbid.pubmatic.com/translator?source=prebid-server",
+          "body": {
+            "id": "test-request-id",
+            "imp": [
+              {
+                "id": "test-imp-id",
+                "tagid":"AdTag_Div1",
+                 "banner": {
+                  "format": [
+                    {
+                      "w": 300,
+                      "h": 250
+                    }
+                  ],
+                  "h": 250,
+                  "w": 300
+                },
+                "bidfloor": 0.12,
+                "ext": {
+                    "pmZoneId": "Zone1,Zone2",
+                    "preference": "sports,movies"
+                }
+              }
+            ], 
+            "device":{
+                "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36"
+            },
+            "site": {
+                "id": "siteID",
+                "publisher": {
+                    "id": "999"
+                }
+            },
+            "ext": {
+                "wrapper": {
+                    "profile": 5123,
+                    "version":1
+                },
+                "acat": ["drg","dlu","ssr"]
+            }
+          }
+        },
+        "mockResponse": {
+          "status": 200,
+          "body": {
+            "id": "test-request-id",
+            "seatbid": [
+              {
+                "seat": "958",
+                "bid": [{
+                  "id": "7706636740145184841",
+                  "impid": "test-imp-id",
+                  "price": 0.500000,
+                  "adid": "29681110",
+                  "adm": "some-test-ad",
+                  "adomain": ["pubmatic.com"],
+                  "crid": "29681110",
+                  "h": 250,
+                  "w": 300,
+                  "dealid":"test deal",
+                  "ext": {
+                    "dspid": 6,
+                    "deal_channel": 1
+                  }
+                }]
+              },
+              {
+                "seat": "123",
+                "bid": [{
+                  "id": "1814876ae93d31",
+                  "impid": "test-imp-id",
+                  "price": 0.500000,
+                  "adid": "29681110",
+                  "adm": "some-test-ad",
+                  "adomain": ["mystartab.com"],
+                  "crid": "29681110",
+                  "h": 250,
+                  "w": 300,
+                  "dealid":"test deal",
+                  "ext": {
+                    "dspid": 6,
+                    "deal_channel": 1,
+                    "marketplace": "groupm"
+                  }
+                }]
+              }
+            ],
+            "bidid": "5778926625248726496",
+            "cur": "USD"
+          }
+        }
+      }
+    ],
+
+    "expectedBidResponses": [
+      {
+        "currency": "USD",
+        "bids": [
+          {
+            "bid": {
+              "id": "7706636740145184841",
+              "impid": "test-imp-id",
+              "price": 0.5,
+              "adid": "29681110",
+              "adm": "some-test-ad",
+              "adomain": ["pubmatic.com"],
+              "crid": "29681110",
+              "w": 300,
+              "h": 250,
+              "dealid":"test deal",
+              "ext": {
+                "dspid": 6,
+                "deal_channel": 1
+              }
+            },
+            "type": "banner"
+          },
+          {
+            "bid": {
+              "id": "1814876ae93d31",
+              "impid": "test-imp-id",
+              "price": 0.5,
+              "adid": "29681110",
+              "adm": "some-test-ad",
+              "adomain": ["mystartab.com"],
+              "crid": "29681110",
+              "w": 300,
+              "h": 250,
+              "dealid":"test deal",
+              "ext": {
+                "dspid": 6,
+                "deal_channel": 1,
+                "marketplace": "groupm"
+              }
+            },
+            "type": "banner",
+            "seat": "groupm"
+          }
+        ]
+      }
+    ]
+  }
\ No newline at end of file
diff --git a/adapters/unicorn/unicorntest/exemplary/banner-app-without-ext.json b/adapters/unicorn/unicorntest/exemplary/banner-app-without-ext.json
index e56e601e..8b5423a5 100644
--- a/adapters/unicorn/unicorntest/exemplary/banner-app-without-ext.json
+++ b/adapters/unicorn/unicorntest/exemplary/banner-app-without-ext.json
@@ -85,7 +85,6 @@
             "ver": "1.9.0"
           },
           "at": 1,
-          "cur": ["JPY"],
           "device": {
             "connectiontype": 1,
             "ext": {
diff --git a/adapters/unicorn/unicorntest/exemplary/banner-app-without-placementid.json b/adapters/unicorn/unicorntest/exemplary/banner-app-without-placementid.json
index 9940ae71..c05d3b6f 100644
--- a/adapters/unicorn/unicorntest/exemplary/banner-app-without-placementid.json
+++ b/adapters/unicorn/unicorntest/exemplary/banner-app-without-placementid.json
@@ -28,7 +28,7 @@
         }
       }
     ],
-    "cur": ["JPY"],
+    "cur": ["USD"],
     "app": {
       "publisher": {
         "id": "test"
@@ -96,7 +96,7 @@
             "ver": "1.9.0"
           },
           "at": 1,
-          "cur": ["JPY"],
+          "cur": ["USD"],
           "device": {
             "connectiontype": 1,
             "ext": {
diff --git a/adapters/unicorn/unicorntest/exemplary/not-have-cur.json b/adapters/unicorn/unicorntest/exemplary/not-have-cur.json
deleted file mode 100644
index 1f9298c4..00000000
--- a/adapters/unicorn/unicorntest/exemplary/not-have-cur.json
+++ /dev/null
@@ -1,243 +0,0 @@
-{
-  "mockBidRequest": {
-    "at": 1,
-    "imp": [
-      {
-        "banner": {
-          "format": [
-            {
-              "w": 300,
-              "h": 250
-            }
-          ],
-          "api": [5]
-        },
-        "id": "29D2F33E-F865-40DA-9320-16EF77935254",
-        "secure": 1,
-        "ext": {
-          "prebid": {
-            "storedrequest": {
-              "id": "test_unicorn"
-            }
-          },
-          "bidder": {
-            "accountId": 199578,
-            "publisherId": 123456,
-            "mediaId": "test_media",
-            "placementId": "test_placement"
-          },
-          "context": {
-            "data": {
-              "firstPartyData1": ["firstPartyData1"],
-              "uuid": ["xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"]
-            }
-          }
-        }
-      }
-    ],
-    "app": {
-      "publisher": {
-        "id": "test"
-      },
-      "ext": {
-        "prebid": {
-          "source": "prebid-mobile",
-          "version": "1.9.0"
-        }
-      },
-      "bundle": "net.ada.test",
-      "ver": "1.9.0"
-    },
-    "id": "CFD24FB1-916F-467D-8825-34892B315DB7",
-    "source": {
-      "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD"
-    },
-    "device": {
-      "make": "Apple",
-      "osv": "14.4",
-      "connectiontype": 1,
-      "os": "iOS",
-      "w": 320,
-      "model": "Simulator",
-      "ifa": "00000000-0000-0000-0000-000000000000",
-      "devtime": 1612413327,
-      "h": 568,
-      "pxratio": 2,
-      "ext": {
-        "atts": 0
-      }
-    },
-    "user": {
-      "gender": "O"
-    },
-    "ext": {
-      "prebid": {
-        "targeting": {},
-        "cache": {
-          "bids": {}
-        },
-        "storedrequest": {
-          "id": "test"
-        }
-      }
-    }
-  },
-
-  "httpCalls": [
-    {
-      "expectedRequest": {
-        "uri": "https://ds.uncn.jp",
-        "body": {
-          "app": {
-            "bundle": "net.ada.test",
-            "ext": {
-              "prebid": {
-                "source": "prebid-mobile",
-                "version": "1.9.0"
-              }
-            },
-            "publisher": {
-              "id": "test"
-            },
-            "ver": "1.9.0"
-          },
-          "at": 1,
-          "cur": ["JPY"],
-          "device": {
-            "connectiontype": 1,
-            "ext": {
-              "atts": 0
-            },
-            "h": 568,
-            "ifa": "00000000-0000-0000-0000-000000000000",
-            "make": "Apple",
-            "model": "Simulator",
-            "os": "iOS",
-            "osv": "14.4",
-            "pxratio": 2,
-            "w": 320
-          },
-          "ext": {
-            "prebid": {
-              "bidder": null,
-              "is_rewarded_inventory": 0,
-              "storedrequest": {
-                "id": "test"
-              }
-            },
-            "accountId": 199578
-          },
-          "id": "CFD24FB1-916F-467D-8825-34892B315DB7",
-          "imp": [
-            {
-              "banner": {
-                "api": [5],
-                "format": [
-                  {
-                    "h": 250,
-                    "w": 300
-                  }
-                ]
-              },
-              "ext": {
-                "bidder": {
-                  "accountId": 199578,
-                  "publisherId": 123456,
-                  "mediaId": "test_media",
-                  "placementId": "test_placement"
-                },
-                "context": {
-                  "data": {
-                    "firstPartyData1": ["firstPartyData1"],
-                    "uuid": ["xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"]
-                  }
-                }
-              },
-              "tagid": "test_placement",
-              "id": "29D2F33E-F865-40DA-9320-16EF77935254",
-              "secure": 1
-            }
-          ],
-          "source": {
-            "ext": {
-              "bidder": "unicorn",
-              "stype": "prebid_server_uncn"
-            },
-            "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD"
-          },
-          "user": {
-            "gender": "O"
-          }
-        }
-      },
-      "mockResponse": {
-        "url": "https://ds.uncn.jp",
-        "status": 200,
-        "headers": {
-          "Content-Type": ["application/json"],
-          "Date": ["Thu, 04 Feb 2021 06:36:31 GMT"],
-          "Vary": ["Accept-Encoding"]
-        },
-        "body": {
-          "bidid": "f1248280-24ec-4fbc-bdf5-6a866e7dcea4",
-          "cur": "JPY",
-          "ext": {},
-          "id": "CFD24FB1-916F-467D-8825-34892B315DB7",
-          "seatbid": [
-            {
-              "bid": [
-                {
-                  "adid": "uoNYbq1L_-123456",
-                  "adm": "awesome domain",
-                  "adomain": ["example.com"],
-                  "attr": [],
-                  "bundle": "example.com",
-                  "cid": "7314",
-                  "crid": "-123456",
-                  "ext": {},
-                  "h": 250,
-                  "id": "1",
-                  "impid": "29D2F33E-F865-40DA-9320-16EF77935254",
-                  "iurl": "https://example.com/banner.png",
-                  "price": 73.283,
-                  "w": 300
-                }
-              ],
-              "group": 0,
-              "seat": "274"
-            }
-          ],
-          "units": 0
-        }
-      }
-    }
-  ],
-  "expectedBidResponses": [
-    {
-      "Currency": "JPY",
-      "bids": [
-        {
-          "Bid": {
-            "id": "1",
-            "impid": "29D2F33E-F865-40DA-9320-16EF77935254",
-            "price": 73.283,
-            "adm": "awesome domain",
-            "adid": "uoNYbq1L_-123456",
-            "adomain": ["example.com"],
-            "bundle": "example.com",
-            "iurl": "https://example.com/banner.png",
-            "cid": "7314",
-            "crid": "-123456",
-            "w": 300,
-            "h": 250,
-            "ext": {}
-          },
-          "type": "banner",
-          "BidType": "banner",
-          "BidVideo": null,
-          "DealPriority": 0
-        }
-      ]
-    }
-  ]
-}
diff --git a/adapters/unicorn/unicorntest/supplemental/is-cur-usd-specified.json b/adapters/unicorn/unicorntest/supplemental/cannot-parse-accountid.json
similarity index 71%
rename from adapters/unicorn/unicorntest/supplemental/is-cur-usd-specified.json
rename to adapters/unicorn/unicorntest/supplemental/cannot-parse-accountid.json
index 3ee20f73..6bf2b251 100644
--- a/adapters/unicorn/unicorntest/supplemental/is-cur-usd-specified.json
+++ b/adapters/unicorn/unicorntest/supplemental/cannot-parse-accountid.json
@@ -21,21 +21,15 @@
             }
           },
           "bidder": {
-            "accountId": 199578,
+            "accountId": "hogeohge",
             "publisherId": 123456,
             "mediaId": "test_media",
             "placementId": "test_placement"
-          },
-          "context": {
-            "data": {
-              "firstPartyData1": ["firstPartyData1"],
-              "uuid": ["xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"]
-            }
           }
         }
       }
     ],
-    "cur": ["USD"],
+    "cur": ["JPY"],
     "app": {
       "publisher": {
         "id": "test"
@@ -70,21 +64,12 @@
     },
     "user": {
       "gender": "O"
-    },
-    "ext": {
-      "prebid": {
-        "targeting": {},
-        "cache": {
-          "bids": {}
-        },
-        "storedrequest": {
-          "id": "test"
-        }
-      }
     }
   },
-  "expectedMakeRequestsErrors" : [{
-    "value": "Only JPY is supported",
-    "comparison": "literal"
-  }]
+  "expectedMakeRequestsErrors": [
+    {
+      "value": "Error while decoding imp[0].ext: json: cannot unmarshal string into Go struct field ExtImpUnicorn.bidder.accountId of type int",
+      "comparison": "literal"
+    }
+  ]
 }
diff --git a/adapters/unicorn/unicorntest/supplemental/no-accountid.json b/adapters/unicorn/unicorntest/supplemental/no-accountid.json
new file mode 100644
index 00000000..f7a2203c
--- /dev/null
+++ b/adapters/unicorn/unicorntest/supplemental/no-accountid.json
@@ -0,0 +1,74 @@
+{
+  "mockBidRequest": {
+    "at": 1,
+    "imp": [
+      {
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            }
+          ],
+          "api": [5]
+        },
+        "id": "29D2F33E-F865-40DA-9320-16EF77935254",
+        "secure": 1,
+        "ext": {
+          "prebid": {
+            "storedrequest": {
+              "id": "test_unicorn"
+            }
+          },
+          "bidder": {
+            "publisherId": 123456,
+            "mediaId": "test_media",
+            "placementId": "test_placement"
+          }
+        }
+      }
+    ],
+    "cur": ["JPY"],
+    "app": {
+      "publisher": {
+        "id": "test"
+      },
+      "ext": {
+        "prebid": {
+          "source": "prebid-mobile",
+          "version": "1.9.0"
+        }
+      },
+      "bundle": "net.ada.test",
+      "ver": "1.9.0"
+    },
+    "id": "CFD24FB1-916F-467D-8825-34892B315DB7",
+    "source": {
+      "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD"
+    },
+    "device": {
+      "make": "Apple",
+      "osv": "14.4",
+      "connectiontype": 1,
+      "os": "iOS",
+      "w": 320,
+      "model": "Simulator",
+      "ifa": "00000000-0000-0000-0000-000000000000",
+      "devtime": 1612413327,
+      "h": 568,
+      "pxratio": 2,
+      "ext": {
+        "atts": 0
+      }
+    },
+    "user": {
+      "gender": "O"
+    }
+  },
+  "expectedMakeRequestsErrors": [
+    {
+      "value": "accountId field is required",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/visx/visxtest/exemplary/headers_ipv4.json b/adapters/visx/visxtest/exemplary/headers_ipv4.json
new file mode 100644
index 00000000..4a4da7b5
--- /dev/null
+++ b/adapters/visx/visxtest/exemplary/headers_ipv4.json
@@ -0,0 +1,177 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "site": {
+            "domain": "good.site",
+            "page": "https://good.site/url",
+            "publisher": {
+                "id": "test-publisher-id"
+            },
+            "ext": {
+                "amp": 0
+            }
+        },
+        "imp": [{
+            "id": "test-imp-id",
+            "banner": {
+                "format": [{
+                    "w": 300,
+                    "h": 250
+                }]
+            },
+            "secure": 1,
+            "ext": {
+                "bidder": {
+                    "size": [
+                        300,
+                        250
+                    ],
+                    "uid": 7
+                }
+            }
+        }],
+        "device": {
+            "ua": "test-user-agent",
+            "ip": "123.123.123.123",
+            "h": 700,
+            "w": 375,
+            "ext": {
+                "prebid": {
+                    "interstitial": {
+                        "minwidthperc": 50,
+                        "minheightperc": 40
+                    }
+                }
+            }
+        },
+        "at": 1,
+        "tmax": 2000,
+        "source": {
+            "tid": "283746293874293"
+        },
+        "ext": {
+            "prebid": {
+                "aliases": {
+                    "districtm": "appnexus",
+                    "emxdigital": "appnexus"
+                }
+            }
+        }
+    },
+
+    "httpCalls": [{
+        "expectedRequest": {
+            "uri": "http://localhost/prebid",
+            "headers": {
+                "Content-Type": ["application/json;charset=utf-8"],
+                "X-Forwarded-For": ["123.123.123.123"]
+            },
+            "body": {
+                "id": "test-request-id",
+                "site": {
+                    "domain": "good.site",
+                    "page": "https://good.site/url",
+                    "publisher": {
+                        "id": "test-publisher-id"
+                    },
+                    "ext": {
+                        "amp": 0
+                    }
+                },
+                "cur": ["USD"],
+                "imp": [{
+                    "id": "test-imp-id",
+                    "banner": {
+                        "format": [{
+                            "w": 300,
+                            "h": 250
+                        }]
+                    },
+                    "secure": 1,
+                    "ext": {
+                        "bidder": {
+                            "size": [
+                                300,
+                                250
+                            ],
+                            "uid": 7
+                        }
+                    }
+                }],
+                "device": {
+                    "ua": "test-user-agent",
+                    "ip": "123.123.123.123",
+                    "h": 700,
+                    "w": 375,
+                    "ext": {
+                        "prebid": {
+                            "interstitial": {
+                                "minwidthperc": 50,
+                                "minheightperc": 40
+                            }
+                        }
+                    }
+                },
+                "at": 1,
+                "tmax": 2000,
+                "source": {
+                    "tid": "283746293874293"
+                },
+                "ext": {
+                    "prebid": {
+                        "aliases": {
+                            "districtm": "appnexus",
+                            "emxdigital": "appnexus"
+                        }
+                    }
+                }
+            }
+        },
+        "mockResponse": {
+            "status": 200,
+            "body": {
+                "cur": ["USD"],
+                "seatbid": [
+                    {
+                        "bid": [
+                            {
+                                "crid": "2_260",
+                                "price": 0.500000,
+                                "adm": "some-test-ad",
+                                "impid": "test-imp-id",
+                                "auid": 46,
+                                "h": 250,
+                                "adomain": [
+                                    "goodadvertiser.com"
+                                ],
+                                "dealid": "test_deal_id",
+                                "w": 300
+                            }
+                        ],
+                        "seat": "51"
+                    }
+                ]
+            }
+        }
+    }],
+
+    "expectedBidResponses": [{
+        "currency": "USD",
+        "bids": [{
+            "bid": {
+                "price": 0.5,
+                "adm": "some-test-ad",
+                "impid": "test-imp-id",
+                "id": "test-request-id",
+                "h": 250,
+                "adomain": [
+                    "goodadvertiser.com"
+                ],
+                "dealid": "test_deal_id",
+                "crid": "2_260",
+                "w": 300
+            },
+            "type": "banner"
+        }]
+    }]
+}
diff --git a/adapters/visx/visxtest/exemplary/headers_ipv6.json b/adapters/visx/visxtest/exemplary/headers_ipv6.json
new file mode 100644
index 00000000..a3774c5e
--- /dev/null
+++ b/adapters/visx/visxtest/exemplary/headers_ipv6.json
@@ -0,0 +1,177 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "site": {
+            "domain": "good.site",
+            "page": "https://good.site/url",
+            "publisher": {
+                "id": "test-publisher-id"
+            },
+            "ext": {
+                "amp": 0
+            }
+        },
+        "imp": [{
+            "id": "test-imp-id",
+            "banner": {
+                "format": [{
+                    "w": 300,
+                    "h": 250
+                }]
+            },
+            "secure": 1,
+            "ext": {
+                "bidder": {
+                    "size": [
+                        300,
+                        250
+                    ],
+                    "uid": 7
+                }
+            }
+        }],
+        "device": {
+            "ua": "test-user-agent",
+            "ipv6": "2001:0db8:85a3:0000:0000:8a2e:0370:7334",
+            "h": 700,
+            "w": 375,
+            "ext": {
+                "prebid": {
+                    "interstitial": {
+                        "minwidthperc": 50,
+                        "minheightperc": 40
+                    }
+                }
+            }
+        },
+        "at": 1,
+        "tmax": 2000,
+        "source": {
+            "tid": "283746293874293"
+        },
+        "ext": {
+            "prebid": {
+                "aliases": {
+                    "districtm": "appnexus",
+                    "emxdigital": "appnexus"
+                }
+            }
+        }
+    },
+
+    "httpCalls": [{
+        "expectedRequest": {
+            "uri": "http://localhost/prebid",
+            "headers": {
+                "Content-Type": ["application/json;charset=utf-8"],
+                "X-Forwarded-For": ["2001:0db8:85a3:0000:0000:8a2e:0370:7334"]
+            },
+            "body": {
+                "id": "test-request-id",
+                "site": {
+                    "domain": "good.site",
+                    "page": "https://good.site/url",
+                    "publisher": {
+                        "id": "test-publisher-id"
+                    },
+                    "ext": {
+                        "amp": 0
+                    }
+                },
+                "cur": ["USD"],
+                "imp": [{
+                    "id": "test-imp-id",
+                    "banner": {
+                        "format": [{
+                            "w": 300,
+                            "h": 250
+                        }]
+                    },
+                    "secure": 1,
+                    "ext": {
+                        "bidder": {
+                            "size": [
+                                300,
+                                250
+                            ],
+                            "uid": 7
+                        }
+                    }
+                }],
+                "device": {
+                    "ua": "test-user-agent",
+                    "ipv6": "2001:0db8:85a3:0000:0000:8a2e:0370:7334",
+                    "h": 700,
+                    "w": 375,
+                    "ext": {
+                        "prebid": {
+                            "interstitial": {
+                                "minwidthperc": 50,
+                                "minheightperc": 40
+                            }
+                        }
+                    }
+                },
+                "at": 1,
+                "tmax": 2000,
+                "source": {
+                    "tid": "283746293874293"
+                },
+                "ext": {
+                    "prebid": {
+                        "aliases": {
+                            "districtm": "appnexus",
+                            "emxdigital": "appnexus"
+                        }
+                    }
+                }
+            }
+        },
+        "mockResponse": {
+            "status": 200,
+            "body": {
+                "cur": ["USD"],
+                "seatbid": [
+                    {
+                        "bid": [
+                            {
+                                "crid": "2_260",
+                                "price": 0.500000,
+                                "adm": "some-test-ad",
+                                "impid": "test-imp-id",
+                                "auid": 46,
+                                "h": 250,
+                                "adomain": [
+                                    "goodadvertiser.com"
+                                ],
+                                "dealid": "test_deal_id",
+                                "w": 300
+                            }
+                        ],
+                        "seat": "51"
+                    }
+                ]
+            }
+        }
+    }],
+
+    "expectedBidResponses": [{
+        "currency": "USD",
+        "bids": [{
+            "bid": {
+                "price": 0.5,
+                "adm": "some-test-ad",
+                "impid": "test-imp-id",
+                "id": "test-request-id",
+                "h": 250,
+                "adomain": [
+                    "goodadvertiser.com"
+                ],
+                "dealid": "test_deal_id",
+                "crid": "2_260",
+                "w": 300
+            },
+            "type": "banner"
+        }]
+    }]
+}
diff --git a/config/accounts_test.go b/config/accounts_test.go
index e2e0b169..a7675d3a 100644
--- a/config/accounts_test.go
+++ b/config/accounts_test.go
@@ -1,6 +1,7 @@
 package config
 
 import (
+	"errors"
 	"testing"
 
 	"github.com/prebid/go-gdpr/consentconstants"
@@ -707,3 +708,142 @@ func TestBasicEnforcementVendor(t *testing.T) {
 		assert.Equal(t, tt.wantBasicVendorSet, present, tt.description)
 	}
 }
+
+func TestAlternateBidderCodes_IsValidBidderCode(t *testing.T) {
+	type fields struct {
+		Enabled  bool
+		Adapters map[string]AdapterAlternateBidderCodes
+	}
+	type args struct {
+		bidder          string
+		alternateBidder string
+	}
+	tests := []struct {
+		name        string
+		fields      fields
+		args        args
+		wantIsValid bool
+		wantErr     error
+	}{
+		{
+			name:        "alternateBidder is not set/blank (default non-extra bid case)",
+			wantIsValid: true,
+		},
+		{
+			name: "alternateBidder and bidder are same (default non-extra bid case with seat's alternateBidder explicitly set)",
+			args: args{
+				bidder:          "pubmatic",
+				alternateBidder: "pubmatic",
+			},
+			wantIsValid: true,
+		},
+		{
+			name: "account.alternatebiddercodes config not defined (default, reject bid)",
+			args: args{
+				bidder:          "pubmatic",
+				alternateBidder: "groupm",
+			},
+			wantIsValid: false,
+		},
+		{
+			name: "account.alternatebiddercodes config enabled but adapter config not defined",
+			args: args{
+				bidder:          "pubmatic",
+				alternateBidder: "groupm",
+			},
+			fields:      fields{Enabled: true},
+			wantIsValid: false,
+			wantErr:     errors.New(`alternateBidderCodes not defined for adapter "pubmatic", rejecting bids for "groupm"`),
+		},
+		{
+			name: "account.alternatebiddercodes config enabled but adapter config is not available",
+			args: args{
+				bidder:          "pubmatic",
+				alternateBidder: "groupm",
+			},
+			fields: fields{
+				Enabled: true,
+				Adapters: map[string]AdapterAlternateBidderCodes{
+					"appnexus": {},
+				},
+			},
+			wantIsValid: false,
+			wantErr:     errors.New(`alternateBidderCodes not defined for adapter "pubmatic", rejecting bids for "groupm"`),
+		},
+		{
+			name: "account.alternatebiddercodes config enabled but adapter config has allowedBidderCodes list empty or not defined",
+			args: args{
+				bidder:          "pubmatic",
+				alternateBidder: "groupm",
+			},
+			fields: fields{
+				Enabled: true,
+				Adapters: map[string]AdapterAlternateBidderCodes{
+					"pubmatic": {},
+				},
+			},
+			wantIsValid: false,
+			wantErr:     errors.New(`alternateBidderCodes disabled for "pubmatic", rejecting bids for "groupm"`),
+		},
+		{
+			name: "allowedBidderCodes is *",
+			args: args{
+				bidder:          "pubmatic",
+				alternateBidder: "groupm",
+			},
+			fields: fields{
+				Enabled: true,
+				Adapters: map[string]AdapterAlternateBidderCodes{
+					"pubmatic": {
+						AllowedBidderCodes: []string{"*"},
+					},
+				},
+			},
+			wantIsValid: true,
+		},
+		{
+			name: "allowedBidderCodes is in the list",
+			args: args{
+				bidder:          "pubmatic",
+				alternateBidder: "groupm",
+			},
+			fields: fields{
+				Enabled: true,
+				Adapters: map[string]AdapterAlternateBidderCodes{
+					"pubmatic": {
+						AllowedBidderCodes: []string{"groupm"},
+					},
+				},
+			},
+			wantIsValid: true,
+		},
+		{
+			name: "allowedBidderCodes is not in the list",
+			args: args{
+				bidder:          "pubmatic",
+				alternateBidder: "groupm",
+			},
+			fields: fields{
+				Enabled: true,
+				Adapters: map[string]AdapterAlternateBidderCodes{
+					"pubmatic": {
+						AllowedBidderCodes: []string{"xyz"},
+					},
+				},
+			},
+			wantIsValid: false,
+			wantErr:     errors.New(`invalid biddercode "groupm" sent by adapter "pubmatic"`),
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			a := &AlternateBidderCodes{
+				Enabled:  tt.fields.Enabled,
+				Adapters: tt.fields.Adapters,
+			}
+			gotIsValid, gotErr := a.IsValidBidderCode(tt.args.bidder, tt.args.alternateBidder)
+			assert.Equal(t, tt.wantIsValid, gotIsValid)
+			assert.Equal(t, tt.wantErr, gotErr)
+		})
+	}
+}
diff --git a/endpoints/openrtb2/interstitial_test.go b/endpoints/openrtb2/interstitial_test.go
index fe0ed966..0a3254b4 100644
--- a/endpoints/openrtb2/interstitial_test.go
+++ b/endpoints/openrtb2/interstitial_test.go
@@ -33,6 +33,30 @@ var request = &openrtb2.BidRequest{
 	},
 }
 
+var requestWithoutPrebidDeviceExt = &openrtb2.BidRequest{
+	ID: "some-id",
+	Imp: []openrtb2.Imp{
+		{
+			ID: "my-imp-id",
+			Banner: &openrtb2.Banner{
+				Format: []openrtb2.Format{
+					{
+						W: 300,
+						H: 600,
+					},
+				},
+			},
+			Instl: 1,
+			Ext:   json.RawMessage(`{"appnexus": {"placementId": 12883451}}`),
+		},
+	},
+	Device: &openrtb2.Device{
+		H:   640,
+		W:   320,
+		Ext: json.RawMessage(`{"field": 1}`),
+	},
+}
+
 func TestInterstitial(t *testing.T) {
 	myRequest := request
 	if err := processInterstitials(&openrtb_ext.RequestWrapper{BidRequest: myRequest}); err != nil {
@@ -83,3 +107,17 @@ func TestInterstitial(t *testing.T) {
 	assert.Equal(t, targetFormat, myRequest.Imp[0].Banner.Format)
 
 }
+
+func TestInterstitialWithoutPrebidDeviceExt(t *testing.T) {
+	myRequest := requestWithoutPrebidDeviceExt
+	if err := processInterstitials(&openrtb_ext.RequestWrapper{BidRequest: myRequest}); err != nil {
+		t.Fatalf("Error processing interstitials: %v", err)
+	}
+	targetFormat := []openrtb2.Format{
+		{
+			W: 300,
+			H: 600,
+		},
+	}
+	assert.Equal(t, targetFormat, myRequest.Imp[0].Banner.Format)
+}
diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go
index 719af304..27ceb96d 100644
--- a/exchange/bidder_test.go
+++ b/exchange/bidder_test.go
@@ -11,6 +11,7 @@ import (
 	"net/http"
 	"net/http/httptest"
 	"net/http/httptrace"
+	"sort"
 	"strings"
 	"testing"
 	"time"
@@ -54,7 +55,7 @@ func TestSingleBidder(t *testing.T) {
 	requestHeaders := http.Header{}
 	requestHeaders.Add("Content-Type", "application/json")
 
-	bidAdjustment := 2.0
+	bidAdjustments := map[string]float64{"test": 2.0}
 	firstInitialPrice := 3.0
 	secondInitialPrice := 4.0
 
@@ -98,7 +99,9 @@ func TestSingleBidder(t *testing.T) {
 			BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}},
 			BidderName: "test",
 		}
-		seatBid, errs := bidder.requestBid(ctx, bidderReq, bidAdjustment, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, false)
+		seatBids, errs := bidder.requestBid(ctx, bidderReq, bidAdjustments, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, false, config.AlternateBidderCodes{})
+		assert.Len(t, seatBids, 1)
+		seatBid := seatBids[0]
 
 		// Make sure the goodSingleBidder was called with the expected arguments.
 		if bidderImpl.httpResponse == nil {
@@ -133,6 +136,7 @@ func TestSingleBidder(t *testing.T) {
 				t.Errorf("Bid %d did not have the right deal priority. Expected %s, got %s", index, typedBid.BidType, seatBid.bids[index].bidType)
 			}
 		}
+		bidAdjustment := bidAdjustments["test"]
 		if mockBidderResponse.Bids[0].Bid.Price != bidAdjustment*firstInitialPrice {
 			t.Errorf("Bid[0].Price was not adjusted properly. Expected %f, got %f", bidAdjustment*firstInitialPrice, mockBidderResponse.Bids[0].Bid.Price)
 		}
@@ -184,7 +188,8 @@ func TestRequestBidRemovesSensitiveHeaders(t *testing.T) {
 		BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}},
 		BidderName: "test",
 	}
-	seatBid, errs := bidder.requestBid(ctx, bidderReq, 1, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, false)
+	bidAdjustments := map[string]float64{"test": 1}
+	seatBids, errs := bidder.requestBid(ctx, bidderReq, bidAdjustments, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, false, config.AlternateBidderCodes{})
 
 	expectedHttpCalls := []*openrtb_ext.ExtHttpCall{
 		{
@@ -197,7 +202,8 @@ func TestRequestBidRemovesSensitiveHeaders(t *testing.T) {
 	}
 
 	assert.Empty(t, errs)
-	assert.ElementsMatch(t, seatBid.httpCalls, expectedHttpCalls)
+	assert.Len(t, seatBids, 1)
+	assert.ElementsMatch(t, seatBids[0].httpCalls, expectedHttpCalls)
 }
 
 func TestSetGPCHeader(t *testing.T) {
@@ -228,7 +234,8 @@ func TestSetGPCHeader(t *testing.T) {
 		BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}},
 		BidderName: "test",
 	}
-	seatBid, errs := bidder.requestBid(ctx, bidderReq, 1, currencyConverter.Rates(), &adapters.ExtraRequestInfo{GlobalPrivacyControlHeader: "1"}, true, false)
+	bidAdjustments := map[string]float64{"test": 1}
+	seatBids, errs := bidder.requestBid(ctx, bidderReq, bidAdjustments, currencyConverter.Rates(), &adapters.ExtraRequestInfo{GlobalPrivacyControlHeader: "1"}, true, false, config.AlternateBidderCodes{})
 
 	expectedHttpCall := []*openrtb_ext.ExtHttpCall{
 		{
@@ -241,7 +248,8 @@ func TestSetGPCHeader(t *testing.T) {
 	}
 
 	assert.Empty(t, errs)
-	assert.ElementsMatch(t, seatBid.httpCalls, expectedHttpCall)
+	assert.Len(t, seatBids, 1)
+	assert.ElementsMatch(t, seatBids[0].httpCalls, expectedHttpCall)
 }
 
 func TestSetGPCHeaderNil(t *testing.T) {
@@ -270,7 +278,8 @@ func TestSetGPCHeaderNil(t *testing.T) {
 		BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}},
 		BidderName: "test",
 	}
-	seatBid, errs := bidder.requestBid(ctx, bidderReq, 1, currencyConverter.Rates(), &adapters.ExtraRequestInfo{GlobalPrivacyControlHeader: "1"}, true, false)
+	bidAdjustments := map[string]float64{"test": 1}
+	seatBids, errs := bidder.requestBid(ctx, bidderReq, bidAdjustments, currencyConverter.Rates(), &adapters.ExtraRequestInfo{GlobalPrivacyControlHeader: "1"}, true, false, config.AlternateBidderCodes{})
 
 	expectedHttpCall := []*openrtb_ext.ExtHttpCall{
 		{
@@ -283,7 +292,8 @@ func TestSetGPCHeaderNil(t *testing.T) {
 	}
 
 	assert.Empty(t, errs)
-	assert.ElementsMatch(t, seatBid.httpCalls, expectedHttpCall)
+	assert.Len(t, seatBids, 1)
+	assert.ElementsMatch(t, seatBids[0].httpCalls, expectedHttpCall)
 }
 
 // TestMultiBidder makes sure all the requests get sent, and the responses processed.
@@ -332,17 +342,18 @@ func TestMultiBidder(t *testing.T) {
 		BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}},
 		BidderName: "test",
 	}
-	seatBid, errs := bidder.requestBid(context.Background(), bidderReq, 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, true)
+	bidAdjustments := map[string]float64{"test": 1.0}
+	seatBids, errs := bidder.requestBid(context.Background(), bidderReq, bidAdjustments, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, true, config.AlternateBidderCodes{})
 
-	if seatBid == nil {
+	if len(seatBids) != 1 {
 		t.Fatalf("SeatBid should exist, because bids exist.")
 	}
 
 	if len(errs) != 1+len(bidderImpl.httpRequests) {
 		t.Errorf("Expected %d errors. Got %d", 1+len(bidderImpl.httpRequests), len(errs))
 	}
-	if len(seatBid.bids) != len(bidderImpl.httpResponses)*len(mockBidderResponse.Bids) {
-		t.Errorf("Expected %d bids. Got %d", len(bidderImpl.httpResponses)*len(mockBidderResponse.Bids), len(seatBid.bids))
+	if len(seatBids[0].bids) != len(bidderImpl.httpResponses)*len(mockBidderResponse.Bids) {
+		t.Errorf("Expected %d bids. Got %d", len(bidderImpl.httpResponses)*len(mockBidderResponse.Bids), len(seatBids[0].bids))
 	}
 
 }
@@ -698,15 +709,19 @@ func TestMultiCurrencies(t *testing.T) {
 			BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}},
 			BidderName: openrtb_ext.BidderAppnexus,
 		}
-		seatBid, errs := bidder.requestBid(
+		bidAdjustments := map[string]float64{string(openrtb_ext.BidderAppnexus): 1}
+		seatBids, errs := bidder.requestBid(
 			context.Background(),
 			bidderReq,
-			1,
+			bidAdjustments,
 			currencyConverter.Rates(),
 			&adapters.ExtraRequestInfo{},
 			true,
 			true,
+			config.AlternateBidderCodes{},
 		)
+		assert.Len(t, seatBids, 1)
+		seatBid := seatBids[0]
 
 		// Verify:
 		resultLightBids := make([]bid, len(seatBid.bids))
@@ -848,15 +863,19 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) {
 			BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}},
 			BidderName: "test",
 		}
-		seatBid, errs := bidder.requestBid(
+		bidAdjustments := map[string]float64{"test": 1}
+		seatBids, errs := bidder.requestBid(
 			context.Background(),
 			bidderReq,
-			1,
+			bidAdjustments,
 			currencyConverter.Rates(),
 			&adapters.ExtraRequestInfo{},
 			true,
 			true,
+			config.AlternateBidderCodes{},
 		)
+		assert.Len(t, seatBids, 1)
+		seatBid := seatBids[0]
 
 		// Verify:
 		assert.Equal(t, false, (seatBid == nil && tc.expectedBidsCount != 0), tc.description)
@@ -1017,15 +1036,19 @@ func TestMultiCurrencies_RequestCurrencyPick(t *testing.T) {
 			BidRequest: &openrtb2.BidRequest{Cur: tc.bidRequestCurrencies, Imp: []openrtb2.Imp{{ID: "impId"}}},
 			BidderName: "test",
 		}
-		seatBid, errs := bidder.requestBid(
+		bidAdjustments := map[string]float64{"test": 1}
+		seatBids, errs := bidder.requestBid(
 			context.Background(),
 			bidderReq,
-			1,
+			bidAdjustments,
 			currencyConverter.Rates(),
 			&adapters.ExtraRequestInfo{},
 			true,
 			false,
+			config.AlternateBidderCodes{},
 		)
+		assert.Len(t, seatBids, 1)
+		seatBid := seatBids[0]
 
 		// Verify:
 		if tc.expectedError {
@@ -1325,18 +1348,21 @@ func TestMobileNativeTypes(t *testing.T) {
 			BidRequest: tc.mockBidderRequest,
 			BidderName: "test",
 		}
+		bidAdjustments := map[string]float64{"test": 1.0}
 		seatBids, _ := bidder.requestBid(
 			context.Background(),
 			bidderReq,
-			1.0,
+			bidAdjustments,
 			currencyConverter.Rates(),
 			&adapters.ExtraRequestInfo{},
 			true,
 			true,
+			config.AlternateBidderCodes{},
 		)
+		assert.Len(t, seatBids, 1)
 
 		var actualValue string
-		for _, bid := range seatBids.bids {
+		for _, bid := range seatBids[0].bids {
 			actualValue = bid.bid.AdM
 			assert.JSONEq(t, tc.expectedValue, actualValue, tc.description)
 		}
@@ -1396,18 +1422,21 @@ func TestRequestBidsStoredBidResponses(t *testing.T) {
 			BidderName:            openrtb_ext.BidderAppnexus,
 			BidderStoredResponses: tc.bidderStoredResponses,
 		}
+		bidAdjustments := map[string]float64{string(openrtb_ext.BidderAppnexus): 1.0}
 		seatBids, _ := bidder.requestBid(
 			context.Background(),
 			bidderReq,
-			1.0,
+			bidAdjustments,
 			currencyConverter.Rates(),
 			&adapters.ExtraRequestInfo{},
 			true,
 			true,
+			config.AlternateBidderCodes{},
 		)
+		assert.Len(t, seatBids, 1)
 
-		assert.Len(t, seatBids.bids, len(tc.expectedBidIds), "Incorrect bids number for test case ", tc.description)
-		for index, bid := range seatBids.bids {
+		assert.Len(t, seatBids[0].bids, len(tc.expectedBidIds), "Incorrect bids number for test case ", tc.description)
+		for index, bid := range seatBids[0].bids {
 			assert.Equal(t, tc.expectedBidIds[index], bid.bid.ID, tc.description)
 			assert.Equal(t, tc.expectedImpIds[index], bid.bid.ImpID, tc.description)
 		}
@@ -1422,7 +1451,8 @@ func TestErrorReporting(t *testing.T) {
 		BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}},
 		BidderName: "test",
 	}
-	bids, errs := bidder.requestBid(context.Background(), bidderReq, 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, false)
+	bidAdjustments := map[string]float64{"test": 1.0}
+	bids, errs := bidder.requestBid(context.Background(), bidderReq, bidAdjustments, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, false, config.AlternateBidderCodes{})
 	if bids != nil {
 		t.Errorf("There should be no seatbid if no http requests are returned.")
 	}
@@ -1621,7 +1651,7 @@ func TestCallRecordAdapterConnections(t *testing.T) {
 	defer server.Close()
 
 	// declare requestBid parameters
-	bidAdjustment := 2.0
+	bidAdjustments := map[string]float64{string(openrtb_ext.BidderAppnexus): 2.0}
 
 	bidderImpl := &goodSingleBidder{
 		httpRequest: &adapters.RequestData{
@@ -1648,7 +1678,7 @@ func TestCallRecordAdapterConnections(t *testing.T) {
 		BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}},
 		BidderName: openrtb_ext.BidderAppnexus,
 	}
-	_, errs := bidder.requestBid(context.Background(), bidderReq, bidAdjustment, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, true)
+	_, errs := bidder.requestBid(context.Background(), bidderReq, bidAdjustments, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, true, config.AlternateBidderCodes{})
 
 	// Assert no errors
 	assert.Equal(t, 0, len(errs), "bidder.requestBid returned errors %v \n", errs)
@@ -1973,3 +2003,397 @@ func (bidder *notifyingBidder) MakeBids(internalRequest *openrtb2.BidRequest, ex
 func (bidder *notifyingBidder) MakeTimeoutNotification(req *adapters.RequestData) (*adapters.RequestData, []error) {
 	return &bidder.notifyRequest, nil
 }
+
+func TestExtraBid(t *testing.T) {
+	respStatus := 200
+	respBody := "{\"bid\":false}"
+	server := httptest.NewServer(mockHandler(respStatus, "getBody", respBody))
+	defer server.Close()
+
+	requestHeaders := http.Header{}
+	requestHeaders.Add("Content-Type", "application/json")
+
+	bidderImpl := &goodSingleBidder{
+		httpRequest: &adapters.RequestData{
+			Method:  "POST",
+			Uri:     server.URL,
+			Body:    []byte("{\"key\":\"val\"}"),
+			Headers: http.Header{},
+		},
+		bidResponse: &adapters.BidderResponse{
+			Bids: []*adapters.TypedBid{
+				{
+					Bid: &openrtb2.Bid{
+						ID: "pubmaticImp1",
+					},
+					BidType:      openrtb_ext.BidTypeBanner,
+					DealPriority: 4,
+					Seat:         "pubmatic",
+				},
+				{
+					Bid: &openrtb2.Bid{
+						ID: "groupmImp1",
+					},
+					BidType:      openrtb_ext.BidTypeVideo,
+					DealPriority: 5,
+					Seat:         "groupm",
+				},
+			},
+		},
+	}
+
+	wantSeatBids := []*pbsOrtbSeatBid{
+		{
+			httpCalls: []*openrtb_ext.ExtHttpCall{},
+			bids: []*pbsOrtbBid{{
+				bid:            &openrtb2.Bid{ID: "groupmImp1"},
+				dealPriority:   5,
+				bidType:        openrtb_ext.BidTypeVideo,
+				originalBidCur: "USD",
+				bidMeta:        &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)},
+			}},
+			seat:     "groupm",
+			currency: "USD",
+		},
+		{
+			httpCalls: []*openrtb_ext.ExtHttpCall{},
+			bids: []*pbsOrtbBid{{
+				bid:            &openrtb2.Bid{ID: "pubmaticImp1"},
+				dealPriority:   4,
+				bidType:        openrtb_ext.BidTypeBanner,
+				originalBidCur: "USD",
+				bidMeta:        &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)},
+			}},
+			seat:     string(openrtb_ext.BidderPubmatic),
+			currency: "USD",
+		},
+	}
+
+	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{})
+	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
+
+	bidderReq := BidderRequest{
+		BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}},
+		BidderName: openrtb_ext.BidderPubmatic,
+	}
+	seatBids, errs := bidder.requestBid(context.Background(), bidderReq, map[string]float64{}, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, false, false,
+		config.AlternateBidderCodes{
+			Enabled: true,
+			Adapters: map[string]config.AdapterAlternateBidderCodes{
+				string(openrtb_ext.BidderPubmatic): {
+					AllowedBidderCodes: []string{"groupm"},
+				},
+			},
+		})
+	assert.Nil(t, errs)
+	assert.Len(t, seatBids, 2)
+	sort.Slice(seatBids, func(i, j int) bool {
+		return len(seatBids[i].seat) < len(seatBids[j].seat)
+	})
+	assert.Equal(t, wantSeatBids, seatBids)
+}
+
+func TestExtraBidWithAlternateBidderCodeDisabled(t *testing.T) {
+	respStatus := 200
+	respBody := "{\"bid\":false}"
+	server := httptest.NewServer(mockHandler(respStatus, "getBody", respBody))
+	defer server.Close()
+
+	requestHeaders := http.Header{}
+	requestHeaders.Add("Content-Type", "application/json")
+
+	bidderImpl := &goodSingleBidder{
+		httpRequest: &adapters.RequestData{
+			Method:  "POST",
+			Uri:     server.URL,
+			Body:    []byte("{\"key\":\"val\"}"),
+			Headers: http.Header{},
+		},
+		bidResponse: &adapters.BidderResponse{
+			Bids: []*adapters.TypedBid{
+				{
+					Bid: &openrtb2.Bid{
+						ID: "pubmaticImp1",
+					},
+					BidType:      openrtb_ext.BidTypeBanner,
+					DealPriority: 4,
+					Seat:         "pubmatic",
+				},
+				{
+					Bid: &openrtb2.Bid{
+						ID: "groupmImp1",
+					},
+					BidType:      openrtb_ext.BidTypeVideo,
+					DealPriority: 5,
+					Seat:         "groupm-rejected",
+				},
+				{
+					Bid: &openrtb2.Bid{
+						ID: "groupmImp2",
+					},
+					BidType:      openrtb_ext.BidTypeVideo,
+					DealPriority: 5,
+					Seat:         "groupm-allowed",
+				},
+			},
+		},
+	}
+
+	wantSeatBids := []*pbsOrtbSeatBid{
+		{
+			httpCalls: []*openrtb_ext.ExtHttpCall{},
+			bids: []*pbsOrtbBid{{
+				bid:            &openrtb2.Bid{ID: "groupmImp2"},
+				dealPriority:   5,
+				bidType:        openrtb_ext.BidTypeVideo,
+				originalBidCur: "USD",
+				bidMeta:        &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)},
+			}},
+			seat:     "groupm-allowed",
+			currency: "USD",
+		},
+		{
+			httpCalls: []*openrtb_ext.ExtHttpCall{},
+			bids: []*pbsOrtbBid{{
+				bid:            &openrtb2.Bid{ID: "pubmaticImp1"},
+				dealPriority:   4,
+				bidType:        openrtb_ext.BidTypeBanner,
+				originalBidCur: "USD",
+				bidMeta:        &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)},
+			}},
+			seat:     string(openrtb_ext.BidderPubmatic),
+			currency: "USD",
+		},
+	}
+	wantErrs := []error{
+		&errortypes.Warning{
+			WarningCode: errortypes.AlternateBidderCodeWarningCode,
+			Message:     `invalid biddercode "groupm-rejected" sent by adapter "pubmatic"`,
+		},
+	}
+
+	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{})
+	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
+
+	bidderReq := BidderRequest{
+		BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}},
+		BidderName: openrtb_ext.BidderPubmatic,
+	}
+	seatBids, errs := bidder.requestBid(context.Background(), bidderReq, map[string]float64{}, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, false, false,
+		config.AlternateBidderCodes{
+			Enabled: true,
+			Adapters: map[string]config.AdapterAlternateBidderCodes{
+				string(openrtb_ext.BidderPubmatic): {
+					AllowedBidderCodes: []string{"groupm-allowed"},
+				},
+			},
+		})
+	assert.Equal(t, wantErrs, errs)
+	assert.Len(t, seatBids, 2)
+	assert.ElementsMatch(t, wantSeatBids, seatBids)
+}
+
+func TestExtraBidWithBidAdjustments(t *testing.T) {
+	respStatus := 200
+	respBody := "{\"bid\":false}"
+	server := httptest.NewServer(mockHandler(respStatus, "getBody", respBody))
+	defer server.Close()
+
+	requestHeaders := http.Header{}
+	requestHeaders.Add("Content-Type", "application/json")
+
+	bidderImpl := &goodSingleBidder{
+		httpRequest: &adapters.RequestData{
+			Method:  "POST",
+			Uri:     server.URL,
+			Body:    []byte("{\"key\":\"val\"}"),
+			Headers: http.Header{},
+		},
+		bidResponse: &adapters.BidderResponse{
+			Bids: []*adapters.TypedBid{
+				{
+					Bid: &openrtb2.Bid{
+						ID:    "pubmaticImp1",
+						Price: 3,
+					},
+					BidType:      openrtb_ext.BidTypeBanner,
+					DealPriority: 4,
+					Seat:         "pubmatic",
+				},
+				{
+					Bid: &openrtb2.Bid{
+						ID:    "groupmImp1",
+						Price: 7,
+					},
+					BidType:      openrtb_ext.BidTypeVideo,
+					DealPriority: 5,
+					Seat:         "groupm",
+				},
+			},
+		},
+	}
+
+	wantSeatBids := []*pbsOrtbSeatBid{
+		{
+			httpCalls: []*openrtb_ext.ExtHttpCall{},
+			bids: []*pbsOrtbBid{{
+				bid: &openrtb2.Bid{
+					ID:    "groupmImp1",
+					Price: 21,
+				},
+				dealPriority:   5,
+				bidType:        openrtb_ext.BidTypeVideo,
+				originalBidCPM: 7,
+				originalBidCur: "USD",
+				bidMeta:        &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)},
+			}},
+			seat:     "groupm",
+			currency: "USD",
+		},
+		{
+			httpCalls: []*openrtb_ext.ExtHttpCall{},
+			bids: []*pbsOrtbBid{{
+				bid: &openrtb2.Bid{
+					ID:    "pubmaticImp1",
+					Price: 6,
+				},
+				dealPriority:   4,
+				bidType:        openrtb_ext.BidTypeBanner,
+				originalBidCur: "USD",
+				originalBidCPM: 3,
+				bidMeta:        &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)},
+			}},
+			seat:     string(openrtb_ext.BidderPubmatic),
+			currency: "USD",
+		},
+	}
+
+	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{})
+	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
+
+	bidderReq := BidderRequest{
+		BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}},
+		BidderName: openrtb_ext.BidderPubmatic,
+	}
+	bidAdjustments := map[string]float64{
+		string(openrtb_ext.BidderPubmatic): 2,
+		string(openrtb_ext.BidderGroupm):   3,
+	}
+	seatBids, errs := bidder.requestBid(context.Background(), bidderReq, bidAdjustments, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, false, false,
+		config.AlternateBidderCodes{
+			Enabled: true,
+			Adapters: map[string]config.AdapterAlternateBidderCodes{
+				string(openrtb_ext.BidderPubmatic): {
+					AllowedBidderCodes: []string{"groupm"},
+				},
+			},
+		})
+	assert.Nil(t, errs)
+	assert.Len(t, seatBids, 2)
+	sort.Slice(seatBids, func(i, j int) bool {
+		return len(seatBids[i].seat) < len(seatBids[j].seat)
+	})
+	assert.Equal(t, wantSeatBids, seatBids)
+}
+
+func TestExtraBidWithBidAdjustmentsUsingAdapterCode(t *testing.T) {
+	respStatus := 200
+	respBody := "{\"bid\":false}"
+	server := httptest.NewServer(mockHandler(respStatus, "getBody", respBody))
+	defer server.Close()
+
+	requestHeaders := http.Header{}
+	requestHeaders.Add("Content-Type", "application/json")
+
+	bidderImpl := &goodSingleBidder{
+		httpRequest: &adapters.RequestData{
+			Method:  "POST",
+			Uri:     server.URL,
+			Body:    []byte("{\"key\":\"val\"}"),
+			Headers: http.Header{},
+		},
+		bidResponse: &adapters.BidderResponse{
+			Bids: []*adapters.TypedBid{
+				{
+					Bid: &openrtb2.Bid{
+						ID:    "pubmaticImp1",
+						Price: 3,
+					},
+					BidType:      openrtb_ext.BidTypeBanner,
+					DealPriority: 4,
+					Seat:         "pubmatic",
+				},
+				{
+					Bid: &openrtb2.Bid{
+						ID:    "groupmImp1",
+						Price: 7,
+					},
+					BidType:      openrtb_ext.BidTypeVideo,
+					DealPriority: 5,
+					Seat:         "groupm",
+				},
+			},
+		},
+	}
+
+	wantSeatBids := []*pbsOrtbSeatBid{
+		{
+			httpCalls: []*openrtb_ext.ExtHttpCall{},
+			bids: []*pbsOrtbBid{{
+				bid: &openrtb2.Bid{
+					ID:    "groupmImp1",
+					Price: 14,
+				},
+				dealPriority:   5,
+				bidType:        openrtb_ext.BidTypeVideo,
+				originalBidCPM: 7,
+				originalBidCur: "USD",
+				bidMeta:        &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)},
+			}},
+			seat:     "groupm",
+			currency: "USD",
+		},
+		{
+			httpCalls: []*openrtb_ext.ExtHttpCall{},
+			bids: []*pbsOrtbBid{{
+				bid: &openrtb2.Bid{
+					ID:    "pubmaticImp1",
+					Price: 6,
+				},
+				dealPriority:   4,
+				bidType:        openrtb_ext.BidTypeBanner,
+				originalBidCur: "USD",
+				originalBidCPM: 3,
+				bidMeta:        &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)},
+			}},
+			seat:     string(openrtb_ext.BidderPubmatic),
+			currency: "USD",
+		},
+	}
+
+	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{})
+	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
+
+	bidderReq := BidderRequest{
+		BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}},
+		BidderName: openrtb_ext.BidderPubmatic,
+	}
+	bidAdjustments := map[string]float64{
+		string(openrtb_ext.BidderPubmatic): 2,
+	}
+	seatBids, errs := bidder.requestBid(context.Background(), bidderReq, bidAdjustments, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, false, false,
+		config.AlternateBidderCodes{
+			Enabled: true,
+			Adapters: map[string]config.AdapterAlternateBidderCodes{
+				string(openrtb_ext.BidderPubmatic): {
+					AllowedBidderCodes: []string{"groupm"},
+				},
+			},
+		})
+	assert.Nil(t, errs)
+	assert.Len(t, seatBids, 2)
+	sort.Slice(seatBids, func(i, j int) bool {
+		return len(seatBids[i].seat) < len(seatBids[j].seat)
+	})
+	assert.Equal(t, wantSeatBids, seatBids)
+}
diff --git a/exchange/bidder_validate_bids_test.go b/exchange/bidder_validate_bids_test.go
index 364726c2..9e12fc05 100644
--- a/exchange/bidder_validate_bids_test.go
+++ b/exchange/bidder_validate_bids_test.go
@@ -6,6 +6,7 @@ import (
 
 	"github.com/mxmCherry/openrtb/v15/openrtb2"
 	"github.com/prebid/prebid-server/adapters"
+	"github.com/prebid/prebid-server/config"
 	"github.com/prebid/prebid-server/currency"
 	"github.com/prebid/prebid-server/openrtb_ext"
 	"github.com/stretchr/testify/assert"
@@ -13,7 +14,7 @@ import (
 
 func TestAllValidBids(t *testing.T) {
 	var bidder AdaptedBidder = addValidatedBidderMiddleware(&mockAdaptedBidder{
-		bidResponse: &pbsOrtbSeatBid{
+		bidResponse: []*pbsOrtbSeatBid{{
 			bids: []*pbsOrtbBid{
 				{
 					bid: &openrtb2.Bid{
@@ -50,19 +51,21 @@ func TestAllValidBids(t *testing.T) {
 				},
 			},
 		},
-	})
+		}})
 	bidderReq := BidderRequest{
 		BidRequest: &openrtb2.BidRequest{},
 		BidderName: openrtb_ext.BidderAppnexus,
 	}
-	seatBid, errs := bidder.requestBid(context.Background(), bidderReq, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true, false)
-	assert.Len(t, seatBid.bids, 4)
+	bidAdjustments := map[string]float64{string(openrtb_ext.BidderAppnexus): 1.0}
+	seatBids, errs := bidder.requestBid(context.Background(), bidderReq, bidAdjustments, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true, false, config.AlternateBidderCodes{})
+	assert.Len(t, seatBids, 1)
+	assert.Len(t, seatBids[0].bids, 4)
 	assert.Len(t, errs, 0)
 }
 
 func TestAllBadBids(t *testing.T) {
 	bidder := addValidatedBidderMiddleware(&mockAdaptedBidder{
-		bidResponse: &pbsOrtbSeatBid{
+		bidResponse: []*pbsOrtbSeatBid{{
 			bids: []*pbsOrtbBid{
 				{
 					bid: &openrtb2.Bid{
@@ -112,19 +115,21 @@ func TestAllBadBids(t *testing.T) {
 				{},
 			},
 		},
-	})
+		}})
 	bidderReq := BidderRequest{
 		BidRequest: &openrtb2.BidRequest{},
 		BidderName: openrtb_ext.BidderAppnexus,
 	}
-	seatBid, errs := bidder.requestBid(context.Background(), bidderReq, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true, false)
-	assert.Len(t, seatBid.bids, 0)
+	bidAdjustments := map[string]float64{string(openrtb_ext.BidderAppnexus): 1.0}
+	seatBids, errs := bidder.requestBid(context.Background(), bidderReq, bidAdjustments, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true, false, config.AlternateBidderCodes{})
+	assert.Len(t, seatBids, 1)
+	assert.Len(t, seatBids[0].bids, 0)
 	assert.Len(t, errs, 7)
 }
 
 func TestMixedBids(t *testing.T) {
 	bidder := addValidatedBidderMiddleware(&mockAdaptedBidder{
-		bidResponse: &pbsOrtbSeatBid{
+		bidResponse: []*pbsOrtbSeatBid{{
 			bids: []*pbsOrtbBid{
 				{
 					bid: &openrtb2.Bid{
@@ -185,13 +190,15 @@ func TestMixedBids(t *testing.T) {
 				{},
 			},
 		},
-	})
+		}})
 	bidderReq := BidderRequest{
 		BidRequest: &openrtb2.BidRequest{},
 		BidderName: openrtb_ext.BidderAppnexus,
 	}
-	seatBid, errs := bidder.requestBid(context.Background(), bidderReq, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true, false)
-	assert.Len(t, seatBid.bids, 3)
+	bidAdjustments := map[string]float64{string(openrtb_ext.BidderAppnexus): 1.0}
+	seatBids, errs := bidder.requestBid(context.Background(), bidderReq, bidAdjustments, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true, false, config.AlternateBidderCodes{})
+	assert.Len(t, seatBids, 1)
+	assert.Len(t, seatBids[0].bids, 3)
 	assert.Len(t, errs, 5)
 }
 
@@ -291,11 +298,11 @@ func TestCurrencyBids(t *testing.T) {
 			},
 		}
 		bidder := addValidatedBidderMiddleware(&mockAdaptedBidder{
-			bidResponse: &pbsOrtbSeatBid{
+			bidResponse: []*pbsOrtbSeatBid{{
 				currency: tc.brpCur,
 				bids:     bids,
 			},
-		})
+			}})
 
 		expectedValidBids := len(bids)
 		expectedErrs := 0
@@ -310,18 +317,19 @@ func TestCurrencyBids(t *testing.T) {
 			Cur: tc.brqCur,
 		}
 		bidderRequest := BidderRequest{BidRequest: request, BidderName: openrtb_ext.BidderAppnexus}
-
-		seatBid, errs := bidder.requestBid(context.Background(), bidderRequest, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true, false)
-		assert.Len(t, seatBid.bids, expectedValidBids)
+		bidAdjustments := map[string]float64{string(openrtb_ext.BidderAppnexus): 1.0}
+		seatBids, errs := bidder.requestBid(context.Background(), bidderRequest, bidAdjustments, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true, false, config.AlternateBidderCodes{})
+		assert.Len(t, seatBids, 1)
+		assert.Len(t, seatBids[0].bids, expectedValidBids)
 		assert.Len(t, errs, expectedErrs)
 	}
 }
 
 type mockAdaptedBidder struct {
-	bidResponse   *pbsOrtbSeatBid
+	bidResponse   []*pbsOrtbSeatBid
 	errorResponse []error
 }
 
-func (b *mockAdaptedBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) (*pbsOrtbSeatBid, []error) {
+func (b *mockAdaptedBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, bidAdjustments map[string]float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool, alternateBidderCodes config.AlternateBidderCodes) ([]*pbsOrtbSeatBid, []error) {
 	return b.bidResponse, b.errorResponse
 }
diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go
index 97ad934b..9d44b53e 100644
--- a/exchange/exchange_test.go
+++ b/exchange/exchange_test.go
@@ -9,7 +9,9 @@ import (
 	"io/ioutil"
 	"net/http"
 	"net/http/httptest"
+	"reflect"
 	"regexp"
+	"sort"
 	"strconv"
 	"strings"
 	"testing"
@@ -4094,12 +4096,12 @@ type bidderSpec struct {
 }
 
 type bidderRequest struct {
-	OrtbRequest   openrtb2.BidRequest `json:"ortbRequest"`
-	BidAdjustment float64             `json:"bidAdjustment"`
+	OrtbRequest    openrtb2.BidRequest `json:"ortbRequest"`
+	BidAdjustments map[string]float64  `json:"bidAdjustments"`
 }
 
 type bidderResponse struct {
-	SeatBid   *bidderSeatBid             `json:"pbsSeatBid,omitempty"`
+	SeatBids  []*bidderSeatBid           `json:"pbsSeatBids,omitempty"`
 	Errors    []string                   `json:"errors,omitempty"`
 	HttpCalls []*openrtb_ext.ExtHttpCall `json:"httpCalls,omitempty"`
 }
@@ -4109,13 +4111,15 @@ type bidderResponse struct {
 // JSON property tags on those types are contracts in prod.
 type bidderSeatBid struct {
 	Bids []bidderBid `json:"pbsBids,omitempty"`
+	Seat string      `json:"seat"`
 }
 
 // bidderBid is basically a subset of pbsOrtbBid from exchange/bidder.go.
 // See the comment on bidderSeatBid for more info.
 type bidderBid struct {
-	Bid  *openrtb2.Bid `json:"ortbBid,omitempty"`
-	Type string        `json:"bidType,omitempty"`
+	Bid  *openrtb2.Bid                 `json:"ortbBid,omitempty"`
+	Type string                        `json:"bidType,omitempty"`
+	Meta *openrtb_ext.ExtBidPrebidMeta `json:"bidMeta,omitempty"`
 }
 
 type mockIdFetcher map[string]string
@@ -4139,11 +4143,11 @@ type validatingBidder struct {
 	mockResponses map[string]bidderResponse
 }
 
-func (b *validatingBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) (seatBid *pbsOrtbSeatBid, errs []error) {
+func (b *validatingBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, bidAdjustments map[string]float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool, alternateBidderCodes config.AlternateBidderCodes) (seatBids []*pbsOrtbSeatBid, errs []error) {
 	if expectedRequest, ok := b.expectations[string(bidderRequest.BidderName)]; ok {
 		if expectedRequest != nil {
-			if expectedRequest.BidAdjustment != bidAdjustment {
-				b.t.Errorf("%s: Bidder %s got wrong bid adjustment. Expected %f, got %f", b.fileName, bidderRequest.BidderName, expectedRequest.BidAdjustment, bidAdjustment)
+			if !reflect.DeepEqual(expectedRequest.BidAdjustments, bidAdjustments) {
+				b.t.Errorf("%s: Bidder %s got wrong bid adjustment. Expected %v, got %v", b.fileName, bidderRequest.BidderName, expectedRequest.BidAdjustments, bidAdjustments)
 			}
 			diffOrtbRequests(b.t, fmt.Sprintf("Request to %s in %s", string(bidderRequest.BidderName), b.fileName), &expectedRequest.OrtbRequest, bidderRequest.BidRequest)
 		}
@@ -4152,25 +4156,34 @@ func (b *validatingBidder) requestBid(ctx context.Context, bidderRequest BidderR
 	}
 
 	if mockResponse, ok := b.mockResponses[string(bidderRequest.BidderName)]; ok {
-		if mockResponse.SeatBid != nil {
-			bids := make([]*pbsOrtbBid, len(mockResponse.SeatBid.Bids))
-			for i := 0; i < len(bids); i++ {
-				bids[i] = &pbsOrtbBid{
-					originalBidCPM: mockResponse.SeatBid.Bids[i].Bid.Price,
-					bid:            mockResponse.SeatBid.Bids[i].Bid,
-					bidType:        openrtb_ext.BidType(mockResponse.SeatBid.Bids[i].Type),
+		if len(mockResponse.SeatBids) != 0 {
+			for _, mockSeatBid := range mockResponse.SeatBids {
+				var bids []*pbsOrtbBid
+
+				if len(mockSeatBid.Bids) != 0 {
+					bids = make([]*pbsOrtbBid, len(mockSeatBid.Bids))
+					for i := 0; i < len(bids); i++ {
+						bids[i] = &pbsOrtbBid{
+							originalBidCPM: mockSeatBid.Bids[i].Bid.Price,
+							bid:            mockSeatBid.Bids[i].Bid,
+							bidType:        openrtb_ext.BidType(mockSeatBid.Bids[i].Type),
+							bidMeta:        mockSeatBid.Bids[i].Meta,
+						}
+					}
 				}
-			}
 
-			seatBid = &pbsOrtbSeatBid{
-				bids:      bids,
-				httpCalls: mockResponse.HttpCalls,
+				seatBids = append(seatBids, &pbsOrtbSeatBid{
+					bids:      bids,
+					httpCalls: mockResponse.HttpCalls,
+					seat:      mockSeatBid.Seat,
+				})
 			}
 		} else {
-			seatBid = &pbsOrtbSeatBid{
+			seatBids = []*pbsOrtbSeatBid{{
 				bids:      nil,
 				httpCalls: mockResponse.HttpCalls,
-			}
+				seat:      string(bidderRequest.BidderName),
+			}}
 		}
 
 		for _, err := range mockResponse.Errors {
@@ -4187,9 +4200,9 @@ type capturingRequestBidder struct {
 	req *openrtb2.BidRequest
 }
 
-func (b *capturingRequestBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) (seatBid *pbsOrtbSeatBid, errs []error) {
+func (b *capturingRequestBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, bidAdjustments map[string]float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool, alternateBidderCodes config.AlternateBidderCodes) (seatBid []*pbsOrtbSeatBid, errs []error) {
 	b.req = bidderRequest.BidRequest
-	return &pbsOrtbSeatBid{}, nil
+	return []*pbsOrtbSeatBid{{}}, nil
 }
 
 func diffOrtbRequests(t *testing.T, description string, expected *openrtb2.BidRequest, actual *openrtb2.BidRequest) {
@@ -4237,6 +4250,11 @@ func mapifySeatBids(t *testing.T, context string, seatBids []openrtb2.SeatBid) m
 		if _, ok := seatMap[seatName]; ok {
 			t.Fatalf("%s: Contains duplicate Seat: %s", context, seatName)
 		} else {
+			// The sequence of extra bids for same seat from different bidder is not guaranteed as we randomize the list of adapters
+			// This is w.r.t changes at exchange.go#561 (club bids from different bidders for same extra-bid)
+			sort.Slice(seatBids[i].Bid, func(x, y int) bool {
+				return seatBids[i].Bid[x].Price > seatBids[i].Bid[y].Price
+			})
 			seatMap[seatName] = &seatBids[i]
 		}
 	}
@@ -4289,7 +4307,7 @@ func (e *emptyUsersync) HasAnyLiveSyncs() bool {
 
 type panicingAdapter struct{}
 
-func (panicingAdapter) requestBid(ctx context.Context, bidderRequest BidderRequest, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) (posb *pbsOrtbSeatBid, errs []error) {
+func (panicingAdapter) requestBid(ctx context.Context, bidderRequest BidderRequest, bidAdjustments map[string]float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool, alternateBidderCodes config.AlternateBidderCodes) (posb []*pbsOrtbSeatBid, errs []error) {
 	panic("Panic! Panic! The world is ending!")
 }
 
diff --git a/exchange/exchangetest/aliases.json b/exchange/exchangetest/aliases.json
index 94dea605..59f270e1 100644
--- a/exchange/exchangetest/aliases.json
+++ b/exchange/exchangetest/aliases.json
@@ -64,8 +64,7 @@
               }
             }
           }
-        },
-        "bidAdjustment": 1.0
+        }
       },
       "mockResponse": {
         "errors": ["appnexus-error"]
@@ -101,8 +100,7 @@
               }
             }
           }
-        },
-        "bidAdjustment": 1.0
+        }
       },
       "mockResponse": {
         "errors": ["districtm-error"]
diff --git a/exchange/exchangetest/append-bidder-names.json b/exchange/exchangetest/append-bidder-names.json
index 2c0d46e4..f470ea2d 100644
--- a/exchange/exchangetest/append-bidder-names.json
+++ b/exchange/exchangetest/append-bidder-names.json
@@ -54,7 +54,7 @@
   "outgoingRequests": {
     "appnexus": {
       "mockResponse": {
-        "pbsSeatBid": {
+        "pbsSeatBids": [{
           "pbsBids": [
             {
               "ortbBid": {
@@ -88,8 +88,9 @@
               },
               "bidType": "video"
             }
-          ]
-        }
+          ],
+          "seat": "appnexus"
+        }]
       }
     }
   },
diff --git a/exchange/exchangetest/bid-consolidation-test.json b/exchange/exchangetest/bid-consolidation-test.json
index 99a2a60a..70096575 100644
--- a/exchange/exchangetest/bid-consolidation-test.json
+++ b/exchange/exchangetest/bid-consolidation-test.json
@@ -48,7 +48,7 @@
   "outgoingRequests": {
     "appnexus": {
       "mockResponse": {
-        "pbsSeatBid": {
+        "pbsSeatBids": [{
           "pbsBids": [
             {
               "ortbBid": {
@@ -72,13 +72,14 @@
               },
               "bidType": "video"
             }
-          ]
-        }
+          ],
+          "seat": "appnexus"
+        }]
       }
     },
     "rubicon": {
       "mockResponse": {
-        "pbsSeatBid": {
+        "pbsSeatBids": [{
           "pbsBids": [
             {
               "ortbBid": {
@@ -91,8 +92,9 @@
               },
               "bidType": "video"
             }
-          ]
-        }
+          ],
+          "seat": "rubicon"
+        }]
       }
     }
   },
diff --git a/exchange/exchangetest/bid-ext-prebid-collision.json b/exchange/exchangetest/bid-ext-prebid-collision.json
index 3cc311ad..14a1ba77 100644
--- a/exchange/exchangetest/bid-ext-prebid-collision.json
+++ b/exchange/exchangetest/bid-ext-prebid-collision.json
@@ -39,11 +39,10 @@
                             }
                         }
                     }]
-                },
-                "bidAdjustment": 1.0
+                }
             },
             "mockResponse": {
-                "pbsSeatBid": {
+                "pbsSeatBids": [{
                     "pbsBids": [{
                         "ortbBid": {
                             "id": "apn-bid",
@@ -61,8 +60,9 @@
                             }
                         },
                         "bidType": "video"
-                    }]
-                }
+                    }],
+                    "seat": "appnexus"
+                }]
             }
         }
     },
diff --git a/exchange/exchangetest/bid-ext.json b/exchange/exchangetest/bid-ext.json
index c208f73c..56c08205 100644
--- a/exchange/exchangetest/bid-ext.json
+++ b/exchange/exchangetest/bid-ext.json
@@ -39,11 +39,10 @@
                             }
                         }
                     }]
-                },
-                "bidAdjustment": 1.0
+                }
             },
             "mockResponse": {
-                "pbsSeatBid": {
+                "pbsSeatBids": [{
                     "pbsBids": [{
                         "ortbBid": {
                             "id": "apn-bid",
@@ -58,8 +57,9 @@
                             }
                         },
                         "bidType": "video"
-                    }]
-                }
+                    }],
+                    "seat": "appnexus"
+                }]
             }
         }
     },
diff --git a/exchange/exchangetest/bid-id-invalid.json b/exchange/exchangetest/bid-id-invalid.json
index 278331d4..aa59965b 100644
--- a/exchange/exchangetest/bid-id-invalid.json
+++ b/exchange/exchangetest/bid-id-invalid.json
@@ -45,7 +45,7 @@
   "outgoingRequests": {
     "appnexus": {
       "mockResponse": {
-        "pbsSeatBid": {
+        "pbsSeatBids": [{
           "pbsBids": [
             {
               "ortbBid": {
@@ -65,8 +65,9 @@
                 "PrimaryCategory": ""
               }
             }
-          ]
-        }
+          ],
+          "seat": "appnexus"
+        }]
       }
     }
   },
diff --git a/exchange/exchangetest/bid-id-valid.json b/exchange/exchangetest/bid-id-valid.json
index f353514f..4fd71470 100644
--- a/exchange/exchangetest/bid-id-valid.json
+++ b/exchange/exchangetest/bid-id-valid.json
@@ -45,7 +45,7 @@
   "outgoingRequests": {
     "appnexus": {
       "mockResponse": {
-        "pbsSeatBid": {
+        "pbsSeatBids": [{
           "pbsBids": [
             {
               "ortbBid": {
@@ -65,8 +65,9 @@
                 "PrimaryCategory": ""
               }
             }
-          ]
-        }
+          ],
+          "seat": "appnexus"
+        }]
       }
     }
   },
diff --git a/exchange/exchangetest/bidadjustmentfactors.json b/exchange/exchangetest/bidadjustmentfactors.json
index 301d98c1..1f096bfb 100644
--- a/exchange/exchangetest/bidadjustmentfactors.json
+++ b/exchange/exchangetest/bidadjustmentfactors.json
@@ -67,7 +67,10 @@
             }
           }
         },
-        "bidAdjustment": 2.5
+        "bidAdjustments": {
+          "appnexus": 2.5,
+          "districtm": 0.3
+        }
       },
       "mockResponse": {
         "errors": ["appnexus-error"]
@@ -105,7 +108,10 @@
             }
           }
         },
-        "bidAdjustment": 0.3
+        "bidAdjustments": {
+          "appnexus": 2.5,
+          "districtm": 0.3
+        }
       },
       "mockResponse": {
         "errors": ["districtm-error"]
diff --git a/exchange/exchangetest/ccpa-featureflag-off.json b/exchange/exchangetest/ccpa-featureflag-off.json
index d5445ccb..19e59510 100644
--- a/exchange/exchangetest/ccpa-featureflag-off.json
+++ b/exchange/exchangetest/ccpa-featureflag-off.json
@@ -54,8 +54,7 @@
                     "user": {
                         "buyeruid": "some-buyer-id"
                     }
-                },
-                "bidAdjustment": 1.0
+                }
             },
             "mockResponse": {
                 "errors": ["appnexus-error"]
diff --git a/exchange/exchangetest/ccpa-featureflag-on.json b/exchange/exchangetest/ccpa-featureflag-on.json
index 97a7c3a9..2689f47c 100644
--- a/exchange/exchangetest/ccpa-featureflag-on.json
+++ b/exchange/exchangetest/ccpa-featureflag-on.json
@@ -53,8 +53,7 @@
                     },
                     "user": {
                     }
-                },
-                "bidAdjustment": 1.0
+                }
             },
             "mockResponse": {
                 "errors": ["appnexus-error"]
diff --git a/exchange/exchangetest/ccpa-nosale-any-bidder.json b/exchange/exchangetest/ccpa-nosale-any-bidder.json
index f7abd91f..db98878d 100644
--- a/exchange/exchangetest/ccpa-nosale-any-bidder.json
+++ b/exchange/exchangetest/ccpa-nosale-any-bidder.json
@@ -64,8 +64,7 @@
                     "user": {
                         "buyeruid": "some-buyer-id"
                     }
-                },
-                "bidAdjustment": 1.0
+                }
             },
             "mockResponse": {
                 "errors": ["appnexus-error"]
diff --git a/exchange/exchangetest/ccpa-nosale-specific-bidder.json b/exchange/exchangetest/ccpa-nosale-specific-bidder.json
index b89e29ae..ffa4e9c7 100644
--- a/exchange/exchangetest/ccpa-nosale-specific-bidder.json
+++ b/exchange/exchangetest/ccpa-nosale-specific-bidder.json
@@ -64,8 +64,7 @@
                     "user": {
                         "buyeruid": "some-buyer-id"
                     }
-                },
-                "bidAdjustment": 1.0
+                }
             },
             "mockResponse": {
                 "errors": ["appnexus-error"]
diff --git a/exchange/exchangetest/debuglog_disabled.json b/exchange/exchangetest/debuglog_disabled.json
index c4b25a01..76f6ee06 100644
--- a/exchange/exchangetest/debuglog_disabled.json
+++ b/exchange/exchangetest/debuglog_disabled.json
@@ -63,7 +63,7 @@
   "outgoingRequests": {
     "appnexus": {
       "mockResponse": {
-        "pbsSeatBid": {
+        "pbsSeatBids": [{
           "pbsBids": [
             {
               "ortbBid": {
@@ -97,8 +97,9 @@
               },
               "bidType": "video"
             }
-          ]
-        }
+          ],
+          "seat": "appnexus"
+        }]
       }
     }
   },
diff --git a/exchange/exchangetest/debuglog_enabled.json b/exchange/exchangetest/debuglog_enabled.json
index 0073c47c..1bedd06a 100644
--- a/exchange/exchangetest/debuglog_enabled.json
+++ b/exchange/exchangetest/debuglog_enabled.json
@@ -65,7 +65,7 @@
   "outgoingRequests": {
     "appnexus": {
       "mockResponse": {
-        "pbsSeatBid": {
+        "pbsSeatBids": [{
           "pbsBids": [
             {
               "ortbBid": {
@@ -99,8 +99,9 @@
               },
               "bidType": "video"
             }
-          ]
-        }
+          ],
+          "seat": "appnexus"
+        }]
       }
     }
   },
diff --git a/exchange/exchangetest/debuglog_enabled_no_bids.json b/exchange/exchangetest/debuglog_enabled_no_bids.json
index b9bb15df..4f2f141e 100644
--- a/exchange/exchangetest/debuglog_enabled_no_bids.json
+++ b/exchange/exchangetest/debuglog_enabled_no_bids.json
@@ -64,7 +64,7 @@
   "outgoingRequests": {
     "appnexus": {
       "mockResponse": {
-        "pbsSeatBid": {}
+        "pbsSeatBids": [{}]
       }
     }
   },
diff --git a/exchange/exchangetest/eidpermissions-allowed-alias.json b/exchange/exchangetest/eidpermissions-allowed-alias.json
index 65287169..305c1823 100644
--- a/exchange/exchangetest/eidpermissions-allowed-alias.json
+++ b/exchange/exchangetest/eidpermissions-allowed-alias.json
@@ -79,11 +79,10 @@
                             }
                         }
                     }]
-                },
-                "bidAdjustment": 1.0
+                }
             },
             "mockResponse": {
-                "pbsSeatBid": {
+                "pbsSeatBids": [{
                     "pbsBids": [{
                         "ortbBid": {
                             "id": "apn-bid",
@@ -94,8 +93,9 @@
                             "crid": "creative-1"
                         },
                         "bidType": "video"
-                    }]
-                }
+                    }],
+                    "seat": "foo"
+                }]
             }
         }
     },
diff --git a/exchange/exchangetest/eidpermissions-allowed.json b/exchange/exchangetest/eidpermissions-allowed.json
index 4e427cca..4687ec5c 100644
--- a/exchange/exchangetest/eidpermissions-allowed.json
+++ b/exchange/exchangetest/eidpermissions-allowed.json
@@ -73,11 +73,10 @@
                             }
                         }
                     }]
-                },
-                "bidAdjustment": 1.0
+                }
             },
             "mockResponse": {
-                "pbsSeatBid": {
+                "pbsSeatBids": [{
                     "pbsBids": [{
                         "ortbBid": {
                             "id": "apn-bid",
@@ -88,8 +87,9 @@
                             "crid": "creative-1"
                         },
                         "bidType": "video"
-                    }]
-                }
+                    }],
+                    "seat": "appnexus"
+                }]
             }
         }
     },
diff --git a/exchange/exchangetest/eidpermissions-denied.json b/exchange/exchangetest/eidpermissions-denied.json
index d834ea54..373616f9 100644
--- a/exchange/exchangetest/eidpermissions-denied.json
+++ b/exchange/exchangetest/eidpermissions-denied.json
@@ -67,11 +67,10 @@
                             }
                         }
                     }]
-                },
-                "bidAdjustment": 1.0
+                }
             },
             "mockResponse": {
-                "pbsSeatBid": {
+                "pbsSeatBids": [{
                     "pbsBids": [{
                         "ortbBid": {
                             "id": "apn-bid",
@@ -82,8 +81,9 @@
                             "crid": "creative-1"
                         },
                         "bidType": "video"
-                    }]
-                }
+                    }],
+                    "seat": "appnexus"
+                }]
             }
         }
     },
diff --git a/exchange/exchangetest/events-bid-account-off-request-off.json b/exchange/exchangetest/events-bid-account-off-request-off.json
index 3bf1fc9e..da269666 100644
--- a/exchange/exchangetest/events-bid-account-off-request-off.json
+++ b/exchange/exchangetest/events-bid-account-off-request-off.json
@@ -27,7 +27,7 @@
   "outgoingRequests": {
     "appnexus": {
       "mockResponse": {
-        "pbsSeatBid": {
+        "pbsSeatBids": [{
           "pbsBids": [
             {
               "ortbBid": {
@@ -51,8 +51,9 @@
               },
               "bidType": "banner"
             }
-          ]
-        }
+          ],
+          "seat": "appnexus"
+        }]
       }
     }
   },
diff --git a/exchange/exchangetest/events-bid-account-off-request-on.json b/exchange/exchangetest/events-bid-account-off-request-on.json
index 6838ec94..9fcfa2bd 100644
--- a/exchange/exchangetest/events-bid-account-off-request-on.json
+++ b/exchange/exchangetest/events-bid-account-off-request-on.json
@@ -29,7 +29,7 @@
   "outgoingRequests": {
     "appnexus": {
       "mockResponse": {
-        "pbsSeatBid": {
+        "pbsSeatBids": [{
           "pbsBids": [
             {
               "ortbBid": {
@@ -53,8 +53,9 @@
               },
               "bidType": "banner"
             }
-          ]
-        }
+          ],
+          "seat": "appnexus"
+        }]
       }
     }
   },
diff --git a/exchange/exchangetest/events-bid-account-on-request-off.json b/exchange/exchangetest/events-bid-account-on-request-off.json
index 677fa21f..afe73792 100644
--- a/exchange/exchangetest/events-bid-account-on-request-off.json
+++ b/exchange/exchangetest/events-bid-account-on-request-off.json
@@ -28,7 +28,7 @@
   "outgoingRequests": {
     "appnexus": {
       "mockResponse": {
-        "pbsSeatBid": {
+        "pbsSeatBids": [{
           "pbsBids": [
             {
               "ortbBid": {
@@ -52,8 +52,9 @@
               },
               "bidType": "banner"
             }
-          ]
-        }
+          ],
+          "seat": "appnexus"
+        }]
       }
     }
   },
diff --git a/exchange/exchangetest/events-vast-account-off-request-off.json b/exchange/exchangetest/events-vast-account-off-request-off.json
index 9eff3e81..2c4c9ff0 100644
--- a/exchange/exchangetest/events-vast-account-off-request-off.json
+++ b/exchange/exchangetest/events-vast-account-off-request-off.json
@@ -35,7 +35,7 @@
     "appnexus": {
       "modifyingVastXmlAllowed": true,
       "mockResponse": {
-        "pbsSeatBid": {
+        "pbsSeatBids": [{
           "pbsBids": [
             {
               "ortbBid": {
@@ -61,14 +61,15 @@
               },
               "bidType": "video"
             }
-          ]
-        }
+          ],
+          "seat": "appnexus"
+        }]
       }
     },
     "audienceNetwork": {
       "modifyingVastXmlAllowed": false,
       "mockResponse": {
-        "pbsSeatBid": {
+        "pbsSeatBids": [{
           "pbsBids": [
             {
               "ortbBid": {
@@ -82,8 +83,9 @@
               },
               "bidType": "video"
             }
-          ]
-        }
+          ],
+          "seat": "audienceNetwork"
+        }]
       }
     }
   },
diff --git a/exchange/exchangetest/events-vast-account-off-request-on.json b/exchange/exchangetest/events-vast-account-off-request-on.json
index 0b30219d..2bfc2f8d 100644
--- a/exchange/exchangetest/events-vast-account-off-request-on.json
+++ b/exchange/exchangetest/events-vast-account-off-request-on.json
@@ -41,7 +41,7 @@
     "appnexus": {
       "modifyingVastXmlAllowed": true,
       "mockResponse": {
-        "pbsSeatBid": {
+        "pbsSeatBids": [{
           "pbsBids": [
             {
               "ortbBid": {
@@ -67,14 +67,15 @@
               },
               "bidType": "video"
             }
-          ]
-        }
+          ],
+          "seat": "appnexus"
+        }]
       }
     },
     "audienceNetwork": {
       "modifyingVastXmlAllowed": false,
       "mockResponse": {
-        "pbsSeatBid": {
+        "pbsSeatBids": [{
           "pbsBids": [
             {
               "ortbBid": {
@@ -88,8 +89,9 @@
               },
               "bidType": "video"
             }
-          ]
-        }
+          ],
+          "seat": "audienceNetwork"
+        }]
       }
     }
   },
diff --git a/exchange/exchangetest/events-vast-account-on-request-off.json b/exchange/exchangetest/events-vast-account-on-request-off.json
index 5a6d0451..2eaa4adc 100644
--- a/exchange/exchangetest/events-vast-account-on-request-off.json
+++ b/exchange/exchangetest/events-vast-account-on-request-off.json
@@ -36,7 +36,7 @@
     "appnexus": {
       "modifyingVastXmlAllowed": true,
       "mockResponse": {
-        "pbsSeatBid": {
+        "pbsSeatBids": [{
           "pbsBids": [
             {
               "ortbBid": {
@@ -62,14 +62,15 @@
               },
               "bidType": "video"
             }
-          ]
-        }
+          ],
+          "seat": "appnexus"
+        }]
       }
     },
     "audienceNetwork": {
       "modifyingVastXmlAllowed": false,
       "mockResponse": {
-        "pbsSeatBid": {
+        "pbsSeatBids": [{
           "pbsBids": [
             {
               "ortbBid": {
@@ -83,8 +84,9 @@
               },
               "bidType": "video"
             }
-          ]
-        }
+          ],
+          "seat": "audienceNetwork"
+        }]
       }
     }
   },
diff --git a/exchange/exchangetest/explicit-buyeruid.json b/exchange/exchangetest/explicit-buyeruid.json
index 04f69571..fa4b4c85 100644
--- a/exchange/exchangetest/explicit-buyeruid.json
+++ b/exchange/exchangetest/explicit-buyeruid.json
@@ -57,8 +57,7 @@
               }
             }
           ]
-        },
-        "bidAdjustment": 1.0
+        }
       },
       "mockResponse": {
         "errors": ["appnexus-error"]
diff --git a/exchange/exchangetest/extra-bids-with-aliases-adaptercode.json b/exchange/exchangetest/extra-bids-with-aliases-adaptercode.json
new file mode 100644
index 00000000..c3755fd1
--- /dev/null
+++ b/exchange/exchangetest/extra-bids-with-aliases-adaptercode.json
@@ -0,0 +1,229 @@
+{
+  "incomingRequest": {
+    "ortbRequest": {
+      "id": "some-request-id",
+      "site": {
+        "page": "test.somepage.com"
+      },
+      "imp": [
+        {
+          "id": "my-imp-id",
+          "video": {
+            "mimes": ["video/mp4"]
+          },
+          "ext": {
+            "pmbidder": {
+              "publisherId": 5890
+            }
+          }
+        },
+        {
+          "id": "imp-id-2",
+          "video": {
+            "mimes": ["video/mp4"]
+          },
+          "ext": {
+            "pmbidder": {
+              "publisherId": 5890
+            }
+          }
+        }
+      ],
+      "ext": {
+        "prebid": {
+          "targeting": {
+            "includewinners": true,
+            "includebidderkeys": true
+          },
+          "aliases": {
+            "pmbidder": "pubmatic"
+          }
+        }
+      }
+    }
+  },
+  "outgoingRequests": {
+    "pmbidder": {
+      "mockResponse": {
+        "pbsSeatBids": [{
+          "pbsBids": [
+            {
+              "ortbBid": {
+                "id": "winning-bid",
+                "impid": "my-imp-id",
+                "price": 0.71,
+                "w": 200,
+                "h": 250,
+                "crid": "creative-1"
+              },
+              "bidType": "video",
+              "bidMeta": {
+                "adaptercode": "pmbidder"
+              }
+            },
+            {
+              "ortbBid": {
+                "id": "losing-bid",
+                "impid": "my-imp-id",
+                "price": 0.21,
+                "w": 200,
+                "h": 250,
+                "crid": "creative-2"
+              },
+              "bidType": "video",
+              "bidMeta": {
+                "adaptercode": "pmbidder"
+              }
+            },
+            {
+              "ortbBid": {
+                "id": "other-bid",
+                "impid": "imp-id-2",
+                "price": 0.61,
+                "w": 300,
+                "h": 500,
+                "crid": "creative-3"
+              },
+              "bidType": "video",
+              "bidMeta": {
+                "adaptercode": "pmbidder"
+              }
+            }
+          ],
+          "seat": "groupm"
+        },
+        {
+          "pbsBids": [
+            {
+              "ortbBid": {
+                "id": "contending-bid",
+                "impid": "my-imp-id",
+                "price": 0.51,
+                "w": 200,
+                "h": 250,
+                "crid": "creative-4"
+              },
+              "bidType": "video",
+              "bidMeta": {
+                "adaptercode": "pmbidder"
+              }
+            }
+          ],
+          "seat": "pmbidder"
+        }]
+      }
+    }
+  },
+  "response": {
+    "bids": {
+      "id": "some-request-id",
+      "seatbid": [
+        {
+          "seat": "pmbidder",
+          "bid": [{
+            "id": "contending-bid",
+            "impid": "my-imp-id",
+            "price": 0.51,
+            "w": 200,
+            "h": 250,
+            "crid": "creative-4",
+            "ext": {
+              "origbidcpm": 0.51,
+              "prebid": {
+                "meta": {
+                  "adaptercode": "pmbidder"
+                },
+                "type": "video",
+                "targeting": {
+                  "hb_bidder_pmbidder": "pmbidder",
+                  "hb_cache_host_pmbidd": "www.pbcserver.com",
+                  "hb_cache_path_pmbidd": "/pbcache/endpoint",
+                  "hb_pb_pmbidder": "0.50",
+                  "hb_size_pmbidder": "200x250"
+                }
+              }
+            }
+          }]
+        },
+        {
+          "seat": "groupm",
+          "bid": [{
+            "id": "winning-bid",
+            "impid": "my-imp-id",
+            "price": 0.71,
+            "w": 200,
+            "h": 250,
+            "crid": "creative-1",
+            "ext": {
+              "origbidcpm": 0.71,
+              "prebid": {
+                "meta": {
+                  "adaptercode": "pmbidder"
+                },
+                "type": "video",
+                "targeting": {
+                  "hb_bidder": "groupm",
+                  "hb_bidder_groupm": "groupm",
+                  "hb_cache_host": "www.pbcserver.com",
+                  "hb_cache_host_groupm": "www.pbcserver.com",
+                  "hb_cache_path": "/pbcache/endpoint",
+                  "hb_cache_path_groupm": "/pbcache/endpoint",
+                  "hb_pb": "0.70",
+                  "hb_pb_groupm": "0.70",
+                  "hb_size": "200x250",
+                  "hb_size_groupm": "200x250"
+                }
+              }
+            }
+          },
+          {
+            "id": "losing-bid",
+            "impid": "my-imp-id",
+            "price": 0.21,
+            "w": 200,
+            "h": 250,
+            "crid": "creative-2",
+            "ext": {
+              "origbidcpm": 0.21,
+              "prebid": {
+                "meta": {
+                  "adaptercode": "pmbidder"
+                },
+                "type": "video"
+              }
+            }
+          },
+          {
+            "id": "other-bid",
+            "impid": "imp-id-2",
+            "price": 0.61,
+            "w": 300,
+            "h": 500,
+            "crid": "creative-3",
+            "ext": {
+              "origbidcpm": 0.61,
+              "prebid": {
+                "meta": {
+                  "adaptercode": "pmbidder"
+                },
+                "type": "video",
+                "targeting": {
+                  "hb_bidder": "groupm",
+                  "hb_bidder_groupm": "groupm",
+                  "hb_cache_host": "www.pbcserver.com",
+                  "hb_cache_host_groupm": "www.pbcserver.com",
+                  "hb_cache_path": "/pbcache/endpoint",
+                  "hb_cache_path_groupm": "/pbcache/endpoint",
+                  "hb_pb": "0.60",
+                  "hb_pb_groupm": "0.60",
+                  "hb_size": "300x500",
+                  "hb_size_groupm": "300x500"
+                }
+              }
+            }
+          }]
+        }
+      ]
+    }
+  }
+}
diff --git a/exchange/exchangetest/extra-bids.json b/exchange/exchangetest/extra-bids.json
new file mode 100644
index 00000000..5dc3da15
--- /dev/null
+++ b/exchange/exchangetest/extra-bids.json
@@ -0,0 +1,318 @@
+{
+  "incomingRequest": {
+    "ortbRequest": {
+      "id": "some-request-id",
+      "site": {
+        "page": "test.somepage.com"
+      },
+      "imp": [
+        {
+          "id": "my-imp-id",
+          "video": {
+            "mimes": ["video/mp4"]
+          },
+          "ext": {
+            "pubmatic": {
+              "publisherId": 5890
+            },
+            "appnexus": {
+              "placementId": 1
+            }
+          }
+        },
+        {
+          "id": "imp-id-2",
+          "video": {
+            "mimes": ["video/mp4"]
+          },
+          "ext": {
+            "pubmatic": {
+              "publisherId": 5890
+            },
+            "appnexus": {
+              "placementId": 1
+            }
+          }
+        }
+      ],
+      "ext": {
+        "prebid": {
+          "targeting": {
+            "includewinners": true,
+            "includebidderkeys": true
+          }
+        }
+      }
+    }
+  },
+  "outgoingRequests": {
+    "pubmatic": {
+      "mockResponse": {
+        "pbsSeatBids": [{
+          "pbsBids": [
+            {
+              "ortbBid": {
+                "id": "winning-bid",
+                "impid": "my-imp-id",
+                "price": 0.71,
+                "w": 200,
+                "h": 250,
+                "crid": "creative-1"
+              },
+              "bidType": "video",
+              "bidMeta": {
+                "adaptercode": "pubmatic"
+              }
+            },
+            {
+              "ortbBid": {
+                "id": "losing-bid",
+                "impid": "my-imp-id",
+                "price": 0.21,
+                "w": 200,
+                "h": 250,
+                "crid": "creative-2"
+              },
+              "bidType": "video",
+              "bidMeta": {
+                "adaptercode": "pubmatic"
+              }
+            },
+            {
+              "ortbBid": {
+                "id": "other-bid",
+                "impid": "imp-id-2",
+                "price": 0.61,
+                "w": 300,
+                "h": 500,
+                "crid": "creative-3"
+              },
+              "bidType": "video",
+              "bidMeta": {
+                "adaptercode": "pubmatic"
+              }
+            }
+          ],
+          "seat": "pubmatic"
+        },
+        {
+          "pbsBids": [
+            {
+              "ortbBid": {
+                "id": "contending-bid",
+                "impid": "my-imp-id",
+                "price": 0.51,
+                "w": 200,
+                "h": 250,
+                "crid": "creative-4"
+              },
+              "bidType": "video",
+              "bidMeta": {
+                "adaptercode": "pubmatic"
+              }
+            }
+          ],
+          "seat": "groupm"
+        }]
+      }
+    },
+    "appnexus": {
+      "mockResponse": {
+        "pbsSeatBids": [{
+          "pbsBids": [
+            {
+              "ortbBid": {
+                "id": "apn-bid",
+                "impid": "my-imp-id",
+                "price": 0.3,
+                "w": 200,
+                "h": 500,
+                "crid": "creative-a-1"
+              },
+              "bidType": "banner",
+              "bidMeta": {
+                "adaptercode": "appnexus"
+              }
+            }
+          ],
+          "seat": "appnexus"
+        },
+        {
+          "pbsBids": [
+            {
+              "ortbBid": {
+                "id": "apn-bid-2",
+                "impid": "my-imp-id",
+                "price": 0.3,
+                "w": 200,
+                "h": 500,
+                "crid": "creative-a-2"
+              },
+              "bidType": "banner",
+              "bidMeta": {
+                "adaptercode": "appnexus"
+              }
+            }
+          ],
+          "seat": "groupm"
+        }]
+      }
+    }
+  },
+  "response": {
+    "bids": {
+      "id": "some-request-id",
+      "seatbid": [
+        {
+          "seat": "groupm",
+          "bid": [{
+            "id": "contending-bid",
+            "impid": "my-imp-id",
+            "price": 0.51,
+            "w": 200,
+            "h": 250,
+            "crid": "creative-4",
+            "ext": {
+              "origbidcpm": 0.51,
+              "prebid": {
+                "meta": {
+                  "adaptercode": "pubmatic"
+                },
+                "type": "video",
+                "targeting": {
+                  "hb_bidder_groupm": "groupm",
+                  "hb_cache_host_groupm": "www.pbcserver.com",
+                  "hb_cache_path_groupm": "/pbcache/endpoint",
+                  "hb_pb_groupm": "0.50",
+                  "hb_size_groupm": "200x250"
+                }
+              }
+            }
+          },
+          {
+            "id": "apn-bid-2",
+            "impid": "my-imp-id",
+            "price": 0.3,
+            "w": 200,
+            "h": 500,
+            "crid": "creative-a-2",
+            "ext": {
+              "origbidcpm": 0.3,
+              "prebid": {
+                "meta": {
+                  "adaptercode": "appnexus"
+                },
+                "type": "banner"
+              }
+            }
+          }]
+        },
+        {
+          "seat": "pubmatic",
+          "bid": [{
+            "id": "winning-bid",
+            "impid": "my-imp-id",
+            "price": 0.71,
+            "w": 200,
+            "h": 250,
+            "crid": "creative-1",
+            "ext": {
+              "origbidcpm": 0.71,
+              "prebid": {
+                "meta": {
+                  "adaptercode": "pubmatic"
+                },
+                "type": "video",
+                "targeting": {
+                  "hb_bidder": "pubmatic",
+                  "hb_bidder_pubmatic": "pubmatic",
+                  "hb_cache_host": "www.pbcserver.com",
+                  "hb_cache_host_pubmat": "www.pbcserver.com",
+                  "hb_cache_path": "/pbcache/endpoint",
+                  "hb_cache_path_pubmat": "/pbcache/endpoint",
+                  "hb_pb": "0.70",
+                  "hb_pb_pubmatic": "0.70",
+                  "hb_size": "200x250",
+                  "hb_size_pubmatic": "200x250"
+                }
+              }
+            }
+          },
+          {
+            "id": "losing-bid",
+            "impid": "my-imp-id",
+            "price": 0.21,
+            "w": 200,
+            "h": 250,
+            "crid": "creative-2",
+            "ext": {
+              "origbidcpm": 0.21,
+              "prebid": {
+                "meta": {
+                  "adaptercode": "pubmatic"
+                },
+                "type": "video"
+              }
+            }
+          },
+          {
+            "id": "other-bid",
+            "impid": "imp-id-2",
+            "price": 0.61,
+            "w": 300,
+            "h": 500,
+            "crid": "creative-3",
+            "ext": {
+              "origbidcpm": 0.61,
+              "prebid": {
+                "meta": {
+                  "adaptercode": "pubmatic"
+                },
+                "type": "video",
+                "targeting": {
+                  "hb_bidder": "pubmatic",
+                  "hb_bidder_pubmatic": "pubmatic",
+                  "hb_cache_host": "www.pbcserver.com",
+                  "hb_cache_host_pubmat": "www.pbcserver.com",
+                  "hb_cache_path": "/pbcache/endpoint",
+                  "hb_cache_path_pubmat": "/pbcache/endpoint",
+                  "hb_pb": "0.60",
+                  "hb_pb_pubmatic": "0.60",
+                  "hb_size": "300x500",
+                  "hb_size_pubmatic": "300x500"
+                }
+              }
+            }
+          }]
+        },
+        {
+          "seat": "appnexus",
+          "bid": [{
+            "id": "apn-bid",
+            "impid": "my-imp-id",
+            "price": 0.3,
+            "w": 200,
+            "h": 500,
+            "crid": "creative-a-1",
+            "ext": {
+                "origbidcpm": 0.3,
+                "prebid": {
+                  "meta": {
+                    "adaptercode": "appnexus"
+                  },
+                  "type": "banner",
+                  "targeting": {
+                    "hb_bidder_appnexus":"appnexus",
+                    "hb_cache_host_appnex":"www.pbcserver.com",
+                    "hb_cache_path_appnex":"/pbcache/endpoint",
+                    "hb_pb_appnexus":"0.20",
+                    "hb_size_appnexus":"200x500"
+                  }
+                }
+            }
+          }]
+        }
+      ]
+    }
+  }
+}
diff --git a/exchange/exchangetest/firstpartydata-imp-ext-multiple-bidders.json b/exchange/exchangetest/firstpartydata-imp-ext-multiple-bidders.json
index c16fbc86..72d48d5a 100644
--- a/exchange/exchangetest/firstpartydata-imp-ext-multiple-bidders.json
+++ b/exchange/exchangetest/firstpartydata-imp-ext-multiple-bidders.json
@@ -71,11 +71,10 @@
                             }
                         }
                     }]
-                },
-                "bidAdjustment": 1.0
+                }
             },
             "mockResponse": {
-                "pbsSeatBid": {
+                "pbsSeatBids": [{
                     "pbsBids": [{
                         "ortbBid": {
                             "id": "apn-bid",
@@ -86,8 +85,9 @@
                             "crid": "creative-1"
                         },
                         "bidType": "banner"
-                    }]
-                }
+                    }],
+                    "seat": "appnexus"
+                }]
             }
         },
         "rubicon": {
@@ -124,11 +124,10 @@
                             }
                         }
                     }]
-                },
-                "bidAdjustment": 1.0
+                }
             },
             "mockResponse": {
-                "pbsSeatBid": {
+                "pbsSeatBids": [{
                     "pbsBids": [{
                         "ortbBid": {
                             "id": "rubi-bid",
@@ -139,8 +138,9 @@
                             "crid": "creative-2"
                         },
                         "bidType": "banner"
-                    }]
-                }
+                    }],
+                    "seat": "rubicon"
+                }]
             }
         }
     },
diff --git a/exchange/exchangetest/firstpartydata-imp-ext-multiple-prebid-bidders.json b/exchange/exchangetest/firstpartydata-imp-ext-multiple-prebid-bidders.json
index aaaac94c..d2239431 100644
--- a/exchange/exchangetest/firstpartydata-imp-ext-multiple-prebid-bidders.json
+++ b/exchange/exchangetest/firstpartydata-imp-ext-multiple-prebid-bidders.json
@@ -75,11 +75,10 @@
                             }
                         }
                     }]
-                },
-                "bidAdjustment": 1.0
+                }
             },
             "mockResponse": {
-                "pbsSeatBid": {
+                "pbsSeatBids": [{
                     "pbsBids": [{
                         "ortbBid": {
                             "id": "apn-bid",
@@ -90,8 +89,9 @@
                             "crid": "creative-1"
                         },
                         "bidType": "banner"
-                    }]
-                }
+                    }],
+                    "seat": "appnexus"
+                }]
             }
         },
         "rubicon": {
@@ -128,11 +128,10 @@
                             }
                         }
                     }]
-                },
-                "bidAdjustment": 1.0
+                }
             },
             "mockResponse": {
-                "pbsSeatBid": {
+                "pbsSeatBids": [{
                     "pbsBids": [{
                         "ortbBid": {
                             "id": "rubi-bid",
@@ -143,8 +142,9 @@
                             "crid": "creative-2"
                         },
                         "bidType": "banner"
-                    }]
-                }
+                    }],
+                    "seat": "rubicon"
+                }]
             }
         }
     },
diff --git a/exchange/exchangetest/firstpartydata-imp-ext-one-bidder.json b/exchange/exchangetest/firstpartydata-imp-ext-one-bidder.json
index 9e42e5ae..762d2401 100644
--- a/exchange/exchangetest/firstpartydata-imp-ext-one-bidder.json
+++ b/exchange/exchangetest/firstpartydata-imp-ext-one-bidder.json
@@ -66,11 +66,10 @@
                             }
                         }
                     }]
-                },
-                "bidAdjustment": 1.0
+                }
             },
             "mockResponse": {
-                "pbsSeatBid": {
+                "pbsSeatBids": [{
                     "pbsBids": [{
                         "ortbBid": {
                             "id": "apn-bid",
@@ -81,8 +80,9 @@
                             "crid": "creative-1"
                         },
                         "bidType": "banner"
-                    }]
-                }
+                    }],
+                    "seat": "appnexus"
+                }]
             }
         }
     },
diff --git a/exchange/exchangetest/firstpartydata-imp-ext-one-prebid-bidder.json b/exchange/exchangetest/firstpartydata-imp-ext-one-prebid-bidder.json
index 87ff8a8a..db722ca3 100644
--- a/exchange/exchangetest/firstpartydata-imp-ext-one-prebid-bidder.json
+++ b/exchange/exchangetest/firstpartydata-imp-ext-one-prebid-bidder.json
@@ -70,11 +70,10 @@
                             }
                         }
                     }]
-                },
-                "bidAdjustment": 1.0
+                }
             },
             "mockResponse": {
-                "pbsSeatBid": {
+                "pbsSeatBids": [{
                     "pbsBids": [{
                         "ortbBid": {
                             "id": "apn-bid",
@@ -85,8 +84,9 @@
                             "crid": "creative-1"
                         },
                         "bidType": "banner"
-                    }]
-                }
+                    }],
+                    "seat": "appnexus"
+                }]
             }
         }
     },
diff --git a/exchange/exchangetest/firstpartydata-multibidder-config-valid.json b/exchange/exchangetest/firstpartydata-multibidder-config-valid.json
index d7c8946f..8ac7e7d2 100644
--- a/exchange/exchangetest/firstpartydata-multibidder-config-valid.json
+++ b/exchange/exchangetest/firstpartydata-multibidder-config-valid.json
@@ -111,8 +111,7 @@
               }
             }
           }
-        },
-        "bidAdjustment": 1.0
+        }
       }
     },
     "rubicon": {
@@ -154,8 +153,7 @@
               }
             }
           }
-        },
-        "bidAdjustment": 1.0
+        }
       }
     }
   }
diff --git a/exchange/exchangetest/gdpr-geo-eu-off-device.json b/exchange/exchangetest/gdpr-geo-eu-off-device.json
index f704cdd5..5b133459 100644
--- a/exchange/exchangetest/gdpr-geo-eu-off-device.json
+++ b/exchange/exchangetest/gdpr-geo-eu-off-device.json
@@ -54,8 +54,7 @@
                             "country": "FRA"
                         }
                     }
-                },
-                "bidAdjustment": 1.0
+                }
             },
             "mockResponse": {
                 "errors": ["appnexus-error"]
diff --git a/exchange/exchangetest/gdpr-geo-eu-off.json b/exchange/exchangetest/gdpr-geo-eu-off.json
index 24357eb7..7c634ce8 100644
--- a/exchange/exchangetest/gdpr-geo-eu-off.json
+++ b/exchange/exchangetest/gdpr-geo-eu-off.json
@@ -50,8 +50,7 @@
                             "country": "FRA"
                         }
                     }
-                },
-                "bidAdjustment": 1.0
+                }
             },
             "mockResponse": {
                 "errors": ["appnexus-error"]
diff --git a/exchange/exchangetest/gdpr-geo-eu-on-featureflag-off.json b/exchange/exchangetest/gdpr-geo-eu-on-featureflag-off.json
index 6c6ca3ed..5165e45f 100644
--- a/exchange/exchangetest/gdpr-geo-eu-on-featureflag-off.json
+++ b/exchange/exchangetest/gdpr-geo-eu-on-featureflag-off.json
@@ -51,8 +51,7 @@
                             "country": "FRA"
                         }
                     }
-                },
-                "bidAdjustment": 1.0
+                }
             },
             "mockResponse": {
                 "errors": ["appnexus-error"]
diff --git a/exchange/exchangetest/gdpr-geo-eu-on.json b/exchange/exchangetest/gdpr-geo-eu-on.json
index eb42a17c..ba9ce76b 100644
--- a/exchange/exchangetest/gdpr-geo-eu-on.json
+++ b/exchange/exchangetest/gdpr-geo-eu-on.json
@@ -50,8 +50,7 @@
                             "country": "FRA"
                         }
                     }
-                },
-                "bidAdjustment": 1.0
+                }
             },
             "mockResponse": {
                 "errors": ["appnexus-error"]
diff --git a/exchange/exchangetest/gdpr-geo-usa-off.json b/exchange/exchangetest/gdpr-geo-usa-off.json
index d56c9318..f461833c 100644
--- a/exchange/exchangetest/gdpr-geo-usa-off.json
+++ b/exchange/exchangetest/gdpr-geo-usa-off.json
@@ -50,8 +50,7 @@
                             "country": "USA"
                         }
                     }
-                },
-                "bidAdjustment": 1.0
+                }
             },
             "mockResponse": {
                 "errors": ["appnexus-error"]
diff --git a/exchange/exchangetest/gdpr-geo-usa-on.json b/exchange/exchangetest/gdpr-geo-usa-on.json
index f922be9e..15d7fae3 100644
--- a/exchange/exchangetest/gdpr-geo-usa-on.json
+++ b/exchange/exchangetest/gdpr-geo-usa-on.json
@@ -50,8 +50,7 @@
                             "country": "USA"
                         }
                     }
-                },
-                "bidAdjustment": 1.0
+                }
             },
             "mockResponse": {
                 "errors": ["appnexus-error"]
diff --git a/exchange/exchangetest/include-brand-category.json b/exchange/exchangetest/include-brand-category.json
index 38b8a7d3..301f3820 100644
--- a/exchange/exchangetest/include-brand-category.json
+++ b/exchange/exchangetest/include-brand-category.json
@@ -48,7 +48,7 @@
   "outgoingRequests": {
     "appnexus": {
       "mockResponse": {
-        "pbsSeatBid": {
+        "pbsSeatBids": [{
           "pbsBids": [
             {
               "ortbBid": {
@@ -78,8 +78,9 @@
               },
               "bidType": "video"
             }
-          ]
-        }
+          ],
+          "seat": "appnexus"
+        }]
       }
     }
   },
diff --git a/exchange/exchangetest/lmt-featureflag-off.json b/exchange/exchangetest/lmt-featureflag-off.json
index 9a15c879..d8723b59 100644
--- a/exchange/exchangetest/lmt-featureflag-off.json
+++ b/exchange/exchangetest/lmt-featureflag-off.json
@@ -52,8 +52,7 @@
                         "id": "some-id",
                         "buyeruid": "some-buyer-id"
                     }
-                },
-                "bidAdjustment": 1.0
+                }
             },
             "mockResponse": {
                 "errors": ["appnexus-error"]
diff --git a/exchange/exchangetest/lmt-featureflag-on.json b/exchange/exchangetest/lmt-featureflag-on.json
index 440f8c76..2bbea632 100644
--- a/exchange/exchangetest/lmt-featureflag-on.json
+++ b/exchange/exchangetest/lmt-featureflag-on.json
@@ -50,8 +50,7 @@
                     },
                     "user": {
                     }
-                },
-                "bidAdjustment": 1.0
+                }
             },
             "mockResponse": {
                 "errors": ["appnexus-error"]
diff --git a/exchange/exchangetest/request-multi-bidders-debug-info.json b/exchange/exchangetest/request-multi-bidders-debug-info.json
index 18d9b1aa..7aa241dd 100644
--- a/exchange/exchangetest/request-multi-bidders-debug-info.json
+++ b/exchange/exchangetest/request-multi-bidders-debug-info.json
@@ -70,7 +70,7 @@
             "status": 200
           }
         ],
-        "pbsSeatBid": {
+        "pbsSeatBids": [{
           "pbsBids": [
             {
               "ortbBid": {
@@ -85,8 +85,9 @@
                 ]
               }
             }
-          ]
-        }
+          ],
+          "seat": "appnexus"
+        }]
       }
     },
     "audienceNetwork": {
diff --git a/exchange/exchangetest/request-multi-bidders-one-no-resp.json b/exchange/exchangetest/request-multi-bidders-one-no-resp.json
index d46de2dc..8ff6441e 100644
--- a/exchange/exchangetest/request-multi-bidders-one-no-resp.json
+++ b/exchange/exchangetest/request-multi-bidders-one-no-resp.json
@@ -53,7 +53,7 @@
   "outgoingRequests": {
     "appnexus": {
       "mockResponse": {
-        "pbsSeatBid": {
+        "pbsSeatBids": [{
           "pbsBids": [
             {
               "ortbBid": {
@@ -68,8 +68,9 @@
                 ]
               }
             }
-          ]
-        }
+          ],
+          "seat": "appnexus"
+        }]
       }
     },
     "audienceNetwork": {
diff --git a/exchange/exchangetest/request-no-user.json b/exchange/exchangetest/request-no-user.json
index 9e3bdb6e..a5c2ed44 100644
--- a/exchange/exchangetest/request-no-user.json
+++ b/exchange/exchangetest/request-no-user.json
@@ -47,8 +47,7 @@
               }
             }
           ]
-        },
-        "bidAdjustment": 1.0
+        }
       },
       "mockResponse": {
         "errors": ["appnexus-error"]
diff --git a/exchange/exchangetest/request-other-user-ext.json b/exchange/exchangetest/request-other-user-ext.json
index f9fb3264..e7c1dad3 100644
--- a/exchange/exchangetest/request-other-user-ext.json
+++ b/exchange/exchangetest/request-other-user-ext.json
@@ -58,8 +58,7 @@
               }
             }
           ]
-        },
-        "bidAdjustment": 1.0
+        }
       },
       "mockResponse": {
         "errors": ["appnexus-error"]
diff --git a/exchange/exchangetest/request-other-user.json b/exchange/exchangetest/request-other-user.json
index 7413d37f..2a9d4bc8 100644
--- a/exchange/exchangetest/request-other-user.json
+++ b/exchange/exchangetest/request-other-user.json
@@ -51,8 +51,7 @@
               }
             }
           ]
-        },
-        "bidAdjustment": 1.0
+        }
       },
       "mockResponse": {
         "errors": ["appnexus-error"]
diff --git a/exchange/exchangetest/request-user-no-prebid.json b/exchange/exchangetest/request-user-no-prebid.json
index aae11606..28fa17de 100644
--- a/exchange/exchangetest/request-user-no-prebid.json
+++ b/exchange/exchangetest/request-user-no-prebid.json
@@ -55,8 +55,7 @@
               }
             }
           ]
-        },
-        "bidAdjustment": 1.0
+        }
       },
       "mockResponse": {
         "errors": ["appnexus-error"]
diff --git a/exchange/exchangetest/targeting-cache-vast-banner.json b/exchange/exchangetest/targeting-cache-vast-banner.json
index d396abbb..15353723 100644
--- a/exchange/exchangetest/targeting-cache-vast-banner.json
+++ b/exchange/exchangetest/targeting-cache-vast-banner.json
@@ -32,7 +32,7 @@
   "outgoingRequests": {
     "appnexus": {
       "mockResponse": {
-        "pbsSeatBid": {
+        "pbsSeatBids": [{
           "pbsBids": [
             {
               "ortbBid": {
@@ -45,8 +45,9 @@
               },
               "bidType": "banner"
             }
-          ]
-        }
+          ],
+          "seat": "appnexus"
+        }]
       }
     }
   },
diff --git a/exchange/exchangetest/targeting-cache-vast.json b/exchange/exchangetest/targeting-cache-vast.json
index 1cda3a5e..e247eb48 100644
--- a/exchange/exchangetest/targeting-cache-vast.json
+++ b/exchange/exchangetest/targeting-cache-vast.json
@@ -31,7 +31,7 @@
   "outgoingRequests": {
     "appnexus": {
       "mockResponse": {
-        "pbsSeatBid": {
+        "pbsSeatBids": [{
           "pbsBids": [
             {
               "ortbBid": {
@@ -44,8 +44,9 @@
               },
               "bidType": "video"
             }
-          ]
-        }
+          ],
+          "seat": "appnexus"
+        }]
       }
     }
   },
diff --git a/exchange/exchangetest/targeting-cache-zero.json b/exchange/exchangetest/targeting-cache-zero.json
index 3be5e630..bda867b8 100644
--- a/exchange/exchangetest/targeting-cache-zero.json
+++ b/exchange/exchangetest/targeting-cache-zero.json
@@ -34,7 +34,7 @@
   "outgoingRequests": {
     "appnexus": {
       "mockResponse": {
-        "pbsSeatBid": {
+        "pbsSeatBids": [{
           "pbsBids": [
             {
               "ortbBid": {
@@ -47,8 +47,9 @@
               },
               "bidType": "video"
             }
-          ]
-        }
+          ],
+          "seat": "appnexus"
+        }]
       }
     }
   },
diff --git a/exchange/exchangetest/targeting-mobile.json b/exchange/exchangetest/targeting-mobile.json
index 4b41bc15..11f3cf84 100644
--- a/exchange/exchangetest/targeting-mobile.json
+++ b/exchange/exchangetest/targeting-mobile.json
@@ -45,7 +45,7 @@
   "outgoingRequests": {
     "appnexus": {
       "mockResponse": {
-        "pbsSeatBid": {
+        "pbsSeatBids": [{
           "pbsBids": [
             {
               "ortbBid": {
@@ -80,13 +80,14 @@
               },
               "bidType": "video"
             }
-          ]
-        }
+          ],
+          "seat": "appnexus"
+        }]
       }
     },
     "audienceNetwork": {
       "mockResponse": {
-        "pbsSeatBid": {
+        "pbsSeatBids": [{
           "pbsBids": [
             {
               "ortbBid": {
@@ -99,8 +100,9 @@
               },
               "bidType": "video"
             }
-          ]
-        }
+          ],
+          "seat": "audienceNetwork"
+        }]
       }
     }
   },
diff --git a/exchange/exchangetest/targeting-no-winners.json b/exchange/exchangetest/targeting-no-winners.json
index 74a67263..a200b9a5 100644
--- a/exchange/exchangetest/targeting-no-winners.json
+++ b/exchange/exchangetest/targeting-no-winners.json
@@ -47,7 +47,7 @@
   "outgoingRequests": {
     "appnexus": {
       "mockResponse": {
-        "pbsSeatBid": {
+        "pbsSeatBids": [{
           "pbsBids": [
             {
               "ortbBid": {
@@ -82,13 +82,14 @@
               },
               "bidType": "video"
             }
-          ]
-        }
+          ],
+          "seat": "appnexus"
+        }]
       }
     },
     "audienceNetwork": {
       "mockResponse": {
-        "pbsSeatBid": {
+        "pbsSeatBids": [{
           "pbsBids": [
             {
               "ortbBid": {
@@ -101,8 +102,9 @@
               },
               "bidType": "video"
             }
-          ]
-        }
+          ],
+          "seat": "audienceNetwork"
+        }]
       }
     }
   },
diff --git a/exchange/exchangetest/targeting-only-winners.json b/exchange/exchangetest/targeting-only-winners.json
index 491ab98f..500f4467 100644
--- a/exchange/exchangetest/targeting-only-winners.json
+++ b/exchange/exchangetest/targeting-only-winners.json
@@ -47,7 +47,7 @@
   "outgoingRequests": {
     "appnexus": {
       "mockResponse": {
-        "pbsSeatBid": {
+        "pbsSeatBids": [{
           "pbsBids": [
             {
               "ortbBid": {
@@ -82,13 +82,14 @@
               },
               "bidType": "video"
             }
-          ]
-        }
+          ],
+          "seat": "appnexus"
+        }]
       }
     },
     "audienceNetwork": {
       "mockResponse": {
-        "pbsSeatBid": {
+        "pbsSeatBids": [{
           "pbsBids": [
             {
               "ortbBid": {
@@ -101,8 +102,9 @@
               },
               "bidType": "video"
             }
-          ]
-        }
+          ],
+          "seat": "audienceNetwork"
+        }]
       }
     }
   },
diff --git a/exchange/exchangetest/targeting-with-winners.json b/exchange/exchangetest/targeting-with-winners.json
index faa5fbb9..6b883674 100644
--- a/exchange/exchangetest/targeting-with-winners.json
+++ b/exchange/exchangetest/targeting-with-winners.json
@@ -45,7 +45,7 @@
   "outgoingRequests": {
     "appnexus": {
       "mockResponse": {
-        "pbsSeatBid": {
+        "pbsSeatBids": [{
           "pbsBids": [
             {
               "ortbBid": {
@@ -80,13 +80,14 @@
               },
               "bidType": "video"
             }
-          ]
-        }
+          ],
+          "seat": "appnexus"
+        }]
       }
     },
     "audienceNetwork": {
       "mockResponse": {
-        "pbsSeatBid": {
+        "pbsSeatBids": [{
           "pbsBids": [
             {
               "ortbBid": {
@@ -99,8 +100,9 @@
               },
               "bidType": "video"
             }
-          ]
-        }
+          ],
+          "seat": "audienceNetwork"
+        }]
       }
     }
   },
diff --git a/exchange/exchangetest/tmax.json b/exchange/exchangetest/tmax.json
index 6cd11fa0..7b8d89c7 100644
--- a/exchange/exchangetest/tmax.json
+++ b/exchange/exchangetest/tmax.json
@@ -49,8 +49,7 @@
               }
             }
           ]
-        },
-        "bidAdjustment": 1.0
+        }
       },
       "mockResponse": {
         "errors": ["appnexus-error"]
diff --git a/exchange/exchangetest/tricky-userids.json b/exchange/exchangetest/tricky-userids.json
index 12188da2..e01f53c7 100644
--- a/exchange/exchangetest/tricky-userids.json
+++ b/exchange/exchangetest/tricky-userids.json
@@ -79,8 +79,7 @@
               }
             }
           }
-        },
-        "bidAdjustment": 1.0
+        }
       },
       "mockResponse": {
         "errors": ["appnexus-error"]
@@ -118,8 +117,7 @@
               }
             }
           }
-        },
-        "bidAdjustment": 1.0
+        }
       },
       "mockResponse": {
         "errors": ["appnexus-error"]
@@ -155,8 +153,7 @@
               }
             }
           }
-        },
-        "bidAdjustment": 1.0
+        }
       },
       "mockResponse": {
         "errors": ["districtm-error"]