Repository: prebid/prebid-server
Pull request #1181 (2020-01-30 21:20:20)

Adding bid rejection messages to debug response


Patch:
diff --git a/adapters/synacormedia/synacormedia.go b/adapters/synacormedia/synacormedia.go
index ccb2798f..2d913f02 100644
--- a/adapters/synacormedia/synacormedia.go
+++ b/adapters/synacormedia/synacormedia.go
@@ -20,6 +20,7 @@ type SynacorMediaAdapter struct {
 
 type SyncEndpointTemplateParams struct {
 	SeatId string
+	TagId  string
 }
 
 type ReqExt struct {
@@ -55,14 +56,23 @@ func (a *SynacorMediaAdapter) makeRequest(request *openrtb.BidRequest) (*adapter
 	var firstExtImp *openrtb_ext.ExtImpSynacormedia = nil
 
 	for _, imp := range request.Imp {
-		validImp, err := getExtImpObj(&imp)
+		validExtImpObj, err := getExtImpObj(&imp) // getExtImpObj returns {seatId:"", tagId:""}
 		if err != nil {
 			errs = append(errs, err)
 			continue
 		}
+		// if the bid request is missing seatId or TagId then ignore it
+		if validExtImpObj.SeatId == "" || validExtImpObj.TagId == "" {
+			errs = append(errs, &errortypes.BadServerResponse{
+				Message: fmt.Sprintf("Invalid Impression"),
+			})
+			continue
+		}
+		// right here is where we need to take out the tagId and then add it to imp
+		imp.TagID = validExtImpObj.TagId
 		validImps = append(validImps, imp)
 		if firstExtImp == nil {
-			firstExtImp = validImp
+			firstExtImp = validExtImpObj
 		}
 	}
 
@@ -72,11 +82,12 @@ func (a *SynacorMediaAdapter) makeRequest(request *openrtb.BidRequest) (*adapter
 
 	var err error
 
-	if firstExtImp == nil || firstExtImp.SeatId == "" {
+	if firstExtImp == nil || firstExtImp.SeatId == "" || firstExtImp.TagId == "" {
 		return nil, append(errs, &errortypes.BadServerResponse{
-			Message: fmt.Sprintf("Impression missing seat id"),
+			Message: fmt.Sprintf("Invalid Impression"),
 		})
 	}
+	// this is where the empty seatId is filled
 	re = &ReqExt{SeatId: firstExtImp.SeatId}
 
 	// create JSON Request Body
diff --git a/config/config.go b/config/config.go
index ba6ed05e..943d18a9 100644
--- a/config/config.go
+++ b/config/config.go
@@ -23,6 +23,7 @@ type Configuration struct {
 	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"`
 	// StatusResponse is the string which will be returned by the /status endpoint when things are OK.
@@ -583,6 +584,9 @@ func SetupViper(v *viper.Viper, filename string) {
 	v.SetDefault("http_client.max_idle_connections", 400)
 	v.SetDefault("http_client.max_idle_connections_per_host", 10)
 	v.SetDefault("http_client.idle_connection_timeout_seconds", 60)
+	v.SetDefault("http_client_cache.max_idle_connections", 10)
+	v.SetDefault("http_client_cache.max_idle_connections_per_host", 2)
+	v.SetDefault("http_client_cache.idle_connection_timeout_seconds", 60)
 	// no metrics configured by default (metrics{host|database|username|password})
 	v.SetDefault("metrics.disabled_metrics.account_adapter_details", false)
 	v.SetDefault("metrics.influxdb.host", "")
@@ -683,7 +687,7 @@ func SetupViper(v *viper.Viper, filename string) {
 	v.SetDefault("adapters.datablocks.endpoint", "http://{{.Host}}/openrtb2?sid={{.SourceId}}")
 	v.SetDefault("adapters.emx_digital.endpoint", "https://hb.emxdgt.com")
 	v.SetDefault("adapters.engagebdr.endpoint", "http://dsp.bnmla.com/hb")
-	v.SetDefault("adapters.eplanning.endpoint", "http://ads.us.e-planning.net/hb/1")
+	v.SetDefault("adapters.eplanning.endpoint", "https://ads.us.e-planning.net/hb/1")
 	v.SetDefault("adapters.gamma.endpoint", "https://hb.gammaplatform.com/adx/request/")
 	v.SetDefault("adapters.gamoshi.endpoint", "https://rtb.gamoshi.io")
 	v.SetDefault("adapters.grid.endpoint", "http://grid.bidswitch.net/sp_bid?sp=prebid")
diff --git a/exchange/exchange.go b/exchange/exchange.go
index 3d9055ca..8c6cac4c 100644
--- a/exchange/exchange.go
+++ b/exchange/exchange.go
@@ -4,6 +4,7 @@ import (
 	"bytes"
 	"context"
 	"encoding/json"
+	"errors"
 	"fmt"
 	"math/rand"
 	"net/http"
@@ -146,10 +147,14 @@ func (e *exchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidReque
 		//If includebrandcategory is present in ext then CE feature is on.
 		if requestExt.Prebid.Targeting != nil && requestExt.Prebid.Targeting.IncludeBrandCategory != nil {
 			var err error
-			bidCategory, adapterBids, err = applyCategoryMapping(ctx, requestExt, adapterBids, *categoriesFetcher, targData)
+			var rejections []string
+			bidCategory, adapterBids, rejections, err = applyCategoryMapping(ctx, requestExt, adapterBids, *categoriesFetcher, targData)
 			if err != nil {
 				return nil, fmt.Errorf("Error in category mapping : %s", err.Error())
 			}
+			for _, message := range rejections {
+				errs = append(errs, errors.New(message))
+			}
 		}
 
 		auc = newAuction(adapterBids, len(bidRequest.Imp))
@@ -340,7 +345,7 @@ func (e *exchange) buildBidResponse(ctx context.Context, liveAdapters []openrtb_
 	return bidResponse, err
 }
 
-func applyCategoryMapping(ctx context.Context, requestExt openrtb_ext.ExtRequest, seatBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, categoriesFetcher stored_requests.CategoryFetcher, targData *targetData) (map[string]string, map[openrtb_ext.BidderName]*pbsOrtbSeatBid, error) {
+func applyCategoryMapping(ctx context.Context, requestExt openrtb_ext.ExtRequest, seatBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, categoriesFetcher stored_requests.CategoryFetcher, targData *targetData) (map[string]string, map[openrtb_ext.BidderName]*pbsOrtbSeatBid, []string, error) {
 	res := make(map[string]string)
 
 	type bidDedupe struct {
@@ -359,6 +364,7 @@ func applyCategoryMapping(ctx context.Context, requestExt openrtb_ext.ExtRequest
 	var primaryAdServer string
 	var publisher string
 	var err error
+	var rejections []string
 	var translateCategories = true
 
 	if includeBrandCategory && brandCatExt.WithCategory {
@@ -370,7 +376,7 @@ func applyCategoryMapping(ctx context.Context, requestExt openrtb_ext.ExtRequest
 			//if ext.prebid.targeting.includebrandcategory present but primaryadserver/publisher not present then error out the request right away.
 			primaryAdServer, err = getPrimaryAdServer(brandCatExt.PrimaryAdServer) //1-Freewheel 2-DFP
 			if err != nil {
-				return res, seatBids, err
+				return res, seatBids, rejections, err
 			}
 			publisher = brandCatExt.Publisher
 		}
@@ -382,6 +388,7 @@ func applyCategoryMapping(ctx context.Context, requestExt openrtb_ext.ExtRequest
 		bidsToRemove := make([]int, 0)
 		for bidInd := range seatBid.bids {
 			bid := seatBid.bids[bidInd]
+			bidID := bid.bid.ID
 			var duration int
 			var category string
 			var pb string
@@ -396,6 +403,7 @@ func applyCategoryMapping(ctx context.Context, requestExt openrtb_ext.ExtRequest
 					//TODO: add metrics
 					//on receiving bids from adapters if no unique IAB category is returned  or if no ad server category is returned discard the bid
 					bidsToRemove = append(bidsToRemove, bidInd)
+					rejections = updateRejections(rejections, bidID, "Bid did not contain a category")
 					continue
 				}
 				if translateCategories {
@@ -405,6 +413,8 @@ func applyCategoryMapping(ctx context.Context, requestExt openrtb_ext.ExtRequest
 						//TODO: add metrics
 						//if mapping required but no mapping file is found then discard the bid
 						bidsToRemove = append(bidsToRemove, bidInd)
+						reason := fmt.Sprintf("Category mapping file for primary ad server: '%s', publisher: '%s' not found", primaryAdServer, publisher)
+						rejections = updateRejections(rejections, bidID, reason)
 						continue
 					}
 				} else {
@@ -424,6 +434,7 @@ func applyCategoryMapping(ctx context.Context, requestExt openrtb_ext.ExtRequest
 				//if the bid is above the range of the listed durations (and outside the buffer), reject the bid
 				if duration > durationRange[len(durationRange)-1] {
 					bidsToRemove = append(bidsToRemove, bidInd)
+					rejections = updateRejections(rejections, bidID, "Bid duration exceeds maximum allowed")
 					continue
 				}
 				for _, dur := range durationRange {
@@ -447,11 +458,13 @@ func applyCategoryMapping(ctx context.Context, requestExt openrtb_ext.ExtRequest
 					if dupe.bidderName == bidderName {
 						// An older bid from the current bidder
 						bidsToRemove = append(bidsToRemove, dupe.bidIndex)
+						rejections = updateRejections(rejections, dupe.bidID, "Bid was deduplicated")
 					} else {
 						// An older bid from a different seatBid we've already finished with
 						oldSeatBid := (seatBids)[dupe.bidderName]
 						if len(oldSeatBid.bids) == 1 {
 							seatBidsToRemove = append(seatBidsToRemove, bidderName)
+							rejections = updateRejections(rejections, dupe.bidID, "Bid was deduplicated")
 						} else {
 							oldSeatBid.bids = append(oldSeatBid.bids[:dupe.bidIndex], oldSeatBid.bids[dupe.bidIndex+1:]...)
 						}
@@ -460,11 +473,12 @@ func applyCategoryMapping(ctx context.Context, requestExt openrtb_ext.ExtRequest
 				} else {
 					// Remove this bid
 					bidsToRemove = append(bidsToRemove, bidInd)
+					rejections = updateRejections(rejections, bidID, "Bid was deduplicated")
 					continue
 				}
 			}
-			res[bid.bid.ID] = categoryDuration
-			dedupe[categoryDuration] = bidDedupe{bidderName: bidderName, bidIndex: bidInd, bidID: bid.bid.ID}
+			res[bidID] = categoryDuration
+			dedupe[categoryDuration] = bidDedupe{bidderName: bidderName, bidIndex: bidInd, bidID: bidID}
 		}
 
 		if len(bidsToRemove) > 0 {
@@ -483,19 +497,16 @@ func applyCategoryMapping(ctx context.Context, requestExt openrtb_ext.ExtRequest
 		}
 
 	}
-	if len(seatBidsToRemove) > 0 {
-		if len(seatBidsToRemove) == len(seatBids) {
-			//delete all seat bids
-			seatBids = nil
-		} else {
-			for _, seatBidInd := range seatBidsToRemove {
-				delete(seatBids, seatBidInd)
-			}
-
-		}
+	for _, seatBidInd := range seatBidsToRemove {
+		seatBids[seatBidInd].bids = nil
 	}
 
-	return res, seatBids, nil
+	return res, seatBids, rejections, nil
+}
+
+func updateRejections(rejections []string, bidID string, reason string) []string {
+	message := fmt.Sprintf("bid rejected [bid ID: %s] reason: %s", bidID, reason)
+	return append(rejections, message)
 }
 
 func getPrimaryAdServer(adServerId int) (string, error) {
diff --git a/openrtb_ext/imp_synacormedia.go b/openrtb_ext/imp_synacormedia.go
index 1b044cea..af48c7df 100644
--- a/openrtb_ext/imp_synacormedia.go
+++ b/openrtb_ext/imp_synacormedia.go
@@ -3,4 +3,5 @@ package openrtb_ext
 // ExtImpSynacormedia defines the contract for bidrequest.imp[i].ext.synacormedia
 type ExtImpSynacormedia struct {
 	SeatId string `json:"seatId"`
+	TagId  string `json:"tagId"`
 }
diff --git a/prebid_cache_client/client.go b/prebid_cache_client/client.go
index 58e2734e..a5730ce7 100644
--- a/prebid_cache_client/client.go
+++ b/prebid_cache_client/client.go
@@ -47,14 +47,9 @@ type Cacheable struct {
 	Key        string
 }
 
-func NewClient(conf *config.Cache, extCache *config.ExternalCache, metrics pbsmetrics.MetricsEngine) Client {
+func NewClient(httpClient *http.Client, conf *config.Cache, extCache *config.ExternalCache, metrics pbsmetrics.MetricsEngine) Client {
 	return &clientImpl{
-		httpClient: &http.Client{
-			Transport: &http.Transport{
-				MaxIdleConns:    10,
-				IdleConnTimeout: 65,
-			},
-		},
+		httpClient:        httpClient,
 		putUrl:            conf.GetBaseURL() + "/cache",
 		externalCacheHost: extCache.Host,
 		externalCachePath: extCache.Path,
diff --git a/router/router.go b/router/router.go
index 19946391..449ab65a 100644
--- a/router/router.go
+++ b/router/router.go
@@ -183,7 +183,7 @@ func New(cfg *config.Configuration, rateConvertor *currencies.RateConverter) (r
 		glog.Infof("Could not read certificates file: %s \n", readCertErr.Error())
 	}
 
-	theClient := &http.Client{
+	generalHttpClient := &http.Client{
 		Transport: &http.Transport{
 			MaxIdleConns:        cfg.Client.MaxIdleConns,
 			MaxIdleConnsPerHost: cfg.Client.MaxIdleConnsPerHost,
@@ -191,13 +191,22 @@ func New(cfg *config.Configuration, rateConvertor *currencies.RateConverter) (r
 			TLSClientConfig:     &tls.Config{RootCAs: certPool},
 		},
 	}
+
+	cacheHttpClient := &http.Client{
+		Transport: &http.Transport{
+			MaxIdleConns:        cfg.CacheClient.MaxIdleConns,
+			MaxIdleConnsPerHost: cfg.CacheClient.MaxIdleConnsPerHost,
+			IdleConnTimeout:     time.Duration(cfg.CacheClient.IdleConnTimeout) * time.Second,
+		},
+	}
+
 	// Hack because of how legacy handles districtm
 	legacyBidderList := openrtb_ext.BidderList()
 	legacyBidderList = append(legacyBidderList, openrtb_ext.BidderName("districtm"))
 
 	// Metrics engine
 	r.MetricsEngine = metricsConf.NewMetricsEngine(cfg, legacyBidderList)
-	db, shutdown, fetcher, ampFetcher, categoriesFetcher, videoFetcher := storedRequestsConf.NewStoredRequests(cfg, r.MetricsEngine, theClient, r.Router)
+	db, shutdown, fetcher, ampFetcher, categoriesFetcher, videoFetcher := storedRequestsConf.NewStoredRequests(cfg, r.MetricsEngine, generalHttpClient, r.Router)
 
 	// todo(zachbadgett): better shutdown
 	r.Shutdown = shutdown
@@ -223,10 +232,11 @@ func New(cfg *config.Configuration, rateConvertor *currencies.RateConverter) (r
 	defaultAliases, defReqJSON := readDefaultRequest(cfg.DefReqConfig)
 
 	syncers := usersyncers.NewSyncerMap(cfg)
-	gdprPerms := gdpr.NewPermissions(context.Background(), cfg.GDPR, adapters.GDPRAwareSyncerIDs(syncers), theClient)
+	gdprPerms := gdpr.NewPermissions(context.Background(), cfg.GDPR, adapters.GDPRAwareSyncerIDs(syncers), generalHttpClient)
 
 	exchanges = newExchangeMap(cfg)
-	theExchange := exchange.NewExchange(theClient, pbc.NewClient(&cfg.CacheURL, &cfg.ExtCacheURL, r.MetricsEngine), cfg, r.MetricsEngine, bidderInfos, gdprPerms, rateConvertor)
+	cacheClient := pbc.NewClient(cacheHttpClient, &cfg.CacheURL, &cfg.ExtCacheURL, r.MetricsEngine)
+	theExchange := exchange.NewExchange(generalHttpClient, cacheClient, cfg, r.MetricsEngine, bidderInfos, gdprPerms, rateConvertor)
 
 	openrtbEndpoint, err := openrtb2.NewEndpoint(theExchange, paramsValidator, fetcher, categoriesFetcher, cfg, r.MetricsEngine, pbsAnalytics, disabledBidders, defReqJSON, activeBiddersMap)
 
diff --git a/static/bidder-params/synacormedia.json b/static/bidder-params/synacormedia.json
index b2dff8fa..8c74ada2 100644
--- a/static/bidder-params/synacormedia.json
+++ b/static/bidder-params/synacormedia.json
@@ -8,6 +8,10 @@
     "seatId": {
       "type": "string",
       "description": "The seat id."
+    },
+    "tagId": {
+      "type": "string",
+      "description": "The tag id."
     }
   },
 


Test patch:
diff --git a/adapters/eplanning/eplanning_test.go b/adapters/eplanning/eplanning_test.go
index d1219ce4..d2c331d4 100644
--- a/adapters/eplanning/eplanning_test.go
+++ b/adapters/eplanning/eplanning_test.go
@@ -8,7 +8,7 @@ import (
 )
 
 func TestJsonSamples(t *testing.T) {
-	eplanningAdapter := NewEPlanningBidder(new(http.Client), "http://ads.us.e-planning.net/hb/1")
+	eplanningAdapter := NewEPlanningBidder(new(http.Client), "https://ads.us.e-planning.net/hb/1")
 	eplanningAdapter.testing = true
 	adapterstest.RunJSONBidderTest(t, "eplanningtest", eplanningAdapter)
 }
diff --git a/adapters/eplanning/eplanningtest/exemplary/simple-banner-2.json b/adapters/eplanning/eplanningtest/exemplary/simple-banner-2.json
index 596b0615..f4c8fe0c 100644
--- a/adapters/eplanning/eplanningtest/exemplary/simple-banner-2.json
+++ b/adapters/eplanning/eplanningtest/exemplary/simple-banner-2.json
@@ -28,7 +28,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://ads.us.e-planning.net/hb/1/12345/1/FILE/ROS?r=pbs&ncb=1&ur=FILE&e=300x250:300x250",
+        "uri": "https://ads.us.e-planning.net/hb/1/12345/1/FILE/ROS?r=pbs&ncb=1&ur=FILE&e=300x250:300x250",
         "body": {}
       },
       "mockResponse": {
diff --git a/adapters/eplanning/eplanningtest/exemplary/simple-banner.json b/adapters/eplanning/eplanningtest/exemplary/simple-banner.json
index 21cbbdae..61b7878a 100644
--- a/adapters/eplanning/eplanningtest/exemplary/simple-banner.json
+++ b/adapters/eplanning/eplanningtest/exemplary/simple-banner.json
@@ -31,7 +31,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://ads.us.e-planning.net/hb/1/12345/1/FILE/ROS?r=pbs&ncb=1&ur=FILE&e=testadun_itco_de:600x300&uid=2154987&ip=123.123.123.123",
+        "uri": "https://ads.us.e-planning.net/hb/1/12345/1/FILE/ROS?r=pbs&ncb=1&ur=FILE&e=testadun_itco_de:600x300&uid=2154987&ip=123.123.123.123",
         "body": {}
       },
       "mockResponse": {
diff --git a/adapters/eplanning/eplanningtest/exemplary/two-banners.json b/adapters/eplanning/eplanningtest/exemplary/two-banners.json
index 15639524..fccc1a30 100644
--- a/adapters/eplanning/eplanningtest/exemplary/two-banners.json
+++ b/adapters/eplanning/eplanningtest/exemplary/two-banners.json
@@ -39,7 +39,7 @@
     "httpCalls": [
       {
         "expectedRequest": {
-          "uri": "http://ads.us.e-planning.net/hb/1/12345/1/FILE/ROS?r=pbs&ncb=1&ur=FILE&e=testadunitcode:600x300+300x250:300x250&ip=123.123.123.123",
+          "uri": "https://ads.us.e-planning.net/hb/1/12345/1/FILE/ROS?r=pbs&ncb=1&ur=FILE&e=testadunitcode:600x300+300x250:300x250&ip=123.123.123.123",
           "body": {}
         },
         "mockResponse": {
@@ -120,4 +120,4 @@
       }
     ]
   }
-  
\ No newline at end of file
+  
diff --git a/adapters/eplanning/eplanningtest/supplemental/banner-no-size-sends-1x1.json b/adapters/eplanning/eplanningtest/supplemental/banner-no-size-sends-1x1.json
index 729115e5..afa7b953 100644
--- a/adapters/eplanning/eplanningtest/supplemental/banner-no-size-sends-1x1.json
+++ b/adapters/eplanning/eplanningtest/supplemental/banner-no-size-sends-1x1.json
@@ -19,7 +19,7 @@
     "httpCalls": [
       {
         "expectedRequest": {
-          "uri": "http://ads.us.e-planning.net/hb/1/12345/1/FILE/ROS?r=pbs&ncb=1&ur=FILE&e=testadunitcodenosize:1x1",
+          "uri": "https://ads.us.e-planning.net/hb/1/12345/1/FILE/ROS?r=pbs&ncb=1&ur=FILE&e=testadunitcodenosize:1x1",
           "body": {}
         },
         "mockResponse": {
@@ -67,4 +67,4 @@
       }
     ]
   }
-  
\ No newline at end of file
+  
diff --git a/adapters/eplanning/eplanningtest/supplemental/invalid-response-no-bids.json b/adapters/eplanning/eplanningtest/supplemental/invalid-response-no-bids.json
index 57db7023..3bf45a16 100644
--- a/adapters/eplanning/eplanningtest/supplemental/invalid-response-no-bids.json
+++ b/adapters/eplanning/eplanningtest/supplemental/invalid-response-no-bids.json
@@ -21,7 +21,7 @@
     "httpCalls": [
       {
         "expectedRequest": {
-          "uri": "http://ads.us.e-planning.net/hb/1/12345/1/FILE/ROS?r=pbs&ncb=1&ur=FILE&e=testadunitcode:600x300",
+          "uri": "https://ads.us.e-planning.net/hb/1/12345/1/FILE/ROS?r=pbs&ncb=1&ur=FILE&e=testadunitcode:600x300",
           "body": {}
         },
         "mockResponse": {
@@ -45,4 +45,4 @@
       }
     ]
   }
-  
\ No newline at end of file
+  
diff --git a/adapters/eplanning/eplanningtest/supplemental/invalid-response-unmarshall-error.json b/adapters/eplanning/eplanningtest/supplemental/invalid-response-unmarshall-error.json
index fc85a6b4..e8d88f17 100644
--- a/adapters/eplanning/eplanningtest/supplemental/invalid-response-unmarshall-error.json
+++ b/adapters/eplanning/eplanningtest/supplemental/invalid-response-unmarshall-error.json
@@ -21,7 +21,7 @@
     "httpCalls": [
       {
         "expectedRequest": {
-          "uri": "http://ads.us.e-planning.net/hb/1/12345/1/FILE/ROS?r=pbs&ncb=1&ur=FILE&e=testadunitcode:600x300",
+          "uri": "https://ads.us.e-planning.net/hb/1/12345/1/FILE/ROS?r=pbs&ncb=1&ur=FILE&e=testadunitcode:600x300",
           "body": {}
         },
         "mockResponse": {
@@ -52,4 +52,4 @@
       }
     ]
   }
-  
\ No newline at end of file
+  
diff --git a/adapters/eplanning/eplanningtest/supplemental/server-bad-request.json b/adapters/eplanning/eplanningtest/supplemental/server-bad-request.json
index 052c0561..421f47ef 100644
--- a/adapters/eplanning/eplanningtest/supplemental/server-bad-request.json
+++ b/adapters/eplanning/eplanningtest/supplemental/server-bad-request.json
@@ -21,7 +21,7 @@
     "httpCalls": [
       {
         "expectedRequest": {
-          "uri": "http://ads.us.e-planning.net/hb/1/12345/1/FILE/ROS?r=pbs&ncb=1&ur=FILE&e=testadunitcode:600x300",
+          "uri": "https://ads.us.e-planning.net/hb/1/12345/1/FILE/ROS?r=pbs&ncb=1&ur=FILE&e=testadunitcode:600x300",
           "body": {}
         },
         "mockResponse": {
@@ -55,4 +55,4 @@
       }
     ]
   }
-  
\ No newline at end of file
+  
diff --git a/adapters/eplanning/eplanningtest/supplemental/server-error-code.json b/adapters/eplanning/eplanningtest/supplemental/server-error-code.json
index 699968ce..ceec970b 100644
--- a/adapters/eplanning/eplanningtest/supplemental/server-error-code.json
+++ b/adapters/eplanning/eplanningtest/supplemental/server-error-code.json
@@ -21,7 +21,7 @@
     "httpCalls": [
       {
         "expectedRequest": {
-          "uri": "http://ads.us.e-planning.net/hb/1/12345/1/FILE/ROS?r=pbs&ncb=1&ur=FILE&e=testadunitcode:600x300",
+          "uri": "https://ads.us.e-planning.net/hb/1/12345/1/FILE/ROS?r=pbs&ncb=1&ur=FILE&e=testadunitcode:600x300",
           "body": {}
         },
         "mockResponse": {
@@ -55,4 +55,4 @@
       }
     ]
   }
-  
\ No newline at end of file
+  
diff --git a/adapters/eplanning/eplanningtest/supplemental/server-no-content.json b/adapters/eplanning/eplanningtest/supplemental/server-no-content.json
index 9058699a..a2e444a9 100644
--- a/adapters/eplanning/eplanningtest/supplemental/server-no-content.json
+++ b/adapters/eplanning/eplanningtest/supplemental/server-no-content.json
@@ -21,7 +21,7 @@
     "httpCalls": [
       {
         "expectedRequest": {
-          "uri": "http://ads.us.e-planning.net/hb/1/12345/1/FILE/ROS?r=pbs&ncb=1&ur=FILE&e=testadunitcode:600x300",
+          "uri": "https://ads.us.e-planning.net/hb/1/12345/1/FILE/ROS?r=pbs&ncb=1&ur=FILE&e=testadunitcode:600x300",
           "body": {}
         },
         "mockResponse": {
@@ -30,4 +30,4 @@
       }
     ]
     }
-  
\ No newline at end of file
+  
diff --git a/adapters/eplanning/eplanningtest/supplemental/site-domain-and-url-correctly-parsed.json b/adapters/eplanning/eplanningtest/supplemental/site-domain-and-url-correctly-parsed.json
index 4ce8ef2f..d889f481 100644
--- a/adapters/eplanning/eplanningtest/supplemental/site-domain-and-url-correctly-parsed.json
+++ b/adapters/eplanning/eplanningtest/supplemental/site-domain-and-url-correctly-parsed.json
@@ -25,7 +25,7 @@
     "httpCalls": [
       {
         "expectedRequest": {
-          "uri": "http://ads.us.e-planning.net/hb/1/12345/1/www.publisher.com/ROS?r=pbs&ncb=1&ur=http%3A%2F%2Fwww.publisher.com%2Fawesome%2Fsite%3Fwith%3Dsome%26parameters%3Dhere&e=testadunitcode:600x300",
+          "uri": "https://ads.us.e-planning.net/hb/1/12345/1/www.publisher.com/ROS?r=pbs&ncb=1&ur=http%3A%2F%2Fwww.publisher.com%2Fawesome%2Fsite%3Fwith%3Dsome%26parameters%3Dhere&e=testadunitcode:600x300",
           "body": {}
         },
         "mockResponse": {
@@ -73,4 +73,4 @@
       }
     ]
   }
-  
\ No newline at end of file
+  
diff --git a/adapters/synacormedia/params_test.go b/adapters/synacormedia/params_test.go
index ffd891f4..a216818e 100644
--- a/adapters/synacormedia/params_test.go
+++ b/adapters/synacormedia/params_test.go
@@ -40,9 +40,9 @@ func TestInvalidParams(t *testing.T) {
 }
 
 var validParams = []string{
-	`{"seatId": "123"}`,
+	`{"seatId": "123", "tagId":"234"}`,
 }
 
 var invalidParams = []string{
-	`{"seatId": 123}`,
+	`{"seatId": 123, "tagId":234}`,
 }
diff --git a/adapters/synacormedia/synacormediatest/exemplary/simple-banner.json b/adapters/synacormedia/synacormediatest/exemplary/simple-banner.json
index 013891b6..944e6e54 100644
--- a/adapters/synacormedia/synacormediatest/exemplary/simple-banner.json
+++ b/adapters/synacormedia/synacormediatest/exemplary/simple-banner.json
@@ -14,7 +14,8 @@
         },
         "ext": {
           "bidder": {
-            "seatId": "1927"
+            "seatId": "prebid",
+            "tagId": "demo1"
           }
         }
       }
@@ -24,15 +25,16 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://1927.technoratimedia.com/openrtb/bids/1927",
+        "uri": "http://prebid.technoratimedia.com/openrtb/bids/prebid",
         "body": {
           "id": "test-request-id",
           "ext": {
-            "seatId": "1927"
+            "seatId": "prebid"
           },
           "imp": [
             {
               "id":"test-imp-id",
+              "tagid": "demo1",
               "banner": {
                 "format": [
                   {"w":300,"h":250}
@@ -40,7 +42,8 @@
               },
               "ext": {
                 "bidder": {
-                  "seatId": "1927"
+                  "seatId": "prebid",
+                  "tagId": "demo1"
                 }
               }
             }
diff --git a/adapters/synacormedia/synacormediatest/exemplary/simple-video.json b/adapters/synacormedia/synacormediatest/exemplary/simple-video.json
index f1255610..2cddd522 100644
--- a/adapters/synacormedia/synacormediatest/exemplary/simple-video.json
+++ b/adapters/synacormedia/synacormediatest/exemplary/simple-video.json
@@ -10,7 +10,6 @@
     "imp": [
       {
         "id": "i3",
-        "tagid": "3020",
         "video": {
           "w": 300,
           "h": 250,
@@ -21,8 +20,9 @@
         },
         "metric": [],
         "ext": {
-          "bidder":{
-            "seatId":"1927"
+          "bidder": {
+            "seatId":"prebid",
+            "tagId": "demo1"
           }
         }
       }
@@ -31,7 +31,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://1927.technoratimedia.com/openrtb/bids/1927",
+        "uri": "http://prebid.technoratimedia.com/openrtb/bids/prebid",
         "body": {
           "id": "1",
           "site": {
@@ -40,12 +40,12 @@
             "publisher": {}
           },
           "ext": {
-            "seatId": "1927"
+            "seatId": "prebid"
           },
           "imp": [
             {
               "id": "i3",
-              "tagid": "3020",
+              "tagid": "demo1",
               "video": {
                 "w": 300,
                 "h": 250,
@@ -56,7 +56,8 @@
               },
               "ext": {
                 "bidder":{
-                  "seatId":"1927"
+                  "seatId":"prebid",
+                  "tagId": "demo1"
                 }
               }
             }
diff --git a/adapters/synacormedia/synacormediatest/params/banner.json b/adapters/synacormedia/synacormediatest/params/banner.json
index d6f4e7e9..bb55ddfc 100644
--- a/adapters/synacormedia/synacormediatest/params/banner.json
+++ b/adapters/synacormedia/synacormediatest/params/banner.json
@@ -1,3 +1,4 @@
 {
-  "seatId": "123"
+  "seatId": "123",
+  "tagId": "234"
 }
diff --git a/adapters/synacormedia/synacormediatest/params/video.json b/adapters/synacormedia/synacormediatest/params/video.json
index d6f4e7e9..bb55ddfc 100644
--- a/adapters/synacormedia/synacormediatest/params/video.json
+++ b/adapters/synacormedia/synacormediatest/params/video.json
@@ -1,3 +1,4 @@
 {
-  "seatId": "123"
+  "seatId": "123",
+  "tagId": "234"
 }
diff --git a/adapters/synacormedia/synacormediatest/supplemental/audio_response.json b/adapters/synacormedia/synacormediatest/supplemental/audio_response.json
index 172a81ef..752d610a 100644
--- a/adapters/synacormedia/synacormediatest/supplemental/audio_response.json
+++ b/adapters/synacormedia/synacormediatest/supplemental/audio_response.json
@@ -9,7 +9,8 @@
         },
         "ext": {
           "bidder": {
-            "seatId": "1927"
+            "seatId": "prebid",
+            "tagId": "demo1"
           }
         }
       }
@@ -19,21 +20,23 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://1927.technoratimedia.com/openrtb/bids/1927",
+        "uri": "http://prebid.technoratimedia.com/openrtb/bids/prebid",
         "body": {
           "id": "test-request-id",
           "ext": {
-            "seatId": "1927"
+            "seatId": "prebid"
           },
           "imp": [
             {
               "id":"test-imp-id",
+              "tagid": "demo1",
               "audio": {
                 "mimes": ["video/mp4"]
               },
               "ext": {
                 "bidder": {
-                  "seatId": "1927"
+                  "seatId": "prebid",
+                  "tagId": "demo1"
                 }
               }
             }
diff --git a/adapters/synacormedia/synacormediatest/supplemental/bad_response.json b/adapters/synacormedia/synacormediatest/supplemental/bad_response.json
index 0893598b..8e8b9a4d 100644
--- a/adapters/synacormedia/synacormediatest/supplemental/bad_response.json
+++ b/adapters/synacormedia/synacormediatest/supplemental/bad_response.json
@@ -18,7 +18,8 @@
         },
         "ext": {
           "bidder": {
-            "seatId": "1927"
+            "seatId": "prebid",
+            "tagId": "demo1"
           }
         }
       }
@@ -28,15 +29,16 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://1927.technoratimedia.com/openrtb/bids/1927",
+        "uri": "http://prebid.technoratimedia.com/openrtb/bids/prebid",
         "body": {
           "id": "test-request-id",
           "ext": {
-            "seatId": "1927"
+            "seatId": "prebid"
           },
           "imp": [
             {
               "id":"test-imp-id",
+              "tagid": "demo1",
               "banner": {
                 "format": [
                   {"w":300,"h":250},
@@ -45,7 +47,8 @@
               },
               "ext": {
                 "bidder": {
-                  "seatId": "1927"
+                  "seatId": "prebid",
+                  "tagId": "demo1"
                 }
               }
             }
diff --git a/adapters/synacormedia/synacormediatest/supplemental/bad_seat_id.json b/adapters/synacormedia/synacormediatest/supplemental/bad_seat_id.json
index bb144f1c..00dd2c23 100644
--- a/adapters/synacormedia/synacormediatest/supplemental/bad_seat_id.json
+++ b/adapters/synacormedia/synacormediatest/supplemental/bad_seat_id.json
@@ -14,7 +14,8 @@
         },
         "ext": {
           "bidder": {
-            "seatId": 1927
+            "seatId": 1927,
+            "tagId": "demo1"
           }
         }
       }
diff --git a/adapters/synacormedia/synacormediatest/supplemental/missing_seat_id.json b/adapters/synacormedia/synacormediatest/supplemental/missing_seat_id.json
index a085b6e6..b85b88e4 100644
--- a/adapters/synacormedia/synacormediatest/supplemental/missing_seat_id.json
+++ b/adapters/synacormedia/synacormediatest/supplemental/missing_seat_id.json
@@ -14,6 +14,7 @@
         },
         "ext": {
           "bidder": {
+            "tagId": "demo1"
           }
         }
       }
@@ -22,7 +23,7 @@
 
   "expectedMakeRequestsErrors": [
     {
-      "value": "Impression missing seat id",
+      "value": "Invalid Impression",
       "comparison": "literal"
     }
   ]
diff --git a/adapters/synacormedia/synacormediatest/supplemental/missing_tag_id.json b/adapters/synacormedia/synacormediatest/supplemental/missing_tag_id.json
new file mode 100644
index 00000000..2e1ef6ad
--- /dev/null
+++ b/adapters/synacormedia/synacormediatest/supplemental/missing_tag_id.json
@@ -0,0 +1,30 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "seatId": "prebid"
+          }
+        }
+      }
+    ]
+  },
+
+  "expectedMakeRequestsErrors": [
+    {
+      "value": "Invalid Impression",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/synacormedia/synacormediatest/supplemental/native_response.json b/adapters/synacormedia/synacormediatest/supplemental/native_response.json
index 89742d6e..1428ac1c 100644
--- a/adapters/synacormedia/synacormediatest/supplemental/native_response.json
+++ b/adapters/synacormedia/synacormediatest/supplemental/native_response.json
@@ -9,7 +9,8 @@
         },
         "ext": {
           "bidder": {
-            "seatId": "1927"
+            "seatId": "prebid",
+            "tagId": "demo1"
           }
         }
       }
@@ -19,21 +20,23 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://1927.technoratimedia.com/openrtb/bids/1927",
+        "uri": "http://prebid.technoratimedia.com/openrtb/bids/prebid",
         "body": {
           "id": "test-request-id",
           "ext": {
-            "seatId": "1927"
+            "seatId": "prebid"
           },
           "imp": [
             {
               "id":"test-imp-id",
+              "tagid": "demo1",
               "native": {
                 "request": ""
               },
               "ext": {
                 "bidder": {
-                  "seatId": "1927"
+                  "seatId": "prebid",
+                  "tagId": "demo1"
                 }
               }
             }
diff --git a/adapters/synacormedia/synacormediatest/supplemental/one_bad_ext.json b/adapters/synacormedia/synacormediatest/supplemental/one_bad_ext.json
new file mode 100644
index 00000000..5749aadb
--- /dev/null
+++ b/adapters/synacormedia/synacormediatest/supplemental/one_bad_ext.json
@@ -0,0 +1,136 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-bad",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "seatId": "",
+            "tagId": ""
+          }
+        }
+      },
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "seatId": "prebid",
+            "tagId": "demo1"
+          }
+        }
+      }
+    ]
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "http://prebid.technoratimedia.com/openrtb/bids/prebid",
+        "body": {
+          "id": "test-request-id",
+          "ext": {
+            "seatId": "prebid"
+          },
+          "imp": [
+            {
+              "id":"test-imp-id",
+              "tagid": "demo1",
+              "banner": {
+                "format": [
+                  {"w":300,"h":250}
+                ]
+              },
+              "ext": {
+                "bidder": {
+                  "seatId": "prebid",
+                  "tagId": "demo1"
+                }
+              }
+            }
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "1",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "test-request-id",
+                  "impid": "test-imp-id",
+                  "price": 2.69,
+                  "adomain": [
+                    "psacentral.org"
+                  ],
+                  "cid": "mock-crid",
+                  "crid": "mock-cid",
+                  "ext": {
+                    "prebid": {
+                      "type": "banner"
+                    }
+                  }
+                }
+              ],
+              "seat": "synacormedia"
+            }
+          ],
+          "ext": {
+            "responsetimemillis": {
+              "synacormedia": 339
+            }
+          }
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "adomain": [
+              "psacentral.org"
+            ],
+            "cid": "mock-crid",
+            "crid": "mock-cid",
+            "ext": {
+              "prebid": {
+                "type": "banner"
+              }
+            },
+            "id": "test-request-id",
+            "impid": "test-imp-id",
+            "price": 2.69
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ],
+  "expectedMakeRequestsErrors": [
+    {
+      "value": "Invalid Impression",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/synacormedia/synacormediatest/supplemental/status_204.json b/adapters/synacormedia/synacormediatest/supplemental/status_204.json
index f53ff1ec..76f97f9c 100644
--- a/adapters/synacormedia/synacormediatest/supplemental/status_204.json
+++ b/adapters/synacormedia/synacormediatest/supplemental/status_204.json
@@ -18,7 +18,8 @@
         },
         "ext": {
           "bidder": {
-            "seatId": "1927"
+            "seatId": "prebid",
+            "tagId": "demo1"
           }
         }
       }
@@ -28,15 +29,16 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://1927.technoratimedia.com/openrtb/bids/1927",
+        "uri": "http://prebid.technoratimedia.com/openrtb/bids/prebid",
         "body": {
           "id": "test-request-id",
           "ext": {
-            "seatId": "1927"
+            "seatId": "prebid"
           },
           "imp": [
             {
               "id":"test-imp-id",
+              "tagid": "demo1",
               "banner": {
                 "format": [
                   {"w":300,"h":250},
@@ -45,7 +47,8 @@
               },
               "ext": {
                 "bidder": {
-                  "seatId": "1927"
+                  "seatId": "prebid",
+                  "tagId": "demo1"
                 }
               }
             }
diff --git a/adapters/synacormedia/synacormediatest/supplemental/status_400.json b/adapters/synacormedia/synacormediatest/supplemental/status_400.json
index a0667658..1bb2cf6f 100644
--- a/adapters/synacormedia/synacormediatest/supplemental/status_400.json
+++ b/adapters/synacormedia/synacormediatest/supplemental/status_400.json
@@ -18,7 +18,8 @@
         },
         "ext": {
           "bidder": {
-            "seatId": "1927"
+            "seatId": "prebid",
+            "tagId": "demo1"
           }
         }
       }
@@ -28,15 +29,16 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://1927.technoratimedia.com/openrtb/bids/1927",
+        "uri": "http://prebid.technoratimedia.com/openrtb/bids/prebid",
         "body": {
           "id": "test-request-id",
           "ext": {
-            "seatId": "1927"
+            "seatId": "prebid"
           },
           "imp": [
             {
               "id":"test-imp-id",
+              "tagid": "demo1",
               "banner": {
                 "format": [
                   {"w":300,"h":250},
@@ -45,7 +47,8 @@
               },
               "ext": {
                 "bidder": {
-                  "seatId": "1927"
+                  "seatId": "prebid",
+                  "tagId": "demo1"
                 }
               }
             }
diff --git a/adapters/synacormedia/synacormediatest/supplemental/status_500.json b/adapters/synacormedia/synacormediatest/supplemental/status_500.json
index 125829c2..37ca398e 100644
--- a/adapters/synacormedia/synacormediatest/supplemental/status_500.json
+++ b/adapters/synacormedia/synacormediatest/supplemental/status_500.json
@@ -18,7 +18,8 @@
         },
         "ext": {
           "bidder": {
-            "seatId": "1927"
+            "seatId": "prebid",
+            "tagId": "demo1"
           }
         }
       }
@@ -28,15 +29,16 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://1927.technoratimedia.com/openrtb/bids/1927",
+        "uri": "http://prebid.technoratimedia.com/openrtb/bids/prebid",
         "body": {
           "id": "test-request-id",
           "ext": {
-            "seatId": "1927"
+            "seatId": "prebid"
           },
           "imp": [
             {
               "id":"test-imp-id",
+              "tagid": "demo1",
               "banner": {
                 "format": [
                   {"w":300,"h":250},
@@ -45,7 +47,8 @@
               },
               "ext": {
                 "bidder": {
-                  "seatId": "1927"
+                  "seatId": "prebid",
+                  "tagId": "demo1"
                 }
               }
             }
diff --git a/config/config_test.go b/config/config_test.go
index 182a46ee..78630e07 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -68,6 +68,10 @@ http_client:
   max_idle_connections: 500
   max_idle_connections_per_host: 20
   idle_connection_timeout_seconds: 30
+http_client_cache:
+  max_idle_connections: 1
+  max_idle_connections_per_host: 2
+  idle_connection_timeout_seconds: 3
 currency_converter:
   fetch_url: https://currency.prebid.org
   fetch_interval_seconds: 1800
@@ -214,6 +218,9 @@ func TestFullConfig(t *testing.T) {
 	cmpInts(t, "http_client.max_idle_connections", cfg.Client.MaxIdleConns, 500)
 	cmpInts(t, "http_client.max_idle_connections_per_host", cfg.Client.MaxIdleConnsPerHost, 20)
 	cmpInts(t, "http_client.idle_connection_timeout_seconds", cfg.Client.IdleConnTimeout, 30)
+	cmpInts(t, "http_client_cache.max_idle_connections", cfg.CacheClient.MaxIdleConns, 1)
+	cmpInts(t, "http_client_cache.max_idle_connections_per_host", cfg.CacheClient.MaxIdleConnsPerHost, 2)
+	cmpInts(t, "http_client_cache.idle_connection_timeout_seconds", cfg.CacheClient.IdleConnTimeout, 3)
 	cmpInts(t, "gdpr.host_vendor_id", cfg.GDPR.HostVendorID, 15)
 	cmpBools(t, "gdpr.usersync_if_ambiguous", cfg.GDPR.UsersyncIfAmbiguous, true)
 
diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go
index 31dddae4..7e199d4b 100644
--- a/exchange/exchange_test.go
+++ b/exchange/exchange_test.go
@@ -9,6 +9,7 @@ import (
 	"io/ioutil"
 	"net/http"
 	"net/http/httptest"
+	"regexp"
 	"strconv"
 	"strings"
 	"testing"
@@ -170,7 +171,7 @@ func TestGetBidCacheInfo(t *testing.T) {
 	server := httptest.NewServer(http.HandlerFunc(handlerNoBidServer))
 	defer server.Close()
 
-	e := NewExchange(server.Client(), pbc.NewClient(&cfg.CacheURL, &cfg.ExtCacheURL, testEngine), cfg, pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}), adapters.ParseBidderInfos(cfg.Adapters, "../static/bidder-info", openrtb_ext.BidderList()), gdpr.AlwaysAllow{}, currencies.NewRateConverterDefault()).(*exchange)
+	e := NewExchange(server.Client(), pbc.NewClient(&http.Client{}, &cfg.CacheURL, &cfg.ExtCacheURL, testEngine), cfg, pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}), adapters.ParseBidderInfos(cfg.Adapters, "../static/bidder-info", openrtb_ext.BidderList()), gdpr.AlwaysAllow{}, currencies.NewRateConverterDefault()).(*exchange)
 
 	/* 	3) Build all the parameters e.buildBidResponse(ctx.Background(), liveA... ) needs */
 	liveAdapters := []openrtb_ext.BidderName{bidderName}
@@ -930,9 +931,11 @@ func TestCategoryMapping(t *testing.T) {
 
 	adapterBids[bidderName1] = &seatBid
 
-	bidCategory, adapterBids, err := applyCategoryMapping(nil, requestExt, adapterBids, categoriesFetcher, targData)
+	bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, requestExt, adapterBids, categoriesFetcher, targData)
 
 	assert.Equal(t, nil, err, "Category mapping error should be empty")
+	assert.Equal(t, 1, len(rejections), "There should be 1 bid rejection message")
+	assert.Equal(t, "bid rejected [bid ID: bid_id4] reason: Category mapping file for primary ad server: 'freewheel', publisher: '' not found", rejections[0], "Rejection message did not match expected")
 	assert.Equal(t, "10.00_Electronics_30s", bidCategory["bid_id1"], "Category mapping doesn't match")
 	assert.Equal(t, "20.00_Sports_50s", bidCategory["bid_id2"], "Category mapping doesn't match")
 	assert.Equal(t, "20.00_AdapterOverride_30s", bidCategory["bid_id3"], "Category mapping override from adapter didn't take")
@@ -983,9 +986,10 @@ func TestCategoryMappingNoIncludeBrandCategory(t *testing.T) {
 
 	adapterBids[bidderName1] = &seatBid
 
-	bidCategory, adapterBids, err := applyCategoryMapping(nil, requestExt, adapterBids, categoriesFetcher, targData)
+	bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, requestExt, adapterBids, categoriesFetcher, targData)
 
 	assert.Equal(t, nil, err, "Category mapping error should be empty")
+	assert.Empty(t, rejections, "There should be no bid rejection messages")
 	assert.Equal(t, "10.00_30s", bidCategory["bid_id1"], "Category mapping doesn't match")
 	assert.Equal(t, "20.00_40s", bidCategory["bid_id2"], "Category mapping doesn't match")
 	assert.Equal(t, "20.00_30s", bidCategory["bid_id3"], "Category mapping doesn't match")
@@ -1034,9 +1038,11 @@ func TestCategoryMappingTranslateCategoriesNil(t *testing.T) {
 
 	adapterBids[bidderName1] = &seatBid
 
-	bidCategory, adapterBids, err := applyCategoryMapping(nil, requestExt, adapterBids, categoriesFetcher, targData)
+	bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, requestExt, adapterBids, categoriesFetcher, targData)
 
 	assert.Equal(t, nil, err, "Category mapping error should be empty")
+	assert.Equal(t, 1, len(rejections), "There should be 1 bid rejection message")
+	assert.Equal(t, "bid rejected [bid ID: bid_id3] reason: Category mapping file for primary ad server: 'freewheel', publisher: '' not found", rejections[0], "Rejection message did not match expected")
 	assert.Equal(t, "10.00_Electronics_30s", bidCategory["bid_id1"], "Category mapping doesn't match")
 	assert.Equal(t, "20.00_Sports_50s", bidCategory["bid_id2"], "Category mapping doesn't match")
 	assert.Equal(t, 2, len(adapterBids[bidderName1].bids), "Bidders number doesn't match")
@@ -1114,9 +1120,10 @@ func TestCategoryMappingTranslateCategoriesFalse(t *testing.T) {
 
 	adapterBids[bidderName1] = &seatBid
 
-	bidCategory, adapterBids, err := applyCategoryMapping(nil, requestExt, adapterBids, categoriesFetcher, targData)
+	bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, requestExt, adapterBids, categoriesFetcher, targData)
 
 	assert.Equal(t, nil, err, "Category mapping error should be empty")
+	assert.Empty(t, rejections, "There should be no bid rejection messages")
 	assert.Equal(t, "10.00_IAB1-3_30s", bidCategory["bid_id1"], "Category should not be translated")
 	assert.Equal(t, "20.00_IAB1-4_50s", bidCategory["bid_id2"], "Category should not be translated")
 	assert.Equal(t, "20.00_IAB1-1000_30s", bidCategory["bid_id3"], "Bid should not be rejected")
@@ -1179,9 +1186,12 @@ func TestCategoryDedupe(t *testing.T) {
 
 		adapterBids[bidderName1] = &seatBid
 
-		bidCategory, adapterBids, err := applyCategoryMapping(nil, requestExt, adapterBids, categoriesFetcher, targData)
+		bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, requestExt, adapterBids, categoriesFetcher, targData)
 
 		assert.Equal(t, nil, err, "Category mapping error should be empty")
+		assert.Equal(t, 2, len(rejections), "There should be 2 bid rejection messages")
+		assert.Regexpf(t, regexp.MustCompile(`bid rejected \[bid ID: bid_id(1|3)\] reason: Bid was deduplicated`), rejections[0], "Rejection message did not match expected")
+		assert.Equal(t, "bid rejected [bid ID: bid_id4] reason: Category mapping file for primary ad server: 'freewheel', publisher: '' not found", rejections[1], "Rejection message did not match expected")
 		assert.Equal(t, 2, len(adapterBids[bidderName1].bids), "Bidders number doesn't match")
 		assert.Equal(t, 2, len(bidCategory), "Bidders category mapping doesn't match")
 
@@ -1196,6 +1206,125 @@ func TestCategoryDedupe(t *testing.T) {
 	assert.NotEqual(t, numIterations, selectedBids["bid_id3"], "Bid 3 made it through every time")
 }
 
+func TestBidRejectionErrors(t *testing.T) {
+	categoriesFetcher, error := newCategoryFetcher("./test/category-mapping")
+	if error != nil {
+		t.Errorf("Failed to create a category Fetcher: %v", error)
+	}
+
+	requestExt := newExtRequest()
+	requestExt.Prebid.Targeting.DurationRangeSec = []int{15, 30, 50}
+
+	targData := &targetData{
+		priceGranularity: requestExt.Prebid.Targeting.PriceGranularity,
+		includeWinners:   true,
+	}
+
+	invalidReqExt := newExtRequest()
+	invalidReqExt.Prebid.Targeting.DurationRangeSec = []int{15, 30, 50}
+	invalidReqExt.Prebid.Targeting.IncludeBrandCategory.PrimaryAdServer = 2
+	invalidReqExt.Prebid.Targeting.IncludeBrandCategory.Publisher = "some_publisher"
+
+	adapterBids := make(map[openrtb_ext.BidderName]*pbsOrtbSeatBid)
+	bidderName := openrtb_ext.BidderName("appnexus")
+
+	testCases := []struct {
+		description        string
+		reqExt             openrtb_ext.ExtRequest
+		bids               []*openrtb.Bid
+		duration           int
+		expectedRejections []string
+		expectedCatDur     string
+	}{
+		{
+			description: "Bid should be rejected due to not containing a category",
+			reqExt:      requestExt,
+			bids: []*openrtb.Bid{
+				{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: []string{}, W: 1, H: 1},
+			},
+			duration: 30,
+			expectedRejections: []string{
+				"bid rejected [bid ID: bid_id1] reason: Bid did not contain a category",
+			},
+		},
+		{
+			description: "Bid should be rejected due to missing category mapping file",
+			reqExt:      invalidReqExt,
+			bids: []*openrtb.Bid{
+				{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: []string{"IAB1-1"}, W: 1, H: 1},
+			},
+			duration: 30,
+			expectedRejections: []string{
+				"bid rejected [bid ID: bid_id1] reason: Category mapping file for primary ad server: 'dfp', publisher: 'some_publisher' not found",
+			},
+		},
+		{
+			description: "Bid should be rejected due to duration exceeding maximum",
+			reqExt:      requestExt,
+			bids: []*openrtb.Bid{
+				{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: []string{"IAB1-1"}, W: 1, H: 1},
+			},
+			duration: 70,
+			expectedRejections: []string{
+				"bid rejected [bid ID: bid_id1] reason: Bid duration exceeds maximum allowed",
+			},
+		},
+		{
+			description: "Bid should be rejected due to duplicate bid",
+			reqExt:      requestExt,
+			bids: []*openrtb.Bid{
+				{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: []string{"IAB1-1"}, W: 1, H: 1},
+				{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: []string{"IAB1-1"}, W: 1, H: 1},
+			},
+			duration: 30,
+			expectedRejections: []string{
+				"bid rejected [bid ID: bid_id1] reason: Bid was deduplicated",
+			},
+			expectedCatDur: "10.00_VideoGames_30s",
+		},
+	}
+
+	for _, test := range testCases {
+		innerBids := []*pbsOrtbBid{}
+		for _, bid := range test.bids {
+			currentBid := pbsOrtbBid{
+				bid, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: test.duration},
+			}
+			innerBids = append(innerBids, &currentBid)
+		}
+
+		seatBid := pbsOrtbSeatBid{innerBids, "USD", nil, nil}
+
+		adapterBids[bidderName] = &seatBid
+
+		bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, test.reqExt, adapterBids, categoriesFetcher, targData)
+
+		if len(test.expectedCatDur) > 0 {
+			// Bid deduplication case
+			assert.Equal(t, 1, len(adapterBids[bidderName].bids), "Bidders number doesn't match")
+			assert.Equal(t, 1, len(bidCategory), "Bidders category mapping doesn't match")
+			assert.Equal(t, test.expectedCatDur, bidCategory["bid_id1"], "Bid category did not contain expected hb_pb_cat_dur")
+		} else {
+			assert.Empty(t, adapterBids[bidderName].bids, "Bidders number doesn't match")
+			assert.Empty(t, bidCategory, "Bidders category mapping doesn't match")
+		}
+
+		assert.Empty(t, err, "Category mapping error should be empty")
+		assert.Equal(t, test.expectedRejections, rejections, test.description)
+	}
+}
+
+func TestUpdateRejections(t *testing.T) {
+	rejections := []string{}
+
+	rejections = updateRejections(rejections, "bid_id1", "some reason 1")
+	rejections = updateRejections(rejections, "bid_id2", "some reason 2")
+
+	assert.Equal(t, 2, len(rejections), "Rejections should contain 2 rejection messages")
+	assert.Containsf(t, rejections, "bid rejected [bid ID: bid_id1] reason: some reason 1", "Rejection message did not match expected")
+	assert.Containsf(t, rejections, "bid rejected [bid ID: bid_id2] reason: some reason 2", "Rejection message did not match expected")
+}
+
 type exchangeSpec struct {
 	IncomingRequest  exchangeRequest        `json:"incomingRequest"`
 	OutgoingRequests map[string]*bidderSpec `json:"outgoingRequests"`
diff --git a/prebid_cache_client/client_test.go b/prebid_cache_client/client_test.go
index 1b5b4e38..5840d4ea 100644
--- a/prebid_cache_client/client_test.go
+++ b/prebid_cache_client/client_test.go
@@ -233,11 +233,9 @@ func TestStripCacheHostAndPath(t *testing.T) {
 		},
 	}
 	for _, test := range testInput {
-		//start client
-		cacheClient := NewClient(&inCacheURL, &test.inExtCacheURL, &metricsConf.DummyMetricsEngine{})
+		cacheClient := NewClient(&http.Client{}, &inCacheURL, &test.inExtCacheURL, &metricsConf.DummyMetricsEngine{})
 		cHost, cPath := cacheClient.GetExtCacheData()
 
-		//assert
 		assert.Equal(t, test.expectedHost, cHost)
 		assert.Equal(t, test.expectedPath, cPath)
 	}