Repository: prebid/prebid-server
Pull request #2239 (2022-05-03 08:23:01)

medianet: Update user macro


Patch:
diff --git a/adapters/aax/aax.go b/adapters/aax/aax.go
new file mode 100644
index 00000000..1a15ebd3
--- /dev/null
+++ b/adapters/aax/aax.go
@@ -0,0 +1,147 @@
+package aax
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"net/url"
+
+	"github.com/mxmCherry/openrtb/v15/openrtb2"
+	"github.com/prebid/prebid-server/adapters"
+	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/errortypes"
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+type adapter struct {
+	endpoint string
+}
+
+type aaxResponseBidExt struct {
+	AdCodeType string `json:"adCodeType"`
+}
+
+func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+	var errs []error
+
+	reqJson, err := json.Marshal(request)
+	if err != nil {
+		errs = append(errs, err)
+		return nil, errs
+	}
+
+	headers := http.Header{}
+	headers.Add("Content-Type", "application/json;charset=utf-8")
+
+	return []*adapters.RequestData{{
+		Method:  "POST",
+		Uri:     a.endpoint,
+		Body:    reqJson,
+		Headers: headers,
+	}}, errs
+}
+
+func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+	var errs []error
+
+	if response.StatusCode == http.StatusNoContent {
+		return nil, nil
+	}
+
+	if response.StatusCode == http.StatusBadRequest {
+		return nil, []error{&errortypes.BadInput{
+			Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode),
+		}}
+	}
+
+	if response.StatusCode != http.StatusOK {
+		return nil, []error{&errortypes.BadServerResponse{
+			Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode),
+		}}
+	}
+
+	var bidResp openrtb2.BidResponse
+
+	if err := json.Unmarshal(response.Body, &bidResp); err != nil {
+		return nil, []error{err}
+	}
+
+	bidResponse := adapters.NewBidderResponse()
+
+	for _, seatBid := range bidResp.SeatBid {
+		for i := range seatBid.Bid {
+			bidType, err := getMediaTypeForImp(seatBid.Bid[i], internalRequest.Imp)
+			if err != nil {
+				errs = append(errs, err)
+			} else {
+				b := &adapters.TypedBid{
+					Bid:     &seatBid.Bid[i],
+					BidType: bidType,
+				}
+				bidResponse.Bids = append(bidResponse.Bids, b)
+			}
+		}
+	}
+	return bidResponse, errs
+}
+
+// Builder builds a new instance of the Aax adapter for the given bidder with the given config.
+func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) {
+	url := buildEndpoint(config.Endpoint, config.ExtraAdapterInfo)
+	return &adapter{
+		endpoint: url,
+	}, nil
+}
+
+func getMediaTypeForImp(bid openrtb2.Bid, imps []openrtb2.Imp) (openrtb_ext.BidType, error) {
+	var bidExt aaxResponseBidExt
+	err := json.Unmarshal(bid.Ext, &bidExt)
+	if err == nil {
+		switch bidExt.AdCodeType {
+		case "banner":
+			return openrtb_ext.BidTypeBanner, nil
+		case "native":
+			return openrtb_ext.BidTypeNative, nil
+		case "video":
+			return openrtb_ext.BidTypeVideo, nil
+		}
+	}
+
+	var mediaType openrtb_ext.BidType
+	var typeCnt = 0
+	for _, imp := range imps {
+		if imp.ID == bid.ImpID {
+			if imp.Banner != nil {
+				typeCnt += 1
+				mediaType = openrtb_ext.BidTypeBanner
+			}
+			if imp.Native != nil {
+				typeCnt += 1
+				mediaType = openrtb_ext.BidTypeNative
+			}
+			if imp.Video != nil {
+				typeCnt += 1
+				mediaType = openrtb_ext.BidTypeVideo
+			}
+		}
+	}
+	if typeCnt == 1 {
+		return mediaType, nil
+	}
+	return mediaType, fmt.Errorf("unable to fetch mediaType in multi-format: %s", bid.ImpID)
+}
+
+func buildEndpoint(aaxUrl, hostUrl string) string {
+
+	if len(hostUrl) == 0 {
+		return aaxUrl
+	}
+	urlObject, err := url.Parse(aaxUrl)
+	if err != nil {
+		return aaxUrl
+	}
+	values := urlObject.Query()
+	values.Add("src", hostUrl)
+	urlObject.RawQuery = values.Encode()
+	return urlObject.String()
+}
diff --git a/config/config.go b/config/config.go
index 0c3bd9bb..a8bf31c0 100644
--- a/config/config.go
+++ b/config/config.go
@@ -18,13 +18,15 @@ import (
 
 // Configuration specifies the static application config.
 type Configuration struct {
-	ExternalURL string     `mapstructure:"external_url"`
-	Host        string     `mapstructure:"host"`
-	Port        int        `mapstructure:"port"`
-	Client      HTTPClient `mapstructure:"http_client"`
-	CacheClient HTTPClient `mapstructure:"http_client_cache"`
-	AdminPort   int        `mapstructure:"admin_port"`
-	EnableGzip  bool       `mapstructure:"enable_gzip"`
+	ExternalURL      string     `mapstructure:"external_url"`
+	Host             string     `mapstructure:"host"`
+	Port             int        `mapstructure:"port"`
+	UnixSocketEnable bool       `mapstructure:"unix_socket_enable"`
+	UnixSocketName   string     `mapstructure:"unix_socket_name"`
+	Client           HTTPClient `mapstructure:"http_client"`
+	CacheClient      HTTPClient `mapstructure:"http_client_cache"`
+	AdminPort        int        `mapstructure:"admin_port"`
+	EnableGzip       bool       `mapstructure:"enable_gzip"`
 	// GarbageCollectorThreshold allocates virtual memory (in bytes) which is not used by PBS but
 	// serves as a hack to trigger the garbage collector only when the heap reaches at least this size.
 	// More info: https://github.com/golang/go/issues/48409
@@ -739,6 +741,8 @@ func SetupViper(v *viper.Viper, filename string) {
 	v.SetDefault("external_url", "http://localhost:8000")
 	v.SetDefault("host", "")
 	v.SetDefault("port", 8000)
+	v.SetDefault("unix_socket_enable", false)              // boolean which decide if the socket-server will be started.
+	v.SetDefault("unix_socket_name", "prebid-server.sock") // path of the socket's file which must be listened.
 	v.SetDefault("admin_port", 6060)
 	v.SetDefault("enable_gzip", false)
 	v.SetDefault("garbage_collector_threshold", 0)
@@ -895,6 +899,8 @@ func SetupViper(v *viper.Viper, filename string) {
 	// for them and specify all the parameters they need for them to work correctly.
 	v.SetDefault("adapters.33across.endpoint", "https://ssc.33across.com/api/v1/s2s")
 	v.SetDefault("adapters.33across.partner_id", "")
+	v.SetDefault("adapters.aax.endpoint", "https://prebid.aaxads.com/rtb/pb/aax-prebid")
+	v.SetDefault("adapters.aax.extra_info", "https://aax.golang.pbs.com")
 	v.SetDefault("adapters.aceex.endpoint", "http://bl-us.aceex.io/?uqhash={{.AccountID}}")
 	v.SetDefault("adapters.acuityads.endpoint", "http://{{.Host}}.admanmedia.com/bid?token={{.AccountID}}")
 	v.SetDefault("adapters.adf.endpoint", "https://adx.adform.net/adx/openrtb")
diff --git a/endpoints/openrtb2/amp_auction.go b/endpoints/openrtb2/amp_auction.go
index 6be67ded..1d5f8a5e 100644
--- a/endpoints/openrtb2/amp_auction.go
+++ b/endpoints/openrtb2/amp_auction.go
@@ -25,6 +25,7 @@ import (
 	"github.com/prebid/prebid-server/privacy/gdpr"
 	"github.com/prebid/prebid-server/stored_requests"
 	"github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher"
+	"github.com/prebid/prebid-server/stored_responses"
 	"github.com/prebid/prebid-server/usersync"
 	"github.com/prebid/prebid-server/util/iputil"
 	"github.com/prebid/prebid-server/version"
@@ -138,7 +139,7 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h
 	w.Header().Set("Access-Control-Expose-Headers", "AMP-Access-Control-Allow-Source-Origin")
 	w.Header().Set("X-Prebid", version.BuildXPrebidHeader(version.Ver))
 
-	req, storedAuctionResponses, errL := deps.parseAmpRequest(r)
+	req, storedAuctionResponses, storedBidResponses, errL := deps.parseAmpRequest(r)
 	ao.Errors = append(ao.Errors, errL...)
 
 	if errortypes.ContainsFatalError(errL) {
@@ -204,6 +205,7 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h
 		TCF2ConfigBuilder:          aggregatedGDPR.NewTCF2Config,
 		GDPRPermissionsBuilder:     aggregatedGDPR.NewPermissions,
 		StoredAuctionResponses:     storedAuctionResponses,
+		StoredBidResponses:         storedBidResponses,
 	}
 
 	response, err := deps.ex.HoldAuction(ctx, auctionRequest, nil)
@@ -304,9 +306,9 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h
 // possible, it will return errors with messages that suggest improvements.
 //
 // If the errors list has at least one element, then no guarantees are made about the returned request.
-func (deps *endpointDeps) parseAmpRequest(httpRequest *http.Request) (req *openrtb2.BidRequest, storedAuctionResponses map[string]json.RawMessage, errs []error) {
+func (deps *endpointDeps) parseAmpRequest(httpRequest *http.Request) (req *openrtb2.BidRequest, storedAuctionResponses stored_responses.ImpsWithBidResponses, storedBidResponses stored_responses.ImpBidderStoredResp, errs []error) {
 	// Load the stored request for the AMP ID.
-	req, storedAuctionResponses, e := deps.loadRequestJSONForAmp(httpRequest)
+	req, storedAuctionResponses, storedBidResponses, e := deps.loadRequestJSONForAmp(httpRequest)
 	if errs = append(errs, e...); errortypes.ContainsFatalError(errs) {
 		return
 	}
@@ -321,20 +323,23 @@ func (deps *endpointDeps) parseAmpRequest(httpRequest *http.Request) (req *openr
 	}
 
 	// At this point, we should have a valid request that definitely has Targeting and Cache turned on
-	hasStoredResponses := len(storedAuctionResponses) > 0
-	e = deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: req}, true, hasStoredResponses)
+	var hasStoredResponses bool
+	if len(storedAuctionResponses) > 0 {
+		hasStoredResponses = true
+	}
+	e = deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: req}, true, hasStoredResponses, storedBidResponses)
 	errs = append(errs, e...)
 	return
 }
 
 // Load the stored OpenRTB request for an incoming AMP request, or return the errors found.
-func (deps *endpointDeps) loadRequestJSONForAmp(httpRequest *http.Request) (req *openrtb2.BidRequest, storedAuctionResponses map[string]json.RawMessage, errs []error) {
+func (deps *endpointDeps) loadRequestJSONForAmp(httpRequest *http.Request) (req *openrtb2.BidRequest, storedAuctionResponses stored_responses.ImpsWithBidResponses, storedBidResponses stored_responses.ImpBidderStoredResp, errs []error) {
 	req = &openrtb2.BidRequest{}
 	errs = nil
 
 	ampParams, err := amp.ParseParams(httpRequest)
 	if err != nil {
-		return nil, nil, []error{err}
+		return nil, nil, nil, []error{err}
 	}
 
 	ctx, cancel := context.WithTimeout(context.Background(), time.Duration(storedRequestTimeoutMillis)*time.Millisecond)
@@ -342,7 +347,7 @@ func (deps *endpointDeps) loadRequestJSONForAmp(httpRequest *http.Request) (req
 
 	storedRequests, _, errs := deps.storedReqFetcher.FetchRequests(ctx, []string{ampParams.StoredRequestID}, nil)
 	if len(errs) > 0 {
-		return nil, nil, errs
+		return nil, nil, nil, errs
 	}
 	if len(storedRequests) == 0 {
 		errs = []error{fmt.Errorf("No AMP config found for tag_id '%s'", ampParams.StoredRequestID)}
@@ -356,7 +361,7 @@ func (deps *endpointDeps) loadRequestJSONForAmp(httpRequest *http.Request) (req
 		return
 	}
 
-	storedAuctionResponses, errs = deps.processStoredAuctionResponses(ctx, requestJSON)
+	storedAuctionResponses, storedBidResponses, errs = stored_responses.ProcessStoredResponses(ctx, requestJSON, deps.storedRespFetcher, deps.bidderMap)
 	if err != nil {
 		errs = []error{err}
 		return
diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go
index a62460e8..05a020a8 100644
--- a/endpoints/openrtb2/auction.go
+++ b/endpoints/openrtb2/auction.go
@@ -39,6 +39,7 @@ import (
 	"github.com/prebid/prebid-server/schain"
 	"github.com/prebid/prebid-server/stored_requests"
 	"github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher"
+	"github.com/prebid/prebid-server/stored_responses"
 	"github.com/prebid/prebid-server/usersync"
 	"github.com/prebid/prebid-server/util/httputil"
 	"github.com/prebid/prebid-server/util/iputil"
@@ -151,7 +152,7 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http
 
 	w.Header().Set("X-Prebid", version.BuildXPrebidHeader(version.Ver))
 
-	req, impExtInfoMap, storedAuctionResponses, errL := deps.parseRequest(r)
+	req, impExtInfoMap, storedAuctionResponses, storedBidResponses, errL := deps.parseRequest(r)
 	if errortypes.ContainsFatalError(errL) && writeError(errL, w, &labels) {
 		return
 	}
@@ -210,6 +211,7 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http
 		GlobalPrivacyControlHeader: secGPC,
 		ImpExtInfoMap:              impExtInfoMap,
 		StoredAuctionResponses:     storedAuctionResponses,
+		StoredBidResponses:         storedBidResponses,
 		TCF2ConfigBuilder:          gdpr.NewTCF2Config,
 		GDPRPermissionsBuilder:     gdpr.NewPermissions,
 	}
@@ -257,7 +259,7 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http
 // possible, it will return errors with messages that suggest improvements.
 //
 // If the errors list has at least one element, then no guarantees are made about the returned request.
-func (deps *endpointDeps) parseRequest(httpRequest *http.Request) (req *openrtb_ext.RequestWrapper, impExtInfoMap map[string]exchange.ImpExtInfo, storedAuctionResponses map[string]json.RawMessage, errs []error) {
+func (deps *endpointDeps) parseRequest(httpRequest *http.Request) (req *openrtb_ext.RequestWrapper, impExtInfoMap map[string]exchange.ImpExtInfo, storedAuctionResponses stored_responses.ImpsWithBidResponses, storedBidResponses stored_responses.ImpBidderStoredResp, errs []error) {
 	req = &openrtb_ext.RequestWrapper{}
 	req.BidRequest = &openrtb2.BidRequest{}
 	errs = nil
@@ -286,7 +288,7 @@ func (deps *endpointDeps) parseRequest(httpRequest *http.Request) (req *openrtb_
 
 	impInfo, errs := parseImpInfo(requestJson)
 	if len(errs) > 0 {
-		return nil, nil, nil, errs
+		return nil, nil, nil, nil, errs
 	}
 
 	// Fetch the Stored Request data and merge it into the HTTP request.
@@ -294,9 +296,10 @@ func (deps *endpointDeps) parseRequest(httpRequest *http.Request) (req *openrtb_
 		return
 	}
 
-	storedAuctionResponses, errs = deps.processStoredAuctionResponses(ctx, requestJson)
+	//Stored auction responses should be processed after stored requests due to possible impression modification
+	storedAuctionResponses, storedBidResponses, errs = stored_responses.ProcessStoredResponses(ctx, requestJson, deps.storedRespFetcher, deps.bidderMap)
 	if len(errs) > 0 {
-		return
+		return nil, nil, nil, nil, errs
 	}
 
 	if err := json.Unmarshal(requestJson, req.BidRequest); err != nil {
@@ -319,8 +322,11 @@ func (deps *endpointDeps) parseRequest(httpRequest *http.Request) (req *openrtb_
 
 	lmt.ModifyForIOS(req.BidRequest)
 
-	hasStoredResponses := len(storedAuctionResponses) > 0
-	errL := deps.validateRequest(req, false, hasStoredResponses)
+	var hasStoredResponses bool
+	if len(storedAuctionResponses) > 0 {
+		hasStoredResponses = true
+	}
+	errL := deps.validateRequest(req, false, hasStoredResponses, storedBidResponses)
 	if len(errL) > 0 {
 		errs = append(errs, errL...)
 	}
@@ -477,7 +483,7 @@ func addMissingReqExtParamsInImpExt(impExtByBidder map[string]json.RawMessage, r
 	return anyModified, nil
 }
 
-func (deps *endpointDeps) validateRequest(req *openrtb_ext.RequestWrapper, isAmp bool, hasStoredResponses bool) []error {
+func (deps *endpointDeps) validateRequest(req *openrtb_ext.RequestWrapper, isAmp bool, hasStoredResponses bool, storedBidResp stored_responses.ImpBidderStoredResp) []error {
 	errL := []error{}
 	if req.ID == "" {
 		return []error{errors.New("request missing required field: \"id\"")}
@@ -596,7 +602,7 @@ func (deps *endpointDeps) validateRequest(req *openrtb_ext.RequestWrapper, isAmp
 			errL = append(errL, fmt.Errorf(`request.imp[%d].id and request.imp[%d].id are both "%s". Imp IDs must be unique.`, firstIndex, index, imp.ID))
 		}
 		impIDs[imp.ID] = index
-		errs := deps.validateImp(imp, aliases, index, hasStoredResponses)
+		errs := deps.validateImp(imp, aliases, index, hasStoredResponses, storedBidResp)
 		if len(errs) > 0 {
 			errL = append(errL, errs...)
 		}
@@ -712,7 +718,7 @@ func validateBidders(bidders []string, knownBidders map[string]openrtb_ext.Bidde
 	return nil
 }
 
-func (deps *endpointDeps) validateImp(imp *openrtb2.Imp, aliases map[string]string, index int, hasStoredResponses bool) []error {
+func (deps *endpointDeps) validateImp(imp *openrtb2.Imp, aliases map[string]string, index int, hasStoredResponses bool, storedBidResp stored_responses.ImpBidderStoredResp) []error {
 	if imp.ID == "" {
 		return []error{fmt.Errorf("request.imp[%d] missing required field: \"id\"", index)}
 	}
@@ -745,7 +751,7 @@ func (deps *endpointDeps) validateImp(imp *openrtb2.Imp, aliases map[string]stri
 		return []error{err}
 	}
 
-	errL := deps.validateImpExt(imp, aliases, index, hasStoredResponses)
+	errL := deps.validateImpExt(imp, aliases, index, hasStoredResponses, storedBidResp)
 	if len(errL) != 0 {
 		return errL
 	}
@@ -1164,7 +1170,7 @@ func validatePmp(pmp *openrtb2.PMP, impIndex int) error {
 	return nil
 }
 
-func (deps *endpointDeps) validateImpExt(imp *openrtb2.Imp, aliases map[string]string, impIndex int, hasStoredResponses bool) []error {
+func (deps *endpointDeps) validateImpExt(imp *openrtb2.Imp, aliases map[string]string, impIndex int, hasStoredResponses bool, storedBidResp stored_responses.ImpBidderStoredResp) []error {
 	errL := []error{}
 	if len(imp.Ext) == 0 {
 		return []error{fmt.Errorf("request.imp[%d].ext is required", impIndex)}
@@ -1192,6 +1198,12 @@ func (deps *endpointDeps) validateImpExt(imp *openrtb2.Imp, aliases map[string]s
 		return []error{fmt.Errorf("request validation failed. The StoredAuctionResponse.ID field must be completely present with, or completely absent from, all impressions in request. No StoredAuctionResponse data found for request.imp[%d].ext.prebid \n", impIndex)}
 	}
 
+	if len(storedBidResp) > 0 {
+		if err := validateStoredBidRespAndImpExtBidders(bidderExts, storedBidResp, imp.ID); len(err) > 0 {
+			return err
+		}
+	}
+
 	/* Process all the bidder exts in the request */
 	disabledBidders := []string{}
 	otherExtElements := 0
@@ -1566,52 +1578,6 @@ func getJsonSyntaxError(testJSON []byte) (bool, string) {
 	return false, ""
 }
 
-// processStoredAuctionResponses takes the incoming request as JSON with any
-// stored requests/imps already merged into it, scans it to find any stored auction response ids
-// in the request/imps and produces a map of imp IDs to stored auction responses.
-// Note that processStoredAuctionResponses must be called after processStoredRequests
-// because stored imps and stored requests can contain stored auction responses
-// so the stored requests/imps have to be merged into the incoming request prior to processing stored auction responses.
-func (deps *endpointDeps) processStoredAuctionResponses(ctx context.Context, requestJson []byte) (map[string]json.RawMessage, []error) {
-	impInfo, errs := parseImpInfo(requestJson)
-	if len(errs) > 0 {
-		return nil, errs
-	}
-
-	storedAuctionResponseIds := make([]string, 0, 0) //all stored responses ids from all imps
-	//because of bulk fetch responses we need to map imp id to stored resp body
-	impIdToRespId := make(map[string]string)              //imp id to stored resp id
-	impIdToStoredResp := make(map[string]json.RawMessage) //imp id to stored resp body
-	for index, impData := range impInfo {
-
-		if impData.ImpExtPrebid.StoredAuctionResponse != nil {
-			if len(impData.ImpExtPrebid.StoredAuctionResponse.ID) == 0 {
-				return nil, []error{fmt.Errorf("request.imp[%d] has ext.prebid.storedauctionresponse specified, but \"id\" field is missing ", index)}
-			}
-			storedAuctionResponseIds = append(storedAuctionResponseIds, impData.ImpExtPrebid.StoredAuctionResponse.ID)
-
-			impId, err := jsonparser.GetString(impData.Imp, "id")
-			if err != nil {
-				return nil, []error{err}
-			}
-
-			impIdToRespId[impId] = impData.ImpExtPrebid.StoredAuctionResponse.ID
-
-		}
-	}
-	if len(storedAuctionResponseIds) > 0 {
-		storedAuctionResponses, errs := deps.storedRespFetcher.FetchResponses(ctx, storedAuctionResponseIds)
-		if len(errs) > 0 {
-			return nil, errs
-		}
-		for impId, respId := range impIdToRespId {
-			impIdToStoredResp[impId] = storedAuctionResponses[respId]
-		}
-		return impIdToStoredResp, nil
-	}
-	return nil, nil
-}
-
 func (deps *endpointDeps) processStoredRequests(ctx context.Context, requestJson []byte, impInfo []ImpExtPrebidData) ([]byte, map[string]exchange.ImpExtInfo, []error) {
 	// Parse the Stored Request IDs from the BidRequest and Imps.
 	storedBidRequestId, hasStoredBidRequest, err := getStoredRequestId(requestJson)
@@ -1946,3 +1912,18 @@ func checkIfAppRequest(request []byte) (bool, error) {
 	}
 	return false, nil
 }
+
+func validateStoredBidRespAndImpExtBidders(bidderExts map[string]json.RawMessage, storedBidResp stored_responses.ImpBidderStoredResp, impId string) []error {
+	if bidResponses, ok := storedBidResp[impId]; ok {
+		storedBidRespValidationError := []error{fmt.Errorf("request validation failed. Stored bid responses are specified for imp %s. Bidders specified in imp.ext should match with bidders specified in imp.ext.prebid.storedbidresponse", impId)}
+		if len(bidResponses) < len(bidderExts)-1 { // do not count "prebid" part of imp.ext. It is always present in imp.ext if stored bid responses are specified. There are can be other properties except of bidders and "prebid"
+			return storedBidRespValidationError
+		}
+		for bidderName := range bidResponses {
+			if _, present := bidderExts[bidderName]; !present {
+				return storedBidRespValidationError
+			}
+		}
+	}
+	return nil
+}
diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-bid-resp.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-bid-resp.json
new file mode 100644
index 00000000..e72cce49
--- /dev/null
+++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-bid-resp.json
@@ -0,0 +1,36 @@
+{
+  "description": "request with impression with stored bid response",
+  "mockBidRequest": {
+    "id": "request-with-stored-resp",
+    "site": {
+      "page": "test.somepage.com"
+    },
+    "imp": [
+      {
+        "id": "imp-id1",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "ext": {
+          "appnexus": {
+            "placementId": 12883451
+          },
+          "prebid": {
+            "storedbidresponse": [
+              {"bidder":"testBidder1", "id": "bidResponseId1"}
+            ]
+          }
+        }
+      }
+    ],
+    "user": {
+      "yob": 1989
+    }
+  },
+  "expectedReturnCode": 200
+}
diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-two-imps-missing-stored-bid-response.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-two-imps-missing-stored-bid-response.json
new file mode 100644
index 00000000..28ee8231
--- /dev/null
+++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-two-imps-missing-stored-bid-response.json
@@ -0,0 +1,54 @@
+{
+  "description": "request with two valid imp with missing stored bid responses",
+  "mockBidRequest": {
+    "id": "some-request-id",
+    "site": {
+      "page": "test.somepage.com"
+    },
+    "imp": [
+      {
+        "id": "imp-id1",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "ext": {
+          "appnexus": {
+            "placementId": 12883451
+          },
+          "prebid": {
+            "storedbidresponse": [
+              {"bidder":"testBidder1", "id": "123"}
+            ]
+          }
+        }
+      },
+      {
+        "id": "imp-id2",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "ext": {
+          "appnexus": {
+            "placementId": 12883451
+          },
+          "prebid": {
+            "storedbidresponse": [
+              {"bidder":"testBidder2", "id": "1234"}
+            ]
+          }
+        }
+      }
+    ]
+  },
+  "expectedReturnCode": 200
+}
\ No newline at end of file
diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-two-imps-stored-bid-responses.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-two-imps-stored-bid-responses.json
new file mode 100644
index 00000000..5906eb91
--- /dev/null
+++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-two-imps-stored-bid-responses.json
@@ -0,0 +1,54 @@
+{
+  "description": "request with two valid imp with stored bid responses",
+  "mockBidRequest": {
+    "id": "some-request-id",
+    "site": {
+      "page": "test.somepage.com"
+    },
+    "imp": [
+      {
+        "id": "imp-id1",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "ext": {
+          "appnexus": {
+            "placementId": 12883451
+          },
+          "prebid": {
+            "storedbidresponse": [
+              {"bidder":"testBidder1", "id": "bidResponseId1"}
+            ]
+          }
+        }
+      },
+      {
+        "id": "imp-id2",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "ext": {
+          "appnexus": {
+            "placementId": 12883451
+          },
+          "prebid": {
+            "storedbidresponse": [
+              {"bidder":"testBidder2", "id": "bidResponseId2"}
+            ]
+          }
+        }
+      }
+    ]
+  },
+  "expectedReturnCode": 200
+}
\ No newline at end of file
diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-two-imps-with-and-without-stored-bid-responses.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-two-imps-with-and-without-stored-bid-responses.json
new file mode 100644
index 00000000..6122e4df
--- /dev/null
+++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-two-imps-with-and-without-stored-bid-responses.json
@@ -0,0 +1,49 @@
+{
+  "description": "request with two valid imp with stored bid responses",
+  "mockBidRequest": {
+    "id": "some-request-id",
+    "site": {
+      "page": "test.somepage.com"
+    },
+    "imp": [
+      {
+        "id": "imp-id1",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "ext": {
+          "appnexus": {
+            "placementId": 12883451
+          }
+        }
+      },
+      {
+        "id": "imp-id2",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "ext": {
+          "appnexus": {
+            "placementId": 12883451
+          },
+          "prebid": {
+            "storedbidresponse": [
+              {"bidder":"testBidder2", "id": "bidResponseId2"}
+            ]
+          }
+        }
+      }
+    ]
+  },
+  "expectedReturnCode": 200
+}
\ No newline at end of file
diff --git a/endpoints/openrtb2/video_auction.go b/endpoints/openrtb2/video_auction.go
index 95b0f7cd..ec5b1c57 100644
--- a/endpoints/openrtb2/video_auction.go
+++ b/endpoints/openrtb2/video_auction.go
@@ -250,7 +250,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re
 	// Populate any "missing" OpenRTB fields with info from other sources, (e.g. HTTP request headers).
 	deps.setFieldsImplicitly(r, bidReq) // move after merge
 
-	errL = deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: bidReq}, false, false)
+	errL = deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: bidReq}, false, false, nil)
 	if errortypes.ContainsFatalError(errL) {
 		handleError(&labels, w, errL, &vo, &debugLog)
 		return
diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go
index 12d31f0d..24a2f0b8 100755
--- a/exchange/adapter_builders.go
+++ b/exchange/adapter_builders.go
@@ -3,6 +3,7 @@ package exchange
 import (
 	"github.com/prebid/prebid-server/adapters"
 	ttx "github.com/prebid/prebid-server/adapters/33across"
+	"github.com/prebid/prebid-server/adapters/aax"
 	"github.com/prebid/prebid-server/adapters/aceex"
 	"github.com/prebid/prebid-server/adapters/acuityads"
 	"github.com/prebid/prebid-server/adapters/adf"
@@ -142,6 +143,7 @@ import (
 func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder {
 	return map[openrtb_ext.BidderName]adapters.Builder{
 		openrtb_ext.Bidder33Across:          ttx.Builder,
+		openrtb_ext.BidderAax:               aax.Builder,
 		openrtb_ext.BidderAceex:             aceex.Builder,
 		openrtb_ext.BidderAcuityAds:         acuityads.Builder,
 		openrtb_ext.BidderAdf:               adf.Builder,
diff --git a/exchange/bidder.go b/exchange/bidder.go
index 77558028..969e4857 100644
--- a/exchange/bidder.go
+++ b/exchange/bidder.go
@@ -10,6 +10,7 @@ import (
 	"io/ioutil"
 	"net/http"
 	"net/http/httptrace"
+	"regexp"
 	"time"
 
 	"github.com/golang/glog"
@@ -50,9 +51,11 @@ 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, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) (*pbsOrtbSeatBid, []error)
+	requestBid(ctx context.Context, bidderRequest BidderRequest, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) (*pbsOrtbSeatBid, []error)
 }
 
+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.
@@ -131,53 +134,74 @@ type bidderAdapterConfig struct {
 	DebugInfo          config.DebugInfo
 }
 
-func (bidder *bidderAdapter) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) (*pbsOrtbSeatBid, []error) {
-	reqData, errs := bidder.Bidder.MakeRequests(request, reqInfo)
+func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest BidderRequest, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) (*pbsOrtbSeatBid, []error) {
+
+	var reqData []*adapters.RequestData
+	var errs []error
+	var responseChannel chan *httpCallInfo
+
+	//check if real request exists for this bidder or it only has stored responses
+	dataLen := 0
+	if len(bidderRequest.BidRequest.Imp) > 0 {
+		reqData, errs = bidder.Bidder.MakeRequests(bidderRequest.BidRequest, reqInfo)
 
-	if len(reqData) == 0 {
-		// If the adapter failed to generate both requests and errors, this is an error.
-		if len(errs) == 0 {
-			errs = append(errs, &errortypes.FailedToRequestBids{Message: "The adapter failed to generate any bid requests, but also failed to generate an error explaining why"})
+		if len(reqData) == 0 {
+			// If the adapter failed to generate both requests and errors, this is an error.
+			if len(errs) == 0 {
+				errs = append(errs, &errortypes.FailedToRequestBids{Message: "The adapter failed to generate any bid requests, but also failed to generate an error explaining why"})
+			}
+			return nil, errs
 		}
-		return nil, errs
-	}
-	xPrebidHeader := version.BuildXPrebidHeaderForRequest(request, version.Ver)
+		xPrebidHeader := version.BuildXPrebidHeaderForRequest(bidderRequest.BidRequest, version.Ver)
 
-	for i := 0; i < len(reqData); i++ {
-		if reqData[i].Headers != nil {
-			reqData[i].Headers = reqData[i].Headers.Clone()
-		} else {
-			reqData[i].Headers = http.Header{}
+		for i := 0; i < len(reqData); i++ {
+			if reqData[i].Headers != nil {
+				reqData[i].Headers = reqData[i].Headers.Clone()
+			} else {
+				reqData[i].Headers = http.Header{}
+			}
+			reqData[i].Headers.Add("X-Prebid", xPrebidHeader)
+			if reqInfo.GlobalPrivacyControlHeader == "1" {
+				reqData[i].Headers.Add("Sec-GPC", reqInfo.GlobalPrivacyControlHeader)
+			}
 		}
-		reqData[i].Headers.Add("X-Prebid", xPrebidHeader)
-		if reqInfo.GlobalPrivacyControlHeader == "1" {
-			reqData[i].Headers.Add("Sec-GPC", reqInfo.GlobalPrivacyControlHeader)
+		// Make any HTTP requests in parallel.
+		// If the bidder only needs to make one, save some cycles by just using the current one.
+		dataLen = len(reqData) + len(bidderRequest.BidderStoredResponses)
+		responseChannel = make(chan *httpCallInfo, dataLen)
+		if len(reqData) == 1 {
+			responseChannel <- bidder.doRequest(ctx, reqData[0])
+		} else {
+			for _, oneReqData := range reqData {
+				go func(data *adapters.RequestData) {
+					responseChannel <- bidder.doRequest(ctx, data)
+				}(oneReqData) // Method arg avoids a race condition on oneReqData
+			}
 		}
 	}
-
-	// Make any HTTP requests in parallel.
-	// If the bidder only needs to make one, save some cycles by just using the current one.
-	responseChannel := make(chan *httpCallInfo, len(reqData))
-	if len(reqData) == 1 {
-		responseChannel <- bidder.doRequest(ctx, reqData[0])
-	} else {
-		for _, oneReqData := range reqData {
-			go func(data *adapters.RequestData) {
-				responseChannel <- bidder.doRequest(ctx, data)
-			}(oneReqData) // Method arg avoids a race condition on oneReqData
+	if len(bidderRequest.BidderStoredResponses) > 0 {
+		//if stored bid responses are present - replace impIds and add them as is to responseChannel <- stored responses
+		if responseChannel == nil {
+			dataLen = dataLen + len(bidderRequest.BidderStoredResponses)
+			responseChannel = make(chan *httpCallInfo, dataLen)
+		}
+		for impId, bidResp := range bidderRequest.BidderStoredResponses {
+			go func(id string, resp json.RawMessage) {
+				responseChannel <- prepareStoredResponse(id, resp)
+			}(impId, bidResp)
 		}
 	}
 
 	defaultCurrency := "USD"
 	seatBid := &pbsOrtbSeatBid{
-		bids:      make([]*pbsOrtbBid, 0, len(reqData)),
+		bids:      make([]*pbsOrtbBid, 0, dataLen),
 		currency:  defaultCurrency,
-		httpCalls: make([]*openrtb_ext.ExtHttpCall, 0, len(reqData)),
+		httpCalls: make([]*openrtb_ext.ExtHttpCall, 0, dataLen),
 	}
 
 	// If the bidder made multiple requests, we still want them to enter as many bids as possible...
 	// even if the timeout occurs sometime halfway through.
-	for i := 0; i < len(reqData); i++ {
+	for i := 0; i < dataLen; i++ {
 		httpInfo := <-responseChannel
 		// If this is a test bid, capture debugging info from the requests.
 		// Write debug data to ext in case if:
@@ -201,7 +225,7 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, request *openrtb2.B
 		}
 
 		if httpInfo.err == nil {
-			bidResponse, moreErrs := bidder.Bidder.MakeBids(request, httpInfo.request, httpInfo.response)
+			bidResponse, moreErrs := bidder.Bidder.MakeBids(bidderRequest.BidRequest, httpInfo.request, httpInfo.response)
 			errs = append(errs, moreErrs...)
 
 			if bidResponse != nil {
@@ -209,8 +233,8 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, request *openrtb2.B
 				if bidResponse.Currency == "" {
 					bidResponse.Currency = defaultCurrency
 				}
-				if len(request.Cur) == 0 {
-					request.Cur = []string{defaultCurrency}
+				if len(bidderRequest.BidRequest.Cur) == 0 {
+					bidderRequest.BidRequest.Cur = []string{defaultCurrency}
 				}
 
 				// Try to get a conversion rate
@@ -218,7 +242,7 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, request *openrtb2.B
 				// and use it as currency
 				var conversionRate float64
 				var err error
-				for _, bidReqCur := range request.Cur {
+				for _, bidReqCur := range bidderRequest.BidRequest.Cur {
 					if conversionRate, err = conversions.GetRate(bidResponse.Currency, bidReqCur); err == nil {
 						seatBid.currency = bidReqCur
 						break
@@ -226,10 +250,10 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, request *openrtb2.B
 				}
 
 				// Only do this for request from mobile app
-				if request.App != nil {
+				if bidderRequest.BidRequest.App != nil {
 					for i := 0; i < len(bidResponse.Bids); i++ {
 						if bidResponse.Bids[i].BidType == openrtb_ext.BidTypeNative {
-							nativeMarkup, moreErrs := addNativeTypes(bidResponse.Bids[i].Bid, request)
+							nativeMarkup, moreErrs := addNativeTypes(bidResponse.Bids[i].Bid, bidderRequest.BidRequest)
 							errs = append(errs, moreErrs...)
 
 							if nativeMarkup != nil {
@@ -244,6 +268,18 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, request *openrtb2.B
 					}
 				}
 
+				if len(bidderRequest.BidderStoredResponses) > 0 {
+					//set imp ids back to response for bids with stored responses
+					for i := 0; i < len(bidResponse.Bids); i++ {
+						if httpInfo.request.Uri == "" {
+							reqBody := string(httpInfo.request.Body)
+							re := regexp.MustCompile(ImpIdReqBody)
+							reqBodySplit := re.Split(reqBody, -1)
+							bidResponse.Bids[i].Bid.ImpID = reqBodySplit[1]
+						}
+					}
+				}
+
 				if err == nil {
 					// Conversion rate found, using it for conversion
 					for i := 0; i < len(bidResponse.Bids); i++ {
@@ -271,7 +307,6 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, request *openrtb2.B
 			errs = append(errs, httpInfo.err)
 		}
 	}
-
 	return seatBid, errs
 }
 
@@ -543,3 +578,22 @@ func (bidder *bidderAdapter) addClientTrace(ctx context.Context) context.Context
 	}
 	return httptrace.WithClientTrace(ctx, trace)
 }
+
+func prepareStoredResponse(impId string, bidResp json.RawMessage) *httpCallInfo {
+	//always one element in reqData because stored response is mapped to single imp
+	body := fmt.Sprintf("%s%s", ImpIdReqBody, impId)
+	reqDataForStoredResp := adapters.RequestData{
+		Method: "POST",
+		Uri:    "",
+		Body:   []byte(body), //use it to pass imp id for stored resp
+	}
+	respData := &httpCallInfo{
+		request: &reqDataForStoredResp,
+		response: &adapters.ResponseData{
+			StatusCode: 200,
+			Body:       bidResp,
+		},
+		err: nil,
+	}
+	return respData
+}
diff --git a/exchange/bidder_validate_bids.go b/exchange/bidder_validate_bids.go
index d58c9ebb..a1383271 100644
--- a/exchange/bidder_validate_bids.go
+++ b/exchange/bidder_validate_bids.go
@@ -9,7 +9,6 @@ import (
 	"github.com/mxmCherry/openrtb/v15/openrtb2"
 	"github.com/prebid/prebid-server/adapters"
 	"github.com/prebid/prebid-server/currency"
-	"github.com/prebid/prebid-server/openrtb_ext"
 	goCurrency "golang.org/x/text/currency"
 )
 
@@ -28,9 +27,9 @@ type validatedBidder struct {
 	bidder adaptedBidder
 }
 
-func (v *validatedBidder) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) (*pbsOrtbSeatBid, []error) {
-	seatBid, errs := v.bidder.requestBid(ctx, request, name, bidAdjustment, conversions, reqInfo, accountDebugAllowed, headerDebugAllowed)
-	if validationErrors := removeInvalidBids(request, seatBid); len(validationErrors) > 0 {
+func (v *validatedBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) (*pbsOrtbSeatBid, []error) {
+	seatBid, errs := v.bidder.requestBid(ctx, bidderRequest, bidAdjustment, conversions, reqInfo, accountDebugAllowed, headerDebugAllowed)
+	if validationErrors := removeInvalidBids(bidderRequest.BidRequest, seatBid); len(validationErrors) > 0 {
 		errs = append(errs, validationErrors...)
 	}
 	return seatBid, errs
diff --git a/exchange/exchange.go b/exchange/exchange.go
index 20527e66..fd622efa 100644
--- a/exchange/exchange.go
+++ b/exchange/exchange.go
@@ -24,6 +24,7 @@ import (
 	"github.com/prebid/prebid-server/openrtb_ext"
 	"github.com/prebid/prebid-server/prebid_cache_client"
 	"github.com/prebid/prebid-server/stored_requests"
+	"github.com/prebid/prebid-server/stored_responses"
 	"github.com/prebid/prebid-server/usersync"
 	"github.com/prebid/prebid-server/util/maputil"
 
@@ -166,18 +167,21 @@ type AuctionRequest struct {
 	LegacyLabels   metrics.Labels
 	FirstPartyData map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData
 	// map of imp id to stored response
-	StoredAuctionResponses map[string]json.RawMessage
+	StoredAuctionResponses stored_responses.ImpsWithBidResponses
 	TCF2ConfigBuilder      gdpr.TCF2ConfigBuilder
 	GDPRPermissionsBuilder gdpr.PermissionsBuilder
+	// map of imp id to bidder to stored response
+	StoredBidResponses stored_responses.ImpBidderStoredResp
 }
 
 // BidderRequest holds the bidder specific request and all other
 // information needed to process that bidder request.
 type BidderRequest struct {
-	BidRequest     *openrtb2.BidRequest
-	BidderName     openrtb_ext.BidderName
-	BidderCoreName openrtb_ext.BidderName
-	BidderLabels   metrics.AdapterLabels
+	BidRequest            *openrtb2.BidRequest
+	BidderName            openrtb_ext.BidderName
+	BidderCoreName        openrtb_ext.BidderName
+	BidderLabels          metrics.AdapterLabels
+	BidderStoredResponses map[string]json.RawMessage
 }
 
 func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog *DebugLog) (*openrtb2.BidResponse, error) {
@@ -511,7 +515,7 @@ func (e *exchange) getAllBids(
 			reqInfo.PbsEntryPoint = bidderRequest.BidderLabels.RType
 			reqInfo.GlobalPrivacyControlHeader = globalPrivacyControlHeader
 
-			bids, err := e.adapterMap[bidderRequest.BidderCoreName].requestBid(ctx, bidderRequest.BidRequest, bidderRequest.BidderName, adjustmentFactor, conversions, &reqInfo, accountDebugAllowed, headerDebugAllowed)
+			bids, err := e.adapterMap[bidderRequest.BidderCoreName].requestBid(ctx, bidderRequest, adjustmentFactor, conversions, &reqInfo, accountDebugAllowed, headerDebugAllowed)
 
 			// Add in time reporting
 			elapsed := time.Since(start)
diff --git a/exchange/utils.go b/exchange/utils.go
index 429d29b9..a3f3f4c4 100644
--- a/exchange/utils.go
+++ b/exchange/utils.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	"encoding/json"
 	"fmt"
+	"github.com/prebid/prebid-server/stored_responses"
 	"math/rand"
 
 	"github.com/buger/jsonparser"
@@ -47,14 +48,18 @@ func cleanOpenRTBRequests(ctx context.Context,
 ) (allowedBidderRequests []BidderRequest, privacyLabels metrics.PrivacyLabels, errs []error) {
 
 	req := auctionReq.BidRequestWrapper
-	impsByBidder, err := splitImps(req.BidRequest.Imp)
-	if err != nil {
-		errs = []error{err}
+	aliases, errs := parseAliases(req.BidRequest)
+	if len(errs) > 0 {
 		return
 	}
 
-	aliases, errs := parseAliases(req.BidRequest)
-	if len(errs) > 0 {
+	allowedBidderRequests = make([]BidderRequest, 0, 0)
+
+	bidderImpWithBidResp := stored_responses.InitStoredBidResponses(req.BidRequest, auctionReq.StoredBidResponses)
+
+	impsByBidder, err := splitImps(req.BidRequest.Imp)
+	if err != nil {
+		errs = []error{err}
 		return
 	}
 
@@ -66,9 +71,9 @@ func cleanOpenRTBRequests(ctx context.Context,
 	var allBidderRequests []BidderRequest
 	allBidderRequests, errs = getAuctionBidderRequests(auctionReq, requestExt, bidderToSyncerKey, impsByBidder, aliases)
 
-	if len(allBidderRequests) == 0 {
-		return
-	}
+	bidderNameToBidderReq := buildBidResponseRequest(req.BidRequest, bidderImpWithBidResp, aliases)
+	//this function should be executed after getAuctionBidderRequests
+	allBidderRequests = mergeBidderRequests(allBidderRequests, bidderNameToBidderReq)
 
 	gdprSignal, err := extractGDPR(req.BidRequest)
 	if err != nil {
@@ -113,7 +118,6 @@ func cleanOpenRTBRequests(ctx context.Context,
 	}
 
 	// bidder level privacy policies
-	allowedBidderRequests = make([]BidderRequest, 0, len(allBidderRequests))
 	for _, bidderRequest := range allBidderRequests {
 		bidRequestAllowed := true
 
@@ -145,6 +149,7 @@ func cleanOpenRTBRequests(ctx context.Context,
 
 		if bidRequestAllowed {
 			privacyEnforcement.Apply(bidderRequest.BidRequest)
+
 			allowedBidderRequests = append(allowedBidderRequests, bidderRequest)
 		}
 	}
@@ -729,3 +734,51 @@ func applyFPD(fpd *firstpartydata.ResolvedFirstPartyData, bidReq *openrtb2.BidRe
 		bidReq.User = fpd.User
 	}
 }
+
+func buildBidResponseRequest(req *openrtb2.BidRequest,
+	bidderImpResponses stored_responses.BidderImpsWithBidResponses,
+	aliases map[string]string) map[openrtb_ext.BidderName]BidderRequest {
+	bidderToBidderResponse := make(map[openrtb_ext.BidderName]BidderRequest)
+	for bidderName, impResps := range bidderImpResponses {
+		resolvedBidder := resolveBidder(string(bidderName), aliases)
+		bidderToBidderResponse[bidderName] = BidderRequest{
+			BidRequest:            req,
+			BidderCoreName:        resolvedBidder,
+			BidderName:            bidderName,
+			BidderStoredResponses: impResps,
+			BidderLabels:          metrics.AdapterLabels{Adapter: resolvedBidder},
+		}
+	}
+	return bidderToBidderResponse
+}
+
+func mergeBidderRequests(allBidderRequests []BidderRequest, bidderNameToBidderReq map[openrtb_ext.BidderName]BidderRequest) []BidderRequest {
+	if len(allBidderRequests) == 0 && len(bidderNameToBidderReq) == 0 {
+		return allBidderRequests
+	}
+	if len(allBidderRequests) == 0 && len(bidderNameToBidderReq) > 0 {
+		for _, v := range bidderNameToBidderReq {
+			allBidderRequests = append(allBidderRequests, v)
+		}
+		return allBidderRequests
+	} else if len(allBidderRequests) > 0 && len(bidderNameToBidderReq) > 0 {
+		//merge bidder requests with real imps and imps with stored resp
+		for bn, br := range bidderNameToBidderReq {
+			found := false
+			for i, ar := range allBidderRequests {
+				if ar.BidderName == bn {
+					//bidder req with real imps and imps with stored resp
+					allBidderRequests[i].BidderStoredResponses = br.BidderStoredResponses
+					found = true
+					break
+				}
+			}
+			if !found {
+				//bidder req with stored bid responses only
+				br.BidRequest.Imp = nil // to indicate this bidder request has bidder responses only
+				allBidderRequests = append(allBidderRequests, br)
+			}
+		}
+	}
+	return allBidderRequests
+}
diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go
index 524ab5e5..de33aa57 100644
--- a/openrtb_ext/bidders.go
+++ b/openrtb_ext/bidders.go
@@ -77,6 +77,7 @@ func IsBidderNameReserved(name string) bool {
 // Please keep this list alphabetized to minimize merge conflicts.
 const (
 	Bidder33Across          BidderName = "33across"
+	BidderAax               BidderName = "aax"
 	BidderAceex             BidderName = "aceex"
 	BidderAcuityAds         BidderName = "acuityads"
 	BidderAdf               BidderName = "adf"
@@ -225,6 +226,7 @@ const (
 func CoreBidderNames() []BidderName {
 	return []BidderName{
 		Bidder33Across,
+		BidderAax,
 		BidderAceex,
 		BidderAcuityAds,
 		BidderAdf,
diff --git a/openrtb_ext/imp.go b/openrtb_ext/imp.go
index 3331ce21..0afc3225 100644
--- a/openrtb_ext/imp.go
+++ b/openrtb_ext/imp.go
@@ -12,6 +12,9 @@ type ExtImpPrebid struct {
 	// StoredResponse specifies which stored impression to use, if any.
 	StoredAuctionResponse *ExtStoredAuctionResponse `json:"storedauctionresponse,omitempty"`
 
+	// Stored bid response determines if imp has stored bid response for bidder
+	StoredBidResponse []ExtStoredBidResponse `json:"storedbidresponse,omitempty"`
+
 	// IsRewardedInventory is a signal intended for video impressions. Must be 0 or 1.
 	IsRewardedInventory int8 `json:"is_rewarded_inventory"`
 
@@ -31,6 +34,12 @@ type ExtStoredAuctionResponse struct {
 	ID string `json:"id"`
 }
 
+// ExtStoredBidResponse defines the contract for bidrequest.imp[i].ext.prebid.storedbidresponse
+type ExtStoredBidResponse struct {
+	ID     string `json:"id"`
+	Bidder string `json:"bidder"`
+}
+
 type Options struct {
 	EchoVideoAttrs bool `json:"echovideoattrs"`
 }
diff --git a/openrtb_ext/imp_aax.go b/openrtb_ext/imp_aax.go
new file mode 100644
index 00000000..dc2d68d0
--- /dev/null
+++ b/openrtb_ext/imp_aax.go
@@ -0,0 +1,6 @@
+package openrtb_ext
+
+type ExtImpAax struct {
+	Cid  string `json:"cid"`
+	Crid string `json:"crid"`
+}
diff --git a/server/listener.go b/server/listener.go
index c1f57723..065cb2de 100644
--- a/server/listener.go
+++ b/server/listener.go
@@ -71,3 +71,7 @@ func (ln tcpKeepAliveListener) Accept() (net.Conn, error) {
 	tc.SetKeepAlivePeriod(3 * time.Minute)
 	return tc, nil
 }
+
+type unixListener struct{ *net.UnixListener }
+
+func (ln unixListener) Accept() (net.Conn, error) { return ln.AcceptUnix() }
diff --git a/server/server.go b/server/server.go
index 46b7e5ae..ce77fdc8 100644
--- a/server/server.go
+++ b/server/server.go
@@ -19,7 +19,7 @@ import (
 )
 
 // Listen blocks forever, serving PBS requests on the given port. This will block forever, until the process is shut down.
-func Listen(cfg *config.Configuration, handler http.Handler, adminHandler http.Handler, metrics *metricsconfig.DetailedMetricsEngine) {
+func Listen(cfg *config.Configuration, handler http.Handler, adminHandler http.Handler, metrics *metricsconfig.DetailedMetricsEngine) (err error) {
 	stopSignals := make(chan os.Signal)
 	signal.Notify(stopSignals, syscall.SIGTERM, syscall.SIGINT)
 
@@ -32,36 +32,54 @@ func Listen(cfg *config.Configuration, handler http.Handler, adminHandler http.H
 	adminServer := newAdminServer(cfg, adminHandler)
 	go shutdownAfterSignals(adminServer, stopAdmin, done)
 
-	mainServer := newMainServer(cfg, handler)
-	go shutdownAfterSignals(mainServer, stopMain, done)
-
-	mainListener, err := newListener(mainServer.Addr, metrics)
-	if err != nil {
-		glog.Errorf("Error listening for TCP connections on %s: %v for main server", mainServer.Addr, err)
-		return
+	if cfg.UnixSocketEnable && len(cfg.UnixSocketName) > 0 { // start the unix_socket server if config enable-it.
+		var (
+			socketListener net.Listener
+			mainServer     = newSocketServer(cfg, handler)
+		)
+		go shutdownAfterSignals(mainServer, stopMain, done)
+		if socketListener, err = newUnixListener(mainServer.Addr, metrics); err != nil {
+			glog.Errorf("Error listening for Unix-Socket connections on path %s: %v for socket server", mainServer.Addr, err)
+			return
+		}
+		go runServer(mainServer, "UnixSocket", socketListener)
+	} else { // start the TCP server
+		var (
+			mainListener net.Listener
+			mainServer   = newMainServer(cfg, handler)
+		)
+		go shutdownAfterSignals(mainServer, stopMain, done)
+		if mainListener, err = newTCPListener(mainServer.Addr, metrics); err != nil {
+			glog.Errorf("Error listening for TCP connections on %s: %v for main server", mainServer.Addr, err)
+			return
+		}
+		go runServer(mainServer, "Main", mainListener)
 	}
-	adminListener, err := newListener(adminServer.Addr, nil)
-	if err != nil {
+
+	var adminListener net.Listener
+	if adminListener, err = newTCPListener(adminServer.Addr, nil); err != nil {
 		glog.Errorf("Error listening for TCP connections on %s: %v for admin server", adminServer.Addr, err)
 		return
 	}
-	go runServer(mainServer, "Main", mainListener)
 	go runServer(adminServer, "Admin", adminListener)
 
 	if cfg.Metrics.Prometheus.Port != 0 {
-		prometheusServer := newPrometheusServer(cfg, metrics)
+		var (
+			prometheusListener net.Listener
+			prometheusServer   = newPrometheusServer(cfg, metrics)
+		)
 		go shutdownAfterSignals(prometheusServer, stopPrometheus, done)
-		prometheusListener, err := newListener(prometheusServer.Addr, nil)
-		if err != nil {
+		if prometheusListener, err = newTCPListener(prometheusServer.Addr, nil); err != nil {
 			glog.Errorf("Error listening for TCP connections on %s: %v for prometheus server", adminServer.Addr, err)
 			return
 		}
-		go runServer(prometheusServer, "Prometheus", prometheusListener)
 
+		go runServer(prometheusServer, "Prometheus", prometheusListener)
 		wait(stopSignals, done, stopMain, stopAdmin, stopPrometheus)
 	} else {
 		wait(stopSignals, done, stopMain, stopAdmin)
 	}
+
 	return
 }
 
@@ -87,13 +105,39 @@ func newMainServer(cfg *config.Configuration, handler http.Handler) *http.Server
 
 }
 
-func runServer(server *http.Server, name string, listener net.Listener) {
+func newSocketServer(cfg *config.Configuration, handler http.Handler) *http.Server {
+	var serverHandler = handler
+	if cfg.EnableGzip {
+		serverHandler = gziphandler.GzipHandler(handler)
+	}
+
+	return &http.Server{
+		Addr:         cfg.UnixSocketName,
+		Handler:      serverHandler,
+		ReadTimeout:  15 * time.Second,
+		WriteTimeout: 15 * time.Second,
+	}
+}
+
+func runServer(server *http.Server, name string, listener net.Listener) (err error) {
+	if server == nil {
+		err = fmt.Errorf(">> Server is a nil_ptr.")
+		glog.Errorf("%s server quit with error: %v", name, err)
+		return
+	} else if listener == nil {
+		err = fmt.Errorf(">> Listener is a nil.")
+		glog.Errorf("%s server quit with error: %v", name, err)
+		return
+	}
+
 	glog.Infof("%s server starting on: %s", name, server.Addr)
-	err := server.Serve(listener)
-	glog.Errorf("%s server quit with error: %v", name, err)
+	if err = server.Serve(listener); err != nil {
+		glog.Errorf("%s server quit with error: %v", name, err)
+	}
+	return
 }
 
-func newListener(address string, metrics metrics.MetricsEngine) (net.Listener, error) {
+func newTCPListener(address string, metrics metrics.MetricsEngine) (net.Listener, error) {
 	ln, err := net.Listen("tcp", address)
 	if err != nil {
 		return nil, fmt.Errorf("Error listening for TCP connections on %s: %v", address, err)
@@ -113,6 +157,25 @@ func newListener(address string, metrics metrics.MetricsEngine) (net.Listener, e
 	return ln, nil
 }
 
+func newUnixListener(address string, metrics metrics.MetricsEngine) (net.Listener, error) {
+	ln, err := net.Listen("unix", address)
+	if err != nil {
+		return nil, fmt.Errorf("Error listening for Unix-Socket connections on path %s: %v", address, err)
+	}
+
+	if casted, ok := ln.(*net.UnixListener); ok {
+		ln = &unixListener{casted}
+	} else {
+		glog.Warning("net.Listen(\"unix\", \"addr\") didn't return an UnixListener.")
+	}
+
+	if metrics != nil {
+		ln = &monitorableListener{ln, metrics}
+	}
+
+	return ln, nil
+}
+
 func wait(inbound <-chan os.Signal, done <-chan struct{}, outbound ...chan<- os.Signal) {
 	sig := <-inbound
 
diff --git a/static/bidder-info/aax.yaml b/static/bidder-info/aax.yaml
new file mode 100644
index 00000000..e08cb559
--- /dev/null
+++ b/static/bidder-info/aax.yaml
@@ -0,0 +1,19 @@
+maintainer:
+  email: product@aax.media
+gvlVendorID: 720
+modifyingVastXmlAllowed: true
+capabilities:
+  app:
+    mediaTypes:
+      - banner
+      - video
+      - native
+  site:
+    mediaTypes:
+      - banner
+      - video
+      - native
+userSync:
+  redirect:
+    url: https://c.aaxads.com/aacxc.php?fv=1&wbsh=psa&ryvlg=setstatuscode&bidder=aax&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect={{.RedirectURL}}
+    userMacro: <vsid>
\ No newline at end of file
diff --git a/static/bidder-info/medianet.yaml b/static/bidder-info/medianet.yaml
index b9363510..b4b5a211 100644
--- a/static/bidder-info/medianet.yaml
+++ b/static/bidder-info/medianet.yaml
@@ -16,4 +16,4 @@ capabilities:
 userSync:
   redirect:
     url: https://hbx.media.net/cksync.php?cs=1&type=pbs&ovsid=setstatuscode&bidder=medianet&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect={{.RedirectURL}}
-    userMacro: "${UUID}"
\ No newline at end of file
+    userMacro: "<vsid>"
\ No newline at end of file
diff --git a/static/bidder-params/aax.json b/static/bidder-params/aax.json
new file mode 100644
index 00000000..d13e73a0
--- /dev/null
+++ b/static/bidder-params/aax.json
@@ -0,0 +1,22 @@
+{
+    "$schema": "http://json-schema.org/draft-04/schema#",
+    "title": "Aax Adapter Params",
+    "description": "A schema which validates params accepted by the Aax adapter",
+    "type": "object",
+    "properties": {
+        "cid": {
+            "type": "string",
+            "minLength": 1,
+            "description": "The customer id provided by AAX."
+        },
+        "crid": {
+            "type": "string",
+            "minLength": 1,
+            "description": "The placement id provided by AAX."
+        }
+    },
+    "required": [
+        "cid",
+        "crid"
+    ]
+}
\ No newline at end of file
diff --git a/stored_responses/stored_responses.go b/stored_responses/stored_responses.go
new file mode 100644
index 00000000..f3540600
--- /dev/null
+++ b/stored_responses/stored_responses.go
@@ -0,0 +1,179 @@
+package stored_responses
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"github.com/buger/jsonparser"
+	"github.com/mxmCherry/openrtb/v15/openrtb2"
+	"github.com/prebid/prebid-server/openrtb_ext"
+	"github.com/prebid/prebid-server/stored_requests"
+)
+
+type ImpsWithAuctionResponseIDs map[string]string
+type ImpBiddersWithBidResponseIDs map[string]map[string]string
+type StoredResponseIDs []string
+type StoredResponseIdToStoredResponse map[string]json.RawMessage
+type BidderImpsWithBidResponses map[openrtb_ext.BidderName]map[string]json.RawMessage
+type ImpsWithBidResponses map[string]json.RawMessage
+type ImpBidderStoredResp map[string]map[string]json.RawMessage
+
+func InitStoredBidResponses(req *openrtb2.BidRequest, storedBidResponses ImpBidderStoredResp) BidderImpsWithBidResponses {
+	removeImpsWithStoredResponses(req, storedBidResponses)
+	return buildStoredResp(storedBidResponses)
+}
+
+// removeImpsWithStoredResponses deletes imps with stored bid resp
+func removeImpsWithStoredResponses(req *openrtb2.BidRequest, storedBidResponses ImpBidderStoredResp) {
+	imps := req.Imp
+	req.Imp = nil //to indicate this bidder doesn't have real requests
+	for _, imp := range imps {
+		if _, ok := storedBidResponses[imp.ID]; !ok {
+			//add real imp back to request
+			req.Imp = append(req.Imp, imp)
+		}
+	}
+}
+
+func buildStoredResp(storedBidResponses ImpBidderStoredResp) BidderImpsWithBidResponses {
+	// bidder -> imp id -> stored bid resp
+	bidderToImpToResponses := BidderImpsWithBidResponses{}
+	for impID, storedData := range storedBidResponses {
+		for bidderName, storedResp := range storedData {
+			if _, ok := bidderToImpToResponses[openrtb_ext.BidderName(bidderName)]; !ok {
+				//new bidder with stored bid responses
+				impToStoredResp := ImpsWithBidResponses{}
+				impToStoredResp[impID] = storedResp
+				bidderToImpToResponses[openrtb_ext.BidderName(bidderName)] = impToStoredResp
+			} else {
+				bidderToImpToResponses[openrtb_ext.BidderName(bidderName)][impID] = storedResp
+			}
+		}
+	}
+	return bidderToImpToResponses
+}
+
+func extractStoredResponsesIds(impInfo []ImpExtPrebidData,
+	bidderMap map[string]openrtb_ext.BidderName) (
+	StoredResponseIDs,
+	ImpBiddersWithBidResponseIDs,
+	ImpsWithAuctionResponseIDs, error,
+) {
+	// extractStoredResponsesIds returns:
+	// 1) all stored responses ids from all imps
+	allStoredResponseIDs := StoredResponseIDs{}
+	// 2) stored bid responses: imp id to bidder to stored response id
+	impBiddersWithBidResponseIDs := ImpBiddersWithBidResponseIDs{}
+	// 3) imp id to stored resp id
+	impAuctionResponseIDs := ImpsWithAuctionResponseIDs{}
+
+	for index, impData := range impInfo {
+		impId, err := jsonparser.GetString(impData.Imp, "id")
+		if err != nil {
+			return nil, nil, nil, fmt.Errorf("request.imp[%d] missing required field: \"id\"", index)
+		}
+
+		if impData.ImpExtPrebid.StoredAuctionResponse != nil {
+			if len(impData.ImpExtPrebid.StoredAuctionResponse.ID) == 0 {
+				return nil, nil, nil, fmt.Errorf("request.imp[%d] has ext.prebid.storedauctionresponse specified, but \"id\" field is missing ", index)
+			}
+			allStoredResponseIDs = append(allStoredResponseIDs, impData.ImpExtPrebid.StoredAuctionResponse.ID)
+
+			impAuctionResponseIDs[impId] = impData.ImpExtPrebid.StoredAuctionResponse.ID
+
+		}
+		if len(impData.ImpExtPrebid.StoredBidResponse) > 0 {
+
+			bidderStoredRespId := make(map[string]string)
+			for _, bidderResp := range impData.ImpExtPrebid.StoredBidResponse {
+				if len(bidderResp.ID) == 0 || len(bidderResp.Bidder) == 0 {
+					return nil, nil, nil, fmt.Errorf("request.imp[%d] has ext.prebid.storedbidresponse specified, but \"id\" or/and \"bidder\" fields are missing ", index)
+				}
+				//check if bidder is valid/exists
+				if _, isValid := bidderMap[bidderResp.Bidder]; !isValid {
+					return nil, nil, nil, fmt.Errorf("request.imp[impId: %s].ext contains unknown bidder: %s. Did you forget an alias in request.ext.prebid.aliases?", impId, bidderResp.Bidder)
+				}
+				// bidder is unique per one bid stored response
+				// if more than one bidder specified the last defined bidder id will take precedence
+				bidderStoredRespId[bidderResp.Bidder] = bidderResp.ID
+				impBiddersWithBidResponseIDs[impId] = bidderStoredRespId
+				//storedAuctionResponseIds are not unique, but fetch will return single data for repeated ids
+				allStoredResponseIDs = append(allStoredResponseIDs, bidderResp.ID)
+			}
+		}
+	}
+	return allStoredResponseIDs, impBiddersWithBidResponseIDs, impAuctionResponseIDs, nil
+}
+
+// ProcessStoredResponses takes the incoming request as JSON with any
+// stored requests/imps already merged into it, scans it to find any stored auction response ids and stored bid response ids
+// in the request/imps and produces a map of imp IDs to stored auction responses and map of imp to bidder to stored response.
+// Note that processStoredResponses must be called after processStoredRequests
+// because stored imps and stored requests can contain stored auction responses and stored bid responses
+// so the stored requests/imps have to be merged into the incoming request prior to processing stored auction responses.
+func ProcessStoredResponses(ctx context.Context, requestJson []byte, storedRespFetcher stored_requests.Fetcher, bidderMap map[string]openrtb_ext.BidderName) (ImpsWithBidResponses, ImpBidderStoredResp, []error) {
+	impInfo, errs := parseImpInfo(requestJson)
+	if len(errs) > 0 {
+		return nil, nil, errs
+	}
+	storedResponsesIds, impBidderToStoredBidResponseId, impIdToRespId, err := extractStoredResponsesIds(impInfo, bidderMap)
+	if err != nil {
+		return nil, nil, append(errs, err)
+	}
+
+	if len(storedResponsesIds) > 0 {
+		storedResponses, errs := storedRespFetcher.FetchResponses(ctx, storedResponsesIds)
+		if len(errs) > 0 {
+			return nil, nil, errs
+		}
+
+		impIdToStoredResp, impBidderToStoredBidResponse := buildStoredResponsesMaps(storedResponses, impBidderToStoredBidResponseId, impIdToRespId)
+		return impIdToStoredResp, impBidderToStoredBidResponse, nil
+	}
+	return nil, nil, nil
+}
+
+func buildStoredResponsesMaps(storedResponses StoredResponseIdToStoredResponse, impBidderToStoredBidResponseId ImpBiddersWithBidResponseIDs, impIdToRespId ImpsWithAuctionResponseIDs) (ImpsWithBidResponses, ImpBidderStoredResp) {
+	//imp id to stored resp body
+	impIdToStoredResp := ImpsWithBidResponses{}
+	//stored bid responses: imp id to bidder to stored response body
+	impBidderToStoredBidResponse := ImpBidderStoredResp{}
+
+	for impId, respId := range impIdToRespId {
+		impIdToStoredResp[impId] = storedResponses[respId]
+	}
+
+	for impId, bidderStoredResp := range impBidderToStoredBidResponseId {
+		bidderStoredResponses := StoredResponseIdToStoredResponse{}
+		for bidderName, id := range bidderStoredResp {
+			bidderStoredResponses[bidderName] = storedResponses[id]
+		}
+		impBidderToStoredBidResponse[impId] = bidderStoredResponses
+	}
+	return impIdToStoredResp, impBidderToStoredBidResponse
+}
+
+// parseImpInfo parses the request JSON and returns the impressions with their unmarshalled imp.ext.prebid
+// copied from exchange to isolate stored responses code from auction dependencies
+func parseImpInfo(requestJson []byte) (impData []ImpExtPrebidData, errs []error) {
+
+	if impArray, dataType, _, err := jsonparser.Get(requestJson, "imp"); err == nil && dataType == jsonparser.Array {
+		_, err = jsonparser.ArrayEach(impArray, func(imp []byte, _ jsonparser.ValueType, _ int, err error) {
+			impExtData, _, _, err := jsonparser.Get(imp, "ext", "prebid")
+			var impExtPrebid openrtb_ext.ExtImpPrebid
+			if impExtData != nil {
+				if err := json.Unmarshal(impExtData, &impExtPrebid); err != nil {
+					errs = append(errs, err)
+				}
+			}
+			newImpData := ImpExtPrebidData{imp, impExtPrebid}
+			impData = append(impData, newImpData)
+		})
+	}
+	return
+}
+
+type ImpExtPrebidData struct {
+	Imp          json.RawMessage
+	ImpExtPrebid openrtb_ext.ExtImpPrebid
+}


Test patch:
diff --git a/adapters/aax/aax_test.go b/adapters/aax/aax_test.go
new file mode 100644
index 00000000..7ff6e610
--- /dev/null
+++ b/adapters/aax/aax_test.go
@@ -0,0 +1,30 @@
+package aax
+
+import (
+	"github.com/stretchr/testify/assert"
+	"testing"
+
+	"github.com/prebid/prebid-server/adapters/adapterstest"
+	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+func TestJsonSamples(t *testing.T) {
+	bidder, buildErr := Builder(openrtb_ext.BidderAax, config.Adapter{
+		Endpoint:         "https://example.aax.media/rtb/prebid",
+		ExtraAdapterInfo: "http://localhost:8080/extrnal_url",
+	})
+
+	if buildErr != nil {
+		t.Fatalf("Builder returned unexpected error %v", buildErr)
+	}
+
+	adapterstest.RunJSONBidderTest(t, "aaxtest", bidder)
+}
+
+func TestEndpointTemplateMalformed(t *testing.T) {
+	_, buildErr := Builder(openrtb_ext.BidderAax, config.Adapter{
+		Endpoint: "{{Malformed}}"})
+
+	assert.Nil(t, buildErr)
+}
diff --git a/adapters/aax/aaxtest/exemplary/multi-format.json b/adapters/aax/aaxtest/exemplary/multi-format.json
new file mode 100644
index 00000000..493f268c
--- /dev/null
+++ b/adapters/aax/aaxtest/exemplary/multi-format.json
@@ -0,0 +1,80 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "imp": [
+            {
+                "id": "1",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 320,
+                            "h": 50
+                        }
+                    ]
+                },
+                "video": {
+                    "mimes": [
+                        "video/mp4"
+                    ],
+                    "protocols": [
+                        2,
+                        5
+                    ],
+                    "w": 320,
+                    "h": 480
+                },
+                "ext": {
+                    "bidder": {
+                        "cid": "TCID",
+                        "crid": "1234"
+                    }
+                }
+            }
+        ]
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "https://example.aax.media/rtb/prebid?src=http%3A%2F%2Flocalhost%3A8080%2Fextrnal_url",
+                "body": {
+                    "id": "test-request-id",
+                    "imp": [
+                        {
+                            "id": "1",
+                            "banner": {
+                                "format": [
+                                    {
+                                        "w": 320,
+                                        "h": 50
+                                    }
+                                ]
+                            },
+                            "video": {
+                                "mimes": [
+                                    "video/mp4"
+                                ],
+                                "protocols": [
+                                    2,
+                                    5
+                                ],
+                                "w": 320,
+                                "h": 480
+                            },
+                            "ext": {
+                                "bidder": {
+                                    "cid": "TCID",
+                                    "crid": "1234"
+                                }
+                            }
+                        }
+                    ]
+                }
+            },
+            "mockResponse": {
+                "status": 204,
+                "body": ""
+            }
+        }
+    ],
+    "expectedBidResponses": []
+}
\ No newline at end of file
diff --git a/adapters/aax/aaxtest/exemplary/multi-imps.json b/adapters/aax/aaxtest/exemplary/multi-imps.json
new file mode 100644
index 00000000..9ad02293
--- /dev/null
+++ b/adapters/aax/aaxtest/exemplary/multi-imps.json
@@ -0,0 +1,129 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "imp": [
+            {
+                "id": "1",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 320,
+                            "h": 50
+                        }
+                    ]
+                },
+                "ext": {
+                    "bidder": {
+                        "cid": "TCID",
+                        "crid": "1234"
+                    }
+                }
+            },
+            {
+                "id": "2",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 300,
+                            "h": 50
+                        }
+                    ]
+                },
+                "ext": {
+                    "bidder": {
+                        "cid": "TCID",
+                        "crid": "1234"
+                    }
+                }
+            }
+        ]
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "https://example.aax.media/rtb/prebid?src=http%3A%2F%2Flocalhost%3A8080%2Fextrnal_url",
+                "body": {
+                    "id": "test-request-id",
+                    "imp": [
+                        {
+                            "id": "1",
+                            "banner": {
+                                "format": [
+                                    {
+                                        "w": 320,
+                                        "h": 50
+                                    }
+                                ]
+                            },
+                            "ext": {
+                                "bidder": {
+                                    "cid": "TCID",
+                                    "crid": "1234"
+                                }
+                            }
+                        },
+                        {
+                            "id": "2",
+                            "banner": {
+                                "format": [
+                                    {
+                                        "w": 300,
+                                        "h": 50
+                                    }
+                                ]
+                            },
+                            "ext": {
+                                "bidder": {
+                                    "cid": "TCID",
+                                    "crid": "1234"
+                                }
+                            }
+                        }
+                    ]
+                }
+            },
+            "mockResponse": {
+                "status": 200,
+                "body": {
+                    "id": "test-request-id",
+                    "seatbid": [
+                        {
+                            "seat": "aax",
+                            "bid": [
+                                {
+                                    "id": "test-bid-id",
+                                    "impid": "1",
+                                    "price": 1.50,
+                                    "adm": "some-test-ad",
+                                    "crid": "test-crid",
+                                    "h": 50,
+                                    "w": 320
+                                }
+                            ]
+                        }
+                    ],
+                    "cur": "USD"
+                }
+            }
+        }
+    ],
+    "expectedBidResponses": [
+        {
+            "currency": "USD",
+            "bids": [
+                {
+                    "bid": {
+                        "id": "test-bid-id",
+                        "impid": "1",
+                        "price": 1.50,
+                        "adm": "some-test-ad",
+                        "crid": "test-crid",
+                        "w": 320,
+                        "h": 50
+                    },
+                    "type": "banner"
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/adapters/aax/aaxtest/exemplary/no-bid.json b/adapters/aax/aaxtest/exemplary/no-bid.json
new file mode 100644
index 00000000..2ffbdac7
--- /dev/null
+++ b/adapters/aax/aaxtest/exemplary/no-bid.json
@@ -0,0 +1,58 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "imp": [
+            {
+                "id": "1",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 300,
+                            "h": 250
+                        }
+                    ]
+                },
+                "ext": {
+                    "bidder": {
+                        "cid": "TCID",
+                        "crid": "1234"
+                    }
+                }
+            }
+        ]
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "https://example.aax.media/rtb/prebid?src=http%3A%2F%2Flocalhost%3A8080%2Fextrnal_url",
+                "body": {
+                    "id": "test-request-id",
+                    "imp": [
+                        {
+                            "id": "1",
+                            "banner": {
+                                "format": [
+                                    {
+                                        "w": 300,
+                                        "h": 250
+                                    }
+                                ]
+                            },
+                            "ext": {
+                                "bidder": {
+                                    "cid": "TCID",
+                                    "crid": "1234"
+                                }
+                            }
+                        }
+                    ]
+                }
+            },
+            "mockResponse": {
+                "status": 204,
+                "body": {}
+            }
+        }
+    ],
+    "expectedBidResponses": []
+}
\ No newline at end of file
diff --git a/adapters/aax/aaxtest/exemplary/optional-params.json b/adapters/aax/aaxtest/exemplary/optional-params.json
new file mode 100644
index 00000000..37458ed6
--- /dev/null
+++ b/adapters/aax/aaxtest/exemplary/optional-params.json
@@ -0,0 +1,60 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "imp": [
+            {
+                "id": "1",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 300,
+                            "h": 250
+                        }
+                    ]
+                },
+                "ext": {
+                    "bidder": {
+                        "cid": "TCID",
+                        "crid": "1234",
+                        "tagid_src": "ext"
+                    }
+                }
+            }
+        ]
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "https://example.aax.media/rtb/prebid?src=http%3A%2F%2Flocalhost%3A8080%2Fextrnal_url",
+                "body": {
+                    "id": "test-request-id",
+                    "imp": [
+                        {
+                            "id": "1",
+                            "banner": {
+                                "format": [
+                                    {
+                                        "w": 300,
+                                        "h": 250
+                                    }
+                                ]
+                            },
+                            "ext": {
+                                "bidder": {
+                                    "cid": "TCID",
+                                    "crid": "1234",
+                                    "tagid_src": "ext"
+                                }
+                            }
+                        }
+                    ]
+                }
+            },
+            "mockResponse": {
+                "status": 204,
+                "body": {}
+            }
+        }
+    ],
+    "expectedBidResponses": []
+}
\ No newline at end of file
diff --git a/adapters/aax/aaxtest/exemplary/simple-banner.json b/adapters/aax/aaxtest/exemplary/simple-banner.json
new file mode 100644
index 00000000..7e8c0fce
--- /dev/null
+++ b/adapters/aax/aaxtest/exemplary/simple-banner.json
@@ -0,0 +1,95 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "imp": [
+            {
+                "id": "1",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 320,
+                            "h": 50
+                        }
+                    ]
+                },
+                "ext": {
+                    "bidder": {
+                        "cid": "TCID",
+                        "crid": "1234"
+                    }
+                }
+            }
+        ]
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "https://example.aax.media/rtb/prebid?src=http%3A%2F%2Flocalhost%3A8080%2Fextrnal_url",
+                "body": {
+                    "id": "test-request-id",
+                    "imp": [
+                        {
+                            "id": "1",
+                            "banner": {
+                                "format": [
+                                    {
+                                        "w": 320,
+                                        "h": 50
+                                    }
+                                ]
+                            },
+                            "ext": {
+                                "bidder": {
+                                    "cid": "TCID",
+                                    "crid": "1234"
+                                }
+                            }
+                        }
+                    ]
+                }
+            },
+            "mockResponse": {
+                "status": 200,
+                "body": {
+                    "id": "test-request-id",
+                    "seatbid": [
+                        {
+                            "seat": "aax",
+                            "bid": [
+                                {
+                                    "id": "test-bid-id",
+                                    "impid": "1",
+                                    "price": 1.50,
+                                    "adm": "some-test-ad",
+                                    "crid": "test-crid",
+                                    "h": 50,
+                                    "w": 320
+                                }
+                            ]
+                        }
+                    ],
+                    "cur": "USD"
+                }
+            }
+        }
+    ],
+    "expectedBidResponses": [
+        {
+            "currency": "USD",
+            "bids": [
+                {
+                    "bid": {
+                        "id": "test-bid-id",
+                        "impid": "1",
+                        "price": 1.50,
+                        "adm": "some-test-ad",
+                        "crid": "test-crid",
+                        "w": 320,
+                        "h": 50
+                    },
+                    "type": "banner"
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/adapters/aax/aaxtest/exemplary/simple-video.json b/adapters/aax/aaxtest/exemplary/simple-video.json
new file mode 100644
index 00000000..6ee8d3d8
--- /dev/null
+++ b/adapters/aax/aaxtest/exemplary/simple-video.json
@@ -0,0 +1,101 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "imp": [
+            {
+                "id": "1",
+                "video": {
+                    "mimes": [
+                        "video/mp4"
+                    ],
+                    "protocols": [
+                        2,
+                        5
+                    ],
+                    "w": 320,
+                    "h": 480
+                },
+                "ext": {
+                    "bidder": {
+                        "cid": "TCID",
+                        "crid": "1234"
+                    }
+                }
+            }
+        ]
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "https://example.aax.media/rtb/prebid?src=http%3A%2F%2Flocalhost%3A8080%2Fextrnal_url",
+                "body": {
+                    "id": "test-request-id",
+                    "imp": [
+                        {
+                            "id": "1",
+                            "video": {
+                                "mimes": [
+                                    "video/mp4"
+                                ],
+                                "protocols": [
+                                    2,
+                                    5
+                                ],
+                                "w": 320,
+                                "h": 480
+                            },
+                            "ext": {
+                                "bidder": {
+                                    "cid": "TCID",
+                                    "crid": "1234"
+                                }
+                            }
+                        }
+                    ]
+                }
+            },
+            "mockResponse": {
+                "status": 200,
+                "body": {
+                    "id": "test-request-id",
+                    "cur": "USD",
+                    "seatbid": [
+                        {
+                            "seat": "aax",
+                            "bid": [
+                                {
+                                    "id": "test-bid-id",
+                                    "impid": "1",
+                                    "price": 2.50,
+                                    "adm": "some-test-ad",
+                                    "crid": "test-crid",
+                                    "w": 320,
+                                    "h": 480
+                                }
+                            ]
+                        }
+                    ]
+                }
+            }
+        }
+    ],
+    "expectedBidResponses": [
+        {
+            "currency": "USD",
+            "bids": [
+                {
+                    "bid": {
+                        "id": "test-bid-id",
+                        "impid": "1",
+                        "price": 2.50,
+                        "adm": "some-test-ad",
+                        "crid": "test-crid",
+                        "w": 320,
+                        "h": 480
+                    },
+                    "type": "video"
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/adapters/aax/aaxtest/supplemental/invalid-req-400-status-code-bad-request.json b/adapters/aax/aaxtest/supplemental/invalid-req-400-status-code-bad-request.json
new file mode 100644
index 00000000..276e0fc3
--- /dev/null
+++ b/adapters/aax/aaxtest/supplemental/invalid-req-400-status-code-bad-request.json
@@ -0,0 +1,97 @@
+{
+    "mockBidRequest": {
+        "id": "some-request-id",
+        "tmax": 1000,
+        "user": {
+            "buyeruid": "0000-000-000-0000"
+        },
+        "app": {
+            "publisher": {
+                "id": "123456789"
+            },
+            "cat": [
+                "IAB22-1"
+            ],
+            "bundle": "com.app.awesome",
+            "name": "Awesome App",
+            "domain": "awesomeapp.com",
+            "id": "123456789"
+        },
+        "imp": [
+            {
+                "id": "some-impression-id",
+                "video": {
+                    "mimes": [
+                        "video/mp4"
+                    ],
+                    "w": 640,
+                    "h": 480,
+                    "minduration": 120,
+                    "maxduration": 150
+                },
+                "ext": {
+                    "bidder": {
+                        "cid": "TCID",
+                        "crid": "1234"
+                    }
+                }
+            }
+        ]
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "https://example.aax.media/rtb/prebid?src=http%3A%2F%2Flocalhost%3A8080%2Fextrnal_url",
+                "body": {
+                    "id": "some-request-id",
+                    "imp": [
+                        {
+                            "id": "some-impression-id",
+                            "video": {
+                                "mimes": [
+                                    "video/mp4"
+                                ],
+                                "w": 640,
+                                "h": 480,
+                                "minduration": 120,
+                                "maxduration": 150
+                            },
+                            "ext": {
+                                "bidder": {
+                                    "cid": "TCID",
+                                    "crid": "1234"
+                                }
+                            }
+                        }
+                    ],
+                    "app": {
+                        "publisher": {
+                            "id": "123456789"
+                        },
+                        "cat": [
+                            "IAB22-1"
+                        ],
+                        "bundle": "com.app.awesome",
+                        "name": "Awesome App",
+                        "domain": "awesomeapp.com",
+                        "id": "123456789"
+                    },
+                    "user": {
+                        "buyeruid": "0000-000-000-0000"
+                    },
+                    "tmax": 1000
+                }
+            },
+            "mockResponse": {
+                "status": 400
+            }
+        }
+    ],
+    "expectedBidResponses": [],
+    "expectedMakeBidsErrors": [
+        {
+            "value": "Unexpected status code: 400. Run with request.debug = 1 for more info",
+            "comparison": "literal"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/adapters/aax/aaxtest/supplemental/invalid-resp-diff-imp-id.json b/adapters/aax/aaxtest/supplemental/invalid-resp-diff-imp-id.json
new file mode 100644
index 00000000..c415c274
--- /dev/null
+++ b/adapters/aax/aaxtest/supplemental/invalid-resp-diff-imp-id.json
@@ -0,0 +1,125 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "banner": {
+                    "w": 300,
+                    "h": 250
+                },
+                "ext": {
+                    "bidder": {
+                        "cid": "TCID",
+                        "crid": "1234"
+                    }
+                }
+            }
+        ],
+        "site": {
+            "domain": "www.example.com",
+            "page": "http://www.example.com",
+            "publisher": {
+                "domain": "example.com"
+            },
+            "ext": {
+                "amp": 0
+            }
+        },
+        "device": {
+            "ua": "userAgent",
+            "ip": "193.168.244.1"
+        },
+        "at": 1,
+        "tmax": 5000,
+        "cur": [
+            "USD"
+        ],
+        "regs": {
+            "ext": {
+                "gdpr": 0
+            }
+        }
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "https://example.aax.media/rtb/prebid?src=http%3A%2F%2Flocalhost%3A8080%2Fextrnal_url",
+                "body": {
+                    "id": "test-request-id",
+                    "imp": [
+                        {
+                            "id": "test-imp-id",
+                            "banner": {
+                                "w": 300,
+                                "h": 250
+                            },
+                            "ext": {
+                                "bidder": {
+                                    "cid": "TCID",
+                                    "crid": "1234"
+                                }
+                            }
+                        }
+                    ],
+                    "site": {
+                        "domain": "www.example.com",
+                        "page": "http://www.example.com",
+                        "publisher": {
+                            "domain": "example.com"
+                        },
+                        "ext": {
+                            "amp": 0
+                        }
+                    },
+                    "device": {
+                        "ua": "userAgent",
+                        "ip": "193.168.244.1"
+                    },
+                    "at": 1,
+                    "tmax": 5000,
+                    "cur": [
+                        "USD"
+                    ],
+                    "regs": {
+                        "ext": {
+                            "gdpr": 0
+                        }
+                    }
+                }
+            },
+            "mockResponse": {
+                "status": 200,
+                "body": {
+                    "id": "tid",
+                    "seatbid": [
+                        {
+                            "seat": "aax",
+                            "bid": [
+                                {
+                                    "id": "randomid",
+                                    "impid": "some-other-imp-id",
+                                    "price": 0.500000,
+                                    "adid": "12345678",
+                                    "adm": "some-test-ad",
+                                    "cid": "987",
+                                    "crid": "12345678",
+                                    "h": 250,
+                                    "w": 300
+                                }
+                            ]
+                        }
+                    ],
+                    "bidid": "bid01"
+                }
+            }
+        }
+    ],
+    "expectedBidResponses": [],
+    "expectedMakeBidsErrors": [
+        {
+            "value": "unable to fetch mediaType in multi-format: some-other-imp-id",
+            "comparison": "literal"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/adapters/aax/aaxtest/supplemental/invalid-resp-multi-imp-type.json b/adapters/aax/aaxtest/supplemental/invalid-resp-multi-imp-type.json
new file mode 100644
index 00000000..23aa0ae6
--- /dev/null
+++ b/adapters/aax/aaxtest/supplemental/invalid-resp-multi-imp-type.json
@@ -0,0 +1,151 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "banner": {
+                    "w": 300,
+                    "h": 250
+                },
+                "video": {
+                    "mimes": [
+                        "video/mp4"
+                    ],
+                    "w": 640,
+                    "h": 480,
+                    "minduration": 120,
+                    "maxduration": 150
+                },
+                "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": {
+                        "cid": "TCID",
+                        "crid": "1234"
+                    }
+                }
+            }
+        ],
+        "site": {
+            "domain": "www.example.com",
+            "page": "http://www.example.com",
+            "publisher": {
+                "domain": "example.com"
+            },
+            "ext": {
+                "amp": 0
+            }
+        },
+        "device": {
+            "ua": "userAgent",
+            "ip": "193.168.244.1"
+        },
+        "at": 1,
+        "tmax": 5000,
+        "cur": [
+            "USD"
+        ],
+        "regs": {
+            "ext": {
+                "gdpr": 0
+            }
+        }
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "https://example.aax.media/rtb/prebid?src=http%3A%2F%2Flocalhost%3A8080%2Fextrnal_url",
+                "body": {
+                    "id": "test-request-id",
+                    "imp": [
+                        {
+                            "id": "test-imp-id",
+                            "banner": {
+                                "w": 300,
+                                "h": 250
+                            },
+                            "video": {
+                                "mimes": [
+                                    "video/mp4"
+                                ],
+                                "w": 640,
+                                "h": 480,
+                                "minduration": 120,
+                                "maxduration": 150
+                            },
+                            "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": {
+                                    "cid": "TCID",
+                                    "crid": "1234"
+                                }
+                            }
+                        }
+                    ],
+                    "site": {
+                        "domain": "www.example.com",
+                        "page": "http://www.example.com",
+                        "publisher": {
+                            "domain": "example.com"
+                        },
+                        "ext": {
+                            "amp": 0
+                        }
+                    },
+                    "device": {
+                        "ua": "userAgent",
+                        "ip": "193.168.244.1"
+                    },
+                    "at": 1,
+                    "tmax": 5000,
+                    "cur": [
+                        "USD"
+                    ],
+                    "regs": {
+                        "ext": {
+                            "gdpr": 0
+                        }
+                    }
+                }
+            },
+            "mockResponse": {
+                "status": 200,
+                "body": {
+                    "id": "tid",
+                    "seatbid": [
+                        {
+                            "seat": "aax",
+                            "bid": [
+                                {
+                                    "id": "randomid",
+                                    "impid": "test-imp-id",
+                                    "price": 0.500000,
+                                    "adid": "12345678",
+                                    "adm": "some-test-ad",
+                                    "cid": "987",
+                                    "crid": "12345678",
+                                    "h": 250,
+                                    "w": 300
+                                }
+                            ]
+                        }
+                    ],
+                    "bidid": "bid01"
+                }
+            }
+        }
+    ],
+    "expectedBidResponses": [],
+    "expectedMakeBidsErrors": [
+        {
+            "value": "unable to fetch mediaType in multi-format: test-imp-id",
+            "comparison": "literal"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/adapters/aax/aaxtest/supplemental/valid-req-200-bid-response-from-aax.json b/adapters/aax/aaxtest/supplemental/valid-req-200-bid-response-from-aax.json
new file mode 100644
index 00000000..bad87434
--- /dev/null
+++ b/adapters/aax/aaxtest/supplemental/valid-req-200-bid-response-from-aax.json
@@ -0,0 +1,139 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "banner": {
+                    "w": 300,
+                    "h": 250
+                },
+                "ext": {
+                    "bidder": {
+                        "cid": "TCID",
+                        "crid": "1234"
+                    }
+                }
+            }
+        ],
+        "site": {
+            "domain": "www.example.com",
+            "page": "http://www.example.com",
+            "publisher": {
+                "domain": "example.com"
+            },
+            "ext": {
+                "amp": 0
+            }
+        },
+        "device": {
+            "ua": "userAgent",
+            "ip": "193.168.244.1"
+        },
+        "at": 1,
+        "tmax": 5000,
+        "cur": [
+            "USD"
+        ],
+        "regs": {
+            "ext": {
+                "gdpr": 0
+            }
+        }
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "https://example.aax.media/rtb/prebid?src=http%3A%2F%2Flocalhost%3A8080%2Fextrnal_url",
+                "body": {
+                    "id": "test-request-id",
+                    "imp": [
+                        {
+                            "id": "test-imp-id",
+                            "banner": {
+                                "w": 300,
+                                "h": 250
+                            },
+                            "ext": {
+                                "bidder": {
+                                    "cid": "TCID",
+                                    "crid": "1234"
+                                }
+                            }
+                        }
+                    ],
+                    "site": {
+                        "domain": "www.example.com",
+                        "page": "http://www.example.com",
+                        "publisher": {
+                            "domain": "example.com"
+                        },
+                        "ext": {
+                            "amp": 0
+                        }
+                    },
+                    "device": {
+                        "ua": "userAgent",
+                        "ip": "193.168.244.1"
+                    },
+                    "at": 1,
+                    "tmax": 5000,
+                    "cur": [
+                        "USD"
+                    ],
+                    "regs": {
+                        "ext": {
+                            "gdpr": 0
+                        }
+                    }
+                }
+            },
+            "mockResponse": {
+                "status": 200,
+                "body": {
+                    "id": "tid",
+                    "seatbid": [
+                        {
+                            "seat": "aax",
+                            "bid": [
+                                {
+                                    "id": "randomid",
+                                    "impid": "test-imp-id",
+                                    "price": 0.500000,
+                                    "adid": "12345678",
+                                    "adm": "some-test-ad",
+                                    "cid": "987",
+                                    "crid": "12345678",
+                                    "h": 250,
+                                    "w": 300
+                                }
+                            ]
+                        }
+                    ],
+                    "bidid": "bid01"
+                }
+            }
+        }
+    ],
+    "expectedBidResponses": [
+        {
+            "currency": "USD",
+            "bids": [
+                {
+                    "bid": {
+                        "id": "randomid",
+                        "impid": "test-imp-id",
+                        "price": 0.500000,
+                        "adid": "12345678",
+                        "adm": "some-test-ad",
+                        "cid": "987",
+                        "crid": "12345678",
+                        "h": 250,
+                        "w": 300
+                    },
+                    "type": "banner"
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/adapters/aax/aaxtest/supplemental/valid-req-204-response-from-aax.json b/adapters/aax/aaxtest/supplemental/valid-req-204-response-from-aax.json
new file mode 100644
index 00000000..db821902
--- /dev/null
+++ b/adapters/aax/aaxtest/supplemental/valid-req-204-response-from-aax.json
@@ -0,0 +1,66 @@
+{
+    "mockBidRequest": {
+        "app": {
+            "bundle": "com.example.app"
+        },
+        "id": "req-id",
+        "device": {
+            "ifa": "9d8fe0a9-c0dd-4482-b16b-5709b00c608d",
+            "ip": "1.1.1.1",
+            "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36"
+        },
+        "imp": [
+            {
+                "ext": {
+                    "bidder": {
+                        "cid": "TCID",
+                        "crid": "1234"
+                    }
+                },
+                "banner": {
+                    "w": 320,
+                    "h": 50
+                },
+                "id": "imp-id"
+            }
+        ]
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "https://example.aax.media/rtb/prebid?src=http%3A%2F%2Flocalhost%3A8080%2Fextrnal_url",
+                "body": {
+                    "app": {
+                        "bundle": "com.example.app"
+                    },
+                    "id": "req-id",
+                    "device": {
+                        "ifa": "9d8fe0a9-c0dd-4482-b16b-5709b00c608d",
+                        "ip": "1.1.1.1",
+                        "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36"
+                    },
+                    "imp": [
+                        {
+                            "ext": {
+                                "bidder": {
+                                    "cid": "TCID",
+                                    "crid": "1234"
+                                }
+                            },
+                            "banner": {
+                                "w": 320,
+                                "h": 50
+                            },
+                            "id": "imp-id"
+                        }
+                    ]
+                }
+            },
+            "mockResponse": {
+                "status": 204,
+                "body": {}
+            }
+        }
+    ],
+    "expectedBidResponses": []
+}
\ No newline at end of file
diff --git a/adapters/aax/params_test.go b/adapters/aax/params_test.go
new file mode 100644
index 00000000..edf9fb6f
--- /dev/null
+++ b/adapters/aax/params_test.go
@@ -0,0 +1,54 @@
+package aax
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+// This file actually intends to test static/bidder-params/aax.json
+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.BidderAax, json.RawMessage(validParam)); err != nil {
+			t.Errorf("Schema rejected aax 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.BidderAax, json.RawMessage(invalidParam)); err == nil {
+			t.Errorf("Schema allowed unexpected params: %s", invalidParam)
+		}
+	}
+}
+
+var validParams = []string{
+	`{"cid":"123", "crid":"1234"}`,
+}
+
+var invalidParams = []string{
+	``,
+	`null`,
+	`true`,
+	`5`,
+	`4.2`,
+	`[]`,
+	`{}`,
+	`{"cid":"", "crid":""}`,
+	`{"cid":"only cid is present"}`,
+	`{"crid":"only crid is present"}`,
+	`{"ccid":"123","ccrid":"123"}`,
+	`{"aid":123, "placementId":"123", "siteId":"321"}`,
+}
diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go
index 0bb79798..e1138587 100644
--- a/endpoints/openrtb2/auction_test.go
+++ b/endpoints/openrtb2/auction_test.go
@@ -26,6 +26,7 @@ import (
 	metricsConfig "github.com/prebid/prebid-server/metrics/config"
 	"github.com/prebid/prebid-server/openrtb_ext"
 	"github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher"
+	"github.com/prebid/prebid-server/stored_responses"
 	"github.com/prebid/prebid-server/util/iputil"
 
 	"github.com/buger/jsonparser"
@@ -1633,7 +1634,7 @@ func TestValidateRequest(t *testing.T) {
 	}
 
 	for _, test := range testCases {
-		errorList := deps.validateRequest(test.givenRequestWrapper, test.givenIsAmp, false)
+		errorList := deps.validateRequest(test.givenRequestWrapper, test.givenIsAmp, false, nil)
 		assert.Equalf(t, test.expectedErrorList, errorList, "Error doesn't match: %s\n", test.description)
 
 		if len(errorList) == 0 {
@@ -2305,7 +2306,7 @@ func TestValidateImpExt(t *testing.T) {
 		for _, test := range group.testCases {
 			imp := &openrtb2.Imp{Ext: test.impExt}
 
-			errs := deps.validateImpExt(imp, nil, 0, false)
+			errs := deps.validateImpExt(imp, nil, 0, false, nil)
 
 			if len(test.expectedImpExt) > 0 {
 				assert.JSONEq(t, test.expectedImpExt, string(imp.Ext), "imp.ext JSON does not match expected. Test: %s. %s\n", group.description, test.description)
@@ -2368,7 +2369,7 @@ func TestCurrencyTrunc(t *testing.T) {
 		Cur: []string{"USD", "EUR"},
 	}
 
-	errL := deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: &req}, false, false)
+	errL := deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: &req}, false, false, nil)
 
 	expectedError := errortypes.Warning{Message: "A prebid request can only process one currency. Taking the first currency in the list, USD, as the active currency"}
 	assert.ElementsMatch(t, errL, []error{&expectedError})
@@ -2416,7 +2417,7 @@ func TestCCPAInvalid(t *testing.T) {
 		},
 	}
 
-	errL := deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: &req}, false, false)
+	errL := deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: &req}, false, false, nil)
 
 	expectedWarning := errortypes.Warning{
 		Message:     "CCPA consent is invalid and will be ignored. (request.regs.ext.us_privacy must contain 4 characters)",
@@ -2467,7 +2468,7 @@ func TestNoSaleInvalid(t *testing.T) {
 		Ext: json.RawMessage(`{"prebid": {"nosale": ["*", "appnexus"]} }`),
 	}
 
-	errL := deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: &req}, false, false)
+	errL := deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: &req}, false, false, nil)
 
 	expectedError := errors.New("request.ext.prebid.nosale is invalid: can only specify all bidders if no other bidders are provided")
 	assert.ElementsMatch(t, errL, []error{expectedError})
@@ -2516,7 +2517,7 @@ func TestValidateSourceTID(t *testing.T) {
 		},
 	}
 
-	deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: &req}, false, false)
+	deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: &req}, false, false, nil)
 	assert.NotEmpty(t, req.Source.TID, "Expected req.Source.TID to be filled with a randomly generated UID")
 }
 
@@ -2560,7 +2561,7 @@ func TestSChainInvalid(t *testing.T) {
 		Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller1.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}, {"bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller2.com","sid":"00002","rid":"BidRequest2","hp":1}],"ver":"1.0"}}]}}`),
 	}
 
-	errL := deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: &req}, false, false)
+	errL := deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: &req}, false, false, nil)
 
 	expectedError := errors.New("request.ext.prebid.schains contains multiple schains for bidder appnexus; it must contain no more than one per bidder.")
 	assert.ElementsMatch(t, errL, []error{expectedError})
@@ -2665,31 +2666,31 @@ func TestMapSChains(t *testing.T) {
 		},
 	}
 
-	for _, tt := range tests {
+	for _, test := range tests {
 		reqWrapper := openrtb_ext.RequestWrapper{
-			BidRequest: &tt.bidRequest,
+			BidRequest: &test.bidRequest,
 		}
 
 		err := mapSChains(&reqWrapper)
 
-		if tt.wantError {
-			assert.NotNil(t, err, tt.description)
+		if test.wantError {
+			assert.NotNil(t, err, test.description)
 		} else {
-			assert.Nil(t, err, tt.description)
+			assert.Nil(t, err, test.description)
 
 			reqExt, err := reqWrapper.GetRequestExt()
 			if err != nil {
-				assert.Fail(t, "Error getting request ext from wrapper", tt.description)
+				assert.Fail(t, "Error getting request ext from wrapper", test.description)
 			}
 			reqExtSChain := reqExt.GetSChain()
-			assert.Equal(t, tt.wantReqExtSChain, reqExtSChain, tt.description)
+			assert.Equal(t, test.wantReqExtSChain, reqExtSChain, test.description)
 
 			sourceExt, err := reqWrapper.GetSourceExt()
 			if err != nil {
-				assert.Fail(t, "Error getting source ext from wrapper", tt.description)
+				assert.Fail(t, "Error getting source ext from wrapper", test.description)
 			}
 			sourceExtSChain := sourceExt.GetSChain()
-			assert.Equal(t, tt.wantSourceExtSChain, sourceExtSChain, tt.description)
+			assert.Equal(t, test.wantSourceExtSChain, sourceExtSChain, test.description)
 		}
 	}
 }
@@ -2909,7 +2910,7 @@ func TestEidPermissionsInvalid(t *testing.T) {
 		Ext: json.RawMessage(`{"prebid": {"data": {"eidpermissions": [{"source":"a", "bidders":[]}]} } }`),
 	}
 
-	errL := deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: &req}, false, false)
+	errL := deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: &req}, false, false, nil)
 
 	expectedError := errors.New(`request.ext.prebid.data.eidpermissions[0] missing or empty required field: "bidders"`)
 	assert.ElementsMatch(t, errL, []error{expectedError})
@@ -3215,7 +3216,7 @@ func TestParseRequestParseImpInfoError(t *testing.T) {
 
 	req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(reqBody))
 
-	resReq, impExtInfoMap, _, errL := deps.parseRequest(req)
+	resReq, impExtInfoMap, _, _, errL := deps.parseRequest(req)
 
 	assert.Nil(t, resReq, "Result request should be nil due to incorrect imp")
 	assert.Nil(t, impExtInfoMap, "Impression info map should be nil due to incorrect imp")
@@ -4642,8 +4643,8 @@ func TestParseRequestMergeBidderParams(t *testing.T) {
 			expectedErrorCount: 0,
 		},
 	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
 
 			deps := &endpointDeps{
 				fakeUUIDGenerator{},
@@ -4652,7 +4653,7 @@ func TestParseRequestMergeBidderParams(t *testing.T) {
 				&mockStoredReqFetcher{},
 				empty_fetcher.EmptyFetcher{},
 				empty_fetcher.EmptyFetcher{},
-				&config.Configuration{MaxRequestSize: int64(len(tt.givenRequestBody))},
+				&config.Configuration{MaxRequestSize: int64(len(test.givenRequestBody))},
 				&metricsConfig.NilMetricsEngine{},
 				analyticsConf.NewPBSAnalytics(&config.Analytics{}),
 				map[string]string{},
@@ -4665,12 +4666,12 @@ func TestParseRequestMergeBidderParams(t *testing.T) {
 				empty_fetcher.EmptyFetcher{},
 			}
 
-			req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(tt.givenRequestBody))
+			req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(test.givenRequestBody))
 
-			resReq, _, _, errL := deps.parseRequest(req)
+			resReq, _, _, _, errL := deps.parseRequest(req)
 
 			var expIExt, iExt map[string]interface{}
-			err := json.Unmarshal(tt.expectedImpExt, &expIExt)
+			err := json.Unmarshal(test.expectedImpExt, &expIExt)
 			assert.Nil(t, err, "unmarshal() should return nil error")
 
 			assert.NotNil(t, resReq.BidRequest.Imp[0].Ext, "imp[0].Ext should not be nil")
@@ -4680,7 +4681,7 @@ func TestParseRequestMergeBidderParams(t *testing.T) {
 			assert.Equal(t, expIExt, iExt, "bidderparams in imp[].Ext should match")
 
 			var eReqE, reqE map[string]interface{}
-			err = json.Unmarshal(tt.expectedReqExt, &eReqE)
+			err = json.Unmarshal(test.expectedReqExt, &eReqE)
 			assert.Nil(t, err, "unmarshal() should return nil error")
 
 			err = json.Unmarshal(resReq.BidRequest.Ext, &reqE)
@@ -4688,7 +4689,7 @@ func TestParseRequestMergeBidderParams(t *testing.T) {
 
 			assert.Equal(t, eReqE, reqE, "req.Ext should match")
 
-			assert.Len(t, errL, tt.expectedErrorCount, "error length should match")
+			assert.Len(t, errL, test.expectedErrorCount, "error length should match")
 		})
 	}
 }
@@ -4702,12 +4703,12 @@ func TestParseRequestStoredResponses(t *testing.T) {
 	tests := []struct {
 		name                    string
 		givenRequestBody        string
-		expectedStoredResponses map[string]json.RawMessage
+		expectedStoredResponses stored_responses.ImpsWithBidResponses
 		expectedErrorCount      int
 		expectedError           string
 	}{
 		{
-			name:             "reqimp has valid stored response",
+			name:             "req imp has valid stored response",
 			givenRequestBody: validRequest(t, "req-imp-stored-response.json"),
 			expectedStoredResponses: map[string]json.RawMessage{
 				"imp-id1": json.RawMessage(`[{"bid": [{"id": "bid_id1"],"seat": "appnexus"}]`),
@@ -4742,8 +4743,8 @@ func TestParseRequestStoredResponses(t *testing.T) {
 			expectedError:      `request validation failed. The StoredAuctionResponse.ID field must be completely present with, or completely absent from, all impressions in request. No StoredAuctionResponse data found for request.imp[1].ext.prebid`,
 		},
 	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
 
 			deps := &endpointDeps{
 				fakeUUIDGenerator{},
@@ -4752,7 +4753,7 @@ func TestParseRequestStoredResponses(t *testing.T) {
 				&mockStoredReqFetcher{},
 				empty_fetcher.EmptyFetcher{},
 				empty_fetcher.EmptyFetcher{},
-				&config.Configuration{MaxRequestSize: int64(len(tt.givenRequestBody))},
+				&config.Configuration{MaxRequestSize: int64(len(test.givenRequestBody))},
 				&metricsConfig.NilMetricsEngine{},
 				analyticsConf.NewPBSAnalytics(&config.Analytics{}),
 				map[string]string{},
@@ -4765,20 +4766,105 @@ func TestParseRequestStoredResponses(t *testing.T) {
 				&mockStoredResponseFetcher{mockStoredResponses},
 			}
 
-			req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(tt.givenRequestBody))
+			req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(test.givenRequestBody))
 
-			_, _, storedResponses, errL := deps.parseRequest(req)
+			_, _, storedResponses, _, errL := deps.parseRequest(req)
 
-			if tt.expectedErrorCount == 0 {
-				assert.Equal(t, tt.expectedStoredResponses, storedResponses, "stored responses should match")
+			if test.expectedErrorCount == 0 {
+				assert.Equal(t, test.expectedStoredResponses, storedResponses, "stored responses should match")
 			} else {
-				assert.Contains(t, errL[0].Error(), tt.expectedError, "error should match")
+				assert.Contains(t, errL[0].Error(), test.expectedError, "error should match")
 			}
 
 		})
 	}
 }
 
+func TestParseRequestStoredBidResponses(t *testing.T) {
+	bidRespId1 := json.RawMessage(`{"id": "resp_id1", "seatbid": [{"bid": [{"id": "bid_id1"}], "seat": "testBidder1"}], "bidid": "123", "cur": "USD"}`)
+	bidRespId2 := json.RawMessage(`{"id": "resp_id2", "seatbid": [{"bid": [{"id": "bid_id2"}], "seat": "testBidder2"}], "bidid": "124", "cur": "USD"}`)
+	mockStoredBidResponses := map[string]json.RawMessage{
+		"bidResponseId1": bidRespId1,
+		"bidResponseId2": bidRespId2,
+	}
+
+	tests := []struct {
+		name                       string
+		givenRequestBody           string
+		expectedStoredBidResponses stored_responses.ImpBidderStoredResp
+		expectedErrorCount         int
+		expectedError              string
+	}{
+		{
+			name:             "req imp has valid stored bid response",
+			givenRequestBody: validRequest(t, "imp-with-stored-bid-resp.json"),
+			expectedStoredBidResponses: map[string]map[string]json.RawMessage{
+				"imp-id1": {"testBidder1": bidRespId1},
+			},
+			expectedErrorCount: 0,
+		},
+		{
+			name:             "req has two imps with valid stored bid responses",
+			givenRequestBody: validRequest(t, "req-two-imps-stored-bid-responses.json"),
+			expectedStoredBidResponses: map[string]map[string]json.RawMessage{
+				"imp-id1": {"testBidder1": bidRespId1},
+				"imp-id2": {"testBidder2": bidRespId2},
+			},
+			expectedErrorCount: 0,
+		},
+		{
+			name:             "req has two imps one with valid stored bid responses and another one without stored bid responses",
+			givenRequestBody: validRequest(t, "req-two-imps-with-and-without-stored-bid-responses.json"),
+			expectedStoredBidResponses: map[string]map[string]json.RawMessage{
+				"imp-id2": {"testBidder2": bidRespId2},
+			},
+			expectedErrorCount: 0,
+		},
+		{
+			name:             "req has two imps with missing stored bid responses",
+			givenRequestBody: validRequest(t, "req-two-imps-missing-stored-bid-response.json"),
+			expectedStoredBidResponses: map[string]map[string]json.RawMessage{
+				"imp-id1": {"testBidder1": nil},
+				"imp-id2": {"testBidder2": nil},
+			},
+			expectedErrorCount: 0,
+		},
+	}
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+
+			deps := &endpointDeps{
+				fakeUUIDGenerator{},
+				&warningsCheckExchange{},
+				newParamsValidator(t),
+				&mockStoredReqFetcher{},
+				empty_fetcher.EmptyFetcher{},
+				empty_fetcher.EmptyFetcher{},
+				&config.Configuration{MaxRequestSize: int64(len(test.givenRequestBody))},
+				&metricsConfig.NilMetricsEngine{},
+				analyticsConf.NewPBSAnalytics(&config.Analytics{}),
+				map[string]string{},
+				false,
+				[]byte{},
+				map[string]openrtb_ext.BidderName{"testBidder1": "testBidder1", "testBidder2": "testBidder2"},
+				nil,
+				nil,
+				hardcodedResponseIPValidator{response: true},
+				&mockStoredResponseFetcher{mockStoredBidResponses},
+			}
+
+			req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(test.givenRequestBody))
+			_, _, _, storedBidResponses, errL := deps.parseRequest(req)
+
+			if test.expectedErrorCount == 0 {
+				assert.Equal(t, test.expectedStoredBidResponses, storedBidResponses, "stored responses should match")
+			} else {
+				assert.Contains(t, errL[0].Error(), test.expectedError, "error should match")
+			}
+		})
+	}
+}
+
 func TestValidateStoredResp(t *testing.T) {
 	deps := &endpointDeps{
 		fakeUUIDGenerator{},
@@ -4801,10 +4887,11 @@ func TestValidateStoredResp(t *testing.T) {
 	}
 
 	testCases := []struct {
-		description         string
-		givenRequestWrapper *openrtb_ext.RequestWrapper
-		expectedErrorList   []error
-		hasStoredResponses  bool
+		description               string
+		givenRequestWrapper       *openrtb_ext.RequestWrapper
+		expectedErrorList         []error
+		hasStoredAuctionResponses bool
+		storedBidResponses        stored_responses.ImpBidderStoredResp
 	}{
 		{
 			description: "One imp with stored response, expect validate request to throw no errors",
@@ -4832,8 +4919,9 @@ func TestValidateStoredResp(t *testing.T) {
 					},
 				},
 			},
-			expectedErrorList:  []error{},
-			hasStoredResponses: true,
+			expectedErrorList:         []error{},
+			hasStoredAuctionResponses: true,
+			storedBidResponses:        nil,
 		},
 		{
 			description: "Two imps with stored responses, expect validate request to throw no errors",
@@ -4877,8 +4965,9 @@ func TestValidateStoredResp(t *testing.T) {
 					},
 				},
 			},
-			expectedErrorList:  []error{},
-			hasStoredResponses: true,
+			expectedErrorList:         []error{},
+			hasStoredAuctionResponses: true,
+			storedBidResponses:        nil,
 		},
 		{
 			description: "Two imps, one with stored response, expect validate request to throw validation error",
@@ -4922,89 +5011,427 @@ func TestValidateStoredResp(t *testing.T) {
 					},
 				},
 			},
-			expectedErrorList:  []error{errors.New("request validation failed. The StoredAuctionResponse.ID field must be completely present with, or completely absent from, all impressions in request. No StoredAuctionResponse data found for request.imp[1].ext.prebid \n")},
-			hasStoredResponses: true,
+			expectedErrorList:         []error{errors.New("request validation failed. The StoredAuctionResponse.ID field must be completely present with, or completely absent from, all impressions in request. No StoredAuctionResponse data found for request.imp[1].ext.prebid \n")},
+			hasStoredAuctionResponses: true,
+			storedBidResponses:        nil,
+		},
+		{
+			description: "One imp with stored bid response and corresponding bidder in imp.ext, expect validate request to throw no errors",
+			givenRequestWrapper: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					ID:  "Some-ID",
+					App: &openrtb2.App{},
+					Imp: []openrtb2.Imp{
+						{
+							ID: "Some-Imp-ID",
+							Banner: &openrtb2.Banner{
+								Format: []openrtb2.Format{
+									{
+										W: 600,
+										H: 500,
+									},
+									{
+										W: 300,
+										H: 600,
+									},
+								},
+							},
+							Ext: []byte(`{"appnexus": {"placementId": 12345678}, "prebid": {"storedbidresponse": []}}`),
+						},
+					},
+				},
+			},
+			expectedErrorList:         []error{},
+			hasStoredAuctionResponses: false,
+			storedBidResponses:        stored_responses.ImpBidderStoredResp{"Some-Imp-ID": {"appnexus": json.RawMessage(`{"test":true}`)}},
+		},
+		{
+			description: "One imp with 2 stored bid responses and 2 corresponding bidders in imp.ext, expect validate request to throw no errors",
+			givenRequestWrapper: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					ID:  "Some-ID",
+					App: &openrtb2.App{},
+					Imp: []openrtb2.Imp{
+						{
+							ID: "Some-Imp-ID",
+							Banner: &openrtb2.Banner{
+								Format: []openrtb2.Format{
+									{
+										W: 600,
+										H: 500,
+									},
+									{
+										W: 300,
+										H: 600,
+									},
+								},
+							},
+							Ext: []byte(`{"appnexus": {"placementId": 12345678}, "telaria": {"seatCode": "12345678"}, "prebid": {"storedbidresponse": []}}`),
+						},
+					},
+				},
+			},
+			expectedErrorList:         []error{},
+			hasStoredAuctionResponses: false,
+			storedBidResponses:        stored_responses.ImpBidderStoredResp{"Some-Imp-ID": {"appnexus": json.RawMessage(`{"test":true}`), "telaria": json.RawMessage(`{"test":true}`)}},
+		},
+		{
+			description: "Two imps, one with 2 stored bid responses and 2 corresponding bidders in imp.ext, expect validate request to throw no errors",
+			givenRequestWrapper: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					ID:  "Some-ID",
+					App: &openrtb2.App{},
+					Imp: []openrtb2.Imp{
+						{
+							ID: "Some-Imp-ID",
+							Banner: &openrtb2.Banner{
+								Format: []openrtb2.Format{
+									{
+										W: 600,
+										H: 500,
+									},
+									{
+										W: 300,
+										H: 600,
+									},
+								},
+							},
+							Ext: []byte(`{"appnexus": {"placementId": 12345678}, "telaria": {"seatCode": "12345678"}, "prebid": {"storedbidresponse": []}}`),
+						},
+						{
+							ID: "Some-Imp-ID2",
+							Banner: &openrtb2.Banner{
+								Format: []openrtb2.Format{
+									{
+										W: 600,
+										H: 500,
+									},
+									{
+										W: 300,
+										H: 600,
+									},
+								},
+							},
+							Ext: []byte(`{"appnexus": {"placementId": 12345678}, "telaria": {"seatCode": "12345678"}, "prebid": {"storedbidresponse": []}}`),
+						},
+					},
+				},
+			},
+			expectedErrorList:         []error{},
+			hasStoredAuctionResponses: false,
+			storedBidResponses:        stored_responses.ImpBidderStoredResp{"Some-Imp-ID": {"appnexus": json.RawMessage(`{"test":true}`), "telaria": json.RawMessage(`{"test":true}`)}},
+		},
+		{
+			description: "Two imps, both with 2 stored bid responses and 2 corresponding bidders in imp.ext, expect validate request to throw no errors",
+			givenRequestWrapper: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					ID:  "Some-ID",
+					App: &openrtb2.App{},
+					Imp: []openrtb2.Imp{
+						{
+							ID: "Some-Imp-ID",
+							Banner: &openrtb2.Banner{
+								Format: []openrtb2.Format{
+									{
+										W: 600,
+										H: 500,
+									},
+									{
+										W: 300,
+										H: 600,
+									},
+								},
+							},
+							Ext: []byte(`{"appnexus": {"placementId": 12345678}, "telaria": {"seatCode": "12345678"}, "prebid": {"storedbidresponse": []}}`),
+						},
+						{
+							ID: "Some-Imp-ID2",
+							Banner: &openrtb2.Banner{
+								Format: []openrtb2.Format{
+									{
+										W: 600,
+										H: 500,
+									},
+									{
+										W: 300,
+										H: 600,
+									},
+								},
+							},
+							Ext: []byte(`{"appnexus": {"placementId": 12345678}, "telaria": {"seatCode": "12345678"}, "prebid": {"storedbidresponse": []}}`),
+						},
+					},
+				},
+			},
+			expectedErrorList:         []error{},
+			hasStoredAuctionResponses: false,
+			storedBidResponses: stored_responses.ImpBidderStoredResp{
+				"Some-Imp-ID":  {"appnexus": json.RawMessage(`{"test":true}`), "telaria": json.RawMessage(`{"test":true}`)},
+				"Some-Imp-ID1": {"appnexus": json.RawMessage(`{"test":true}`), "telaria": json.RawMessage(`{"test":true}`)},
+			},
+		},
+		{
+			description: "One imp with 2 stored bid responses and 1 bidder in imp.ext, expect validate request to throw an errors",
+			givenRequestWrapper: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					ID:  "Some-ID",
+					App: &openrtb2.App{},
+					Imp: []openrtb2.Imp{
+						{
+							ID: "Some-Imp-ID",
+							Banner: &openrtb2.Banner{
+								Format: []openrtb2.Format{
+									{
+										W: 600,
+										H: 500,
+									},
+									{
+										W: 300,
+										H: 600,
+									},
+								},
+							},
+							Ext: []byte(`{"appnexus": {"placementId": 12345678}, "prebid": {"storedbidresponse": []}}`),
+						},
+					},
+				},
+			},
+			expectedErrorList:         []error{errors.New("request validation failed. Stored bid responses are specified for imp Some-Imp-ID. Bidders specified in imp.ext should match with bidders specified in imp.ext.prebid.storedbidresponse")},
+			hasStoredAuctionResponses: false,
+			storedBidResponses:        stored_responses.ImpBidderStoredResp{"Some-Imp-ID": {"appnexus": json.RawMessage(`{"test":true}`), "telaria": json.RawMessage(`{"test":true}`)}},
 		},
-	}
-
-	for _, test := range testCases {
-		errorList := deps.validateRequest(test.givenRequestWrapper, false, test.hasStoredResponses)
-		assert.Equalf(t, test.expectedErrorList, errorList, "Error doesn't match: %s\n", test.description)
-	}
-}
-
-func TestProcessStoredAuctionResponsesErrors(t *testing.T) {
-	deps := &endpointDeps{}
-
-	testCases := []struct {
-		description       string
-		requestJson       []byte
-		expectedErrorList []error
-	}{
 		{
-			description: "Invalid stored auction response format: empty stored Auction Response Id",
-			requestJson: []byte(`{"imp": [
-    			  {
-    			    "id": "imp-id1",
-    			    "ext": {
-    			      "prebid": {
-    			        "storedauctionresponse": {
-    			        }
-    			      }
-    			    }
-    			  }
-    			]}`),
-			expectedErrorList: []error{errors.New("request.imp[0] has ext.prebid.storedauctionresponse specified, but \"id\" field is missing ")},
-		},
-		{
-			description: "Invalid stored auction response format: empty stored Auction Response Id in second imp",
-			requestJson: []byte(`{"imp": [
-    			  {
-    			    "id": "imp-id1",
-    			    "ext": {
-    			      "prebid": {
-    			        "storedauctionresponse": {
-							"id":"123"
-    			        }
-    			      }
-    			    }
-    			  },
-			      {
-    			    "id": "imp-id2",
-    			    "ext": {
-    			      "prebid": {
-    			        "storedauctionresponse": {
-							"id":""
-    			        }
-    			      }
-    			    }
-    			  }
-    			]}`),
-			expectedErrorList: []error{errors.New("request.imp[1] has ext.prebid.storedauctionresponse specified, but \"id\" field is missing ")},
-		},
-		{
-			description: "Invalid stored auction response format: integer imp Id",
-			requestJson: []byte(`{"imp": [
-    			  {
-    			    "id": 123,
-    			    "ext": {
-    			      "prebid": {
-    			        "storedauctionresponse": {
-							"id":"123"
-    			        }
-    			      }
-    			    }
-    			  }
-    			]}`),
-			expectedErrorList: []error{errors.New("Value is not a string: 123")},
+			description: "One imp with 1 stored bid responses and 2 bidders in imp.ext, expect validate request to throw an errors",
+			givenRequestWrapper: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					ID:  "Some-ID",
+					App: &openrtb2.App{},
+					Imp: []openrtb2.Imp{
+						{
+							ID: "Some-Imp-ID",
+							Banner: &openrtb2.Banner{
+								Format: []openrtb2.Format{
+									{
+										W: 600,
+										H: 500,
+									},
+									{
+										W: 300,
+										H: 600,
+									},
+								},
+							},
+							Ext: []byte(`{"appnexus": {"placementId": 12345678}, "telaria": {"seatCode": "12345678"}, "prebid": {"storedbidresponse": []}}`),
+						},
+					},
+				},
+			},
+			expectedErrorList:         []error{errors.New("request validation failed. Stored bid responses are specified for imp Some-Imp-ID. Bidders specified in imp.ext should match with bidders specified in imp.ext.prebid.storedbidresponse")},
+			hasStoredAuctionResponses: false,
+			storedBidResponses:        stored_responses.ImpBidderStoredResp{"Some-Imp-ID": {"appnexus": json.RawMessage(`{"test":true}`)}},
+		},
+		{
+			description: "One imp with 2 stored bid responses and 2 different bidders in imp.ext, expect validate request to throw an errors",
+			givenRequestWrapper: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					ID:  "Some-ID",
+					App: &openrtb2.App{},
+					Imp: []openrtb2.Imp{
+						{
+							ID: "Some-Imp-ID",
+							Banner: &openrtb2.Banner{
+								Format: []openrtb2.Format{
+									{
+										W: 600,
+										H: 500,
+									},
+									{
+										W: 300,
+										H: 600,
+									},
+								},
+							},
+							Ext: []byte(`{"appnexus": {"placementId": 12345678}, "telaria": {"seatCode": "12345678"}, "prebid": {"storedbidresponse": []}}`),
+						},
+					},
+				},
+			},
+			expectedErrorList:         []error{errors.New("request validation failed. Stored bid responses are specified for imp Some-Imp-ID. Bidders specified in imp.ext should match with bidders specified in imp.ext.prebid.storedbidresponse")},
+			hasStoredAuctionResponses: false,
+			storedBidResponses:        stored_responses.ImpBidderStoredResp{"Some-Imp-ID": {"appnexus": json.RawMessage(`{"test":true}`), "rubicon": json.RawMessage(`{"test":true}`)}},
+		},
+		{
+			description: "One imp with 2 stored bid responses and 1 bidders in imp.ext and 1 in imp.ext.prebid.bidder, expect validate request to throw no errors",
+			givenRequestWrapper: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					ID:  "Some-ID",
+					App: &openrtb2.App{},
+					Imp: []openrtb2.Imp{
+						{
+							ID: "Some-Imp-ID",
+							Banner: &openrtb2.Banner{
+								Format: []openrtb2.Format{
+									{
+										W: 600,
+										H: 500,
+									},
+									{
+										W: 300,
+										H: 600,
+									},
+								},
+							},
+							Ext: []byte(`{"appnexus": {"placementId": 12345678}, "prebid": {"bidder":{"telaria": {"seatCode": "12345678"}}, "storedbidresponse": []}}`),
+						},
+					},
+				},
+			},
+			expectedErrorList:         []error{},
+			hasStoredAuctionResponses: false,
+			storedBidResponses:        stored_responses.ImpBidderStoredResp{"Some-Imp-ID": {"appnexus": json.RawMessage(`{"test":true}`), "telaria": json.RawMessage(`{"test":true}`)}},
+		},
+		{
+			description: "One imp with 2 stored bid responses and 1 bidders in imp.ext and 1 in imp.ext.prebid.bidder that is not defined in stored bid responses, expect validate request to throw an error",
+			givenRequestWrapper: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					ID:  "Some-ID",
+					App: &openrtb2.App{},
+					Imp: []openrtb2.Imp{
+						{
+							ID: "Some-Imp-ID",
+							Banner: &openrtb2.Banner{
+								Format: []openrtb2.Format{
+									{
+										W: 600,
+										H: 500,
+									},
+									{
+										W: 300,
+										H: 600,
+									},
+								},
+							},
+							Ext: []byte(`{"appnexus": {"placementId": 12345678}, "prebid": {"bidder":{"rubicon": {"seatCode": "12345678"}}, "storedbidresponse": []}}`),
+						},
+					},
+				},
+			},
+			expectedErrorList:         []error{errors.New("request validation failed. Stored bid responses are specified for imp Some-Imp-ID. Bidders specified in imp.ext should match with bidders specified in imp.ext.prebid.storedbidresponse")},
+			hasStoredAuctionResponses: false,
+			storedBidResponses:        stored_responses.ImpBidderStoredResp{"Some-Imp-ID": {"appnexus": json.RawMessage(`{"test":true}`), "telaria": json.RawMessage(`{"test":true}`)}},
+		},
+		{
+			description: "One imp with 1 stored bid response and 1 in imp.ext.prebid.bidder that is defined in stored bid responses, expect validate request to throw no errors",
+			givenRequestWrapper: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					ID:  "Some-ID",
+					App: &openrtb2.App{},
+					Imp: []openrtb2.Imp{
+						{
+							ID: "Some-Imp-ID",
+							Banner: &openrtb2.Banner{
+								Format: []openrtb2.Format{
+									{
+										W: 600,
+										H: 500,
+									},
+									{
+										W: 300,
+										H: 600,
+									},
+								},
+							},
+							Ext: []byte(`{"prebid": {"bidder":{"telaria": {"seatCode": "12345678"}}, "storedbidresponse": []}}`),
+						},
+					},
+				},
+			},
+			expectedErrorList:         []error{},
+			hasStoredAuctionResponses: false,
+			storedBidResponses:        stored_responses.ImpBidderStoredResp{"Some-Imp-ID": {"telaria": json.RawMessage(`{"test":true}`)}},
+		},
+		{
+			description: "One imp with 1 stored bid response and 1 in imp.ext.prebid.bidder that is not defined in stored bid responses, expect validate request to throw an error",
+			givenRequestWrapper: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					ID:  "Some-ID",
+					App: &openrtb2.App{},
+					Imp: []openrtb2.Imp{
+						{
+							ID: "Some-Imp-ID",
+							Banner: &openrtb2.Banner{
+								Format: []openrtb2.Format{
+									{
+										W: 600,
+										H: 500,
+									},
+									{
+										W: 300,
+										H: 600,
+									},
+								},
+							},
+							Ext: []byte(`{"prebid": {"bidder":{"telaria": {"seatCode": "12345678"}}, "storedbidresponse": []}}`),
+						},
+					},
+				},
+			},
+			expectedErrorList:         []error{errors.New("request validation failed. Stored bid responses are specified for imp Some-Imp-ID. Bidders specified in imp.ext should match with bidders specified in imp.ext.prebid.storedbidresponse")},
+			hasStoredAuctionResponses: false,
+			storedBidResponses:        stored_responses.ImpBidderStoredResp{"Some-Imp-ID": {"appnexus": json.RawMessage(`{"test":true}`)}},
+		},
+		{
+			description: "2 imps, one imp without stored responses, another imp with 1 stored bid response and 1 in imp.ext.prebid.bidder that is not defined in stored bid responses, expect validate request to throw an error",
+			givenRequestWrapper: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					ID:  "Some-ID",
+					App: &openrtb2.App{},
+					Imp: []openrtb2.Imp{
+						{
+							ID: "Some-Imp-ID",
+							Banner: &openrtb2.Banner{
+								Format: []openrtb2.Format{
+									{
+										W: 600,
+										H: 500,
+									},
+									{
+										W: 300,
+										H: 600,
+									},
+								},
+							},
+							Ext: []byte(`{"prebid": {"bidder":{"telaria": {"seatCode": "12345678"}}}}`),
+						},
+						{
+							ID: "Some-Imp-ID2",
+							Banner: &openrtb2.Banner{
+								Format: []openrtb2.Format{
+									{
+										W: 600,
+										H: 500,
+									},
+									{
+										W: 300,
+										H: 600,
+									},
+								},
+							},
+							Ext: []byte(`{"prebid": {"bidder":{"telaria": {"seatCode": "12345678"}}, "storedbidresponse": []}}`),
+						},
+					},
+				},
+			},
+			expectedErrorList:         []error{errors.New("request validation failed. Stored bid responses are specified for imp Some-Imp-ID2. Bidders specified in imp.ext should match with bidders specified in imp.ext.prebid.storedbidresponse")},
+			hasStoredAuctionResponses: false,
+			storedBidResponses:        stored_responses.ImpBidderStoredResp{"Some-Imp-ID2": {"appnexus": json.RawMessage(`{"test":true}`)}},
 		},
 	}
 
 	for _, test := range testCases {
-		_, errorList := deps.processStoredAuctionResponses(nil, test.requestJson)
+		errorList := deps.validateRequest(test.givenRequestWrapper, false, test.hasStoredAuctionResponses, test.storedBidResponses)
 		assert.Equalf(t, test.expectedErrorList, errorList, "Error doesn't match: %s\n", test.description)
 	}
-
 }
 
 type mockStoredResponseFetcher struct {
diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go
index c4998f75..3b9879dc 100644
--- a/exchange/bidder_test.go
+++ b/exchange/bidder_test.go
@@ -94,7 +94,11 @@ func TestSingleBidder(t *testing.T) {
 		bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, test.debugInfo)
 		currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
 
-		seatBid, errs := bidder.requestBid(ctx, &openrtb2.BidRequest{}, "test", bidAdjustment, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, false)
+		bidderReq := BidderRequest{
+			BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}},
+			BidderName: "test",
+		}
+		seatBid, errs := bidder.requestBid(ctx, bidderReq, bidAdjustment, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, false)
 
 		// Make sure the goodSingleBidder was called with the expected arguments.
 		if bidderImpl.httpResponse == nil {
@@ -175,7 +179,12 @@ func TestRequestBidRemovesSensitiveHeaders(t *testing.T) {
 
 	bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, debugInfo)
 	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
-	seatBid, errs := bidder.requestBid(ctx, &openrtb2.BidRequest{}, "test", 1, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, false)
+
+	bidderReq := BidderRequest{
+		BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}},
+		BidderName: "test",
+	}
+	seatBid, errs := bidder.requestBid(ctx, bidderReq, 1, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, false)
 
 	expectedHttpCalls := []*openrtb_ext.ExtHttpCall{
 		{
@@ -215,7 +224,11 @@ func TestSetGPCHeader(t *testing.T) {
 
 	bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, debugInfo)
 	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
-	seatBid, errs := bidder.requestBid(ctx, &openrtb2.BidRequest{}, "test", 1, currencyConverter.Rates(), &adapters.ExtraRequestInfo{GlobalPrivacyControlHeader: "1"}, true, false)
+	bidderReq := BidderRequest{
+		BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}},
+		BidderName: "test",
+	}
+	seatBid, errs := bidder.requestBid(ctx, bidderReq, 1, currencyConverter.Rates(), &adapters.ExtraRequestInfo{GlobalPrivacyControlHeader: "1"}, true, false)
 
 	expectedHttpCall := []*openrtb_ext.ExtHttpCall{
 		{
@@ -252,7 +265,12 @@ func TestSetGPCHeaderNil(t *testing.T) {
 
 	bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, debugInfo)
 	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
-	seatBid, errs := bidder.requestBid(ctx, &openrtb2.BidRequest{}, "test", 1, currencyConverter.Rates(), &adapters.ExtraRequestInfo{GlobalPrivacyControlHeader: "1"}, true, false)
+
+	bidderReq := BidderRequest{
+		BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}},
+		BidderName: "test",
+	}
+	seatBid, errs := bidder.requestBid(ctx, bidderReq, 1, currencyConverter.Rates(), &adapters.ExtraRequestInfo{GlobalPrivacyControlHeader: "1"}, true, false)
 
 	expectedHttpCall := []*openrtb_ext.ExtHttpCall{
 		{
@@ -310,7 +328,11 @@ func TestMultiBidder(t *testing.T) {
 	}
 	bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil)
 	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
-	seatBid, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, "test", 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, true)
+	bidderReq := BidderRequest{
+		BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}},
+		BidderName: "test",
+	}
+	seatBid, errs := bidder.requestBid(context.Background(), bidderReq, 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, true)
 
 	if seatBid == nil {
 		t.Fatalf("SeatBid should exist, because bids exist.")
@@ -672,10 +694,13 @@ func TestMultiCurrencies(t *testing.T) {
 		time.Sleep(time.Duration(500) * time.Millisecond)
 		currencyConverter.Run()
 
+		bidderReq := BidderRequest{
+			BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}},
+			BidderName: openrtb_ext.BidderAppnexus,
+		}
 		seatBid, errs := bidder.requestBid(
 			context.Background(),
-			&openrtb2.BidRequest{},
-			"test",
+			bidderReq,
 			1,
 			currencyConverter.Rates(),
 			&adapters.ExtraRequestInfo{},
@@ -819,10 +844,13 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) {
 		// Execute:
 		bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil)
 		currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
+		bidderReq := BidderRequest{
+			BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}},
+			BidderName: "test",
+		}
 		seatBid, errs := bidder.requestBid(
 			context.Background(),
-			&openrtb2.BidRequest{},
-			"test",
+			bidderReq,
 			1,
 			currencyConverter.Rates(),
 			&adapters.ExtraRequestInfo{},
@@ -985,12 +1013,13 @@ func TestMultiCurrencies_RequestCurrencyPick(t *testing.T) {
 			mockedHTTPServer.URL,
 			time.Duration(24)*time.Hour,
 		)
+		bidderReq := BidderRequest{
+			BidRequest: &openrtb2.BidRequest{Cur: tc.bidRequestCurrencies, Imp: []openrtb2.Imp{{ID: "impId"}}},
+			BidderName: "test",
+		}
 		seatBid, errs := bidder.requestBid(
 			context.Background(),
-			&openrtb2.BidRequest{
-				Cur: tc.bidRequestCurrencies,
-			},
-			"test",
+			bidderReq,
 			1,
 			currencyConverter.Rates(),
 			&adapters.ExtraRequestInfo{},
@@ -1292,10 +1321,13 @@ func TestMobileNativeTypes(t *testing.T) {
 		bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil)
 		currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
 
+		bidderReq := BidderRequest{
+			BidRequest: tc.mockBidderRequest,
+			BidderName: "test",
+		}
 		seatBids, _ := bidder.requestBid(
 			context.Background(),
-			tc.mockBidderRequest,
-			"test",
+			bidderReq,
 			1.0,
 			currencyConverter.Rates(),
 			&adapters.ExtraRequestInfo{},
@@ -1311,10 +1343,86 @@ func TestMobileNativeTypes(t *testing.T) {
 	}
 }
 
+func TestRequestBidsStoredBidResponses(t *testing.T) {
+	respBody := "{\"bid\":false}"
+	respStatus := 200
+	server := httptest.NewServer(mockHandler(respStatus, "getBody", respBody))
+	defer server.Close()
+
+	bidRespId1 := json.RawMessage(`{"id": "resp_id1", "seatbid": [{"bid": [{"id": "bid_id1"}], "seat": "testBidder1"}], "bidid": "123", "cur": "USD"}`)
+	bidRespId2 := json.RawMessage(`{"id": "resp_id2", "seatbid": [{"bid": [{"id": "bid_id2_1"},{"id": "bid_id2_2"}], "seat": "testBidder2"}], "bidid": "124", "cur": "USD"}`)
+
+	testCases := []struct {
+		description           string
+		mockBidderRequest     *openrtb2.BidRequest
+		bidderStoredResponses map[string]json.RawMessage
+		expectedBidIds        []string
+		expectedImpIds        []string
+	}{
+		{
+			description: "Single imp with stored bid response",
+			mockBidderRequest: &openrtb2.BidRequest{
+				Imp: nil,
+				App: &openrtb2.App{},
+			},
+			bidderStoredResponses: map[string]json.RawMessage{
+				"bidResponseId1": bidRespId1,
+			},
+			expectedBidIds: []string{"bid_id1"},
+			expectedImpIds: []string{"bidResponseId1"},
+		},
+		{
+			description: "Single imp with multiple stored bid responses",
+			mockBidderRequest: &openrtb2.BidRequest{
+				Imp: nil,
+				App: &openrtb2.App{},
+			},
+			bidderStoredResponses: map[string]json.RawMessage{
+				"bidResponseId2": bidRespId2,
+			},
+			expectedBidIds: []string{"bid_id2_1", "bid_id2_2"},
+			expectedImpIds: []string{"bidResponseId2", "bidResponseId2"},
+		},
+	}
+
+	for _, tc := range testCases {
+
+		bidderImpl := &goodSingleBidderWithStoredBidResp{}
+		bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil)
+		currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
+
+		bidderReq := BidderRequest{
+			BidRequest:            tc.mockBidderRequest,
+			BidderName:            openrtb_ext.BidderAppnexus,
+			BidderStoredResponses: tc.bidderStoredResponses,
+		}
+		seatBids, _ := bidder.requestBid(
+			context.Background(),
+			bidderReq,
+			1.0,
+			currencyConverter.Rates(),
+			&adapters.ExtraRequestInfo{},
+			true,
+			true,
+		)
+
+		assert.Len(t, seatBids.bids, len(tc.expectedBidIds), "Incorrect bids number for test case ", tc.description)
+		for index, bid := range seatBids.bids {
+			assert.Equal(t, tc.expectedBidIds[index], bid.bid.ID, tc.description)
+			assert.Equal(t, tc.expectedImpIds[index], bid.bid.ImpID, tc.description)
+		}
+	}
+
+}
+
 func TestErrorReporting(t *testing.T) {
 	bidder := adaptBidder(&bidRejector{}, nil, &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil)
 	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
-	bids, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, "test", 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, false)
+	bidderReq := BidderRequest{
+		BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}},
+		BidderName: "test",
+	}
+	bids, errs := bidder.requestBid(context.Background(), bidderReq, 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, false)
 	if bids != nil {
 		t.Errorf("There should be no seatbid if no http requests are returned.")
 	}
@@ -1535,7 +1643,12 @@ func TestCallRecordAdapterConnections(t *testing.T) {
 	// Run requestBid using an http.Client with a mock handler
 	bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, metrics, openrtb_ext.BidderAppnexus, nil)
 	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
-	_, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, "test", bidAdjustment, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, true)
+
+	bidderReq := BidderRequest{
+		BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}},
+		BidderName: openrtb_ext.BidderAppnexus,
+	}
+	_, errs := bidder.requestBid(context.Background(), bidderReq, bidAdjustment, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, true)
 
 	// Assert no errors
 	assert.Equal(t, 0, len(errs), "bidder.requestBid returned errors %v \n", errs)
@@ -1727,6 +1840,12 @@ func TestParseDebugInfoIsNil(t *testing.T) {
 	assert.True(t, resDebugInfo, "Debug Allow value should be true")
 }
 
+func TestPrepareStoredResponse(t *testing.T) {
+	result := prepareStoredResponse("imp_id1", json.RawMessage(`{"id": "resp_id1"}`))
+	assert.Equal(t, []byte(ImpIdReqBody+"imp_id1"), result.request.Body, "incorrect request body")
+	assert.Equal(t, []byte(`{"id": "resp_id1"}`), result.response.Body, "incorrect response body")
+}
+
 func wrapWithBidderInfo(bidder adapters.Bidder) adapters.Bidder {
 	bidderInfo := config.BidderInfo{
 		Enabled: true,
@@ -1740,10 +1859,11 @@ func wrapWithBidderInfo(bidder adapters.Bidder) adapters.Bidder {
 }
 
 type goodSingleBidder struct {
-	bidRequest   *openrtb2.BidRequest
-	httpRequest  *adapters.RequestData
-	httpResponse *adapters.ResponseData
-	bidResponse  *adapters.BidderResponse
+	bidRequest            *openrtb2.BidRequest
+	httpRequest           *adapters.RequestData
+	httpResponse          *adapters.ResponseData
+	bidResponse           *adapters.BidderResponse
+	hasStoredBidResponses bool
 }
 
 func (bidder *goodSingleBidder) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
@@ -1756,6 +1876,31 @@ func (bidder *goodSingleBidder) MakeBids(internalRequest *openrtb2.BidRequest, e
 	return bidder.bidResponse, nil
 }
 
+type goodSingleBidderWithStoredBidResp struct {
+}
+
+func (bidder *goodSingleBidderWithStoredBidResp) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+	return nil, nil
+}
+
+func (bidder *goodSingleBidderWithStoredBidResp) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+	var bidResp openrtb2.BidResponse
+	if err := json.Unmarshal(response.Body, &bidResp); err != nil {
+		return nil, []error{err}
+	}
+	bidResponse := adapters.NewBidderResponseWithBidsCapacity(5)
+
+	for _, sb := range bidResp.SeatBid {
+		for i := range sb.Bid {
+			bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
+				Bid:     &sb.Bid[i],
+				BidType: openrtb_ext.BidTypeVideo,
+			})
+		}
+	}
+	return bidResponse, nil
+}
+
 type goodMultiHTTPCallsBidder struct {
 	bidRequest        *openrtb2.BidRequest
 	httpRequest       []*adapters.RequestData
diff --git a/exchange/bidder_validate_bids_test.go b/exchange/bidder_validate_bids_test.go
index 37c7bbec..250d2599 100644
--- a/exchange/bidder_validate_bids_test.go
+++ b/exchange/bidder_validate_bids_test.go
@@ -51,7 +51,11 @@ func TestAllValidBids(t *testing.T) {
 			},
 		},
 	})
-	seatBid, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true, false)
+	bidderReq := BidderRequest{
+		BidRequest: &openrtb2.BidRequest{},
+		BidderName: openrtb_ext.BidderAppnexus,
+	}
+	seatBid, errs := bidder.requestBid(context.Background(), bidderReq, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true, false)
 	assert.Len(t, seatBid.bids, 4)
 	assert.Len(t, errs, 0)
 }
@@ -109,7 +113,11 @@ func TestAllBadBids(t *testing.T) {
 			},
 		},
 	})
-	seatBid, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true, false)
+	bidderReq := BidderRequest{
+		BidRequest: &openrtb2.BidRequest{},
+		BidderName: openrtb_ext.BidderAppnexus,
+	}
+	seatBid, errs := bidder.requestBid(context.Background(), bidderReq, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true, false)
 	assert.Len(t, seatBid.bids, 0)
 	assert.Len(t, errs, 7)
 }
@@ -178,7 +186,11 @@ func TestMixedBids(t *testing.T) {
 			},
 		},
 	})
-	seatBid, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true, false)
+	bidderReq := BidderRequest{
+		BidRequest: &openrtb2.BidRequest{},
+		BidderName: openrtb_ext.BidderAppnexus,
+	}
+	seatBid, errs := bidder.requestBid(context.Background(), bidderReq, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true, false)
 	assert.Len(t, seatBid.bids, 3)
 	assert.Len(t, errs, 5)
 }
@@ -297,8 +309,9 @@ func TestCurrencyBids(t *testing.T) {
 		request := &openrtb2.BidRequest{
 			Cur: tc.brqCur,
 		}
+		bidderRequest := BidderRequest{BidRequest: request, BidderName: openrtb_ext.BidderAppnexus}
 
-		seatBid, errs := bidder.requestBid(context.Background(), request, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true, false)
+		seatBid, errs := bidder.requestBid(context.Background(), bidderRequest, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true, false)
 		assert.Len(t, seatBid.bids, expectedValidBids)
 		assert.Len(t, errs, expectedErrs)
 	}
@@ -309,6 +322,6 @@ type mockAdaptedBidder struct {
 	errorResponse []error
 }
 
-func (b *mockAdaptedBidder) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) (*pbsOrtbSeatBid, []error) {
+func (b *mockAdaptedBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) (*pbsOrtbSeatBid, []error) {
 	return b.bidResponse, b.errorResponse
 }
diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go
index 4e8b47ad..d7a47121 100644
--- a/exchange/exchange_test.go
+++ b/exchange/exchange_test.go
@@ -4037,19 +4037,19 @@ type validatingBidder struct {
 	mockResponses map[string]bidderResponse
 }
 
-func (b *validatingBidder) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) (seatBid *pbsOrtbSeatBid, errs []error) {
-	if expectedRequest, ok := b.expectations[string(name)]; ok {
+func (b *validatingBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) (seatBid *pbsOrtbSeatBid, errs []error) {
+	if expectedRequest, ok := b.expectations[string(bidderRequest.BidderName)]; ok {
 		if expectedRequest != nil {
 			if expectedRequest.BidAdjustment != bidAdjustment {
-				b.t.Errorf("%s: Bidder %s got wrong bid adjustment. Expected %f, got %f", b.fileName, name, expectedRequest.BidAdjustment, bidAdjustment)
+				b.t.Errorf("%s: Bidder %s got wrong bid adjustment. Expected %f, got %f", b.fileName, bidderRequest.BidderName, expectedRequest.BidAdjustment, bidAdjustment)
 			}
-			diffOrtbRequests(b.t, fmt.Sprintf("Request to %s in %s", string(name), b.fileName), &expectedRequest.OrtbRequest, request)
+			diffOrtbRequests(b.t, fmt.Sprintf("Request to %s in %s", string(bidderRequest.BidderName), b.fileName), &expectedRequest.OrtbRequest, bidderRequest.BidRequest)
 		}
 	} else {
-		b.t.Errorf("%s: Bidder %s got unexpected request for alias %s. No input assertions.", b.fileName, b.bidderName, name)
+		b.t.Errorf("%s: Bidder %s got unexpected request for alias %s. No input assertions.", b.fileName, b.bidderName, bidderRequest.BidderName)
 	}
 
-	if mockResponse, ok := b.mockResponses[string(name)]; ok {
+	if mockResponse, ok := b.mockResponses[string(bidderRequest.BidderName)]; ok {
 		if mockResponse.SeatBid != nil {
 			bids := make([]*pbsOrtbBid, len(mockResponse.SeatBid.Bids))
 			for i := 0; i < len(bids); i++ {
@@ -4075,7 +4075,7 @@ func (b *validatingBidder) requestBid(ctx context.Context, request *openrtb2.Bid
 			errs = append(errs, errors.New(err))
 		}
 	} else {
-		b.t.Errorf("%s: Bidder %s got unexpected request for alias %s. No mock responses.", b.fileName, b.bidderName, name)
+		b.t.Errorf("%s: Bidder %s got unexpected request for alias %s. No mock responses.", b.fileName, b.bidderName, bidderRequest.BidderName)
 	}
 
 	return
@@ -4085,8 +4085,8 @@ type capturingRequestBidder struct {
 	req *openrtb2.BidRequest
 }
 
-func (b *capturingRequestBidder) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) (seatBid *pbsOrtbSeatBid, errs []error) {
-	b.req = request
+func (b *capturingRequestBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) (seatBid *pbsOrtbSeatBid, errs []error) {
+	b.req = bidderRequest.BidRequest
 	return &pbsOrtbSeatBid{}, nil
 }
 
@@ -4187,7 +4187,7 @@ func (e *emptyUsersync) HasAnyLiveSyncs() bool {
 
 type panicingAdapter struct{}
 
-func (panicingAdapter) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) (posb *pbsOrtbSeatBid, errs []error) {
+func (panicingAdapter) requestBid(ctx context.Context, bidderRequest BidderRequest, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) (posb *pbsOrtbSeatBid, errs []error) {
 	panic("Panic! Panic! The world is ending!")
 }
 
diff --git a/exchange/utils_test.go b/exchange/utils_test.go
index 4c5f291e..2ddafd67 100644
--- a/exchange/utils_test.go
+++ b/exchange/utils_test.go
@@ -564,6 +564,267 @@ func TestCleanOpenRTBRequestsWithFPD(t *testing.T) {
 	}
 }
 
+func TestCleanOpenRTBRequestsWithBidResponses(t *testing.T) {
+	bidRespId1 := json.RawMessage(`{"id": "resp_id1"}`)
+	bidRespId2 := json.RawMessage(`{"id": "resp_id2"}`)
+
+	testCases := []struct {
+		description            string
+		storedBidResponses     map[string]map[string]json.RawMessage
+		imps                   []openrtb2.Imp
+		expectedBidderRequests map[string]BidderRequest
+	}{
+		{
+			description: "Request with imp with one bidder stored bid response",
+			storedBidResponses: map[string]map[string]json.RawMessage{
+				"imp-id1": {"bidderA": bidRespId1},
+			},
+			imps: []openrtb2.Imp{
+				{
+					ID: "imp-id1",
+					Video: &openrtb2.Video{
+						W: 300,
+						H: 250,
+					},
+					Ext: json.RawMessage(`{"bidderA": {"placementId":"123"}}`),
+				},
+			},
+			expectedBidderRequests: map[string]BidderRequest{
+				"bidderA": {
+					BidRequest: &openrtb2.BidRequest{Imp: nil},
+					BidderName: "bidderA",
+					BidderStoredResponses: map[string]json.RawMessage{
+						"imp-id1": bidRespId1},
+				},
+			},
+		},
+		{
+			description: "Request with imps with and without stored bid response for one bidder",
+			storedBidResponses: map[string]map[string]json.RawMessage{
+				"imp-id1": {"bidderA": bidRespId1},
+			},
+			imps: []openrtb2.Imp{
+				{
+					ID: "imp-id1",
+					Video: &openrtb2.Video{
+						W: 300,
+						H: 250,
+					},
+					Ext: json.RawMessage(`{"bidderA": {"placementId":"123"}}`),
+				},
+				{
+					ID:  "imp-id2",
+					Ext: json.RawMessage(`{"bidderA": {"placementId":"123"}}`),
+				},
+			},
+			expectedBidderRequests: map[string]BidderRequest{
+				"bidderA": {
+					BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{
+						{ID: "imp-id2", Ext: json.RawMessage(`{"bidder":{"placementId":"123"}}`)},
+					}},
+					BidderName: "bidderA",
+					BidderStoredResponses: map[string]json.RawMessage{
+						"imp-id1": bidRespId1},
+				},
+			},
+		},
+		{
+			description: "Request with imp with 2 bidders stored bid response",
+			storedBidResponses: map[string]map[string]json.RawMessage{
+				"imp-id1": {"bidderA": bidRespId1, "bidderB": bidRespId2},
+			},
+			imps: []openrtb2.Imp{
+				{
+					ID: "imp-id1",
+					Video: &openrtb2.Video{
+						W: 300,
+						H: 250,
+					},
+					Ext: json.RawMessage(`{"bidderA": {"placementId":"123"}}`),
+				},
+			},
+			expectedBidderRequests: map[string]BidderRequest{
+				"bidderA": {
+					BidRequest: &openrtb2.BidRequest{Imp: nil},
+					BidderName: "bidderA",
+					BidderStoredResponses: map[string]json.RawMessage{
+						"imp-id1": bidRespId1,
+					},
+				},
+				"bidderB": {
+					BidRequest: &openrtb2.BidRequest{Imp: nil},
+					BidderName: "bidderB",
+					BidderStoredResponses: map[string]json.RawMessage{
+						"imp-id1": bidRespId2},
+				},
+			},
+		},
+		{
+			description: "Request with 2 imps: with 2 bidders stored bid response and imp without stored responses",
+			storedBidResponses: map[string]map[string]json.RawMessage{
+				"imp-id1": {"bidderA": bidRespId1, "bidderB": bidRespId2},
+			},
+			imps: []openrtb2.Imp{
+				{
+					ID: "imp-id1",
+					Video: &openrtb2.Video{
+						W: 300,
+						H: 250,
+					},
+					Ext: json.RawMessage(`{"bidderA": {"placementId":"123"}}`),
+				},
+				{
+					ID:  "imp-id2",
+					Ext: json.RawMessage(`{"bidderA": {"placementId":"123"}}`),
+				},
+			},
+			expectedBidderRequests: map[string]BidderRequest{
+				"bidderA": {
+					BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{
+						{ID: "imp-id2", Ext: json.RawMessage(`{"bidder":{"placementId":"123"}}`)},
+					}},
+					BidderName: "bidderA",
+					BidderStoredResponses: map[string]json.RawMessage{
+						"imp-id1": bidRespId1},
+				},
+				"bidderB": {
+					BidRequest: &openrtb2.BidRequest{Imp: nil},
+					BidderName: "bidderB",
+					BidderStoredResponses: map[string]json.RawMessage{
+						"imp-id1": bidRespId2},
+				},
+			},
+		},
+		{
+			description: "Request with 3 imps: with 2 bidders stored bid response and 2 imps without stored responses",
+			storedBidResponses: map[string]map[string]json.RawMessage{
+				"imp-id1": {"bidderA": bidRespId1, "bidderB": bidRespId2},
+			},
+			imps: []openrtb2.Imp{
+				{
+					ID:  "imp-id3",
+					Ext: json.RawMessage(`{"bidderC": {"placementId":"1234"}}`),
+				},
+				{
+					ID: "imp-id1",
+					Video: &openrtb2.Video{
+						W: 300,
+						H: 250,
+					},
+					Ext: json.RawMessage(`{"bidderA": {"placementId":"123"}}`),
+				},
+				{
+					ID:  "imp-id2",
+					Ext: json.RawMessage(`{"bidderA": {"placementId":"123"}}`),
+				},
+			},
+			expectedBidderRequests: map[string]BidderRequest{
+				"bidderA": {
+					BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{
+						{ID: "imp-id2", Ext: json.RawMessage(`{"bidder":{"placementId":"123"}}`)},
+					}},
+					BidderName: "bidderA",
+					BidderStoredResponses: map[string]json.RawMessage{
+						"imp-id1": bidRespId1},
+				},
+				"bidderB": {
+					BidRequest: &openrtb2.BidRequest{Imp: nil},
+					BidderName: "bidderB",
+					BidderStoredResponses: map[string]json.RawMessage{
+						"imp-id1": bidRespId2},
+				},
+				"bidderC": {
+					BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{
+						{ID: "imp-id3", Ext: json.RawMessage(`{"bidder":{"placementId":"1234"}}`)},
+					}},
+					BidderName:            "bidderC",
+					BidderStoredResponses: nil,
+				},
+			},
+		},
+		{
+			description: "Request with 2 imps: with 1 bidders stored bid response and imp without stored responses and with the same bidder",
+			storedBidResponses: map[string]map[string]json.RawMessage{
+				"imp-id2": {"bidderA": bidRespId2},
+			},
+			imps: []openrtb2.Imp{
+				{
+					ID:  "imp-id1",
+					Ext: json.RawMessage(`{"bidderA": {"placementId":"123"}}`),
+				},
+				{
+					ID:  "imp-id2",
+					Ext: json.RawMessage(`{"bidderA": {"placementId":"123"}}`),
+				},
+			},
+			expectedBidderRequests: map[string]BidderRequest{
+				"bidderA": {
+					BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{
+						{ID: "imp-id1", Ext: json.RawMessage(`{"bidder":{"placementId":"123"}}`)},
+					}},
+					BidderName: "bidderA",
+					BidderStoredResponses: map[string]json.RawMessage{
+						"imp-id2": bidRespId2},
+				},
+			},
+		},
+		{
+			description: "Request with 2 imps with stored responses and with the same bidder",
+			storedBidResponses: map[string]map[string]json.RawMessage{
+				"imp-id1": {"bidderA": bidRespId1},
+				"imp-id2": {"bidderA": bidRespId2},
+			},
+			imps: []openrtb2.Imp{
+				{
+					ID:  "imp-id1",
+					Ext: json.RawMessage(`"prebid": {}`),
+				},
+				{
+					ID:  "imp-id2",
+					Ext: json.RawMessage(`"prebid": {}`),
+				},
+			},
+			expectedBidderRequests: map[string]BidderRequest{
+				"bidderA": {
+					BidRequest: &openrtb2.BidRequest{Imp: nil},
+					BidderName: "bidderA",
+					BidderStoredResponses: map[string]json.RawMessage{
+						"imp-id1": bidRespId1,
+						"imp-id2": bidRespId2,
+					},
+				},
+			},
+		},
+	}
+
+	for _, test := range testCases {
+		metricsMock := metrics.MetricsEngineMock{}
+		bidderToSyncerKey := map[string]string{}
+		auctionReq := AuctionRequest{
+			BidRequestWrapper:  &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{Imp: test.imps}},
+			UserSyncs:          &emptyUsersync{},
+			StoredBidResponses: test.storedBidResponses,
+		}
+		actualBidderRequests, _, err := cleanOpenRTBRequests(
+			context.Background(),
+			auctionReq,
+			nil,
+			bidderToSyncerKey,
+			&metricsMock,
+			gdpr.SignalNo,
+			gdpr.AlwaysAllow{},
+			config.Privacy{},
+			&config.TCF2{})
+		assert.Empty(t, err, "No errors should be returned")
+		assert.Len(t, actualBidderRequests, len(test.expectedBidderRequests), "result len doesn't match for testCase %s", test.description)
+		for _, actualBidderRequest := range actualBidderRequests {
+			bidderName := string(actualBidderRequest.BidderName)
+			assert.Equal(t, test.expectedBidderRequests[bidderName].BidRequest.Imp, actualBidderRequest.BidRequest.Imp, "incorrect Impressions for testCase %s", test.description)
+			assert.Equal(t, test.expectedBidderRequests[bidderName].BidderStoredResponses, actualBidderRequest.BidderStoredResponses, "incorrect Bidder Stored Responses for testCase %s", test.description)
+		}
+	}
+}
+
 func TestCleanOpenRTBRequestsCCPA(t *testing.T) {
 	trueValue, falseValue := true, false
 
diff --git a/server/server_test.go b/server/server_test.go
index e7ef593a..23b45656 100644
--- a/server/server_test.go
+++ b/server/server_test.go
@@ -1,37 +1,19 @@
 package server
 
 import (
+	"fmt"
+	"net"
 	"net/http"
 	"os"
+	"strconv"
 	"testing"
+	"time"
 
 	"github.com/prebid/prebid-server/config"
+	metricsconfig "github.com/prebid/prebid-server/metrics/config"
+	"github.com/stretchr/testify/assert"
 )
 
-func TestNewAdminServer(t *testing.T) {
-	cfg := &config.Configuration{
-		Host:      "prebid.com",
-		AdminPort: 6060,
-		Port:      8000,
-	}
-	server := newAdminServer(cfg, http.HandlerFunc(handler))
-	if server.Addr != "prebid.com:6060" {
-		t.Errorf("Admin server address should be %s. Got %s", "prebid.com:6060", server.Addr)
-	}
-}
-
-func TestNewMainServer(t *testing.T) {
-	cfg := &config.Configuration{
-		Host:      "prebid.com",
-		AdminPort: 6060,
-		Port:      8000,
-	}
-	server := newMainServer(cfg, http.HandlerFunc(handler))
-	if server.Addr != "prebid.com:8000" {
-		t.Errorf("Admin server address should be %s. Got %s", "prebid.com:8000", server.Addr)
-	}
-}
-
 func TestServerShutdown(t *testing.T) {
 	server := &http.Server{}
 	ln := &mockListener{}
@@ -67,10 +49,6 @@ func TestWait(t *testing.T) {
 	// If this doesn't hang, then wait() is sending and receiving messages as expected.
 }
 
-func handler(w http.ResponseWriter, req *http.Request) {
-
-}
-
 // forwardSignal is basically a working mock for shutdownAfterSignals().
 // It is used to test wait() effectively
 func forwardSignal(t *testing.T, outbound chan<- struct{}, inbound <-chan os.Signal) {
@@ -81,3 +59,143 @@ func forwardSignal(t *testing.T, outbound chan<- struct{}, inbound <-chan os.Sig
 	}
 	outbound <- s
 }
+
+func TestNewSocketServer(t *testing.T) {
+	const mockSocket = "socket_addr:socket_port"
+	cfg := new(config.Configuration)
+	cfg.UnixSocketName = mockSocket
+
+	mockServer := &http.Server{
+		Addr:         cfg.UnixSocketName,
+		Handler:      nil,
+		ReadTimeout:  15 * time.Second,
+		WriteTimeout: 15 * time.Second,
+	}
+
+	ret := newSocketServer(cfg, nil)
+	assert.NotEqual(t, nil, ret, "ret : isNil()")
+	assert.Equal(t, mockServer.Addr, ret.Addr, fmt.Sprintf("Addr invalide: %v != %v",
+		ret.Addr, mockServer.Addr))
+	assert.Equal(t, mockServer.ReadTimeout, ret.ReadTimeout, fmt.Sprintf("ReadTimeout invalide: %v != %v",
+		ret.ReadTimeout, mockServer.ReadTimeout))
+	assert.Equal(t, mockServer.WriteTimeout, ret.WriteTimeout, fmt.Sprintf("WriteTimeout invalide: %v != %v",
+		ret.WriteTimeout, mockServer.WriteTimeout))
+
+	if ret != nil {
+		ret.Close()
+	}
+}
+
+func TestNewMainServer(t *testing.T) {
+	const (
+		mockPort    = 8000         // chose your socket_port
+		mockAddress = "prebid.com" // chose your socket_address
+	)
+	cfg := new(config.Configuration)
+	cfg.Port = mockPort
+	cfg.Host = mockAddress
+
+	mockServer := &http.Server{
+		Addr:         fmt.Sprintf("%s:%d", mockAddress, mockPort),
+		Handler:      nil,
+		ReadTimeout:  15 * time.Second,
+		WriteTimeout: 15 * time.Second,
+	}
+
+	ret := newMainServer(cfg, nil)
+	assert.NotEqual(t, nil, ret, "ret : isNil()")
+	assert.Equal(t, ret.Addr, mockServer.Addr, fmt.Sprintf("Addr invalide: %v != %v",
+		ret.Addr, mockServer.Addr))
+	assert.Equal(t, ret.ReadTimeout, mockServer.ReadTimeout,
+		fmt.Sprintf("ReadTimeout invalide: %v != %v", ret.ReadTimeout, mockServer.ReadTimeout))
+	assert.Equal(t, ret.WriteTimeout, mockServer.WriteTimeout,
+		fmt.Sprintf("WriteTimeout invalide: %v != %v", ret.WriteTimeout, mockServer.WriteTimeout))
+
+	if ret != nil {
+		ret.Close()
+	}
+}
+
+func TestNewTCPListener(t *testing.T) {
+	const mockAddress = ":8000" //:chose your socket_port
+
+	ret, err := newTCPListener(mockAddress, nil)
+	assert.Equal(t, nil, err, fmt.Sprintf("err_ : %v", err))
+	assert.NotEqual(t, nil, ret, "ret : isNil()")
+
+	if ret != nil {
+		ret.Close()
+	}
+}
+
+func TestNewUnixListener(t *testing.T) {
+	const mockFile = "file_referer" // chose your file_referer
+
+	ret, err := newUnixListener(mockFile, nil)
+	assert.Equal(t, nil, err, "err_ : NOT-Nil()")
+	assert.NotEqual(t, nil, ret, "ret : isNil()")
+
+	if ret != nil {
+		ret.Close()
+	}
+}
+
+func TestNewAdminServer(t *testing.T) {
+	const (
+		mockHost  = "prebid.com" // chose your host
+		mockAdmin = 6060         // chose your admin_port
+	)
+	cfg := new(config.Configuration)
+	cfg.Host = mockHost
+	cfg.AdminPort = mockAdmin
+
+	mockServer := &http.Server{
+		Addr:    cfg.Host + ":" + strconv.Itoa(cfg.AdminPort),
+		Handler: nil,
+	}
+
+	ret := newAdminServer(cfg, nil)
+	assert.NotEqual(t, nil, ret, "ret : isNil()")
+	assert.Equal(t, mockServer.Addr, ret.Addr, fmt.Sprintf("Addr invalide: %v != %v",
+		ret.Addr, mockServer.Addr))
+
+	if ret != nil {
+		ret.Close()
+	}
+}
+
+func TestRunServer(t *testing.T) {
+	const mockName = "mockServer_name"
+
+	err := runServer(nil, mockName, nil)
+	assert.NotEqual(t, nil, err, "runServer(nil, 'mockName', nil) : didn't trigger any error.")
+
+	s := http.Server{}
+	err = runServer(&s, mockName, nil)
+	assert.NotEqual(t, nil, err, "runServer(not_nil, 'mockName', nil) : didn't trigger any error.")
+
+	var l net.Listener
+	l, _ = net.Listen("error", ":8000")
+	err = runServer(&s, mockName, l)
+	assert.NotEqual(t, nil, err, "Listen('error', ':8000') : didn't trigger any error.")
+}
+
+func TestListen(t *testing.T) {
+	const name = "TestListen"
+	var (
+		handler, adminHandler http.Handler
+
+		metrics = new(metricsconfig.DetailedMetricsEngine)
+		cfg     = &config.Configuration{
+			Host:             "prebid.com",
+			AdminPort:        6060,
+			Port:             8000,
+			UnixSocketEnable: false,
+			UnixSocketName:   "prebid_socket",
+			EnableGzip:       false,
+		}
+	)
+
+	err := Listen(cfg, handler, adminHandler, metrics)
+	assert.NotEqual(t, nil, err, "err : isNil()")
+}
diff --git a/stored_responses/stored_responses_test.go b/stored_responses/stored_responses_test.go
new file mode 100644
index 00000000..e63bbc99
--- /dev/null
+++ b/stored_responses/stored_responses_test.go
@@ -0,0 +1,581 @@
+package stored_responses
+
+import (
+	"context"
+	"encoding/json"
+	"errors"
+	"github.com/mxmCherry/openrtb/v15/openrtb2"
+	"github.com/prebid/prebid-server/openrtb_ext"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestRemoveImpsWithStoredResponses(t *testing.T) {
+	bidRespId1 := json.RawMessage(`{"id": "resp_id1"}`)
+	testCases := []struct {
+		description        string
+		reqIn              *openrtb2.BidRequest
+		storedBidResponses ImpBidderStoredResp
+		expectedImps       []openrtb2.Imp
+	}{
+		{
+			description: "request with imps and stored bid response for this imp",
+			reqIn: &openrtb2.BidRequest{Imp: []openrtb2.Imp{
+				{ID: "imp-id1"},
+			}},
+			storedBidResponses: ImpBidderStoredResp{
+				"imp-id1": {"appnexus": bidRespId1},
+			},
+			expectedImps: nil,
+		},
+		{
+			description: "request with imps and stored bid response for one of these imp",
+			reqIn: &openrtb2.BidRequest{Imp: []openrtb2.Imp{
+				{ID: "imp-id1"},
+				{ID: "imp-id2"},
+			}},
+			storedBidResponses: ImpBidderStoredResp{
+				"imp-id1": {"appnexus": bidRespId1},
+			},
+			expectedImps: []openrtb2.Imp{
+				{
+					ID: "imp-id2",
+				},
+			},
+		},
+		{
+			description: "request with imps and stored bid response for both of these imp",
+			reqIn: &openrtb2.BidRequest{Imp: []openrtb2.Imp{
+				{ID: "imp-id1"},
+				{ID: "imp-id2"},
+			}},
+			storedBidResponses: ImpBidderStoredResp{
+				"imp-id1": {"appnexus": bidRespId1},
+				"imp-id2": {"appnexus": bidRespId1},
+			},
+			expectedImps: nil,
+		},
+		{
+			description: "request with imps and no stored bid responses",
+			reqIn: &openrtb2.BidRequest{Imp: []openrtb2.Imp{
+				{ID: "imp-id1"},
+				{ID: "imp-id2"},
+			}},
+			storedBidResponses: nil,
+
+			expectedImps: []openrtb2.Imp{
+				{ID: "imp-id1"},
+				{ID: "imp-id2"},
+			},
+		},
+	}
+	for _, testCase := range testCases {
+		request := testCase.reqIn
+		removeImpsWithStoredResponses(request, testCase.storedBidResponses)
+		assert.Equal(t, testCase.expectedImps, request.Imp, "incorrect Impressions for testCase %s", testCase.description)
+	}
+}
+
+func TestBuildStoredBidResponses(t *testing.T) {
+	bidRespId1 := json.RawMessage(`{"id": "resp_id1"}`)
+	bidRespId2 := json.RawMessage(`{"id": "resp_id2"}`)
+	bidRespId3 := json.RawMessage(`{"id": "resp_id3"}`)
+	testCases := []struct {
+		description        string
+		storedBidResponses ImpBidderStoredResp
+		expectedResult     BidderImpsWithBidResponses
+	}{
+		{
+			description: "one imp and stored response for this imp with one bidder",
+			storedBidResponses: ImpBidderStoredResp{
+				"imp-id1": {"bidderA": bidRespId1},
+			},
+			expectedResult: BidderImpsWithBidResponses{
+				"bidderA": {
+					"imp-id1": bidRespId1,
+				},
+			},
+		},
+		{
+			description: "one imp and stored response for this imp with two bidders",
+			storedBidResponses: ImpBidderStoredResp{
+				"imp-id1": {"bidderA": bidRespId1, "bidderB": bidRespId2},
+			},
+
+			expectedResult: BidderImpsWithBidResponses{
+				"bidderA": {
+					"imp-id1": bidRespId1,
+				},
+				"bidderB": {
+					"imp-id1": bidRespId2,
+				},
+			},
+		},
+		{
+			description: "two imps and stored response for this imp with two bidders",
+			storedBidResponses: ImpBidderStoredResp{
+				"imp-id1": {"bidderA": bidRespId1},
+				"imp-id2": {"bidderB": bidRespId2},
+			},
+
+			expectedResult: BidderImpsWithBidResponses{
+				"bidderA": {
+					"imp-id1": bidRespId1,
+				},
+				"bidderB": {
+					"imp-id2": bidRespId2,
+				},
+			},
+		},
+		{
+			description: "three imps and stored response for these imps with two bidders",
+			storedBidResponses: ImpBidderStoredResp{
+				"imp-id1": {"bidderA": bidRespId1},
+				"imp-id2": {"bidderB": bidRespId2},
+				"imp-id3": {"bidderA": bidRespId3},
+			},
+
+			expectedResult: BidderImpsWithBidResponses{
+				"bidderA": {
+					"imp-id1": bidRespId1,
+					"imp-id3": bidRespId3,
+				},
+				"bidderB": {
+					"imp-id2": bidRespId2,
+				},
+			},
+		},
+		{
+			description:        "empty stored responses",
+			storedBidResponses: ImpBidderStoredResp{},
+			expectedResult:     BidderImpsWithBidResponses{},
+		},
+	}
+
+	for _, testCase := range testCases {
+
+		bidderToImpToResponses := buildStoredResp(testCase.storedBidResponses)
+		for expectedBidderName := range testCase.expectedResult {
+			assert.Equal(t, testCase.expectedResult[expectedBidderName], bidderToImpToResponses[expectedBidderName], "incorrect stored responses for testCase %s", testCase.description)
+		}
+	}
+}
+
+func TestProcessStoredAuctionAndBidResponsesErrors(t *testing.T) {
+	bidderMap := map[string]openrtb_ext.BidderName{"testBidder": "testBidder"}
+
+	testCases := []struct {
+		description       string
+		requestJson       []byte
+		expectedErrorList []error
+	}{
+		{
+			description: "Invalid stored auction response format: empty stored Auction Response Id",
+			requestJson: []byte(`{"imp": [
+    			  {
+    			    "id": "imp-id1",
+    			    "ext": {
+    			      "prebid": {
+    			        "storedauctionresponse": {
+    			        }
+    			      }
+    			    }
+    			  }
+    			]}`),
+			expectedErrorList: []error{errors.New("request.imp[0] has ext.prebid.storedauctionresponse specified, but \"id\" field is missing ")},
+		},
+		{
+			description: "Invalid stored bid response format: empty storedbidresponse.bidder",
+			requestJson: []byte(`{"imp": [
+    			  {
+    			    "id": "imp-id1",
+    			    "ext": {
+    			      "prebid": {
+    			        "storedbidresponse": [
+							{ "id": "123abc"}]
+    			      }
+    			    }
+    			  }
+    			]}`),
+			expectedErrorList: []error{errors.New("request.imp[0] has ext.prebid.storedbidresponse specified, but \"id\" or/and \"bidder\" fields are missing ")},
+		},
+		{
+			description: "Invalid stored bid response format: empty storedbidresponse.id",
+			requestJson: []byte(`{"imp": [
+    			  {
+    			    "id": "imp-id1",
+    			    "ext": {
+    			      "prebid": {
+    			        "storedbidresponse": [
+							{ "bidder": "testbidder"}]
+    			      }
+    			    }
+    			  }
+    			]}`),
+			expectedErrorList: []error{errors.New("request.imp[0] has ext.prebid.storedbidresponse specified, but \"id\" or/and \"bidder\" fields are missing ")},
+		},
+		{
+			description: "Invalid stored bid response: storedbidresponse.bidder not found",
+			requestJson: []byte(`{"imp": [
+    			  {
+    			    "id": "imp-id1",
+    			    "ext": {
+    			      "prebid": {
+    			        "storedbidresponse": [
+							{ "bidder": "testBidder123", "id": "123abc"}]
+    			      }
+    			    }
+    			  }
+    			]}`),
+			expectedErrorList: []error{errors.New("request.imp[impId: imp-id1].ext contains unknown bidder: testBidder123. Did you forget an alias in request.ext.prebid.aliases?")},
+		},
+		{
+			description: "Invalid stored auction response format: empty stored Auction Response Id in second imp",
+			requestJson: []byte(`{"imp": [
+    			  {
+    			    "id": "imp-id1",
+    			    "ext": {
+    			      "prebid": {
+    			        "storedauctionresponse": {
+							"id":"123"
+    			        }
+    			      }
+    			    }
+    			  },
+			      {
+    			    "id": "imp-id2",
+    			    "ext": {
+    			      "prebid": {
+    			        "storedauctionresponse": {
+							"id":""
+    			        }
+    			      }
+    			    }
+    			  }
+    			]}`),
+			expectedErrorList: []error{errors.New("request.imp[1] has ext.prebid.storedauctionresponse specified, but \"id\" field is missing ")},
+		},
+		{
+			description: "Invalid stored bid response format: empty stored bid Response Id in second imp",
+			requestJson: []byte(`{"imp": [
+    			  {
+    			    "id": "imp-id1",
+    			    "ext": {
+    			      "prebid": {
+    			        "storedbidresponse": [
+                             {"bidder":"testBidder", "id": "123abc"}
+                        ]
+    			      }
+    			    }
+    			  },
+			      {
+    			    "id": "imp-id2",
+    			    "ext": {
+    			      "prebid": {
+    			        "storedbidresponse": [
+                             {"bidder":"testBidder", "id": ""}
+                        ]
+    			      }
+    			    }
+    			  }
+    			]}`),
+			expectedErrorList: []error{errors.New("request.imp[1] has ext.prebid.storedbidresponse specified, but \"id\" or/and \"bidder\" fields are missing ")},
+		},
+	}
+
+	for _, test := range testCases {
+		_, _, errorList := ProcessStoredResponses(nil, test.requestJson, nil, bidderMap)
+		assert.Equalf(t, test.expectedErrorList, errorList, "Error doesn't match: %s\n", test.description)
+	}
+
+}
+
+func TestProcessStoredAuctionAndBidResponses(t *testing.T) {
+	bidderMap := map[string]openrtb_ext.BidderName{"bidderA": "bidderA", "bidderB": "bidderB"}
+	bidStoredResp1 := json.RawMessage(`[{"bid": [{"id": "bid_id1"],"seat": "bidderA"}]`)
+	bidStoredResp2 := json.RawMessage(`[{"bid": [{"id": "bid_id2"],"seat": "bidderB"}]`)
+	bidStoredResp3 := json.RawMessage(`[{"bid": [{"id": "bid_id3"],"seat": "bidderA"}]`)
+	mockStoredResponses := map[string]json.RawMessage{
+		"1": bidStoredResp1,
+		"2": bidStoredResp2,
+		"3": bidStoredResp3,
+	}
+	fetcher := &mockStoredBidResponseFetcher{mockStoredResponses}
+
+	testCases := []struct {
+		description                    string
+		requestJson                    []byte
+		expectedStoredAuctionResponses ImpsWithBidResponses
+		expectedStoredBidResponses     ImpBidderStoredResp
+	}{
+		{
+			description: "No stored responses",
+			requestJson: []byte(`{"imp": [
+    			  {
+    			    "id": "imp-id1",
+    			    "ext": {
+    			      "prebid": {
+    			        
+    			      }
+    			    }
+    			  }
+    			]}`),
+			expectedStoredAuctionResponses: nil,
+			expectedStoredBidResponses:     nil,
+		},
+		{
+			description: "Stored auction response one imp",
+			requestJson: []byte(`{"imp": [
+    			  {
+    			    "id": "imp-id1",
+    			    "ext": {
+                		"appnexus": {
+							"placementId": 123
+                		},
+                		"prebid": {
+                    		"storedauctionresponse": {
+                        		"id": "1"
+                    		}
+                		}
+            		}
+    			  }
+    			]}`),
+			expectedStoredAuctionResponses: ImpsWithBidResponses{
+				"imp-id1": bidStoredResp1,
+			},
+			expectedStoredBidResponses: ImpBidderStoredResp{},
+		},
+		{
+			description: "Stored bid response one imp",
+			requestJson: []byte(`{"imp": [
+    			  {
+    			    "id": "imp-id1",
+    			    "ext": {
+                		"appnexus": {
+							"placementId": 123
+                		},
+                		"prebid": {
+                    		"storedbidresponse": [
+                        		{"bidder":"bidderA", "id": "1"}
+                    		]
+                		}
+            		}
+    			  }
+    			]}`),
+			expectedStoredAuctionResponses: ImpsWithBidResponses{},
+			expectedStoredBidResponses: ImpBidderStoredResp{
+				"imp-id1": {"bidderA": bidStoredResp1},
+			},
+		},
+		{
+			description: "Stored bid responses two bidders one imp",
+			requestJson: []byte(`{"imp": [
+    			  {
+    			    "id": "imp-id1",
+    			    "ext": {
+                		"appnexus": {
+							"placementId": 123
+                		},
+                		"prebid": {
+                    		"storedbidresponse": [
+                        		{"bidder":"bidderA", "id": "1"},
+                        		{"bidder":"bidderB", "id": "2"}
+                    		]
+                		}
+            		}
+    			  }
+    			]}`),
+			expectedStoredAuctionResponses: ImpsWithBidResponses{},
+			expectedStoredBidResponses: ImpBidderStoredResp{
+				"imp-id1": {"bidderA": bidStoredResp1, "bidderB": bidStoredResp2},
+			},
+		},
+		{
+			//This is not a valid scenario for real auction request, added for testing purposes
+			description: "Stored auction and bid responses one imp",
+			requestJson: []byte(`{"imp": [
+    			  {
+    			    "id": "imp-id1",
+    			    "ext": {
+                		"appnexus": {
+							"placementId": 123
+                		},
+                		"prebid": {
+							"storedauctionresponse": {
+                        		"id": "1"
+                    		},
+                    		"storedbidresponse": [
+                        		{"bidder":"bidderA", "id": "1"},
+                        		{"bidder":"bidderB", "id": "2"}
+                    		]
+                		}
+            		}
+    			  }
+    			]}`),
+			expectedStoredAuctionResponses: ImpsWithBidResponses{
+				"imp-id1": bidStoredResp1,
+			},
+			expectedStoredBidResponses: ImpBidderStoredResp{
+				"imp-id1": {"bidderA": bidStoredResp1, "bidderB": bidStoredResp2},
+			},
+		},
+		{
+			description: "Stored auction response three imps",
+			requestJson: []byte(`{"imp": [
+    			  {
+    			    "id": "imp-id1",
+    			    "ext": {
+                		"appnexus": {
+							"placementId": 123
+                		},
+                		"prebid": {
+                    		"storedauctionresponse": {
+                        		"id": "1"
+                    		}
+                		}
+            		}
+    			  },
+					{
+    			    "id": "imp-id2",
+    			    "ext": {
+                		"appnexus": {
+							"placementId": 123
+                		},
+                		"prebid": {
+                    		"storedauctionresponse": {
+                        		"id": "2"
+                    		}
+                		}
+            		}
+    			  },
+					{
+    			    "id": "imp-id3",
+    			    "ext": {
+                		"appnexus": {
+							"placementId": 123
+                		},
+                		"prebid": {
+                    		"storedauctionresponse": {
+                        		"id": "3"
+                    		}
+                		}
+            		}
+    			  }
+    			]}`),
+			expectedStoredAuctionResponses: ImpsWithBidResponses{
+				"imp-id1": bidStoredResp1,
+				"imp-id2": bidStoredResp2,
+				"imp-id3": bidStoredResp3,
+			},
+			expectedStoredBidResponses: ImpBidderStoredResp{},
+		},
+		{
+			description: "Stored auction response three imps duplicated stored auction response",
+			requestJson: []byte(`{"imp": [
+    			  {
+    			    "id": "imp-id1",
+    			    "ext": {
+                		"appnexus": {
+							"placementId": 123
+                		},
+                		"prebid": {
+                    		"storedauctionresponse": {
+                        		"id": "1"
+                    		}
+                		}
+            		}
+    			  },
+					{
+    			    "id": "imp-id2",
+    			    "ext": {
+                		"appnexus": {
+							"placementId": 123
+                		},
+                		"prebid": {
+                    		"storedauctionresponse": {
+                        		"id": "2"
+                    		}
+                		}
+            		}
+    			  },
+					{
+    			    "id": "imp-id3",
+    			    "ext": {
+                		"appnexus": {
+							"placementId": 123
+                		},
+                		"prebid": {
+                    		"storedauctionresponse": {
+                        		"id": "2"
+                    		}
+                		}
+            		}
+    			  }
+    			]}`),
+			expectedStoredAuctionResponses: ImpsWithBidResponses{
+				"imp-id1": bidStoredResp1,
+				"imp-id2": bidStoredResp2,
+				"imp-id3": bidStoredResp2,
+			},
+			expectedStoredBidResponses: ImpBidderStoredResp{},
+		},
+		{
+			description: "Stored bid responses two bidders two imp",
+			requestJson: []byte(`{"imp": [
+    			  {
+    			    "id": "imp-id1",
+    			    "ext": {
+                		"appnexus": {
+							"placementId": 123
+                		},
+                		"prebid": {
+                    		"storedbidresponse": [
+                        		{"bidder":"bidderA", "id": "1"},
+                        		{"bidder":"bidderB", "id": "2"}
+                    		]
+                		}
+            		}
+    			  },
+					{
+    			    "id": "imp-id2",
+    			    "ext": {
+                		"appnexus": {
+							"placementId": 123
+                		},
+                		"prebid": {
+                    		"storedbidresponse": [
+                        		{"bidder":"bidderA", "id": "3"},
+                        		{"bidder":"bidderB", "id": "2"}
+                    		]
+                		}
+            		}
+    			  }
+    			]}`),
+			expectedStoredAuctionResponses: ImpsWithBidResponses{},
+			expectedStoredBidResponses: ImpBidderStoredResp{
+				"imp-id1": {"bidderA": bidStoredResp1, "bidderB": bidStoredResp2},
+				"imp-id2": {"bidderA": bidStoredResp3, "bidderB": bidStoredResp2},
+			},
+		},
+	}
+
+	for _, test := range testCases {
+		storedAuctionResponses, storedBidResponses, errorList := ProcessStoredResponses(nil, test.requestJson, fetcher, bidderMap)
+		assert.Equal(t, test.expectedStoredAuctionResponses, storedAuctionResponses, "storedAuctionResponses doesn't match: %s\n", test.description)
+		assert.Equalf(t, test.expectedStoredBidResponses, storedBidResponses, "storedBidResponses doesn't match: %s\n", test.description)
+		assert.Nil(t, errorList, "Error should be nil")
+	}
+
+}
+
+type mockStoredBidResponseFetcher struct {
+	data map[string]json.RawMessage
+}
+
+func (cf *mockStoredBidResponseFetcher) FetchRequests(ctx context.Context, requestIDs []string, impIDs []string) (requestData map[string]json.RawMessage, impData map[string]json.RawMessage, errs []error) {
+	return nil, nil, nil
+}
+
+func (cf *mockStoredBidResponseFetcher) FetchResponses(ctx context.Context, ids []string) (data map[string]json.RawMessage, errs []error) {
+	return cf.data, nil
+}