Repository: prebid/prebid-server
Pull request #1220 (2020-03-12 03:25:12)

Added logging to cache for video endpoint
Redo of the previous PR. Caching now occurs during VAST caching. If the request fails, a new cache call is made. Unit tests to be added.


Patch:
diff --git a/.gitignore b/.gitignore
index c2cbc1e9..60c24e79 100644
--- a/.gitignore
+++ b/.gitignore
@@ -38,6 +38,10 @@ debug
 pbs.*
 inventory_url.yaml
 
+# generated log files during tests
+analytics/config/testFiles/
+analytics/filesystem/testFiles/
+
 # autogenerated version file
 # static/version.txt
 
diff --git a/adapters/kidoz/kidoz.go b/adapters/kidoz/kidoz.go
new file mode 100644
index 00000000..2d04cdff
--- /dev/null
+++ b/adapters/kidoz/kidoz.go
@@ -0,0 +1,188 @@
+package kidoz
+
+import (
+	"encoding/json"
+	"errors"
+	"net/http"
+	"strconv"
+
+	"github.com/mxmCherry/openrtb"
+	"github.com/prebid/prebid-server/adapters"
+	"github.com/prebid/prebid-server/errortypes"
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+type KidozAdapter struct {
+	endpoint string
+}
+
+func (a *KidozAdapter) MakeRequests(request *openrtb.BidRequest, _ *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+	headers := http.Header{}
+	headers.Add("Content-Type", "application/json;charset=utf-8")
+	headers.Add("Accept", "application/json")
+	headers.Add("x-openrtb-version", "2.5")
+
+	impressions := request.Imp
+	result := make([]*adapters.RequestData, 0, len(impressions))
+	errs := make([]error, 0, len(impressions))
+
+	for i, impression := range impressions {
+		if impression.Banner == nil && impression.Video == nil {
+			errs = append(errs, &errortypes.BadInput{
+				Message: "Kidoz only supports banner or video ads",
+			})
+			continue
+		}
+
+		if impression.Banner != nil {
+			banner := impression.Banner
+			if banner.Format == nil {
+				errs = append(errs, &errortypes.BadInput{
+					Message: "banner format required",
+				})
+				continue
+			}
+			if len(banner.Format) == 0 {
+				errs = append(errs, &errortypes.BadInput{
+					Message: "banner format array is empty",
+				})
+				continue
+			}
+		}
+
+		if len(impression.Ext) == 0 {
+			errs = append(errs, errors.New("impression extensions required"))
+			continue
+		}
+		var bidderExt adapters.ExtImpBidder
+		err := json.Unmarshal(impression.Ext, &bidderExt)
+		if err != nil {
+			errs = append(errs, err)
+			continue
+		}
+		if len(bidderExt.Bidder) == 0 {
+			errs = append(errs, errors.New("bidder required"))
+			continue
+		}
+		var impressionExt openrtb_ext.ExtImpKidoz
+		err = json.Unmarshal(bidderExt.Bidder, &impressionExt)
+		if err != nil {
+			errs = append(errs, err)
+			continue
+		}
+		if impressionExt.AccessToken == "" {
+			errs = append(errs, errors.New("Kidoz access_token required"))
+			continue
+		}
+		if impressionExt.PublisherID == "" {
+			errs = append(errs, errors.New("Kidoz publisher_id required"))
+			continue
+		}
+
+		request.Imp = impressions[i : i+1]
+		body, err := json.Marshal(request)
+		if err != nil {
+			errs = append(errs, err)
+			continue
+		}
+		result = append(result, &adapters.RequestData{
+			Method:  "POST",
+			Uri:     a.endpoint,
+			Body:    body,
+			Headers: headers,
+		})
+	}
+
+	request.Imp = impressions
+
+	if len(result) == 0 {
+		return nil, errs
+	}
+	return result, errs
+}
+
+func (a *KidozAdapter) MakeBids(request *openrtb.BidRequest, _ *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+	var errs []error
+
+	switch responseData.StatusCode {
+	case http.StatusNoContent:
+		fallthrough
+	case http.StatusServiceUnavailable:
+		return nil, nil
+
+	case http.StatusBadRequest:
+		fallthrough
+	case http.StatusUnauthorized:
+		fallthrough
+	case http.StatusForbidden:
+		return nil, []error{&errortypes.BadInput{
+			Message: "unexpected status code: " + strconv.Itoa(responseData.StatusCode) + " " + string(responseData.Body),
+		}}
+
+	case http.StatusOK:
+		break
+
+	default:
+		return nil, []error{&errortypes.BadServerResponse{
+			Message: "unexpected status code: " + strconv.Itoa(responseData.StatusCode) + " " + string(responseData.Body),
+		}}
+	}
+
+	var bidResponse openrtb.BidResponse
+	err := json.Unmarshal(responseData.Body, &bidResponse)
+	if err != nil {
+		return nil, []error{&errortypes.BadServerResponse{
+			Message: err.Error(),
+		}}
+	}
+
+	response := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp))
+
+	for _, seatBid := range bidResponse.SeatBid {
+		for _, bid := range seatBid.Bid {
+			thisBid := bid
+			bidType := GetMediaTypeForImp(bid.ImpID, request.Imp)
+			if bidType == UndefinedMediaType {
+				errs = append(errs, &errortypes.BadServerResponse{
+					Message: "ignoring bid id=" + bid.ID + ", request doesn't contain any valid impression with id=" + bid.ImpID,
+				})
+				continue
+			}
+			response.Bids = append(response.Bids, &adapters.TypedBid{
+				Bid:     &thisBid,
+				BidType: bidType,
+			})
+		}
+	}
+
+	return response, errs
+}
+
+func NewKidozBidder(endpoint string) *KidozAdapter {
+	return &KidozAdapter{
+		endpoint: endpoint,
+	}
+}
+
+const UndefinedMediaType = openrtb_ext.BidType("")
+
+func GetMediaTypeForImp(impID string, imps []openrtb.Imp) openrtb_ext.BidType {
+	var bidType openrtb_ext.BidType = UndefinedMediaType
+	for _, impression := range imps {
+		if impression.ID != impID {
+			continue
+		}
+		switch {
+		case impression.Banner != nil:
+			bidType = openrtb_ext.BidTypeBanner
+		case impression.Video != nil:
+			bidType = openrtb_ext.BidTypeVideo
+		case impression.Native != nil:
+			bidType = openrtb_ext.BidTypeNative
+		case impression.Audio != nil:
+			bidType = openrtb_ext.BidTypeAudio
+		}
+		break
+	}
+	return bidType
+}
diff --git a/adapters/valueimpression/usersync.go b/adapters/valueimpression/usersync.go
new file mode 100644
index 00000000..34addbc0
--- /dev/null
+++ b/adapters/valueimpression/usersync.go
@@ -0,0 +1,12 @@
+package valueimpression
+
+import (
+	"text/template"
+
+	"github.com/prebid/prebid-server/adapters"
+	"github.com/prebid/prebid-server/usersync"
+)
+
+func NewValueImpressionSyncer(temp *template.Template) usersync.Usersyncer {
+	return adapters.NewSyncer("valueimpression", 0, temp, adapters.SyncTypeRedirect)
+}
diff --git a/adapters/valueimpression/valueimpression.go b/adapters/valueimpression/valueimpression.go
new file mode 100644
index 00000000..7e0f5f28
--- /dev/null
+++ b/adapters/valueimpression/valueimpression.go
@@ -0,0 +1,154 @@
+package valueimpression
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+
+	"github.com/mxmCherry/openrtb"
+	"github.com/prebid/prebid-server/adapters"
+	"github.com/prebid/prebid-server/errortypes"
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+type ValueImpressionAdapter struct {
+	endpoint string
+}
+
+func (a *ValueImpressionAdapter) MakeRequests(request *openrtb.BidRequest, unused *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+	var errs []error
+	var adapterRequests []*adapters.RequestData
+
+	if err := preprocess(request); err != nil {
+		errs = append(errs, err)
+		return nil, errs
+	}
+
+	adapterReq, err := a.makeRequest(request)
+	if err != nil {
+		errs = append(errs, err)
+		return nil, errs
+	}
+
+	adapterRequests = append(adapterRequests, adapterReq)
+
+	return adapterRequests, errs
+}
+
+func (a *ValueImpressionAdapter) makeRequest(request *openrtb.BidRequest) (*adapters.RequestData, error) {
+	var err error
+
+	jsonBody, err := json.Marshal(request)
+	if err != nil {
+		return nil, err
+	}
+
+	headers := http.Header{}
+	headers.Add("Content-Type", "application/json;charset=utf-8")
+
+	return &adapters.RequestData{
+		Method:  "POST",
+		Uri:     a.endpoint,
+		Body:    jsonBody,
+		Headers: headers,
+	}, nil
+}
+
+func preprocess(request *openrtb.BidRequest) error {
+	if len(request.Imp) == 0 {
+		return &errortypes.BadInput{
+			Message: "No Imps in Bid Request",
+		}
+	}
+	for i := 0; i < len(request.Imp); i++ {
+		var imp = &request.Imp[i]
+		var bidderExt adapters.ExtImpBidder
+
+		if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
+			return &errortypes.BadInput{
+				Message: err.Error(),
+			}
+		}
+
+		var extImp openrtb_ext.ExtImpValueImpression
+		if err := json.Unmarshal(bidderExt.Bidder, &extImp); err != nil {
+			return &errortypes.BadInput{
+				Message: err.Error(),
+			}
+		}
+
+		imp.Ext = bidderExt.Bidder
+	}
+
+	return nil
+}
+
+// MakeBids based on valueimpression server response
+func (a *ValueImpressionAdapter) MakeBids(bidRequest *openrtb.BidRequest, unused *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+	if responseData.StatusCode == http.StatusNoContent {
+		return nil, nil
+	}
+
+	if responseData.StatusCode == http.StatusBadRequest {
+		return nil, []error{&errortypes.BadInput{
+			Message: fmt.Sprintf("Bad user input: HTTP status %d", responseData.StatusCode),
+		}}
+	}
+
+	if responseData.StatusCode != http.StatusOK {
+		return nil, []error{&errortypes.BadServerResponse{
+			Message: fmt.Sprintf("Bad server response: HTTP status %d", responseData.StatusCode),
+		}}
+	}
+
+	var bidResponse openrtb.BidResponse
+
+	if err := json.Unmarshal(responseData.Body, &bidResponse); err != nil {
+		return nil, []error{&errortypes.BadServerResponse{
+			Message: err.Error(),
+		}}
+	}
+
+	if len(bidResponse.SeatBid) == 0 {
+		return nil, nil
+	}
+
+	rv := adapters.NewBidderResponseWithBidsCapacity(len(bidResponse.SeatBid[0].Bid))
+	var errors []error
+
+	for _, seatbid := range bidResponse.SeatBid {
+		for _, bid := range seatbid.Bid {
+			foundMatchingBid := false
+			bidType := openrtb_ext.BidTypeBanner
+			for _, imp := range bidRequest.Imp {
+				if imp.ID == bid.ImpID {
+					foundMatchingBid = true
+					if imp.Banner != nil {
+						bidType = openrtb_ext.BidTypeBanner
+					} else if imp.Video != nil {
+						bidType = openrtb_ext.BidTypeVideo
+					}
+					break
+				}
+			}
+
+			if foundMatchingBid {
+				rv.Bids = append(rv.Bids, &adapters.TypedBid{
+					Bid:     &bid,
+					BidType: bidType,
+				})
+			} else {
+				errors = append(errors, &errortypes.BadServerResponse{
+					Message: fmt.Sprintf("bid id='%s' could not find valid impid='%s'", bid.ID, bid.ImpID),
+				})
+			}
+		}
+	}
+	return rv, errors
+}
+
+func NewValueImpressionBidder(endpoint string) *ValueImpressionAdapter {
+	return &ValueImpressionAdapter{
+		endpoint: endpoint,
+	}
+}
diff --git a/analytics/config/testFiles/test-20200303 b/analytics/config/testFiles/test-20200303
new file mode 100644
index 00000000..e69de29b
diff --git a/config/config.go b/config/config.go
index e3b6c67b..d4edab2b 100644
--- a/config/config.go
+++ b/config/config.go
@@ -538,6 +538,7 @@ func (cfg *Configuration) setDerivedDefaults() {
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderTripleliftNative, "https://eb2.3lift.com/getuid?gdpr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dtriplelift_native%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID")
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderUcfunnel, "https://sync.aralego.com/idsync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&usprivacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Ducfunnel%26uid%3DSspCookieUserId")
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderUnruly, "https://usermatch.targeting.unrulymedia.com/pbsync?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&rurl="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dunruly%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID")
+	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderValueImpression, "https://rtb.valueimpression.com/usersync?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dvalueimpression%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID")
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderVisx, "https://t.visx.net/s2s_sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dvisx%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUUID%7D")
 	// openrtb_ext.BidderVrtcal doesn't have a good default.
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderYieldmo, "https://ads.yieldmo.com/pbsync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirectUri="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dyieldmo%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID")
@@ -706,6 +707,7 @@ func SetupViper(v *viper.Viper, filename string) {
 	v.SetDefault("adapters.gumgum.endpoint", "https://g2.gumgum.com/providers/prbds2s/bid")
 	v.SetDefault("adapters.improvedigital.endpoint", "http://ad.360yield.com/pbs")
 	v.SetDefault("adapters.ix.endpoint", "http://appnexus-us-east.lb.indexww.com/transbidder?p=184932")
+	v.SetDefault("adapters.kidoz.endpoint", "http://prebid-adapter.kidoz.net/openrtb2/auction?src=prebid-server")
 	v.SetDefault("adapters.kubient.endpoint", "http://kbntx.ch/prebid")
 	v.SetDefault("adapters.lifestreet.endpoint", "https://prebid.s2s.lfstmedia.com/adrequest")
 	v.SetDefault("adapters.lockerdome.endpoint", "https://lockerdome.com/ladbid/prebidserver/openrtb2")
@@ -730,6 +732,7 @@ func SetupViper(v *viper.Viper, filename string) {
 	v.SetDefault("adapters.triplelift.endpoint", "https://tlx.3lift.com/s2s/auction?supplier_id=20")
 	v.SetDefault("adapters.ucfunnel.endpoint", "http://apac-hk-adx.aralego.com/prebid")
 	v.SetDefault("adapters.unruly.endpoint", "http://targeting.unrulymedia.com/openrtb/2.2")
+	v.SetDefault("adapters.valueimpression.endpoint", "https://rtb.valueimpression.com/endpoint")
 	v.SetDefault("adapters.verizonmedia.disabled", true)
 	v.SetDefault("adapters.visx.endpoint", "https://t.visx.net/s2s_bid?wrapperType=s2s_prebid_standard")
 	v.SetDefault("adapters.vrtcal.endpoint", "http://rtb.vrtcal.com/bidder_prebid.vap?ssp=1804")
diff --git a/docs/endpoints/openrtb2/auction.md b/docs/endpoints/openrtb2/auction.md
index 9ae6ec78..7795ef5a 100644
--- a/docs/endpoints/openrtb2/auction.md
+++ b/docs/endpoints/openrtb2/auction.md
@@ -174,7 +174,7 @@ will be truncated to only include the first 20 characters.
 #### Cookie syncs
 
 Each Bidder should receive their own ID in the `request.user.buyeruid` property.
-Prebid Server has three ways to popualte this field. In order of priority:
+Prebid Server has three ways to populate this field. In order of priority:
 
 1. If the request payload contains `request.user.buyeruid`, then that value will be sent to all Bidders.
 In most cases, this is probably a bad idea.
@@ -336,7 +336,7 @@ Bids can be temporarily cached on the server by sending the following data as `r
 }
 ```
 
-Both `bids` and `vastxml` are optional, but one of the two is required. Thils property will have no effect
+Both `bids` and `vastxml` are optional, but one of the two is required. This property will have no effect
 unless `request.ext.prebid.targeting` is also set in the request.
 
 If `bids` is present, Prebid Server will make a _best effort_ to include these extra
@@ -403,6 +403,29 @@ Example:
 PBS receiving a request for an interstitial imp and these parameters set, it will rewrite the format object within the interstitial imp. If the format array's first object is a size, PBS will take it as the max size for the interstitial. If that size is 1x1, it will look up the device's size and use that as the max size. If the format is not present, it will also use the device size as the max size. (1x1 support so that you don't have to omit the format object to use the device size)
 PBS with interstitial support will come preconfigured with a list of common ad sizes. Preferentially organized by weighing the larger and more common sizes first. But no guarantees to the ordering will be made. PBS will generate a new format list for the interstitial imp by traversing this list and picking the first 10 sizes that fall within the imp's max size and minimum percentage size. There will be no attempt to favor aspect ratios closer to the original size's aspect ratio. The limit of 10 is enforced to ensure we don't overload bidders with an overlong list. All the interstitial parameters will still be passed to the bidders, so they may recognize them and use their own size matching algorithms if they prefer.
 
+#### Currency Support
+
+To set the desired 'ad server currency', use the standard OpenRTB `cur` attribute. Note that Prebid Server only looks at the first currency in the array.
+```
+"cur": ["USD"]
+```
+
+If you want or need to define currency conversion rates (e.g. for currencies that your Prebid Server doesn't support), define ext.prebid.currency.rates. (Currently supported in PBS-Java only)
+
+```
+"ext": {
+  "prebid": {
+	  "currency": {
+		  "rates": {
+			  "USD": { "UAH": 24.47, "ETB": 32.04 }
+		  }
+	  }
+  }
+}
+```
+
+If it exists, a rate defined in ext.prebid.currency.rates has the highest priority. If a currency rate doesn't exist in the request, the external file will be used.
+
 #### Stored Responses (PBS-Java only)
 
 While testing SDK and video integrations, it's important, but often difficult, to get consistent responses back from bidders that cover a range of scenarios like different CPM values, deals, etc. Prebid Server supports a debugging workflow in two ways:
diff --git a/endpoints/openrtb2/amp_auction.go b/endpoints/openrtb2/amp_auction.go
index d92f9d0a..8edc1e13 100644
--- a/endpoints/openrtb2/amp_auction.go
+++ b/endpoints/openrtb2/amp_auction.go
@@ -71,7 +71,8 @@ func NewAmpEndpoint(
 		disabledBidders,
 		defRequest,
 		defReqJSON,
-		bidderMap}).AmpAuction), nil
+		bidderMap,
+		nil}).AmpAuction), nil
 
 }
 
@@ -165,7 +166,7 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h
 		return
 	}
 
-	response, err := deps.ex.HoldAuction(ctx, req, usersyncs, labels, &deps.categories)
+	response, err := deps.ex.HoldAuction(ctx, req, usersyncs, labels, &deps.categories, nil)
 	ao.AuctionResponse = response
 
 	if err != nil {
diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go
index a0ed19e5..d9c31eca 100644
--- a/endpoints/openrtb2/auction.go
+++ b/endpoints/openrtb2/auction.go
@@ -27,6 +27,7 @@ import (
 	"github.com/prebid/prebid-server/openrtb_ext"
 	"github.com/prebid/prebid-server/pbsmetrics"
 	"github.com/prebid/prebid-server/prebid"
+	"github.com/prebid/prebid-server/prebid_cache_client"
 	"github.com/prebid/prebid-server/privacy/ccpa"
 	"github.com/prebid/prebid-server/stored_requests"
 	"github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher"
@@ -55,7 +56,8 @@ func NewEndpoint(ex exchange.Exchange, validator openrtb_ext.BidderParamValidato
 		disabledBidders,
 		defRequest,
 		defReqJSON,
-		bidderMap}).Auction), nil
+		bidderMap,
+		nil}).Auction), nil
 }
 
 type endpointDeps struct {
@@ -71,6 +73,7 @@ type endpointDeps struct {
 	defaultRequest   bool
 	defReqJSON       []byte
 	bidderMap        map[string]openrtb_ext.BidderName
+	cache            prebid_cache_client.Client
 }
 
 func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
@@ -137,7 +140,7 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http
 		return
 	}
 
-	response, err := deps.ex.HoldAuction(ctx, req, usersyncs, labels, &deps.categories)
+	response, err := deps.ex.HoldAuction(ctx, req, usersyncs, labels, &deps.categories, nil)
 	ao.Request = req
 	ao.Response = response
 	if err != nil {
diff --git a/endpoints/openrtb2/video_auction.go b/endpoints/openrtb2/video_auction.go
index 2a866395..630a3f5a 100644
--- a/endpoints/openrtb2/video_auction.go
+++ b/endpoints/openrtb2/video_auction.go
@@ -14,6 +14,7 @@ import (
 
 	"github.com/buger/jsonparser"
 	jsonpatch "github.com/evanphx/json-patch"
+	"github.com/gofrs/uuid"
 	"github.com/prebid/prebid-server/errortypes"
 
 	"github.com/golang/glog"
@@ -24,20 +25,21 @@ import (
 	"github.com/prebid/prebid-server/exchange"
 	"github.com/prebid/prebid-server/openrtb_ext"
 	"github.com/prebid/prebid-server/pbsmetrics"
+	"github.com/prebid/prebid-server/prebid_cache_client"
 	"github.com/prebid/prebid-server/stored_requests"
 	"github.com/prebid/prebid-server/usersync"
 )
 
 var defaultRequestTimeout int64 = 5000
 
-func NewVideoEndpoint(ex exchange.Exchange, validator openrtb_ext.BidderParamValidator, requestsById stored_requests.Fetcher, videoFetcher stored_requests.Fetcher, categories stored_requests.CategoryFetcher, cfg *config.Configuration, met pbsmetrics.MetricsEngine, pbsAnalytics analytics.PBSAnalyticsModule, disabledBidders map[string]string, defReqJSON []byte, bidderMap map[string]openrtb_ext.BidderName) (httprouter.Handle, error) {
+func NewVideoEndpoint(ex exchange.Exchange, validator openrtb_ext.BidderParamValidator, requestsById stored_requests.Fetcher, videoFetcher stored_requests.Fetcher, categories stored_requests.CategoryFetcher, cfg *config.Configuration, met pbsmetrics.MetricsEngine, pbsAnalytics analytics.PBSAnalyticsModule, disabledBidders map[string]string, defReqJSON []byte, bidderMap map[string]openrtb_ext.BidderName, cache prebid_cache_client.Client) (httprouter.Handle, error) {
 
 	if ex == nil || validator == nil || requestsById == nil || cfg == nil || met == nil {
 		return nil, errors.New("NewVideoEndpoint requires non-nil arguments.")
 	}
 	defRequest := defReqJSON != nil && len(defReqJSON) > 0
 
-	return httprouter.Handle((&endpointDeps{ex, validator, requestsById, videoFetcher, categories, cfg, met, pbsAnalytics, disabledBidders, defRequest, defReqJSON, bidderMap}).VideoAuctionEndpoint), nil
+	return httprouter.Handle((&endpointDeps{ex, validator, requestsById, videoFetcher, categories, cfg, met, pbsAnalytics, disabledBidders, defRequest, defReqJSON, bidderMap, cache}).VideoAuctionEndpoint), nil
 }
 
 /*
@@ -79,7 +81,38 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re
 		CookieFlag:    pbsmetrics.CookieFlagUnknown,
 		RequestStatus: pbsmetrics.RequestStatusOK,
 	}
+
+	debugQuery := r.URL.Query().Get("debug")
+	cacheTTL := int64(3600)
+	if deps.cfg.CacheURL.DefaultTTLs.Video > 0 {
+		cacheTTL = int64(deps.cfg.CacheURL.DefaultTTLs.Video)
+	}
+	debugLog := exchange.DebugLog{
+		EnableDebug: strings.EqualFold(debugQuery, "true"),
+		CacheType:   prebid_cache_client.TypeXML,
+		TTL:         cacheTTL,
+	}
+
 	defer func() {
+		if len(debugLog.CacheKey) > 0 && vo.VideoResponse == nil {
+			debugLog.Data = fmt.Sprintf("<!--\n%s\n\nNo response created\n-->", debugLog.Data)
+			data, err := json.Marshal(debugLog.Data)
+			if err == nil {
+				toCache := []prebid_cache_client.Cacheable{
+					{
+						Type:       debugLog.CacheType,
+						Data:       data,
+						TTLSeconds: debugLog.TTL,
+						Key:        "log_" + debugLog.CacheKey,
+					},
+				}
+				if deps.cache != nil {
+					ctx, cancel := context.WithDeadline(context.Background(), start.Add(time.Duration(100)*time.Millisecond))
+					defer cancel()
+					deps.cache.PutJson(ctx, toCache)
+				}
+			}
+		}
 		deps.metricsEngine.RecordRequest(labels)
 		deps.metricsEngine.RecordRequestTime(labels, time.Since(start))
 		deps.analytics.LogVideoObject(&vo)
@@ -91,38 +124,46 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re
 	}
 	requestJson, err := ioutil.ReadAll(lr)
 	if err != nil {
-		handleError(&labels, w, []error{err}, &vo)
+		handleError(&labels, w, []error{err}, &vo, &debugLog)
 		return
 	}
 
 	resolvedRequest := requestJson
+	if debugLog.EnableDebug {
+		debugLog.Data = fmt.Sprintf("Request:\n%s", string(requestJson))
+		if headerBytes, err := json.Marshal(r.Header); err == nil {
+			debugLog.Data = fmt.Sprintf("%s\n\nHeaders:\n%s", debugLog.Data, string(headerBytes))
+		} else {
+			debugLog.Data = fmt.Sprintf("%s\n\nUnable to marshal headers data\n", debugLog.Data)
+		}
+	}
 
 	//load additional data - stored simplified req
 	storedRequestId, err := getVideoStoredRequestId(requestJson)
 
 	if err != nil {
 		if deps.cfg.VideoStoredRequestRequired {
-			handleError(&labels, w, []error{err}, &vo)
+			handleError(&labels, w, []error{err}, &vo, &debugLog)
 			return
 		}
 	} else {
 		storedRequest, errs := deps.loadStoredVideoRequest(context.Background(), storedRequestId)
 		if len(errs) > 0 {
-			handleError(&labels, w, errs, &vo)
+			handleError(&labels, w, errs, &vo, &debugLog)
 			return
 		}
 
 		//merge incoming req with stored video req
 		resolvedRequest, err = jsonpatch.MergePatch(storedRequest, requestJson)
 		if err != nil {
-			handleError(&labels, w, []error{err}, &vo)
+			handleError(&labels, w, []error{err}, &vo, &debugLog)
 			return
 		}
 	}
 	//unmarshal and validate combined result
 	videoBidReq, errL, podErrors := deps.parseVideoRequest(resolvedRequest, r.Header)
 	if len(errL) > 0 {
-		handleError(&labels, w, errL, &vo)
+		handleError(&labels, w, errL, &vo, &debugLog)
 		return
 	}
 
@@ -132,13 +173,17 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re
 	if deps.defaultRequest {
 		if err := json.Unmarshal(deps.defReqJSON, bidReq); err != nil {
 			err = fmt.Errorf("Invalid JSON in Default Request Settings: %s", err)
-			handleError(&labels, w, []error{err}, &vo)
+			handleError(&labels, w, []error{err}, &vo, &debugLog)
 			return
 		}
 	}
 
 	//create full open rtb req from full video request
 	mergeData(videoBidReq, bidReq)
+	// If debug query param is set, force the response to enable test flag
+	if debugLog.EnableDebug {
+		bidReq.Test = 1
+	}
 
 	initialPodNumber := len(videoBidReq.PodConfig.Pods)
 	if len(podErrors) > 0 {
@@ -156,7 +201,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re
 		}
 		err := errors.New(fmt.Sprintf("all pods are incorrect: %s", strings.Join(resPodErr, "; ")))
 		errL = append(errL, err)
-		handleError(&labels, w, errL, &vo)
+		handleError(&labels, w, errL, &vo, &debugLog)
 		return
 	}
 
@@ -168,7 +213,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re
 
 	errL = deps.validateRequest(bidReq)
 	if len(errL) > 0 {
-		handleError(&labels, w, errL, &vo)
+		handleError(&labels, w, errL, &vo, &debugLog)
 		return
 	}
 
@@ -196,16 +241,16 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re
 
 	if acctIdErr := validateAccount(deps.cfg, labels.PubID); acctIdErr != nil {
 		errL = append(errL, acctIdErr)
-		handleError(&labels, w, errL, &vo)
+		handleError(&labels, w, errL, &vo, &debugLog)
 		return
 	}
 	//execute auction logic
-	response, err := deps.ex.HoldAuction(ctx, bidReq, usersyncs, labels, &deps.categories)
+	response, err := deps.ex.HoldAuction(ctx, bidReq, usersyncs, labels, &deps.categories, &debugLog)
 	vo.Request = bidReq
 	vo.Response = response
 	if err != nil {
 		errL := []error{err}
-		handleError(&labels, w, errL, &vo)
+		handleError(&labels, w, errL, &vo, &debugLog)
 		return
 	}
 
@@ -213,7 +258,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re
 	bidResp, err := buildVideoResponse(response, podErrors)
 	if err != nil {
 		errL := []error{err}
-		handleError(&labels, w, errL, &vo)
+		handleError(&labels, w, errL, &vo, &debugLog)
 		return
 	}
 	if bidReq.Test == 1 {
@@ -226,7 +271,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re
 	//resp, err := json.Marshal(response)
 	if err != nil {
 		errL := []error{err}
-		handleError(&labels, w, errL, &vo)
+		handleError(&labels, w, errL, &vo, &debugLog)
 		return
 	}
 
@@ -242,7 +287,13 @@ func cleanupVideoBidRequest(videoReq *openrtb_ext.BidRequestVideo, podErrors []P
 	return videoReq
 }
 
-func handleError(labels *pbsmetrics.Labels, w http.ResponseWriter, errL []error, vo *analytics.VideoObject) {
+func handleError(labels *pbsmetrics.Labels, w http.ResponseWriter, errL []error, vo *analytics.VideoObject, debugLog *exchange.DebugLog) {
+	if debugLog != nil && debugLog.EnableDebug {
+		if rawUUID, err := uuid.NewV4(); err == nil {
+			debugLog.CacheKey = rawUUID.String()
+		}
+		errL = append(errL, fmt.Errorf("[Debug cache ID: %s]", debugLog.CacheKey))
+	}
 	labels.RequestStatus = pbsmetrics.RequestStatusErr
 	var errors string
 	var status int = http.StatusInternalServerError
diff --git a/exchange/adapter_map.go b/exchange/adapter_map.go
index 0354f258..05f44e24 100644
--- a/exchange/adapter_map.go
+++ b/exchange/adapter_map.go
@@ -5,8 +5,6 @@ import (
 	"net/http"
 	"strings"
 
-	"github.com/prebid/prebid-server/adapters/kubient"
-
 	"github.com/prebid/prebid-server/adapters"
 	ttx "github.com/prebid/prebid-server/adapters/33across"
 	"github.com/prebid/prebid-server/adapters/adform"
@@ -35,6 +33,8 @@ import (
 	"github.com/prebid/prebid-server/adapters/gumgum"
 	"github.com/prebid/prebid-server/adapters/improvedigital"
 	"github.com/prebid/prebid-server/adapters/ix"
+	"github.com/prebid/prebid-server/adapters/kidoz"
+	"github.com/prebid/prebid-server/adapters/kubient"
 	"github.com/prebid/prebid-server/adapters/lifestreet"
 	"github.com/prebid/prebid-server/adapters/lockerdome"
 	"github.com/prebid/prebid-server/adapters/marsmedia"
@@ -57,6 +57,7 @@ import (
 	"github.com/prebid/prebid-server/adapters/triplelift_native"
 	"github.com/prebid/prebid-server/adapters/ucfunnel"
 	"github.com/prebid/prebid-server/adapters/unruly"
+	"github.com/prebid/prebid-server/adapters/valueimpression"
 	"github.com/prebid/prebid-server/adapters/verizonmedia"
 	"github.com/prebid/prebid-server/adapters/visx"
 	"github.com/prebid/prebid-server/adapters/vrtcal"
@@ -100,6 +101,7 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter
 		openrtb_ext.BidderGrid:           grid.NewGridBidder(cfg.Adapters[string(openrtb_ext.BidderGrid)].Endpoint),
 		openrtb_ext.BidderGumGum:         gumgum.NewGumGumBidder(cfg.Adapters[string(openrtb_ext.BidderGumGum)].Endpoint),
 		openrtb_ext.BidderImprovedigital: improvedigital.NewImprovedigitalBidder(cfg.Adapters[string(openrtb_ext.BidderImprovedigital)].Endpoint),
+		openrtb_ext.BidderKidoz:          kidoz.NewKidozBidder(cfg.Adapters[string(openrtb_ext.BidderKidoz)].Endpoint),
 		openrtb_ext.BidderKubient:        kubient.NewKubientBidder(cfg.Adapters[string(openrtb_ext.BidderKubient)].Endpoint),
 		openrtb_ext.BidderLockerDome:     lockerdome.NewLockerDomeBidder(cfg.Adapters[string(openrtb_ext.BidderLockerDome)].Endpoint),
 		openrtb_ext.BidderMarsmedia:      marsmedia.NewMarsmediaBidder(cfg.Adapters[string(openrtb_ext.BidderMarsmedia)].Endpoint),
@@ -127,6 +129,7 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter
 		openrtb_ext.BidderTripleliftNative: triplelift_native.NewTripleliftNativeBidder(client, cfg.Adapters[string(openrtb_ext.BidderTripleliftNative)].Endpoint, cfg.Adapters[string(openrtb_ext.BidderTripleliftNative)].ExtraAdapterInfo),
 		openrtb_ext.BidderUcfunnel:         ucfunnel.NewUcfunnelBidder(cfg.Adapters[string(openrtb_ext.BidderUcfunnel)].Endpoint),
 		openrtb_ext.BidderUnruly:           unruly.NewUnrulyBidder(client, cfg.Adapters[string(openrtb_ext.BidderUnruly)].Endpoint),
+		openrtb_ext.BidderValueImpression:  valueimpression.NewValueImpressionBidder(cfg.Adapters[string(openrtb_ext.BidderValueImpression)].Endpoint),
 		openrtb_ext.BidderVerizonMedia:     verizonmedia.NewVerizonMediaBidder(client, cfg.Adapters[string(openrtb_ext.BidderVerizonMedia)].Endpoint),
 		openrtb_ext.BidderVisx:             visx.NewVisxBidder(cfg.Adapters[string(openrtb_ext.BidderVisx)].Endpoint),
 		openrtb_ext.BidderVrtcal:           vrtcal.NewVrtcalBidder(cfg.Adapters[string(openrtb_ext.BidderVrtcal)].Endpoint),
diff --git a/exchange/auction.go b/exchange/auction.go
index 2b9a8cb5..9909b78d 100644
--- a/exchange/auction.go
+++ b/exchange/auction.go
@@ -60,7 +60,7 @@ func (a *auction) setRoundedPrices(priceGranularity openrtb_ext.PriceGranularity
 	a.roundedPrices = roundedPrices
 }
 
-func (a *auction) doCache(ctx context.Context, cache prebid_cache_client.Client, targData *targetData, bidRequest *openrtb.BidRequest, ttlBuffer int64, defaultTTLs *config.DefaultTTLs, bidCategory map[string]string) []error {
+func (a *auction) doCache(ctx context.Context, cache prebid_cache_client.Client, targData *targetData, bidRequest *openrtb.BidRequest, ttlBuffer int64, defaultTTLs *config.DefaultTTLs, bidCategory map[string]string, debugLog *DebugLog) []error {
 	var bids, vast, includeBidderKeys, includeWinners bool = targData.includeCacheBids, targData.includeCacheVast, targData.includeBidderKeys, targData.includeWinners
 	if !((bids || vast) && (includeBidderKeys || includeWinners)) {
 		return nil
@@ -147,6 +147,18 @@ func (a *auction) doCache(ctx context.Context, cache prebid_cache_client.Client,
 		}
 	}
 
+	if debugLog != nil && debugLog.EnableDebug {
+		debugLog.CacheKey = hbCacheID
+		if jsonBytes, err := json.Marshal(debugLog.Data); err == nil {
+			toCache = append(toCache, prebid_cache_client.Cacheable{
+				Type:       debugLog.CacheType,
+				Data:       jsonBytes,
+				TTLSeconds: debugLog.TTL,
+				Key:        "log_" + debugLog.CacheKey,
+			})
+		}
+	}
+
 	ids, err := cache.PutJson(ctx, toCache)
 	if err != nil {
 		errs = append(errs, err...)
diff --git a/exchange/exchange.go b/exchange/exchange.go
index ef10180a..995add3d 100644
--- a/exchange/exchange.go
+++ b/exchange/exchange.go
@@ -27,10 +27,18 @@ import (
 	"github.com/prebid/prebid-server/prebid_cache_client"
 )
 
+type DebugLog struct {
+	EnableDebug bool
+	CacheType   prebid_cache_client.PayloadType
+	Data        string
+	TTL         int64
+	CacheKey    string
+}
+
 // Exchange runs Auctions. Implementations must be threadsafe, and will be shared across many goroutines.
 type Exchange interface {
 	// HoldAuction executes an OpenRTB v2.5 Auction.
-	HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, usersyncs IdFetcher, labels pbsmetrics.Labels, categoriesFetcher *stored_requests.CategoryFetcher) (*openrtb.BidResponse, error)
+	HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, usersyncs IdFetcher, labels pbsmetrics.Labels, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *DebugLog) (*openrtb.BidResponse, error)
 }
 
 // IdFetcher can find the user's ID for a specific Bidder.
@@ -78,7 +86,7 @@ func NewExchange(client *http.Client, cache prebid_cache_client.Client, cfg *con
 	return e
 }
 
-func (e *exchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, usersyncs IdFetcher, labels pbsmetrics.Labels, categoriesFetcher *stored_requests.CategoryFetcher) (*openrtb.BidResponse, error) {
+func (e *exchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, usersyncs IdFetcher, labels pbsmetrics.Labels, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *DebugLog) (*openrtb.BidResponse, error) {
 	// Snapshot of resolved bid request for debug if test request
 	resolvedRequest, err := buildResolvedRequest(bidRequest)
 	if err != nil {
@@ -142,6 +150,7 @@ func (e *exchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidReque
 	adapterBids, adapterExtra, anyBidsReturned := e.getAllBids(auctionCtx, cleanRequests, aliases, bidAdjustmentFactors, blabels, conversions)
 
 	var auc *auction = nil
+	var bidResponseExt *openrtb_ext.ExtBidResponse = nil
 	if anyBidsReturned {
 
 		var bidCategory map[string]string
@@ -162,7 +171,15 @@ func (e *exchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidReque
 
 		if targData != nil {
 			auc.setRoundedPrices(targData.priceGranularity)
-			cacheErrs := auc.doCache(ctx, e.cache, targData, bidRequest, 60, &e.defaultTTLs, bidCategory)
+			if debugLog != nil && debugLog.EnableDebug {
+				bidResponseExt = e.makeExtBidResponse(adapterBids, adapterExtra, bidRequest, resolvedRequest, errs)
+				if bidRespExtBytes, err := json.Marshal(bidResponseExt); err == nil {
+					debugLog.Data = fmt.Sprintf("<!--\n%s\n\nResponse:\n%s\n-->", debugLog.Data, string(bidRespExtBytes))
+				} else {
+					errs = append(errs, errors.New("Unable to marshal response ext for debugging"))
+				}
+			}
+			cacheErrs := auc.doCache(ctx, e.cache, targData, bidRequest, 60, &e.defaultTTLs, bidCategory, debugLog)
 			if len(cacheErrs) > 0 {
 				errs = append(errs, cacheErrs...)
 			}
@@ -176,7 +193,7 @@ func (e *exchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidReque
 	}
 
 	// Build the response
-	return e.buildBidResponse(ctx, liveAdapters, adapterBids, bidRequest, resolvedRequest, adapterExtra, auc, errs)
+	return e.buildBidResponse(ctx, liveAdapters, adapterBids, bidRequest, resolvedRequest, adapterExtra, auc, bidResponseExt, errs)
 }
 
 type DealTierInfo struct {
@@ -394,7 +411,7 @@ func errsToBidderErrors(errs []error) []openrtb_ext.ExtBidderError {
 }
 
 // This piece takes all the bids supplied by the adapters and crafts an openRTB response to send back to the requester
-func (e *exchange) buildBidResponse(ctx context.Context, liveAdapters []openrtb_ext.BidderName, adapterBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, bidRequest *openrtb.BidRequest, resolvedRequest json.RawMessage, adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra, auc *auction, errList []error) (*openrtb.BidResponse, error) {
+func (e *exchange) buildBidResponse(ctx context.Context, liveAdapters []openrtb_ext.BidderName, adapterBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, bidRequest *openrtb.BidRequest, resolvedRequest json.RawMessage, adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra, auc *auction, bidResponseExt *openrtb_ext.ExtBidResponse, errList []error) (*openrtb.BidResponse, error) {
 	bidResponse := new(openrtb.BidResponse)
 
 	bidResponse.ID = bidRequest.ID
@@ -417,7 +434,9 @@ func (e *exchange) buildBidResponse(ctx context.Context, liveAdapters []openrtb_
 
 	bidResponse.SeatBid = seatBids
 
-	bidResponseExt := e.makeExtBidResponse(adapterBids, adapterExtra, bidRequest, resolvedRequest, errList)
+	if bidResponseExt == nil {
+		bidResponseExt = e.makeExtBidResponse(adapterBids, adapterExtra, bidRequest, resolvedRequest, errList)
+	}
 	buffer := &bytes.Buffer{}
 	enc := json.NewEncoder(buffer)
 	enc.SetEscapeHTML(false)
diff --git a/go.mod b/go.mod
index af4bf557..ea1f65ef 100644
--- a/go.mod
+++ b/go.mod
@@ -61,6 +61,7 @@ require (
 	github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
 	github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
 	github.com/xeipuuv/gojsonschema v0.0.0-20180816142147-da425ebb7609
+	github.com/xorcare/pointer v1.1.0
 	github.com/yudai/gojsondiff v0.0.0-20170107030110-7b1b7adf999d
 	github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect
 	github.com/yudai/pp v2.0.1+incompatible // indirect
diff --git a/go.sum b/go.sum
index 06f07b1e..6d215da0 100644
--- a/go.sum
+++ b/go.sum
@@ -156,6 +156,8 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHo
 github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
 github.com/xeipuuv/gojsonschema v0.0.0-20180816142147-da425ebb7609 h1:BcMExZAULPkihVZ7UJXK7t8rwGqisXFw75tILnafhBY=
 github.com/xeipuuv/gojsonschema v0.0.0-20180816142147-da425ebb7609/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=
+github.com/xorcare/pointer v1.1.0 h1:sFwXOhRF8QZ0tyVZrtxWGIoVZNEmRzBCaFWdONPQIUM=
+github.com/xorcare/pointer v1.1.0/go.mod h1:6KLhkOh6YbuvZkT4YbxIbR/wzLBjyMxOiNzZhJTor2Y=
 github.com/yudai/gojsondiff v0.0.0-20170107030110-7b1b7adf999d h1:yJIizrfO599ot2kQ6Af1enICnwBD3XoxgX3MrMwot2M=
 github.com/yudai/gojsondiff v0.0.0-20170107030110-7b1b7adf999d/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg=
 github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M=
diff --git a/openrtb_ext/bid.go b/openrtb_ext/bid.go
index c9c6f363..768128c9 100644
--- a/openrtb_ext/bid.go
+++ b/openrtb_ext/bid.go
@@ -42,9 +42,9 @@ type BidType string
 
 const (
 	BidTypeBanner BidType = "banner"
-	BidTypeVideo          = "video"
-	BidTypeAudio          = "audio"
-	BidTypeNative         = "native"
+	BidTypeVideo  BidType = "video"
+	BidTypeAudio  BidType = "audio"
+	BidTypeNative BidType = "native"
 )
 
 func BidTypes() []BidType {
diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go
index ed3d20e0..00c25f8a 100644
--- a/openrtb_ext/bidders.go
+++ b/openrtb_ext/bidders.go
@@ -47,6 +47,7 @@ const (
 	BidderGumGum           BidderName = "gumgum"
 	BidderImprovedigital   BidderName = "improvedigital"
 	BidderIx               BidderName = "ix"
+	BidderKidoz            BidderName = "kidoz"
 	BidderKubient          BidderName = "kubient"
 	BidderLifestreet       BidderName = "lifestreet"
 	BidderLockerDome       BidderName = "lockerdome"
@@ -70,6 +71,7 @@ const (
 	BidderTripleliftNative BidderName = "triplelift_native"
 	BidderUcfunnel         BidderName = "ucfunnel"
 	BidderUnruly           BidderName = "unruly"
+	BidderValueImpression  BidderName = "valueimpression"
 	BidderVerizonMedia     BidderName = "verizonmedia"
 	BidderVisx             BidderName = "visx"
 	BidderVrtcal           BidderName = "vrtcal"
@@ -106,6 +108,7 @@ var BidderMap = map[string]BidderName{
 	"gumgum":            BidderGumGum,
 	"improvedigital":    BidderImprovedigital,
 	"ix":                BidderIx,
+	"kidoz":             BidderKidoz,
 	"kubient":           BidderKubient,
 	"lifestreet":        BidderLifestreet,
 	"lockerdome":        BidderLockerDome,
@@ -129,6 +132,7 @@ var BidderMap = map[string]BidderName{
 	"triplelift_native": BidderTripleliftNative,
 	"ucfunnel":          BidderUcfunnel,
 	"unruly":            BidderUnruly,
+	"valueimpression":   BidderValueImpression,
 	"verizonmedia":      BidderVerizonMedia,
 	"visx":              BidderVisx,
 	"vrtcal":            BidderVrtcal,
diff --git a/openrtb_ext/imp_kidoz.go b/openrtb_ext/imp_kidoz.go
new file mode 100644
index 00000000..45f9866a
--- /dev/null
+++ b/openrtb_ext/imp_kidoz.go
@@ -0,0 +1,6 @@
+package openrtb_ext
+
+type ExtImpKidoz struct {
+	AccessToken string `json:"access_token"`
+	PublisherID string `json:"publisher_id"`
+}
diff --git a/openrtb_ext/imp_valueimpression.go b/openrtb_ext/imp_valueimpression.go
new file mode 100644
index 00000000..7c5c70ee
--- /dev/null
+++ b/openrtb_ext/imp_valueimpression.go
@@ -0,0 +1,5 @@
+package openrtb_ext
+
+type ExtImpValueImpression struct {
+	SiteId string `json:"siteId"`
+}
diff --git a/router/router.go b/router/router.go
index 7e713ca6..8ac463b8 100644
--- a/router/router.go
+++ b/router/router.go
@@ -251,7 +251,7 @@ func New(cfg *config.Configuration, rateConvertor *currencies.RateConverter) (r
 		glog.Fatalf("Failed to create the amp endpoint handler. %v", err)
 	}
 
-	videoEndpoint, err := openrtb2.NewVideoEndpoint(theExchange, paramsValidator, fetcher, videoFetcher, categoriesFetcher, cfg, r.MetricsEngine, pbsAnalytics, disabledBidders, defReqJSON, activeBiddersMap)
+	videoEndpoint, err := openrtb2.NewVideoEndpoint(theExchange, paramsValidator, fetcher, videoFetcher, categoriesFetcher, cfg, r.MetricsEngine, pbsAnalytics, disabledBidders, defReqJSON, activeBiddersMap, cacheClient)
 	if err != nil {
 		glog.Fatalf("Failed to create the video endpoint handler. %v", err)
 	}
diff --git a/static/bidder-info/kidoz.yaml b/static/bidder-info/kidoz.yaml
new file mode 100644
index 00000000..e2a9eee3
--- /dev/null
+++ b/static/bidder-info/kidoz.yaml
@@ -0,0 +1,11 @@
+maintainer:
+  email: prebid-support@kidoz.net
+capabilities:
+  app:
+    mediaTypes:
+      - banner
+      - video
+  site:
+    mediaTypes:
+      - banner
+      - video
diff --git a/static/bidder-info/valueimpression.yaml b/static/bidder-info/valueimpression.yaml
new file mode 100644
index 00000000..1d64abcb
--- /dev/null
+++ b/static/bidder-info/valueimpression.yaml
@@ -0,0 +1,11 @@
+maintainer:
+  email: "info@valueimpression.com"
+capabilities:
+  app:
+    mediaTypes:
+      - banner
+      - video
+  site:
+    mediaTypes:
+      - banner
+      - video
diff --git a/static/bidder-params/kidoz.json b/static/bidder-params/kidoz.json
new file mode 100644
index 00000000..79e2edc2
--- /dev/null
+++ b/static/bidder-params/kidoz.json
@@ -0,0 +1,26 @@
+{
+    "$schema": "http://json-schema.org/draft-04/schema#",
+    "title": "Kidoz Adapter Params",
+    "description": "A schema which validates params accepted by the Kidoz adapter",
+    "type": "object",
+    "properties": {
+        "access_token": {
+            "$ref": "#/definitions/non-empty-string",
+            "description": "Kidoz access_token"
+        },
+        "publisher_id": {
+            "$ref": "#/definitions/non-empty-string",
+            "description": "Kidoz publisher_id"
+        }
+    },
+    "definitions": {
+        "non-empty-string": {
+            "type": "string",
+            "minLength": 1
+        }
+    },
+    "required": [
+        "access_token",
+        "publisher_id"
+    ]
+}
\ No newline at end of file
diff --git a/static/bidder-params/valueimpression.json b/static/bidder-params/valueimpression.json
new file mode 100644
index 00000000..5b9c32c5
--- /dev/null
+++ b/static/bidder-params/valueimpression.json
@@ -0,0 +1,15 @@
+
+{
+    "$schema": "http://json-schema.org/draft-04/schema#",
+    "title": "ValueImpression Adapter Params",
+    "description": "Schema to validate params accepted by the ValueImpression adapter",
+  
+    "type": "object",
+    "properties": {
+      "siteId": {
+        "type": "string",
+        "description": "Site ID"
+      }
+    },
+    "required": ["siteId"]
+  }
diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go
index c58d5528..c7ad70b7 100644
--- a/usersync/usersyncers/syncer.go
+++ b/usersync/usersyncers/syncer.go
@@ -50,6 +50,7 @@ import (
 	"github.com/prebid/prebid-server/adapters/triplelift_native"
 	"github.com/prebid/prebid-server/adapters/ucfunnel"
 	"github.com/prebid/prebid-server/adapters/unruly"
+	"github.com/prebid/prebid-server/adapters/valueimpression"
 	"github.com/prebid/prebid-server/adapters/verizonmedia"
 	"github.com/prebid/prebid-server/adapters/visx"
 	"github.com/prebid/prebid-server/adapters/vrtcal"
@@ -111,6 +112,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderTripleliftNative, triplelift_native.NewTripleliftSyncer)
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderUcfunnel, ucfunnel.NewUcfunnelSyncer)
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderUnruly, unruly.NewUnrulySyncer)
+	insertIntoMap(cfg, syncers, openrtb_ext.BidderValueImpression, valueimpression.NewValueImpressionSyncer)
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderVerizonMedia, verizonmedia.NewVerizonMediaSyncer)
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderVisx, visx.NewVisxSyncer)
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderVrtcal, vrtcal.NewVrtcalSyncer)
diff --git a/validate.sh b/validate.sh
index b5210550..b81ade34 100755
--- a/validate.sh
+++ b/validate.sh
@@ -27,11 +27,11 @@ GOGLOB="${GOGLOB/ docs/}"
 GOGLOB="${GOGLOB/ vendor/}"
 
 # Check that there are no formatting issues
-GOFMT_LINES=`gofmt -s -l $GOGLOB | wc -l | xargs`
+GOFMT_LINES=`gofmt -s -l $GOGLOB | tr '\\\\' '/' | wc -l | xargs`
 if $AUTOFMT; then
   # if there are files with formatting issues, they will be automatically corrected using the gofmt -w <file> command
   if [[ $GOFMT_LINES -ne 0 ]]; then
-    FMT_FILES=`gofmt -s -l $GOGLOB | xargs`
+    FMT_FILES=`gofmt -s -l $GOGLOB | tr '\\\\' '/' | xargs`
     for FILE in $FMT_FILES; do
         echo "Running: gofmt -s -w $FILE"
         `gofmt -s -w $FILE`


Test patch:
diff --git a/adapters/kidoz/kidoz_test.go b/adapters/kidoz/kidoz_test.go
new file mode 100644
index 00000000..55036c08
--- /dev/null
+++ b/adapters/kidoz/kidoz_test.go
@@ -0,0 +1,113 @@
+package kidoz
+
+import (
+	"math"
+	"net/http"
+	"testing"
+
+	"github.com/mxmCherry/openrtb"
+	"github.com/prebid/prebid-server/adapters"
+	"github.com/prebid/prebid-server/adapters/adapterstest"
+	"github.com/prebid/prebid-server/openrtb_ext"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestJsonSamples(t *testing.T) {
+	adapterstest.RunJSONBidderTest(t, "kidoztest", NewKidozBidder("http://example.com/prebid"))
+}
+
+func makeBidRequest() *openrtb.BidRequest {
+	request := &openrtb.BidRequest{
+		ID: "test-req-id-0",
+		Imp: []openrtb.Imp{
+			{
+				ID: "test-imp-id-0",
+				Banner: &openrtb.Banner{
+					Format: []openrtb.Format{
+						{
+							W: 320,
+							H: 50,
+						},
+					},
+				},
+				Ext: []byte(`{"bidder":{"access_token":"token-0","publisher_id":"pub-0"}}`),
+			},
+		},
+	}
+	return request
+}
+
+func TestMakeRequests(t *testing.T) {
+	kidoz := NewKidozBidder("http://example.com/prebid")
+
+	t.Run("Handles Request marshal failure", func(t *testing.T) {
+		request := makeBidRequest()
+		request.Imp[0].BidFloor = math.Inf(1) // cant be marshalled
+		extra := &adapters.ExtraRequestInfo{}
+		reqs, errs := kidoz.MakeRequests(request, extra)
+		// cant assert message its different on different versions of go
+		assert.Equal(t, 1, len(errs))
+		assert.Contains(t, errs[0].Error(), "json")
+		assert.Equal(t, 0, len(reqs))
+	})
+}
+
+func TestMakeBids(t *testing.T) {
+	kidoz := NewKidozBidder("http://example.com/prebid")
+
+	t.Run("Handles response marshal failure", func(t *testing.T) {
+		request := makeBidRequest()
+		requestData := &adapters.RequestData{}
+		responseData := &adapters.ResponseData{
+			StatusCode: http.StatusOK,
+		}
+
+		resp, errs := kidoz.MakeBids(request, requestData, responseData)
+		// cant assert message its different on different versions of go
+		assert.Equal(t, 1, len(errs))
+		assert.Contains(t, errs[0].Error(), "JSON")
+		assert.Nil(t, resp)
+	})
+}
+
+func TestGetMediaTypeForImp(t *testing.T) {
+	imps := []openrtb.Imp{
+		{
+			ID:     "1",
+			Banner: &openrtb.Banner{},
+		},
+		{
+			ID:    "2",
+			Video: &openrtb.Video{},
+		},
+		{
+			ID:     "3",
+			Native: &openrtb.Native{},
+		},
+		{
+			ID:    "4",
+			Audio: &openrtb.Audio{},
+		},
+	}
+
+	t.Run("Bid not found is type empty string", func(t *testing.T) {
+		actual := GetMediaTypeForImp("ARGLE_BARGLE", imps)
+		assert.Equal(t, UndefinedMediaType, actual)
+	})
+	t.Run("Can find banner type", func(t *testing.T) {
+		actual := GetMediaTypeForImp("1", imps)
+		assert.Equal(t, openrtb_ext.BidTypeBanner, actual)
+	})
+	t.Run("Can find video type", func(t *testing.T) {
+		actual := GetMediaTypeForImp("2", imps)
+		assert.Equal(t, openrtb_ext.BidTypeVideo, actual)
+	})
+	t.Run("Can find native type", func(t *testing.T) {
+		actual := GetMediaTypeForImp("3", imps)
+		assert.Equal(t, openrtb_ext.BidTypeNative, actual)
+	})
+	t.Run("Can find audio type", func(t *testing.T) {
+		actual := GetMediaTypeForImp("4", imps)
+		assert.Equal(t, openrtb_ext.BidTypeAudio, actual)
+	})
+}
diff --git a/adapters/kidoz/kidoztest/exemplary/simple-banner.json b/adapters/kidoz/kidoztest/exemplary/simple-banner.json
new file mode 100644
index 00000000..c44fdba7
--- /dev/null
+++ b/adapters/kidoz/kidoztest/exemplary/simple-banner.json
@@ -0,0 +1,71 @@
+{
+	"mockBidRequest": {
+		"id": "test-request-id",
+		"imp": [
+			{
+				"id": "test-impression-id-1",
+				"banner": {
+					"format": [
+						{
+							"w": 320,
+							"h": 50
+						}
+					]
+				},
+				"ext": {
+					"bidder": {
+						"access_token": "test-token-1",
+						"publisher_id": "test-publisher-1"
+					}
+				}
+			}
+		]
+	},
+	"httpCalls": [
+		{
+			"expectedRequest": {
+				"uri": "http://example.com/prebid",
+				"body": {
+					"id": "test-request-id",
+					"imp": [
+						{
+							"id": "test-impression-id-1",
+							"banner": {
+								"format": [
+									{
+										"w": 320,
+										"h": 50
+									}
+								]
+							},
+							"ext": {
+								"bidder": {
+									"access_token": "test-token-1",
+									"publisher_id": "test-publisher-1"
+								}
+							}
+						}
+					]
+				}
+			},
+			"mockResponse": {
+				"status": 200,
+				"body": {
+					"id": "test-response-id",
+					"seatbid": [
+						{
+							"bid": [
+								{
+									"id": "test-bid-id-1",
+									"impid": "test-impression-id-1",
+									"price": 1
+								}
+							],
+							"seat": "kidoz"
+						}
+					]
+				}
+			}
+		}
+	]
+}
\ No newline at end of file
diff --git a/adapters/kidoz/kidoztest/exemplary/simple-video.json b/adapters/kidoz/kidoztest/exemplary/simple-video.json
new file mode 100644
index 00000000..3b682078
--- /dev/null
+++ b/adapters/kidoz/kidoztest/exemplary/simple-video.json
@@ -0,0 +1,69 @@
+{
+	"mockBidRequest": {
+		"id": "test-request-id",
+		"imp": [
+			{
+				"id": "test-impression-id-1",
+				"video": {
+					"w": 900,
+					"h": 250,
+					"mimes": [
+						"video/mp4"
+					]
+				},
+				"ext": {
+					"bidder": {
+						"access_token": "test-token-1",
+						"publisher_id": "test-publisher-1"
+					}
+				}
+			}
+		]
+	},
+	"httpCalls": [
+		{
+			"expectedRequest": {
+				"uri": "http://example.com/prebid",
+				"body": {
+					"id": "test-request-id",
+					"imp": [
+						{
+							"id": "test-impression-id-1",
+							"video": {
+								"w": 900,
+								"h": 250,
+								"mimes": [
+									"video/mp4"
+								]
+							},
+							"ext": {
+								"bidder": {
+									"access_token": "test-token-1",
+									"publisher_id": "test-publisher-1"
+								}
+							}
+						}
+					]
+				}
+			},
+			"mockResponse": {
+				"status": 200,
+				"body": {
+					"id": "test-response-id",
+					"seatbid": [
+						{
+							"bid": [
+								{
+									"id": "test-bid-id-1",
+									"impid": "test-impression-id-1",
+									"price": 1
+								}
+							],
+							"seat": "kidoz"
+						}
+					]
+				}
+			}
+		}
+	]
+}
diff --git a/adapters/kidoz/kidoztest/supplemental/bad-bid.json b/adapters/kidoz/kidoztest/supplemental/bad-bid.json
new file mode 100644
index 00000000..32b8ec2c
--- /dev/null
+++ b/adapters/kidoz/kidoztest/supplemental/bad-bid.json
@@ -0,0 +1,96 @@
+{
+	"mockBidRequest": {
+		"id": "test-request-id",
+		"imp": [
+			{
+				"id": "test-impression-id-0",
+				"banner": {
+					"format": [
+						{
+							"w": 320,
+							"h": 50
+						}
+					]
+				},
+				"ext": {
+					"bidder": {
+						"access_token": "test-token-0",
+						"publisher_id": "test-publisher-0"
+					}
+				}
+			}
+		]
+	},
+	"httpCalls": [
+		{
+			"expectedRequest": {
+				"uri": "http://example.com/prebid",
+				"body": {
+					"id": "test-request-id",
+					"imp": [
+						{
+							"id": "test-impression-id-0",
+							"banner": {
+								"format": [
+									{
+										"w": 320,
+										"h": 50
+									}
+								]
+							},
+							"ext": {
+								"bidder": {
+									"access_token": "test-token-0",
+									"publisher_id": "test-publisher-0"
+								}
+							}
+						}
+					]
+				}
+			},
+			"mockResponse": {
+				"status": 200,
+				"body": {
+					"id": "test-response-id",
+					"seatbid": [
+						{
+							"bid": [
+								{
+									"id": "test-bid-id-0",
+									"impid": "test-impression-id-0",
+									"price": 10
+								},
+								{
+									"id": "test-bid-id-bogus",
+									"impid": "test-impression-id-bogus",
+									"price": 11
+								}
+							]
+						}
+					]
+				}
+			}
+		}
+	],
+	"expectedBidResponses": [
+		{
+			"bids": [
+				{
+					"bid": {
+						"id": "test-bid-id-0",
+						"impid": "test-impression-id-0",
+						"price": 10
+					},
+					"type": "banner"
+				}
+			]
+		}
+	],
+	"expectedMakeRequestsErrors": [],
+	"expectedMakeBidsErrors": [
+		{
+			"value": "ignoring bid id=test-bid-id-bogus, request doesn't contain any valid impression with id=test-impression-id-bogus",
+			"comparison": "literal"
+		}
+	]
+}
\ No newline at end of file
diff --git a/adapters/kidoz/kidoztest/supplemental/bidder-marshal.json b/adapters/kidoz/kidoztest/supplemental/bidder-marshal.json
new file mode 100644
index 00000000..8a8a5e76
--- /dev/null
+++ b/adapters/kidoz/kidoztest/supplemental/bidder-marshal.json
@@ -0,0 +1,30 @@
+{
+	"mockBidRequest": {
+		"id": "test-request-id",
+		"imp": [
+			{
+				"id": "test-impression-id-7",
+				"banner": {
+					"format": [
+						{
+							"w": 320,
+							"h": 50
+						}
+					]
+				},
+				"ext": {
+					"bidder": "invalid bidder"
+				}
+			}
+		]
+	},
+	"httpCalls": [],
+	"expectedBidResponses": [],
+	"expectedMakeRequestsErrors": [
+		{
+			"value": "json: cannot unmarshal string into Go value of type openrtb_ext.ExtImpKidoz",
+			"comparison": "literal"
+		}
+	],
+	"expectedMakeBidsErrors": []
+}
\ No newline at end of file
diff --git a/adapters/kidoz/kidoztest/supplemental/empty-banner-format .json b/adapters/kidoz/kidoztest/supplemental/empty-banner-format .json
new file mode 100644
index 00000000..18b62a4e
--- /dev/null
+++ b/adapters/kidoz/kidoztest/supplemental/empty-banner-format .json
@@ -0,0 +1,19 @@
+{
+	"mockBidRequest": {
+		"id": "test-request-id",
+		"imp": [
+			{
+				"id": "test-impression-id-1",
+				"banner": {
+					"format": []
+				}
+			}
+		]
+	},
+	"expectedMakeRequestsErrors": [
+		{
+			"value": "banner format array is empty",
+			"comparison": "literal"
+		}
+	]
+}
\ No newline at end of file
diff --git a/adapters/kidoz/kidoztest/supplemental/ext-marshal.json b/adapters/kidoz/kidoztest/supplemental/ext-marshal.json
new file mode 100644
index 00000000..eaab4594
--- /dev/null
+++ b/adapters/kidoz/kidoztest/supplemental/ext-marshal.json
@@ -0,0 +1,28 @@
+{
+	"mockBidRequest": {
+		"id": "test-request-id",
+		"imp": [
+			{
+				"id": "test-impression-id-7",
+				"banner": {
+					"format": [
+						{
+							"w": 320,
+							"h": 50
+						}
+					]
+				},
+				"ext": "invalid ext"
+			}
+		]
+	},
+	"httpCalls": [],
+	"expectedBidResponses": [],
+	"expectedMakeRequestsErrors": [
+		{
+			"value": "json: cannot unmarshal string into Go value of type adapters.ExtImpBidder",
+			"comparison": "literal"
+		}
+	],
+	"expectedMakeBidsErrors": []
+}
\ No newline at end of file
diff --git a/adapters/kidoz/kidoztest/supplemental/missing-banner-format.json b/adapters/kidoz/kidoztest/supplemental/missing-banner-format.json
new file mode 100644
index 00000000..3fdb5443
--- /dev/null
+++ b/adapters/kidoz/kidoztest/supplemental/missing-banner-format.json
@@ -0,0 +1,18 @@
+{
+	"mockBidRequest": {
+		"id": "test-request-id",
+		"imp": [
+			{
+				"id": "test-impression-id-1",
+				"banner": {
+				}
+			}
+		]
+	},
+	"expectedMakeRequestsErrors": [
+		{
+			"value": "banner format required",
+			"comparison": "literal"
+		}
+	]
+}
\ No newline at end of file
diff --git a/adapters/kidoz/kidoztest/supplemental/missing-bidder.json b/adapters/kidoz/kidoztest/supplemental/missing-bidder.json
new file mode 100644
index 00000000..06ab222e
--- /dev/null
+++ b/adapters/kidoz/kidoztest/supplemental/missing-bidder.json
@@ -0,0 +1,25 @@
+{
+	"mockBidRequest": {
+		"id": "test-request-id",
+		"imp": [
+			{
+				"id": "test-impression-id-1",
+				"banner": {
+					"format": [
+						{
+							"w": 320,
+							"h": 50
+						}
+					]
+				},
+				"ext": {}
+			}
+		]
+	},
+	"expectedMakeRequestsErrors": [
+		{
+			"value": "bidder required",
+			"comparison": "literal"
+		}
+	]
+}
\ No newline at end of file
diff --git a/adapters/kidoz/kidoztest/supplemental/missing-ext.json b/adapters/kidoz/kidoztest/supplemental/missing-ext.json
new file mode 100644
index 00000000..8424a0b1
--- /dev/null
+++ b/adapters/kidoz/kidoztest/supplemental/missing-ext.json
@@ -0,0 +1,24 @@
+{
+	"mockBidRequest": {
+		"id": "test-request-id",
+		"imp": [
+			{
+				"id": "test-impression-id-1",
+				"banner": {
+					"format": [
+						{
+							"w": 320,
+							"h": 50
+						}
+					]
+				}
+			}
+		]
+	},
+	"expectedMakeRequestsErrors": [
+		{
+			"value": "impression extensions required",
+			"comparison": "literal"
+		}
+	]
+}
\ No newline at end of file
diff --git a/adapters/kidoz/kidoztest/supplemental/missing-kidoz-info.json b/adapters/kidoz/kidoztest/supplemental/missing-kidoz-info.json
new file mode 100644
index 00000000..bfe67aa7
--- /dev/null
+++ b/adapters/kidoz/kidoztest/supplemental/missing-kidoz-info.json
@@ -0,0 +1,52 @@
+{
+	"mockBidRequest": {
+		"id": "test-request-id",
+		"imp": [
+			{
+				"id": "test-impression-id-5",
+				"banner": {
+					"format": [
+						{
+							"w": 320,
+							"h": 50
+						}
+					]
+				},
+				"ext": {
+					"bidder": {
+						"access_token": "test-token-5"
+					}
+				}
+			},
+			{
+				"id": "test-impression-id-6",
+				"banner": {
+					"format": [
+						{
+							"w": 320,
+							"h": 50
+						}
+					]
+				},
+				"ext": {
+					"bidder": {
+						"publisher_id": "test-publisher-6"
+					}
+				}
+			}
+		]
+	},
+	"httpCalls": [],
+	"expectedBidResponses": [],
+	"expectedMakeRequestsErrors": [
+		{
+			"value": "Kidoz publisher_id required",
+			"comparison": "literal"
+		},
+		{
+			"value": "Kidoz access_token required",
+			"comparison": "literal"
+		}
+	],
+	"expectedMakeBidsErrors": []
+}
\ No newline at end of file
diff --git a/adapters/kidoz/kidoztest/supplemental/only-video-banner.json b/adapters/kidoz/kidoztest/supplemental/only-video-banner.json
new file mode 100644
index 00000000..6e87e808
--- /dev/null
+++ b/adapters/kidoz/kidoztest/supplemental/only-video-banner.json
@@ -0,0 +1,27 @@
+{
+	"mockBidRequest": {
+		"id": "test-request-id",
+		"imp": [
+			{
+				"id": "test-impression-id-0",
+				"audio": {
+				}
+			},
+			{
+				"id": "test-impression-id-1",
+				"native": {
+				}
+			}
+		]
+	},
+	"expectedMakeRequestsErrors": [
+		{
+			"value": "Kidoz only supports banner or video ads",
+			"comparison": "literal"
+		},
+		{
+			"value": "Kidoz only supports banner or video ads",
+			"comparison": "literal"
+		}
+	]
+}
\ No newline at end of file
diff --git a/adapters/kidoz/kidoztest/supplemental/status-204.json b/adapters/kidoz/kidoztest/supplemental/status-204.json
new file mode 100644
index 00000000..0bff1022
--- /dev/null
+++ b/adapters/kidoz/kidoztest/supplemental/status-204.json
@@ -0,0 +1,57 @@
+{
+	"mockBidRequest": {
+		"id": "test-request-id",
+		"imp": [
+			{
+				"id": "test-impression-id-1",
+				"banner": {
+					"format": [
+						{
+							"h": 250,
+							"w": 300
+						}
+					]
+				},
+				"ext": {
+					"bidder": {
+						"access_token": "test-token-1",
+						"publisher_id": "test-publisher-1"
+					}
+				}
+			}
+		]
+	},
+	"httpCalls": [
+		{
+			"expectedRequest": {
+				"uri": "http://example.com/prebid",
+				"body": {
+					"id": "test-request-id",
+					"imp": [
+						{
+							"id": "test-impression-id-1",
+							"banner": {
+								"format": [
+									{
+										"h": 250,
+										"w": 300
+									}
+								]
+							},
+							"ext": {
+								"bidder": {
+									"access_token": "test-token-1",
+									"publisher_id": "test-publisher-1"
+								}
+							}
+						}
+					]
+				}
+			},
+			"mockResponse": {
+				"status": 204,
+				"body": {}
+			}
+		}
+	]
+}
\ No newline at end of file
diff --git a/adapters/kidoz/kidoztest/supplemental/status-400.json b/adapters/kidoz/kidoztest/supplemental/status-400.json
new file mode 100644
index 00000000..ca42aefd
--- /dev/null
+++ b/adapters/kidoz/kidoztest/supplemental/status-400.json
@@ -0,0 +1,63 @@
+{
+	"mockBidRequest": {
+		"id": "test-request-id",
+		"imp": [
+			{
+				"id": "test-impression-id-1",
+				"banner": {
+					"format": [
+						{
+							"h": 250,
+							"w": 300
+						}
+					]
+				},
+				"ext": {
+					"bidder": {
+						"access_token": "test-token-1",
+						"publisher_id": "test-publisher-1"
+					}
+				}
+			}
+		]
+	},
+	"httpCalls": [
+		{
+			"expectedRequest": {
+				"uri": "http://example.com/prebid",
+				"body": {
+					"id": "test-request-id",
+					"imp": [
+						{
+							"id": "test-impression-id-1",
+							"banner": {
+								"format": [
+									{
+										"h": 250,
+										"w": 300
+									}
+								]
+							},
+							"ext": {
+								"bidder": {
+									"access_token": "test-token-1",
+									"publisher_id": "test-publisher-1"
+								}
+							}
+						}
+					]
+				}
+			},
+			"mockResponse": {
+				"status": 400,
+				"body": "server text here"
+			}
+		}
+	],
+	"expectedMakeBidsErrors": [
+		{
+			"value": "unexpected status code: 400 \"server text here\"",
+			"comparison": "literal"
+		}
+	]
+}
\ No newline at end of file
diff --git a/adapters/kidoz/kidoztest/supplemental/status-403.json b/adapters/kidoz/kidoztest/supplemental/status-403.json
new file mode 100644
index 00000000..3b6d268e
--- /dev/null
+++ b/adapters/kidoz/kidoztest/supplemental/status-403.json
@@ -0,0 +1,63 @@
+{
+	"mockBidRequest": {
+		"id": "test-request-id",
+		"imp": [
+			{
+				"id": "test-impression-id-1",
+				"banner": {
+					"format": [
+						{
+							"h": 250,
+							"w": 300
+						}
+					]
+				},
+				"ext": {
+					"bidder": {
+						"access_token": "test-token-1",
+						"publisher_id": "test-publisher-1"
+					}
+				}
+			}
+		]
+	},
+	"httpCalls": [
+		{
+			"expectedRequest": {
+				"uri": "http://example.com/prebid",
+				"body": {
+					"id": "test-request-id",
+					"imp": [
+						{
+							"id": "test-impression-id-1",
+							"banner": {
+								"format": [
+									{
+										"h": 250,
+										"w": 300
+									}
+								]
+							},
+							"ext": {
+								"bidder": {
+									"access_token": "test-token-1",
+									"publisher_id": "test-publisher-1"
+								}
+							}
+						}
+					]
+				}
+			},
+			"mockResponse": {
+				"status": 403,
+				"body": "server text here"
+			}
+		}
+	],
+	"expectedMakeBidsErrors": [
+		{
+			"value": "unexpected status code: 403 \"server text here\"",
+			"comparison": "literal"
+		}
+	]
+}
\ No newline at end of file
diff --git a/adapters/kidoz/kidoztest/supplemental/status-408.json b/adapters/kidoz/kidoztest/supplemental/status-408.json
new file mode 100644
index 00000000..8230967f
--- /dev/null
+++ b/adapters/kidoz/kidoztest/supplemental/status-408.json
@@ -0,0 +1,63 @@
+{
+	"mockBidRequest": {
+		"id": "test-request-id",
+		"imp": [
+			{
+				"id": "test-impression-id-1",
+				"banner": {
+					"format": [
+						{
+							"h": 250,
+							"w": 300
+						}
+					]
+				},
+				"ext": {
+					"bidder": {
+						"access_token": "test-token-1",
+						"publisher_id": "test-publisher-1"
+					}
+				}
+			}
+		]
+	},
+	"httpCalls": [
+		{
+			"expectedRequest": {
+				"uri": "http://example.com/prebid",
+				"body": {
+					"id": "test-request-id",
+					"imp": [
+						{
+							"id": "test-impression-id-1",
+							"banner": {
+								"format": [
+									{
+										"h": 250,
+										"w": 300
+									}
+								]
+							},
+							"ext": {
+								"bidder": {
+									"access_token": "test-token-1",
+									"publisher_id": "test-publisher-1"
+								}
+							}
+						}
+					]
+				}
+			},
+			"mockResponse": {
+				"status": 408,
+				"body": "server text here"
+			}
+		}
+	],
+	"expectedMakeBidsErrors": [
+		{
+			"value": "unexpected status code: 408 \"server text here\"",
+			"comparison": "literal"
+		}
+	]
+}
\ No newline at end of file
diff --git a/adapters/kidoz/kidoztest/supplemental/status-500.json b/adapters/kidoz/kidoztest/supplemental/status-500.json
new file mode 100644
index 00000000..f734e691
--- /dev/null
+++ b/adapters/kidoz/kidoztest/supplemental/status-500.json
@@ -0,0 +1,63 @@
+{
+	"mockBidRequest": {
+		"id": "test-request-id",
+		"imp": [
+			{
+				"id": "test-impression-id-1",
+				"banner": {
+					"format": [
+						{
+							"h": 250,
+							"w": 300
+						}
+					]
+				},
+				"ext": {
+					"bidder": {
+						"access_token": "test-token-1",
+						"publisher_id": "test-publisher-1"
+					}
+				}
+			}
+		]
+	},
+	"httpCalls": [
+		{
+			"expectedRequest": {
+				"uri": "http://example.com/prebid",
+				"body": {
+					"id": "test-request-id",
+					"imp": [
+						{
+							"id": "test-impression-id-1",
+							"banner": {
+								"format": [
+									{
+										"h": 250,
+										"w": 300
+									}
+								]
+							},
+							"ext": {
+								"bidder": {
+									"access_token": "test-token-1",
+									"publisher_id": "test-publisher-1"
+								}
+							}
+						}
+					]
+				}
+			},
+			"mockResponse": {
+				"status": 500,
+				"body": "server text here"
+			}
+		}
+	],
+	"expectedMakeBidsErrors": [
+		{
+			"value": "unexpected status code: 500 \"server text here\"",
+			"comparison": "literal"
+		}
+	]
+}
\ No newline at end of file
diff --git a/adapters/kidoz/kidoztest/supplemental/status-502.json b/adapters/kidoz/kidoztest/supplemental/status-502.json
new file mode 100644
index 00000000..b99f52a2
--- /dev/null
+++ b/adapters/kidoz/kidoztest/supplemental/status-502.json
@@ -0,0 +1,63 @@
+{
+	"mockBidRequest": {
+		"id": "test-request-id",
+		"imp": [
+			{
+				"id": "test-impression-id-1",
+				"banner": {
+					"format": [
+						{
+							"h": 250,
+							"w": 300
+						}
+					]
+				},
+				"ext": {
+					"bidder": {
+						"access_token": "test-token-1",
+						"publisher_id": "test-publisher-1"
+					}
+				}
+			}
+		]
+	},
+	"httpCalls": [
+		{
+			"expectedRequest": {
+				"uri": "http://example.com/prebid",
+				"body": {
+					"id": "test-request-id",
+					"imp": [
+						{
+							"id": "test-impression-id-1",
+							"banner": {
+								"format": [
+									{
+										"h": 250,
+										"w": 300
+									}
+								]
+							},
+							"ext": {
+								"bidder": {
+									"access_token": "test-token-1",
+									"publisher_id": "test-publisher-1"
+								}
+							}
+						}
+					]
+				}
+			},
+			"mockResponse": {
+				"status": 502,
+				"body": "server text here"
+			}
+		}
+	],
+	"expectedMakeBidsErrors": [
+		{
+			"value": "unexpected status code: 502 \"server text here\"",
+			"comparison": "literal"
+		}
+	]
+}
\ No newline at end of file
diff --git a/adapters/kidoz/kidoztest/supplemental/status-503.json b/adapters/kidoz/kidoztest/supplemental/status-503.json
new file mode 100644
index 00000000..f8233729
--- /dev/null
+++ b/adapters/kidoz/kidoztest/supplemental/status-503.json
@@ -0,0 +1,58 @@
+{
+	"mockBidRequest": {
+		"id": "test-request-id",
+		"imp": [
+			{
+				"id": "test-impression-id-1",
+				"banner": {
+					"format": [
+						{
+							"h": 250,
+							"w": 300
+						}
+					]
+				},
+				"ext": {
+					"bidder": {
+						"access_token": "test-token-1",
+						"publisher_id": "test-publisher-1"
+					}
+				}
+			}
+		]
+	},
+	"httpCalls": [
+		{
+			"expectedRequest": {
+				"uri": "http://example.com/prebid",
+				"body": {
+					"id": "test-request-id",
+					"imp": [
+						{
+							"id": "test-impression-id-1",
+							"banner": {
+								"format": [
+									{
+										"h": 250,
+										"w": 300
+									}
+								]
+							},
+							"ext": {
+								"bidder": {
+									"access_token": "test-token-1",
+									"publisher_id": "test-publisher-1"
+								}
+							}
+						}
+					]
+				}
+			},
+			"mockResponse": {
+				"status": 503,
+				"body": "server text here"
+			}
+		}
+	],
+	"expectedMakeBidsErrors": []
+}
\ No newline at end of file
diff --git a/adapters/kidoz/kidoztest/supplemental/status-504.json b/adapters/kidoz/kidoztest/supplemental/status-504.json
new file mode 100644
index 00000000..b996611e
--- /dev/null
+++ b/adapters/kidoz/kidoztest/supplemental/status-504.json
@@ -0,0 +1,63 @@
+{
+	"mockBidRequest": {
+		"id": "test-request-id",
+		"imp": [
+			{
+				"id": "test-impression-id-1",
+				"banner": {
+					"format": [
+						{
+							"h": 250,
+							"w": 300
+						}
+					]
+				},
+				"ext": {
+					"bidder": {
+						"access_token": "test-token-1",
+						"publisher_id": "test-publisher-1"
+					}
+				}
+			}
+		]
+	},
+	"httpCalls": [
+		{
+			"expectedRequest": {
+				"uri": "http://example.com/prebid",
+				"body": {
+					"id": "test-request-id",
+					"imp": [
+						{
+							"id": "test-impression-id-1",
+							"banner": {
+								"format": [
+									{
+										"h": 250,
+										"w": 300
+									}
+								]
+							},
+							"ext": {
+								"bidder": {
+									"access_token": "test-token-1",
+									"publisher_id": "test-publisher-1"
+								}
+							}
+						}
+					]
+				}
+			},
+			"mockResponse": {
+				"status": 504,
+				"body": "server text here"
+			}
+		}
+	],
+	"expectedMakeBidsErrors": [
+		{
+			"value": "unexpected status code: 504 \"server text here\"",
+			"comparison": "literal"
+		}
+	]
+}
\ No newline at end of file
diff --git a/adapters/kidoz/params_test.go b/adapters/kidoz/params_test.go
new file mode 100644
index 00000000..073d7382
--- /dev/null
+++ b/adapters/kidoz/params_test.go
@@ -0,0 +1,79 @@
+package kidoz
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+func TestValidParams(t *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+	if err != nil {
+		t.Fatalf("Failed to fetch the json-schemas. %v", err)
+	}
+
+	for _, validParam := range validParams {
+		if err := validator.Validate(openrtb_ext.BidderKidoz, json.RawMessage(validParam)); err != nil {
+			t.Errorf("Schema rejected kidoz params: %s \n Error: %s", validParam, err)
+		}
+	}
+}
+
+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.BidderKidoz, json.RawMessage(invalidParam)); err == nil {
+			t.Errorf("Schema allowed unexpected params: %s", invalidParam)
+		}
+	}
+}
+
+var validParams = []string{
+	`{"publisher_id":"pub-valid-0", "access_token":"token-valid-0"}`,
+}
+
+var invalidParams = []string{
+	``,
+	`null`,
+	`true`,
+	`5`,
+	`4.2`,
+	`[]`,
+	`{}`,
+	`{"some_random_field":""}`,
+	`{"publisher_id":""}`,
+	`{"publisher_id": 1}`,
+	`{"publisher_id": 1.2}`,
+	`{"publisher_id": null}`,
+	`{"publisher_id": true}`,
+	`{"publisher_id": []}`,
+	`{"publisher_id": {}}`,
+	`{"publisher_id":"", "access_token":"token-valid-0"}`,
+	`{"publisher_id": 1, "access_token":"token-valid-0"}`,
+	`{"publisher_id": 1.2, "access_token":"token-valid-0"}`,
+	`{"publisher_id": null, "access_token":"token-valid-0"}`,
+	`{"publisher_id": true, "access_token":"token-valid-0"}`,
+	`{"publisher_id": [], "access_token":"token-valid-0"}`,
+	`{"publisher_id": {}, "access_token":"token-valid-0"}`,
+	`{"access_token":""}`,
+	`{"access_token": 1}`,
+	`{"access_token": 1.2}`,
+	`{"access_token": null}`,
+	`{"access_token": true}`,
+	`{"access_token": []}`,
+	`{"access_token": {}}`,
+	`{"access_token":"", "publisher_id":"pub-valid-0"}`,
+	`{"access_token": 1, "publisher_id":"pub-valid-0"}`,
+	`{"access_token": 1.2, "publisher_id":"pub-valid-0"}`,
+	`{"access_token": null, "publisher_id":"pub-valid-0"}`,
+	`{"access_token": true, "publisher_id":"pub-valid-0"}`,
+	`{"access_token": [], "publisher_id":"pub-valid-0"}`,
+	`{"access_token": {}, "publisher_id":"pub-valid-0"}`,
+	`{"access_token": 1, "publisher_id":"pub-valid-0"}`,
+	`{"access_token":"token-valid-0", "publisher_id": 1}`,
+}
diff --git a/adapters/valueimpression/params_test.go b/adapters/valueimpression/params_test.go
new file mode 100644
index 00000000..46471de2
--- /dev/null
+++ b/adapters/valueimpression/params_test.go
@@ -0,0 +1,52 @@
+package valueimpression
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+// This file actually intends to test static/bidder-params/valueimpression.json
+// These also validate the format of the external API: request.imp[i].ext.valueimpression
+// TestValidParams makes sure that the ValueImpression schema accepts all imp.ext fields which we intend to support.
+
+func TestValidParams(t *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+	if err != nil {
+		t.Fatalf("Failed to fetch the json-schemas. %v", err)
+	}
+
+	for _, validParam := range validParams {
+		if err := validator.Validate(openrtb_ext.BidderValueImpression, json.RawMessage(validParam)); err != nil {
+			t.Errorf("Schema rejected ValueImpression params: %s", validParam)
+		}
+	}
+}
+
+// TestInvalidParams makes sure that the ValueImpression schema rejects all the imp.ext fields we don't support.
+func TestInvalidParams(t *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+	if err != nil {
+		t.Fatalf("Failed to fetch the json-schemas. %v", err)
+	}
+
+	for _, invalidParam := range invalidParams {
+		if err := validator.Validate(openrtb_ext.BidderValueImpression, json.RawMessage(invalidParam)); err == nil {
+			t.Errorf("Schema allowed unexpected params: %s", invalidParam)
+		}
+	}
+}
+
+var validParams = []string{
+	`{"siteId": "123"}`,
+}
+
+var invalidParams = []string{
+	`{}`,
+	`null`,
+	`true`,
+	`154`,
+	`{"siteId": 123}`, // siteId should be string
+	`{"invalid_param": "123"}`,
+}
diff --git a/adapters/valueimpression/usersync_test.go b/adapters/valueimpression/usersync_test.go
new file mode 100644
index 00000000..63f12305
--- /dev/null
+++ b/adapters/valueimpression/usersync_test.go
@@ -0,0 +1,35 @@
+package valueimpression
+
+import (
+	"testing"
+	"text/template"
+
+	"github.com/prebid/prebid-server/privacy"
+	"github.com/prebid/prebid-server/privacy/ccpa"
+	"github.com/prebid/prebid-server/privacy/gdpr"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestValueImpressionSyncer(t *testing.T) {
+	syncURL := "https://rtb.valueimpression.com/usersync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&redirectUri=http%3A%2F%2Flocalhost:8000%2Fsetuid%3Fbidder%3Dvalueimpression%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID"
+	syncURLTemplate := template.Must(
+		template.New("sync-template").Parse(syncURL),
+	)
+
+	syncer := NewValueImpressionSyncer(syncURLTemplate)
+	syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{
+		GDPR: gdpr.Policy{
+			Signal:  "1",
+			Consent: "BOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA",
+		},
+		CCPA: ccpa.Policy{
+			Value: "1NYN",
+		},
+	})
+
+	assert.NoError(t, err)
+	assert.Equal(t, "https://rtb.valueimpression.com/usersync?gdpr=1&gdpr_consent=BOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA&redirectUri=http%3A%2F%2Flocalhost:8000%2Fsetuid%3Fbidder%3Dvalueimpression%26gdpr%3D1%26gdpr_consent%3DBOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA%26uid%3D%24UID", syncInfo.URL)
+	assert.Equal(t, "redirect", syncInfo.Type)
+	assert.EqualValues(t, 0, syncer.GDPRVendorID())
+	assert.False(t, syncInfo.SupportCORS)
+}
diff --git a/adapters/valueimpression/valueimpression_test.go b/adapters/valueimpression/valueimpression_test.go
new file mode 100644
index 00000000..047521ce
--- /dev/null
+++ b/adapters/valueimpression/valueimpression_test.go
@@ -0,0 +1,11 @@
+package valueimpression
+
+import (
+	"testing"
+
+	"github.com/prebid/prebid-server/adapters/adapterstest"
+)
+
+func TestJsonSamples(t *testing.T) {
+	adapterstest.RunJSONBidderTest(t, "valueimpressiontest", NewValueImpressionBidder("//host"))
+}
diff --git a/adapters/valueimpression/valueimpressiontest/exemplary/banner-and-video.json b/adapters/valueimpression/valueimpressiontest/exemplary/banner-and-video.json
new file mode 100644
index 00000000..107c0d84
--- /dev/null
+++ b/adapters/valueimpression/valueimpressiontest/exemplary/banner-and-video.json
@@ -0,0 +1,150 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-banner-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "siteId": "123"
+          }
+        }
+      },
+      {
+        "id": "test-video-imp-id",
+        "video": {
+          "mimes": [
+            "video/mp4"
+          ],
+          "protocols": [
+            2,
+            5
+          ],
+          "w": 640,
+          "h": 480
+        },
+        "ext": {
+          "bidder": {
+            "siteId": "123"
+          }
+        }
+      }
+    ],
+    "site": {
+      "id": "123"
+    }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "//host",
+        "body": {
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id": "test-banner-imp-id",
+              "banner": {
+                "format": [
+                  {
+                    "h": 250,
+                    "w": 300
+                  }
+                ]
+              },
+              "ext": {
+                "siteId": "123"
+              }
+            },
+            {
+              "id": "test-video-imp-id",
+              "video": {
+                "h": 480,
+                "mimes": [
+                  "video/mp4"
+                ],
+                "protocols": [
+                  2,
+                  5
+                ],
+                "w": 640
+              },
+              "ext": {
+                "siteId": "123"
+              }
+            }
+          ],
+          "site": {
+            "id": "123"
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "seatbid": [
+            {
+              "seat": "valueimpression",
+              "bid": [
+                {
+                  "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800",
+                  "impid": "test-video-imp-id",
+                  "price": 0.5,
+                  "adm": "some-test-ad",
+                  "crid": "crid_10",
+                  "h": 250,
+                  "w": 300
+                }
+              ]
+            }
+          ],
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+  "expectedBids": [
+    {
+      "bid": {
+        "id": "7706636740145184841",
+        "impid": "test-imp-id",
+        "price": 0.5,
+        "adm": "some-test-ad",
+        "adid": "29681110",
+        "adomain": [
+          "sample.com"
+        ],
+        "cid": "958",
+        "crid": "29681110",
+        "w": 1024,
+        "h": 576
+      },
+      "type": "banner"
+    },
+    {
+      "bid": {
+        "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800",
+        "impid": "test-imp-video-id",
+        "price": 0.5,
+        "adm": "some-test-ad",
+        "adid": "29484110",
+        "adomain": [
+          "sample.com"
+        ],
+        "cid": "958",
+        "crid": "29484110",
+        "w": 1024,
+        "h": 576
+      },
+      "type": "video"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/valueimpression/valueimpressiontest/exemplary/banner.json b/adapters/valueimpression/valueimpressiontest/exemplary/banner.json
new file mode 100644
index 00000000..1ef11ade
--- /dev/null
+++ b/adapters/valueimpression/valueimpressiontest/exemplary/banner.json
@@ -0,0 +1,98 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-banner-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "siteId": "123"
+          }
+        }
+      }
+    ],
+    "site": {
+      "id": "fake-site-id"
+    }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "//host",
+        "body": {
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id": "test-banner-imp-id",
+              "banner": {
+                "format": [
+                  {
+                    "w": 300,
+                    "h": 250
+                  }
+                ]
+              },
+              "ext": {
+                "siteId": "123"
+              }
+            }
+          ],
+          "site": {
+            "id": "fake-site-id"
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "seatbid": [
+            {
+              "seat": "valueimpression",
+              "bid": [
+                {
+                  "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800",
+                  "impid": "test-banner-imp-id",
+                  "price": 0.500000,
+                  "adm": "some-test-ad",
+                  "crid": "crid_10",
+                  "h": 250,
+                  "w": 300
+                }
+              ]
+            }
+          ],
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800",
+            "impid": "test-banner-imp-id",
+            "price": 0.5,
+            "adm": "some-test-ad",
+            "crid": "crid_10",
+            "w": 300,
+            "h": 250
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
+  
\ No newline at end of file
diff --git a/adapters/valueimpression/valueimpressiontest/exemplary/video.json b/adapters/valueimpression/valueimpressiontest/exemplary/video.json
new file mode 100644
index 00000000..c6e71e7a
--- /dev/null
+++ b/adapters/valueimpression/valueimpressiontest/exemplary/video.json
@@ -0,0 +1,53 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-video-imp-id",
+        "video": {
+          "w": 900,
+          "h": 250,
+          "mimes": [
+            "video/x-flv",
+            "video/mp4"
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "siteId": "123"
+          }
+        }
+      }
+    ]
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "//host",
+        "body": {
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id":"test-video-imp-id",
+              "video": {
+                "w": 900,
+                "h": 250,
+                "mimes": [
+                  "video/x-flv",
+                  "video/mp4"
+                ]
+              },
+              "ext": {
+                "siteId": "123"
+              }
+            }
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 204
+      }
+    }
+  ]
+}
diff --git a/adapters/valueimpression/valueimpressiontest/supplemental/explicit-dimensions.json b/adapters/valueimpression/valueimpressiontest/supplemental/explicit-dimensions.json
new file mode 100644
index 00000000..ee23350c
--- /dev/null
+++ b/adapters/valueimpression/valueimpressiontest/supplemental/explicit-dimensions.json
@@ -0,0 +1,56 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            }
+          ],
+          "w": 100,
+          "h": 400
+        },
+        "ext": {
+          "bidder": {
+            "siteId": "123"
+          }
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "//host",
+        "body": {
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id": "test-imp-id",
+              "banner": {
+                "format": [
+                  {
+                    "w": 300,
+                    "h": 250
+                  }
+                ],
+                "w": 100,
+                "h": 400
+              },
+              "ext": {
+                "siteId": "123"
+              }
+            }
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 204
+      }
+    }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/valueimpression/valueimpressiontest/supplemental/invalid-response-no-bids.json b/adapters/valueimpression/valueimpressiontest/supplemental/invalid-response-no-bids.json
new file mode 100644
index 00000000..114b27ba
--- /dev/null
+++ b/adapters/valueimpression/valueimpressiontest/supplemental/invalid-response-no-bids.json
@@ -0,0 +1,50 @@
+{
+  "mockBidRequest": {
+    "id": "some_test_auction",
+    "imp": [
+      {
+        "id": "some_test_ad",
+        "banner": {
+          "w": 90,
+          "h": 728
+        },
+        "ext": {
+          "bidder": {
+            "siteId": "123"
+          }
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "//host",
+        "body": {
+          "id": "some_test_auction",
+          "imp": [
+            {
+              "id": "some_test_ad",
+              "banner": {
+                "h": 728,
+                "w": 90
+              },
+              "ext": {
+                "siteId": "123"
+              }
+            }
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "some_test_auction",
+          "seatbid": [
+          ],
+          "cur": "USD"
+        }
+      }
+    }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/valueimpression/valueimpressiontest/supplemental/invalid-response-unmarshall-error.json b/adapters/valueimpression/valueimpressiontest/supplemental/invalid-response-unmarshall-error.json
new file mode 100644
index 00000000..c854548b
--- /dev/null
+++ b/adapters/valueimpression/valueimpressiontest/supplemental/invalid-response-unmarshall-error.json
@@ -0,0 +1,66 @@
+{
+    "mockBidRequest": {
+        "id": "some_test_auction",
+        "imp": [
+            {
+                "id": "some_test_ad",
+                "banner": {
+                    "w": 90,
+                    "h": 728
+                },
+                "ext": {
+                    "bidder": {
+                        "siteId": "123"
+                    }
+                }
+            }
+        ]
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "//host",
+                "body": {
+                    "id": "some_test_auction",
+                    "imp": [
+                        {
+                            "id": "some_test_ad",
+                            "banner": {
+                                "h": 728,
+                                "w": 90
+                            },
+                            "ext": {
+                                "siteId": "123"
+                            }
+                        }
+                    ]
+                }
+            },
+            "mockResponse": {
+                "status": 200,
+                "body": {
+                    "id": "some_test_auction",
+                    "seatbid": [
+                        {
+                            "bid": [
+                                {
+                                    "id": "uuid",
+                                    "impid": "some_test_ad",
+                                    "w": "728",
+                                    "h": 90
+                                }
+                            ]
+                        }
+                    ],
+                    "cur": "USD"
+                }
+            }
+        }
+    ],
+    "expectedMakeBidsErrors": [
+        {
+            "value": "json: cannot unmarshal string into Go struct field Bid(\\.seatbid\\.bid)?\\.w of type uint64",
+            "comparison": "regex"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/adapters/valueimpression/valueimpressiontest/supplemental/no-imps-in-request.json b/adapters/valueimpression/valueimpressiontest/supplemental/no-imps-in-request.json
new file mode 100644
index 00000000..274a3422
--- /dev/null
+++ b/adapters/valueimpression/valueimpressiontest/supplemental/no-imps-in-request.json
@@ -0,0 +1,18 @@
+{
+    "mockBidRequest": {
+        "id": "some_test_auction",
+        "imp": [
+        ],
+        "site": {
+          "domain": "www.publisher.com",
+          "page": "http://www.publisher.com/awesome/site?with=some&parameters=here"
+        }
+      },
+
+      "expectedMakeRequestsErrors": [
+        {
+          "value": "No Imps in Bid Request",
+          "comparison": "literal"
+        }
+      ]
+}
\ No newline at end of file
diff --git a/adapters/valueimpression/valueimpressiontest/supplemental/server-error-code.json b/adapters/valueimpression/valueimpressiontest/supplemental/server-error-code.json
new file mode 100644
index 00000000..ea31fdc2
--- /dev/null
+++ b/adapters/valueimpression/valueimpressiontest/supplemental/server-error-code.json
@@ -0,0 +1,53 @@
+{
+    "mockBidRequest": {
+      "id": "some_test_auction",
+      "imp": [
+        {
+          "id": "test-imp-id",
+          "banner": {
+            "w": 600,
+            "h": 300
+          },
+          "ext": {
+            "bidder": {
+              "siteId": "123"
+            }
+          }
+        }
+      ]
+    },
+
+    "httpCalls": [
+      {
+        "expectedRequest": {
+          "uri": "//host",
+          "body": {
+            "id": "some_test_auction",
+            "imp": [
+              {
+                "id": "test-imp-id",
+                "banner": {
+                  "w": 600,
+                  "h": 300
+                },
+                "ext": {
+                  "siteId": "123"
+                }
+              }
+            ]
+          }
+        },
+        "mockResponse": {
+          "status": 500,
+          "body": {}
+        }
+      }
+    ],
+
+    "expectedMakeBidsErrors": [
+      {
+        "value": "Bad server response: HTTP status 500",
+        "comparison": "literal"
+      }
+    ]
+  }
diff --git a/adapters/valueimpression/valueimpressiontest/supplemental/server-no-content.json b/adapters/valueimpression/valueimpressiontest/supplemental/server-no-content.json
new file mode 100644
index 00000000..85633201
--- /dev/null
+++ b/adapters/valueimpression/valueimpressiontest/supplemental/server-no-content.json
@@ -0,0 +1,45 @@
+{
+    "mockBidRequest": {
+    "id": "some_test_auction",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+            "w": 300,
+            "h": 250
+        },
+        "ext": {
+          "bidder": {
+            "siteId": "123"
+          }
+        }
+      }
+    ]
+  },
+
+    "httpCalls": [
+      {
+        "expectedRequest": {
+          "uri": "//host",
+          "body": {
+            "id": "some_test_auction",
+            "imp": [
+              {
+                "id": "test-imp-id",
+                "banner": {
+                  "w": 300,
+                  "h": 250
+                },
+                "ext": {
+                  "siteId": "123"
+                }
+              }
+            ]
+          }
+        },
+        "mockResponse": {
+          "status": 204
+        }
+      }
+    ]
+    }
diff --git a/adapters/valueimpression/valueimpressiontest/supplemental/wrong-impression-ext.json b/adapters/valueimpression/valueimpressiontest/supplemental/wrong-impression-ext.json
new file mode 100644
index 00000000..13514ac8
--- /dev/null
+++ b/adapters/valueimpression/valueimpressiontest/supplemental/wrong-impression-ext.json
@@ -0,0 +1,26 @@
+{
+  "mockBidRequest": {
+    "id": "rqid",
+    "imp": [
+      {
+        "id": "impid",
+        "video": {
+         "w": 100,
+          "h": 200
+        },
+        "ext": {
+          "bidder": {
+            "siteId": 123
+          }
+        }
+      }
+    ]
+  },
+
+  "expectedMakeRequestsErrors": [
+    {
+      "value": "json: cannot unmarshal number into Go struct field ExtImpValueImpression.siteId of type string",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/valueimpression/valueimpressiontest/supplemental/wrong-impression-mapping.json b/adapters/valueimpression/valueimpressiontest/supplemental/wrong-impression-mapping.json
new file mode 100644
index 00000000..ef4d3a75
--- /dev/null
+++ b/adapters/valueimpression/valueimpressiontest/supplemental/wrong-impression-mapping.json
@@ -0,0 +1,75 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "video": {
+          "w": 900,
+          "h": 250,
+          "mimes": [
+            "video/x-flv",
+            "video/mp4"
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "siteId": "123"
+          }
+        }
+      }
+    ]
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "//host",
+        "body": {
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id":"test-imp-id",
+              "video": {
+                "w": 900,
+                "h": 250,
+                "mimes": [
+                  "video/x-flv",
+                  "video/mp4"
+                ]
+              },
+              "ext": {
+                "siteId": "123"
+              }
+            }
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "test-bid-id",
+                  "impid": "BOGUS-IMPID",
+                  "price": 3.5,
+                  "w": 900,
+                  "h": 250
+                }
+              ]
+            }
+          ]
+        }
+      }
+    }
+  ],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "bid id='test-bid-id' could not find valid impid='BOGUS-IMPID'",
+      "comparison": "regex"
+    }
+]
+}
\ No newline at end of file
diff --git a/endpoints/openrtb2/amp_auction_test.go b/endpoints/openrtb2/amp_auction_test.go
index c62a6a71..39d1e13c 100644
--- a/endpoints/openrtb2/amp_auction_test.go
+++ b/endpoints/openrtb2/amp_auction_test.go
@@ -902,7 +902,7 @@ type mockAmpExchange struct {
 	lastRequest *openrtb.BidRequest
 }
 
-func (m *mockAmpExchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, ids exchange.IdFetcher, labels pbsmetrics.Labels, categoriesFetcher *stored_requests.CategoryFetcher) (*openrtb.BidResponse, error) {
+func (m *mockAmpExchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, ids exchange.IdFetcher, labels pbsmetrics.Labels, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) {
 	m.lastRequest = bidRequest
 
 	response := &openrtb.BidResponse{
diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go
index 89f0fa25..74a70c69 100644
--- a/endpoints/openrtb2/auction_test.go
+++ b/endpoints/openrtb2/auction_test.go
@@ -602,7 +602,7 @@ func TestStoredRequests(t *testing.T) {
 	// NewMetrics() will create a new go_metrics MetricsEngine, bypassing the need for a crafted configuration set to support it.
 	// As a side effect this gives us some coverage of the go_metrics piece of the metrics engine.
 	theMetrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{})
-	edep := &endpointDeps{&nobidExchange{}, newParamsValidator(t), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, theMetrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, false, []byte{}, openrtb_ext.BidderMap}
+	edep := &endpointDeps{&nobidExchange{}, newParamsValidator(t), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, theMetrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, false, []byte{}, openrtb_ext.BidderMap, nil}
 
 	for i, requestData := range testStoredRequests {
 		newRequest, errList := edep.processStoredRequests(context.Background(), json.RawMessage(requestData))
@@ -638,6 +638,7 @@ func TestOversizedRequest(t *testing.T) {
 		false,
 		[]byte{},
 		openrtb_ext.BidderMap,
+		nil,
 	}
 
 	req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(reqBody))
@@ -670,6 +671,7 @@ func TestRequestSizeEdgeCase(t *testing.T) {
 		false,
 		[]byte{},
 		openrtb_ext.BidderMap,
+		nil,
 	}
 
 	req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(reqBody))
@@ -807,6 +809,7 @@ func TestDisabledBidder(t *testing.T) {
 		false,
 		[]byte{},
 		openrtb_ext.BidderMap,
+		nil,
 	}
 
 	req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(reqBody))
@@ -840,6 +843,7 @@ func TestValidateImpExtDisabledBidder(t *testing.T) {
 		false,
 		[]byte{},
 		openrtb_ext.BidderMap,
+		nil,
 	}
 	errs := deps.validateImpExt(imp, nil, 0)
 	assert.JSONEq(t, `{"appnexus":{"placement_id":555}}`, string(imp.Ext))
@@ -878,6 +882,7 @@ func TestCurrencyTrunc(t *testing.T) {
 		false,
 		[]byte{},
 		openrtb_ext.BidderMap,
+		nil,
 	}
 
 	ui := uint64(1)
@@ -919,6 +924,7 @@ func TestCCPAInvalidValueWarning(t *testing.T) {
 		false,
 		[]byte{},
 		openrtb_ext.BidderMap,
+		nil,
 	}
 
 	ui := uint64(1)
@@ -953,7 +959,7 @@ type nobidExchange struct {
 	gotRequest *openrtb.BidRequest
 }
 
-func (e *nobidExchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, ids exchange.IdFetcher, labels pbsmetrics.Labels, categoriesFetcher *stored_requests.CategoryFetcher) (*openrtb.BidResponse, error) {
+func (e *nobidExchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, ids exchange.IdFetcher, labels pbsmetrics.Labels, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) {
 	e.gotRequest = bidRequest
 	return &openrtb.BidResponse{
 		ID:    bidRequest.ID,
@@ -964,7 +970,7 @@ func (e *nobidExchange) HoldAuction(ctx context.Context, bidRequest *openrtb.Bid
 
 type brokenExchange struct{}
 
-func (e *brokenExchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, ids exchange.IdFetcher, labels pbsmetrics.Labels, categoriesFetcher *stored_requests.CategoryFetcher) (*openrtb.BidResponse, error) {
+func (e *brokenExchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, ids exchange.IdFetcher, labels pbsmetrics.Labels, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) {
 	return nil, errors.New("Critical, unrecoverable error.")
 }
 
@@ -1324,7 +1330,7 @@ type mockExchange struct {
 	lastRequest *openrtb.BidRequest
 }
 
-func (m *mockExchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, ids exchange.IdFetcher, labels pbsmetrics.Labels, categoriesFetcher *stored_requests.CategoryFetcher) (*openrtb.BidResponse, error) {
+func (m *mockExchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, ids exchange.IdFetcher, labels pbsmetrics.Labels, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) {
 	m.lastRequest = bidRequest
 	return &openrtb.BidResponse{
 		SeatBid: []openrtb.SeatBid{{
diff --git a/endpoints/openrtb2/video_auction_test.go b/endpoints/openrtb2/video_auction_test.go
index a5ad62c9..0199b43f 100644
--- a/endpoints/openrtb2/video_auction_test.go
+++ b/endpoints/openrtb2/video_auction_test.go
@@ -17,6 +17,7 @@ import (
 	"github.com/prebid/prebid-server/exchange"
 	"github.com/prebid/prebid-server/openrtb_ext"
 	"github.com/prebid/prebid-server/pbsmetrics"
+	"github.com/prebid/prebid-server/prebid_cache_client"
 	"github.com/prebid/prebid-server/stored_requests"
 	"github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher"
 	metrics "github.com/rcrowley/go-metrics"
@@ -171,6 +172,112 @@ func TestCreateBidExtensionExactDurTrueNoPriceRange(t *testing.T) {
 	assert.Equal(t, resExt.Prebid.Targeting.PriceGranularity, openrtb_ext.PriceGranularityFromString("med"), "Price granularity is incorrect")
 }
 
+func TestVideoEndpointDebugQueryTrue(t *testing.T) {
+	ex := &mockExchangeVideo{
+		cache: &mockCacheClient{},
+	}
+	reqData, err := ioutil.ReadFile("sample-requests/video/video_valid_sample.json")
+	if err != nil {
+		t.Fatalf("Failed to fetch a valid request: %v", err)
+	}
+	reqBody := string(getRequestPayload(t, reqData))
+	req := httptest.NewRequest("POST", "/openrtb2/video?debug=true", strings.NewReader(reqBody))
+	recorder := httptest.NewRecorder()
+
+	deps := mockDeps(t, ex)
+	deps.VideoAuctionEndpoint(recorder, req, nil)
+
+	if ex.lastRequest == nil {
+		t.Fatalf("The request never made it into the Exchange.")
+	}
+	if !ex.cache.called {
+		t.Fatalf("Cache was not called when it should have been")
+	}
+
+	respBytes := recorder.Body.Bytes()
+	resp := &openrtb_ext.BidResponseVideo{}
+	if err := json.Unmarshal(respBytes, resp); err != nil {
+		t.Fatalf("Unable to umarshal response.")
+	}
+
+	assert.Len(t, ex.lastRequest.Imp, 11, "Incorrect number of impressions in request")
+	assert.Equal(t, string(ex.lastRequest.Site.Page), "prebid.com", "Incorrect site page in request")
+	assert.Equal(t, ex.lastRequest.Site.Content.Series, "TvName", "Incorrect site content series in request")
+
+	assert.Len(t, resp.AdPods, 5, "Incorrect number of Ad Pods in response")
+	assert.Len(t, resp.AdPods[0].Targeting, 4, "Incorrect Targeting data in response")
+	assert.Len(t, resp.AdPods[1].Targeting, 3, "Incorrect Targeting data in response")
+	assert.Len(t, resp.AdPods[2].Targeting, 5, "Incorrect Targeting data in response")
+	assert.Len(t, resp.AdPods[3].Targeting, 1, "Incorrect Targeting data in response")
+	assert.Len(t, resp.AdPods[4].Targeting, 3, "Incorrect Targeting data in response")
+
+	assert.Equal(t, resp.AdPods[4].Targeting[0].HbPbCatDur, "20.00_395_30s", "Incorrect number of Ad Pods in response")
+}
+
+func TestVideoEndpointDebugQueryFalse(t *testing.T) {
+	ex := &mockExchangeVideo{
+		cache: &mockCacheClient{},
+	}
+	reqData, err := ioutil.ReadFile("sample-requests/video/video_valid_sample.json")
+	if err != nil {
+		t.Fatalf("Failed to fetch a valid request: %v", err)
+	}
+	reqBody := string(getRequestPayload(t, reqData))
+	req := httptest.NewRequest("POST", "/openrtb2/video?debug=123", strings.NewReader(reqBody))
+	recorder := httptest.NewRecorder()
+
+	deps := mockDeps(t, ex)
+	deps.VideoAuctionEndpoint(recorder, req, nil)
+
+	if ex.lastRequest == nil {
+		t.Fatalf("The request never made it into the Exchange.")
+	}
+	if ex.cache.called {
+		t.Fatalf("Cache was called when it shouldn't have been")
+	}
+
+	respBytes := recorder.Body.Bytes()
+	resp := &openrtb_ext.BidResponseVideo{}
+	if err := json.Unmarshal(respBytes, resp); err != nil {
+		t.Fatalf("Unable to umarshal response.")
+	}
+
+	assert.Len(t, ex.lastRequest.Imp, 11, "Incorrect number of impressions in request")
+	assert.Equal(t, string(ex.lastRequest.Site.Page), "prebid.com", "Incorrect site page in request")
+	assert.Equal(t, ex.lastRequest.Site.Content.Series, "TvName", "Incorrect site content series in request")
+
+	assert.Len(t, resp.AdPods, 5, "Incorrect number of Ad Pods in response")
+	assert.Len(t, resp.AdPods[0].Targeting, 4, "Incorrect Targeting data in response")
+	assert.Len(t, resp.AdPods[1].Targeting, 3, "Incorrect Targeting data in response")
+	assert.Len(t, resp.AdPods[2].Targeting, 5, "Incorrect Targeting data in response")
+	assert.Len(t, resp.AdPods[3].Targeting, 1, "Incorrect Targeting data in response")
+	assert.Len(t, resp.AdPods[4].Targeting, 3, "Incorrect Targeting data in response")
+
+	assert.Equal(t, resp.AdPods[4].Targeting[0].HbPbCatDur, "20.00_395_30s", "Incorrect number of Ad Pods in response")
+}
+
+func TestVideoEndpointDebugError(t *testing.T) {
+	ex := &mockExchangeVideo{
+		cache: &mockCacheClient{},
+	}
+	reqData, err := ioutil.ReadFile("sample-requests/video/video_invalid_sample.json")
+	if err != nil {
+		t.Fatalf("Failed to fetch a valid request: %v", err)
+	}
+	reqBody := string(getRequestPayload(t, reqData))
+	req := httptest.NewRequest("POST", "/openrtb2/video?debug=true", strings.NewReader(reqBody))
+	recorder := httptest.NewRecorder()
+
+	deps := mockDeps(t, ex)
+	deps.VideoAuctionEndpoint(recorder, req, nil)
+
+	if !ex.cache.called {
+		t.Fatalf("Cache was not called when it should have been")
+	}
+
+	assert.Equal(t, recorder.Code, 500, "Should catch error in request")
+}
+
 func TestVideoEndpointNoPods(t *testing.T) {
 	ex := &mockExchangeVideo{}
 	reqData, err := ioutil.ReadFile("sample-requests/video/video_invalid_sample.json")
@@ -714,7 +821,7 @@ func TestHandleError(t *testing.T) {
 	recorder := httptest.NewRecorder()
 	err1 := errors.New("Error for testing handleError 1")
 	err2 := errors.New("Error for testing handleError 2")
-	handleError(&labels, recorder, []error{err1, err2}, &vo)
+	handleError(&labels, recorder, []error{err1, err2}, &vo, nil)
 
 	assert.Equal(t, pbsmetrics.RequestStatusErr, labels.RequestStatus, "labels.RequestStatus should indicate an error")
 	assert.Equal(t, 500, recorder.Code, "Error status should be written to writer")
@@ -820,6 +927,41 @@ func TestParseVideoRequestWithoutUserAgentAndEmptyHeader(t *testing.T) {
 
 }
 
+func TestHandleErrorDebugLog(t *testing.T) {
+	vo := analytics.VideoObject{
+		Status: 200,
+		Errors: make([]error, 0),
+	}
+
+	labels := pbsmetrics.Labels{
+		Source:        pbsmetrics.DemandUnknown,
+		RType:         pbsmetrics.ReqTypeVideo,
+		PubID:         pbsmetrics.PublisherUnknown,
+		Browser:       "test browser",
+		CookieFlag:    pbsmetrics.CookieFlagUnknown,
+		RequestStatus: pbsmetrics.RequestStatusOK,
+	}
+
+	recorder := httptest.NewRecorder()
+	err1 := errors.New("Error for testing handleError 1")
+	err2 := errors.New("Error for testing handleError 2")
+	debugLog := exchange.DebugLog{
+		EnableDebug: true,
+		CacheType:   prebid_cache_client.TypeXML,
+		Data:        "test debug data",
+		TTL:         int64(3600),
+	}
+	handleError(&labels, recorder, []error{err1, err2}, &vo, &debugLog)
+
+	assert.Equal(t, pbsmetrics.RequestStatusErr, labels.RequestStatus, "labels.RequestStatus should indicate an error")
+	assert.Equal(t, 500, recorder.Code, "Error status should be written to writer")
+	assert.Equal(t, 500, vo.Status, "Analytics object should have error status")
+	assert.Equal(t, 3, len(vo.Errors), "New errors including debug cache ID should be appended to Analytics object Errors")
+	assert.Equal(t, "Error for testing handleError 1", vo.Errors[0].Error(), "Error in Analytics object should have test error message for first error")
+	assert.Equal(t, "Error for testing handleError 2", vo.Errors[1].Error(), "Error in Analytics object should have test error message for second error")
+	assert.NotEmpty(t, debugLog.CacheKey, "DebugLog CacheKey value should have been set")
+}
+
 func mockDepsWithMetrics(t *testing.T, ex *mockExchangeVideo) (*endpointDeps, *pbsmetrics.Metrics, *mockAnalyticsModule) {
 	theMetrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{})
 	mockModule := &mockAnalyticsModule{}
@@ -836,6 +978,7 @@ func mockDepsWithMetrics(t *testing.T, ex *mockExchangeVideo) (*endpointDeps, *p
 		false,
 		[]byte{},
 		openrtb_ext.BidderMap,
+		nil,
 	}
 
 	return edep, theMetrics, mockModule
@@ -875,11 +1018,27 @@ func mockDeps(t *testing.T, ex *mockExchangeVideo) *endpointDeps {
 		false,
 		[]byte{},
 		openrtb_ext.BidderMap,
+		ex.cache,
 	}
 
 	return edep
 }
 
+type mockCacheClient struct {
+	called bool
+}
+
+func (m *mockCacheClient) PutJson(ctx context.Context, values []prebid_cache_client.Cacheable) ([]string, []error) {
+	if !m.called {
+		m.called = true
+	}
+	return []string{}, []error{}
+}
+
+func (m *mockCacheClient) GetExtCacheData() (string, string) {
+	return "", ""
+}
+
 type mockVideoStoredReqFetcher struct {
 }
 
@@ -889,10 +1048,14 @@ func (cf mockVideoStoredReqFetcher) FetchRequests(ctx context.Context, requestID
 
 type mockExchangeVideo struct {
 	lastRequest *openrtb.BidRequest
+	cache       *mockCacheClient
 }
 
-func (m *mockExchangeVideo) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, ids exchange.IdFetcher, labels pbsmetrics.Labels, categoriesFetcher *stored_requests.CategoryFetcher) (*openrtb.BidResponse, error) {
+func (m *mockExchangeVideo) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, ids exchange.IdFetcher, labels pbsmetrics.Labels, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) {
 	m.lastRequest = bidRequest
+	if debugLog != nil && debugLog.EnableDebug {
+		m.cache.called = true
+	}
 	ext := []byte(`{"prebid":{"targeting":{"hb_bidder":"appnexus","hb_pb":"20.00","hb_pb_cat_dur":"20.00_395_30s","hb_size":"1x1", "hb_uuid":"837ea3b7-5598-4958-8c45-8e9ef2bf7cc1"},"type":"video"},"bidder":{"appnexus":{"brand_id":1,"auction_id":7840037870526938650,"bidder_id":2,"bid_ad_type":1,"creative_info":{"video":{"duration":30,"mimes":["video\/mp4"]}}}}}`)
 	return &openrtb.BidResponse{
 		SeatBid: []openrtb.SeatBid{{
diff --git a/exchange/auction_test.go b/exchange/auction_test.go
index ea19732d..d23ff03e 100644
--- a/exchange/auction_test.go
+++ b/exchange/auction_test.go
@@ -188,7 +188,7 @@ func runCacheSpec(t *testing.T, fileDisplayName string, specData *cacheSpec) {
 		winningBidsByBidder: winningBidsByBidder,
 		roundedPrices:       roundedPrices,
 	}
-	_ = testAuction.doCache(ctx, cache, targData, &specData.BidRequest, 60, &specData.DefaultTTLs, bidCategory)
+	_ = testAuction.doCache(ctx, cache, targData, &specData.BidRequest, 60, &specData.DefaultTTLs, bidCategory, &specData.DebugLog)
 
 	if len(specData.ExpectedCacheables) > len(cache.items) {
 		t.Errorf("%s:  [CACHE_ERROR] Less elements were cached than expected \n", fileDisplayName)
@@ -232,6 +232,7 @@ type cacheSpec struct {
 	TargetDataIncludeBidderKeys bool                            `json:"targetDataIncludeBidderKeys"`
 	TargetDataIncludeCacheBids  bool                            `json:"targetDataIncludeCacheBids"`
 	TargetDataIncludeCacheVast  bool                            `json:"targetDataIncludeCacheVast"`
+	DebugLog                    DebugLog                        `json:"debugLog,omitempty"`
 }
 
 type pbsBid struct {
diff --git a/exchange/cachetest/debuglog_disabled.json b/exchange/cachetest/debuglog_disabled.json
new file mode 100644
index 00000000..675488c0
--- /dev/null
+++ b/exchange/cachetest/debuglog_disabled.json
@@ -0,0 +1,54 @@
+{
+    "debugLog": {
+        "EnableDebug": false,
+        "CacheType": "xml",
+        "TTL": 3600,
+        "Data": "test debug data"
+    },
+    "bidRequest": {
+        "imp": [{
+            "id":  "oneImp",
+            "exp":   600
+        }, {
+            "id":  "twoImp"
+        }]
+    },
+    "pbsBids": [{
+        "bid":{
+            "id": "bidOne",
+            "impid": "oneImp",
+            "price": 7.64
+        },
+        "bidType": "video",
+        "bidder": "appnexus"
+    }, {
+        "bid": {
+            "id": "bidTwo",
+            "impid": "twoImp",
+            "price": 5.64
+        },
+        "bidType": "video",
+        "bidder": "pubmatic"
+    }],
+    "expectedCacheables": [
+        {
+            "Type": "json",
+            "TTLSeconds": 660,
+            "Data": "{\"id\": \"bidOne\", \"impid\": \"oneImp\", \"price\": 7.64}"
+        }, {
+            "Type": "json",
+            "TTLSeconds": 3660,
+            "Data": "{\"id\": \"bidTwo\", \"impid\": \"twoImp\", \"price\": 5.64}"
+        }
+    ],
+    "defaultTTLs": {
+        "banner": 300,
+        "video": 3600,
+        "audio": 1800,
+        "native": 300
+    },
+    "targetDataIncludeWinners":true,
+    "targetDataIncludeBidderKeys":true,
+    "targetDataIncludeCacheBids":true,
+    "targetDataIncludeCacheVast":false
+}
diff --git a/exchange/cachetest/debuglog_enabled.json b/exchange/cachetest/debuglog_enabled.json
new file mode 100644
index 00000000..d4486558
--- /dev/null
+++ b/exchange/cachetest/debuglog_enabled.json
@@ -0,0 +1,58 @@
+{    
+    "debugLog": {
+        "EnableDebug": true,
+        "CacheType": "xml",
+        "TTL": 3600,
+        "Data": "test debug data"
+    },
+    "bidRequest": {
+        "imp": [{
+            "id":  "oneImp",
+            "exp":   600
+        }, {
+            "id":  "twoImp"
+        }]
+    },
+    "pbsBids": [{
+        "bid":{
+            "id": "bidOne",
+            "impid": "oneImp",
+            "price": 7.64
+        },
+        "bidType": "video",
+        "bidder": "appnexus"
+    }, {
+        "bid": {
+            "id": "bidTwo",
+            "impid": "twoImp",
+            "price": 5.64
+        },
+        "bidType": "video",
+        "bidder": "pubmatic"
+    }],
+    "expectedCacheables": [
+        {
+            "Type": "json",
+            "TTLSeconds": 660,
+            "Data": "{\"id\": \"bidOne\", \"impid\": \"oneImp\", \"price\": 7.64}"
+        }, {
+            "Type": "json",
+            "TTLSeconds": 3660,
+            "Data": "{\"id\": \"bidTwo\", \"impid\": \"twoImp\", \"price\": 5.64}"
+        }, {
+            "Type": "xml",
+            "TTLSeconds": 3600,
+            "Data": "test debug data"
+        }
+    ],
+    "defaultTTLs": {
+        "banner": 300,
+        "video": 3600,
+        "audio": 1800,
+        "native": 300
+    },
+    "targetDataIncludeWinners":true,
+    "targetDataIncludeBidderKeys":true,
+    "targetDataIncludeCacheBids":true,
+    "targetDataIncludeCacheVast":false
+}
diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go
index 0a64bce0..7217e609 100644
--- a/exchange/exchange_test.go
+++ b/exchange/exchange_test.go
@@ -127,7 +127,7 @@ func TestCharacterEscape(t *testing.T) {
 	var errList []error
 
 	/* 	4) Build bid response 									*/
-	bidResp, err := e.buildBidResponse(context.Background(), liveAdapters, adapterBids, bidRequest, resolvedRequest, adapterExtra, nil, errList)
+	bidResp, err := e.buildBidResponse(context.Background(), liveAdapters, adapterBids, bidRequest, resolvedRequest, adapterExtra, nil, nil, errList)
 
 	/* 	5) Assert we have no errors and one '&' character as we are supposed to 	*/
 	if err != nil {
@@ -279,7 +279,7 @@ func TestGetBidCacheInfo(t *testing.T) {
 	var errList []error
 
 	/* 	4) Build bid response 									*/
-	bid_resp, err := e.buildBidResponse(context.Background(), liveAdapters, adapterBids, bidRequest, resolvedRequest, adapterExtra, auc, errList)
+	bid_resp, err := e.buildBidResponse(context.Background(), liveAdapters, adapterBids, bidRequest, resolvedRequest, adapterExtra, auc, nil, errList)
 
 	/* 	5) Assert we have no errors and the bid response we expected*/
 	assert.NoError(t, err, "[TestGetBidCacheInfo] buildBidResponse() threw an error")
@@ -450,7 +450,7 @@ func TestBidResponseCurrency(t *testing.T) {
 
 	// Run tests
 	for i := range testCases {
-		actualBidResp, err := e.buildBidResponse(context.Background(), liveAdapters, testCases[i].adapterBids, bidRequest, resolvedRequest, adapterExtra, nil, errList)
+		actualBidResp, err := e.buildBidResponse(context.Background(), liveAdapters, testCases[i].adapterBids, bidRequest, resolvedRequest, adapterExtra, nil, nil, errList)
 		assert.NoError(t, err, fmt.Sprintf("[TEST_FAILED] e.buildBidResponse resturns error in test: %s Error message: %s \n", testCases[i].description, err))
 		assert.Equalf(t, testCases[i].expectedBidResponse, actualBidResp, fmt.Sprintf("[TEST_FAILED] Objects must be equal for test: %s \n Expected: >>%s<< \n Actual: >>%s<< ", testCases[i].description, testCases[i].expectedBidResponse.Ext, actualBidResp.Ext))
 	}
@@ -489,7 +489,7 @@ func TestRaceIntegration(t *testing.T) {
 	}
 	theMetrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{})
 	ex := NewExchange(server.Client(), &wellBehavedCache{}, cfg, theMetrics, adapters.ParseBidderInfos(cfg.Adapters, "../static/bidder-info", openrtb_ext.BidderList()), gdpr.AlwaysAllow{}, currencies.NewRateConverterDefault())
-	_, err := ex.HoldAuction(context.Background(), newRaceCheckingRequest(t), &emptyUsersync{}, pbsmetrics.Labels{}, &categoriesFetcher)
+	_, err := ex.HoldAuction(context.Background(), newRaceCheckingRequest(t), &emptyUsersync{}, pbsmetrics.Labels{}, &categoriesFetcher, nil)
 	if err != nil {
 		t.Errorf("HoldAuction returned unexpected error: %v", err)
 	}
@@ -666,7 +666,7 @@ func TestPanicRecoveryHighLevel(t *testing.T) {
 	if error != nil {
 		t.Errorf("Failed to create a category Fetcher: %v", error)
 	}
-	_, err := e.HoldAuction(context.Background(), request, &emptyUsersync{}, pbsmetrics.Labels{}, &categoriesFetcher)
+	_, err := e.HoldAuction(context.Background(), request, &emptyUsersync{}, pbsmetrics.Labels{}, &categoriesFetcher, nil)
 	if err != nil {
 		t.Errorf("HoldAuction returned unexpected error: %v", err)
 	}
@@ -732,7 +732,11 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) {
 	if error != nil {
 		t.Errorf("Failed to create a category Fetcher: %v", error)
 	}
-	bid, err := ex.HoldAuction(context.Background(), &spec.IncomingRequest.OrtbRequest, mockIdFetcher(spec.IncomingRequest.Usersyncs), pbsmetrics.Labels{}, &categoriesFetcher)
+	debugLog := &DebugLog{}
+	if spec.DebugLog != nil {
+		*debugLog = *spec.DebugLog
+	}
+	bid, err := ex.HoldAuction(context.Background(), &spec.IncomingRequest.OrtbRequest, mockIdFetcher(spec.IncomingRequest.Usersyncs), pbsmetrics.Labels{}, &categoriesFetcher, debugLog)
 	responseTimes := extractResponseTimes(t, filename, bid)
 	for _, bidderName := range biddersInAuction {
 		if _, ok := responseTimes[bidderName]; !ok {
@@ -751,6 +755,17 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) {
 			}
 		}
 	}
+	if spec.DebugLog != nil {
+		if spec.DebugLog.EnableDebug {
+			if len(debugLog.Data) <= len(spec.DebugLog.Data) {
+				t.Errorf("%s: DebugLog was not modified when it should have been", filename)
+			}
+		} else {
+			if !strings.EqualFold(spec.DebugLog.Data, debugLog.Data) {
+				t.Errorf("%s: DebugLog was modified when it shouldn't have been", filename)
+			}
+		}
+	}
 }
 
 func findBiddersInAuction(t *testing.T, context string, req *openrtb.BidRequest) []string {
@@ -1588,6 +1603,7 @@ type exchangeSpec struct {
 	OutgoingRequests map[string]*bidderSpec `json:"outgoingRequests"`
 	Response         exchangeResponse       `json:"response,omitempty"`
 	EnforceCCPA      bool                   `json:"enforceCcpa"`
+	DebugLog         *DebugLog              `json:"debuglog,omitempty"`
 }
 
 type exchangeRequest struct {
diff --git a/exchange/exchangetest/debuglog_disabled.json b/exchange/exchangetest/debuglog_disabled.json
new file mode 100644
index 00000000..0c24c121
--- /dev/null
+++ b/exchange/exchangetest/debuglog_disabled.json
@@ -0,0 +1,161 @@
+{
+    "debugLog": {
+        "EnableDebug": false,
+        "CacheType": "xml",
+        "TTL": 3600,
+        "Data": "test debug data"
+    },
+    "incomingRequest": {
+      "ortbRequest": {
+        "id": "some-request-id",
+        "site": {
+          "page": "test.somepage.com"
+        },
+        "imp": [
+          {
+            "id": "my-imp-id",
+            "video": {
+              "mimes": ["video/mp4"]
+            },
+            "ext": {
+              "appnexus": {
+                "placementId": 1
+              }
+            }
+          },
+          {
+            "id": "imp-id-2",
+            "video": {
+              "mimes": ["video/mp4"]
+            },
+            "ext": {
+              "appnexus": {
+                "placementId": 1
+              }
+            }
+          }
+        ],
+        "ext": {
+          "prebid": {
+            "targeting": {
+              "includebrandcategory": {
+                "primaryadserver": 1,
+                "publisher":"",
+                "withcategory": true
+              }
+            }
+          }
+        }
+      },
+      "usersyncs": {
+        "appnexus": "123"
+      }
+    },
+    "outgoingRequests": {
+      "appnexus": {
+        "mockResponse": {
+          "pbsSeatBid": {
+            "pbsBids": [
+              {
+                "ortbBid": {
+                  "id": "apn-bid",
+                  "impid": "my-imp-id",
+                  "price": 0.3,
+                  "w": 200,
+                  "h": 250,
+                  "crid": "creative-1",
+                  "cat": ["IAB1-1"]
+                },
+                "bidType": "video",
+                "bidVideo": {
+                  "duration": 30,
+                  "PrimaryCategory": ""
+                }
+              },
+              {
+                "ortbBid": {
+                  "id": "apn-other-bid",
+                  "impid": "imp-id-2",
+                  "price": 0.6,
+                  "w": 300,
+                  "h": 500,
+                  "crid": "creative-3",
+                  "cat": ["IAB1-2"]
+                },
+                "bidType": "video"
+              }
+            ]
+          }
+        }
+      }
+    },
+    "response": {
+      "bids": {
+        "id": "some-request-id",
+        "seatbid": [
+          {
+            "seat": "appnexus",
+            "bid": [
+              {
+                "id": "apn-bid",
+                "impid": "my-imp-id",
+                "price": 0.3,
+                "w": 200,
+                "h": 250,
+                "crid": "creative-1",
+                "cat": ["IAB1-1"],
+                "ext": {
+                  "prebid": {
+                    "type": "video",
+                    "targeting": {
+                       "hb_bidder": "appnexus",
+                       "hb_bidder_appnexus": "appnexus",
+                       "hb_cache_host": "www.pbcserver.com",
+                       "hb_cache_host_appnex": "www.pbcserver.com",
+                       "hb_cache_path": "/pbcache/endpoint",
+                       "hb_cache_path_appnex": "/pbcache/endpoint",
+                       "hb_pb": "0.20",
+                       "hb_pb_appnexus": "0.20",
+                       "hb_pb_cat_dur": "0.20_VideoGames_0s",
+                       "hb_pb_cat_dur_appnex": "0.20_VideoGames_0s",
+                       "hb_size": "200x250",
+                       "hb_size_appnexus": "200x250"
+                     }
+                  }
+                }
+              },
+              {
+                "cat": ["IAB1-2"],
+                "crid": "creative-3",
+                "ext": {
+                  "prebid": {
+                    "targeting": {
+                        "hb_bidder": "appnexus",
+                        "hb_bidder_appnexus": "appnexus",
+                        "hb_cache_host": "www.pbcserver.com",
+                        "hb_cache_host_appnex": "www.pbcserver.com",
+                        "hb_cache_path": "/pbcache/endpoint",
+                        "hb_cache_path_appnex": "/pbcache/endpoint",
+                        "hb_pb": "0.50",
+                        "hb_pb_appnexus": "0.50",
+                        "hb_pb_cat_dur": "0.50_HomeDecor_0s",
+                        "hb_pb_cat_dur_appnex": "0.50_HomeDecor_0s",
+                        "hb_size": "300x500",
+                        "hb_size_appnexus": "300x500"
+                      },
+                    "type": "video"
+                  }
+                },
+                "h": 500,
+                "id": "apn-other-bid",
+                "impid": "imp-id-2",
+                "price": 0.6,
+                "w": 300
+              }
+            ]
+          }
+        ]
+      }
+    }
+  }
+  
\ No newline at end of file
diff --git a/exchange/exchangetest/debuglog_enabled.json b/exchange/exchangetest/debuglog_enabled.json
new file mode 100644
index 00000000..281bf3a1
--- /dev/null
+++ b/exchange/exchangetest/debuglog_enabled.json
@@ -0,0 +1,161 @@
+{
+    "debugLog": {
+        "EnableDebug": true,
+        "CacheType": "xml",
+        "TTL": 3600,
+        "Data": "test debug data"
+    },
+    "incomingRequest": {
+      "ortbRequest": {
+        "id": "some-request-id",
+        "site": {
+          "page": "test.somepage.com"
+        },
+        "imp": [
+          {
+            "id": "my-imp-id",
+            "video": {
+              "mimes": ["video/mp4"]
+            },
+            "ext": {
+              "appnexus": {
+                "placementId": 1
+              }
+            }
+          },
+          {
+            "id": "imp-id-2",
+            "video": {
+              "mimes": ["video/mp4"]
+            },
+            "ext": {
+              "appnexus": {
+                "placementId": 1
+              }
+            }
+          }
+        ],
+        "ext": {
+          "prebid": {
+            "targeting": {
+              "includebrandcategory": {
+                "primaryadserver": 1,
+                "publisher":"",
+                "withcategory": true
+              }
+            }
+          }
+        }
+      },
+      "usersyncs": {
+        "appnexus": "123"
+      }
+    },
+    "outgoingRequests": {
+      "appnexus": {
+        "mockResponse": {
+          "pbsSeatBid": {
+            "pbsBids": [
+              {
+                "ortbBid": {
+                  "id": "apn-bid",
+                  "impid": "my-imp-id",
+                  "price": 0.3,
+                  "w": 200,
+                  "h": 250,
+                  "crid": "creative-1",
+                  "cat": ["IAB1-1"]
+                },
+                "bidType": "video",
+                "bidVideo": {
+                  "duration": 30,
+                  "PrimaryCategory": ""
+                }
+              },
+              {
+                "ortbBid": {
+                  "id": "apn-other-bid",
+                  "impid": "imp-id-2",
+                  "price": 0.6,
+                  "w": 300,
+                  "h": 500,
+                  "crid": "creative-3",
+                  "cat": ["IAB1-2"]
+                },
+                "bidType": "video"
+              }
+            ]
+          }
+        }
+      }
+    },
+    "response": {
+      "bids": {
+        "id": "some-request-id",
+        "seatbid": [
+          {
+            "seat": "appnexus",
+            "bid": [
+              {
+                "id": "apn-bid",
+                "impid": "my-imp-id",
+                "price": 0.3,
+                "w": 200,
+                "h": 250,
+                "crid": "creative-1",
+                "cat": ["IAB1-1"],
+                "ext": {
+                  "prebid": {
+                    "type": "video",
+                    "targeting": {
+                       "hb_bidder": "appnexus",
+                       "hb_bidder_appnexus": "appnexus",
+                       "hb_cache_host": "www.pbcserver.com",
+                       "hb_cache_host_appnex": "www.pbcserver.com",
+                       "hb_cache_path": "/pbcache/endpoint",
+                       "hb_cache_path_appnex": "/pbcache/endpoint",
+                       "hb_pb": "0.20",
+                       "hb_pb_appnexus": "0.20",
+                       "hb_pb_cat_dur": "0.20_VideoGames_0s",
+                       "hb_pb_cat_dur_appnex": "0.20_VideoGames_0s",
+                       "hb_size": "200x250",
+                       "hb_size_appnexus": "200x250"
+                     }
+                  }
+                }
+              },
+              {
+                "cat": ["IAB1-2"],
+                "crid": "creative-3",
+                "ext": {
+                  "prebid": {
+                    "targeting": {
+                        "hb_bidder": "appnexus",
+                        "hb_bidder_appnexus": "appnexus",
+                        "hb_cache_host": "www.pbcserver.com",
+                        "hb_cache_host_appnex": "www.pbcserver.com",
+                        "hb_cache_path": "/pbcache/endpoint",
+                        "hb_cache_path_appnex": "/pbcache/endpoint",
+                        "hb_pb": "0.50",
+                        "hb_pb_appnexus": "0.50",
+                        "hb_pb_cat_dur": "0.50_HomeDecor_0s",
+                        "hb_pb_cat_dur_appnex": "0.50_HomeDecor_0s",
+                        "hb_size": "300x500",
+                        "hb_size_appnexus": "300x500"
+                      },
+                    "type": "video"
+                  }
+                },
+                "h": 500,
+                "id": "apn-other-bid",
+                "impid": "imp-id-2",
+                "price": 0.6,
+                "w": 300
+              }
+            ]
+          }
+        ]
+      }
+    }
+  }
+  
\ No newline at end of file
diff --git a/exchange/targeting_test.go b/exchange/targeting_test.go
index 92b338f9..f8630968 100644
--- a/exchange/targeting_test.go
+++ b/exchange/targeting_test.go
@@ -106,7 +106,7 @@ func runTargetingAuction(t *testing.T, mockBids map[openrtb_ext.BidderName][]*op
 	if error != nil {
 		t.Errorf("Failed to create a category Fetcher: %v", error)
 	}
-	bidResp, err := ex.HoldAuction(context.Background(), req, &mockFetcher{}, pbsmetrics.Labels{}, &categoriesFetcher)
+	bidResp, err := ex.HoldAuction(context.Background(), req, &mockFetcher{}, pbsmetrics.Labels{}, &categoriesFetcher, nil)
 
 	if err != nil {
 		t.Fatalf("Unexpected errors running auction: %v", err)
diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go
index cc6d4b58..3de64ec1 100644
--- a/usersync/usersyncers/syncer_test.go
+++ b/usersync/usersyncers/syncer_test.go
@@ -59,6 +59,7 @@ func TestNewSyncerMap(t *testing.T) {
 			string(openrtb_ext.BidderTripleliftNative): syncConfig,
 			string(openrtb_ext.BidderUcfunnel):         syncConfig,
 			string(openrtb_ext.BidderUnruly):           syncConfig,
+			string(openrtb_ext.BidderValueImpression):  syncConfig,
 			string(openrtb_ext.BidderVerizonMedia):     syncConfig,
 			string(openrtb_ext.BidderVisx):             syncConfig,
 			string(openrtb_ext.BidderVrtcal):           syncConfig,
@@ -73,6 +74,7 @@ func TestNewSyncerMap(t *testing.T) {
 		openrtb_ext.BidderTappx:     true,
 		openrtb_ext.BidderKubient:   true,
 		openrtb_ext.BidderPubnative: true,
+		openrtb_ext.BidderKidoz:     true,
 	}
 
 	for bidder, config := range cfg.Adapters {