Repository: prebid/prebid-server
Pull request #2500 (2022-12-20 22:21:01)

Modularity: All processed bid responses stage implementation
During the implementation of All Processed Bid Responses Stage we faced the issue of cycle import because the hook needs a map of exchange.pbsOrtbSeatBids as parameter in hookexecution.hookExecutor function. But we already import the hookexecution package in exchange package in order to call the hooks.
There were 2 solutions for this issue:

1. Move the exchange.pbsOrtbSeatBid and related exchange.pbsOrtbBid to a new package imported both by exchange and hookexecution which resulted in exporting these entities and their fields. The fields would be exported anyway in order to be able to use them in the modules.
2. Write 2 interfaces for exchange.pbsOrtbSeatBid and exchange.pbsOrtbBid respectively and use them in the hook call functions but this approach had some pitfalls. Since we need to pass a map of exchange.pbsOrtbSeatBids to the hook call function, we can not pass it directly as a map of structs which implement our interface, because they do not have the same representation in memory. This would require to copy all values from map[openrtb_ext.BidderName]pbsOrtbSeatBid to a map[openrtb_ext.BidderName]PbsOrtbSeatBidInterface in order to be able to pass them as an argument and use inside the hook execution function. Moreover each pbsOrtbSeatBid has a slice of pbsOrtbBids which will require the same copy operation for all of its elements because of the same reason.

We decided to use the first approach as a much simpler and more effective solution without unnecessary copy operations.


legendko: **One more important change:**
We changed the location of current hook call from the agreed https://github.com/prebid/prebid-server/blob/master/exchange/exchange.go#L324 to https://github.com/prebid/prebid-server/blob/master/exchange/exchange.go#L312
The main reason is to put the hook call before category mapping. In PBS-Java they have such order. If you think PBS-Go has to act differently please tell us.
legendko: > > **One more important change:** We changed the location of current hook call from the agreed https://github.com/prebid/prebid-server/blob/master/exchange/exchange.go#L324 to https://github.com/prebid/prebid-server/blob/master/exchange/exchange.go#L312 The main reason is to put the hook call before category mapping. In PBS-Java they have such order. If you think PBS-Go has to act differently please tell us.
> 
> UPD: this change also influenced on the order of bid Id generation and vast generation. Now they are called after the hook. We should decide which order should be used for these processes.

Copying my comment from slack, so it does not get lost in the threads:

At the beginning when we were planning the locations for each hook we decided to call the all_processed_bid_responses hook after bid Id generation and vast generation because PBS-Java has a similar flow. But at that time we did not took into account the process of category mapping which in Java is called after the hook and we had it before the hook because of the current implementation. A few days ago we noticed this difference but forgot about the original reasons of the primal location and moved the `all_processed_bid_responses` hook before category mapping like in PBS-Java, but this resulted in changing the order for bid Id generation and vast generation as well.
Is category mapping critical for all_processed_bid_responses hook? I did not find any information about it in the PRD. If this hook is not intended to change that information, I suggest to move back the location of the hook as we agreed on the very beginning: after bid id generation and vast generation. If category mapping can be changed during this stage then the only way is to refactor the existing func at https://github.com/prebid/prebid-server/blob/master/exchange/exchange.go#L742 so category mapping is called after the hook, but bid Id generation and vast generation before.
To simplify the understanding I'll write a small schema

```
PBS-Java:
bid Id generation and vast generation => Hook => Category mapping

Original agreement:
Category mapping => bid Id generation and vast generation => Hook

Updated code:
Hook => Category mapping => bid Id generation and vast generation
```
bsardo: @legendko, the `applyCategoryMapping` function may remove some bids but given that the intent of this hook is for creative malware scans, it is ok to run this function before executing the hook since there is no reason to perform such a scan on a bid that will be removed anyway. As such, I think we should go with the original agreement:
`Category mapping => bid Id generation and vast generation => Hook`
bsardo: @legendko I think you mentioned the original plan was to put it after bid generation and VAST generation as well so maybe move it down a few lines just after this:
```
evTracking := getEventTracking(&requestExt.Prebid, r.StartTime, &r.Account, e.bidderInfo, e.externalURL)
adapterBids = evTracking.modifyBidsForEvents(adapterBids)
```
legendko: > @legendko I think you mentioned the original plan was to put it after bid generation and VAST generation as well so maybe move it down a few lines just after this:
> 
> ```
> evTracking := getEventTracking(&requestExt.Prebid, r.StartTime, &r.Account, e.bidderInfo, e.externalURL)
> adapterBids = evTracking.modifyBidsForEvents(adapterBids)
> ```

@bsardo sorry, looked and inserted in the wrong place.
bsardo: No worries. Thanks!

Patch:
diff --git a/adapters/videoheroes/videoheroes.go b/adapters/videoheroes/videoheroes.go
new file mode 100755
index 00000000..4d04cd4f
--- /dev/null
+++ b/adapters/videoheroes/videoheroes.go
@@ -0,0 +1,157 @@
+package videoheroes
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"text/template"
+
+	"github.com/prebid/openrtb/v17/openrtb2"
+	"github.com/prebid/prebid-server/adapters"
+	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/errortypes"
+	"github.com/prebid/prebid-server/macros"
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+type adapter struct {
+	endpoint *template.Template
+}
+
+// Builder builds a new instance of the VideoHeroes adapter for the given bidder with the given config.
+func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
+	uri, err := template.New("endpointTemplate").Parse(config.Endpoint)
+	if err != nil {
+		return nil, fmt.Errorf("unable to parse endpoint url template: %v", err)
+	}
+
+	bidder := &adapter{
+		endpoint: uri,
+	}
+	return bidder, nil
+}
+
+func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+	var videoHeroesExt *openrtb_ext.ExtImpVideoHeroes
+	var err error
+
+	videoHeroesExt, err = a.getImpressionExt(&request.Imp[0])
+	if err != nil {
+		return nil, []error{err}
+	}
+
+	request.Imp[0].Ext = nil
+
+	reqJSON, err := json.Marshal(request)
+	if err != nil {
+		return nil, []error{err}
+	}
+
+	headers := http.Header{}
+	headers.Add("Content-Type", "application/json;charset=utf-8")
+	headers.Add("Accept", "application/json")
+
+	url, err := a.buildEndpointURL(videoHeroesExt)
+	if err != nil {
+		return nil, []error{err}
+	}
+
+	return []*adapters.RequestData{{
+		Method:  http.MethodPost,
+		Body:    reqJSON,
+		Uri:     url,
+		Headers: headers,
+	}}, nil
+}
+
+func (a *adapter) getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ExtImpVideoHeroes, error) {
+	var bidderExt adapters.ExtImpBidder
+	if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
+		return nil, &errortypes.BadInput{
+			Message: "ext.bidder not provided",
+		}
+	}
+	var videoHeroesExt openrtb_ext.ExtImpVideoHeroes
+	if err := json.Unmarshal(bidderExt.Bidder, &videoHeroesExt); err != nil {
+		return nil, &errortypes.BadInput{
+			Message: "ext.bidder not provided",
+		}
+	}
+	return &videoHeroesExt, nil
+}
+
+func (a *adapter) buildEndpointURL(params *openrtb_ext.ExtImpVideoHeroes) (string, error) {
+	endpointParams := macros.EndpointTemplateParams{PublisherID: params.PlacementID}
+	return macros.ResolveMacros(a.endpoint, endpointParams)
+}
+
+func (a *adapter) MakeBids(
+	receivedRequest *openrtb2.BidRequest,
+	bidderRequest *adapters.RequestData,
+	bidderResponse *adapters.ResponseData,
+) (
+	*adapters.BidderResponse,
+	[]error,
+) {
+
+	if bidderResponse.StatusCode == http.StatusNoContent {
+		return nil, []error{&errortypes.BadInput{Message: "No bid"}}
+	}
+
+	if bidderResponse.StatusCode == http.StatusBadRequest {
+		return nil, []error{&errortypes.BadInput{
+			Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", bidderResponse.StatusCode),
+		}}
+	}
+
+	if bidderResponse.StatusCode == http.StatusServiceUnavailable {
+		return nil, []error{&errortypes.BadInput{
+			Message: fmt.Sprintf("Service Unavailable. Status Code: [ %d ] ", bidderResponse.StatusCode),
+		}}
+	}
+
+	if bidderResponse.StatusCode != http.StatusOK {
+		return nil, []error{&errortypes.BadServerResponse{
+			Message: fmt.Sprintf("Something went wrong, please contact your Account Manager. Status Code: [ %d ] ", bidderResponse.StatusCode),
+		}}
+	}
+
+	var bidResponse openrtb2.BidResponse
+	if err := json.Unmarshal(bidderResponse.Body, &bidResponse); err != nil {
+		return nil, []error{&errortypes.BadServerResponse{
+			Message: "Bad Server Response",
+		}}
+	}
+
+	if len(bidResponse.SeatBid) == 0 {
+		return nil, []error{&errortypes.BadServerResponse{
+			Message: "Empty SeatBid array",
+		}}
+	}
+
+	bidResponseFinal := adapters.NewBidderResponseWithBidsCapacity(len(bidResponse.SeatBid[0].Bid))
+	sb := bidResponse.SeatBid[0]
+
+	for _, bid := range sb.Bid {
+		bidResponseFinal.Bids = append(bidResponseFinal.Bids, &adapters.TypedBid{
+			Bid:     &bid,
+			BidType: getMediaTypeForImp(bid.ImpID, receivedRequest.Imp),
+		})
+	}
+	return bidResponseFinal, nil
+}
+
+func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType {
+	mediaType := openrtb_ext.BidTypeBanner
+	for _, imp := range imps {
+		if imp.ID == impId {
+			if imp.Video != nil {
+				mediaType = openrtb_ext.BidTypeVideo
+			} else if imp.Native != nil {
+				mediaType = openrtb_ext.BidTypeNative
+			}
+			return mediaType
+		}
+	}
+	return mediaType
+}
diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go
index 5ceaae1c..71c8e987 100755
--- a/exchange/adapter_builders.go
+++ b/exchange/adapter_builders.go
@@ -145,6 +145,7 @@ import (
 	"github.com/prebid/prebid-server/adapters/unicorn"
 	"github.com/prebid/prebid-server/adapters/unruly"
 	"github.com/prebid/prebid-server/adapters/videobyte"
+	"github.com/prebid/prebid-server/adapters/videoheroes"
 	"github.com/prebid/prebid-server/adapters/vidoomy"
 	"github.com/prebid/prebid-server/adapters/visx"
 	"github.com/prebid/prebid-server/adapters/vrtcal"
@@ -317,6 +318,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder {
 		openrtb_ext.BidderValueImpression:   apacdex.Builder,
 		openrtb_ext.BidderVerizonMedia:      yahoossp.Builder,
 		openrtb_ext.BidderVideoByte:         videobyte.Builder,
+		openrtb_ext.BidderVideoHeroes:       videoheroes.Builder,
 		openrtb_ext.BidderVidoomy:           vidoomy.Builder,
 		openrtb_ext.BidderViewdeos:          adtelligent.Builder,
 		openrtb_ext.BidderVisx:              visx.Builder,
diff --git a/exchange/auction.go b/exchange/auction.go
index 32a89cb5..3c6f28f9 100644
--- a/exchange/auction.go
+++ b/exchange/auction.go
@@ -13,6 +13,7 @@ import (
 	uuid "github.com/gofrs/uuid"
 	"github.com/prebid/openrtb/v17/openrtb2"
 	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/exchange/entities"
 	"github.com/prebid/prebid-server/openrtb_ext"
 	"github.com/prebid/prebid-server/prebid_cache_client"
 )
@@ -104,26 +105,26 @@ func (d *DebugLog) PutDebugLogError(cache prebid_cache_client.Client, timeout in
 	return nil
 }
 
-func newAuction(seatBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, numImps int, preferDeals bool) *auction {
-	winningBids := make(map[string]*pbsOrtbBid, numImps)
-	winningBidsByBidder := make(map[string]map[openrtb_ext.BidderName]*pbsOrtbBid, numImps)
+func newAuction(seatBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, numImps int, preferDeals bool) *auction {
+	winningBids := make(map[string]*entities.PbsOrtbBid, numImps)
+	winningBidsByBidder := make(map[string]map[openrtb_ext.BidderName]*entities.PbsOrtbBid, numImps)
 
 	for bidderName, seatBid := range seatBids {
 		if seatBid != nil {
-			for _, bid := range seatBid.bids {
-				cpm := bid.bid.Price
-				wbid, ok := winningBids[bid.bid.ImpID]
-				if !ok || isNewWinningBid(bid.bid, wbid.bid, preferDeals) {
-					winningBids[bid.bid.ImpID] = bid
+			for _, bid := range seatBid.Bids {
+				cpm := bid.Bid.Price
+				wbid, ok := winningBids[bid.Bid.ImpID]
+				if !ok || isNewWinningBid(bid.Bid, wbid.Bid, preferDeals) {
+					winningBids[bid.Bid.ImpID] = bid
 				}
-				if bidMap, ok := winningBidsByBidder[bid.bid.ImpID]; ok {
+				if bidMap, ok := winningBidsByBidder[bid.Bid.ImpID]; ok {
 					bestSoFar, ok := bidMap[bidderName]
-					if !ok || cpm > bestSoFar.bid.Price {
+					if !ok || cpm > bestSoFar.Bid.Price {
 						bidMap[bidderName] = bid
 					}
 				} else {
-					winningBidsByBidder[bid.bid.ImpID] = make(map[openrtb_ext.BidderName]*pbsOrtbBid)
-					winningBidsByBidder[bid.bid.ImpID][bidderName] = bid
+					winningBidsByBidder[bid.Bid.ImpID] = make(map[openrtb_ext.BidderName]*entities.PbsOrtbBid)
+					winningBidsByBidder[bid.Bid.ImpID][bidderName] = bid
 				}
 			}
 		}
@@ -149,10 +150,10 @@ func isNewWinningBid(bid, wbid *openrtb2.Bid, preferDeals bool) bool {
 }
 
 func (a *auction) setRoundedPrices(priceGranularity openrtb_ext.PriceGranularity) {
-	roundedPrices := make(map[*pbsOrtbBid]string, 5*len(a.winningBids))
+	roundedPrices := make(map[*entities.PbsOrtbBid]string, 5*len(a.winningBids))
 	for _, topBidsPerImp := range a.winningBidsByBidder {
 		for _, topBidPerBidder := range topBidsPerImp {
-			roundedPrices[topBidPerBidder] = GetPriceBucket(topBidPerBidder.bid.Price, priceGranularity)
+			roundedPrices[topBidPerBidder] = GetPriceBucket(topBidPerBidder.Bid.Price, priceGranularity)
 		}
 	}
 	a.roundedPrices = roundedPrices
@@ -188,7 +189,7 @@ func (a *auction) doCache(ctx context.Context, cache prebid_cache_client.Client,
 	}
 	for _, topBidsPerImp := range a.winningBidsByBidder {
 		for bidderName, topBidPerBidder := range topBidsPerImp {
-			impID := topBidPerBidder.bid.ImpID
+			impID := topBidPerBidder.Bid.ImpID
 			isOverallWinner := a.winningBids[impID] == topBidPerBidder
 			if !includeBidderKeys && !isOverallWinner {
 				continue
@@ -198,50 +199,50 @@ func (a *auction) doCache(ctx context.Context, cache prebid_cache_client.Client,
 			useCustomCacheKey := false
 			if competitiveExclusion && isOverallWinner || includeBidderKeys {
 				// set custom cache key for winning bid when competitive exclusion applies
-				catDur = bidCategory[topBidPerBidder.bid.ID]
+				catDur = bidCategory[topBidPerBidder.Bid.ID]
 				if len(catDur) > 0 {
 					customCacheKey = fmt.Sprintf("%s_%s", catDur, hbCacheID)
 					useCustomCacheKey = true
 				}
 			}
 			if bids {
-				if jsonBytes, err := json.Marshal(topBidPerBidder.bid); err == nil {
+				if jsonBytes, err := json.Marshal(topBidPerBidder.Bid); err == nil {
 					jsonBytes, err = evTracking.modifyBidJSON(topBidPerBidder, bidderName, jsonBytes)
 					if err != nil {
 						errs = append(errs, err)
 					}
 					if useCustomCacheKey {
 						// not allowed if bids is true; log error and cache normally
-						errs = append(errs, errors.New("cannot use custom cache key for non-vast bids"))
+						errs = append(errs, errors.New("cannot use custom cache key for non-vast Bids"))
 					}
 					toCache = append(toCache, prebid_cache_client.Cacheable{
 						Type:       prebid_cache_client.TypeJSON,
 						Data:       jsonBytes,
-						TTLSeconds: cacheTTL(expByImp[impID], topBidPerBidder.bid.Exp, defTTL(topBidPerBidder.bidType, defaultTTLs), ttlBuffer),
+						TTLSeconds: cacheTTL(expByImp[impID], topBidPerBidder.Bid.Exp, defTTL(topBidPerBidder.BidType, defaultTTLs), ttlBuffer),
 					})
-					bidIndices[len(toCache)-1] = topBidPerBidder.bid
+					bidIndices[len(toCache)-1] = topBidPerBidder.Bid
 				} else {
 					errs = append(errs, err)
 				}
 			}
-			if vast && topBidPerBidder.bidType == openrtb_ext.BidTypeVideo {
-				vastXML := makeVAST(topBidPerBidder.bid)
+			if vast && topBidPerBidder.BidType == openrtb_ext.BidTypeVideo {
+				vastXML := makeVAST(topBidPerBidder.Bid)
 				if jsonBytes, err := json.Marshal(vastXML); err == nil {
 					if useCustomCacheKey {
 						toCache = append(toCache, prebid_cache_client.Cacheable{
 							Type:       prebid_cache_client.TypeXML,
 							Data:       jsonBytes,
-							TTLSeconds: cacheTTL(expByImp[impID], topBidPerBidder.bid.Exp, defTTL(topBidPerBidder.bidType, defaultTTLs), ttlBuffer),
+							TTLSeconds: cacheTTL(expByImp[impID], topBidPerBidder.Bid.Exp, defTTL(topBidPerBidder.BidType, defaultTTLs), ttlBuffer),
 							Key:        customCacheKey,
 						})
 					} else {
 						toCache = append(toCache, prebid_cache_client.Cacheable{
 							Type:       prebid_cache_client.TypeXML,
 							Data:       jsonBytes,
-							TTLSeconds: cacheTTL(expByImp[impID], topBidPerBidder.bid.Exp, defTTL(topBidPerBidder.bidType, defaultTTLs), ttlBuffer),
+							TTLSeconds: cacheTTL(expByImp[impID], topBidPerBidder.Bid.Exp, defTTL(topBidPerBidder.BidType, defaultTTLs), ttlBuffer),
 						})
 					}
-					vastIndices[len(toCache)-1] = topBidPerBidder.bid
+					vastIndices[len(toCache)-1] = topBidPerBidder.Bid
 				} else {
 					errs = append(errs, err)
 				}
@@ -353,11 +354,11 @@ func defTTL(bidType openrtb_ext.BidType, defaultTTLs *config.DefaultTTLs) (ttl i
 
 type auction struct {
 	// winningBids is a map from imp.id to the highest overall CPM bid in that imp.
-	winningBids map[string]*pbsOrtbBid
+	winningBids map[string]*entities.PbsOrtbBid
 	// winningBidsByBidder stores the highest bid on each imp by each bidder.
-	winningBidsByBidder map[string]map[openrtb_ext.BidderName]*pbsOrtbBid
+	winningBidsByBidder map[string]map[openrtb_ext.BidderName]*entities.PbsOrtbBid
 	// roundedPrices stores the price strings rounded for each bid according to the price granularity.
-	roundedPrices map[*pbsOrtbBid]string
+	roundedPrices map[*entities.PbsOrtbBid]string
 	// cacheIds stores the UUIDs from Prebid Cache for fetching the full bid JSON.
 	cacheIds map[*openrtb2.Bid]string
 	// vastCacheIds stores UUIDS from Prebid cache for fetching the VAST markup to video bids.
diff --git a/exchange/bidder.go b/exchange/bidder.go
index 53d5efe1..969b1887 100644
--- a/exchange/bidder.go
+++ b/exchange/bidder.go
@@ -18,6 +18,7 @@ import (
 	"github.com/golang/glog"
 	"github.com/prebid/prebid-server/config/util"
 	"github.com/prebid/prebid-server/currency"
+	"github.com/prebid/prebid-server/exchange/entities"
 	"github.com/prebid/prebid-server/experiment/adscert"
 	"github.com/prebid/prebid-server/hooks/hookexecution"
 	"github.com/prebid/prebid-server/version"
@@ -55,7 +56,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, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, hookExecutor hookexecution.StageExecutor) ([]*pbsOrtbSeatBid, []error)
+	requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, hookExecutor hookexecution.StageExecutor) ([]*entities.PbsOrtbSeatBid, []error)
 }
 
 // bidRequestOptions holds additional options for bid request execution to maintain clean code and reasonable number of parameters
@@ -68,47 +69,6 @@ type bidRequestOptions struct {
 
 const ImpIdReqBody = "Stored bid response for impression id: "
 
-// pbsOrtbBid is a Bid returned by an AdaptedBidder.
-//
-// pbsOrtbBid.bid.Ext will become "response.seatbid[i].bid.ext.bidder" in the final OpenRTB response.
-// pbsOrtbBid.bidMeta will become "response.seatbid[i].bid.ext.prebid.meta" in the final OpenRTB response.
-// pbsOrtbBid.bidType will become "response.seatbid[i].bid.ext.prebid.type" in the final OpenRTB response.
-// pbsOrtbBid.bidTargets does not need to be filled out by the Bidder. It will be set later by the exchange.
-// pbsOrtbBid.bidVideo is optional but should be filled out by the Bidder if bidType is video.
-// pbsOrtbBid.bidEvents is set by exchange when event tracking is enabled
-// pbsOrtbBid.dealPriority is optionally provided by adapters and used internally by the exchange to support deal targeted campaigns.
-// pbsOrtbBid.dealTierSatisfied is set to true by exchange.updateHbPbCatDur if deal tier satisfied otherwise it will be set to false
-// pbsOrtbBid.generatedBidID is unique bid id generated by prebid server if generate bid id option is enabled in config
-type pbsOrtbBid struct {
-	bid               *openrtb2.Bid
-	bidMeta           *openrtb_ext.ExtBidPrebidMeta
-	bidType           openrtb_ext.BidType
-	bidTargets        map[string]string
-	bidVideo          *openrtb_ext.ExtBidPrebidVideo
-	bidEvents         *openrtb_ext.ExtBidPrebidEvents
-	dealPriority      int
-	dealTierSatisfied bool
-	generatedBidID    string
-	originalBidCPM    float64
-	originalBidCur    string
-}
-
-// pbsOrtbSeatBid is a SeatBid returned by an AdaptedBidder.
-//
-// This is distinct from the openrtb2.SeatBid so that the prebid-server ext can be passed back with typesafety.
-type pbsOrtbSeatBid struct {
-	// bids is the list of bids which this AdaptedBidder wishes to make.
-	bids []*pbsOrtbBid
-	// currency is the currency in which the bids are made.
-	// Should be a valid currency ISO code.
-	currency string
-	// 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
-}
-
 // Possible values of compression types Prebid Server can support for bidder compression
 const (
 	Gzip string = "GZIP"
@@ -155,7 +115,7 @@ type bidderAdapterConfig struct {
 	EndpointCompression string
 }
 
-func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, hookExecutor hookexecution.StageExecutor) ([]*pbsOrtbSeatBid, []error) {
+func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, hookExecutor hookexecution.StageExecutor) ([]*entities.PbsOrtbSeatBid, []error) {
 	reject := hookExecutor.ExecuteBidderRequestStage(bidderRequest.BidRequest, string(bidderRequest.BidderName))
 	if reject != nil {
 		return nil, []error{reject}
@@ -232,12 +192,12 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde
 	}
 
 	defaultCurrency := "USD"
-	seatBidMap := map[openrtb_ext.BidderName]*pbsOrtbSeatBid{
+	seatBidMap := map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{
 		bidderRequest.BidderName: {
-			bids:      make([]*pbsOrtbBid, 0, dataLen),
-			currency:  defaultCurrency,
-			httpCalls: make([]*openrtb_ext.ExtHttpCall, 0, dataLen),
-			seat:      string(bidderRequest.BidderName),
+			Bids:      make([]*entities.PbsOrtbBid, 0, dataLen),
+			Currency:  defaultCurrency,
+			HttpCalls: make([]*openrtb_ext.ExtHttpCall, 0, dataLen),
+			Seat:      string(bidderRequest.BidderName),
 		},
 	}
 
@@ -251,11 +211,11 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde
 		// - account debug is allowed
 		// - bidder debug is allowed
 		if bidRequestOptions.headerDebugAllowed {
-			seatBidMap[bidderRequest.BidderName].httpCalls = append(seatBidMap[bidderRequest.BidderName].httpCalls, makeExt(httpInfo))
+			seatBidMap[bidderRequest.BidderName].HttpCalls = append(seatBidMap[bidderRequest.BidderName].HttpCalls, makeExt(httpInfo))
 		} else {
 			if bidRequestOptions.accountDebugAllowed {
 				if bidder.config.DebugInfo.Allow {
-					seatBidMap[bidderRequest.BidderName].httpCalls = append(seatBidMap[bidderRequest.BidderName].httpCalls, makeExt(httpInfo))
+					seatBidMap[bidderRequest.BidderName].HttpCalls = append(seatBidMap[bidderRequest.BidderName].HttpCalls, makeExt(httpInfo))
 				} else {
 					debugDisabledWarning := errortypes.Warning{
 						WarningCode: errortypes.BidderLevelDebugDisabledWarningCode,
@@ -291,7 +251,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 {
-						seatBidMap[bidderRequest.BidderName].currency = bidReqCur
+						seatBidMap[bidderRequest.BidderName].Currency = bidReqCur
 						break
 					}
 				}
@@ -370,23 +330,23 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde
 
 						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: seatBidMap[bidderRequest.BidderName].currency,
+							seatBidMap[bidderName] = &entities.PbsOrtbSeatBid{
+								Bids:     make([]*entities.PbsOrtbBid, 0, dataLen),
+								Currency: seatBidMap[bidderRequest.BidderName].Currency,
 								// Do we need to fill httpCalls for this?. Can we refer one from adaptercode for debugging?
-								httpCalls: seatBidMap[bidderRequest.BidderName].httpCalls,
-								seat:      bidderName.String(),
+								HttpCalls: seatBidMap[bidderRequest.BidderName].HttpCalls,
+								Seat:      bidderName.String(),
 							}
 						}
 
-						seatBidMap[bidderName].bids = append(seatBidMap[bidderName].bids, &pbsOrtbBid{
-							bid:            bidResponse.Bids[i].Bid,
-							bidMeta:        bidResponse.Bids[i].BidMeta,
-							bidType:        bidResponse.Bids[i].BidType,
-							bidVideo:       bidResponse.Bids[i].BidVideo,
-							dealPriority:   bidResponse.Bids[i].DealPriority,
-							originalBidCPM: originalBidCpm,
-							originalBidCur: bidResponse.Currency,
+						seatBidMap[bidderName].Bids = append(seatBidMap[bidderName].Bids, &entities.PbsOrtbBid{
+							Bid:            bidResponse.Bids[i].Bid,
+							BidMeta:        bidResponse.Bids[i].BidMeta,
+							BidType:        bidResponse.Bids[i].BidType,
+							BidVideo:       bidResponse.Bids[i].BidVideo,
+							DealPriority:   bidResponse.Bids[i].DealPriority,
+							OriginalBidCPM: originalBidCpm,
+							OriginalBidCur: bidResponse.Currency,
 						})
 					}
 				} else {
@@ -399,7 +359,7 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde
 		}
 	}
 
-	seatBids := make([]*pbsOrtbSeatBid, 0, len(seatBidMap))
+	seatBids := make([]*entities.PbsOrtbSeatBid, 0, len(seatBidMap))
 	for _, seatBid := range seatBidMap {
 		seatBids = append(seatBids, seatBid)
 	}
diff --git a/exchange/bidder_validate_bids.go b/exchange/bidder_validate_bids.go
index 12dc21de..b3886941 100644
--- a/exchange/bidder_validate_bids.go
+++ b/exchange/bidder_validate_bids.go
@@ -9,6 +9,7 @@ import (
 	"github.com/prebid/openrtb/v17/openrtb2"
 	"github.com/prebid/prebid-server/adapters"
 	"github.com/prebid/prebid-server/currency"
+	"github.com/prebid/prebid-server/exchange/entities"
 	"github.com/prebid/prebid-server/experiment/adscert"
 	"github.com/prebid/prebid-server/hooks/hookexecution"
 	"github.com/prebid/prebid-server/openrtb_ext"
@@ -30,7 +31,7 @@ type validatedBidder struct {
 	bidder AdaptedBidder
 }
 
-func (v *validatedBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, hookExecutor hookexecution.StageExecutor) ([]*pbsOrtbSeatBid, []error) {
+func (v *validatedBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, hookExecutor hookexecution.StageExecutor) ([]*entities.PbsOrtbSeatBid, []error) {
 	seatBids, errs := v.bidder.requestBid(ctx, bidderRequest, conversions, reqInfo, adsCertSigner, bidRequestOptions, alternateBidderCodes, hookExecutor)
 	for _, seatBid := range seatBids {
 		if validationErrors := removeInvalidBids(bidderRequest.BidRequest, seatBid); len(validationErrors) > 0 {
@@ -41,28 +42,28 @@ func (v *validatedBidder) requestBid(ctx context.Context, bidderRequest BidderRe
 }
 
 // validateBids will run some validation checks on the returned bids and excise any invalid bids
-func removeInvalidBids(request *openrtb2.BidRequest, seatBid *pbsOrtbSeatBid) []error {
+func removeInvalidBids(request *openrtb2.BidRequest, seatBid *entities.PbsOrtbSeatBid) []error {
 	// Exit early if there is nothing to do.
-	if seatBid == nil || len(seatBid.bids) == 0 {
+	if seatBid == nil || len(seatBid.Bids) == 0 {
 		return nil
 	}
 
 	// By design, default currency is USD.
-	if cerr := validateCurrency(request.Cur, seatBid.currency); cerr != nil {
-		seatBid.bids = nil
+	if cerr := validateCurrency(request.Cur, seatBid.Currency); cerr != nil {
+		seatBid.Bids = nil
 		return []error{cerr}
 	}
 
-	errs := make([]error, 0, len(seatBid.bids))
-	validBids := make([]*pbsOrtbBid, 0, len(seatBid.bids))
-	for _, bid := range seatBid.bids {
+	errs := make([]error, 0, len(seatBid.Bids))
+	validBids := make([]*entities.PbsOrtbBid, 0, len(seatBid.Bids))
+	for _, bid := range seatBid.Bids {
 		if ok, berr := validateBid(bid); ok {
 			validBids = append(validBids, bid)
 		} else {
 			errs = append(errs, berr)
 		}
 	}
-	seatBid.bids = validBids
+	seatBid.Bids = validBids
 	return errs
 }
 
@@ -103,25 +104,25 @@ func validateCurrency(requestAllowedCurrencies []string, bidCurrency string) err
 }
 
 // validateBid will run the supplied bid through validation checks and return true if it passes, false otherwise.
-func validateBid(bid *pbsOrtbBid) (bool, error) {
-	if bid.bid == nil {
+func validateBid(bid *entities.PbsOrtbBid) (bool, error) {
+	if bid.Bid == nil {
 		return false, errors.New("Empty bid object submitted.")
 	}
 
-	if bid.bid.ID == "" {
+	if bid.Bid.ID == "" {
 		return false, errors.New("Bid missing required field 'id'")
 	}
-	if bid.bid.ImpID == "" {
-		return false, fmt.Errorf("Bid \"%s\" missing required field 'impid'", bid.bid.ID)
+	if bid.Bid.ImpID == "" {
+		return false, fmt.Errorf("Bid \"%s\" missing required field 'impid'", bid.Bid.ID)
 	}
-	if bid.bid.Price < 0.0 {
-		return false, fmt.Errorf("Bid \"%s\" does not contain a positive (or zero if there is a deal) 'price'", bid.bid.ID)
+	if bid.Bid.Price < 0.0 {
+		return false, fmt.Errorf("Bid \"%s\" does not contain a positive (or zero if there is a deal) 'price'", bid.Bid.ID)
 	}
-	if bid.bid.Price == 0.0 && bid.bid.DealID == "" {
-		return false, fmt.Errorf("Bid \"%s\" does not contain positive 'price' which is required since there is no deal set for this bid", bid.bid.ID)
+	if bid.Bid.Price == 0.0 && bid.Bid.DealID == "" {
+		return false, fmt.Errorf("Bid \"%s\" does not contain positive 'price' which is required since there is no deal set for this bid", bid.Bid.ID)
 	}
-	if bid.bid.CrID == "" {
-		return false, fmt.Errorf("Bid \"%s\" missing creative ID", bid.bid.ID)
+	if bid.Bid.CrID == "" {
+		return false, fmt.Errorf("Bid \"%s\" missing creative ID", bid.Bid.ID)
 	}
 
 	return true, nil
diff --git a/exchange/entities/entities.go b/exchange/entities/entities.go
new file mode 100644
index 00000000..f038d6b2
--- /dev/null
+++ b/exchange/entities/entities.go
@@ -0,0 +1,47 @@
+package entities
+
+import (
+	"github.com/prebid/openrtb/v17/openrtb2"
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+// PbsOrtbSeatBid is a SeatBid returned by an AdaptedBidder.
+//
+// This is distinct from the openrtb2.SeatBid so that the prebid-server ext can be passed back with typesafety.
+type PbsOrtbSeatBid struct {
+	// Bids is the list of Bids which this AdaptedBidder wishes to make.
+	Bids []*PbsOrtbBid
+	// Currency is the Currency in which the Bids are made.
+	// Should be a valid Currency ISO code.
+	Currency string
+	// 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
+}
+
+// PbsOrtbBid is a Bid returned by an AdaptedBidder.
+//
+// PbsOrtbBid.Bid.Ext will become "response.seatbid[i].Bid.ext.bidder" in the final OpenRTB response.
+// PbsOrtbBid.BidMeta will become "response.seatbid[i].Bid.ext.prebid.meta" in the final OpenRTB response.
+// PbsOrtbBid.BidType will become "response.seatbid[i].Bid.ext.prebid.type" in the final OpenRTB response.
+// PbsOrtbBid.BidTargets does not need to be filled out by the Bidder. It will be set later by the exchange.
+// PbsOrtbBid.BidVideo is optional but should be filled out by the Bidder if BidType is video.
+// PbsOrtbBid.BidEvents is set by exchange when event tracking is enabled
+// PbsOrtbBid.DealPriority is optionally provided by adapters and used internally by the exchange to support deal targeted campaigns.
+// PbsOrtbBid.DealTierSatisfied is set to true by exchange.updateHbPbCatDur if deal tier satisfied otherwise it will be set to false
+// PbsOrtbBid.GeneratedBidID is unique Bid id generated by prebid server if generate Bid id option is enabled in config
+type PbsOrtbBid struct {
+	Bid               *openrtb2.Bid
+	BidMeta           *openrtb_ext.ExtBidPrebidMeta
+	BidType           openrtb_ext.BidType
+	BidTargets        map[string]string
+	BidVideo          *openrtb_ext.ExtBidPrebidVideo
+	BidEvents         *openrtb_ext.ExtBidPrebidEvents
+	DealPriority      int
+	DealTierSatisfied bool
+	GeneratedBidID    string
+	OriginalBidCPM    float64
+	OriginalBidCur    string
+}
diff --git a/exchange/events.go b/exchange/events.go
index 02fe10a9..9b856075 100644
--- a/exchange/events.go
+++ b/exchange/events.go
@@ -4,6 +4,7 @@ import (
 	"encoding/json"
 	"time"
 
+	"github.com/prebid/prebid-server/exchange/entities"
 	jsonpatch "gopkg.in/evanphx/json-patch.v4"
 
 	"github.com/prebid/prebid-server/analytics"
@@ -37,14 +38,14 @@ func getEventTracking(requestExtPrebid *openrtb_ext.ExtRequestPrebid, ts time.Ti
 }
 
 // modifyBidsForEvents adds bidEvents and modifies VAST AdM if necessary.
-func (ev *eventTracking) modifyBidsForEvents(seatBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid) map[openrtb_ext.BidderName]*pbsOrtbSeatBid {
+func (ev *eventTracking) modifyBidsForEvents(seatBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid) map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid {
 	for bidderName, seatBid := range seatBids {
 		modifyingVastXMLAllowed := ev.isModifyingVASTXMLAllowed(bidderName.String())
-		for _, pbsBid := range seatBid.bids {
+		for _, pbsBid := range seatBid.Bids {
 			if modifyingVastXMLAllowed {
 				ev.modifyBidVAST(pbsBid, bidderName)
 			}
-			pbsBid.bidEvents = ev.makeBidExtEvents(pbsBid, bidderName)
+			pbsBid.BidEvents = ev.makeBidExtEvents(pbsBid, bidderName)
 		}
 	}
 	return seatBids
@@ -56,15 +57,15 @@ func (ev *eventTracking) isModifyingVASTXMLAllowed(bidderName string) bool {
 }
 
 // modifyBidVAST injects event Impression url if needed, otherwise returns original VAST string
-func (ev *eventTracking) modifyBidVAST(pbsBid *pbsOrtbBid, bidderName openrtb_ext.BidderName) {
-	bid := pbsBid.bid
-	if pbsBid.bidType != openrtb_ext.BidTypeVideo || len(bid.AdM) == 0 && len(bid.NURL) == 0 {
+func (ev *eventTracking) modifyBidVAST(pbsBid *entities.PbsOrtbBid, bidderName openrtb_ext.BidderName) {
+	bid := pbsBid.Bid
+	if pbsBid.BidType != openrtb_ext.BidTypeVideo || len(bid.AdM) == 0 && len(bid.NURL) == 0 {
 		return
 	}
 	vastXML := makeVAST(bid)
 	bidID := bid.ID
-	if len(pbsBid.generatedBidID) > 0 {
-		bidID = pbsBid.generatedBidID
+	if len(pbsBid.GeneratedBidID) > 0 {
+		bidID = pbsBid.GeneratedBidID
 	}
 	if newVastXML, ok := events.ModifyVastXmlString(ev.externalURL, vastXML, bidID, bidderName.String(), ev.accountID, ev.auctionTimestampMs, ev.integrationType); ok {
 		bid.AdM = newVastXML
@@ -72,13 +73,13 @@ func (ev *eventTracking) modifyBidVAST(pbsBid *pbsOrtbBid, bidderName openrtb_ex
 }
 
 // modifyBidJSON injects "wurl" (win) event url if needed, otherwise returns original json
-func (ev *eventTracking) modifyBidJSON(pbsBid *pbsOrtbBid, bidderName openrtb_ext.BidderName, jsonBytes []byte) ([]byte, error) {
-	if !(ev.enabledForAccount || ev.enabledForRequest) || pbsBid.bidType == openrtb_ext.BidTypeVideo {
+func (ev *eventTracking) modifyBidJSON(pbsBid *entities.PbsOrtbBid, bidderName openrtb_ext.BidderName, jsonBytes []byte) ([]byte, error) {
+	if !(ev.enabledForAccount || ev.enabledForRequest) || pbsBid.BidType == openrtb_ext.BidTypeVideo {
 		return jsonBytes, nil
 	}
 	var winEventURL string
-	if pbsBid.bidEvents != nil { // All bids should have already been updated with win/imp event URLs
-		winEventURL = pbsBid.bidEvents.Win
+	if pbsBid.BidEvents != nil { // All bids should have already been updated with win/imp event URLs
+		winEventURL = pbsBid.BidEvents.Win
 	} else {
 		winEventURL = ev.makeEventURL(analytics.Win, pbsBid, bidderName)
 	}
@@ -95,8 +96,8 @@ func (ev *eventTracking) modifyBidJSON(pbsBid *pbsOrtbBid, bidderName openrtb_ex
 }
 
 // makeBidExtEvents make the data for bid.ext.prebid.events if needed, otherwise returns nil
-func (ev *eventTracking) makeBidExtEvents(pbsBid *pbsOrtbBid, bidderName openrtb_ext.BidderName) *openrtb_ext.ExtBidPrebidEvents {
-	if !(ev.enabledForAccount || ev.enabledForRequest) || pbsBid.bidType == openrtb_ext.BidTypeVideo {
+func (ev *eventTracking) makeBidExtEvents(pbsBid *entities.PbsOrtbBid, bidderName openrtb_ext.BidderName) *openrtb_ext.ExtBidPrebidEvents {
+	if !(ev.enabledForAccount || ev.enabledForRequest) || pbsBid.BidType == openrtb_ext.BidTypeVideo {
 		return nil
 	}
 	return &openrtb_ext.ExtBidPrebidEvents{
@@ -106,10 +107,10 @@ func (ev *eventTracking) makeBidExtEvents(pbsBid *pbsOrtbBid, bidderName openrtb
 }
 
 // makeEventURL returns an analytics event url for the requested type (win or imp)
-func (ev *eventTracking) makeEventURL(evType analytics.EventType, pbsBid *pbsOrtbBid, bidderName openrtb_ext.BidderName) string {
-	bidId := pbsBid.bid.ID
-	if len(pbsBid.generatedBidID) > 0 {
-		bidId = pbsBid.generatedBidID
+func (ev *eventTracking) makeEventURL(evType analytics.EventType, pbsBid *entities.PbsOrtbBid, bidderName openrtb_ext.BidderName) string {
+	bidId := pbsBid.Bid.ID
+	if len(pbsBid.GeneratedBidID) > 0 {
+		bidId = pbsBid.GeneratedBidID
 	}
 	return events.EventRequestToUrl(ev.externalURL,
 		&analytics.EventRequest{
diff --git a/exchange/exchange.go b/exchange/exchange.go
index 611f7c06..5c186070 100644
--- a/exchange/exchange.go
+++ b/exchange/exchange.go
@@ -18,6 +18,7 @@ import (
 	"github.com/prebid/prebid-server/config"
 	"github.com/prebid/prebid-server/currency"
 	"github.com/prebid/prebid-server/errortypes"
+	"github.com/prebid/prebid-server/exchange/entities"
 	"github.com/prebid/prebid-server/experiment/adscert"
 	"github.com/prebid/prebid-server/firstpartydata"
 	"github.com/prebid/prebid-server/gdpr"
@@ -84,7 +85,7 @@ type seatResponseExtra struct {
 }
 
 type bidResponseWrapper struct {
-	adapterSeatBids []*pbsOrtbSeatBid
+	adapterSeatBids []*entities.PbsOrtbSeatBid
 	adapterExtra    *seatResponseExtra
 	bidder          openrtb_ext.BidderName
 }
@@ -281,7 +282,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog *
 	// Get currency rates conversions for the auction
 	conversions := e.getAuctionCurrencyRates(requestExt.Prebid.CurrencyConversions)
 
-	var adapterBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid
+	var adapterBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid
 	var adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra
 	var anyBidsReturned bool
 
@@ -330,8 +331,8 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog *
 
 		if e.bidIDGenerator.Enabled() {
 			for _, seatBid := range adapterBids {
-				for _, pbsBid := range seatBid.bids {
-					pbsBid.generatedBidID, err = e.bidIDGenerator.New()
+				for _, pbsBid := range seatBid.Bids {
+					pbsBid.GeneratedBidID, err = e.bidIDGenerator.New()
 					if err != nil {
 						errs = append(errs, errors.New("Error generating bid.ext.prebid.bidid"))
 					}
@@ -342,6 +343,8 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog *
 		evTracking := getEventTracking(&requestExt.Prebid, r.StartTime, &r.Account, e.bidderInfo, e.externalURL)
 		adapterBids = evTracking.modifyBidsForEvents(adapterBids)
 
+		r.HookExecutor.ExecuteAllProcessedBidResponsesStage(adapterBids)
+
 		if targData != nil {
 			// A non-nil auction is only needed if targeting is active. (It is used below this block to extract cache keys)
 			auc = newAuction(adapterBids, len(r.BidRequestWrapper.Imp), targData.preferDeals)
@@ -448,7 +451,7 @@ func applyDealSupport(bidRequest *openrtb2.BidRequest, auc *auction, bidCategory
 	for impID, topBidsPerImp := range auc.winningBidsByBidder {
 		impDeal := impDealMap[impID]
 		for bidder, topBidPerBidder := range topBidsPerImp {
-			if topBidPerBidder.dealPriority > 0 {
+			if topBidPerBidder.DealPriority > 0 {
 				if validateDealTier(impDeal[bidder]) {
 					updateHbPbCatDur(topBidPerBidder, impDeal[bidder], bidCategory)
 				} else {
@@ -480,17 +483,17 @@ func validateDealTier(dealTier openrtb_ext.DealTier) bool {
 	return len(dealTier.Prefix) > 0 && dealTier.MinDealTier > 0
 }
 
-func updateHbPbCatDur(bid *pbsOrtbBid, dealTier openrtb_ext.DealTier, bidCategory map[string]string) {
-	if bid.dealPriority >= dealTier.MinDealTier {
-		prefixTier := fmt.Sprintf("%s%d_", dealTier.Prefix, bid.dealPriority)
-		bid.dealTierSatisfied = true
+func updateHbPbCatDur(bid *entities.PbsOrtbBid, dealTier openrtb_ext.DealTier, bidCategory map[string]string) {
+	if bid.DealPriority >= dealTier.MinDealTier {
+		prefixTier := fmt.Sprintf("%s%d_", dealTier.Prefix, bid.DealPriority)
+		bid.DealTierSatisfied = true
 
-		if oldCatDur, ok := bidCategory[bid.bid.ID]; ok {
+		if oldCatDur, ok := bidCategory[bid.Bid.ID]; ok {
 			oldCatDurSplit := strings.SplitAfterN(oldCatDur, "_", 2)
 			oldCatDurSplit[0] = prefixTier
 
 			newCatDur := strings.Join(oldCatDurSplit, "")
-			bidCategory[bid.bid.ID] = newCatDur
+			bidCategory[bid.Bid.ID] = newCatDur
 		}
 	}
 }
@@ -518,10 +521,10 @@ func (e *exchange) getAllBids(
 	alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes,
 	experiment *openrtb_ext.Experiment,
 	hookExecutor hookexecution.StageExecutor) (
-	map[openrtb_ext.BidderName]*pbsOrtbSeatBid,
+	map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid,
 	map[openrtb_ext.BidderName]*seatResponseExtra, bool) {
 	// Set up pointers to the bid results
-	adapterBids := make(map[openrtb_ext.BidderName]*pbsOrtbSeatBid, len(bidderRequests))
+	adapterBids := make(map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, len(bidderRequests))
 	adapterExtra := make(map[openrtb_ext.BidderName]*seatResponseExtra, len(bidderRequests))
 	chBids := make(chan *bidResponseWrapper, len(bidderRequests))
 	bidsFound := false
@@ -561,7 +564,7 @@ func (e *exchange) getAllBids(
 			ae := new(seatResponseExtra)
 			ae.ResponseTimeMillis = int(elapsed / time.Millisecond)
 			if len(seatBids) != 0 {
-				ae.HttpCalls = seatBids[0].httpCalls
+				ae.HttpCalls = seatBids[0].HttpCalls
 			}
 
 			// Timing statistics
@@ -574,10 +577,10 @@ func (e *exchange) getAllBids(
 			brw.adapterExtra = ae
 			for _, seatBid := range seatBids {
 				if seatBid != nil {
-					for _, bid := range seatBid.bids {
-						var cpm = float64(bid.bid.Price * 1000)
+					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 != "")
+						e.me.RecordAdapterBidReceived(bidderRequest.BidderLabels, bid.BidType, bid.Bid.AdM != "")
 					}
 				}
 			}
@@ -591,18 +594,18 @@ func (e *exchange) getAllBids(
 
 		//if bidder returned no bids back - remove bidder from further processing
 		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...)
+			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
+					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
 
-		if !bidsFound && adapterBids[brw.bidder] != nil && len(adapterBids[brw.bidder].bids) > 0 {
+		if !bidsFound && adapterBids[brw.bidder] != nil && len(adapterBids[brw.bidder].Bids) > 0 {
 			bidsFound = true
 		}
 	}
@@ -641,9 +644,9 @@ func (e *exchange) recoverSafely(bidderRequests []BidderRequest,
 	}
 }
 
-func bidsToMetric(seatBids []*pbsOrtbSeatBid) metrics.AdapterBid {
+func bidsToMetric(seatBids []*entities.PbsOrtbSeatBid) metrics.AdapterBid {
 	for _, seatBid := range seatBids {
-		if seatBid != nil && len(seatBid.bids) != 0 {
+		if seatBid != nil && len(seatBid.Bids) != 0 {
 			return metrics.AdapterBidPresent
 		}
 	}
@@ -701,7 +704,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, 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) {
+func (e *exchange) buildBidResponse(ctx context.Context, liveAdapters []openrtb_ext.BidderName, adapterSeatBids map[openrtb_ext.BidderName]*entities.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
 
@@ -716,10 +719,10 @@ func (e *exchange) buildBidResponse(ctx context.Context, liveAdapters []openrtb_
 	seatBids := make([]openrtb2.SeatBid, 0, len(liveAdapters))
 	for a, adapterSeatBids := range adapterSeatBids {
 		//while processing every single bib, do we need to handle categories here?
-		if adapterSeatBids != nil && len(adapterSeatBids.bids) > 0 {
+		if adapterSeatBids != nil && len(adapterSeatBids.Bids) > 0 {
 			sb := e.makeSeatBid(adapterSeatBids, a, adapterExtra, auc, returnCreative, impExtInfoMap)
 			seatBids = append(seatBids, *sb)
-			bidResponse.Cur = adapterSeatBids.currency
+			bidResponse.Cur = adapterSeatBids.Currency
 		}
 	}
 
@@ -740,7 +743,7 @@ func encodeBidResponseExt(bidResponseExt *openrtb_ext.ExtBidResponse) ([]byte, e
 	return buffer.Bytes(), err
 }
 
-func applyCategoryMapping(ctx context.Context, requestExt *openrtb_ext.ExtRequest, seatBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, categoriesFetcher stored_requests.CategoryFetcher, targData *targetData, booleanGenerator deduplicateChanceGenerator) (map[string]string, map[openrtb_ext.BidderName]*pbsOrtbSeatBid, []string, error) {
+func applyCategoryMapping(ctx context.Context, requestExt *openrtb_ext.ExtRequest, seatBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, categoriesFetcher stored_requests.CategoryFetcher, targData *targetData, booleanGenerator deduplicateChanceGenerator) (map[string]string, map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, []string, error) {
 	res := make(map[string]string)
 
 	type bidDedupe struct {
@@ -785,19 +788,19 @@ func applyCategoryMapping(ctx context.Context, requestExt *openrtb_ext.ExtReques
 
 	for bidderName, seatBid := range seatBids {
 		bidsToRemove := make([]int, 0)
-		for bidInd := range seatBid.bids {
-			bid := seatBid.bids[bidInd]
-			bidID := bid.bid.ID
+		for bidInd := range seatBid.Bids {
+			bid := seatBid.Bids[bidInd]
+			bidID := bid.Bid.ID
 			var duration int
 			var category string
 			var pb string
 
-			if bid.bidVideo != nil {
-				duration = bid.bidVideo.Duration
-				category = bid.bidVideo.PrimaryCategory
+			if bid.BidVideo != nil {
+				duration = bid.BidVideo.Duration
+				category = bid.BidVideo.PrimaryCategory
 			}
 			if brandCatExt.WithCategory && category == "" {
-				bidIabCat := bid.bid.Cat
+				bidIabCat := bid.Bid.Cat
 				if len(bidIabCat) != 1 {
 					//TODO: add metrics
 					//on receiving bids from adapters if no unique IAB category is returned  or if no ad server category is returned discard the bid
@@ -824,7 +827,7 @@ func applyCategoryMapping(ctx context.Context, requestExt *openrtb_ext.ExtReques
 
 			// TODO: consider should we remove bids with zero duration here?
 
-			pb = GetPriceBucket(bid.bid.Price, targData.priceGranularity)
+			pb = GetPriceBucket(bid.Bid.Price, targData.priceGranularity)
 
 			newDur := duration
 			if len(requestExt.Prebid.Targeting.DurationRangeSec) > 0 {
@@ -885,7 +888,7 @@ func applyCategoryMapping(ctx context.Context, requestExt *openrtb_ext.ExtReques
 						// An older bid from a different seatBid we've already finished with
 						oldSeatBid := (seatBids)[dupe.bidderName]
 						rejections = updateRejections(rejections, dupe.bidID, "Bid was deduplicated")
-						if len(oldSeatBid.bids) == 1 {
+						if len(oldSeatBid.Bids) == 1 {
 							seatBidsToRemove = append(seatBidsToRemove, dupe.bidderName)
 						} else {
 							// This is a very rare, but still possible case where bid needs to be removed from already processed bidder
@@ -910,41 +913,41 @@ func applyCategoryMapping(ctx context.Context, requestExt *openrtb_ext.ExtReques
 
 		if len(bidsToRemove) > 0 {
 			sort.Ints(bidsToRemove)
-			if len(bidsToRemove) == len(seatBid.bids) {
+			if len(bidsToRemove) == len(seatBid.Bids) {
 				//if all bids are invalid - remove entire seat bid
 				seatBidsToRemove = append(seatBidsToRemove, bidderName)
 			} else {
-				bids := seatBid.bids
+				bids := seatBid.Bids
 				for i := len(bidsToRemove) - 1; i >= 0; i-- {
 					remInd := bidsToRemove[i]
 					bids = append(bids[:remInd], bids[remInd+1:]...)
 				}
-				seatBid.bids = bids
+				seatBid.Bids = bids
 			}
 		}
 
 	}
 	for _, seatBidInd := range seatBidsToRemove {
-		seatBids[seatBidInd].bids = nil
+		seatBids[seatBidInd].Bids = nil
 	}
 
 	return res, seatBids, rejections, nil
 }
 
-func removeBidById(seatBid *pbsOrtbSeatBid, bidID string) {
+func removeBidById(seatBid *entities.PbsOrtbSeatBid, bidID string) {
 	//Find index of bid to remove
 	dupeBidIndex := -1
-	for i, bid := range seatBid.bids {
-		if bid.bid.ID == bidID {
+	for i, bid := range seatBid.Bids {
+		if bid.Bid.ID == bidID {
 			dupeBidIndex = i
 			break
 		}
 	}
 	if dupeBidIndex != -1 {
-		if dupeBidIndex < len(seatBid.bids)-1 {
-			seatBid.bids = append(seatBid.bids[:dupeBidIndex], seatBid.bids[dupeBidIndex+1:]...)
-		} else if dupeBidIndex == len(seatBid.bids)-1 {
-			seatBid.bids = seatBid.bids[:len(seatBid.bids)-1]
+		if dupeBidIndex < len(seatBid.Bids)-1 {
+			seatBid.Bids = append(seatBid.Bids[:dupeBidIndex], seatBid.Bids[dupeBidIndex+1:]...)
+		} else if dupeBidIndex == len(seatBid.Bids)-1 {
+			seatBid.Bids = seatBid.Bids[:len(seatBid.Bids)-1]
 		}
 	}
 }
@@ -966,7 +969,7 @@ func getPrimaryAdServer(adServerId int) (string, error) {
 }
 
 // Extract all the data from the SeatBids and build the ExtBidResponse
-func (e *exchange) makeExtBidResponse(adapterBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra, r AuctionRequest, debugInfo bool, passthrough json.RawMessage, errList []error) *openrtb_ext.ExtBidResponse {
+func (e *exchange) makeExtBidResponse(adapterBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra, r AuctionRequest, debugInfo bool, passthrough json.RawMessage, errList []error) *openrtb_ext.ExtBidResponse {
 	bidResponseExt := &openrtb_ext.ExtBidResponse{
 		Errors:               make(map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage, len(adapterBids)),
 		Warnings:             make(map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage, len(adapterBids)),
@@ -1017,14 +1020,14 @@ func (e *exchange) makeExtBidResponse(adapterBids map[openrtb_ext.BidderName]*pb
 
 // Return an openrtb seatBid for a bidder
 // BuildBidResponse is responsible for ensuring nil bid seatbids are not included
-func (e *exchange) makeSeatBid(adapterBid *pbsOrtbSeatBid, adapter openrtb_ext.BidderName, adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra, auc *auction, returnCreative bool, impExtInfoMap map[string]ImpExtInfo) *openrtb2.SeatBid {
+func (e *exchange) makeSeatBid(adapterBid *entities.PbsOrtbSeatBid, adapter openrtb_ext.BidderName, adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra, auc *auction, returnCreative bool, impExtInfoMap map[string]ImpExtInfo) *openrtb2.SeatBid {
 	seatBid := &openrtb2.SeatBid{
 		Seat:  adapter.String(),
 		Group: 0, // Prebid cannot support roadblocking
 	}
 
 	var errList []error
-	seatBid.Bid, errList = e.makeBid(adapterBid.bids, auc, returnCreative, impExtInfoMap)
+	seatBid.Bid, errList = e.makeBid(adapterBid.Bids, auc, returnCreative, impExtInfoMap)
 	if len(errList) > 0 {
 		adapterExtra[adapter].Errors = append(adapterExtra[adapter].Errors, errsToBidderErrors(errList)...)
 	}
@@ -1032,20 +1035,20 @@ func (e *exchange) makeSeatBid(adapterBid *pbsOrtbSeatBid, adapter openrtb_ext.B
 	return seatBid
 }
 
-func (e *exchange) makeBid(bids []*pbsOrtbBid, auc *auction, returnCreative bool, impExtInfoMap map[string]ImpExtInfo) ([]openrtb2.Bid, []error) {
+func (e *exchange) makeBid(bids []*entities.PbsOrtbBid, auc *auction, returnCreative bool, impExtInfoMap map[string]ImpExtInfo) ([]openrtb2.Bid, []error) {
 	result := make([]openrtb2.Bid, 0, len(bids))
 	errs := make([]error, 0, 1)
 
 	for _, bid := range bids {
 		bidExtPrebid := &openrtb_ext.ExtBidPrebid{
-			DealPriority:      bid.dealPriority,
-			DealTierSatisfied: bid.dealTierSatisfied,
-			Events:            bid.bidEvents,
-			Targeting:         bid.bidTargets,
-			Type:              bid.bidType,
-			Meta:              bid.bidMeta,
-			Video:             bid.bidVideo,
-			BidId:             bid.generatedBidID,
+			DealPriority:      bid.DealPriority,
+			DealTierSatisfied: bid.DealTierSatisfied,
+			Events:            bid.BidEvents,
+			Targeting:         bid.BidTargets,
+			Type:              bid.BidType,
+			Meta:              bid.BidMeta,
+			Video:             bid.BidVideo,
+			BidId:             bid.GeneratedBidID,
 		}
 
 		if cacheInfo, found := e.getBidCacheInfo(bid, auc); found {
@@ -1054,10 +1057,10 @@ func (e *exchange) makeBid(bids []*pbsOrtbBid, auc *auction, returnCreative bool
 			}
 		}
 
-		if bidExtJSON, err := makeBidExtJSON(bid.bid.Ext, bidExtPrebid, impExtInfoMap, bid.bid.ImpID, bid.originalBidCPM, bid.originalBidCur); err != nil {
+		if bidExtJSON, err := makeBidExtJSON(bid.Bid.Ext, bidExtPrebid, impExtInfoMap, bid.Bid.ImpID, bid.OriginalBidCPM, bid.OriginalBidCur); err != nil {
 			errs = append(errs, err)
 		} else {
-			result = append(result, *bid.bid)
+			result = append(result, *bid.Bid)
 			resultBid := &result[len(result)-1]
 			resultBid.Ext = bidExtJSON
 			if !returnCreative {
@@ -1122,7 +1125,7 @@ func makeBidExtJSON(ext json.RawMessage, prebid *openrtb_ext.ExtBidPrebid, impEx
 
 // If bid got cached inside `(a *auction) doCache(ctx context.Context, cache prebid_cache_client.Client, targData *targetData, bidRequest *openrtb2.BidRequest, ttlBuffer int64, defaultTTLs *config.DefaultTTLs, bidCategory map[string]string)`,
 // a UUID should be found inside `a.cacheIds` or `a.vastCacheIds`. This function returns the UUID along with the internal cache URL
-func (e *exchange) getBidCacheInfo(bid *pbsOrtbBid, auction *auction) (cacheInfo openrtb_ext.ExtBidPrebidCacheBids, found bool) {
+func (e *exchange) getBidCacheInfo(bid *entities.PbsOrtbBid, auction *auction) (cacheInfo openrtb_ext.ExtBidPrebidCacheBids, found bool) {
 	uuid, found := findCacheID(bid, auction)
 
 	if found {
@@ -1161,13 +1164,13 @@ func (e *exchange) getAuctionCurrencyRates(requestRates *openrtb_ext.ExtRequestC
 	return currency.NewAggregateConversions(currency.NewRates(requestRates.ConversionRates), e.currencyConverter.Rates())
 }
 
-func findCacheID(bid *pbsOrtbBid, auction *auction) (string, bool) {
-	if bid != nil && bid.bid != nil && auction != nil {
-		if id, found := auction.cacheIds[bid.bid]; found {
+func findCacheID(bid *entities.PbsOrtbBid, auction *auction) (string, bool) {
+	if bid != nil && bid.Bid != nil && auction != nil {
+		if id, found := auction.cacheIds[bid.Bid]; found {
 			return id, true
 		}
 
-		if id, found := auction.vastCacheIds[bid.bid]; found {
+		if id, found := auction.vastCacheIds[bid.Bid]; found {
 			return id, true
 		}
 	}
@@ -1209,9 +1212,9 @@ func listBiddersWithRequests(bidderRequests []BidderRequest) []openrtb_ext.Bidde
 	return liveAdapters
 }
 
-func buildStoredAuctionResponse(storedAuctionResponses map[string]json.RawMessage) (map[openrtb_ext.BidderName]*pbsOrtbSeatBid, []openrtb_ext.BidderName, error) {
+func buildStoredAuctionResponse(storedAuctionResponses map[string]json.RawMessage) (map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, []openrtb_ext.BidderName, error) {
 
-	adapterBids := make(map[openrtb_ext.BidderName]*pbsOrtbSeatBid, 0)
+	adapterBids := make(map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, 0)
 	liveAdapters := make([]openrtb_ext.BidderName, 0)
 	for impId, storedResp := range storedAuctionResponses {
 		var seatBids []openrtb2.SeatBid
@@ -1220,22 +1223,22 @@ func buildStoredAuctionResponse(storedAuctionResponses map[string]json.RawMessag
 			return nil, nil, err
 		}
 		for _, seat := range seatBids {
-			var bidsToAdd []*pbsOrtbBid
+			var bidsToAdd []*entities.PbsOrtbBid
 			//set imp id from request
 			for i := range seat.Bid {
 				seat.Bid[i].ImpID = impId
-				bidsToAdd = append(bidsToAdd, &pbsOrtbBid{bid: &seat.Bid[i]})
+				bidsToAdd = append(bidsToAdd, &entities.PbsOrtbBid{Bid: &seat.Bid[i]})
 			}
 
 			bidderName := openrtb_ext.BidderName(seat.Seat)
 
 			if _, ok := adapterBids[bidderName]; ok {
-				adapterBids[bidderName].bids = append(adapterBids[bidderName].bids, bidsToAdd...)
+				adapterBids[bidderName].Bids = append(adapterBids[bidderName].Bids, bidsToAdd...)
 
 			} else {
 				//create new seat bid and add it to live adapters
 				liveAdapters = append(liveAdapters, bidderName)
-				newSeatBid := pbsOrtbSeatBid{bidsToAdd, "", nil, ""}
+				newSeatBid := entities.PbsOrtbSeatBid{bidsToAdd, "", nil, ""}
 				adapterBids[bidderName] = &newSeatBid
 
 			}
diff --git a/exchange/targeting.go b/exchange/targeting.go
index 4b45fa3b..b636b2b9 100644
--- a/exchange/targeting.go
+++ b/exchange/targeting.go
@@ -46,17 +46,17 @@ func (targData *targetData) setTargeting(auc *auction, isApp bool, categoryMappi
 				targData.addKeys(targets, openrtb_ext.HbpbConstantKey, cpm, bidderName, isOverallWinner, truncateTargetAttr)
 			}
 			targData.addKeys(targets, openrtb_ext.HbBidderConstantKey, string(bidderName), bidderName, isOverallWinner, truncateTargetAttr)
-			if hbSize := makeHbSize(topBidPerBidder.bid); hbSize != "" {
+			if hbSize := makeHbSize(topBidPerBidder.Bid); hbSize != "" {
 				targData.addKeys(targets, openrtb_ext.HbSizeConstantKey, hbSize, bidderName, isOverallWinner, truncateTargetAttr)
 			}
-			if cacheID, ok := auc.cacheIds[topBidPerBidder.bid]; ok {
+			if cacheID, ok := auc.cacheIds[topBidPerBidder.Bid]; ok {
 				targData.addKeys(targets, openrtb_ext.HbCacheKey, cacheID, bidderName, isOverallWinner, truncateTargetAttr)
 			}
-			if vastID, ok := auc.vastCacheIds[topBidPerBidder.bid]; ok {
+			if vastID, ok := auc.vastCacheIds[topBidPerBidder.Bid]; ok {
 				targData.addKeys(targets, openrtb_ext.HbVastCacheKey, vastID, bidderName, isOverallWinner, truncateTargetAttr)
 			}
 			if targData.includeFormat {
-				targData.addKeys(targets, openrtb_ext.HbFormatKey, string(topBidPerBidder.bidType), bidderName, isOverallWinner, truncateTargetAttr)
+				targData.addKeys(targets, openrtb_ext.HbFormatKey, string(topBidPerBidder.BidType), bidderName, isOverallWinner, truncateTargetAttr)
 			}
 
 			if targData.cacheHost != "" {
@@ -66,7 +66,7 @@ func (targData *targetData) setTargeting(auc *auction, isApp bool, categoryMappi
 				targData.addKeys(targets, openrtb_ext.HbConstantCachePathKey, targData.cachePath, bidderName, isOverallWinner, truncateTargetAttr)
 			}
 
-			if deal := topBidPerBidder.bid.DealID; len(deal) > 0 {
+			if deal := topBidPerBidder.Bid.DealID; len(deal) > 0 {
 				targData.addKeys(targets, openrtb_ext.HbDealIDConstantKey, deal, bidderName, isOverallWinner, truncateTargetAttr)
 			}
 
@@ -74,10 +74,10 @@ func (targData *targetData) setTargeting(auc *auction, isApp bool, categoryMappi
 				targData.addKeys(targets, openrtb_ext.HbEnvKey, openrtb_ext.HbEnvKeyApp, bidderName, isOverallWinner, truncateTargetAttr)
 			}
 			if len(categoryMapping) > 0 {
-				targData.addKeys(targets, openrtb_ext.HbCategoryDurationKey, categoryMapping[topBidPerBidder.bid.ID], bidderName, isOverallWinner, truncateTargetAttr)
+				targData.addKeys(targets, openrtb_ext.HbCategoryDurationKey, categoryMapping[topBidPerBidder.Bid.ID], bidderName, isOverallWinner, truncateTargetAttr)
 			}
 
-			topBidPerBidder.bidTargets = targets
+			topBidPerBidder.BidTargets = targets
 		}
 	}
 }
diff --git a/hooks/hookexecution/executor.go b/hooks/hookexecution/executor.go
index 071060ec..82863a85 100644
--- a/hooks/hookexecution/executor.go
+++ b/hooks/hookexecution/executor.go
@@ -8,9 +8,11 @@ import (
 	"github.com/prebid/openrtb/v17/openrtb2"
 	"github.com/prebid/prebid-server/adapters"
 	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/exchange/entities"
 	"github.com/prebid/prebid-server/hooks"
 	"github.com/prebid/prebid-server/hooks/hookstage"
 	"github.com/prebid/prebid-server/metrics"
+	"github.com/prebid/prebid-server/openrtb_ext"
 )
 
 const (
@@ -34,6 +36,7 @@ type StageExecutor interface {
 	ExecuteProcessedAuctionStage(req *openrtb2.BidRequest) *RejectError
 	ExecuteBidderRequestStage(req *openrtb2.BidRequest, bidder string) *RejectError
 	ExecuteRawBidderResponseStage(response *adapters.BidderResponse, bidder string) *RejectError
+	ExecuteAllProcessedBidResponsesStage(adapterBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid)
 }
 
 type HookStageExecutor interface {
@@ -221,6 +224,32 @@ func (e *hookExecutor) ExecuteRawBidderResponseStage(response *adapters.BidderRe
 	return reject
 }
 
+func (e *hookExecutor) ExecuteAllProcessedBidResponsesStage(adapterBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid) {
+	plan := e.planBuilder.PlanForAllProcessedBidResponsesStage(e.endpoint, e.account)
+	if len(plan) == 0 {
+		return
+	}
+
+	handler := func(
+		ctx context.Context,
+		moduleCtx hookstage.ModuleInvocationContext,
+		hook hookstage.AllProcessedBidResponses,
+		payload hookstage.AllProcessedBidResponsesPayload,
+	) (hookstage.HookResult[hookstage.AllProcessedBidResponsesPayload], error) {
+		return hook.HandleAllProcessedBidResponsesHook(ctx, moduleCtx, payload)
+	}
+
+	stageName := hooks.StageAllProcessedBidResponses.String()
+	executionCtx := e.newContext(stageName)
+	payload := hookstage.AllProcessedBidResponsesPayload{Responses: adapterBids}
+	outcome, _, contexts, _ := executeStage(executionCtx, plan, payload, handler, e.metricEngine)
+	outcome.Entity = entityAllProcessedBidResponses
+	outcome.Stage = stageName
+
+	e.saveModuleContexts(contexts)
+	e.pushStageOutcome(outcome)
+}
+
 func (e *hookExecutor) newContext(stage string) executionContext {
 	return executionContext{
 		account:        e.account,
@@ -272,3 +301,6 @@ func (executor *EmptyHookExecutor) ExecuteBidderRequestStage(_ *openrtb2.BidRequ
 func (executor *EmptyHookExecutor) ExecuteRawBidderResponseStage(_ *adapters.BidderResponse, _ string) *RejectError {
 	return nil
 }
+
+func (executor *EmptyHookExecutor) ExecuteAllProcessedBidResponsesStage(_ map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid) {
+}
diff --git a/hooks/hookstage/allprocessedbidresponses.go b/hooks/hookstage/allprocessedbidresponses.go
index 85b3e4f9..3f90c562 100644
--- a/hooks/hookstage/allprocessedbidresponses.go
+++ b/hooks/hookstage/allprocessedbidresponses.go
@@ -2,6 +2,9 @@ package hookstage
 
 import (
 	"context"
+
+	"github.com/prebid/prebid-server/exchange/entities"
+	"github.com/prebid/prebid-server/openrtb_ext"
 )
 
 // AllProcessedBidResponses hooks are invoked over a list of all
@@ -24,5 +27,5 @@ type AllProcessedBidResponses interface {
 // processed responses received from bidders.
 // Hooks are allowed to modify payload object and discard bids using mutations.
 type AllProcessedBidResponsesPayload struct {
-	// todo: decide what payload to use within the hook invocation task
+	Responses map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid
 }
diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go
index c5de0576..fe128f2e 100644
--- a/openrtb_ext/bidders.go
+++ b/openrtb_ext/bidders.go
@@ -236,6 +236,7 @@ const (
 	BidderValueImpression   BidderName = "valueimpression"
 	BidderVerizonMedia      BidderName = "verizonmedia"
 	BidderVideoByte         BidderName = "videobyte"
+	BidderVideoHeroes       BidderName = "videoheroes"
 	BidderVidoomy           BidderName = "vidoomy"
 	BidderViewdeos          BidderName = "viewdeos"
 	BidderVisx              BidderName = "visx"
@@ -406,6 +407,7 @@ func CoreBidderNames() []BidderName {
 		BidderValueImpression,
 		BidderVerizonMedia,
 		BidderVideoByte,
+		BidderVideoHeroes,
 		BidderVidoomy,
 		BidderViewdeos,
 		BidderVisx,
diff --git a/openrtb_ext/imp_videoheroes.go b/openrtb_ext/imp_videoheroes.go
new file mode 100644
index 00000000..818f58c9
--- /dev/null
+++ b/openrtb_ext/imp_videoheroes.go
@@ -0,0 +1,5 @@
+package openrtb_ext
+
+type ExtImpVideoHeroes struct {
+	PlacementID string `json:"placementId"`
+}
diff --git a/static/bidder-info/videoheroes.yaml b/static/bidder-info/videoheroes.yaml
new file mode 100644
index 00000000..ed90d70c
--- /dev/null
+++ b/static/bidder-info/videoheroes.yaml
@@ -0,0 +1,14 @@
+endpoint: "http://point.contextualadv.com/?t=3&partner={{.PublisherID}}"
+maintainer:
+  email: "support@videoheroes.tv"
+capabilities:
+  app:
+    mediaTypes:
+      - banner
+      - video
+      - native
+  site:
+    mediaTypes:
+      - banner
+      - video
+      - native
diff --git a/static/bidder-params/videoheroes.json b/static/bidder-params/videoheroes.json
new file mode 100644
index 00000000..d442e953
--- /dev/null
+++ b/static/bidder-params/videoheroes.json
@@ -0,0 +1,13 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "title": "VideoHeroes Adapter Params",
+  "description": "A schema which validates params accepted by the VideoHeroes adapter",
+  "type": "object",
+  "properties": {
+    "placementId": {
+      "type": "string",
+      "description": "Unique Placement ID of publisher"
+    }
+  },
+  "required": ["placementId"]
+}
\ No newline at end of file


Test patch:
diff --git a/adapters/videoheroes/params_test.go b/adapters/videoheroes/params_test.go
new file mode 100644
index 00000000..d79f8324
--- /dev/null
+++ b/adapters/videoheroes/params_test.go
@@ -0,0 +1,51 @@
+package videoheroes
+
+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-schemas. %v", err)
+	}
+
+	for _, validParam := range validParams {
+		if err := validator.Validate(openrtb_ext.BidderVideoHeroes, json.RawMessage(validParam)); err != nil {
+			t.Errorf("Schema rejected VideoHeroes params: %s", validParam)
+		}
+	}
+}
+
+func TestInvalidParams(t *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+	if err != nil {
+		t.Fatalf("Failed to fetch the json-schemas. %v", err)
+	}
+
+	for _, invalidParam := range invalidParams {
+		if err := validator.Validate(openrtb_ext.BidderVideoHeroes, json.RawMessage(invalidParam)); err == nil {
+			t.Errorf("Schema allowed unexpected params: %s", invalidParam)
+		}
+	}
+}
+
+var validParams = []string{
+	`{ "placementId": "f897beb0daba0253d8e59a098eef9311" }`,
+}
+
+var invalidParams = []string{
+	``,
+	`null`,
+	`true`,
+	`5`,
+	`4.2`,
+	`[]`,
+	`{}`,
+	`{"adCode": "string", "seatCode": 5, "originalPublisherid": "string"}`,
+	`{ "placementid": "f897beb0daba0253d8e59a098eef9311" }`,
+	`{ "PlacementId": "f897beb0daba0253d8e59a098eef9311" }`,
+}
diff --git a/adapters/videoheroes/videoheroes_test.go b/adapters/videoheroes/videoheroes_test.go
new file mode 100644
index 00000000..ac60d56e
--- /dev/null
+++ b/adapters/videoheroes/videoheroes_test.go
@@ -0,0 +1,30 @@
+package videoheroes
+
+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.BidderVideoHeroes, config.Adapter{
+		Endpoint: "http://point.contextualadv.com/?t=3&partner={{.PublisherID}}"},
+		config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"})
+
+	if buildErr != nil {
+		t.Fatalf("Builder returned unexpected error %v", buildErr)
+	}
+
+	adapterstest.RunJSONBidderTest(t, "videoheroestest", bidder)
+}
+
+func TestEndpointTemplateMalformed(t *testing.T) {
+	_, buildErr := Builder(openrtb_ext.BidderVideoHeroes, config.Adapter{
+		Endpoint: "{{Malformed}}"},
+		config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"})
+
+	assert.Error(t, buildErr)
+}
diff --git a/adapters/videoheroes/videoheroestest/exemplary/banner-app.json b/adapters/videoheroes/videoheroestest/exemplary/banner-app.json
new file mode 100644
index 00000000..17d3add9
--- /dev/null
+++ b/adapters/videoheroes/videoheroestest/exemplary/banner-app.json
@@ -0,0 +1,140 @@
+{
+    "mockBidRequest": {
+      "id": "request-id",
+      "app": {
+        "publisher": {
+          "id": "123456789"
+        },
+        "cat": [
+          "IAB9-1"
+        ],
+        "bundle": "com.app.test",
+        "name": "Test App",
+        "id": "12345678"
+      },
+      "device": {
+        "ua": "useragent",
+        "ip": "100.100.100.100",
+        "language": "en"
+      },
+      "tmax": 1000,
+      "user": {
+        "id": "some-user"
+      },
+      "imp": [
+        {
+          "id": "impression-id",
+          "tagid": "tid",
+          "banner": {
+            "w":320,
+            "h":50
+          },
+          "ext": {
+            "bidder": {
+              "placementId": "f897beb0daba0253d8e59a098eef9311"
+            }
+          }
+        }
+      ]
+    },
+    "httpCalls": [
+    {
+        "expectedRequest": {
+          "headers": {
+            "Content-Type": [
+              "application/json;charset=utf-8"
+            ],
+            "Accept": [
+              "application/json"
+            ]
+          },
+          "uri": "http://point.contextualadv.com/?t=3&partner=f897beb0daba0253d8e59a098eef9311",
+          "body": {
+            "id": "request-id",
+            "device": {
+              "ua": "useragent",
+              "ip": "100.100.100.100",
+              "language": "en"
+            },
+            "imp": [
+              {
+                "id": "impression-id",
+                "banner": {
+                    "w":320,
+                    "h":50
+                },
+                "tagid": "tid"
+              }
+            ],
+            "app": {
+              "publisher": {
+                "id": "123456789"
+              },
+              "cat": [
+                "IAB9-1"
+              ],
+              "bundle": "com.app.test",
+              "name": "Test App",
+              "id": "12345678"
+            },
+            "user": {
+              "id": "some-user"
+            },
+            "tmax": 1000
+          }
+        },
+        "mockResponse": {
+          "status": 200,
+          "body": {
+            "id": "resp-id",
+            "seatbid": [
+              {
+                "bid": [
+                  {
+                    "id": "123456789",
+                    "impid": "impression-id",
+                    "price": 2,
+                    "adm": "adm code",
+                    "adomain": [
+                      "testdomain.com"
+                    ],
+                    "crid": "100",
+                    "w":320,
+                    "h":50
+                  }
+                ],
+              "type": "banner",
+                "seat": "videoheroes"
+              }
+            ],
+            "cur": "USD",
+            "ext": {
+              "responsetimemillis": {
+                "videoheroes": 120
+              },
+              "tmaxrequest": 1000
+            }
+          }
+        }
+      }
+    ],
+    "expectedBidResponses": [
+      {
+        "bids": [{
+          "bid": {
+            "id": "123456789",
+            "impid": "impression-id",
+            "price": 2,
+            "adm": "adm code",
+            "adomain": [
+              "testdomain.com"
+            ],
+            "crid": "100",
+            "w":320,
+            "h":50
+          },
+          "type": "banner"
+        }]
+      }
+    ]
+  }
diff --git a/adapters/videoheroes/videoheroestest/exemplary/banner-web.json b/adapters/videoheroes/videoheroestest/exemplary/banner-web.json
new file mode 100644
index 00000000..f8387163
--- /dev/null
+++ b/adapters/videoheroes/videoheroestest/exemplary/banner-web.json
@@ -0,0 +1,138 @@
+{
+  "mockBidRequest": {
+    "id": "request-id",
+    "site": {
+      "page": "test.com/page",
+      "domain": "test.com",
+      "cat": [
+        "IAB9-1"
+      ],
+      "publisher": {
+        "id": "123456789"
+      }
+    },
+    "device": {
+      "ua": "useragent",
+      "ip": "100.100.100.100",
+      "language": "en"
+    },
+    "tmax": 1000,
+    "user": {
+      "id": "some-user"
+    },
+    "imp": [
+      {
+        "id": "impression-id",
+        "tagid": "tid",
+        "banner": {
+          "w":320,
+          "h":50
+        },
+        "ext": {
+          "bidder": {
+            "placementId": "f897beb0daba0253d8e59a098eef9311"
+          }
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "headers": {
+          "Content-Type": [
+            "application/json;charset=utf-8"
+          ],
+          "Accept": [
+            "application/json"
+          ]
+        },
+        "uri": "http://point.contextualadv.com/?t=3&partner=f897beb0daba0253d8e59a098eef9311",
+        "body": {
+          "id": "request-id",
+          "device": {
+            "ua": "useragent",
+            "ip": "100.100.100.100",
+            "language": "en"
+          },
+          "imp": [
+            {
+              "id": "impression-id",
+              "banner": {
+                  "w":320,
+                  "h":50
+              },
+              "tagid": "tid"
+            }
+          ],
+          "site": {
+            "page": "test.com/page",
+            "domain": "test.com",
+            "cat": [
+              "IAB9-1"
+            ],
+            "publisher": {
+              "id": "123456789"
+            }
+          },
+          "user": {
+            "id": "some-user"
+          },
+          "tmax": 1000
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "resp-id",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "123456789",
+                  "impid": "impression-id",
+                  "price": 2,
+                  "adm": "adm code",
+                  "adomain": [
+                    "testdomain.com"
+                  ],
+                  "crid": "100",
+                  "w":320,
+                  "h":50
+                }
+              ],
+            "type": "banner",
+              "seat": "videoheroes"
+            }
+          ],
+          "cur": "USD",
+          "ext": {
+            "responsetimemillis": {
+              "videoheroes": 120
+            },
+            "tmaxrequest": 1000
+          }
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "bids": [{
+        "bid": {
+          "id": "123456789",
+          "impid": "impression-id",
+          "price": 2,
+          "adm": "adm code",
+          "adomain": [
+            "testdomain.com"
+          ],
+          "crid": "100",
+          "w":320,
+          "h":50
+        },
+        "type": "banner"
+      }]
+    }
+  ]
+}
diff --git a/adapters/videoheroes/videoheroestest/exemplary/native-app.json b/adapters/videoheroes/videoheroestest/exemplary/native-app.json
new file mode 100644
index 00000000..cce80109
--- /dev/null
+++ b/adapters/videoheroes/videoheroestest/exemplary/native-app.json
@@ -0,0 +1,137 @@
+{
+    "mockBidRequest": {
+      "id": "request-id",
+      "app": {
+        "publisher": {
+          "id": "123456789"
+        },
+        "cat": [
+          "IAB9-1"
+        ],
+        "bundle": "com.app.test",
+        "name": "Test App",
+        "id": "12345678"
+      },
+      "device": {
+        "ua": "useragent",
+        "ip": "100.100.100.100",
+        "language": "en"
+      },
+      "tmax": 1000,
+      "user": {
+        "id": "some-user"
+      },
+      "imp": [
+        {
+          "id": "impression-id",
+          "tagid": "tid",
+          "native": {
+            "ver":"1.1",
+            "request":"{\"adunit\":2,\"assets\":[{\"id\":3,\"img\":{\"h\":120,\"hmin\":0,\"type\":3,\"w\":180,\"wmin\":0},\"required\":1},{\"id\":0,\"required\":1,\"title\":{\"len\":25}},{\"data\":{\"len\":25,\"type\":1},\"id\":4,\"required\":1},{\"data\":{\"len\":140,\"type\":2},\"id\":6,\"required\":1}],\"context\":1,\"layout\":1,\"contextsubtype\":11,\"plcmtcnt\":1,\"plcmttype\":2,\"ver\":\"1.1\",\"ext\":{\"banner\":{\"w\":320,\"h\":50}}}"
+          },
+	        "ext": {
+            "bidder": {
+              "placementId": "f897beb0daba0253d8e59a098eef9311"
+            }
+          }
+        }
+      ]
+    },
+    "httpCalls": [
+    {
+        "expectedRequest": {
+            "headers": {
+              "Content-Type": [
+                "application/json;charset=utf-8"
+              ],
+              "Accept": [
+                "application/json"
+              ]
+            },
+            "uri": "http://point.contextualadv.com/?t=3&partner=f897beb0daba0253d8e59a098eef9311",
+            "body": {
+              "id": "request-id",
+              "device": {
+                "ua": "useragent",
+                "ip": "100.100.100.100",
+                "language": "en"
+              },
+              "imp": [
+                {
+                  "id": "impression-id",
+                  "native": {
+                    "ver":"1.1",
+                    "request":"{\"adunit\":2,\"assets\":[{\"id\":3,\"img\":{\"h\":120,\"hmin\":0,\"type\":3,\"w\":180,\"wmin\":0},\"required\":1},{\"id\":0,\"required\":1,\"title\":{\"len\":25}},{\"data\":{\"len\":25,\"type\":1},\"id\":4,\"required\":1},{\"data\":{\"len\":140,\"type\":2},\"id\":6,\"required\":1}],\"context\":1,\"layout\":1,\"contextsubtype\":11,\"plcmtcnt\":1,\"plcmttype\":2,\"ver\":\"1.1\",\"ext\":{\"banner\":{\"w\":320,\"h\":50}}}"
+                  },
+                  "tagid": "tid"
+                }
+              ],
+              "app": {
+                "publisher": {
+                  "id": "123456789"
+                },
+                "cat": [
+                  "IAB9-1"
+                ],
+                "bundle": "com.app.test",
+                "name": "Test App",
+                "id": "12345678"
+              },
+              "user": {
+                "id": "some-user"
+              },
+              "tmax": 1000
+            }
+        },
+        "mockResponse": {
+
+          "status": 200,
+          "body": {
+            "id": "resp-id",
+            "seatbid": [
+              {
+                "bid": [
+                  {
+                    "id": "123456789",
+                    "impid": "impression-id",
+                    "price": 2,
+                    "adm": "adm code",
+                    "adomain": [
+                      "testdomain.com"
+                    ],
+                    "crid": "100"
+                  }
+                ],
+                "type": "native",
+                "seat": "videoheroes"
+              }
+            ],
+            "cur": "USD",
+            "ext": {
+              "responsetimemillis": {
+                "videoheroes": 120
+              },
+              "tmaxrequest": 1000
+            }
+          }
+        }
+      }
+    ],
+    "expectedBidResponses": [
+      {
+        "bids": [{
+          "bid": {
+            "id": "123456789",
+            "impid": "impression-id",
+            "price": 2,
+            "adm": "adm code",
+            "adomain": [
+              "testdomain.com"
+            ],
+            "crid": "100"
+          },
+          "type": "native"
+        }]
+      }
+    ]
+  }
diff --git a/adapters/videoheroes/videoheroestest/exemplary/native-web.json b/adapters/videoheroes/videoheroestest/exemplary/native-web.json
new file mode 100644
index 00000000..381bac5c
--- /dev/null
+++ b/adapters/videoheroes/videoheroestest/exemplary/native-web.json
@@ -0,0 +1,135 @@
+{
+    "mockBidRequest": {
+      "id": "request-id",
+      "site": {
+        "page": "test.com/page",
+        "domain": "test.com",
+        "cat": [
+          "IAB9-1"
+        ],
+        "publisher": {
+          "id": "123456789"
+        }
+      },
+      "device": {
+        "ua": "useragent",
+        "ip": "100.100.100.100",
+        "language": "en"
+      },
+      "tmax": 1000,
+      "user": {
+        "id": "some-user"
+      },
+      "imp": [
+        {
+          "id": "impression-id",
+          "tagid": "tid",
+          "native": {
+            "ver":"1.1",
+            "request":"{\"adunit\":2,\"assets\":[{\"id\":3,\"img\":{\"h\":120,\"hmin\":0,\"type\":3,\"w\":180,\"wmin\":0},\"required\":1},{\"id\":0,\"required\":1,\"title\":{\"len\":25}},{\"data\":{\"len\":25,\"type\":1},\"id\":4,\"required\":1},{\"data\":{\"len\":140,\"type\":2},\"id\":6,\"required\":1}],\"context\":1,\"layout\":1,\"contextsubtype\":11,\"plcmtcnt\":1,\"plcmttype\":2,\"ver\":\"1.1\",\"ext\":{\"banner\":{\"w\":320,\"h\":50}}}"
+          },
+	        "ext": {
+            "bidder": {
+              "placementId": "f897beb0daba0253d8e59a098eef9311"
+            }
+          }
+        }
+      ]
+    },
+    "httpCalls": [
+    {
+        "expectedRequest": {
+            "headers": {
+              "Content-Type": [
+                "application/json;charset=utf-8"
+              ],
+              "Accept": [
+                "application/json"
+              ]
+            },
+            "uri": "http://point.contextualadv.com/?t=3&partner=f897beb0daba0253d8e59a098eef9311",
+            "body": {
+              "id": "request-id",
+              "device": {
+                "ua": "useragent",
+                "ip": "100.100.100.100",
+                "language": "en"
+              },
+              "imp": [
+                {
+                  "id": "impression-id",
+                  "native": {
+                    "ver":"1.1",
+                    "request":"{\"adunit\":2,\"assets\":[{\"id\":3,\"img\":{\"h\":120,\"hmin\":0,\"type\":3,\"w\":180,\"wmin\":0},\"required\":1},{\"id\":0,\"required\":1,\"title\":{\"len\":25}},{\"data\":{\"len\":25,\"type\":1},\"id\":4,\"required\":1},{\"data\":{\"len\":140,\"type\":2},\"id\":6,\"required\":1}],\"context\":1,\"layout\":1,\"contextsubtype\":11,\"plcmtcnt\":1,\"plcmttype\":2,\"ver\":\"1.1\",\"ext\":{\"banner\":{\"w\":320,\"h\":50}}}"
+                  },
+                  "tagid": "tid"
+                }
+              ],
+              "site": {
+                "page": "test.com/page",
+                "domain": "test.com",
+                "cat": [
+                  "IAB9-1"
+                ],
+                "publisher": {
+                  "id": "123456789"
+                }
+              },
+              "user": {
+                "id": "some-user"
+              },
+              "tmax": 1000
+            }
+        },
+        "mockResponse": {
+
+          "status": 200,
+          "body": {
+            "id": "resp-id",
+            "seatbid": [
+              {
+                "bid": [
+                  {
+                    "id": "123456789",
+                    "impid": "impression-id",
+                    "price": 2,
+                    "adm": "adm code",
+                    "adomain": [
+                      "testdomain.com"
+                    ],
+                    "crid": "100"
+                  }
+                ],
+                "type": "native",
+                "seat": "videoheroes"
+              }
+            ],
+            "cur": "USD",
+            "ext": {
+              "responsetimemillis": {
+                "videoheroes": 120
+              },
+              "tmaxrequest": 1000
+            }
+          }
+        }
+      }
+    ],
+    "expectedBidResponses": [
+      {
+        "bids": [{
+          "bid": {
+            "id": "123456789",
+            "impid": "impression-id",
+            "price": 2,
+            "adm": "adm code",
+            "adomain": [
+              "testdomain.com"
+            ],
+            "crid": "100"
+          },
+          "type": "native"
+        }]
+      }
+    ]
+  }
diff --git a/adapters/videoheroes/videoheroestest/exemplary/video-app.json b/adapters/videoheroes/videoheroestest/exemplary/video-app.json
new file mode 100644
index 00000000..96dd555d
--- /dev/null
+++ b/adapters/videoheroes/videoheroestest/exemplary/video-app.json
@@ -0,0 +1,150 @@
+{
+  "mockBidRequest": {
+    "id": "request-id",
+    "app": {
+      "publisher": {
+        "id": "123456789"
+      },
+      "cat": [
+        "IAB9-1"
+      ],
+      "bundle": "com.app.test",
+      "name": "Test App",
+      "id": "12345678"
+    },
+    "device": {
+      "ua": "useragent",
+      "ip": "100.100.100.100",
+      "language": "en"
+    },
+    "tmax": 1000,
+    "user": {
+      "id": "some-user"
+    },
+    "imp": [
+      {
+        "id": "impression-id",
+        "tagid": "tid",
+        "video": {
+          "mimes": [
+            "video/mp4"
+          ],
+          "w": 640,
+          "h": 480,
+          "minduration": 30,
+          "maxduration": 150
+        },
+	      "ext": {
+            "bidder": {
+              "placementId": "f897beb0daba0253d8e59a098eef9311"
+            }
+          }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "headers": {
+          "Content-Type": [
+            "application/json;charset=utf-8"
+          ],
+          "Accept": [
+            "application/json"
+          ]
+        },
+        "uri": "http://point.contextualadv.com/?t=3&partner=f897beb0daba0253d8e59a098eef9311",
+        "body": {
+          "id": "request-id",
+          "device": {
+            "ua": "useragent",
+            "ip": "100.100.100.100",
+            "language": "en"
+          },
+          "imp": [
+            {
+              "id": "impression-id",
+              "video": {
+                "mimes": [
+                  "video/mp4"
+                ],
+                "minduration": 30,
+                "maxduration": 150,
+                "w": 640,
+                "h": 480
+              },
+              "tagid": "tid"
+            }
+          ],
+          "app": {
+            "publisher": {
+              "id": "123456789"
+            },
+            "cat": [
+              "IAB9-1"
+            ],
+            "bundle": "com.app.test",
+            "name": "Test App",
+            "id": "12345678"
+          },
+          "user": {
+            "id": "some-user"
+          },
+          "tmax": 1000
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "resp-id",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "123456789",
+                  "impid": "impression-id",
+                  "price": 2,
+                  "adm": "adm code",
+                  "adomain": [
+                    "testdomain.com"
+                  ],
+                  "crid": "100",
+                  "w": 1280,
+                  "h": 720
+                }
+              ],
+            "type": "video",
+            "seat": "videoheroes"
+            }
+          ],
+          "cur": "USD",
+          "ext": {
+            "responsetimemillis": {
+              "videoheroes": 120
+            },
+            "tmaxrequest": 1000
+          }
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "bids": [{
+        "bid": {
+          "id": "123456789",
+          "impid": "impression-id",
+          "price": 2,
+          "adm": "adm code",
+          "adomain": [
+            "testdomain.com"
+          ],
+          "crid": "100",
+          "w": 1280,
+          "h": 720
+        },
+        "type": "video"
+      }]
+    }
+  ]
+}
diff --git a/adapters/videoheroes/videoheroestest/exemplary/video-web.json b/adapters/videoheroes/videoheroestest/exemplary/video-web.json
new file mode 100644
index 00000000..243349a9
--- /dev/null
+++ b/adapters/videoheroes/videoheroestest/exemplary/video-web.json
@@ -0,0 +1,148 @@
+{
+  "mockBidRequest": {
+    "id": "request-id",
+    "site": {
+      "page": "test.com/page",
+      "domain": "test.com",
+      "cat": [
+        "IAB9-1"
+      ],
+      "publisher": {
+        "id": "123456789"
+      }
+    },
+    "device": {
+      "ua": "useragent",
+      "ip": "100.100.100.100",
+      "language": "en"
+    },
+    "tmax": 1000,
+    "user": {
+      "id": "some-user"
+    },
+    "imp": [
+      {
+        "id": "impression-id",
+        "tagid": "tid",
+        "video": {
+          "mimes": [
+            "video/mp4"
+          ],
+          "w": 640,
+          "h": 480,
+          "minduration": 30,
+          "maxduration": 150
+        },
+	      "ext": {
+            "bidder": {
+              "placementId": "f897beb0daba0253d8e59a098eef9311"
+            }
+          }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "headers": {
+          "Content-Type": [
+            "application/json;charset=utf-8"
+          ],
+          "Accept": [
+            "application/json"
+          ]
+        },
+        "uri": "http://point.contextualadv.com/?t=3&partner=f897beb0daba0253d8e59a098eef9311",
+        "body": {
+          "id": "request-id",
+          "device": {
+            "ua": "useragent",
+            "ip": "100.100.100.100",
+            "language": "en"
+          },
+          "imp": [
+            {
+              "id": "impression-id",
+              "video": {
+                "mimes": [
+                  "video/mp4"
+                ],
+                "minduration": 30,
+                "maxduration": 150,
+                "w": 640,
+                "h": 480
+              },
+              "tagid": "tid"
+            }
+          ],
+          "site": {
+            "page": "test.com/page",
+            "domain": "test.com",
+            "cat": [
+              "IAB9-1"
+            ],
+            "publisher": {
+              "id": "123456789"
+            }
+          },
+          "user": {
+            "id": "some-user"
+          },
+          "tmax": 1000
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "resp-id",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "123456789",
+                  "impid": "impression-id",
+                  "price": 2,
+                  "adm": "adm code",
+                  "adomain": [
+                    "testdomain.com"
+                  ],
+                  "crid": "100",
+                  "w": 1280,
+                  "h": 720
+                }
+              ],
+            "type": "video",
+            "seat": "videoheroes"
+            }
+          ],
+          "cur": "USD",
+          "ext": {
+            "responsetimemillis": {
+              "videoheroes": 120
+            },
+            "tmaxrequest": 1000
+          }
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "bids": [{
+        "bid": {
+          "id": "123456789",
+          "impid": "impression-id",
+          "price": 2,
+          "adm": "adm code",
+          "adomain": [
+            "testdomain.com"
+          ],
+          "crid": "100",
+          "w": 1280,
+          "h": 720
+        },
+        "type": "video"
+      }]
+    }
+  ]
+}
diff --git a/adapters/videoheroes/videoheroestest/supplemental/empty-seatbid-array.json b/adapters/videoheroes/videoheroestest/supplemental/empty-seatbid-array.json
new file mode 100644
index 00000000..3743daea
--- /dev/null
+++ b/adapters/videoheroes/videoheroestest/supplemental/empty-seatbid-array.json
@@ -0,0 +1,121 @@
+{
+  "mockBidRequest": {
+    "id": "request-id",
+    "site": {
+      "page": "test.com/page",
+      "domain": "test.com",
+      "cat": [
+        "IAB9-1"
+      ],
+      "publisher": {
+        "id": "123456789"
+      }
+    },
+    "device": {
+      "ua": "useragent",
+      "ip": "100.100.100.100",
+      "language": "en"
+    },
+    "tmax": 1000,
+    "user": {
+      "id": "some-user"
+    },
+    "imp": [
+      {
+        "id": "impression-id",
+        "tagid": "tid",
+        "video": {
+          "mimes": [
+            "video/mp4"
+          ],
+          "w": 640,
+          "h": 480,
+          "minduration": 30,
+          "maxduration": 150
+        },
+	      "ext": {
+            "bidder": {
+              "placementId": "f897beb0daba0253d8e59a098eef9311"
+            }
+          }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "headers": {
+          "Content-Type": [
+            "application/json;charset=utf-8"
+          ],
+          "Accept": [
+            "application/json"
+          ]
+        },
+        "uri": "http://point.contextualadv.com/?t=3&partner=f897beb0daba0253d8e59a098eef9311",
+        "body": {
+          "id": "request-id",
+          "device": {
+            "ua": "useragent",
+            "ip": "100.100.100.100",
+            "language": "en"
+          },
+          "imp": [
+            {
+              "id": "impression-id",
+              "video": {
+                "mimes": [
+                  "video/mp4"
+                ],
+                "minduration": 30,
+                "maxduration": 150,
+                "w": 640,
+                "h": 480
+              },
+              "tagid": "tid"
+            }
+          ],
+          "site": {
+            "page": "test.com/page",
+            "domain": "test.com",
+            "cat": [
+              "IAB9-1"
+            ],
+            "publisher": {
+              "id": "123456789"
+            }
+          },
+          "user": {
+            "id": "some-user"
+          },
+          "tmax": 1000
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "resp-id",
+          "seatbid": [
+          ],
+          "cur": "USD",
+          "ext": {
+            "responsetimemillis": {
+              "videoheroes": 120
+            },
+            "tmaxrequest": 1000
+          }
+        }
+      }
+    }
+  ],
+    "mockResponse": {
+      "status": 200,
+      "body": "invalid response"
+    },
+  "expectedMakeBidsErrors": [
+    {
+      "value": "Empty SeatBid array",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/videoheroes/videoheroestest/supplemental/invalid-brave-ext-object.json b/adapters/videoheroes/videoheroestest/supplemental/invalid-brave-ext-object.json
new file mode 100644
index 00000000..899834e6
--- /dev/null
+++ b/adapters/videoheroes/videoheroestest/supplemental/invalid-brave-ext-object.json
@@ -0,0 +1,29 @@
+{
+  "expectedMakeRequestsErrors": [
+    {
+      "value": "ext.bidder not provided",
+      "comparison": "literal"
+    }
+  ],
+  "mockBidRequest": {
+    "id": "request-id",
+    "imp": [
+      {
+        "id": "impression-id",
+        "tagid": "tid",
+        "video": {
+          "mimes": ["video/mp4"],
+          "w": 640,
+          "h": 480,
+          "minduration": 30,
+          "maxduration": 150
+        },
+        "ext": "invalid data"
+      }
+    ],
+    "site": {
+      "page": "test.com"
+    }
+  },
+  "httpCalls": []
+}
diff --git a/adapters/videoheroes/videoheroestest/supplemental/invalid-response.json b/adapters/videoheroes/videoheroestest/supplemental/invalid-response.json
new file mode 100644
index 00000000..ad9a32c0
--- /dev/null
+++ b/adapters/videoheroes/videoheroestest/supplemental/invalid-response.json
@@ -0,0 +1,98 @@
+
+{
+  "mockBidRequest": {
+    "id": "request-id",
+    "app": {
+      "publisher": {
+        "id": "123456789"
+      },
+      "cat": [
+        "IAB9-1"
+      ],
+      "bundle": "com.app.test",
+      "name": "Test App",
+      "id": "12345678"
+    },
+    "device": {
+      "ua": "useragent",
+      "ip": "100.100.100.100",
+      "language": "en"
+    },
+    "tmax": 1000,
+    "user": {
+      "id": "some-user"
+    },
+    "imp": [
+      {
+        "id": "impression-id",
+        "tagid": "tid",
+        "banner": {
+          "w":320,
+          "h":50
+        },
+        "ext": {
+          "bidder": {
+            "placementId": "f897beb0daba0253d8e59a098eef9311"
+          }
+        }
+      }
+    ]
+  },
+
+  "httpCalls": [{
+    "expectedRequest": {
+      "headers": {
+        "Content-Type": [
+          "application/json;charset=utf-8"
+        ],
+        "Accept": [
+          "application/json"
+        ]
+      },
+      "uri": "http://point.contextualadv.com/?t=3&partner=f897beb0daba0253d8e59a098eef9311",
+      "body": {
+        "id": "request-id",
+        "device": {
+          "ua": "useragent",
+          "ip": "100.100.100.100",
+          "language": "en"
+        },
+        "imp": [
+          {
+            "id": "impression-id",
+            "banner": {
+                "w":320,
+                "h":50
+            },
+            "tagid": "tid"
+          }
+        ],
+        "app": {
+          "publisher": {
+            "id": "123456789"
+          },
+          "cat": [
+            "IAB9-1"
+          ],
+          "bundle": "com.app.test",
+          "name": "Test App",
+          "id": "12345678"
+        },
+        "user": {
+          "id": "some-user"
+        },
+        "tmax": 1000
+      }
+    },
+    "mockResponse": {
+      "status": 200,
+      "body": "invalid response"
+    }
+  }],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "Bad Server Response",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/videoheroes/videoheroestest/supplemental/status-code-bad-request.json b/adapters/videoheroes/videoheroestest/supplemental/status-code-bad-request.json
new file mode 100644
index 00000000..432540e3
--- /dev/null
+++ b/adapters/videoheroes/videoheroestest/supplemental/status-code-bad-request.json
@@ -0,0 +1,108 @@
+
+{
+  "mockBidRequest": {
+    "id": "request-id",
+    "app": {
+      "publisher": {
+        "id": "123456789"
+      },
+      "cat": [
+        "IAB9-1"
+      ],
+      "bundle": "com.app.test",
+      "name": "Test App",
+      "id": "12345678"
+    },
+    "device": {
+      "ua": "useragent",
+      "ip": "100.100.100.100",
+      "language": "en"
+    },
+    "tmax": 1000,
+    "user": {
+      "id": "some-user"
+    },
+    "imp": [
+      {
+        "id": "impression-id",
+        "tagid": "tid",
+        "video": {
+          "mimes": [
+            "video/mp4"
+          ],
+          "w": 640,
+          "h": 480,
+          "minduration": 30,
+          "maxduration": 150
+        },
+	      "ext": {
+            "bidder": {
+              "placementId": "f897beb0daba0253d8e59a098eef9311"
+            }
+          }
+      }
+    ]
+  },
+
+  "httpCalls": [{
+    "expectedRequest": {
+      "headers": {
+        "Content-Type": [
+          "application/json;charset=utf-8"
+        ],
+        "Accept": [
+          "application/json"
+        ]
+      },
+      "uri": "http://point.contextualadv.com/?t=3&partner=f897beb0daba0253d8e59a098eef9311",
+      "body": {
+        "id": "request-id",
+        "device": {
+          "ua": "useragent",
+          "ip": "100.100.100.100",
+          "language": "en"
+        },
+        "imp": [
+          {
+            "id": "impression-id",
+            "video": {
+              "mimes": [
+                "video/mp4"
+              ],
+              "minduration": 30,
+              "maxduration": 150,
+              "w": 640,
+              "h": 480
+            },
+            "tagid": "tid"
+          }
+        ],
+        "app": {
+          "publisher": {
+            "id": "123456789"
+          },
+          "cat": [
+            "IAB9-1"
+          ],
+          "bundle": "com.app.test",
+          "name": "Test App",
+          "id": "12345678"
+        },
+        "user": {
+          "id": "some-user"
+        },
+        "tmax": 1000
+      }
+    },
+    "mockResponse": {
+      "status": 400
+    }
+  }],
+  "expectedBidResponses": [],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "Unexpected status code: 400. Run with request.debug = 1 for more info",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/videoheroes/videoheroestest/supplemental/status-code-no-content.json b/adapters/videoheroes/videoheroestest/supplemental/status-code-no-content.json
new file mode 100644
index 00000000..bec17d52
--- /dev/null
+++ b/adapters/videoheroes/videoheroestest/supplemental/status-code-no-content.json
@@ -0,0 +1,108 @@
+
+{
+  "mockBidRequest": {
+    "id": "request-id",
+    "app": {
+      "publisher": {
+        "id": "123456789"
+      },
+      "cat": [
+        "IAB9-1"
+      ],
+      "bundle": "com.app.test",
+      "name": "Test App",
+      "id": "12345678"
+    },
+    "device": {
+      "ua": "useragent",
+      "ip": "100.100.100.100",
+      "language": "en"
+    },
+    "tmax": 1000,
+    "user": {
+      "id": "some-user"
+    },
+    "imp": [
+      {
+        "id": "impression-id",
+        "tagid": "tid",
+        "video": {
+          "mimes": [
+            "video/mp4"
+          ],
+          "w": 640,
+          "h": 480,
+          "minduration": 30,
+          "maxduration": 150
+        },
+	      "ext": {
+            "bidder": {
+              "placementId": "f897beb0daba0253d8e59a098eef9311"
+            }
+          }
+      }
+    ]
+  },
+
+  "httpCalls": [{
+    "expectedRequest": {
+      "headers": {
+        "Content-Type": [
+          "application/json;charset=utf-8"
+        ],
+        "Accept": [
+          "application/json"
+        ]
+      },
+      "uri": "http://point.contextualadv.com/?t=3&partner=f897beb0daba0253d8e59a098eef9311",
+      "body": {
+        "id": "request-id",
+        "device": {
+          "ua": "useragent",
+          "ip": "100.100.100.100",
+          "language": "en"
+        },
+        "imp": [
+          {
+            "id": "impression-id",
+            "video": {
+              "mimes": [
+                "video/mp4"
+              ],
+              "minduration": 30,
+              "maxduration": 150,
+              "w": 640,
+              "h": 480
+            },
+            "tagid": "tid"
+          }
+        ],
+        "app": {
+          "publisher": {
+            "id": "123456789"
+          },
+          "cat": [
+            "IAB9-1"
+          ],
+          "bundle": "com.app.test",
+          "name": "Test App",
+          "id": "12345678"
+        },
+        "user": {
+          "id": "some-user"
+        },
+        "tmax": 1000
+      }
+    },
+    "mockResponse": {
+      "status": 204
+    }
+  }],
+  "expectedBidResponses": [],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "No bid",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/videoheroes/videoheroestest/supplemental/status-code-other-error.json b/adapters/videoheroes/videoheroestest/supplemental/status-code-other-error.json
new file mode 100644
index 00000000..cb37a4d9
--- /dev/null
+++ b/adapters/videoheroes/videoheroestest/supplemental/status-code-other-error.json
@@ -0,0 +1,108 @@
+
+{
+  "mockBidRequest": {
+    "id": "request-id",
+    "app": {
+      "publisher": {
+        "id": "123456789"
+      },
+      "cat": [
+        "IAB9-1"
+      ],
+      "bundle": "com.app.test",
+      "name": "Test App",
+      "id": "12345678"
+    },
+    "device": {
+      "ua": "useragent",
+      "ip": "100.100.100.100",
+      "language": "en"
+    },
+    "tmax": 1000,
+    "user": {
+      "id": "some-user"
+    },
+    "imp": [
+      {
+        "id": "impression-id",
+        "tagid": "tid",
+        "video": {
+          "mimes": [
+            "video/mp4"
+          ],
+          "w": 640,
+          "h": 480,
+          "minduration": 30,
+          "maxduration": 150
+        },
+	      "ext": {
+            "bidder": {
+              "placementId": "f897beb0daba0253d8e59a098eef9311"
+            }
+          }
+      }
+    ]
+  },
+
+  "httpCalls": [{
+    "expectedRequest": {
+      "headers": {
+        "Content-Type": [
+          "application/json;charset=utf-8"
+        ],
+        "Accept": [
+          "application/json"
+        ]
+      },
+      "uri": "http://point.contextualadv.com/?t=3&partner=f897beb0daba0253d8e59a098eef9311",
+      "body": {
+        "id": "request-id",
+        "device": {
+          "ua": "useragent",
+          "ip": "100.100.100.100",
+          "language": "en"
+        },
+        "imp": [
+          {
+            "id": "impression-id",
+            "video": {
+              "mimes": [
+                "video/mp4"
+              ],
+              "minduration": 30,
+              "maxduration": 150,
+              "w": 640,
+              "h": 480
+            },
+            "tagid": "tid"
+          }
+        ],
+        "app": {
+          "publisher": {
+            "id": "123456789"
+          },
+          "cat": [
+            "IAB9-1"
+          ],
+          "bundle": "com.app.test",
+          "name": "Test App",
+          "id": "12345678"
+        },
+        "user": {
+          "id": "some-user"
+        },
+        "tmax": 1000
+      }
+    },
+    "mockResponse": {
+      "status": 306
+    }
+  }],
+  "expectedBidResponses": [],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "Something went wrong, please contact your Account Manager. Status Code: [ 306 ] ",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/videoheroes/videoheroestest/supplemental/status-code-service-unavailable.json b/adapters/videoheroes/videoheroestest/supplemental/status-code-service-unavailable.json
new file mode 100644
index 00000000..38d14f4f
--- /dev/null
+++ b/adapters/videoheroes/videoheroestest/supplemental/status-code-service-unavailable.json
@@ -0,0 +1,107 @@
+{
+  "mockBidRequest": {
+    "id": "request-id",
+    "app": {
+      "publisher": {
+        "id": "123456789"
+      },
+      "cat": [
+        "IAB9-1"
+      ],
+      "bundle": "com.app.test",
+      "name": "Test App",
+      "id": "12345678"
+    },
+    "device": {
+      "ua": "useragent",
+      "ip": "100.100.100.100",
+      "language": "en"
+    },
+    "tmax": 1000,
+    "user": {
+      "id": "some-user"
+    },
+    "imp": [
+      {
+        "id": "impression-id",
+        "tagid": "tid",
+        "video": {
+          "mimes": [
+            "video/mp4"
+          ],
+          "w": 640,
+          "h": 480,
+          "minduration": 30,
+          "maxduration": 150
+        },
+	      "ext": {
+            "bidder": {
+              "placementId": "f897beb0daba0253d8e59a098eef9311"
+            }
+          }
+      }
+    ]
+  },
+
+  "httpCalls": [{
+    "expectedRequest": {
+      "headers": {
+        "Content-Type": [
+          "application/json;charset=utf-8"
+        ],
+        "Accept": [
+          "application/json"
+        ]
+      },
+      "uri": "http://point.contextualadv.com/?t=3&partner=f897beb0daba0253d8e59a098eef9311",
+      "body": {
+        "id": "request-id",
+        "device": {
+          "ua": "useragent",
+          "ip": "100.100.100.100",
+          "language": "en"
+        },
+        "imp": [
+          {
+            "id": "impression-id",
+            "video": {
+              "mimes": [
+                "video/mp4"
+              ],
+              "minduration": 30,
+              "maxduration": 150,
+              "w": 640,
+              "h": 480
+            },
+            "tagid": "tid"
+          }
+        ],
+        "app": {
+          "publisher": {
+            "id": "123456789"
+          },
+          "cat": [
+            "IAB9-1"
+          ],
+          "bundle": "com.app.test",
+          "name": "Test App",
+          "id": "12345678"
+        },
+        "user": {
+          "id": "some-user"
+        },
+        "tmax": 1000
+      }
+    },
+    "mockResponse": {
+      "status": 503
+    }
+  }],
+  "expectedBidResponses": [],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "Service Unavailable. Status Code: [ 503 ] ",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/exchange/auction_test.go b/exchange/auction_test.go
index 8f064914..9f335aa8 100644
--- a/exchange/auction_test.go
+++ b/exchange/auction_test.go
@@ -13,6 +13,7 @@ import (
 
 	"github.com/prebid/openrtb/v17/openrtb2"
 	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/exchange/entities"
 	"github.com/prebid/prebid-server/openrtb_ext"
 	"github.com/prebid/prebid-server/prebid_cache_client"
 
@@ -186,41 +187,41 @@ func loadCacheSpec(filename string) (*cacheSpec, error) {
 // runCacheSpec cycles through the bids found in the json test cases and
 // finds the highest bid of every Imp, then tests doCache() with resulting auction object
 func runCacheSpec(t *testing.T, fileDisplayName string, specData *cacheSpec) {
-	var bid *pbsOrtbBid
-	winningBidsByImp := make(map[string]*pbsOrtbBid)
-	winningBidsByBidder := make(map[string]map[openrtb_ext.BidderName]*pbsOrtbBid)
-	roundedPrices := make(map[*pbsOrtbBid]string)
+	var bid *entities.PbsOrtbBid
+	winningBidsByImp := make(map[string]*entities.PbsOrtbBid)
+	winningBidsByBidder := make(map[string]map[openrtb_ext.BidderName]*entities.PbsOrtbBid)
+	roundedPrices := make(map[*entities.PbsOrtbBid]string)
 	bidCategory := make(map[string]string)
 
 	// Traverse through the bid list found in the parsed in Json file
 	for _, pbsBid := range specData.PbsBids {
-		bid = &pbsOrtbBid{
-			bid:     pbsBid.Bid,
-			bidType: pbsBid.BidType,
+		bid = &entities.PbsOrtbBid{
+			Bid:     pbsBid.Bid,
+			BidType: pbsBid.BidType,
 		}
-		cpm := bid.bid.Price
+		cpm := bid.Bid.Price
 
 		// Map this bid if it's the highest we've seen from this Imp so far
-		wbid, ok := winningBidsByImp[bid.bid.ImpID]
-		if !ok || cpm > wbid.bid.Price {
-			winningBidsByImp[bid.bid.ImpID] = bid
+		wbid, ok := winningBidsByImp[bid.Bid.ImpID]
+		if !ok || cpm > wbid.Bid.Price {
+			winningBidsByImp[bid.Bid.ImpID] = bid
 		}
 
 		// Map this bid if it's the highest we've seen from this bidder so far
-		if _, ok := winningBidsByBidder[bid.bid.ImpID]; ok {
-			bestSoFar, ok := winningBidsByBidder[bid.bid.ImpID][pbsBid.Bidder]
-			if !ok || cpm > bestSoFar.bid.Price {
-				winningBidsByBidder[bid.bid.ImpID][pbsBid.Bidder] = bid
+		if _, ok := winningBidsByBidder[bid.Bid.ImpID]; ok {
+			bestSoFar, ok := winningBidsByBidder[bid.Bid.ImpID][pbsBid.Bidder]
+			if !ok || cpm > bestSoFar.Bid.Price {
+				winningBidsByBidder[bid.Bid.ImpID][pbsBid.Bidder] = bid
 			}
 		} else {
-			winningBidsByBidder[bid.bid.ImpID] = make(map[openrtb_ext.BidderName]*pbsOrtbBid)
-			winningBidsByBidder[bid.bid.ImpID][pbsBid.Bidder] = bid
+			winningBidsByBidder[bid.Bid.ImpID] = make(map[openrtb_ext.BidderName]*entities.PbsOrtbBid)
+			winningBidsByBidder[bid.Bid.ImpID][pbsBid.Bidder] = bid
 		}
 
 		if len(pbsBid.Bid.Cat) == 1 {
 			bidCategory[pbsBid.Bid.ID] = pbsBid.Bid.Cat[0]
 		}
-		roundedPrices[bid] = strconv.FormatFloat(bid.bid.Price, 'f', 2, 64)
+		roundedPrices[bid] = strconv.FormatFloat(bid.Bid.Price, 'f', 2, 64)
 	}
 
 	ctx := context.Background()
@@ -317,74 +318,74 @@ func runCacheSpec(t *testing.T, fileDisplayName string, specData *cacheSpec) {
 }
 
 func TestNewAuction(t *testing.T) {
-	bid1p077 := pbsOrtbBid{
-		bid: &openrtb2.Bid{
+	bid1p077 := entities.PbsOrtbBid{
+		Bid: &openrtb2.Bid{
 			ImpID: "imp1",
 			Price: 0.77,
 		},
 	}
-	bid1p123 := pbsOrtbBid{
-		bid: &openrtb2.Bid{
+	bid1p123 := entities.PbsOrtbBid{
+		Bid: &openrtb2.Bid{
 			ImpID: "imp1",
 			Price: 1.23,
 		},
 	}
-	bid1p230 := pbsOrtbBid{
-		bid: &openrtb2.Bid{
+	bid1p230 := entities.PbsOrtbBid{
+		Bid: &openrtb2.Bid{
 			ImpID: "imp1",
 			Price: 2.30,
 		},
 	}
-	bid1p088d := pbsOrtbBid{
-		bid: &openrtb2.Bid{
+	bid1p088d := entities.PbsOrtbBid{
+		Bid: &openrtb2.Bid{
 			ImpID:  "imp1",
 			Price:  0.88,
 			DealID: "SpecialDeal",
 		},
 	}
-	bid1p166d := pbsOrtbBid{
-		bid: &openrtb2.Bid{
+	bid1p166d := entities.PbsOrtbBid{
+		Bid: &openrtb2.Bid{
 			ImpID:  "imp1",
 			Price:  1.66,
 			DealID: "BigDeal",
 		},
 	}
-	bid2p123 := pbsOrtbBid{
-		bid: &openrtb2.Bid{
+	bid2p123 := entities.PbsOrtbBid{
+		Bid: &openrtb2.Bid{
 			ImpID: "imp2",
 			Price: 1.23,
 		},
 	}
-	bid2p144 := pbsOrtbBid{
-		bid: &openrtb2.Bid{
+	bid2p144 := entities.PbsOrtbBid{
+		Bid: &openrtb2.Bid{
 			ImpID: "imp2",
 			Price: 1.44,
 		},
 	}
 	tests := []struct {
 		description     string
-		seatBids        map[openrtb_ext.BidderName]*pbsOrtbSeatBid
+		seatBids        map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid
 		numImps         int
 		preferDeals     bool
 		expectedAuction auction
 	}{
 		{
 			description: "Basic auction test",
-			seatBids: map[openrtb_ext.BidderName]*pbsOrtbSeatBid{
+			seatBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{
 				"appnexus": {
-					bids: []*pbsOrtbBid{&bid1p123},
+					Bids: []*entities.PbsOrtbBid{&bid1p123},
 				},
 				"rubicon": {
-					bids: []*pbsOrtbBid{&bid1p230},
+					Bids: []*entities.PbsOrtbBid{&bid1p230},
 				},
 			},
 			numImps:     1,
 			preferDeals: false,
 			expectedAuction: auction{
-				winningBids: map[string]*pbsOrtbBid{
+				winningBids: map[string]*entities.PbsOrtbBid{
 					"imp1": &bid1p230,
 				},
-				winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*pbsOrtbBid{
+				winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*entities.PbsOrtbBid{
 					"imp1": {
 						"appnexus": &bid1p123,
 						"rubicon":  &bid1p230,
@@ -394,25 +395,25 @@ func TestNewAuction(t *testing.T) {
 		},
 		{
 			description: "Multi-imp auction",
-			seatBids: map[openrtb_ext.BidderName]*pbsOrtbSeatBid{
+			seatBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{
 				"appnexus": {
-					bids: []*pbsOrtbBid{&bid1p230, &bid2p123},
+					Bids: []*entities.PbsOrtbBid{&bid1p230, &bid2p123},
 				},
 				"rubicon": {
-					bids: []*pbsOrtbBid{&bid1p077, &bid2p144},
+					Bids: []*entities.PbsOrtbBid{&bid1p077, &bid2p144},
 				},
 				"openx": {
-					bids: []*pbsOrtbBid{&bid1p123},
+					Bids: []*entities.PbsOrtbBid{&bid1p123},
 				},
 			},
 			numImps:     2,
 			preferDeals: false,
 			expectedAuction: auction{
-				winningBids: map[string]*pbsOrtbBid{
+				winningBids: map[string]*entities.PbsOrtbBid{
 					"imp1": &bid1p230,
 					"imp2": &bid2p144,
 				},
-				winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*pbsOrtbBid{
+				winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*entities.PbsOrtbBid{
 					"imp1": {
 						"appnexus": &bid1p230,
 						"rubicon":  &bid1p077,
@@ -427,21 +428,21 @@ func TestNewAuction(t *testing.T) {
 		},
 		{
 			description: "Basic auction with deals, no preference",
-			seatBids: map[openrtb_ext.BidderName]*pbsOrtbSeatBid{
+			seatBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{
 				"appnexus": {
-					bids: []*pbsOrtbBid{&bid1p123},
+					Bids: []*entities.PbsOrtbBid{&bid1p123},
 				},
 				"rubicon": {
-					bids: []*pbsOrtbBid{&bid1p088d},
+					Bids: []*entities.PbsOrtbBid{&bid1p088d},
 				},
 			},
 			numImps:     1,
 			preferDeals: false,
 			expectedAuction: auction{
-				winningBids: map[string]*pbsOrtbBid{
+				winningBids: map[string]*entities.PbsOrtbBid{
 					"imp1": &bid1p123,
 				},
-				winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*pbsOrtbBid{
+				winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*entities.PbsOrtbBid{
 					"imp1": {
 						"appnexus": &bid1p123,
 						"rubicon":  &bid1p088d,
@@ -451,21 +452,21 @@ func TestNewAuction(t *testing.T) {
 		},
 		{
 			description: "Basic auction with deals, prefer deals",
-			seatBids: map[openrtb_ext.BidderName]*pbsOrtbSeatBid{
+			seatBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{
 				"appnexus": {
-					bids: []*pbsOrtbBid{&bid1p123},
+					Bids: []*entities.PbsOrtbBid{&bid1p123},
 				},
 				"rubicon": {
-					bids: []*pbsOrtbBid{&bid1p088d},
+					Bids: []*entities.PbsOrtbBid{&bid1p088d},
 				},
 			},
 			numImps:     1,
 			preferDeals: true,
 			expectedAuction: auction{
-				winningBids: map[string]*pbsOrtbBid{
+				winningBids: map[string]*entities.PbsOrtbBid{
 					"imp1": &bid1p088d,
 				},
-				winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*pbsOrtbBid{
+				winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*entities.PbsOrtbBid{
 					"imp1": {
 						"appnexus": &bid1p123,
 						"rubicon":  &bid1p088d,
@@ -475,21 +476,21 @@ func TestNewAuction(t *testing.T) {
 		},
 		{
 			description: "Auction with 2 deals",
-			seatBids: map[openrtb_ext.BidderName]*pbsOrtbSeatBid{
+			seatBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{
 				"appnexus": {
-					bids: []*pbsOrtbBid{&bid1p166d},
+					Bids: []*entities.PbsOrtbBid{&bid1p166d},
 				},
 				"rubicon": {
-					bids: []*pbsOrtbBid{&bid1p088d},
+					Bids: []*entities.PbsOrtbBid{&bid1p088d},
 				},
 			},
 			numImps:     1,
 			preferDeals: true,
 			expectedAuction: auction{
-				winningBids: map[string]*pbsOrtbBid{
+				winningBids: map[string]*entities.PbsOrtbBid{
 					"imp1": &bid1p166d,
 				},
-				winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*pbsOrtbBid{
+				winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*entities.PbsOrtbBid{
 					"imp1": {
 						"appnexus": &bid1p166d,
 						"rubicon":  &bid1p088d,
@@ -499,24 +500,24 @@ func TestNewAuction(t *testing.T) {
 		},
 		{
 			description: "Auction with 3 bids and 2 deals",
-			seatBids: map[openrtb_ext.BidderName]*pbsOrtbSeatBid{
+			seatBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{
 				"appnexus": {
-					bids: []*pbsOrtbBid{&bid1p166d},
+					Bids: []*entities.PbsOrtbBid{&bid1p166d},
 				},
 				"rubicon": {
-					bids: []*pbsOrtbBid{&bid1p088d},
+					Bids: []*entities.PbsOrtbBid{&bid1p088d},
 				},
 				"openx": {
-					bids: []*pbsOrtbBid{&bid1p230},
+					Bids: []*entities.PbsOrtbBid{&bid1p230},
 				},
 			},
 			numImps:     1,
 			preferDeals: true,
 			expectedAuction: auction{
-				winningBids: map[string]*pbsOrtbBid{
+				winningBids: map[string]*entities.PbsOrtbBid{
 					"imp1": &bid1p166d,
 				},
-				winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*pbsOrtbBid{
+				winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*entities.PbsOrtbBid{
 					"imp1": {
 						"appnexus": &bid1p166d,
 						"rubicon":  &bid1p088d,
diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go
index b963fc77..dbe88169 100644
--- a/exchange/bidder_test.go
+++ b/exchange/bidder_test.go
@@ -24,6 +24,7 @@ import (
 	"github.com/prebid/prebid-server/config"
 	"github.com/prebid/prebid-server/currency"
 	"github.com/prebid/prebid-server/errortypes"
+	"github.com/prebid/prebid-server/exchange/entities"
 	"github.com/prebid/prebid-server/experiment/adscert"
 	"github.com/prebid/prebid-server/hooks/hookexecution"
 	"github.com/prebid/prebid-server/metrics"
@@ -130,18 +131,18 @@ func TestSingleBidder(t *testing.T) {
 		if !test.debugInfo.Allow && len(errortypes.WarningOnly(errs)) != 1 {
 			t.Errorf("bidder.Bid returned %d warnings. Expected 1", len(errs))
 		}
-		if len(seatBid.bids) != len(mockBidderResponse.Bids) {
-			t.Fatalf("Expected %d bids. Got %d", len(mockBidderResponse.Bids), len(seatBid.bids))
+		if len(seatBid.Bids) != len(mockBidderResponse.Bids) {
+			t.Fatalf("Expected %d bids. Got %d", len(mockBidderResponse.Bids), len(seatBid.Bids))
 		}
 		for index, typedBid := range mockBidderResponse.Bids {
-			if typedBid.Bid != seatBid.bids[index].bid {
+			if typedBid.Bid != seatBid.Bids[index].Bid {
 				t.Errorf("Bid %d did not point to the same bid returned by the Bidder.", index)
 			}
-			if typedBid.BidType != seatBid.bids[index].bidType {
-				t.Errorf("Bid %d did not have the right type. Expected %s, got %s", index, typedBid.BidType, seatBid.bids[index].bidType)
+			if typedBid.BidType != seatBid.Bids[index].BidType {
+				t.Errorf("Bid %d did not have the right type. Expected %s, got %s", index, typedBid.BidType, seatBid.Bids[index].BidType)
 			}
-			if typedBid.DealPriority != seatBid.bids[index].dealPriority {
-				t.Errorf("Bid %d did not have the right deal priority. Expected %s, got %s", index, typedBid.BidType, seatBid.bids[index].bidType)
+			if typedBid.DealPriority != seatBid.Bids[index].DealPriority {
+				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"]
@@ -151,11 +152,11 @@ func TestSingleBidder(t *testing.T) {
 		if mockBidderResponse.Bids[1].Bid.Price != bidAdjustment*secondInitialPrice {
 			t.Errorf("Bid[1].Price was not adjusted properly. Expected %f, got %f", bidAdjustment*secondInitialPrice, mockBidderResponse.Bids[1].Bid.Price)
 		}
-		if len(seatBid.httpCalls) != test.httpCallsLen {
-			t.Errorf("The bidder shouldn't log HttpCalls when request.test == 0. Found %d", len(seatBid.httpCalls))
+		if len(seatBid.HttpCalls) != test.httpCallsLen {
+			t.Errorf("The bidder shouldn't log HttpCalls when request.test == 0. Found %d", len(seatBid.HttpCalls))
 		}
-		for index, bid := range seatBid.bids {
-			assert.NotEqual(t, mockBidderResponse.Bids[index].Bid.Price, bid.originalBidCPM, "The bid price was adjusted, so the originally bid CPM should be different")
+		for index, bid := range seatBid.Bids {
+			assert.NotEqual(t, mockBidderResponse.Bids[index].Bid.Price, bid.OriginalBidCPM, "The bid price was adjusted, so the originally bid CPM should be different")
 		}
 	}
 }
@@ -252,18 +253,18 @@ func TestSingleBidderGzip(t *testing.T) {
 		if !test.debugInfo.Allow && len(errortypes.WarningOnly(errs)) != 1 {
 			t.Errorf("bidder.Bid returned %d warnings. Expected 1", len(errs))
 		}
-		if len(seatBid.bids) != len(mockBidderResponse.Bids) {
-			t.Fatalf("Expected %d bids. Got %d", len(mockBidderResponse.Bids), len(seatBid.bids))
+		if len(seatBid.Bids) != len(mockBidderResponse.Bids) {
+			t.Fatalf("Expected %d bids. Got %d", len(mockBidderResponse.Bids), len(seatBid.Bids))
 		}
 		for index, typedBid := range mockBidderResponse.Bids {
-			if typedBid.Bid != seatBid.bids[index].bid {
+			if typedBid.Bid != seatBid.Bids[index].Bid {
 				t.Errorf("Bid %d did not point to the same bid returned by the Bidder.", index)
 			}
-			if typedBid.BidType != seatBid.bids[index].bidType {
-				t.Errorf("Bid %d did not have the right type. Expected %s, got %s", index, typedBid.BidType, seatBid.bids[index].bidType)
+			if typedBid.BidType != seatBid.Bids[index].BidType {
+				t.Errorf("Bid %d did not have the right type. Expected %s, got %s", index, typedBid.BidType, seatBid.Bids[index].BidType)
 			}
-			if typedBid.DealPriority != seatBid.bids[index].dealPriority {
-				t.Errorf("Bid %d did not have the right deal priority. Expected %s, got %s", index, typedBid.BidType, seatBid.bids[index].bidType)
+			if typedBid.DealPriority != seatBid.Bids[index].DealPriority {
+				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"]
@@ -273,15 +274,15 @@ func TestSingleBidderGzip(t *testing.T) {
 		if mockBidderResponse.Bids[1].Bid.Price != bidAdjustment*secondInitialPrice {
 			t.Errorf("Bid[1].Price was not adjusted properly. Expected %f, got %f", bidAdjustment*secondInitialPrice, mockBidderResponse.Bids[1].Bid.Price)
 		}
-		if len(seatBid.httpCalls) != test.httpCallsLen {
-			t.Errorf("The bidder shouldn't log HttpCalls when request.test == 0. Found %d", len(seatBid.httpCalls))
+		if len(seatBid.HttpCalls) != test.httpCallsLen {
+			t.Errorf("The bidder shouldn't log HttpCalls when request.test == 0. Found %d", len(seatBid.HttpCalls))
 		}
-		if test.debugInfo.Allow && len(seatBid.httpCalls) > 0 {
-			assert.Equalf(t, "gzip", seatBid.httpCalls[0].RequestHeaders["Content-Encoding"][0], "Mismatched headers")
-			assert.Equalf(t, "{\"key\":\"val\"}", seatBid.httpCalls[0].RequestBody, "Mismatched request bodies")
+		if test.debugInfo.Allow && len(seatBid.HttpCalls) > 0 {
+			assert.Equalf(t, "gzip", seatBid.HttpCalls[0].RequestHeaders["Content-Encoding"][0], "Mismatched headers")
+			assert.Equalf(t, "{\"key\":\"val\"}", seatBid.HttpCalls[0].RequestBody, "Mismatched request bodies")
 		}
-		for index, bid := range seatBid.bids {
-			assert.NotEqual(t, mockBidderResponse.Bids[index].Bid.Price, bid.originalBidCPM, "The bid price was adjusted, so the originally bid CPM should be different")
+		for index, bid := range seatBid.Bids {
+			assert.NotEqual(t, mockBidderResponse.Bids[index].Bid.Price, bid.OriginalBidCPM, "The bid price was adjusted, so the originally bid CPM should be different")
 		}
 	}
 }
@@ -343,7 +344,7 @@ func TestRequestBidRemovesSensitiveHeaders(t *testing.T) {
 
 	assert.Empty(t, errs)
 	assert.Len(t, seatBids, 1)
-	assert.ElementsMatch(t, seatBids[0].httpCalls, expectedHttpCalls)
+	assert.ElementsMatch(t, seatBids[0].HttpCalls, expectedHttpCalls)
 }
 
 func TestSetGPCHeader(t *testing.T) {
@@ -395,7 +396,7 @@ func TestSetGPCHeader(t *testing.T) {
 
 	assert.Empty(t, errs)
 	assert.Len(t, seatBids, 1)
-	assert.ElementsMatch(t, seatBids[0].httpCalls, expectedHttpCall)
+	assert.ElementsMatch(t, seatBids[0].HttpCalls, expectedHttpCall)
 }
 
 func TestSetGPCHeaderNil(t *testing.T) {
@@ -445,7 +446,7 @@ func TestSetGPCHeaderNil(t *testing.T) {
 
 	assert.Empty(t, errs)
 	assert.Len(t, seatBids, 1)
-	assert.ElementsMatch(t, seatBids[0].httpCalls, expectedHttpCall)
+	assert.ElementsMatch(t, seatBids[0].HttpCalls, expectedHttpCall)
 }
 
 // TestMultiBidder makes sure all the requests get sent, and the responses processed.
@@ -510,8 +511,8 @@ func TestMultiBidder(t *testing.T) {
 	if len(errs) != 1+len(bidderImpl.httpRequests) {
 		t.Errorf("Expected %d errors. Got %d", 1+len(bidderImpl.httpRequests), len(errs))
 	}
-	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))
+	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))
 	}
 
 }
@@ -887,12 +888,12 @@ func TestMultiCurrencies(t *testing.T) {
 		seatBid := seatBids[0]
 
 		// Verify:
-		resultLightBids := make([]bid, len(seatBid.bids))
-		for i, b := range seatBid.bids {
+		resultLightBids := make([]bid, len(seatBid.Bids))
+		for i, b := range seatBid.Bids {
 			resultLightBids[i] = bid{
-				price:          b.bid.Price,
-				currency:       seatBid.currency,
-				originalBidCur: b.originalBidCur,
+				price:          b.Bid.Price,
+				currency:       seatBid.Currency,
+				originalBidCur: b.OriginalBidCur,
 			}
 		}
 		assert.ElementsMatch(t, tc.expectedBids, resultLightBids, tc.description)
@@ -1047,7 +1048,7 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) {
 
 		// Verify:
 		assert.Equal(t, false, (seatBid == nil && tc.expectedBidsCount != 0), tc.description)
-		assert.Equal(t, tc.expectedBidsCount, uint(len(seatBid.bids)), tc.description)
+		assert.Equal(t, tc.expectedBidsCount, uint(len(seatBid.Bids)), tc.description)
 		assert.ElementsMatch(t, tc.expectedBadCurrencyErrors, errs, tc.description)
 	}
 }
@@ -1228,7 +1229,7 @@ func TestMultiCurrencies_RequestCurrencyPick(t *testing.T) {
 			assert.NotNil(t, errs, tc.description)
 		} else {
 			assert.Nil(t, errs, tc.description)
-			assert.Equal(t, tc.expectedPickedCurrency, seatBid.currency, tc.description)
+			assert.Equal(t, tc.expectedPickedCurrency, seatBid.Currency, tc.description)
 		}
 	}
 }
@@ -1540,8 +1541,8 @@ func TestMobileNativeTypes(t *testing.T) {
 		assert.Len(t, seatBids, 1)
 
 		var actualValue string
-		for _, bid := range seatBids[0].bids {
-			actualValue = bid.bid.AdM
+		for _, bid := range seatBids[0].Bids {
+			actualValue = bid.Bid.AdM
 			assert.JSONEq(t, tc.expectedValue, actualValue, tc.description)
 		}
 	}
@@ -1658,10 +1659,10 @@ func TestRequestBidsStoredBidResponses(t *testing.T) {
 		)
 		assert.Len(t, seatBids, 1)
 
-		assert.Len(t, seatBids[0].bids, len(tc.expectedBidIds), "Incorrect bids number for test case ", tc.description)
-		for _, bid := range seatBids[0].bids {
-			assert.Contains(t, tc.expectedBidIds, bid.bid.ID, tc.description)
-			assert.Contains(t, tc.expectedImpIds, bid.bid.ImpID, tc.description)
+		assert.Len(t, seatBids[0].Bids, len(tc.expectedBidIds), "Incorrect bids number for test case ", tc.description)
+		for _, bid := range seatBids[0].Bids {
+			assert.Contains(t, tc.expectedBidIds, bid.Bid.ID, tc.description)
+			assert.Contains(t, tc.expectedImpIds, bid.Bid.ImpID, tc.description)
 		}
 	}
 
@@ -2327,30 +2328,30 @@ func TestExtraBid(t *testing.T) {
 		},
 	}
 
-	wantSeatBids := []*pbsOrtbSeatBid{
+	wantSeatBids := []*entities.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)},
+			HttpCalls: []*openrtb_ext.ExtHttpCall{},
+			Bids: []*entities.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",
+			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)},
+			HttpCalls: []*openrtb_ext.ExtHttpCall{},
+			Bids: []*entities.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",
+			Seat:     string(openrtb_ext.BidderPubmatic),
+			Currency: "USD",
 		},
 	}
 
@@ -2384,7 +2385,7 @@ func TestExtraBid(t *testing.T) {
 	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)
+		return len(seatBids[i].Seat) < len(seatBids[j].Seat)
 	})
 	assert.Equal(t, wantSeatBids, seatBids)
 }
@@ -2435,30 +2436,30 @@ func TestExtraBidWithAlternateBidderCodeDisabled(t *testing.T) {
 		},
 	}
 
-	wantSeatBids := []*pbsOrtbSeatBid{
+	wantSeatBids := []*entities.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)},
+			HttpCalls: []*openrtb_ext.ExtHttpCall{},
+			Bids: []*entities.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",
+			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)},
+			HttpCalls: []*openrtb_ext.ExtHttpCall{},
+			Bids: []*entities.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",
+			Seat:     string(openrtb_ext.BidderPubmatic),
+			Currency: "USD",
 		},
 	}
 	wantErrs := []error{
@@ -2539,38 +2540,38 @@ func TestExtraBidWithBidAdjustments(t *testing.T) {
 		},
 	}
 
-	wantSeatBids := []*pbsOrtbSeatBid{
+	wantSeatBids := []*entities.PbsOrtbSeatBid{
 		{
-			httpCalls: []*openrtb_ext.ExtHttpCall{},
-			bids: []*pbsOrtbBid{{
-				bid: &openrtb2.Bid{
+			HttpCalls: []*openrtb_ext.ExtHttpCall{},
+			Bids: []*entities.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)},
+				DealPriority:   5,
+				BidType:        openrtb_ext.BidTypeVideo,
+				OriginalBidCPM: 7,
+				OriginalBidCur: "USD",
+				BidMeta:        &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)},
 			}},
-			seat:     "groupm",
-			currency: "USD",
+			Seat:     "groupm",
+			Currency: "USD",
 		},
 		{
-			httpCalls: []*openrtb_ext.ExtHttpCall{},
-			bids: []*pbsOrtbBid{{
-				bid: &openrtb2.Bid{
+			HttpCalls: []*openrtb_ext.ExtHttpCall{},
+			Bids: []*entities.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)},
+				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",
+			Seat:     string(openrtb_ext.BidderPubmatic),
+			Currency: "USD",
 		},
 	}
 
@@ -2607,7 +2608,7 @@ func TestExtraBidWithBidAdjustments(t *testing.T) {
 	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)
+		return len(seatBids[i].Seat) < len(seatBids[j].Seat)
 	})
 	assert.Equal(t, wantSeatBids, seatBids)
 }
@@ -2652,38 +2653,38 @@ func TestExtraBidWithBidAdjustmentsUsingAdapterCode(t *testing.T) {
 		},
 	}
 
-	wantSeatBids := []*pbsOrtbSeatBid{
+	wantSeatBids := []*entities.PbsOrtbSeatBid{
 		{
-			httpCalls: []*openrtb_ext.ExtHttpCall{},
-			bids: []*pbsOrtbBid{{
-				bid: &openrtb2.Bid{
+			HttpCalls: []*openrtb_ext.ExtHttpCall{},
+			Bids: []*entities.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)},
+				DealPriority:   5,
+				BidType:        openrtb_ext.BidTypeVideo,
+				OriginalBidCPM: 7,
+				OriginalBidCur: "USD",
+				BidMeta:        &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)},
 			}},
-			seat:     "groupm",
-			currency: "USD",
+			Seat:     "groupm",
+			Currency: "USD",
 		},
 		{
-			httpCalls: []*openrtb_ext.ExtHttpCall{},
-			bids: []*pbsOrtbBid{{
-				bid: &openrtb2.Bid{
+			HttpCalls: []*openrtb_ext.ExtHttpCall{},
+			Bids: []*entities.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)},
+				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",
+			Seat:     string(openrtb_ext.BidderPubmatic),
+			Currency: "USD",
 		},
 	}
 
@@ -2719,7 +2720,7 @@ func TestExtraBidWithBidAdjustmentsUsingAdapterCode(t *testing.T) {
 	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)
+		return len(seatBids[i].Seat) < len(seatBids[j].Seat)
 	})
 	assert.Equal(t, wantSeatBids, seatBids)
 }
@@ -2764,38 +2765,38 @@ func TestExtraBidWithMultiCurrencies(t *testing.T) {
 		},
 	}
 
-	wantSeatBids := []*pbsOrtbSeatBid{
+	wantSeatBids := []*entities.PbsOrtbSeatBid{
 		{
-			httpCalls: []*openrtb_ext.ExtHttpCall{},
-			bids: []*pbsOrtbBid{{
-				bid: &openrtb2.Bid{
+			HttpCalls: []*openrtb_ext.ExtHttpCall{},
+			Bids: []*entities.PbsOrtbBid{{
+				Bid: &openrtb2.Bid{
 					ID:    "groupmImp1",
 					Price: 571.5994430039375,
 				},
-				dealPriority:   5,
-				bidType:        openrtb_ext.BidTypeVideo,
-				originalBidCPM: 7,
-				originalBidCur: "USD",
-				bidMeta:        &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)},
+				DealPriority:   5,
+				BidType:        openrtb_ext.BidTypeVideo,
+				OriginalBidCPM: 7,
+				OriginalBidCur: "USD",
+				BidMeta:        &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)},
 			}},
-			seat:     "groupm",
-			currency: "INR",
+			Seat:     "groupm",
+			Currency: "INR",
 		},
 		{
-			httpCalls: []*openrtb_ext.ExtHttpCall{},
-			bids: []*pbsOrtbBid{{
-				bid: &openrtb2.Bid{
+			HttpCalls: []*openrtb_ext.ExtHttpCall{},
+			Bids: []*entities.PbsOrtbBid{{
+				Bid: &openrtb2.Bid{
 					ID:    "pubmaticImp1",
 					Price: 244.97118985883034,
 				},
-				dealPriority:   4,
-				bidType:        openrtb_ext.BidTypeBanner,
-				originalBidCPM: 3,
-				originalBidCur: "USD",
-				bidMeta:        &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)},
+				DealPriority:   4,
+				BidType:        openrtb_ext.BidTypeBanner,
+				OriginalBidCPM: 3,
+				OriginalBidCur: "USD",
+				BidMeta:        &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)},
 			}},
-			seat:     string(openrtb_ext.BidderPubmatic),
-			currency: "INR",
+			Seat:     string(openrtb_ext.BidderPubmatic),
+			Currency: "INR",
 		},
 	}
 
@@ -2843,7 +2844,7 @@ func TestExtraBidWithMultiCurrencies(t *testing.T) {
 	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)
+		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 38cce3ee..08516a7d 100644
--- a/exchange/bidder_validate_bids_test.go
+++ b/exchange/bidder_validate_bids_test.go
@@ -7,6 +7,7 @@ import (
 	"github.com/prebid/openrtb/v17/openrtb2"
 	"github.com/prebid/prebid-server/adapters"
 	"github.com/prebid/prebid-server/currency"
+	"github.com/prebid/prebid-server/exchange/entities"
 	"github.com/prebid/prebid-server/experiment/adscert"
 	"github.com/prebid/prebid-server/hooks/hookexecution"
 	"github.com/prebid/prebid-server/openrtb_ext"
@@ -15,10 +16,10 @@ import (
 
 func TestAllValidBids(t *testing.T) {
 	var bidder AdaptedBidder = addValidatedBidderMiddleware(&mockAdaptedBidder{
-		bidResponse: []*pbsOrtbSeatBid{{
-			bids: []*pbsOrtbBid{
+		bidResponse: []*entities.PbsOrtbSeatBid{{
+			Bids: []*entities.PbsOrtbBid{
 				{
-					bid: &openrtb2.Bid{
+					Bid: &openrtb2.Bid{
 						ID:    "one-bid",
 						ImpID: "thisImp",
 						Price: 0.45,
@@ -26,7 +27,7 @@ func TestAllValidBids(t *testing.T) {
 					},
 				},
 				{
-					bid: &openrtb2.Bid{
+					Bid: &openrtb2.Bid{
 						ID:    "thatBid",
 						ImpID: "thatImp",
 						Price: 0.40,
@@ -34,7 +35,7 @@ func TestAllValidBids(t *testing.T) {
 					},
 				},
 				{
-					bid: &openrtb2.Bid{
+					Bid: &openrtb2.Bid{
 						ID:    "123",
 						ImpID: "456",
 						Price: 0.44,
@@ -42,7 +43,7 @@ func TestAllValidBids(t *testing.T) {
 					},
 				},
 				{
-					bid: &openrtb2.Bid{
+					Bid: &openrtb2.Bid{
 						ID:     "zeroPriceBid",
 						ImpID:  "444",
 						Price:  0.00,
@@ -66,44 +67,44 @@ func TestAllValidBids(t *testing.T) {
 	}
 	seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{})
 	assert.Len(t, seatBids, 1)
-	assert.Len(t, seatBids[0].bids, 4)
+	assert.Len(t, seatBids[0].Bids, 4)
 	assert.Len(t, errs, 0)
 }
 
 func TestAllBadBids(t *testing.T) {
 	bidder := addValidatedBidderMiddleware(&mockAdaptedBidder{
-		bidResponse: []*pbsOrtbSeatBid{{
-			bids: []*pbsOrtbBid{
+		bidResponse: []*entities.PbsOrtbSeatBid{{
+			Bids: []*entities.PbsOrtbBid{
 				{
-					bid: &openrtb2.Bid{
+					Bid: &openrtb2.Bid{
 						ID:    "one-bid",
 						Price: 0.45,
 						CrID:  "thisCreative",
 					},
 				},
 				{
-					bid: &openrtb2.Bid{
+					Bid: &openrtb2.Bid{
 						ID:    "thatBid",
 						ImpID: "thatImp",
 						CrID:  "thatCreative",
 					},
 				},
 				{
-					bid: &openrtb2.Bid{
+					Bid: &openrtb2.Bid{
 						ID:    "123",
 						ImpID: "456",
 						Price: 0.44,
 					},
 				},
 				{
-					bid: &openrtb2.Bid{
+					Bid: &openrtb2.Bid{
 						ImpID: "456",
 						Price: 0.44,
 						CrID:  "blah",
 					},
 				},
 				{
-					bid: &openrtb2.Bid{
+					Bid: &openrtb2.Bid{
 						ID:     "zeroPriceBidNoDeal",
 						ImpID:  "444",
 						Price:  0.00,
@@ -112,7 +113,7 @@ func TestAllBadBids(t *testing.T) {
 					},
 				},
 				{
-					bid: &openrtb2.Bid{
+					Bid: &openrtb2.Bid{
 						ID:    "negativePrice",
 						ImpID: "999",
 						Price: -0.10,
@@ -136,16 +137,16 @@ func TestAllBadBids(t *testing.T) {
 	}
 	seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{})
 	assert.Len(t, seatBids, 1)
-	assert.Len(t, seatBids[0].bids, 0)
+	assert.Len(t, seatBids[0].Bids, 0)
 	assert.Len(t, errs, 7)
 }
 
 func TestMixedBids(t *testing.T) {
 	bidder := addValidatedBidderMiddleware(&mockAdaptedBidder{
-		bidResponse: []*pbsOrtbSeatBid{{
-			bids: []*pbsOrtbBid{
+		bidResponse: []*entities.PbsOrtbSeatBid{{
+			Bids: []*entities.PbsOrtbBid{
 				{
-					bid: &openrtb2.Bid{
+					Bid: &openrtb2.Bid{
 						ID:    "one-bid",
 						ImpID: "thisImp",
 						Price: 0.45,
@@ -153,14 +154,14 @@ func TestMixedBids(t *testing.T) {
 					},
 				},
 				{
-					bid: &openrtb2.Bid{
+					Bid: &openrtb2.Bid{
 						ID:    "thatBid",
 						ImpID: "thatImp",
 						CrID:  "thatCreative",
 					},
 				},
 				{
-					bid: &openrtb2.Bid{
+					Bid: &openrtb2.Bid{
 						ID:    "123",
 						ImpID: "456",
 						Price: 0.44,
@@ -168,14 +169,14 @@ func TestMixedBids(t *testing.T) {
 					},
 				},
 				{
-					bid: &openrtb2.Bid{
+					Bid: &openrtb2.Bid{
 						ImpID: "456",
 						Price: 0.44,
 						CrID:  "blah",
 					},
 				},
 				{
-					bid: &openrtb2.Bid{
+					Bid: &openrtb2.Bid{
 						ID:     "zeroPriceBid",
 						ImpID:  "444",
 						Price:  0.00,
@@ -184,7 +185,7 @@ func TestMixedBids(t *testing.T) {
 					},
 				},
 				{
-					bid: &openrtb2.Bid{
+					Bid: &openrtb2.Bid{
 						ID:     "zeroPriceBidNoDeal",
 						ImpID:  "444",
 						Price:  0.00,
@@ -193,7 +194,7 @@ func TestMixedBids(t *testing.T) {
 					},
 				},
 				{
-					bid: &openrtb2.Bid{
+					Bid: &openrtb2.Bid{
 						ID:    "negativePrice",
 						ImpID: "999",
 						Price: -0.10,
@@ -217,7 +218,7 @@ func TestMixedBids(t *testing.T) {
 	}
 	seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{})
 	assert.Len(t, seatBids, 1)
-	assert.Len(t, seatBids[0].bids, 3)
+	assert.Len(t, seatBids[0].Bids, 3)
 	assert.Len(t, errs, 5)
 }
 
@@ -298,9 +299,9 @@ func TestCurrencyBids(t *testing.T) {
 	}
 
 	for _, tc := range currencyTestCases {
-		bids := []*pbsOrtbBid{
+		bids := []*entities.PbsOrtbBid{
 			{
-				bid: &openrtb2.Bid{
+				Bid: &openrtb2.Bid{
 					ID:    "one-bid",
 					ImpID: "thisImp",
 					Price: 0.45,
@@ -308,7 +309,7 @@ func TestCurrencyBids(t *testing.T) {
 				},
 			},
 			{
-				bid: &openrtb2.Bid{
+				Bid: &openrtb2.Bid{
 					ID:    "thatBid",
 					ImpID: "thatImp",
 					Price: 0.44,
@@ -317,9 +318,9 @@ func TestCurrencyBids(t *testing.T) {
 			},
 		}
 		bidder := addValidatedBidderMiddleware(&mockAdaptedBidder{
-			bidResponse: []*pbsOrtbSeatBid{{
-				currency: tc.brpCur,
-				bids:     bids,
+			bidResponse: []*entities.PbsOrtbSeatBid{{
+				Currency: tc.brpCur,
+				Bids:     bids,
 			},
 			}})
 
@@ -346,16 +347,16 @@ func TestCurrencyBids(t *testing.T) {
 		}
 		seatBids, errs := bidder.requestBid(context.Background(), bidderRequest, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{})
 		assert.Len(t, seatBids, 1)
-		assert.Len(t, seatBids[0].bids, expectedValidBids)
+		assert.Len(t, seatBids[0].Bids, expectedValidBids)
 		assert.Len(t, errs, expectedErrs)
 	}
 }
 
 type mockAdaptedBidder struct {
-	bidResponse   []*pbsOrtbSeatBid
+	bidResponse   []*entities.PbsOrtbSeatBid
 	errorResponse []error
 }
 
-func (b *mockAdaptedBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestMetadata bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, executor hookexecution.StageExecutor) ([]*pbsOrtbSeatBid, []error) {
+func (b *mockAdaptedBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestMetadata bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, executor hookexecution.StageExecutor) ([]*entities.PbsOrtbSeatBid, []error) {
 	return b.bidResponse, b.errorResponse
 }
diff --git a/exchange/events_test.go b/exchange/events_test.go
index 922624e8..4784c862 100644
--- a/exchange/events_test.go
+++ b/exchange/events_test.go
@@ -4,6 +4,7 @@ import (
 	"testing"
 
 	"github.com/prebid/openrtb/v17/openrtb2"
+	"github.com/prebid/prebid-server/exchange/entities"
 	"github.com/prebid/prebid-server/openrtb_ext"
 	"github.com/stretchr/testify/assert"
 )
@@ -69,7 +70,7 @@ func Test_eventsData_makeBidExtEvents(t *testing.T) {
 				auctionTimestampMs: 1234567890,
 				externalURL:        "http://localhost",
 			}
-			bid := &pbsOrtbBid{bid: &openrtb2.Bid{ID: "BID-1"}, bidType: tt.args.bidType, generatedBidID: tt.args.generatedBidId}
+			bid := &entities.PbsOrtbBid{Bid: &openrtb2.Bid{ID: "BID-1"}, BidType: tt.args.bidType, GeneratedBidID: tt.args.generatedBidId}
 			assert.Equal(t, tt.want, evData.makeBidExtEvents(bid, openrtb_ext.BidderOpenx))
 		})
 	}
@@ -140,7 +141,7 @@ func Test_eventsData_modifyBidJSON(t *testing.T) {
 				auctionTimestampMs: 1234567890,
 				externalURL:        "http://localhost",
 			}
-			bid := &pbsOrtbBid{bid: &openrtb2.Bid{ID: "BID-1"}, bidType: tt.args.bidType, generatedBidID: tt.args.generatedBidId}
+			bid := &entities.PbsOrtbBid{Bid: &openrtb2.Bid{ID: "BID-1"}, BidType: tt.args.bidType, GeneratedBidID: tt.args.generatedBidId}
 			modifiedJSON, err := evData.modifyBidJSON(bid, openrtb_ext.BidderOpenx, tt.jsonBytes)
 			if tt.want != nil {
 				assert.NoError(t, err, "Unexpected error")
diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go
index ae8093ba..d63fa6f6 100644
--- a/exchange/exchange_test.go
+++ b/exchange/exchange_test.go
@@ -20,6 +20,7 @@ import (
 
 	"github.com/buger/jsonparser"
 	"github.com/prebid/openrtb/v17/openrtb2"
+	"github.com/prebid/prebid-server/exchange/entities"
 	"github.com/prebid/prebid-server/hooks"
 	"github.com/prebid/prebid-server/hooks/hookexecution"
 	"github.com/prebid/prebid-server/hooks/hookstage"
@@ -141,9 +142,9 @@ func TestCharacterEscape(t *testing.T) {
 	liveAdapters := make([]openrtb_ext.BidderName, 1)
 	liveAdapters[0] = "appnexus"
 
-	//adapterBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid,
-	adapterBids := make(map[openrtb_ext.BidderName]*pbsOrtbSeatBid, 1)
-	adapterBids["appnexus"] = &pbsOrtbSeatBid{currency: "USD"}
+	//adapterBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid,
+	adapterBids := make(map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, 1)
+	adapterBids["appnexus"] = &entities.PbsOrtbSeatBid{Currency: "USD"}
 
 	//An openrtb2.BidRequest struct as specified in https://github.com/prebid/prebid-server/issues/465
 	bidRequest := &openrtb2.BidRequest{
@@ -1258,7 +1259,7 @@ func TestGetBidCacheInfoEndToEnd(t *testing.T) {
 	// 	3) Build all the parameters e.buildBidResponse(ctx.Background(), liveA... ) needs
 	liveAdapters := []openrtb_ext.BidderName{bidderName}
 
-	//adapterBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid,
+	//adapterBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid,
 	bids := []*openrtb2.Bid{
 		{
 			ID:             "some-imp-id",
@@ -1295,21 +1296,21 @@ func TestGetBidCacheInfoEndToEnd(t *testing.T) {
 			bids[0]: testUUID,
 		},
 	}
-	aPbsOrtbBidArr := []*pbsOrtbBid{
+	aPbsOrtbBidArr := []*entities.PbsOrtbBid{
 		{
-			bid:     bids[0],
-			bidType: openrtb_ext.BidTypeBanner,
-			bidTargets: map[string]string{
+			Bid:     bids[0],
+			BidType: openrtb_ext.BidTypeBanner,
+			BidTargets: map[string]string{
 				"pricegranularity":  "med",
 				"includewinners":    "true",
 				"includebidderkeys": "false",
 			},
 		},
 	}
-	adapterBids := map[openrtb_ext.BidderName]*pbsOrtbSeatBid{
+	adapterBids := map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{
 		bidderName: {
-			bids:     aPbsOrtbBidArr,
-			currency: "USD",
+			Bids:     aPbsOrtbBidArr,
+			Currency: "USD",
 		},
 	}
 
@@ -1417,12 +1418,12 @@ func TestBidReturnsCreative(t *testing.T) {
 	}
 
 	// Test set up
-	sampleBids := []*pbsOrtbBid{
+	sampleBids := []*entities.PbsOrtbBid{
 		{
-			bid:            sampleOpenrtbBid,
-			bidType:        openrtb_ext.BidTypeBanner,
-			bidTargets:     map[string]string{},
-			generatedBidID: "randomId",
+			Bid:            sampleOpenrtbBid,
+			BidType:        openrtb_ext.BidTypeBanner,
+			BidTargets:     map[string]string{},
+			GeneratedBidID: "randomId",
 		},
 	}
 	sampleAuction := &auction{cacheIds: map[*openrtb2.Bid]string{sampleOpenrtbBid: "CACHE_UUID_1234"}}
@@ -1470,7 +1471,7 @@ func TestGetBidCacheInfo(t *testing.T) {
 		scheme           string
 		host             string
 		path             string
-		bid              *pbsOrtbBid
+		bid              *entities.PbsOrtbBid
 		auction          *auction
 		expectedFound    bool
 		expectedCacheID  string
@@ -1481,7 +1482,7 @@ func TestGetBidCacheInfo(t *testing.T) {
 			scheme:           "https",
 			host:             "prebid.org",
 			path:             "cache",
-			bid:              &pbsOrtbBid{bid: bid},
+			bid:              &entities.PbsOrtbBid{Bid: bid},
 			auction:          &auction{cacheIds: map[*openrtb2.Bid]string{bid: "anyID"}},
 			expectedFound:    true,
 			expectedCacheID:  "anyID",
@@ -1492,7 +1493,7 @@ func TestGetBidCacheInfo(t *testing.T) {
 			scheme:           "https",
 			host:             "prebid.org",
 			path:             "cache",
-			bid:              &pbsOrtbBid{bid: bid},
+			bid:              &entities.PbsOrtbBid{Bid: bid},
 			auction:          &auction{vastCacheIds: map[*openrtb2.Bid]string{bid: "anyID"}},
 			expectedFound:    true,
 			expectedCacheID:  "anyID",
@@ -1503,7 +1504,7 @@ func TestGetBidCacheInfo(t *testing.T) {
 			scheme:           "https",
 			host:             "prebid.org",
 			path:             "cache",
-			bid:              &pbsOrtbBid{bid: bid},
+			bid:              &entities.PbsOrtbBid{Bid: bid},
 			auction:          &auction{},
 			expectedFound:    false,
 			expectedCacheID:  "",
@@ -1513,7 +1514,7 @@ func TestGetBidCacheInfo(t *testing.T) {
 			description:      "Scheme Not Provided",
 			host:             "prebid.org",
 			path:             "cache",
-			bid:              &pbsOrtbBid{bid: bid},
+			bid:              &entities.PbsOrtbBid{Bid: bid},
 			auction:          &auction{cacheIds: map[*openrtb2.Bid]string{bid: "anyID"}},
 			expectedFound:    true,
 			expectedCacheID:  "anyID",
@@ -1521,7 +1522,7 @@ func TestGetBidCacheInfo(t *testing.T) {
 		},
 		{
 			description:      "Host And Path Not Provided - Without Scheme",
-			bid:              &pbsOrtbBid{bid: bid},
+			bid:              &entities.PbsOrtbBid{Bid: bid},
 			auction:          &auction{cacheIds: map[*openrtb2.Bid]string{bid: "anyID"}},
 			expectedFound:    true,
 			expectedCacheID:  "anyID",
@@ -1530,7 +1531,7 @@ func TestGetBidCacheInfo(t *testing.T) {
 		{
 			description:      "Host And Path Not Provided - With Scheme",
 			scheme:           "https",
-			bid:              &pbsOrtbBid{bid: bid},
+			bid:              &entities.PbsOrtbBid{Bid: bid},
 			auction:          &auction{cacheIds: map[*openrtb2.Bid]string{bid: "anyID"}},
 			expectedFound:    true,
 			expectedCacheID:  "anyID",
@@ -1552,7 +1553,7 @@ func TestGetBidCacheInfo(t *testing.T) {
 			scheme:           "https",
 			host:             "prebid.org",
 			path:             "cache",
-			bid:              &pbsOrtbBid{bid: nil},
+			bid:              &entities.PbsOrtbBid{Bid: nil},
 			auction:          &auction{cacheIds: map[*openrtb2.Bid]string{bid: "anyID"}},
 			expectedFound:    false,
 			expectedCacheID:  "",
@@ -1563,7 +1564,7 @@ func TestGetBidCacheInfo(t *testing.T) {
 			scheme:           "https",
 			host:             "prebid.org",
 			path:             "cache",
-			bid:              &pbsOrtbBid{bid: bid},
+			bid:              &entities.PbsOrtbBid{Bid: bid},
 			auction:          nil,
 			expectedFound:    false,
 			expectedCacheID:  "",
@@ -1649,7 +1650,7 @@ func TestBidResponseCurrency(t *testing.T) {
 		H:     250,
 		Ext:   nil,
 	}
-	aPbsOrtbBidArr := []*pbsOrtbBid{{bid: sampleBid, bidType: openrtb_ext.BidTypeBanner, originalBidCPM: 9.517803}}
+	aPbsOrtbBidArr := []*entities.PbsOrtbBid{{Bid: sampleBid, BidType: openrtb_ext.BidTypeBanner, OriginalBidCPM: 9.517803}}
 	sampleSeatBid := []openrtb2.SeatBid{
 		{
 			Seat: "appnexus",
@@ -1669,16 +1670,16 @@ func TestBidResponseCurrency(t *testing.T) {
 	// Test cases
 	type aTest struct {
 		description         string
-		adapterBids         map[openrtb_ext.BidderName]*pbsOrtbSeatBid
+		adapterBids         map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid
 		expectedBidResponse *openrtb2.BidResponse
 	}
 	testCases := []aTest{
 		{
 			description: "1) Adapter to bids map comes with a non-empty currency field and non-empty bid array",
-			adapterBids: map[openrtb_ext.BidderName]*pbsOrtbSeatBid{
+			adapterBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{
 				openrtb_ext.BidderName("appnexus"): {
-					bids:     aPbsOrtbBidArr,
-					currency: "USD",
+					Bids:     aPbsOrtbBidArr,
+					Currency: "USD",
 				},
 			},
 			expectedBidResponse: &openrtb2.BidResponse{
@@ -1691,10 +1692,10 @@ func TestBidResponseCurrency(t *testing.T) {
 		},
 		{
 			description: "2) Adapter to bids map comes with a non-empty currency field but an empty bid array",
-			adapterBids: map[openrtb_ext.BidderName]*pbsOrtbSeatBid{
+			adapterBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{
 				openrtb_ext.BidderName("appnexus"): {
-					bids:     nil,
-					currency: "USD",
+					Bids:     nil,
+					Currency: "USD",
 				},
 			},
 			expectedBidResponse: &openrtb2.BidResponse{
@@ -1707,10 +1708,10 @@ func TestBidResponseCurrency(t *testing.T) {
 		},
 		{
 			description: "3) Adapter to bids map comes with an empty currency string and a non-empty bid array",
-			adapterBids: map[openrtb_ext.BidderName]*pbsOrtbSeatBid{
+			adapterBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{
 				openrtb_ext.BidderName("appnexus"): {
-					bids:     aPbsOrtbBidArr,
-					currency: "",
+					Bids:     aPbsOrtbBidArr,
+					Currency: "",
 				},
 			},
 			expectedBidResponse: &openrtb2.BidResponse{
@@ -1723,10 +1724,10 @@ func TestBidResponseCurrency(t *testing.T) {
 		},
 		{
 			description: "4) Adapter to bids map comes with an empty currency string and an empty bid array",
-			adapterBids: map[openrtb_ext.BidderName]*pbsOrtbSeatBid{
+			adapterBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{
 				openrtb_ext.BidderName("appnexus"): {
-					bids:     nil,
-					currency: "",
+					Bids:     nil,
+					Currency: "",
 				},
 			},
 			expectedBidResponse: &openrtb2.BidResponse{
@@ -1799,11 +1800,11 @@ func TestBidResponseImpExtInfo(t *testing.T) {
 		H:     250,
 		Ext:   nil,
 	}
-	aPbsOrtbBidArr := []*pbsOrtbBid{{bid: sampleBid, bidType: openrtb_ext.BidTypeVideo}}
+	aPbsOrtbBidArr := []*entities.PbsOrtbBid{{Bid: sampleBid, BidType: openrtb_ext.BidTypeVideo}}
 
-	adapterBids := map[openrtb_ext.BidderName]*pbsOrtbSeatBid{
+	adapterBids := map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{
 		openrtb_ext.BidderName("appnexus"): {
-			bids: aPbsOrtbBidArr,
+			Bids: aPbsOrtbBidArr,
 		},
 	}
 
@@ -2510,7 +2511,7 @@ func TestCategoryMapping(t *testing.T) {
 
 	requestExt.Prebid.Targeting.DurationRangeSec = []int{15, 30, 50}
 
-	adapterBids := make(map[openrtb_ext.BidderName]*pbsOrtbSeatBid)
+	adapterBids := make(map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid)
 
 	cats1 := []string{"IAB1-3"}
 	cats2 := []string{"IAB1-4"}
@@ -2521,19 +2522,19 @@ func TestCategoryMapping(t *testing.T) {
 	bid3 := openrtb2.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 30.0000, Cat: cats3, W: 1, H: 1}
 	bid4 := openrtb2.Bid{ID: "bid_id4", ImpID: "imp_id4", Price: 40.0000, Cat: cats4, W: 1, H: 1}
 
-	bid1_1 := pbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, "", 10.0000, "USD"}
-	bid1_2 := pbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, 0, false, "", 20.0000, "USD"}
-	bid1_3 := pbsOrtbBid{&bid3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30, PrimaryCategory: "AdapterOverride"}, nil, 0, false, "", 30.0000, "USD"}
-	bid1_4 := pbsOrtbBid{&bid4, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, "", 40.0000, "USD"}
+	bid1_1 := entities.PbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, "", 10.0000, "USD"}
+	bid1_2 := entities.PbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, 0, false, "", 20.0000, "USD"}
+	bid1_3 := entities.PbsOrtbBid{&bid3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30, PrimaryCategory: "AdapterOverride"}, nil, 0, false, "", 30.0000, "USD"}
+	bid1_4 := entities.PbsOrtbBid{&bid4, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, "", 40.0000, "USD"}
 
-	innerBids := []*pbsOrtbBid{
+	innerBids := []*entities.PbsOrtbBid{
 		&bid1_1,
 		&bid1_2,
 		&bid1_3,
 		&bid1_4,
 	}
 
-	seatBid := pbsOrtbSeatBid{bids: innerBids, currency: "USD"}
+	seatBid := entities.PbsOrtbSeatBid{Bids: innerBids, Currency: "USD"}
 	bidderName1 := openrtb_ext.BidderName("appnexus")
 
 	adapterBids[bidderName1] = &seatBid
@@ -2546,7 +2547,7 @@ func TestCategoryMapping(t *testing.T) {
 	assert.Equal(t, "10.00_Electronics_30s", bidCategory["bid_id1"], "Category mapping doesn't match")
 	assert.Equal(t, "20.00_Sports_50s", bidCategory["bid_id2"], "Category mapping doesn't match")
 	assert.Equal(t, "20.00_AdapterOverride_30s", bidCategory["bid_id3"], "Category mapping override from adapter didn't take")
-	assert.Equal(t, 3, len(adapterBids[bidderName1].bids), "Bidders number doesn't match")
+	assert.Equal(t, 3, len(adapterBids[bidderName1].Bids), "Bidders number doesn't match")
 	assert.Equal(t, 3, len(bidCategory), "Bidders category mapping doesn't match")
 }
 
@@ -2565,7 +2566,7 @@ func TestCategoryMappingNoIncludeBrandCategory(t *testing.T) {
 	}
 	requestExt.Prebid.Targeting.DurationRangeSec = []int{15, 30, 40, 50}
 
-	adapterBids := make(map[openrtb_ext.BidderName]*pbsOrtbSeatBid)
+	adapterBids := make(map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid)
 
 	cats1 := []string{"IAB1-3"}
 	cats2 := []string{"IAB1-4"}
@@ -2576,19 +2577,19 @@ func TestCategoryMappingNoIncludeBrandCategory(t *testing.T) {
 	bid3 := openrtb2.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 30.0000, Cat: cats3, W: 1, H: 1}
 	bid4 := openrtb2.Bid{ID: "bid_id4", ImpID: "imp_id4", Price: 40.0000, Cat: cats4, W: 1, H: 1}
 
-	bid1_1 := pbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, "", 10.0000, "USD"}
-	bid1_2 := pbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, 0, false, "", 20.0000, "USD"}
-	bid1_3 := pbsOrtbBid{&bid3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30, PrimaryCategory: "AdapterOverride"}, nil, 0, false, "", 30.0000, "USD"}
-	bid1_4 := pbsOrtbBid{&bid4, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 50}, nil, 0, false, "", 40.0000, "USD"}
+	bid1_1 := entities.PbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, "", 10.0000, "USD"}
+	bid1_2 := entities.PbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, 0, false, "", 20.0000, "USD"}
+	bid1_3 := entities.PbsOrtbBid{&bid3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30, PrimaryCategory: "AdapterOverride"}, nil, 0, false, "", 30.0000, "USD"}
+	bid1_4 := entities.PbsOrtbBid{&bid4, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 50}, nil, 0, false, "", 40.0000, "USD"}
 
-	innerBids := []*pbsOrtbBid{
+	innerBids := []*entities.PbsOrtbBid{
 		&bid1_1,
 		&bid1_2,
 		&bid1_3,
 		&bid1_4,
 	}
 
-	seatBid := pbsOrtbSeatBid{bids: innerBids, currency: "USD"}
+	seatBid := entities.PbsOrtbSeatBid{Bids: innerBids, Currency: "USD"}
 	bidderName1 := openrtb_ext.BidderName("appnexus")
 
 	adapterBids[bidderName1] = &seatBid
@@ -2601,7 +2602,7 @@ func TestCategoryMappingNoIncludeBrandCategory(t *testing.T) {
 	assert.Equal(t, "20.00_40s", bidCategory["bid_id2"], "Category mapping doesn't match")
 	assert.Equal(t, "20.00_30s", bidCategory["bid_id3"], "Category mapping doesn't match")
 	assert.Equal(t, "20.00_50s", bidCategory["bid_id4"], "Category mapping doesn't match")
-	assert.Equal(t, 4, len(adapterBids[bidderName1].bids), "Bidders number doesn't match")
+	assert.Equal(t, 4, len(adapterBids[bidderName1].Bids), "Bidders number doesn't match")
 	assert.Equal(t, 4, len(bidCategory), "Bidders category mapping doesn't match")
 }
 
@@ -2621,7 +2622,7 @@ func TestCategoryMappingTranslateCategoriesNil(t *testing.T) {
 
 	requestExt.Prebid.Targeting.DurationRangeSec = []int{15, 30, 50}
 
-	adapterBids := make(map[openrtb_ext.BidderName]*pbsOrtbSeatBid)
+	adapterBids := make(map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid)
 
 	cats1 := []string{"IAB1-3"}
 	cats2 := []string{"IAB1-4"}
@@ -2630,17 +2631,17 @@ func TestCategoryMappingTranslateCategoriesNil(t *testing.T) {
 	bid2 := openrtb2.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 20.0000, Cat: cats2, W: 1, H: 1}
 	bid3 := openrtb2.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 30.0000, Cat: cats3, W: 1, H: 1}
 
-	bid1_1 := pbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, "", 10.0000, "USD"}
-	bid1_2 := pbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, 0, false, "", 20.0000, "USD"}
-	bid1_3 := pbsOrtbBid{&bid3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, "", 30.0000, "USD"}
+	bid1_1 := entities.PbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, "", 10.0000, "USD"}
+	bid1_2 := entities.PbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, 0, false, "", 20.0000, "USD"}
+	bid1_3 := entities.PbsOrtbBid{&bid3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, "", 30.0000, "USD"}
 
-	innerBids := []*pbsOrtbBid{
+	innerBids := []*entities.PbsOrtbBid{
 		&bid1_1,
 		&bid1_2,
 		&bid1_3,
 	}
 
-	seatBid := pbsOrtbSeatBid{bids: innerBids, currency: "USD"}
+	seatBid := entities.PbsOrtbSeatBid{Bids: innerBids, Currency: "USD"}
 	bidderName1 := openrtb_ext.BidderName("appnexus")
 
 	adapterBids[bidderName1] = &seatBid
@@ -2652,7 +2653,7 @@ func TestCategoryMappingTranslateCategoriesNil(t *testing.T) {
 	assert.Equal(t, "bid rejected [bid ID: bid_id3] reason: Category mapping file for primary ad server: 'freewheel', publisher: '' not found", rejections[0], "Rejection message did not match expected")
 	assert.Equal(t, "10.00_Electronics_30s", bidCategory["bid_id1"], "Category mapping doesn't match")
 	assert.Equal(t, "20.00_Sports_50s", bidCategory["bid_id2"], "Category mapping doesn't match")
-	assert.Equal(t, 2, len(adapterBids[bidderName1].bids), "Bidders number doesn't match")
+	assert.Equal(t, 2, len(adapterBids[bidderName1].Bids), "Bidders number doesn't match")
 	assert.Equal(t, 2, len(bidCategory), "Bidders category mapping doesn't match")
 }
 
@@ -2703,7 +2704,7 @@ func TestCategoryMappingTranslateCategoriesFalse(t *testing.T) {
 
 	requestExt.Prebid.Targeting.DurationRangeSec = []int{15, 30, 50}
 
-	adapterBids := make(map[openrtb_ext.BidderName]*pbsOrtbSeatBid)
+	adapterBids := make(map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid)
 
 	cats1 := []string{"IAB1-3"}
 	cats2 := []string{"IAB1-4"}
@@ -2712,17 +2713,17 @@ func TestCategoryMappingTranslateCategoriesFalse(t *testing.T) {
 	bid2 := openrtb2.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 20.0000, Cat: cats2, W: 1, H: 1}
 	bid3 := openrtb2.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 30.0000, Cat: cats3, W: 1, H: 1}
 
-	bid1_1 := pbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, "", 10.0000, "USD"}
-	bid1_2 := pbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, 0, false, "", 20.0000, "USD"}
-	bid1_3 := pbsOrtbBid{&bid3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, "", 30.0000, "USD"}
+	bid1_1 := entities.PbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, "", 10.0000, "USD"}
+	bid1_2 := entities.PbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, 0, false, "", 20.0000, "USD"}
+	bid1_3 := entities.PbsOrtbBid{&bid3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, "", 30.0000, "USD"}
 
-	innerBids := []*pbsOrtbBid{
+	innerBids := []*entities.PbsOrtbBid{
 		&bid1_1,
 		&bid1_2,
 		&bid1_3,
 	}
 
-	seatBid := pbsOrtbSeatBid{bids: innerBids, currency: "USD"}
+	seatBid := entities.PbsOrtbSeatBid{Bids: innerBids, Currency: "USD"}
 	bidderName1 := openrtb_ext.BidderName("appnexus")
 
 	adapterBids[bidderName1] = &seatBid
@@ -2734,7 +2735,7 @@ func TestCategoryMappingTranslateCategoriesFalse(t *testing.T) {
 	assert.Equal(t, "10.00_IAB1-3_30s", bidCategory["bid_id1"], "Category should not be translated")
 	assert.Equal(t, "20.00_IAB1-4_50s", bidCategory["bid_id2"], "Category should not be translated")
 	assert.Equal(t, "20.00_IAB1-1000_30s", bidCategory["bid_id3"], "Bid should not be rejected")
-	assert.Equal(t, 3, len(adapterBids[bidderName1].bids), "Bidders number doesn't match")
+	assert.Equal(t, 3, len(adapterBids[bidderName1].Bids), "Bidders number doesn't match")
 	assert.Equal(t, 3, len(bidCategory), "Bidders category mapping doesn't match")
 }
 
@@ -2752,7 +2753,7 @@ func TestCategoryDedupe(t *testing.T) {
 		includeWinners:   true,
 	}
 
-	adapterBids := make(map[openrtb_ext.BidderName]*pbsOrtbSeatBid)
+	adapterBids := make(map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid)
 
 	cats1 := []string{"IAB1-3"}
 	cats2 := []string{"IAB1-4"}
@@ -2764,11 +2765,11 @@ func TestCategoryDedupe(t *testing.T) {
 	bid4 := openrtb2.Bid{ID: "bid_id4", ImpID: "imp_id4", Price: 20.0000, Cat: cats4, W: 1, H: 1}
 	bid5 := openrtb2.Bid{ID: "bid_id5", ImpID: "imp_id5", Price: 20.0000, Cat: cats1, W: 1, H: 1}
 
-	bid1_1 := pbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, "", 10.0000, "USD"}
-	bid1_2 := pbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 50}, nil, 0, false, "", 15.0000, "USD"}
-	bid1_3 := pbsOrtbBid{&bid3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, "", 20.0000, "USD"}
-	bid1_4 := pbsOrtbBid{&bid4, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, "", 20.0000, "USD"}
-	bid1_5 := pbsOrtbBid{&bid5, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, "", 20.0000, "USD"}
+	bid1_1 := entities.PbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, "", 10.0000, "USD"}
+	bid1_2 := entities.PbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 50}, nil, 0, false, "", 15.0000, "USD"}
+	bid1_3 := entities.PbsOrtbBid{&bid3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, "", 20.0000, "USD"}
+	bid1_4 := entities.PbsOrtbBid{&bid4, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, "", 20.0000, "USD"}
+	bid1_5 := entities.PbsOrtbBid{&bid5, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, "", 20.0000, "USD"}
 
 	selectedBids := make(map[string]int)
 	expectedCategories := map[string]string{
@@ -2784,7 +2785,7 @@ func TestCategoryDedupe(t *testing.T) {
 	// and bid3 others. It's conceivably possible (but highly unlikely) that the same bid get chosen every single time, but
 	// if you notice false fails from this test increase numIterations to make it even less likely to happen.
 	for i := 0; i < numIterations; i++ {
-		innerBids := []*pbsOrtbBid{
+		innerBids := []*entities.PbsOrtbBid{
 			&bid1_1,
 			&bid1_2,
 			&bid1_3,
@@ -2792,7 +2793,7 @@ func TestCategoryDedupe(t *testing.T) {
 			&bid1_5,
 		}
 
-		seatBid := pbsOrtbSeatBid{bids: innerBids, currency: "USD"}
+		seatBid := entities.PbsOrtbSeatBid{Bids: innerBids, Currency: "USD"}
 		bidderName1 := openrtb_ext.BidderName("appnexus")
 
 		adapterBids[bidderName1] = &seatBid
@@ -2803,7 +2804,7 @@ func TestCategoryDedupe(t *testing.T) {
 		assert.Equal(t, 3, len(rejections), "There should be 2 bid rejection messages")
 		assert.Regexpf(t, regexp.MustCompile(`bid rejected \[bid ID: bid_id(1|3)\] reason: Bid was deduplicated`), rejections[0], "Rejection message did not match expected")
 		assert.Equal(t, "bid rejected [bid ID: bid_id4] reason: Category mapping file for primary ad server: 'freewheel', publisher: '' not found", rejections[1], "Rejection message did not match expected")
-		assert.Equal(t, 2, len(adapterBids[bidderName1].bids), "Bidders number doesn't match")
+		assert.Equal(t, 2, len(adapterBids[bidderName1].Bids), "Bidders number doesn't match")
 		assert.Equal(t, 2, len(bidCategory), "Bidders category mapping doesn't match")
 
 		for bidId, bidCat := range bidCategory {
@@ -2832,7 +2833,7 @@ func TestNoCategoryDedupe(t *testing.T) {
 		includeWinners:   true,
 	}
 
-	adapterBids := make(map[openrtb_ext.BidderName]*pbsOrtbSeatBid)
+	adapterBids := make(map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid)
 
 	cats1 := []string{"IAB1-3"}
 	cats2 := []string{"IAB1-4"}
@@ -2843,11 +2844,11 @@ func TestNoCategoryDedupe(t *testing.T) {
 	bid4 := openrtb2.Bid{ID: "bid_id4", ImpID: "imp_id4", Price: 20.0000, Cat: cats4, W: 1, H: 1}
 	bid5 := openrtb2.Bid{ID: "bid_id5", ImpID: "imp_id5", Price: 10.0000, Cat: cats1, W: 1, H: 1}
 
-	bid1_1 := pbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, "", 14.0000, "USD"}
-	bid1_2 := pbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, "", 14.0000, "USD"}
-	bid1_3 := pbsOrtbBid{&bid3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, "", 20.0000, "USD"}
-	bid1_4 := pbsOrtbBid{&bid4, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, "", 20.0000, "USD"}
-	bid1_5 := pbsOrtbBid{&bid5, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, "", 10.0000, "USD"}
+	bid1_1 := entities.PbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, "", 14.0000, "USD"}
+	bid1_2 := entities.PbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, "", 14.0000, "USD"}
+	bid1_3 := entities.PbsOrtbBid{&bid3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, "", 20.0000, "USD"}
+	bid1_4 := entities.PbsOrtbBid{&bid4, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, "", 20.0000, "USD"}
+	bid1_5 := entities.PbsOrtbBid{&bid5, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, "", 10.0000, "USD"}
 
 	selectedBids := make(map[string]int)
 	expectedCategories := map[string]string{
@@ -2864,7 +2865,7 @@ func TestNoCategoryDedupe(t *testing.T) {
 	// and bid3 others. It's conceivably possible (but highly unlikely) that the same bid get chosen every single time, but
 	// if you notice false fails from this test increase numIterations to make it even less likely to happen.
 	for i := 0; i < numIterations; i++ {
-		innerBids := []*pbsOrtbBid{
+		innerBids := []*entities.PbsOrtbBid{
 			&bid1_1,
 			&bid1_2,
 			&bid1_3,
@@ -2872,7 +2873,7 @@ func TestNoCategoryDedupe(t *testing.T) {
 			&bid1_5,
 		}
 
-		seatBid := pbsOrtbSeatBid{bids: innerBids, currency: "USD"}
+		seatBid := entities.PbsOrtbSeatBid{Bids: innerBids, Currency: "USD"}
 		bidderName1 := openrtb_ext.BidderName("appnexus")
 
 		adapterBids[bidderName1] = &seatBid
@@ -2883,7 +2884,7 @@ func TestNoCategoryDedupe(t *testing.T) {
 		assert.Equal(t, 2, len(rejections), "There should be 2 bid rejection messages")
 		assert.Regexpf(t, regexp.MustCompile(`bid rejected \[bid ID: bid_id(1|2)\] reason: Bid was deduplicated`), rejections[0], "Rejection message did not match expected")
 		assert.Regexpf(t, regexp.MustCompile(`bid rejected \[bid ID: bid_id(3|4)\] reason: Bid was deduplicated`), rejections[1], "Rejection message did not match expected")
-		assert.Equal(t, 3, len(adapterBids[bidderName1].bids), "Bidders number doesn't match")
+		assert.Equal(t, 3, len(adapterBids[bidderName1].Bids), "Bidders number doesn't match")
 		assert.Equal(t, 3, len(bidCategory), "Bidders category mapping doesn't match")
 
 		for bidId, bidCat := range bidCategory {
@@ -2916,27 +2917,27 @@ func TestCategoryMappingBidderName(t *testing.T) {
 
 	requestExt.Prebid.Targeting.DurationRangeSec = []int{15, 30}
 
-	adapterBids := make(map[openrtb_ext.BidderName]*pbsOrtbSeatBid)
+	adapterBids := make(map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid)
 
 	cats1 := []string{"IAB1-1"}
 	cats2 := []string{"IAB1-2"}
 	bid1 := openrtb2.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: cats1, W: 1, H: 1}
 	bid2 := openrtb2.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 10.0000, Cat: cats2, W: 1, H: 1}
 
-	bid1_1 := pbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, "", 10.0000, "USD"}
-	bid1_2 := pbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, "", 10.0000, "USD"}
+	bid1_1 := entities.PbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, "", 10.0000, "USD"}
+	bid1_2 := entities.PbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, "", 10.0000, "USD"}
 
-	innerBids1 := []*pbsOrtbBid{
+	innerBids1 := []*entities.PbsOrtbBid{
 		&bid1_1,
 	}
-	innerBids2 := []*pbsOrtbBid{
+	innerBids2 := []*entities.PbsOrtbBid{
 		&bid1_2,
 	}
 
-	seatBid1 := pbsOrtbSeatBid{bids: innerBids1, currency: "USD"}
+	seatBid1 := entities.PbsOrtbSeatBid{Bids: innerBids1, Currency: "USD"}
 	bidderName1 := openrtb_ext.BidderName("bidder1")
 
-	seatBid2 := pbsOrtbSeatBid{bids: innerBids2, currency: "USD"}
+	seatBid2 := entities.PbsOrtbSeatBid{Bids: innerBids2, Currency: "USD"}
 	bidderName2 := openrtb_ext.BidderName("bidder2")
 
 	adapterBids[bidderName1] = &seatBid1
@@ -2948,8 +2949,8 @@ func TestCategoryMappingBidderName(t *testing.T) {
 	assert.Empty(t, rejections, "There should be 0 bid rejection messages")
 	assert.Equal(t, "10.00_VideoGames_30s_bidder1", bidCategory["bid_id1"], "Category mapping doesn't match")
 	assert.Equal(t, "10.00_HomeDecor_30s_bidder2", bidCategory["bid_id2"], "Category mapping doesn't match")
-	assert.Len(t, adapterBids[bidderName1].bids, 1, "Bidders number doesn't match")
-	assert.Len(t, adapterBids[bidderName2].bids, 1, "Bidders number doesn't match")
+	assert.Len(t, adapterBids[bidderName1].Bids, 1, "Bidders number doesn't match")
+	assert.Len(t, adapterBids[bidderName2].Bids, 1, "Bidders number doesn't match")
 	assert.Len(t, bidCategory, 2, "Bidders category mapping doesn't match")
 }
 
@@ -2970,27 +2971,27 @@ func TestCategoryMappingBidderNameNoCategories(t *testing.T) {
 
 	requestExt.Prebid.Targeting.DurationRangeSec = []int{15, 30}
 
-	adapterBids := make(map[openrtb_ext.BidderName]*pbsOrtbSeatBid)
+	adapterBids := make(map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid)
 
 	cats1 := []string{"IAB1-1"}
 	cats2 := []string{"IAB1-2"}
 	bid1 := openrtb2.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: cats1, W: 1, H: 1}
 	bid2 := openrtb2.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 12.0000, Cat: cats2, W: 1, H: 1}
 
-	bid1_1 := pbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, "", 10.0000, "USD"}
-	bid1_2 := pbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, "", 12.0000, "USD"}
+	bid1_1 := entities.PbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, "", 10.0000, "USD"}
+	bid1_2 := entities.PbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, "", 12.0000, "USD"}
 
-	innerBids1 := []*pbsOrtbBid{
+	innerBids1 := []*entities.PbsOrtbBid{
 		&bid1_1,
 	}
-	innerBids2 := []*pbsOrtbBid{
+	innerBids2 := []*entities.PbsOrtbBid{
 		&bid1_2,
 	}
 
-	seatBid1 := pbsOrtbSeatBid{bids: innerBids1, currency: "USD"}
+	seatBid1 := entities.PbsOrtbSeatBid{Bids: innerBids1, Currency: "USD"}
 	bidderName1 := openrtb_ext.BidderName("bidder1")
 
-	seatBid2 := pbsOrtbSeatBid{bids: innerBids2, currency: "USD"}
+	seatBid2 := entities.PbsOrtbSeatBid{Bids: innerBids2, Currency: "USD"}
 	bidderName2 := openrtb_ext.BidderName("bidder2")
 
 	adapterBids[bidderName1] = &seatBid1
@@ -3002,8 +3003,8 @@ func TestCategoryMappingBidderNameNoCategories(t *testing.T) {
 	assert.Empty(t, rejections, "There should be 0 bid rejection messages")
 	assert.Equal(t, "10.00_30s_bidder1", bidCategory["bid_id1"], "Category mapping doesn't match")
 	assert.Equal(t, "12.00_30s_bidder2", bidCategory["bid_id2"], "Category mapping doesn't match")
-	assert.Len(t, adapterBids[bidderName1].bids, 1, "Bidders number doesn't match")
-	assert.Len(t, adapterBids[bidderName2].bids, 1, "Bidders number doesn't match")
+	assert.Len(t, adapterBids[bidderName1].Bids, 1, "Bidders number doesn't match")
+	assert.Len(t, adapterBids[bidderName2].Bids, 1, "Bidders number doesn't match")
 	assert.Len(t, bidCategory, 2, "Bidders category mapping doesn't match")
 }
 
@@ -3026,7 +3027,7 @@ func TestBidRejectionErrors(t *testing.T) {
 	invalidReqExt.Prebid.Targeting.IncludeBrandCategory.PrimaryAdServer = 2
 	invalidReqExt.Prebid.Targeting.IncludeBrandCategory.Publisher = "some_publisher"
 
-	adapterBids := make(map[openrtb_ext.BidderName]*pbsOrtbSeatBid)
+	adapterBids := make(map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid)
 	bidderName := openrtb_ext.BidderName("appnexus")
 
 	testCases := []struct {
@@ -3086,14 +3087,14 @@ func TestBidRejectionErrors(t *testing.T) {
 	}
 
 	for _, test := range testCases {
-		innerBids := []*pbsOrtbBid{}
+		innerBids := []*entities.PbsOrtbBid{}
 		for _, bid := range test.bids {
-			currentBid := pbsOrtbBid{
+			currentBid := entities.PbsOrtbBid{
 				bid, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: test.duration}, nil, 0, false, "", 10.0000, "USD"}
 			innerBids = append(innerBids, &currentBid)
 		}
 
-		seatBid := pbsOrtbSeatBid{bids: innerBids, currency: "USD"}
+		seatBid := entities.PbsOrtbSeatBid{Bids: innerBids, Currency: "USD"}
 
 		adapterBids[bidderName] = &seatBid
 
@@ -3101,11 +3102,11 @@ func TestBidRejectionErrors(t *testing.T) {
 
 		if len(test.expectedCatDur) > 0 {
 			// Bid deduplication case
-			assert.Equal(t, 1, len(adapterBids[bidderName].bids), "Bidders number doesn't match")
+			assert.Equal(t, 1, len(adapterBids[bidderName].Bids), "Bidders number doesn't match")
 			assert.Equal(t, 1, len(bidCategory), "Bidders category mapping doesn't match")
 			assert.Equal(t, test.expectedCatDur, bidCategory["bid_id1"], "Bid category did not contain expected hb_pb_cat_dur")
 		} else {
-			assert.Empty(t, adapterBids[bidderName].bids, "Bidders number doesn't match")
+			assert.Empty(t, adapterBids[bidderName].Bids, "Bidders number doesn't match")
 			assert.Empty(t, bidCategory, "Bidders category mapping doesn't match")
 		}
 
@@ -3137,24 +3138,24 @@ func TestCategoryMappingTwoBiddersOneBidEachNoCategorySamePrice(t *testing.T) {
 	bidApn1 := openrtb2.Bid{ID: "bid_idApn1", ImpID: "imp_idApn1", Price: 10.0000, Cat: cats1, W: 1, H: 1}
 	bidApn2 := openrtb2.Bid{ID: "bid_idApn2", ImpID: "imp_idApn2", Price: 10.0000, Cat: cats2, W: 1, H: 1}
 
-	bid1_Apn1 := pbsOrtbBid{&bidApn1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, "", 10.0000, "USD"}
-	bid1_Apn2 := pbsOrtbBid{&bidApn2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, "", 10.0000, "USD"}
+	bid1_Apn1 := entities.PbsOrtbBid{&bidApn1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, "", 10.0000, "USD"}
+	bid1_Apn2 := entities.PbsOrtbBid{&bidApn2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, "", 10.0000, "USD"}
 
-	innerBidsApn1 := []*pbsOrtbBid{
+	innerBidsApn1 := []*entities.PbsOrtbBid{
 		&bid1_Apn1,
 	}
 
-	innerBidsApn2 := []*pbsOrtbBid{
+	innerBidsApn2 := []*entities.PbsOrtbBid{
 		&bid1_Apn2,
 	}
 
 	for i := 1; i < 10; i++ {
-		adapterBids := make(map[openrtb_ext.BidderName]*pbsOrtbSeatBid)
+		adapterBids := make(map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid)
 
-		seatBidApn1 := pbsOrtbSeatBid{bids: innerBidsApn1, currency: "USD"}
+		seatBidApn1 := entities.PbsOrtbSeatBid{Bids: innerBidsApn1, Currency: "USD"}
 		bidderNameApn1 := openrtb_ext.BidderName("appnexus1")
 
-		seatBidApn2 := pbsOrtbSeatBid{bids: innerBidsApn2, currency: "USD"}
+		seatBidApn2 := entities.PbsOrtbSeatBid{Bids: innerBidsApn2, Currency: "USD"}
 		bidderNameApn2 := openrtb_ext.BidderName("appnexus2")
 
 		adapterBids[bidderNameApn1] = &seatBidApn1
@@ -3173,12 +3174,12 @@ func TestCategoryMappingTwoBiddersOneBidEachNoCategorySamePrice(t *testing.T) {
 		}
 
 		if resultBid == "bid_idApn1" {
-			assert.Nil(t, seatBidApn2.bids, "Appnexus_2 seat bid should not have any bids back")
-			assert.Len(t, seatBidApn1.bids, 1, "Appnexus_1 seat bid should have only one back")
+			assert.Nil(t, seatBidApn2.Bids, "Appnexus_2 seat bid should not have any bids back")
+			assert.Len(t, seatBidApn1.Bids, 1, "Appnexus_1 seat bid should have only one back")
 
 		} else {
-			assert.Nil(t, seatBidApn1.bids, "Appnexus_1 seat bid should not have any bids back")
-			assert.Len(t, seatBidApn2.bids, 1, "Appnexus_2 seat bid should have only one back")
+			assert.Nil(t, seatBidApn1.Bids, "Appnexus_1 seat bid should not have any bids back")
+			assert.Len(t, seatBidApn2.Bids, 1, "Appnexus_2 seat bid should have only one back")
 		}
 	}
 }
@@ -3217,28 +3218,28 @@ func TestCategoryMappingTwoBiddersManyBidsEachNoCategorySamePrice(t *testing.T)
 	bidApn2_1 := openrtb2.Bid{ID: "bid_idApn2_1", ImpID: "imp_idApn2_1", Price: 10.0000, Cat: cats2, W: 1, H: 1}
 	bidApn2_2 := openrtb2.Bid{ID: "bid_idApn2_2", ImpID: "imp_idApn2_2", Price: 20.0000, Cat: cats2, W: 1, H: 1}
 
-	bid1_Apn1_1 := pbsOrtbBid{&bidApn1_1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, "", 10.0000, "USD"}
-	bid1_Apn1_2 := pbsOrtbBid{&bidApn1_2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, "", 20.0000, "USD"}
+	bid1_Apn1_1 := entities.PbsOrtbBid{&bidApn1_1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, "", 10.0000, "USD"}
+	bid1_Apn1_2 := entities.PbsOrtbBid{&bidApn1_2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, "", 20.0000, "USD"}
 
-	bid1_Apn2_1 := pbsOrtbBid{&bidApn2_1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, "", 10.0000, "USD"}
-	bid1_Apn2_2 := pbsOrtbBid{&bidApn2_2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, "", 20.0000, "USD"}
+	bid1_Apn2_1 := entities.PbsOrtbBid{&bidApn2_1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, "", 10.0000, "USD"}
+	bid1_Apn2_2 := entities.PbsOrtbBid{&bidApn2_2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, "", 20.0000, "USD"}
 
-	innerBidsApn1 := []*pbsOrtbBid{
+	innerBidsApn1 := []*entities.PbsOrtbBid{
 		&bid1_Apn1_1,
 		&bid1_Apn1_2,
 	}
 
-	innerBidsApn2 := []*pbsOrtbBid{
+	innerBidsApn2 := []*entities.PbsOrtbBid{
 		&bid1_Apn2_1,
 		&bid1_Apn2_2,
 	}
 
-	adapterBids := make(map[openrtb_ext.BidderName]*pbsOrtbSeatBid)
+	adapterBids := make(map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid)
 
-	seatBidApn1 := pbsOrtbSeatBid{bids: innerBidsApn1, currency: "USD"}
+	seatBidApn1 := entities.PbsOrtbSeatBid{Bids: innerBidsApn1, Currency: "USD"}
 	bidderNameApn1 := openrtb_ext.BidderName("appnexus1")
 
-	seatBidApn2 := pbsOrtbSeatBid{bids: innerBidsApn2, currency: "USD"}
+	seatBidApn2 := entities.PbsOrtbSeatBid{Bids: innerBidsApn2, Currency: "USD"}
 	bidderNameApn2 := openrtb_ext.BidderName("appnexus2")
 
 	adapterBids[bidderNameApn1] = &seatBidApn1
@@ -3257,34 +3258,34 @@ func TestCategoryMappingTwoBiddersManyBidsEachNoCategorySamePrice(t *testing.T)
 	//due to random map order we need to identify what bidder was first
 	firstBidderIndicator := true
 
-	if bidsFromFirstBidder.bids != nil {
-		totalNumberOfbids += len(bidsFromFirstBidder.bids)
+	if bidsFromFirstBidder.Bids != nil {
+		totalNumberOfbids += len(bidsFromFirstBidder.Bids)
 	}
 
-	if bidsFromSecondBidder.bids != nil {
+	if bidsFromSecondBidder.Bids != nil {
 		firstBidderIndicator = false
-		totalNumberOfbids += len(bidsFromSecondBidder.bids)
+		totalNumberOfbids += len(bidsFromSecondBidder.Bids)
 	}
 
 	assert.Equal(t, 2, totalNumberOfbids, "2 bids total should be returned")
 	assert.Len(t, rejections, 2, "2 bids should be de-duplicated")
 
 	if firstBidderIndicator {
-		assert.Len(t, adapterBids[bidderNameApn1].bids, 2)
-		assert.Len(t, adapterBids[bidderNameApn2].bids, 0)
+		assert.Len(t, adapterBids[bidderNameApn1].Bids, 2)
+		assert.Len(t, adapterBids[bidderNameApn2].Bids, 0)
 
-		assert.Equal(t, "bid_idApn1_1", adapterBids[bidderNameApn1].bids[0].bid.ID, "Incorrect expected bid 1 id")
-		assert.Equal(t, "bid_idApn1_2", adapterBids[bidderNameApn1].bids[1].bid.ID, "Incorrect expected bid 2 id")
+		assert.Equal(t, "bid_idApn1_1", adapterBids[bidderNameApn1].Bids[0].Bid.ID, "Incorrect expected bid 1 id")
+		assert.Equal(t, "bid_idApn1_2", adapterBids[bidderNameApn1].Bids[1].Bid.ID, "Incorrect expected bid 2 id")
 
 		assert.Equal(t, "bid rejected [bid ID: bid_idApn2_1] reason: Bid was deduplicated", rejections[0], "Incorrect rejected bid 1")
 		assert.Equal(t, "bid rejected [bid ID: bid_idApn2_2] reason: Bid was deduplicated", rejections[1], "Incorrect rejected bid 2")
 
 	} else {
-		assert.Len(t, adapterBids[bidderNameApn1].bids, 0)
-		assert.Len(t, adapterBids[bidderNameApn2].bids, 2)
+		assert.Len(t, adapterBids[bidderNameApn1].Bids, 0)
+		assert.Len(t, adapterBids[bidderNameApn2].Bids, 2)
 
-		assert.Equal(t, "bid_idApn2_1", adapterBids[bidderNameApn2].bids[0].bid.ID, "Incorrect expected bid 1 id")
-		assert.Equal(t, "bid_idApn2_2", adapterBids[bidderNameApn2].bids[1].bid.ID, "Incorrect expected bid 2 id")
+		assert.Equal(t, "bid_idApn2_1", adapterBids[bidderNameApn2].Bids[0].Bid.ID, "Incorrect expected bid 1 id")
+		assert.Equal(t, "bid_idApn2_2", adapterBids[bidderNameApn2].Bids[1].Bid.ID, "Incorrect expected bid 2 id")
 
 		assert.Equal(t, "bid rejected [bid ID: bid_idApn1_1] reason: Bid was deduplicated", rejections[0], "Incorrect rejected bid 1")
 		assert.Equal(t, "bid rejected [bid ID: bid_idApn1_2] reason: Bid was deduplicated", rejections[1], "Incorrect rejected bid 2")
@@ -3299,50 +3300,50 @@ func TestRemoveBidById(t *testing.T) {
 	bidApn1_2 := openrtb2.Bid{ID: "bid_idApn1_2", ImpID: "imp_idApn1_2", Price: 20.0000, Cat: cats1, W: 1, H: 1}
 	bidApn1_3 := openrtb2.Bid{ID: "bid_idApn1_3", ImpID: "imp_idApn1_3", Price: 10.0000, Cat: cats1, W: 1, H: 1}
 
-	bid1_Apn1_1 := pbsOrtbBid{&bidApn1_1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, "", 10.0000, "USD"}
-	bid1_Apn1_2 := pbsOrtbBid{&bidApn1_2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, "", 20.0000, "USD"}
-	bid1_Apn1_3 := pbsOrtbBid{&bidApn1_3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, "", 10.0000, "USD"}
+	bid1_Apn1_1 := entities.PbsOrtbBid{&bidApn1_1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, "", 10.0000, "USD"}
+	bid1_Apn1_2 := entities.PbsOrtbBid{&bidApn1_2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, "", 20.0000, "USD"}
+	bid1_Apn1_3 := entities.PbsOrtbBid{&bidApn1_3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, "", 10.0000, "USD"}
 
 	type aTest struct {
 		desc      string
 		inBidName string
-		outBids   []*pbsOrtbBid
+		outBids   []*entities.PbsOrtbBid
 	}
 	testCases := []aTest{
 		{
 			desc:      "remove element from the middle",
 			inBidName: "bid_idApn1_2",
-			outBids:   []*pbsOrtbBid{&bid1_Apn1_1, &bid1_Apn1_3},
+			outBids:   []*entities.PbsOrtbBid{&bid1_Apn1_1, &bid1_Apn1_3},
 		},
 		{
 			desc:      "remove element from the end",
 			inBidName: "bid_idApn1_3",
-			outBids:   []*pbsOrtbBid{&bid1_Apn1_1, &bid1_Apn1_2},
+			outBids:   []*entities.PbsOrtbBid{&bid1_Apn1_1, &bid1_Apn1_2},
 		},
 		{
 			desc:      "remove element from the beginning",
 			inBidName: "bid_idApn1_1",
-			outBids:   []*pbsOrtbBid{&bid1_Apn1_2, &bid1_Apn1_3},
+			outBids:   []*entities.PbsOrtbBid{&bid1_Apn1_2, &bid1_Apn1_3},
 		},
 		{
 			desc:      "remove element that doesn't exist",
 			inBidName: "bid_idApn",
-			outBids:   []*pbsOrtbBid{&bid1_Apn1_1, &bid1_Apn1_2, &bid1_Apn1_3},
+			outBids:   []*entities.PbsOrtbBid{&bid1_Apn1_1, &bid1_Apn1_2, &bid1_Apn1_3},
 		},
 	}
 	for _, test := range testCases {
 
-		innerBidsApn1 := []*pbsOrtbBid{
+		innerBidsApn1 := []*entities.PbsOrtbBid{
 			&bid1_Apn1_1,
 			&bid1_Apn1_2,
 			&bid1_Apn1_3,
 		}
 
-		seatBidApn1 := &pbsOrtbSeatBid{bids: innerBidsApn1, currency: "USD"}
+		seatBidApn1 := &entities.PbsOrtbSeatBid{Bids: innerBidsApn1, Currency: "USD"}
 
 		removeBidById(seatBidApn1, test.inBidName)
-		assert.Len(t, seatBidApn1.bids, len(test.outBids), test.desc)
-		assert.ElementsMatch(t, seatBidApn1.bids, test.outBids, "Incorrect bids in response")
+		assert.Len(t, seatBidApn1.Bids, len(test.outBids), test.desc)
+		assert.ElementsMatch(t, seatBidApn1.Bids, test.outBids, "Incorrect bids in response")
 	}
 
 }
@@ -3426,13 +3427,13 @@ func TestApplyDealSupport(t *testing.T) {
 			},
 		}
 
-		bid := pbsOrtbBid{&openrtb2.Bid{ID: "123456"}, nil, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, test.dealPriority, false, "", 0, "USD"}
+		bid := entities.PbsOrtbBid{&openrtb2.Bid{ID: "123456"}, nil, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, test.dealPriority, false, "", 0, "USD"}
 		bidCategory := map[string]string{
-			bid.bid.ID: test.targ["hb_pb_cat_dur"],
+			bid.Bid.ID: test.targ["hb_pb_cat_dur"],
 		}
 
 		auc := &auction{
-			winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*pbsOrtbBid{
+			winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*entities.PbsOrtbBid{
 				"imp_id1": {
 					bidderName: &bid,
 				},
@@ -3441,8 +3442,8 @@ func TestApplyDealSupport(t *testing.T) {
 
 		dealErrs := applyDealSupport(bidRequest, auc, bidCategory)
 
-		assert.Equal(t, test.expectedHbPbCatDur, bidCategory[auc.winningBidsByBidder["imp_id1"][bidderName].bid.ID], test.description)
-		assert.Equal(t, test.expectedDealTierSatisfied, auc.winningBidsByBidder["imp_id1"][bidderName].dealTierSatisfied, "expectedDealTierSatisfied=%v when %v", test.expectedDealTierSatisfied, test.description)
+		assert.Equal(t, test.expectedHbPbCatDur, bidCategory[auc.winningBidsByBidder["imp_id1"][bidderName].Bid.ID], test.description)
+		assert.Equal(t, test.expectedDealTierSatisfied, auc.winningBidsByBidder["imp_id1"][bidderName].DealTierSatisfied, "expectedDealTierSatisfied=%v when %v", test.expectedDealTierSatisfied, test.description)
 		if len(test.expectedDealErr) > 0 {
 			assert.Containsf(t, dealErrs, errors.New(test.expectedDealErr), "Expected error message not found in deal errors")
 		}
@@ -3593,15 +3594,15 @@ func TestUpdateHbPbCatDur(t *testing.T) {
 	}
 
 	for _, test := range testCases {
-		bid := pbsOrtbBid{&openrtb2.Bid{ID: "123456"}, nil, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, test.dealPriority, false, "", 0, "USD"}
+		bid := entities.PbsOrtbBid{&openrtb2.Bid{ID: "123456"}, nil, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, test.dealPriority, false, "", 0, "USD"}
 		bidCategory := map[string]string{
-			bid.bid.ID: test.targ["hb_pb_cat_dur"],
+			bid.Bid.ID: test.targ["hb_pb_cat_dur"],
 		}
 
 		updateHbPbCatDur(&bid, test.dealTier, bidCategory)
 
-		assert.Equal(t, test.expectedHbPbCatDur, bidCategory[bid.bid.ID], test.description)
-		assert.Equal(t, test.expectedDealTierSatisfied, bid.dealTierSatisfied, test.description)
+		assert.Equal(t, test.expectedHbPbCatDur, bidCategory[bid.Bid.ID], test.description)
+		assert.Equal(t, test.expectedDealTierSatisfied, bid.DealTierSatisfied, test.description)
 	}
 }
 
@@ -3898,7 +3899,7 @@ func TestBuildStoredAuctionResponses(t *testing.T) {
 		StoredAuctionResponses map[string]json.RawMessage
 	}
 	type testResults struct {
-		adapterBids  map[openrtb_ext.BidderName]*pbsOrtbSeatBid
+		adapterBids  map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid
 		liveAdapters []openrtb_ext.BidderName
 	}
 
@@ -3915,11 +3916,11 @@ func TestBuildStoredAuctionResponses(t *testing.T) {
 				},
 			},
 			expected: testResults{
-				adapterBids: map[openrtb_ext.BidderName]*pbsOrtbSeatBid{
+				adapterBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{
 					openrtb_ext.BidderName("appnexus"): {
-						bids: []*pbsOrtbBid{
+						Bids: []*entities.PbsOrtbBid{
 							{
-								bid: &openrtb2.Bid{ID: "bid_id", ImpID: "impression-id"},
+								Bid: &openrtb2.Bid{ID: "bid_id", ImpID: "impression-id"},
 							},
 						},
 					},
@@ -3935,11 +3936,11 @@ func TestBuildStoredAuctionResponses(t *testing.T) {
 				},
 			},
 			expected: testResults{
-				adapterBids: map[openrtb_ext.BidderName]*pbsOrtbSeatBid{
+				adapterBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{
 					openrtb_ext.BidderName("appnexus"): {
-						bids: []*pbsOrtbBid{
-							{bid: &openrtb2.Bid{ID: "bid_id1", ImpID: "impression-id"}},
-							{bid: &openrtb2.Bid{ID: "bid_id2", ImpID: "impression-id"}},
+						Bids: []*entities.PbsOrtbBid{
+							{Bid: &openrtb2.Bid{ID: "bid_id1", ImpID: "impression-id"}},
+							{Bid: &openrtb2.Bid{ID: "bid_id2", ImpID: "impression-id"}},
 						},
 					},
 				},
@@ -3954,17 +3955,17 @@ func TestBuildStoredAuctionResponses(t *testing.T) {
 				},
 			},
 			expected: testResults{
-				adapterBids: map[openrtb_ext.BidderName]*pbsOrtbSeatBid{
+				adapterBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{
 					openrtb_ext.BidderName("appnexus"): {
-						bids: []*pbsOrtbBid{
-							{bid: &openrtb2.Bid{ID: "apn_id1", ImpID: "impression-id"}},
-							{bid: &openrtb2.Bid{ID: "apn_id2", ImpID: "impression-id"}},
+						Bids: []*entities.PbsOrtbBid{
+							{Bid: &openrtb2.Bid{ID: "apn_id1", ImpID: "impression-id"}},
+							{Bid: &openrtb2.Bid{ID: "apn_id2", ImpID: "impression-id"}},
 						},
 					},
 					openrtb_ext.BidderName("rubicon"): {
-						bids: []*pbsOrtbBid{
-							{bid: &openrtb2.Bid{ID: "rubicon_id1", ImpID: "impression-id"}},
-							{bid: &openrtb2.Bid{ID: "rubicon_id2", ImpID: "impression-id"}},
+						Bids: []*entities.PbsOrtbBid{
+							{Bid: &openrtb2.Bid{ID: "rubicon_id1", ImpID: "impression-id"}},
+							{Bid: &openrtb2.Bid{ID: "rubicon_id2", ImpID: "impression-id"}},
 						},
 					},
 				},
@@ -3980,19 +3981,19 @@ func TestBuildStoredAuctionResponses(t *testing.T) {
 				},
 			},
 			expected: testResults{
-				adapterBids: map[openrtb_ext.BidderName]*pbsOrtbSeatBid{
+				adapterBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{
 					openrtb_ext.BidderName("appnexus"): {
-						bids: []*pbsOrtbBid{
-							{bid: &openrtb2.Bid{ID: "apn_id1", ImpID: "impression-id1"}},
-							{bid: &openrtb2.Bid{ID: "apn_id2", ImpID: "impression-id1"}},
-							{bid: &openrtb2.Bid{ID: "apn_id1", ImpID: "impression-id2"}},
-							{bid: &openrtb2.Bid{ID: "apn_id2", ImpID: "impression-id2"}},
+						Bids: []*entities.PbsOrtbBid{
+							{Bid: &openrtb2.Bid{ID: "apn_id1", ImpID: "impression-id1"}},
+							{Bid: &openrtb2.Bid{ID: "apn_id2", ImpID: "impression-id1"}},
+							{Bid: &openrtb2.Bid{ID: "apn_id1", ImpID: "impression-id2"}},
+							{Bid: &openrtb2.Bid{ID: "apn_id2", ImpID: "impression-id2"}},
 						},
 					},
 					openrtb_ext.BidderName("rubicon"): {
-						bids: []*pbsOrtbBid{
-							{bid: &openrtb2.Bid{ID: "rubicon_id1", ImpID: "impression-id2"}},
-							{bid: &openrtb2.Bid{ID: "rubicon_id2", ImpID: "impression-id2"}},
+						Bids: []*entities.PbsOrtbBid{
+							{Bid: &openrtb2.Bid{ID: "rubicon_id1", ImpID: "impression-id2"}},
+							{Bid: &openrtb2.Bid{ID: "rubicon_id2", ImpID: "impression-id2"}},
 						},
 					},
 				},
@@ -4008,21 +4009,21 @@ func TestBuildStoredAuctionResponses(t *testing.T) {
 				},
 			},
 			expected: testResults{
-				adapterBids: map[openrtb_ext.BidderName]*pbsOrtbSeatBid{
+				adapterBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{
 					openrtb_ext.BidderName("appnexus"): {
-						bids: []*pbsOrtbBid{
-							{bid: &openrtb2.Bid{ID: "apn_id1", ImpID: "impression-id1"}},
-							{bid: &openrtb2.Bid{ID: "apn_id2", ImpID: "impression-id1"}},
-							{bid: &openrtb2.Bid{ID: "apn_id1", ImpID: "impression-id2"}},
-							{bid: &openrtb2.Bid{ID: "apn_id2", ImpID: "impression-id2"}},
+						Bids: []*entities.PbsOrtbBid{
+							{Bid: &openrtb2.Bid{ID: "apn_id1", ImpID: "impression-id1"}},
+							{Bid: &openrtb2.Bid{ID: "apn_id2", ImpID: "impression-id1"}},
+							{Bid: &openrtb2.Bid{ID: "apn_id1", ImpID: "impression-id2"}},
+							{Bid: &openrtb2.Bid{ID: "apn_id2", ImpID: "impression-id2"}},
 						},
 					},
 					openrtb_ext.BidderName("rubicon"): {
-						bids: []*pbsOrtbBid{
-							{bid: &openrtb2.Bid{ID: "rubicon_id1", ImpID: "impression-id1"}},
-							{bid: &openrtb2.Bid{ID: "rubicon_id2", ImpID: "impression-id1"}},
-							{bid: &openrtb2.Bid{ID: "rubicon_id1", ImpID: "impression-id2"}},
-							{bid: &openrtb2.Bid{ID: "rubicon_id2", ImpID: "impression-id2"}},
+						Bids: []*entities.PbsOrtbBid{
+							{Bid: &openrtb2.Bid{ID: "rubicon_id1", ImpID: "impression-id1"}},
+							{Bid: &openrtb2.Bid{ID: "rubicon_id2", ImpID: "impression-id1"}},
+							{Bid: &openrtb2.Bid{ID: "rubicon_id1", ImpID: "impression-id2"}},
+							{Bid: &openrtb2.Bid{ID: "rubicon_id2", ImpID: "impression-id2"}},
 						},
 					},
 				},
@@ -4038,7 +4039,7 @@ func TestBuildStoredAuctionResponses(t *testing.T) {
 		assert.ElementsMatch(t, test.expected.liveAdapters, adapters, "Incorrect adapter list")
 
 		for _, bidderName := range test.expected.liveAdapters {
-			assert.ElementsMatch(t, test.expected.adapterBids[bidderName].bids, bids[bidderName].bids, "Incorrect bids")
+			assert.ElementsMatch(t, test.expected.adapterBids[bidderName].Bids, bids[bidderName].Bids, "Incorrect bids")
 		}
 
 	}
@@ -4445,7 +4446,7 @@ type bidderResponse struct {
 	HttpCalls []*openrtb_ext.ExtHttpCall `json:"httpCalls,omitempty"`
 }
 
-// bidderSeatBid is basically a subset of pbsOrtbSeatBid from exchange/bidder.go.
+// bidderSeatBid is basically a subset of entities.PbsOrtbSeatBid from exchange/bidder.go.
 // The only real reason I'm not reusing that type is because I don't want people to think that the
 // JSON property tags on those types are contracts in prod.
 type bidderSeatBid struct {
@@ -4453,7 +4454,7 @@ type bidderSeatBid struct {
 	Seat string      `json:"seat"`
 }
 
-// bidderBid is basically a subset of pbsOrtbBid from exchange/bidder.go.
+// bidderBid is basically a subset of entities.PbsOrtbBid from exchange/bidder.go.
 // See the comment on bidderSeatBid for more info.
 type bidderBid struct {
 	Bid  *openrtb2.Bid                 `json:"ortbBid,omitempty"`
@@ -4482,7 +4483,7 @@ type validatingBidder struct {
 	mockResponses map[string]bidderResponse
 }
 
-func (b *validatingBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, executor hookexecution.StageExecutor) (seatBids []*pbsOrtbSeatBid, errs []error) {
+func (b *validatingBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, executor hookexecution.StageExecutor) (seatBids []*entities.PbsOrtbSeatBid, errs []error) {
 	if expectedRequest, ok := b.expectations[string(bidderRequest.BidderName)]; ok {
 		if expectedRequest != nil {
 			if !reflect.DeepEqual(expectedRequest.BidAdjustments, bidRequestOptions.bidAdjustments) {
@@ -4497,31 +4498,31 @@ func (b *validatingBidder) requestBid(ctx context.Context, bidderRequest BidderR
 	if mockResponse, ok := b.mockResponses[string(bidderRequest.BidderName)]; ok {
 		if len(mockResponse.SeatBids) != 0 {
 			for _, mockSeatBid := range mockResponse.SeatBids {
-				var bids []*pbsOrtbBid
+				var bids []*entities.PbsOrtbBid
 
 				if len(mockSeatBid.Bids) != 0 {
-					bids = make([]*pbsOrtbBid, len(mockSeatBid.Bids))
+					bids = make([]*entities.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,
+						bids[i] = &entities.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,
 						}
 					}
 				}
 
-				seatBids = append(seatBids, &pbsOrtbSeatBid{
-					bids:      bids,
-					httpCalls: mockResponse.HttpCalls,
-					seat:      mockSeatBid.Seat,
+				seatBids = append(seatBids, &entities.PbsOrtbSeatBid{
+					Bids:      bids,
+					HttpCalls: mockResponse.HttpCalls,
+					Seat:      mockSeatBid.Seat,
 				})
 			}
 		} else {
-			seatBids = []*pbsOrtbSeatBid{{
-				bids:      nil,
-				httpCalls: mockResponse.HttpCalls,
-				seat:      string(bidderRequest.BidderName),
+			seatBids = []*entities.PbsOrtbSeatBid{{
+				Bids:      nil,
+				HttpCalls: mockResponse.HttpCalls,
+				Seat:      string(bidderRequest.BidderName),
 			}}
 		}
 
@@ -4539,9 +4540,9 @@ type capturingRequestBidder struct {
 	req *openrtb2.BidRequest
 }
 
-func (b *capturingRequestBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestMetadata bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, executor hookexecution.StageExecutor) (seatBid []*pbsOrtbSeatBid, errs []error) {
+func (b *capturingRequestBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, executor hookexecution.StageExecutor) (seatBid []*entities.PbsOrtbSeatBid, errs []error) {
 	b.req = bidderRequest.BidRequest
-	return []*pbsOrtbSeatBid{{}}, nil
+	return []*entities.PbsOrtbSeatBid{{}}, nil
 }
 
 func diffOrtbRequests(t *testing.T, description string, expected *openrtb2.BidRequest, actual *openrtb2.BidRequest) {
@@ -4645,7 +4646,7 @@ func (e *emptyUsersync) HasAnyLiveSyncs() bool {
 
 type panicingAdapter struct{}
 
-func (panicingAdapter) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestMetadata bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, executor hookexecution.StageExecutor) (posb []*pbsOrtbSeatBid, errs []error) {
+func (panicingAdapter) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestMetadata bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, executor hookexecution.StageExecutor) (posb []*entities.PbsOrtbSeatBid, errs []error) {
 	panic("Panic! Panic! The world is ending!")
 }
 
diff --git a/exchange/targeting_test.go b/exchange/targeting_test.go
index ff30b325..dc91177a 100644
--- a/exchange/targeting_test.go
+++ b/exchange/targeting_test.go
@@ -11,6 +11,7 @@ import (
 	"github.com/prebid/prebid-server/adapters"
 	"github.com/prebid/prebid-server/config"
 	"github.com/prebid/prebid-server/currency"
+	"github.com/prebid/prebid-server/exchange/entities"
 	"github.com/prebid/prebid-server/gdpr"
 	"github.com/prebid/prebid-server/hooks/hookexecution"
 	metricsConfig "github.com/prebid/prebid-server/metrics/config"
@@ -292,15 +293,15 @@ var TargetingTests []TargetingTestData = []TargetingTestData{
 			includeWinners:   true,
 		},
 		Auction: auction{
-			winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*pbsOrtbBid{
+			winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*entities.PbsOrtbBid{
 				"ImpId-1": {
 					openrtb_ext.BidderAppnexus: {
-						bid:     bid123,
-						bidType: openrtb_ext.BidTypeBanner,
+						Bid:     bid123,
+						BidType: openrtb_ext.BidTypeBanner,
 					},
 					openrtb_ext.BidderRubicon: {
-						bid:     bid084,
-						bidType: openrtb_ext.BidTypeBanner,
+						Bid:     bid084,
+						BidType: openrtb_ext.BidTypeBanner,
 					},
 				},
 			},
@@ -323,15 +324,15 @@ var TargetingTests []TargetingTestData = []TargetingTestData{
 			includeBidderKeys: true,
 		},
 		Auction: auction{
-			winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*pbsOrtbBid{
+			winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*entities.PbsOrtbBid{
 				"ImpId-1": {
 					openrtb_ext.BidderAppnexus: {
-						bid:     bid123,
-						bidType: openrtb_ext.BidTypeBanner,
+						Bid:     bid123,
+						BidType: openrtb_ext.BidTypeBanner,
 					},
 					openrtb_ext.BidderRubicon: {
-						bid:     bid084,
-						bidType: openrtb_ext.BidTypeBanner,
+						Bid:     bid084,
+						BidType: openrtb_ext.BidTypeBanner,
 					},
 				},
 			},
@@ -359,15 +360,15 @@ var TargetingTests []TargetingTestData = []TargetingTestData{
 			includeFormat:     true,
 		},
 		Auction: auction{
-			winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*pbsOrtbBid{
+			winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*entities.PbsOrtbBid{
 				"ImpId-1": {
 					openrtb_ext.BidderAppnexus: {
-						bid:     bid123,
-						bidType: openrtb_ext.BidTypeBanner,
+						Bid:     bid123,
+						BidType: openrtb_ext.BidTypeBanner,
 					},
 					openrtb_ext.BidderRubicon: {
-						bid:     bid084,
-						bidType: openrtb_ext.BidTypeBanner,
+						Bid:     bid084,
+						BidType: openrtb_ext.BidTypeBanner,
 					},
 				},
 			},
@@ -400,15 +401,15 @@ var TargetingTests []TargetingTestData = []TargetingTestData{
 			cachePath:         "cache",
 		},
 		Auction: auction{
-			winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*pbsOrtbBid{
+			winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*entities.PbsOrtbBid{
 				"ImpId-1": {
 					openrtb_ext.BidderAppnexus: {
-						bid:     bid123,
-						bidType: openrtb_ext.BidTypeBanner,
+						Bid:     bid123,
+						BidType: openrtb_ext.BidTypeBanner,
 					},
 					openrtb_ext.BidderRubicon: {
-						bid:     bid111,
-						bidType: openrtb_ext.BidTypeBanner,
+						Bid:     bid111,
+						BidType: openrtb_ext.BidTypeBanner,
 					},
 				},
 			},
@@ -445,15 +446,15 @@ var TargetingTests []TargetingTestData = []TargetingTestData{
 			includeBidderKeys: true,
 		},
 		Auction: auction{
-			winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*pbsOrtbBid{
+			winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*entities.PbsOrtbBid{
 				"ImpId-1": {
 					openrtb_ext.BidderAppnexus: {
-						bid:     bid123,
-						bidType: openrtb_ext.BidTypeBanner,
+						Bid:     bid123,
+						BidType: openrtb_ext.BidTypeBanner,
 					},
 					openrtb_ext.BidderRubicon: {
-						bid:     bid084,
-						bidType: openrtb_ext.BidTypeBanner,
+						Bid:     bid084,
+						BidType: openrtb_ext.BidTypeBanner,
 					},
 				},
 			},
@@ -479,15 +480,15 @@ var TargetingTests []TargetingTestData = []TargetingTestData{
 			includeBidderKeys: true,
 		},
 		Auction: auction{
-			winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*pbsOrtbBid{
+			winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*entities.PbsOrtbBid{
 				"ImpId-1": {
 					openrtb_ext.BidderAppnexus: {
-						bid:     bid123,
-						bidType: openrtb_ext.BidTypeBanner,
+						Bid:     bid123,
+						BidType: openrtb_ext.BidTypeBanner,
 					},
 					openrtb_ext.BidderRubicon: {
-						bid:     bid084,
-						bidType: openrtb_ext.BidTypeBanner,
+						Bid:     bid084,
+						BidType: openrtb_ext.BidTypeBanner,
 					},
 				},
 			},
@@ -513,15 +514,15 @@ var TargetingTests []TargetingTestData = []TargetingTestData{
 			includeBidderKeys: true,
 		},
 		Auction: auction{
-			winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*pbsOrtbBid{
+			winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*entities.PbsOrtbBid{
 				"ImpId-1": {
 					openrtb_ext.BidderAppnexus: {
-						bid:     bid123,
-						bidType: openrtb_ext.BidTypeBanner,
+						Bid:     bid123,
+						BidType: openrtb_ext.BidTypeBanner,
 					},
 					openrtb_ext.BidderRubicon: {
-						bid:     bid084,
-						bidType: openrtb_ext.BidTypeBanner,
+						Bid:     bid084,
+						BidType: openrtb_ext.BidTypeBanner,
 					},
 				},
 			},
@@ -547,15 +548,15 @@ var TargetingTests []TargetingTestData = []TargetingTestData{
 			includeWinners:   true,
 		},
 		Auction: auction{
-			winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*pbsOrtbBid{
+			winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*entities.PbsOrtbBid{
 				"ImpId-1": {
 					openrtb_ext.BidderAppnexus: {
-						bid:     bid123,
-						bidType: openrtb_ext.BidTypeBanner,
+						Bid:     bid123,
+						BidType: openrtb_ext.BidTypeBanner,
 					},
 					openrtb_ext.BidderRubicon: {
-						bid:     bid084,
-						bidType: openrtb_ext.BidTypeBanner,
+						Bid:     bid084,
+						BidType: openrtb_ext.BidTypeBanner,
 					},
 				},
 			},
@@ -578,15 +579,15 @@ var TargetingTests []TargetingTestData = []TargetingTestData{
 			includeWinners:   true,
 		},
 		Auction: auction{
-			winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*pbsOrtbBid{
+			winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*entities.PbsOrtbBid{
 				"ImpId-1": {
 					openrtb_ext.BidderAppnexus: {
-						bid:     bid123,
-						bidType: openrtb_ext.BidTypeBanner,
+						Bid:     bid123,
+						BidType: openrtb_ext.BidTypeBanner,
 					},
 					openrtb_ext.BidderRubicon: {
-						bid:     bid084,
-						bidType: openrtb_ext.BidTypeBanner,
+						Bid:     bid084,
+						BidType: openrtb_ext.BidTypeBanner,
 					},
 				},
 			},
@@ -609,15 +610,15 @@ var TargetingTests []TargetingTestData = []TargetingTestData{
 			includeWinners:   true,
 		},
 		Auction: auction{
-			winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*pbsOrtbBid{
+			winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*entities.PbsOrtbBid{
 				"ImpId-1": {
 					openrtb_ext.BidderAppnexus: {
-						bid:     bid123,
-						bidType: openrtb_ext.BidTypeBanner,
+						Bid:     bid123,
+						BidType: openrtb_ext.BidTypeBanner,
 					},
 					openrtb_ext.BidderRubicon: {
-						bid:     bid084,
-						bidType: openrtb_ext.BidTypeBanner,
+						Bid:     bid084,
+						BidType: openrtb_ext.BidTypeBanner,
 					},
 				},
 			},
@@ -640,12 +641,12 @@ func TestSetTargeting(t *testing.T) {
 		auc := &test.Auction
 		// Set rounded prices from the auction data
 		auc.setRoundedPrices(test.TargetData.priceGranularity)
-		winningBids := make(map[string]*pbsOrtbBid)
+		winningBids := make(map[string]*entities.PbsOrtbBid)
 		// Set winning bids from the auction data
 		for imp, bidsByBidder := range auc.winningBidsByBidder {
 			for _, bid := range bidsByBidder {
 				if winningBid, ok := winningBids[imp]; ok {
-					if winningBid.bid.Price < bid.bid.Price {
+					if winningBid.Bid.Price < bid.Bid.Price {
 						winningBids[imp] = bid
 					}
 				} else {
@@ -660,7 +661,7 @@ func TestSetTargeting(t *testing.T) {
 			for bidder, expected := range targetsByBidder {
 				assert.Equal(t,
 					expected,
-					auc.winningBidsByBidder[imp][bidder].bidTargets,
+					auc.winningBidsByBidder[imp][bidder].BidTargets,
 					"Test: %s\nTargeting failed for bidder %s on imp %s.",
 					test.Description,
 					string(bidder),
diff --git a/hooks/hookexecution/executor_test.go b/hooks/hookexecution/executor_test.go
index 8b652bc1..c279d828 100644
--- a/hooks/hookexecution/executor_test.go
+++ b/hooks/hookexecution/executor_test.go
@@ -11,11 +11,13 @@ import (
 	"github.com/prebid/openrtb/v17/openrtb2"
 	"github.com/prebid/prebid-server/adapters"
 	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/exchange/entities"
 	"github.com/prebid/prebid-server/hooks"
 	"github.com/prebid/prebid-server/hooks/hookanalytics"
 	"github.com/prebid/prebid-server/hooks/hookstage"
 	"github.com/prebid/prebid-server/metrics"
 	metricsConfig "github.com/prebid/prebid-server/metrics/config"
+	"github.com/prebid/prebid-server/openrtb_ext"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/mock"
 )
@@ -1404,6 +1406,284 @@ func TestExecuteRawBidderResponseStage(t *testing.T) {
 	}
 }
 
+func TestExecuteAllProcessedBidResponsesStage(t *testing.T) {
+	foobarModuleCtx := &moduleContexts{ctxs: map[string]hookstage.ModuleContext{"foobar": nil}}
+	account := &config.Account{}
+
+	expectedAllProcBidResponses := map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{
+		"some-bidder": {Bids: []*entities.PbsOrtbBid{{DealPriority: 1}}},
+	}
+	expectedUpdatedAllProcBidResponses := map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{
+		"some-bidder": {Bids: []*entities.PbsOrtbBid{{DealPriority: 10}}},
+	}
+
+	testCases := []struct {
+		description             string
+		givenBiddersResponse    map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid
+		givenPlanBuilder        hooks.ExecutionPlanBuilder
+		givenAccount            *config.Account
+		expectedBiddersResponse map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid
+		expectedReject          *RejectError
+		expectedModuleContexts  *moduleContexts
+		expectedStageOutcomes   []StageOutcome
+	}{
+		{
+			description: "Payload not changed if hook execution plan empty",
+			givenBiddersResponse: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{
+				"some-bidder": {Bids: []*entities.PbsOrtbBid{{DealPriority: 1}}},
+			},
+			givenPlanBuilder:        hooks.EmptyPlanBuilder{},
+			givenAccount:            account,
+			expectedBiddersResponse: expectedAllProcBidResponses,
+			expectedReject:          nil,
+			expectedModuleContexts:  &moduleContexts{ctxs: map[string]hookstage.ModuleContext{}},
+			expectedStageOutcomes:   []StageOutcome{},
+		},
+		{
+			description: "Payload changed if hooks return mutations",
+			givenBiddersResponse: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{
+				"some-bidder": {Bids: []*entities.PbsOrtbBid{{DealPriority: 1}}},
+			},
+			givenPlanBuilder:        TestApplyHookMutationsBuilder{},
+			givenAccount:            account,
+			expectedBiddersResponse: expectedUpdatedAllProcBidResponses,
+			expectedReject:          nil,
+			expectedModuleContexts:  foobarModuleCtx,
+			expectedStageOutcomes: []StageOutcome{
+				{
+					Entity: entityAllProcessedBidResponses,
+					Stage:  hooks.StageAllProcessedBidResponses.String(),
+					Groups: []GroupOutcome{
+						{
+							InvocationResults: []HookOutcome{
+								{
+									AnalyticsTags: hookanalytics.Analytics{},
+									HookID:        HookID{ModuleCode: "foobar", HookImplCode: "foo"},
+									Status:        StatusSuccess,
+									Action:        ActionUpdate,
+									Message:       "",
+									DebugMessages: []string{
+										fmt.Sprintf("Hook mutation successfully applied, affected key: processedBidderResponse.bid.deal-priority, mutation type: %s", hookstage.MutationUpdate),
+									},
+									Errors:   nil,
+									Warnings: nil,
+								},
+								{
+									AnalyticsTags: hookanalytics.Analytics{},
+									HookID:        HookID{ModuleCode: "foobar", HookImplCode: "bar"},
+									Status:        StatusExecutionFailure,
+									Action:        ActionUpdate,
+									Message:       "",
+									DebugMessages: nil,
+									Errors:        nil,
+									Warnings:      []string{"failed to apply hook mutation: key not found"},
+								},
+							},
+						},
+						{
+							InvocationResults: []HookOutcome{
+								{
+									AnalyticsTags: hookanalytics.Analytics{},
+									HookID:        HookID{ModuleCode: "foobar", HookImplCode: "baz"},
+									Status:        StatusFailure,
+									Action:        "",
+									Message:       "",
+									DebugMessages: nil,
+									Errors:        []string{"hook execution failed: attribute not found"},
+									Warnings:      nil,
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		{
+			description: "Stage execution can't be rejected - stage doesn't support rejection",
+			givenBiddersResponse: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{
+				"some-bidder": {Bids: []*entities.PbsOrtbBid{{DealPriority: 1}}},
+			},
+			givenPlanBuilder:        TestRejectPlanBuilder{},
+			givenAccount:            nil,
+			expectedBiddersResponse: expectedUpdatedAllProcBidResponses,
+			expectedReject:          &RejectError{0, HookID{ModuleCode: "foobar", HookImplCode: "foo"}, hooks.StageAllProcessedBidResponses.String()},
+			expectedModuleContexts:  foobarModuleCtx,
+			expectedStageOutcomes: []StageOutcome{
+				{
+					Entity: entityAllProcessedBidResponses,
+					Stage:  hooks.StageAllProcessedBidResponses.String(),
+					Groups: []GroupOutcome{
+						{
+							InvocationResults: []HookOutcome{
+								{
+									AnalyticsTags: hookanalytics.Analytics{},
+									HookID:        HookID{ModuleCode: "foobar", HookImplCode: "baz"},
+									Status:        StatusExecutionFailure,
+									Action:        "",
+									Message:       "",
+									DebugMessages: nil,
+									Errors:        []string{"unexpected error"},
+									Warnings:      nil,
+								},
+							},
+						},
+						{
+							InvocationResults: []HookOutcome{
+								{
+									AnalyticsTags: hookanalytics.Analytics{},
+									HookID:        HookID{ModuleCode: "foobar", HookImplCode: "foo"},
+									Status:        StatusExecutionFailure,
+									Action:        "",
+									Message:       "",
+									DebugMessages: nil,
+									Errors: []string{
+										fmt.Sprintf("Module (name: foobar, hook code: foo) tried to reject request on the %s stage that does not support rejection", hooks.StageAllProcessedBidResponses),
+									},
+									Warnings: nil,
+								},
+							},
+						},
+						{
+							InvocationResults: []HookOutcome{
+								{
+									AnalyticsTags: hookanalytics.Analytics{},
+									HookID:        HookID{ModuleCode: "foobar", HookImplCode: "bar"},
+									Status:        StatusSuccess,
+									Action:        ActionUpdate,
+									Message:       "",
+									DebugMessages: []string{
+										fmt.Sprintf("Hook mutation successfully applied, affected key: processedBidderResponse.bid.deal-priority, mutation type: %s", hookstage.MutationUpdate),
+									},
+									Errors:   nil,
+									Warnings: nil,
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		{
+			description: "Stage execution can be timed out",
+			givenBiddersResponse: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{
+				"some-bidder": {Bids: []*entities.PbsOrtbBid{{DealPriority: 1}}},
+			},
+			givenPlanBuilder:        TestWithTimeoutPlanBuilder{},
+			givenAccount:            account,
+			expectedBiddersResponse: expectedUpdatedAllProcBidResponses,
+			expectedReject:          nil,
+			expectedModuleContexts:  foobarModuleCtx,
+			expectedStageOutcomes: []StageOutcome{
+				{
+					Entity: entityAllProcessedBidResponses,
+					Stage:  hooks.StageAllProcessedBidResponses.String(),
+					Groups: []GroupOutcome{
+						{
+							InvocationResults: []HookOutcome{
+								{
+									AnalyticsTags: hookanalytics.Analytics{},
+									HookID:        HookID{ModuleCode: "foobar", HookImplCode: "foo"},
+									Status:        StatusTimeout,
+									Action:        "",
+									Message:       "",
+									DebugMessages: nil,
+									Errors:        []string{"Hook execution timeout"},
+									Warnings:      nil,
+								},
+							},
+						},
+						{
+							InvocationResults: []HookOutcome{
+								{
+									AnalyticsTags: hookanalytics.Analytics{},
+									HookID:        HookID{ModuleCode: "foobar", HookImplCode: "bar"},
+									Status:        StatusSuccess,
+									Action:        ActionUpdate,
+									Message:       "",
+									DebugMessages: []string{
+										fmt.Sprintf("Hook mutation successfully applied, affected key: processedBidderResponse.bid.deal-priority, mutation type: %s", hookstage.MutationUpdate),
+									},
+									Errors:   nil,
+									Warnings: nil,
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		{
+			description: "Modules contexts are preserved and correct",
+			givenBiddersResponse: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{
+				"some-bidder": {Bids: []*entities.PbsOrtbBid{{DealPriority: 1}}},
+			},
+			givenPlanBuilder:        TestWithModuleContextsPlanBuilder{},
+			givenAccount:            account,
+			expectedBiddersResponse: expectedAllProcBidResponses,
+			expectedReject:          nil,
+			expectedModuleContexts: &moduleContexts{ctxs: map[string]hookstage.ModuleContext{
+				"module-1": {"all-processed-bid-responses-ctx-1": "some-ctx-1"},
+				"module-2": {"all-processed-bid-responses-ctx-2": "some-ctx-2"},
+			}},
+			expectedStageOutcomes: []StageOutcome{
+				{
+					Entity: entityAllProcessedBidResponses,
+					Stage:  hooks.StageAllProcessedBidResponses.String(),
+					Groups: []GroupOutcome{
+						{
+							InvocationResults: []HookOutcome{
+								{
+									AnalyticsTags: hookanalytics.Analytics{},
+									HookID:        HookID{ModuleCode: "module-1", HookImplCode: "foo"},
+									Status:        StatusSuccess,
+									Action:        ActionNone,
+									Message:       "",
+									DebugMessages: nil,
+									Errors:        nil,
+									Warnings:      nil,
+								},
+							},
+						},
+						{
+							InvocationResults: []HookOutcome{
+								{
+									AnalyticsTags: hookanalytics.Analytics{},
+									HookID:        HookID{ModuleCode: "module-2", HookImplCode: "bar"},
+									Status:        StatusSuccess,
+									Action:        ActionNone,
+									Message:       "",
+									DebugMessages: nil,
+									Errors:        nil,
+									Warnings:      nil,
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+
+	for _, test := range testCases {
+		t.Run(test.description, func(t *testing.T) {
+			exec := NewHookExecutor(test.givenPlanBuilder, EndpointAuction, &metricsConfig.NilMetricsEngine{})
+			exec.SetAccount(test.givenAccount)
+
+			exec.ExecuteAllProcessedBidResponsesStage(test.givenBiddersResponse)
+
+			assert.Equal(t, test.expectedBiddersResponse, test.givenBiddersResponse, "Incorrect bidders response.")
+			assert.Equal(t, test.expectedModuleContexts, exec.moduleContexts, "Incorrect module contexts")
+
+			stageOutcomes := exec.GetOutcomes()
+			if len(test.expectedStageOutcomes) == 0 {
+				assert.Empty(t, stageOutcomes, "Incorrect stage outcomes.")
+			} else {
+				assertEqualStageOutcomes(t, test.expectedStageOutcomes[0], stageOutcomes[0])
+			}
+		})
+	}
+}
+
 func TestInterStageContextCommunication(t *testing.T) {
 	body := []byte(`{"foo": "bar"}`)
 	reader := bytes.NewReader(body)
@@ -1567,6 +1847,24 @@ func (e TestApplyHookMutationsBuilder) PlanForRawBidderResponseStage(_ string, _
 	}
 }
 
+func (e TestApplyHookMutationsBuilder) PlanForAllProcessedBidResponsesStage(_ string, _ *config.Account) hooks.Plan[hookstage.AllProcessedBidResponses] {
+	return hooks.Plan[hookstage.AllProcessedBidResponses]{
+		hooks.Group[hookstage.AllProcessedBidResponses]{
+			Timeout: 1 * time.Millisecond,
+			Hooks: []hooks.HookWrapper[hookstage.AllProcessedBidResponses]{
+				{Module: "foobar", Code: "foo", Hook: mockUpdateBiddersResponsesHook{}},
+				{Module: "foobar", Code: "bar", Hook: mockFailedMutationHook{}},
+			},
+		},
+		hooks.Group[hookstage.AllProcessedBidResponses]{
+			Timeout: 1 * time.Millisecond,
+			Hooks: []hooks.HookWrapper[hookstage.AllProcessedBidResponses]{
+				{Module: "foobar", Code: "baz", Hook: mockFailureHook{}},
+			},
+		},
+	}
+}
+
 type TestRejectPlanBuilder struct {
 	hooks.EmptyPlanBuilder
 }
@@ -1681,6 +1979,31 @@ func (e TestRejectPlanBuilder) PlanForRawBidderResponseStage(_ string, _ *config
 	}
 }
 
+func (e TestRejectPlanBuilder) PlanForAllProcessedBidResponsesStage(_ string, _ *config.Account) hooks.Plan[hookstage.AllProcessedBidResponses] {
+	return hooks.Plan[hookstage.AllProcessedBidResponses]{
+		hooks.Group[hookstage.AllProcessedBidResponses]{
+			Timeout: 1 * time.Millisecond,
+			Hooks: []hooks.HookWrapper[hookstage.AllProcessedBidResponses]{
+				{Module: "foobar", Code: "baz", Hook: mockErrorHook{}},
+			},
+		},
+		// rejection ignored, stage doesn't support rejection
+		hooks.Group[hookstage.AllProcessedBidResponses]{
+			Timeout: 1 * time.Millisecond,
+			Hooks: []hooks.HookWrapper[hookstage.AllProcessedBidResponses]{
+				{Module: "foobar", Code: "foo", Hook: mockRejectHook{}},
+			},
+		},
+		// hook executed and payload updated because this stage doesn't support rejection
+		hooks.Group[hookstage.AllProcessedBidResponses]{
+			Timeout: 1 * time.Millisecond,
+			Hooks: []hooks.HookWrapper[hookstage.AllProcessedBidResponses]{
+				{Module: "foobar", Code: "bar", Hook: mockUpdateBiddersResponsesHook{}},
+			},
+		},
+	}
+}
+
 type TestWithTimeoutPlanBuilder struct {
 	hooks.EmptyPlanBuilder
 }
@@ -1771,6 +2094,23 @@ func (e TestWithTimeoutPlanBuilder) PlanForRawBidderResponseStage(_ string, _ *c
 	}
 }
 
+func (e TestWithTimeoutPlanBuilder) PlanForAllProcessedBidResponsesStage(_ string, _ *config.Account) hooks.Plan[hookstage.AllProcessedBidResponses] {
+	return hooks.Plan[hookstage.AllProcessedBidResponses]{
+		hooks.Group[hookstage.AllProcessedBidResponses]{
+			Timeout: 1 * time.Millisecond,
+			Hooks: []hooks.HookWrapper[hookstage.AllProcessedBidResponses]{
+				{Module: "foobar", Code: "foo", Hook: mockTimeoutHook{}},
+			},
+		},
+		hooks.Group[hookstage.AllProcessedBidResponses]{
+			Timeout: 1 * time.Millisecond,
+			Hooks: []hooks.HookWrapper[hookstage.AllProcessedBidResponses]{
+				{Module: "foobar", Code: "bar", Hook: mockUpdateBiddersResponsesHook{}},
+			},
+		},
+	}
+}
+
 type TestWithModuleContextsPlanBuilder struct {
 	hooks.EmptyPlanBuilder
 }
@@ -1864,6 +2204,23 @@ func (e TestWithModuleContextsPlanBuilder) PlanForRawBidderResponseStage(_ strin
 	}
 }
 
+func (e TestWithModuleContextsPlanBuilder) PlanForAllProcessedBidResponsesStage(_ string, _ *config.Account) hooks.Plan[hookstage.AllProcessedBidResponses] {
+	return hooks.Plan[hookstage.AllProcessedBidResponses]{
+		hooks.Group[hookstage.AllProcessedBidResponses]{
+			Timeout: 1 * time.Millisecond,
+			Hooks: []hooks.HookWrapper[hookstage.AllProcessedBidResponses]{
+				{Module: "module-1", Code: "foo", Hook: mockModuleContextHook{key: "all-processed-bid-responses-ctx-1", val: "some-ctx-1"}},
+			},
+		},
+		hooks.Group[hookstage.AllProcessedBidResponses]{
+			Timeout: 1 * time.Millisecond,
+			Hooks: []hooks.HookWrapper[hookstage.AllProcessedBidResponses]{
+				{Module: "module-2", Code: "bar", Hook: mockModuleContextHook{key: "all-processed-bid-responses-ctx-2", val: "some-ctx-2"}},
+			},
+		},
+	}
+}
+
 type TestAllHookResultsBuilder struct {
 	hooks.EmptyPlanBuilder
 }
diff --git a/hooks/hookexecution/mocks_test.go b/hooks/hookexecution/mocks_test.go
index c931cbf6..1f93e07e 100644
--- a/hooks/hookexecution/mocks_test.go
+++ b/hooks/hookexecution/mocks_test.go
@@ -93,6 +93,10 @@ func (e mockRejectHook) HandleRawBidderResponseHook(_ context.Context, _ hooksta
 	return hookstage.HookResult[hookstage.RawBidderResponsePayload]{Reject: true}, nil
 }
 
+func (e mockRejectHook) HandleAllProcessedBidResponsesHook(_ context.Context, _ hookstage.ModuleInvocationContext, _ hookstage.AllProcessedBidResponsesPayload) (hookstage.HookResult[hookstage.AllProcessedBidResponsesPayload], error) {
+	return hookstage.HookResult[hookstage.AllProcessedBidResponsesPayload]{Reject: true}, nil
+}
+
 type mockTimeoutHook struct{}
 
 func (e mockTimeoutHook) HandleEntrypointHook(_ context.Context, _ hookstage.ModuleInvocationContext, _ hookstage.EntrypointPayload) (hookstage.HookResult[hookstage.EntrypointPayload], error) {
@@ -152,6 +156,17 @@ func (e mockTimeoutHook) HandleRawBidderResponseHook(_ context.Context, _ hookst
 	return hookstage.HookResult[hookstage.RawBidderResponsePayload]{ChangeSet: c}, nil
 }
 
+func (e mockTimeoutHook) HandleAllProcessedBidResponsesHook(_ context.Context, _ hookstage.ModuleInvocationContext, _ hookstage.AllProcessedBidResponsesPayload) (hookstage.HookResult[hookstage.AllProcessedBidResponsesPayload], error) {
+	time.Sleep(2 * time.Millisecond)
+	c := &hookstage.ChangeSet[hookstage.AllProcessedBidResponsesPayload]{}
+	c.AddMutation(func(payload hookstage.AllProcessedBidResponsesPayload) (hookstage.AllProcessedBidResponsesPayload, error) {
+		payload.Responses["some-bidder"].Bids[0].BidMeta = &openrtb_ext.ExtBidPrebidMeta{AdapterCode: "new-code"}
+		return payload, nil
+	}, hookstage.MutationUpdate, "processedBidderResponse", "bidMeta.AdapterCode")
+
+	return hookstage.HookResult[hookstage.AllProcessedBidResponsesPayload]{ChangeSet: c}, nil
+}
+
 type mockModuleContextHook struct {
 	key, val string
 }
@@ -181,6 +196,11 @@ func (e mockModuleContextHook) HandleRawBidderResponseHook(_ context.Context, mi
 	return hookstage.HookResult[hookstage.RawBidderResponsePayload]{ModuleContext: miCtx.ModuleContext}, nil
 }
 
+func (e mockModuleContextHook) HandleAllProcessedBidResponsesHook(_ context.Context, miCtx hookstage.ModuleInvocationContext, _ hookstage.AllProcessedBidResponsesPayload) (hookstage.HookResult[hookstage.AllProcessedBidResponsesPayload], error) {
+	miCtx.ModuleContext = map[string]interface{}{e.key: e.val}
+	return hookstage.HookResult[hookstage.AllProcessedBidResponsesPayload]{ModuleContext: miCtx.ModuleContext}, nil
+}
+
 type mockFailureHook struct{}
 
 func (h mockFailureHook) HandleEntrypointHook(_ context.Context, _ hookstage.ModuleInvocationContext, _ hookstage.EntrypointPayload) (hookstage.HookResult[hookstage.EntrypointPayload], error) {
@@ -195,6 +215,10 @@ func (h mockFailureHook) HandleBidderRequestHook(_ context.Context, _ hookstage.
 	return hookstage.HookResult[hookstage.BidderRequestPayload]{}, FailureError{Message: "attribute not found"}
 }
 
+func (h mockFailureHook) HandleAllProcessedBidResponsesHook(_ context.Context, _ hookstage.ModuleInvocationContext, _ hookstage.AllProcessedBidResponsesPayload) (hookstage.HookResult[hookstage.AllProcessedBidResponsesPayload], error) {
+	return hookstage.HookResult[hookstage.AllProcessedBidResponsesPayload]{}, FailureError{Message: "attribute not found"}
+}
+
 type mockErrorHook struct{}
 
 func (h mockErrorHook) HandleEntrypointHook(_ context.Context, _ hookstage.ModuleInvocationContext, _ hookstage.EntrypointPayload) (hookstage.HookResult[hookstage.EntrypointPayload], error) {
@@ -209,6 +233,10 @@ func (h mockErrorHook) HandleBidderRequestHook(_ context.Context, _ hookstage.Mo
 	return hookstage.HookResult[hookstage.BidderRequestPayload]{}, errors.New("unexpected error")
 }
 
+func (h mockErrorHook) HandleAllProcessedBidResponsesHook(_ context.Context, _ hookstage.ModuleInvocationContext, _ hookstage.AllProcessedBidResponsesPayload) (hookstage.HookResult[hookstage.AllProcessedBidResponsesPayload], error) {
+	return hookstage.HookResult[hookstage.AllProcessedBidResponsesPayload]{}, errors.New("unexpected error")
+}
+
 type mockFailedMutationHook struct{}
 
 func (h mockFailedMutationHook) HandleEntrypointHook(_ context.Context, _ hookstage.ModuleInvocationContext, _ hookstage.EntrypointPayload) (hookstage.HookResult[hookstage.EntrypointPayload], error) {
@@ -238,6 +266,15 @@ func (h mockFailedMutationHook) HandleBidderRequestHook(_ context.Context, _ hoo
 	return hookstage.HookResult[hookstage.BidderRequestPayload]{ChangeSet: changeSet}, nil
 }
 
+func (h mockFailedMutationHook) HandleAllProcessedBidResponsesHook(_ context.Context, _ hookstage.ModuleInvocationContext, _ hookstage.AllProcessedBidResponsesPayload) (hookstage.HookResult[hookstage.AllProcessedBidResponsesPayload], error) {
+	changeSet := &hookstage.ChangeSet[hookstage.AllProcessedBidResponsesPayload]{}
+	changeSet.AddMutation(func(payload hookstage.AllProcessedBidResponsesPayload) (hookstage.AllProcessedBidResponsesPayload, error) {
+		return payload, errors.New("key not found")
+	}, hookstage.MutationUpdate, "some-bidder", "bids[0]", "deal_priority")
+
+	return hookstage.HookResult[hookstage.AllProcessedBidResponsesPayload]{ChangeSet: changeSet}, nil
+}
+
 type mockUpdateBidRequestHook struct{}
 
 func (e mockUpdateBidRequestHook) HandleProcessedAuctionHook(_ context.Context, _ hookstage.ModuleInvocationContext, _ hookstage.ProcessedAuctionRequestPayload) (hookstage.HookResult[hookstage.ProcessedAuctionRequestPayload], error) {
@@ -287,3 +324,17 @@ func (e mockUpdateBidderResponseHook) HandleRawBidderResponseHook(_ context.Cont
 
 	return hookstage.HookResult[hookstage.RawBidderResponsePayload]{ChangeSet: c}, nil
 }
+
+type mockUpdateBiddersResponsesHook struct{}
+
+func (e mockUpdateBiddersResponsesHook) HandleAllProcessedBidResponsesHook(_ context.Context, _ hookstage.ModuleInvocationContext, _ hookstage.AllProcessedBidResponsesPayload) (hookstage.HookResult[hookstage.AllProcessedBidResponsesPayload], error) {
+	c := &hookstage.ChangeSet[hookstage.AllProcessedBidResponsesPayload]{}
+	c.AddMutation(
+		func(payload hookstage.AllProcessedBidResponsesPayload) (hookstage.AllProcessedBidResponsesPayload, error) {
+			payload.Responses["some-bidder"].Bids[0].DealPriority = 10
+			return payload, nil
+		}, hookstage.MutationUpdate, "processedBidderResponse", "bid.deal-priority",
+	)
+
+	return hookstage.HookResult[hookstage.AllProcessedBidResponsesPayload]{ChangeSet: c}, nil
+}