Repository: prebid/prebid-server
Pull request #2352 (2022-08-17 13:49:51)

Adnuntius Adapter: Set no cookies as a parameter
This will allow prebid server partners to be able to tell the ad server not to set cookies as a parameter with the ad request.


Patch:
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index a1459626..8b4d73b3 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -9,7 +9,7 @@ jobs:
   release:
     name: Create Release
     if: github.event.base_ref == 'refs/heads/master'
-    runs-on: ubuntu-latest
+    runs-on: ubuntu-20.04
     steps:
       - name: Get Version
         id: get_version
diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml
index 570114b0..b56ddad9 100644
--- a/.github/workflows/security.yml
+++ b/.github/workflows/security.yml
@@ -9,7 +9,7 @@ on:
 jobs:
   build:
     name: Trivy
-    runs-on: ubuntu-18.04
+    runs-on: ubuntu-20.04
     steps:
       - name: Checkout Code
         uses: actions/checkout@v2
diff --git a/.github/workflows/validate-merge.yml b/.github/workflows/validate-merge.yml
index 9cf371ca..63dd7a97 100644
--- a/.github/workflows/validate-merge.yml
+++ b/.github/workflows/validate-merge.yml
@@ -6,7 +6,7 @@ on:
 
 jobs:
   validate-merge:
-    runs-on: ubuntu-18.04
+    runs-on: ubuntu-20.04
     
     steps:
     - name: Install Go
diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml
index fc36f45c..972d6c0e 100644
--- a/.github/workflows/validate.yml
+++ b/.github/workflows/validate.yml
@@ -11,7 +11,7 @@ jobs:
     strategy:
       matrix:
         go-version: [1.16.x, 1.17.x]
-        os: [ubuntu-18.04]
+        os: [ubuntu-20.04]
     runs-on: ${{ matrix.os }}
     
     steps:
diff --git a/Dockerfile b/Dockerfile
index 92cd74e7..c2214da5 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,4 @@
-FROM ubuntu:18.04 AS build
+FROM ubuntu:20.04 AS build
 RUN apt-get update && \
     apt-get -y upgrade && \
     apt-get install -y wget
@@ -22,7 +22,7 @@ ARG TEST="true"
 RUN if [ "$TEST" != "false" ]; then ./validate.sh ; fi
 RUN go build -mod=vendor -ldflags "-X github.com/prebid/prebid-server/version.Ver=`git describe --tags | sed 's/^v//'` -X github.com/prebid/prebid-server/version.Rev=`git rev-parse HEAD`" .
 
-FROM ubuntu:18.04 AS release
+FROM ubuntu:20.04 AS release
 LABEL maintainer="hans.hjort@xandr.com" 
 WORKDIR /usr/local/bin/
 COPY --from=build /app/prebid-server .
diff --git a/adapters/adnuntius/adnuntius.go b/adapters/adnuntius/adnuntius.go
index 19c52166..57b6590b 100644
--- a/adapters/adnuntius/adnuntius.go
+++ b/adapters/adnuntius/adnuntius.go
@@ -95,7 +95,7 @@ func setHeaders(ortbRequest openrtb2.BidRequest) http.Header {
 	return headers
 }
 
-func makeEndpointUrl(ortbRequest openrtb2.BidRequest, a *adapter) (string, []error) {
+func makeEndpointUrl(ortbRequest openrtb2.BidRequest, a *adapter, noCookies bool) (string, []error) {
 	uri, err := url.Parse(a.endpoint)
 	if err != nil {
 		return "", []error{fmt.Errorf("failed to parse Adnuntius endpoint: %v", err)}
@@ -106,11 +106,18 @@ func makeEndpointUrl(ortbRequest openrtb2.BidRequest, a *adapter) (string, []err
 		return "", []error{fmt.Errorf("failed to parse Adnuntius endpoint: %v", err)}
 	}
 
-	var deviceExt extDeviceAdnuntius
-	if ortbRequest.Device != nil && ortbRequest.Device.Ext != nil {
-		if err := json.Unmarshal(ortbRequest.Device.Ext, &deviceExt); err != nil {
-			return "", []error{fmt.Errorf("failed to parse Adnuntius endpoint: %v", err)}
+	if !noCookies {
+		var deviceExt extDeviceAdnuntius
+		if ortbRequest.Device != nil && ortbRequest.Device.Ext != nil {
+			if err := json.Unmarshal(ortbRequest.Device.Ext, &deviceExt); err != nil {
+				return "", []error{fmt.Errorf("failed to parse Adnuntius endpoint: %v", err)}
+			}
+		}
+
+		if deviceExt.NoCookies {
+			noCookies = true
 		}
+
 	}
 
 	_, offset := a.time.Now().Zone()
@@ -122,7 +129,7 @@ func makeEndpointUrl(ortbRequest openrtb2.BidRequest, a *adapter) (string, []err
 		q.Set("consentString", consent)
 	}
 
-	if deviceExt.NoCookies {
+	if noCookies {
 		q.Set("noCookies", "true")
 	}
 
@@ -160,13 +167,7 @@ func (a *adapter) generateRequests(ortbRequest openrtb2.BidRequest) ([]*adapters
 	var requestData []*adapters.RequestData
 	networkAdunitMap := make(map[string][]adnAdunit)
 	headers := setHeaders(ortbRequest)
-
-	endpoint, err := makeEndpointUrl(ortbRequest, a)
-	if err != nil {
-		return nil, []error{&errortypes.BadInput{
-			Message: fmt.Sprintf("failed to parse URL: %s", err),
-		}}
-	}
+	var noCookies bool = false
 
 	for _, imp := range ortbRequest.Imp {
 		if imp.Banner == nil {
@@ -188,6 +189,10 @@ func (a *adapter) generateRequests(ortbRequest openrtb2.BidRequest) ([]*adapters
 			}}
 		}
 
+		if adnuntiusExt.NoCookies == true {
+			noCookies = true
+		}
+
 		network := defaultNetwork
 		if adnuntiusExt.Network != "" {
 			network = adnuntiusExt.Network
@@ -202,6 +207,13 @@ func (a *adapter) generateRequests(ortbRequest openrtb2.BidRequest) ([]*adapters
 			})
 	}
 
+	endpoint, err := makeEndpointUrl(ortbRequest, a, noCookies)
+	if err != nil {
+		return nil, []error{&errortypes.BadInput{
+			Message: fmt.Sprintf("failed to parse URL: %s", err),
+		}}
+	}
+
 	site := defaultSite
 	if ortbRequest.Site != nil && ortbRequest.Site.Page != "" {
 		site = ortbRequest.Site.Page
diff --git a/adapters/andbeyondmedia/andbeyondmedia.go b/adapters/andbeyondmedia/andbeyondmedia.go
new file mode 100644
index 00000000..53163512
--- /dev/null
+++ b/adapters/andbeyondmedia/andbeyondmedia.go
@@ -0,0 +1,147 @@
+package andbeyondmedia
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+
+	"github.com/mxmCherry/openrtb/v16/openrtb2"
+	"github.com/prebid/prebid-server/adapters"
+	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/errortypes"
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+type adapter struct {
+	endpoint string
+}
+
+type reqBodyExt struct {
+	AndBeyondMediaBidderExt reqBodyExtBidder `json:"bidder"`
+}
+
+type reqBodyExtBidder struct {
+	Type        string `json:"type"`
+	PlacementID string `json:"placementId,omitempty"`
+}
+
+func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) {
+	bidder := &adapter{
+		endpoint: config.Endpoint,
+	}
+	return bidder, nil
+}
+
+func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+	var err error
+	var adapterRequests []*adapters.RequestData
+
+	reqCopy := *request
+	for _, imp := range request.Imp {
+		reqCopy.Imp = []openrtb2.Imp{imp}
+
+		var bidderExt adapters.ExtImpBidder
+		var andBeyondMediaExt openrtb_ext.ImpExtAndBeyondMedia
+
+		if err = json.Unmarshal(reqCopy.Imp[0].Ext, &bidderExt); err != nil {
+			return nil, []error{err}
+		}
+		if err = json.Unmarshal(bidderExt.Bidder, &andBeyondMediaExt); err != nil {
+			return nil, []error{err}
+		}
+
+		temp := reqBodyExt{AndBeyondMediaBidderExt: reqBodyExtBidder{}}
+		temp.AndBeyondMediaBidderExt.PlacementID = andBeyondMediaExt.PlacementID
+		temp.AndBeyondMediaBidderExt.Type = "publisher"
+
+		finalyImpExt, err := json.Marshal(temp)
+		if err != nil {
+			return nil, []error{err}
+		}
+
+		reqCopy.Imp[0].Ext = finalyImpExt
+
+		adapterReq, err := a.makeRequest(&reqCopy)
+		if err != nil {
+			return nil, []error{err}
+		}
+
+		if adapterReq != nil {
+			adapterRequests = append(adapterRequests, adapterReq)
+		}
+	}
+	return adapterRequests, nil
+}
+
+func (a *adapter) makeRequest(request *openrtb2.BidRequest) (*adapters.RequestData, error) {
+	reqJSON, err := json.Marshal(request)
+	if err != nil {
+		return nil, err
+	}
+
+	headers := http.Header{}
+	headers.Add("Content-Type", "application/json;charset=utf-8")
+	headers.Add("Accept", "application/json")
+	return &adapters.RequestData{
+		Method:  "POST",
+		Uri:     a.endpoint,
+		Body:    reqJSON,
+		Headers: headers,
+	}, err
+}
+
+func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+	if responseData.StatusCode == http.StatusNoContent {
+		return nil, nil
+	}
+
+	if responseData.StatusCode != http.StatusOK {
+		err := &errortypes.BadServerResponse{
+			Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info.", responseData.StatusCode),
+		}
+		return nil, []error{err}
+	}
+
+	var response openrtb2.BidResponse
+	if err := json.Unmarshal(responseData.Body, &response); err != nil {
+		return nil, []error{err}
+	}
+
+	bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp))
+	bidResponse.Currency = response.Cur
+	for _, seatBid := range response.SeatBid {
+		for i := range seatBid.Bid {
+			bidType, err := getMediaTypeForImp(seatBid.Bid[i].ImpID, request.Imp)
+			if err != nil {
+				return nil, []error{err}
+			}
+
+			b := &adapters.TypedBid{
+				Bid:     &seatBid.Bid[i],
+				BidType: bidType,
+			}
+			bidResponse.Bids = append(bidResponse.Bids, b)
+		}
+	}
+	return bidResponse, nil
+}
+
+func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) {
+	for _, imp := range imps {
+		if imp.ID == impID {
+			if imp.Banner != nil {
+				return openrtb_ext.BidTypeBanner, nil
+			}
+			if imp.Video != nil {
+				return openrtb_ext.BidTypeVideo, nil
+			}
+			if imp.Native != nil {
+				return openrtb_ext.BidTypeNative, nil
+			}
+		}
+	}
+
+	return "", &errortypes.BadInput{
+		Message: fmt.Sprintf("Failed to find impression \"%s\"", impID),
+	}
+}
diff --git a/adapters/nextmillennium/nextmillennium.go b/adapters/nextmillennium/nextmillennium.go
index 605da43f..30e7e67c 100644
--- a/adapters/nextmillennium/nextmillennium.go
+++ b/adapters/nextmillennium/nextmillennium.go
@@ -16,16 +16,14 @@ type adapter struct {
 	endpoint string
 }
 
-type NextMillenniumBidRequest struct {
-	ID   string `json:"id"`
-	Test uint8  `json:"test,omitempty"`
-	Ext  struct {
-		Prebid struct {
-			StoredRequest struct {
-				ID string `json:"id"`
-			} `json:"storedrequest"`
-		} `json:"prebid"`
-	} `json:"ext"`
+type nmExtPrebidStoredRequest struct {
+	ID string `json:"id"`
+}
+type nmExtPrebid struct {
+	StoredRequest nmExtPrebidStoredRequest `json:"storedrequest"`
+}
+type nextMillJsonExt struct {
+	Prebid nmExtPrebid `json:"prebid"`
 }
 
 //MakeRequests prepares request information for prebid-server core
@@ -98,12 +96,8 @@ func (adapter *adapter) buildAdapterRequest(prebidBidRequest *openrtb2.BidReques
 		Headers: headers}, nil
 }
 
-func createBidRequest(prebidBidRequest *openrtb2.BidRequest, params *openrtb_ext.ImpExtNextMillennium) *NextMillenniumBidRequest {
+func createBidRequest(prebidBidRequest *openrtb2.BidRequest, params *openrtb_ext.ImpExtNextMillennium) *openrtb2.BidRequest {
 	placementID := params.PlacementID
-	bidRequest := NextMillenniumBidRequest{
-		ID:   prebidBidRequest.ID,
-		Test: uint8(prebidBidRequest.Test),
-	}
 
 	if params.GroupID != "" {
 		domain := ""
@@ -126,8 +120,15 @@ func createBidRequest(prebidBidRequest *openrtb2.BidRequest, params *openrtb_ext
 
 		placementID = fmt.Sprintf("g%s;%s;%s", params.GroupID, size, domain)
 	}
-
-	bidRequest.Ext.Prebid.StoredRequest.ID = placementID
+	ext := nextMillJsonExt{}
+	ext.Prebid.StoredRequest.ID = placementID
+	jsonExt, err := json.Marshal(ext)
+	if err != nil {
+		return prebidBidRequest
+	}
+	bidRequest := *prebidBidRequest
+	bidRequest.Ext = jsonExt
+	bidRequest.Imp[0].Ext = jsonExt
 	return &bidRequest
 }
 
diff --git a/adapters/pubmatic/pubmatic.go b/adapters/pubmatic/pubmatic.go
index 802806da..50fed350 100644
--- a/adapters/pubmatic/pubmatic.go
+++ b/adapters/pubmatic/pubmatic.go
@@ -2,6 +2,7 @@ package pubmatic
 
 import (
 	"encoding/json"
+	"errors"
 	"fmt"
 	"net/http"
 	"strconv"
@@ -407,8 +408,9 @@ func (a *PubmaticAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externa
 			}
 
 			if bidType == openrtb_ext.BidTypeNative {
-				if value, _, _, err := jsonparser.Get([]byte(bid.AdM), string(openrtb_ext.BidTypeNative)); err == nil {
-					bid.AdM = string(value)
+				bid.AdM, err = getNativeAdm(bid.AdM)
+				if err != nil {
+					errs = append(errs, err)
 				}
 			}
 
@@ -424,6 +426,27 @@ func (a *PubmaticAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externa
 	return bidResponse, errs
 }
 
+func getNativeAdm(adm string) (string, error) {
+	var err error
+	nativeAdm := make(map[string]interface{})
+	err = json.Unmarshal([]byte(adm), &nativeAdm)
+	if err != nil {
+		return adm, errors.New("unable to unmarshal native adm")
+	}
+
+	// move bid.adm.native to bid.adm
+	if _, ok := nativeAdm["native"]; ok {
+		//using jsonparser to avoid marshaling, encode escape, etc.
+		value, _, _, err := jsonparser.Get([]byte(adm), string(openrtb_ext.BidTypeNative))
+		if err != nil {
+			return adm, errors.New("unable to get native adm")
+		}
+		adm = string(value)
+	}
+
+	return adm, nil
+}
+
 // getBidType returns the bid type specified in the response bid.ext
 func getBidType(bidExt *pubmaticBidExt) openrtb_ext.BidType {
 	// setting "banner" as the default bid type
diff --git a/adapters/seedingAlliance/seedingAlliance.go b/adapters/seedingAlliance/seedingAlliance.go
new file mode 100644
index 00000000..8cdaa0a1
--- /dev/null
+++ b/adapters/seedingAlliance/seedingAlliance.go
@@ -0,0 +1,142 @@
+package seedingAlliance
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"strconv"
+	"strings"
+
+	"github.com/mxmCherry/openrtb/v16/openrtb2"
+	"github.com/prebid/prebid-server/adapters"
+	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/errortypes"
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+type adapter struct {
+	endpoint string
+}
+
+func Builder(_ openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) {
+	bidder := &adapter{
+		endpoint: config.Endpoint,
+	}
+	return bidder, nil
+}
+
+func (a *adapter) MakeRequests(request *openrtb2.BidRequest, extraRequestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+	for i := range request.Imp {
+		if err := addTagID(&request.Imp[i]); err != nil {
+			return nil, []error{err}
+		}
+	}
+
+	if !curExists(request.Cur, "EUR") {
+		request.Cur = append(request.Cur, "EUR")
+	}
+
+	requestJSON, err := json.Marshal(request)
+	if err != nil {
+		return nil, []error{err}
+	}
+
+	requestData := &adapters.RequestData{
+		Method: http.MethodPost,
+		Uri:    a.endpoint,
+		Body:   requestJSON,
+	}
+
+	return []*adapters.RequestData{requestData}, nil
+}
+
+func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+	if responseData.StatusCode == http.StatusNoContent {
+		return nil, nil
+	}
+
+	if responseData.StatusCode == http.StatusBadRequest {
+		err := &errortypes.BadInput{
+			Message: "Unexpected status code: 400. Bad request from publisher. Run with request.debug = 1 for more info.",
+		}
+		return nil, []error{err}
+	}
+
+	if responseData.StatusCode != http.StatusOK {
+		err := &errortypes.BadServerResponse{
+			Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info.", responseData.StatusCode),
+		}
+		return nil, []error{err}
+	}
+
+	var response openrtb2.BidResponse
+	if err := json.Unmarshal(responseData.Body, &response); err != nil {
+		return nil, []error{err}
+	}
+
+	bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp))
+	bidResponse.Currency = response.Cur
+
+	for _, seatBid := range response.SeatBid {
+		for i := range seatBid.Bid {
+			resolvePriceMacro(&seatBid.Bid[i])
+
+			b := &adapters.TypedBid{
+				Bid:     &seatBid.Bid[i],
+				BidType: getMediaTypeForImp(seatBid.Bid[i].ImpID, request.Imp),
+			}
+
+			bidResponse.Bids = append(bidResponse.Bids, b)
+		}
+	}
+
+	return bidResponse, nil
+}
+
+func resolvePriceMacro(bid *openrtb2.Bid) {
+	price := strconv.FormatFloat(bid.Price, 'f', -1, 64)
+	bid.AdM = strings.Replace(bid.AdM, "${AUCTION_PRICE}", price, -1)
+}
+
+func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType {
+	var UnknownBidType openrtb_ext.BidType = "unknown"
+
+	for _, imp := range imps {
+		if imp.ID == impId {
+			switch {
+			case imp.Native != nil:
+				return openrtb_ext.BidTypeNative
+			case imp.Banner != nil:
+				return openrtb_ext.BidTypeBanner
+			}
+		}
+	}
+
+	return UnknownBidType
+}
+
+func curExists(cc []string, c string) bool {
+	for i := range cc {
+		if cc[i] == c {
+			return true
+		}
+	}
+	return false
+}
+
+func addTagID(imp *openrtb2.Imp) error {
+	var ext adapters.ExtImpBidder
+	var extSA openrtb_ext.ImpExtSeedingAlliance
+
+	if err := json.Unmarshal(imp.Ext, &ext); err != nil {
+		return fmt.Errorf("could not unmarshal adapters.ExtImpBidder: %w", err)
+	}
+
+	if err := json.Unmarshal(ext.Bidder, &extSA); err != nil {
+		return fmt.Errorf("could not unmarshal openrtb_ext.ImpExtSeedingAlliance: %w", err)
+	}
+
+	imp.TagID = extSA.AdUnitID
+
+	return nil
+}
diff --git a/config/bidderinfo.go b/config/bidderinfo.go
index d6c4ceaf..fc307de9 100644
--- a/config/bidderinfo.go
+++ b/config/bidderinfo.go
@@ -22,6 +22,7 @@ type BidderInfo struct {
 	GVLVendorID             uint16               `yaml:"gvlVendorID"`
 	Syncer                  *Syncer              `yaml:"userSync"`
 	Experiment              BidderInfoExperiment `yaml:"experiment"`
+	EndpointCompression     string               `yaml:"endpointCompression"` // EndpointCompression determines, if set, the type of compression the bid request will undergo before being sent to the corresponding bid server
 }
 
 // BidderInfoExperiment specifies non-production ready feature config for a bidder
@@ -132,8 +133,8 @@ func (s *Syncer) Override(original *Syncer) *Syncer {
 // In most cases, bidders will specify a URL with a `{{.RedirectURL}}` macro for the call back to
 // Prebid Server and a UserMacro which the bidder server will replace with the user's id. Example:
 //
-//  url: "https://sync.bidderserver.com/usersync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect={{.RedirectURL}}"
-//  userMacro: "$UID"
+//	url: "https://sync.bidderserver.com/usersync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect={{.RedirectURL}}"
+//	userMacro: "$UID"
 //
 // Prebid Server is configured with a default RedirectURL template matching the /setuid call. This
 // may be overridden for all bidders with the `user_sync.redirect_url` host configuration or for a
diff --git a/config/config.go b/config/config.go
index 4f4698dc..a6d71ee0 100644
--- a/config/config.go
+++ b/config/config.go
@@ -946,6 +946,7 @@ func SetupViper(v *viper.Viper, filename string) {
 	v.SetDefault("adapters.aja.endpoint", "https://ad.as.amanad.adtdp.com/v1/bid/4")
 	v.SetDefault("adapters.algorix.endpoint", "https://{{.Host}}.svr-algorix.com/rtb/sa?sid={{.SourceId}}&token={{.AccountID}}")
 	v.SetDefault("adapters.amx.endpoint", "http://pbs.amxrtb.com/auction/openrtb")
+	v.SetDefault("adapters.andbeyondmedia.endpoint", "http://backend.andbeyond.media/pserver")
 	v.SetDefault("adapters.apacdex.endpoint", "http://useast.quantumdex.io/auction/pbs")
 	v.SetDefault("adapters.applogy.endpoint", "http://rtb.applogy.com/v1/prebid")
 	v.SetDefault("adapters.appnexus.endpoint", "http://ib.adnxs.com/openrtb2") // Docs: https://wiki.appnexus.com/display/supply/Incoming+Bid+Request+from+SSPs
@@ -1045,6 +1046,7 @@ func SetupViper(v *viper.Viper, filename string) {
 	v.SetDefault("adapters.rtbhouse.endpoint", "http://prebidserver-s2s-ams.creativecdn.com/bidder/prebidserver/bids")
 	v.SetDefault("adapters.rubicon.disabled", true)
 	v.SetDefault("adapters.rubicon.endpoint", "http://exapi-us-east.rubiconproject.com/a/api/exchange.json")
+	v.SetDefault("adapters.seedingalliance.endpoint", "http://b.nativendo.de/cds/rtb/bid?ssp=pb")
 	v.SetDefault("adapters.sa_lunamedia.endpoint", "http://balancer.lmgssp.com/pserver")
 	v.SetDefault("adapters.sharethrough.endpoint", "https://btlr.sharethrough.com/universal/v1?supply_id=FGMrCMMc")
 	v.SetDefault("adapters.silvermob.endpoint", "http://{{.Host}}.silvermob.com/marketplace/api/dsp/bid/{{.ZoneID}}")
@@ -1083,7 +1085,6 @@ func SetupViper(v *viper.Viper, filename string) {
 	v.SetDefault("adapters.yieldlab.endpoint", "https://ad.yieldlab.net/yp/")
 	v.SetDefault("adapters.yieldmo.endpoint", "https://ads.yieldmo.com/exchange/prebid-server")
 	v.SetDefault("adapters.yieldone.endpoint", "https://y.one.impact-ad.jp/hbs_imp")
-	v.SetDefault("adapters.yssp.disabled", true)
 	v.SetDefault("adapters.zeroclickfraud.endpoint", "http://{{.Host}}/openrtb2?sid={{.SourceId}}")
 
 	v.SetDefault("max_request_size", 1024*256)
diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go
index 43dc7a10..9935ea74 100644
--- a/endpoints/openrtb2/auction.go
+++ b/endpoints/openrtb2/auction.go
@@ -1403,11 +1403,12 @@ func validateRegs(req *openrtb_ext.RequestWrapper) error {
 	if err != nil {
 		return fmt.Errorf("request.regs.ext is invalid: %v", err)
 	}
-	regExt := regsExt.GetExt()
-	gdprJSON, hasGDPR := regExt["gdpr"]
-	if hasGDPR && (string(gdprJSON) != "0" && string(gdprJSON) != "1") {
-		return errors.New("request.regs.ext.gdpr must be either 0 or 1.")
+
+	gdpr := regsExt.GetGDPR()
+	if gdpr != nil && *gdpr != 0 && *gdpr != 1 {
+		return errors.New("request.regs.ext.gdpr must be either 0 or 1")
 	}
+
 	return nil
 }
 
diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-gdpr-invalid.json b/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-gdpr-invalid.json
index 1847dc72..03e789ee 100644
--- a/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-gdpr-invalid.json
+++ b/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-gdpr-invalid.json
@@ -44,5 +44,5 @@
     }
   },
   "expectedReturnCode": 400,
-  "expectedErrorMessage": "Invalid request: request.regs.ext.gdpr must be either 0 or 1.\n"
+  "expectedErrorMessage": "Invalid request: request.regs.ext.gdpr must be either 0 or 1\n"
 }
diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-gdpr-string.json b/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-gdpr-string.json
index 4a315911..dc15410a 100644
--- a/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-gdpr-string.json
+++ b/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-gdpr-string.json
@@ -44,5 +44,5 @@
     }
   },
   "expectedReturnCode": 400,
-  "expectedErrorMessage": "Invalid request: request.regs.ext.gdpr must be either 0 or 1.\n"
+  "expectedErrorMessage": "Invalid request: request.regs.ext is invalid: gdpr must be an integer\n"
 }
diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go
index 19ba1638..1d34708d 100755
--- a/exchange/adapter_builders.go
+++ b/exchange/adapter_builders.go
@@ -30,6 +30,7 @@ import (
 	"github.com/prebid/prebid-server/adapters/aja"
 	"github.com/prebid/prebid-server/adapters/algorix"
 	"github.com/prebid/prebid-server/adapters/amx"
+	"github.com/prebid/prebid-server/adapters/andbeyondmedia"
 	"github.com/prebid/prebid-server/adapters/apacdex"
 	"github.com/prebid/prebid-server/adapters/applogy"
 	"github.com/prebid/prebid-server/adapters/appnexus"
@@ -115,6 +116,7 @@ import (
 	"github.com/prebid/prebid-server/adapters/rtbhouse"
 	"github.com/prebid/prebid-server/adapters/rubicon"
 	salunamedia "github.com/prebid/prebid-server/adapters/sa_lunamedia"
+	"github.com/prebid/prebid-server/adapters/seedingAlliance"
 	"github.com/prebid/prebid-server/adapters/sharethrough"
 	"github.com/prebid/prebid-server/adapters/silvermob"
 	"github.com/prebid/prebid-server/adapters/smaato"
@@ -183,6 +185,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder {
 		openrtb_ext.BidderAJA:               aja.Builder,
 		openrtb_ext.BidderAlgorix:           algorix.Builder,
 		openrtb_ext.BidderAMX:               amx.Builder,
+		openrtb_ext.BidderAndBeyondMedia:    andbeyondmedia.Builder,
 		openrtb_ext.BidderApacdex:           apacdex.Builder,
 		openrtb_ext.BidderApplogy:           applogy.Builder,
 		openrtb_ext.BidderAppnexus:          appnexus.Builder,
@@ -273,6 +276,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder {
 		openrtb_ext.BidderRichaudience:      richaudience.Builder,
 		openrtb_ext.BidderRTBHouse:          rtbhouse.Builder,
 		openrtb_ext.BidderRubicon:           rubicon.Builder,
+		openrtb_ext.BidderSeedingAlliance:   seedingAlliance.Builder,
 		openrtb_ext.BidderSaLunaMedia:       salunamedia.Builder,
 		openrtb_ext.BidderSharethrough:      sharethrough.Builder,
 		openrtb_ext.BidderSilverMob:         silvermob.Builder,
@@ -309,7 +313,6 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder {
 		openrtb_ext.BidderYieldlab:          yieldlab.Builder,
 		openrtb_ext.BidderYieldmo:           yieldmo.Builder,
 		openrtb_ext.BidderYieldone:          yieldone.Builder,
-		openrtb_ext.BidderYSSP:              yahoossp.Builder,
 		openrtb_ext.BidderZeroClickFraud:    zeroclickfraud.Builder,
 	}
 }
diff --git a/exchange/adapter_util.go b/exchange/adapter_util.go
index c3d5f0ec..6ecac1ea 100644
--- a/exchange/adapter_util.go
+++ b/exchange/adapter_util.go
@@ -19,7 +19,7 @@ func BuildAdapters(client *http.Client, cfg *config.Configuration, infos config.
 	exchangeBidders := make(map[openrtb_ext.BidderName]AdaptedBidder, len(bidders))
 	for bidderName, bidder := range bidders {
 		info := infos[string(bidderName)]
-		exchangeBidder := AdaptBidder(bidder, client, cfg, me, bidderName, info.Debug)
+		exchangeBidder := AdaptBidder(bidder, client, cfg, me, bidderName, info.Debug, info.EndpointCompression)
 		exchangeBidder = addValidatedBidderMiddleware(exchangeBidder)
 		exchangeBidders[bidderName] = exchangeBidder
 	}
@@ -81,6 +81,7 @@ func GetDisabledBiddersErrorMessages(infos config.BidderInfos) map[string]string
 		"lifestreet":   `Bidder "lifestreet" is no longer available in Prebid Server. Please update your configuration.`,
 		"adagio":       `Bidder "adagio" is no longer available in Prebid Server. Please update your configuration.`,
 		"somoaudience": `Bidder "somoaudience" is no longer available in Prebid Server. Please update your configuration.`,
+		"yssp":         `Bidder "yssp" is no longer available in Prebid Server. If you're looking to use the Yahoo SSP adapter, please rename it to "yahoossp" in your configuration.`,
 	}
 
 	for name, info := range infos {
diff --git a/exchange/bidder.go b/exchange/bidder.go
index 99fb8949..479c0256 100644
--- a/exchange/bidder.go
+++ b/exchange/bidder.go
@@ -2,6 +2,7 @@ package exchange
 
 import (
 	"bytes"
+	"compress/gzip"
 	"context"
 	"crypto/tls"
 	"encoding/json"
@@ -11,6 +12,7 @@ import (
 	"net/http"
 	"net/http/httptrace"
 	"regexp"
+	"strings"
 	"time"
 
 	"github.com/golang/glog"
@@ -55,7 +57,7 @@ type AdaptedBidder interface {
 	requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes config.AlternateBidderCodes) ([]*pbsOrtbSeatBid, []error)
 }
 
-//bidRequestOptions holds additional options for bid request execution to maintain clean code and reasonable number of parameters
+// bidRequestOptions holds additional options for bid request execution to maintain clean code and reasonable number of parameters
 type bidRequestOptions struct {
 	accountDebugAllowed bool
 	headerDebugAllowed  bool
@@ -106,20 +108,26 @@ type pbsOrtbSeatBid struct {
 	seat string
 }
 
+// Possible values of compression types Prebid Server can support for bidder compression
+const (
+	Gzip string = "GZIP"
+)
+
 // AdaptBidder converts an adapters.Bidder into an exchange.AdaptedBidder.
 //
 // The name refers to the "Adapter" architecture pattern, and should not be confused with a Prebid "Adapter"
 // (which is being phased out and replaced by Bidder for OpenRTB auctions)
-func AdaptBidder(bidder adapters.Bidder, client *http.Client, cfg *config.Configuration, me metrics.MetricsEngine, name openrtb_ext.BidderName, debugInfo *config.DebugInfo) AdaptedBidder {
+func AdaptBidder(bidder adapters.Bidder, client *http.Client, cfg *config.Configuration, me metrics.MetricsEngine, name openrtb_ext.BidderName, debugInfo *config.DebugInfo, endpointCompression string) AdaptedBidder {
 	return &bidderAdapter{
 		Bidder:     bidder,
 		BidderName: name,
 		Client:     client,
 		me:         me,
 		config: bidderAdapterConfig{
-			Debug:              cfg.Debug,
-			DisableConnMetrics: cfg.Metrics.Disabled.AdapterConnectionMetrics,
-			DebugInfo:          config.DebugInfo{Allow: parseDebugInfo(debugInfo)},
+			Debug:               cfg.Debug,
+			DisableConnMetrics:  cfg.Metrics.Disabled.AdapterConnectionMetrics,
+			DebugInfo:           config.DebugInfo{Allow: parseDebugInfo(debugInfo)},
+			EndpointCompression: endpointCompression,
 		},
 	}
 }
@@ -140,9 +148,10 @@ type bidderAdapter struct {
 }
 
 type bidderAdapterConfig struct {
-	Debug              config.Debug
-	DisableConnMetrics bool
-	DebugInfo          config.DebugInfo
+	Debug               config.Debug
+	DisableConnMetrics  bool
+	DebugInfo           config.DebugInfo
+	EndpointCompression string
 }
 
 func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes config.AlternateBidderCodes) ([]*pbsOrtbSeatBid, []error) {
@@ -176,13 +185,16 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde
 				reqData[i].Headers.Add("Sec-GPC", reqInfo.GlobalPrivacyControlHeader)
 			}
 			if bidRequestOptions.addCallSignHeader {
+				startSignRequestTime := time.Now()
 				signatureMessage, err := adsCertSigner.Sign(reqData[i].Uri, reqData[i].Body)
+				bidder.me.RecordAdsCertSignTime(time.Since(startSignRequestTime))
 				if err != nil {
-					//add metrics here
+					bidder.me.RecordAdsCertReq(false)
 					errs = append(errs, &errortypes.Warning{Message: fmt.Sprintf("AdsCert signer is enabled but cannot sign the request: %s", err.Error())})
 				}
 				if err == nil && len(signatureMessage) > 0 {
 					reqData[i].Headers.Add(adscert.SignHeader, signatureMessage)
+					bidder.me.RecordAdsCertReq(true)
 				}
 			}
 
@@ -501,7 +513,16 @@ func (bidder *bidderAdapter) doRequest(ctx context.Context, req *adapters.Reques
 }
 
 func (bidder *bidderAdapter) doRequestImpl(ctx context.Context, req *adapters.RequestData, logger util.LogMsg) *httpCallInfo {
-	httpReq, err := http.NewRequest(req.Method, req.Uri, bytes.NewBuffer(req.Body))
+	var requestBody []byte
+
+	switch strings.ToUpper(bidder.config.EndpointCompression) {
+	case Gzip:
+		requestBody = compressToGZIP(req.Body)
+		req.Headers.Set("Content-Encoding", "gzip")
+	default:
+		requestBody = req.Body
+	}
+	httpReq, err := http.NewRequest(req.Method, req.Uri, bytes.NewBuffer(requestBody))
 	if err != nil {
 		return &httpCallInfo{
 			request: req,
@@ -672,3 +693,11 @@ func prepareStoredResponse(impId string, bidResp json.RawMessage) *httpCallInfo
 	}
 	return respData
 }
+
+func compressToGZIP(requestBody []byte) []byte {
+	var b bytes.Buffer
+	w := gzip.NewWriter(&b)
+	w.Write([]byte(requestBody))
+	w.Close()
+	return b.Bytes()
+}
diff --git a/metrics/config/metrics.go b/metrics/config/metrics.go
index 127363f2..4079a785 100644
--- a/metrics/config/metrics.go
+++ b/metrics/config/metrics.go
@@ -267,6 +267,18 @@ func (me *MultiMetricsEngine) RecordStoredResponse(pubId string) {
 	}
 }
 
+func (me *MultiMetricsEngine) RecordAdsCertReq(success bool) {
+	for _, thisME := range *me {
+		thisME.RecordAdsCertReq(success)
+	}
+}
+
+func (me *MultiMetricsEngine) RecordAdsCertSignTime(adsCertSignTime time.Duration) {
+	for _, thisME := range *me {
+		thisME.RecordAdsCertSignTime(adsCertSignTime)
+	}
+}
+
 // NilMetricsEngine implements the MetricsEngine interface where no metrics are actually captured. This is
 // used if no metric backend is configured and also for tests.
 type NilMetricsEngine struct{}
@@ -385,3 +397,11 @@ func (me *NilMetricsEngine) RecordDebugRequest(debugEnabled bool, pubId string)
 
 func (me *NilMetricsEngine) RecordStoredResponse(pubId string) {
 }
+
+func (me *NilMetricsEngine) RecordAdsCertReq(success bool) {
+
+}
+
+func (me *NilMetricsEngine) RecordAdsCertSignTime(adsCertSignTime time.Duration) {
+
+}
diff --git a/metrics/go_metrics.go b/metrics/go_metrics.go
index 4d6e9bf6..9e6d8d1b 100644
--- a/metrics/go_metrics.go
+++ b/metrics/go_metrics.go
@@ -70,6 +70,11 @@ type Metrics struct {
 	exchanges []openrtb_ext.BidderName
 	// Will hold boolean values to help us disable metric collection if needed
 	MetricsDisabled config.DisabledMetrics
+
+	// AdsCert metrics
+	AdsCertRequestsSuccess metrics.Meter
+	AdsCertRequestsFailure metrics.Meter
+	adsCertSignTimer       metrics.Timer
 }
 
 // AdapterMetrics houses the metrics for a particular adapter
@@ -163,6 +168,10 @@ func NewBlankMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderNa
 		accountMetrics:  make(map[string]*accountMetrics),
 		MetricsDisabled: disabledMetrics,
 
+		AdsCertRequestsSuccess: blankMeter,
+		AdsCertRequestsFailure: blankMeter,
+		adsCertSignTimer:       blankTimer,
+
 		exchanges: exchanges,
 	}
 
@@ -298,6 +307,10 @@ func NewMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderName, d
 		newMetrics.PrivacyTCFRequestVersion[version] = metrics.GetOrRegisterMeter(fmt.Sprintf("privacy.request.tcf.%s", string(version)), registry)
 	}
 
+	newMetrics.AdsCertRequestsSuccess = metrics.GetOrRegisterMeter("ads_cert_requests.ok", registry)
+	newMetrics.AdsCertRequestsFailure = metrics.GetOrRegisterMeter("ads_cert_requests.failed", registry)
+	newMetrics.adsCertSignTimer = metrics.GetOrRegisterTimer("ads_cert_sign_time", registry)
+
 	return newMetrics
 }
 
@@ -756,3 +769,15 @@ func (me *Metrics) RecordAdapterGDPRRequestBlocked(adapterName openrtb_ext.Bidde
 
 	am.GDPRRequestBlocked.Mark(1)
 }
+
+func (me *Metrics) RecordAdsCertReq(success bool) {
+	if success {
+		me.AdsCertRequestsSuccess.Mark(1)
+	} else {
+		me.AdsCertRequestsFailure.Mark(1)
+	}
+}
+
+func (me *Metrics) RecordAdsCertSignTime(adsCertSignTime time.Duration) {
+	me.adsCertSignTimer.Update(adsCertSignTime)
+}
diff --git a/metrics/metrics.go b/metrics/metrics.go
index 31bfb15f..51faafb1 100644
--- a/metrics/metrics.go
+++ b/metrics/metrics.go
@@ -421,4 +421,6 @@ type MetricsEngine interface {
 	RecordAdapterGDPRRequestBlocked(adapterName openrtb_ext.BidderName)
 	RecordDebugRequest(debugEnabled bool, pubId string)
 	RecordStoredResponse(pubId string)
+	RecordAdsCertReq(success bool)
+	RecordAdsCertSignTime(adsCertSignTime time.Duration)
 }
diff --git a/metrics/metrics_mock.go b/metrics/metrics_mock.go
index 1ad7a5cc..586a4783 100644
--- a/metrics/metrics_mock.go
+++ b/metrics/metrics_mock.go
@@ -154,3 +154,11 @@ func (me *MetricsEngineMock) RecordDebugRequest(debugEnabled bool, pubId string)
 func (me *MetricsEngineMock) RecordStoredResponse(pubId string) {
 	me.Called(pubId)
 }
+
+func (me *MetricsEngineMock) RecordAdsCertReq(success bool) {
+	me.Called(success)
+}
+
+func (me *MetricsEngineMock) RecordAdsCertSignTime(adsCertSignTime time.Duration) {
+	me.Called(adsCertSignTime)
+}
diff --git a/metrics/prometheus/preload.go b/metrics/prometheus/preload.go
index bf98a820..bef88f20 100644
--- a/metrics/prometheus/preload.go
+++ b/metrics/prometheus/preload.go
@@ -145,6 +145,10 @@ func preloadLabelValues(m *Metrics, syncerKeys []string) {
 		hasBidsLabel: boolValues,
 	})
 
+	preloadLabelValuesForCounter(m.adsCertRequests, map[string][]string{
+		successLabel: boolValues,
+	})
+
 	if !m.metricsDisabled.AdapterConnectionMetrics {
 		preloadLabelValuesForCounter(m.adapterCreatedConnections, map[string][]string{
 			adapterLabel: adapterValues,
diff --git a/metrics/prometheus/prometheus.go b/metrics/prometheus/prometheus.go
index e1b8d4a4..df54df92 100644
--- a/metrics/prometheus/prometheus.go
+++ b/metrics/prometheus/prometheus.go
@@ -54,6 +54,8 @@ type Metrics struct {
 	storedResponses              prometheus.Counter
 	storedResponsesFetchTimer    *prometheus.HistogramVec
 	storedResponsesErrors        *prometheus.CounterVec
+	adsCertRequests              *prometheus.CounterVec
+	adsCertSignTimer             prometheus.Histogram
 
 	// Adapter Metrics
 	adapterBids                *prometheus.CounterVec
@@ -411,9 +413,25 @@ func NewMetrics(cfg config.PrometheusMetrics, disabledMetrics config.DisabledMet
 		"Count of total requests to Prebid Server that have stored responses labled by account",
 		[]string{accountLabel})
 
+	metrics.adsCertSignTimer = newHistogram(cfg, reg,
+		"ads_cert_sign_time",
+		"Seconds to generate an AdsCert header",
+		standardTimeBuckets)
+
+	metrics.adsCertRequests = newCounter(cfg, reg,
+		"ads_cert_requests",
+		"Count of AdsCert request, and if they were successfully sent.",
+		[]string{successLabel})
+
 	metrics.Gatherer = reg
 
-	metricsPrefix := fmt.Sprintf("%s_%s_", cfg.Namespace, cfg.Subsystem)
+	metricsPrefix := ""
+	if len(cfg.Namespace) > 0 {
+		metricsPrefix += fmt.Sprintf("%s_", cfg.Namespace)
+	}
+	if len(cfg.Subsystem) > 0 {
+		metricsPrefix += fmt.Sprintf("%s_", cfg.Subsystem)
+	}
 
 	metrics.Registerer = prometheus.WrapRegistererWithPrefix(metricsPrefix, reg)
 	metrics.Registerer.MustRegister(promCollector.NewGoCollector())
@@ -794,3 +812,18 @@ func (m *Metrics) RecordAdapterGDPRRequestBlocked(adapterName openrtb_ext.Bidder
 		adapterLabel: string(adapterName),
 	}).Inc()
 }
+
+func (m *Metrics) RecordAdsCertReq(success bool) {
+	if success {
+		m.adsCertRequests.With(prometheus.Labels{
+			successLabel: requestSuccessful,
+		}).Inc()
+	} else {
+		m.adsCertRequests.With(prometheus.Labels{
+			successLabel: requestFailed,
+		}).Inc()
+	}
+}
+func (m *Metrics) RecordAdsCertSignTime(adsCertSignTime time.Duration) {
+	m.adsCertSignTimer.Observe(adsCertSignTime.Seconds())
+}
diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go
index 73d4dc03..a1795b32 100644
--- a/openrtb_ext/bidders.go
+++ b/openrtb_ext/bidders.go
@@ -110,6 +110,7 @@ const (
 	BidderAJA               BidderName = "aja"
 	BidderAlgorix           BidderName = "algorix"
 	BidderAMX               BidderName = "amx"
+	BidderAndBeyondMedia    BidderName = "andbeyondmedia"
 	BidderApacdex           BidderName = "apacdex"
 	BidderApplogy           BidderName = "applogy"
 	BidderAppnexus          BidderName = "appnexus"
@@ -200,6 +201,7 @@ const (
 	BidderRichaudience      BidderName = "richaudience"
 	BidderRTBHouse          BidderName = "rtbhouse"
 	BidderRubicon           BidderName = "rubicon"
+	BidderSeedingAlliance   BidderName = "seedingAlliance"
 	BidderSaLunaMedia       BidderName = "sa_lunamedia"
 	BidderSharethrough      BidderName = "sharethrough"
 	BidderSilverMob         BidderName = "silvermob"
@@ -236,7 +238,6 @@ const (
 	BidderYieldlab          BidderName = "yieldlab"
 	BidderYieldmo           BidderName = "yieldmo"
 	BidderYieldone          BidderName = "yieldone"
-	BidderYSSP              BidderName = "yssp"
 	BidderZeroClickFraud    BidderName = "zeroclickfraud"
 )
 
@@ -272,6 +273,7 @@ func CoreBidderNames() []BidderName {
 		BidderAJA,
 		BidderAlgorix,
 		BidderAMX,
+		BidderAndBeyondMedia,
 		BidderApacdex,
 		BidderApplogy,
 		BidderAppnexus,
@@ -362,6 +364,7 @@ func CoreBidderNames() []BidderName {
 		BidderRichaudience,
 		BidderRTBHouse,
 		BidderRubicon,
+		BidderSeedingAlliance,
 		BidderSaLunaMedia,
 		BidderSharethrough,
 		BidderSilverMob,
@@ -398,7 +401,6 @@ func CoreBidderNames() []BidderName {
 		BidderYieldlab,
 		BidderYieldmo,
 		BidderYieldone,
-		BidderYSSP,
 		BidderZeroClickFraud,
 	}
 }
diff --git a/openrtb_ext/convert_down.go b/openrtb_ext/convert_down.go
new file mode 100644
index 00000000..95f8a948
--- /dev/null
+++ b/openrtb_ext/convert_down.go
@@ -0,0 +1,147 @@
+package openrtb_ext
+
+func ConvertDownTo25(r *RequestWrapper) error {
+	// schain
+	if err := moveSupplyChainFrom26To25(r); err != nil {
+		return err
+	}
+
+	// gdpr
+	if err := moveGDPRFrom26To25(r); err != nil {
+		return err
+	}
+	if err := moveConsentFrom26To25(r); err != nil {
+		return err
+	}
+
+	// ccpa
+	if err := moveUSPrivacyFrom26To25(r); err != nil {
+		return err
+	}
+
+	// eid
+	if err := moveEIDFrom26To25(r); err != nil {
+		return err
+	}
+
+	// Do not remove new OpenRTB 2.6 fields. The spec specifies that bidders and exchanges
+	// must tolerate new or unexpected fields gracefully, either ignoring them or treating
+	// them as unknown.
+
+	return nil
+}
+
+// moveSupplyChainFrom26To25 modifies the request to move the OpenRTB 2.6 supply chain
+// object (req.source.schain) to the OpenRTB 2.5 location (req.source.ext.schain). If the
+// OpenRTB 2.5 location is already present it may be overwritten. The OpenRTB 2.5 location
+// is expected to be empty.
+func moveSupplyChainFrom26To25(r *RequestWrapper) error {
+	if r.Source == nil || r.Source.SChain == nil {
+		return nil
+	}
+
+	// read and clear 2.6 location
+	schain26 := r.Source.SChain
+	r.Source.SChain = nil
+
+	// move to 2.5 location
+	sourceExt, err := r.GetSourceExt()
+	if err != nil {
+		return err
+	}
+	sourceExt.SetSChain(schain26)
+
+	return nil
+}
+
+// moveGDPRFrom26To25 modifies the request to move the OpenRTB 2.6 GDPR signal
+// field (req.regs.gdpr) to the OpenRTB 2.5 location (req.regs.ext.gdpr). If the
+// OpenRTB 2.5 location is already present it may be overwritten. The OpenRTB 2.5
+// location is expected to be empty.
+func moveGDPRFrom26To25(r *RequestWrapper) error {
+	if r.Regs == nil || r.Regs.GDPR == nil {
+		return nil
+	}
+
+	// read and clear 2.6 location
+	gdpr26 := r.Regs.GDPR
+	r.Regs.GDPR = nil
+
+	// move to 2.5 location
+	regExt, err := r.GetRegExt()
+	if err != nil {
+		return err
+	}
+	regExt.SetGDPR(gdpr26)
+
+	return nil
+}
+
+// moveConsentFrom26To25 modifies the request to move the OpenRTB 2.6 GDPR consent
+// field (req.user.consent) to the OpenRTB 2.5 location (req.user.ext.consent). If
+// the OpenRTB 2.5 location is already present it may be overwritten. The OpenRTB 2.5
+// location is expected to be empty.
+func moveConsentFrom26To25(r *RequestWrapper) error {
+	if r.User == nil || len(r.User.Consent) == 0 {
+		return nil
+	}
+
+	// read and clear 2.6 location
+	consent26 := r.User.Consent
+	r.User.Consent = ""
+
+	// move to 2.5 location
+	userExt, err := r.GetUserExt()
+	if err != nil {
+		return err
+	}
+	userExt.SetConsent(&consent26)
+
+	return nil
+}
+
+// moveUSPrivacyFrom26To25 modifies the request to move the OpenRTB 2.6 US Privacy (CCPA)
+// consent string (req.regs.us_privacy) to the OpenRTB 2.5 location (req.regs.ext.us_privacy).
+// If the OpenRTB 2.5 location is already present it may be overwritten. The OpenRTB 2.5
+// location is expected to be empty.
+func moveUSPrivacyFrom26To25(r *RequestWrapper) error {
+	if r.Regs == nil || len(r.Regs.USPrivacy) == 0 {
+		return nil
+	}
+
+	// read and clear 2.6 location
+	usprivacy26 := r.Regs.USPrivacy
+	r.Regs.USPrivacy = ""
+
+	// move to 2.5 location
+	regExt, err := r.GetRegExt()
+	if err != nil {
+		return err
+	}
+	regExt.SetUSPrivacy(usprivacy26)
+
+	return nil
+}
+
+// moveEIDFrom26To25 modifies the request to move the OpenRTB 2.6 external identifiers
+// (req.user.eids) to the OpenRTB 2.5 location (req.user.ext.eids). If the OpenRTB 2.5
+// location is already present it may be overwritten. The OpenRTB 2.5 location is
+// expected to be empty.
+func moveEIDFrom26To25(r *RequestWrapper) error {
+	if r.User == nil || r.User.EIDs == nil {
+		return nil
+	}
+
+	// read and clear 2.6 location
+	eid26 := r.User.EIDs
+	r.User.EIDs = nil
+
+	// move to 2.5 location
+	userExt, err := r.GetUserExt()
+	if err != nil {
+		return err
+	}
+	userExt.SetEid(&eid26)
+
+	return nil
+}
diff --git a/openrtb_ext/convert_up.go b/openrtb_ext/convert_up.go
new file mode 100644
index 00000000..6ecf0266
--- /dev/null
+++ b/openrtb_ext/convert_up.go
@@ -0,0 +1,150 @@
+package openrtb_ext
+
+import (
+	"fmt"
+
+	"github.com/mxmCherry/openrtb/v16/openrtb2"
+)
+
+func ConvertUpTo26(r *RequestWrapper) error {
+	if err := convertUpEnsureExt(r); err != nil {
+		return err
+	}
+
+	// schain
+	moveSupplyChainFrom24To25(r)
+	moveSupplyChainFrom25To26(r)
+
+	// gdpr
+	moveGDPRFrom25To26(r)
+	moveConsentFrom25To26(r)
+
+	// ccpa
+	moveUSPrivacyFrom25To26(r)
+
+	// eid
+	moveEIDFrom25To26(r)
+
+	return nil
+}
+
+// convertUpEnsureExt gets all extension objects required for migration to verify there
+// are no access errors.
+func convertUpEnsureExt(r *RequestWrapper) error {
+	if _, err := r.GetRequestExt(); err != nil {
+		return fmt.Errorf("req.ext is invalid: %v", err)
+	}
+
+	if _, err := r.GetSourceExt(); err != nil {
+		return fmt.Errorf("req.source.ext is invalid: %v", err)
+	}
+
+	if _, err := r.GetRegExt(); err != nil {
+		return fmt.Errorf("req.regs.ext is invalid: %v", err)
+	}
+
+	if _, err := r.GetUserExt(); err != nil {
+		return fmt.Errorf("req.user.ext is invalid: %v", err)
+	}
+
+	return nil
+}
+
+// moveSupplyChainFrom24To25 modifies the request to move the OpenRTB 2.4 supply chain
+// object (req.ext.schain) to the OpenRTB 2.5 location (req.source.ext.schain). If the
+// OpenRTB 2.5 location is already present the OpenRTB 2.4 supply chain object is dropped.
+func moveSupplyChainFrom24To25(r *RequestWrapper) {
+	// read and clear 2.4 location
+	reqExt, _ := r.GetRequestExt()
+	schain24 := reqExt.GetSChain()
+	reqExt.SetSChain(nil)
+
+	// move to 2.5 location if not already present
+	sourceExt, _ := r.GetSourceExt()
+	if sourceExt.GetSChain() == nil {
+		sourceExt.SetSChain(schain24)
+	}
+}
+
+// moveSupplyChainFrom25To26 modifies the request to move the OpenRTB 2.5 supply chain
+// object (req.source.ext.schain) to the OpenRTB 2.6 location (req.source.schain). If the
+// OpenRTB 2.6 location is already present the OpenRTB 2.5 supply chain object is dropped.
+func moveSupplyChainFrom25To26(r *RequestWrapper) {
+	// read and clear 2.5 location
+	sourceExt, _ := r.GetSourceExt()
+	schain25 := sourceExt.GetSChain()
+	sourceExt.SetSChain(nil)
+
+	// move to 2.6 location if not already present
+	if schain25 != nil {
+		// source may be nil if moved indirectly from an OpenRTB 2.4 location, since the ext
+		// is not defined on the source object.
+		if r.Source == nil {
+			r.Source = &openrtb2.Source{}
+		}
+
+		if r.Source.SChain == nil {
+			r.Source.SChain = schain25
+		}
+	}
+}
+
+// moveGDPRFrom25To26 modifies the request to move the OpenRTB 2.5 GDPR signal field
+// (req.regs.ext.gdpr) to the OpenRTB 2.6 location (req.regs.gdpr). If the OpenRTB 2.6
+// location is already present the OpenRTB 2.5 GDPR signal is dropped.
+func moveGDPRFrom25To26(r *RequestWrapper) {
+	// read and clear 2.5 location
+	regsExt, _ := r.GetRegExt()
+	gdpr25 := regsExt.GetGDPR()
+	regsExt.SetGDPR(nil)
+
+	// move to 2.6 location
+	if gdpr25 != nil && r.Regs.GDPR == nil {
+		r.Regs.GDPR = gdpr25
+	}
+}
+
+// moveConsentFrom25To26 modifies the request to move the OpenRTB 2.5 GDPR consent field
+// (req.user.ext.consent) to the OpenRTB 2.6 location (req.user.consent). If the OpenRTB 2.6
+// location is already present the OpenRTB 2.5 GDPR consent is dropped.
+func moveConsentFrom25To26(r *RequestWrapper) {
+	// read and clear 2.5 location
+	userExt, _ := r.GetUserExt()
+	consent25 := userExt.GetConsent()
+	userExt.SetConsent(nil)
+
+	// move to 2.6 location
+	if consent25 != nil && r.User.Consent == "" {
+		r.User.Consent = *consent25
+	}
+}
+
+// moveUSPrivacyFrom25To26 modifies the request to move the OpenRTB 2.5 US Privacy (CCPA)
+// consent string (req.regs.ext.usprivacy) to the OpenRTB 2.6 location (req.regs.usprivacy).
+// If the OpenRTB 2.6 location is already present the OpenRTB 2.5 consent string is dropped.
+func moveUSPrivacyFrom25To26(r *RequestWrapper) {
+	// read and clear 2.5 location
+	regsExt, _ := r.GetRegExt()
+	usPrivacy25 := regsExt.GetUSPrivacy()
+	regsExt.SetUSPrivacy("")
+
+	// move to 2.6 location
+	if usPrivacy25 != "" && r.Regs.USPrivacy == "" {
+		r.Regs.USPrivacy = usPrivacy25
+	}
+}
+
+// moveEIDFrom25To26 modifies the request to move the OpenRTB 2.5 external identifiers
+// (req.user.ext.eids) to the OpenRTB 2.6 location (req.user.eids). If the OpenRTB 2.6
+// location is already present the OpenRTB 2.5 external identifiers is dropped.
+func moveEIDFrom25To26(r *RequestWrapper) {
+	// read and clear 2.5 location
+	userExt, _ := r.GetUserExt()
+	eid25 := userExt.GetEid()
+	userExt.SetEid(nil)
+
+	// move to 2.6 location
+	if eid25 != nil && r.User.EIDs == nil {
+		r.User.EIDs = *eid25
+	}
+}
diff --git a/openrtb_ext/imp_adnuntius.go b/openrtb_ext/imp_adnuntius.go
index bde30f05..b1e41e80 100644
--- a/openrtb_ext/imp_adnuntius.go
+++ b/openrtb_ext/imp_adnuntius.go
@@ -1,6 +1,7 @@
 package openrtb_ext
 
 type ImpExtAdnunitus struct {
-	Auid    string `json:"auId"`
-	Network string `json:"network"`
+	Auid      string `json:"auId"`
+	Network   string `json:"network"`
+	NoCookies bool   `json:noCookies`
 }
diff --git a/openrtb_ext/imp_andbeyondmedia.go b/openrtb_ext/imp_andbeyondmedia.go
new file mode 100644
index 00000000..68b65a3c
--- /dev/null
+++ b/openrtb_ext/imp_andbeyondmedia.go
@@ -0,0 +1,5 @@
+package openrtb_ext
+
+type ImpExtAndBeyondMedia struct {
+	PlacementID string `json:"placementId"`
+}
diff --git a/openrtb_ext/imp_seedingAlliance.go b/openrtb_ext/imp_seedingAlliance.go
new file mode 100644
index 00000000..759683ad
--- /dev/null
+++ b/openrtb_ext/imp_seedingAlliance.go
@@ -0,0 +1,5 @@
+package openrtb_ext
+
+type ImpExtSeedingAlliance struct {
+	AdUnitID string `json:"adUnitID"`
+}
diff --git a/openrtb_ext/request_wrapper.go b/openrtb_ext/request_wrapper.go
index 51ce753d..380d14d1 100644
--- a/openrtb_ext/request_wrapper.go
+++ b/openrtb_ext/request_wrapper.go
@@ -299,7 +299,9 @@ func (ue *UserExt) unmarshal(extJson json.RawMessage) error {
 	if len(ue.ext) != 0 || ue.Dirty() {
 		return nil
 	}
+
 	ue.ext = make(map[string]json.RawMessage)
+
 	if len(extJson) == 0 {
 		return nil
 	}
@@ -458,26 +460,34 @@ func (re *RequestExt) unmarshal(extJson json.RawMessage) error {
 	if len(re.ext) != 0 || re.Dirty() {
 		return nil
 	}
+
 	re.ext = make(map[string]json.RawMessage)
+
 	if len(extJson) == 0 {
 		return nil
 	}
-	err := json.Unmarshal(extJson, &re.ext)
-	if err != nil {
+
+	if err := json.Unmarshal(extJson, &re.ext); err != nil {
 		return err
 	}
+
 	prebidJson, hasPrebid := re.ext["prebid"]
 	if hasPrebid {
 		re.prebid = &ExtRequestPrebid{}
-		err = json.Unmarshal(prebidJson, re.prebid)
+		if err := json.Unmarshal(prebidJson, re.prebid); err != nil {
+			return err
+		}
 	}
+
 	schainJson, hasSChain := re.ext["schain"]
 	if hasSChain {
 		re.schain = &openrtb2.SupplyChain{}
-		err = json.Unmarshal(schainJson, re.schain)
+		if err := json.Unmarshal(schainJson, re.schain); err != nil {
+			return err
+		}
 	}
 
-	return err
+	return nil
 }
 
 func (re *RequestExt) marshal() (json.RawMessage, error) {
@@ -587,21 +597,26 @@ func (de *DeviceExt) unmarshal(extJson json.RawMessage) error {
 	if len(de.ext) != 0 || de.Dirty() {
 		return nil
 	}
+
 	de.ext = make(map[string]json.RawMessage)
+
 	if len(extJson) == 0 {
 		return nil
 	}
-	err := json.Unmarshal(extJson, &de.ext)
-	if err != nil {
+
+	if err := json.Unmarshal(extJson, &de.ext); err != nil {
 		return err
 	}
+
 	prebidJson, hasPrebid := de.ext["prebid"]
 	if hasPrebid {
 		de.prebid = &ExtDevicePrebid{}
-		err = json.Unmarshal(prebidJson, de.prebid)
+		if err := json.Unmarshal(prebidJson, de.prebid); err != nil {
+			return err
+		}
 	}
 
-	return err
+	return nil
 }
 
 func (de *DeviceExt) marshal() (json.RawMessage, error) {
@@ -674,21 +689,26 @@ func (ae *AppExt) unmarshal(extJson json.RawMessage) error {
 	if len(ae.ext) != 0 || ae.Dirty() {
 		return nil
 	}
+
 	ae.ext = make(map[string]json.RawMessage)
+
 	if len(extJson) == 0 {
 		return nil
 	}
-	err := json.Unmarshal(extJson, &ae.ext)
-	if err != nil {
+
+	if err := json.Unmarshal(extJson, &ae.ext); err != nil {
 		return err
 	}
+
 	prebidJson, hasPrebid := ae.ext["prebid"]
 	if hasPrebid {
 		ae.prebid = &ExtAppPrebid{}
-		err = json.Unmarshal(prebidJson, ae.prebid)
+		if err := json.Unmarshal(prebidJson, ae.prebid); err != nil {
+			return err
+		}
 	}
 
-	return err
+	return nil
 }
 
 func (ae *AppExt) marshal() (json.RawMessage, error) {
@@ -753,6 +773,8 @@ func (ae *AppExt) SetPrebid(prebid *ExtAppPrebid) {
 type RegExt struct {
 	ext            map[string]json.RawMessage
 	extDirty       bool
+	gdpr           *int8
+	gdprDirty      bool
 	usPrivacy      string
 	usPrivacyDirty bool
 }
@@ -761,23 +783,48 @@ func (re *RegExt) unmarshal(extJson json.RawMessage) error {
 	if len(re.ext) != 0 || re.Dirty() {
 		return nil
 	}
+
 	re.ext = make(map[string]json.RawMessage)
+
 	if len(extJson) == 0 {
 		return nil
 	}
-	err := json.Unmarshal(extJson, &re.ext)
-	if err != nil {
+
+	if err := json.Unmarshal(extJson, &re.ext); err != nil {
 		return err
 	}
+
+	gdprJson, hasGDPR := re.ext["gdpr"]
+	if hasGDPR {
+		if err := json.Unmarshal(gdprJson, &re.gdpr); err != nil {
+			return errors.New("gdpr must be an integer")
+		}
+	}
+
 	uspJson, hasUsp := re.ext["us_privacy"]
 	if hasUsp {
-		err = json.Unmarshal(uspJson, &re.usPrivacy)
+		if err := json.Unmarshal(uspJson, &re.usPrivacy); err != nil {
+			return err
+		}
 	}
 
-	return err
+	return nil
 }
 
 func (re *RegExt) marshal() (json.RawMessage, error) {
+	if re.gdprDirty {
+		if re.gdpr != nil {
+			rawjson, err := json.Marshal(re.gdpr)
+			if err != nil {
+				return nil, err
+			}
+			re.ext["gdpr"] = rawjson
+		} else {
+			delete(re.ext, "gdpr")
+		}
+		re.gdprDirty = false
+	}
+
 	if re.usPrivacyDirty {
 		if len(re.usPrivacy) > 0 {
 			rawjson, err := json.Marshal(re.usPrivacy)
@@ -799,7 +846,7 @@ func (re *RegExt) marshal() (json.RawMessage, error) {
 }
 
 func (re *RegExt) Dirty() bool {
-	return re.extDirty || re.usPrivacyDirty
+	return re.extDirty || re.gdprDirty || re.usPrivacyDirty
 }
 
 func (re *RegExt) GetExt() map[string]json.RawMessage {
@@ -815,6 +862,16 @@ func (re *RegExt) SetExt(ext map[string]json.RawMessage) {
 	re.extDirty = true
 }
 
+func (re *RegExt) GetGDPR() *int8 {
+	gdpr := re.gdpr
+	return gdpr
+}
+
+func (re *RegExt) SetGDPR(gdpr *int8) {
+	re.gdpr = gdpr
+	re.gdprDirty = true
+}
+
 func (re *RegExt) GetUSPrivacy() string {
 	uSPrivacy := re.usPrivacy
 	return uSPrivacy
@@ -840,23 +897,25 @@ func (se *SiteExt) unmarshal(extJson json.RawMessage) error {
 	if len(se.ext) != 0 || se.Dirty() {
 		return nil
 	}
+
 	se.ext = make(map[string]json.RawMessage)
+
 	if len(extJson) == 0 {
 		return nil
 	}
-	err := json.Unmarshal(extJson, &se.ext)
-	if err != nil {
+
+	if err := json.Unmarshal(extJson, &se.ext); err != nil {
 		return err
 	}
-	AmpJson, hasAmp := se.ext["amp"]
+
+	ampJson, hasAmp := se.ext["amp"]
 	if hasAmp {
-		err = json.Unmarshal(AmpJson, &se.amp)
-		if err != nil {
-			err = errors.New(`request.site.ext.amp must be either 1, 0, or undefined`)
+		if err := json.Unmarshal(ampJson, &se.amp); err != nil {
+			return errors.New(`request.site.ext.amp must be either 1, 0, or undefined`)
 		}
 	}
 
-	return err
+	return nil
 }
 
 func (se *SiteExt) marshal() (json.RawMessage, error) {
@@ -921,20 +980,25 @@ func (se *SourceExt) unmarshal(extJson json.RawMessage) error {
 	if len(se.ext) != 0 || se.Dirty() {
 		return nil
 	}
+
 	se.ext = make(map[string]json.RawMessage)
+
 	if len(extJson) == 0 {
 		return nil
 	}
-	err := json.Unmarshal(extJson, &se.ext)
-	if err != nil {
+
+	if err := json.Unmarshal(extJson, &se.ext); err != nil {
 		return err
 	}
+
 	schainJson, hasSChain := se.ext["schain"]
 	if hasSChain {
-		err = json.Unmarshal(schainJson, &se.schain)
+		if err := json.Unmarshal(schainJson, &se.schain); err != nil {
+			return err
+		}
 	}
 
-	return err
+	return nil
 }
 
 func (se *SourceExt) marshal() (json.RawMessage, error) {
diff --git a/static/bidder-info/andbeyondmedia.yaml b/static/bidder-info/andbeyondmedia.yaml
new file mode 100644
index 00000000..34db6782
--- /dev/null
+++ b/static/bidder-info/andbeyondmedia.yaml
@@ -0,0 +1,14 @@
+maintainer:
+  email: "sysengg@andbeyond.media"
+capabilities:
+  site:
+    mediaTypes:
+    - banner
+    - video
+    - native
+    
+  app:
+    mediaTypes:
+    - banner
+    - video
+    - native
diff --git a/static/bidder-info/seedingAlliance.yaml b/static/bidder-info/seedingAlliance.yaml
new file mode 100644
index 00000000..10f37dde
--- /dev/null
+++ b/static/bidder-info/seedingAlliance.yaml
@@ -0,0 +1,12 @@
+maintainer:
+  email: tech@seeding-alliance.de
+gvlVendorID: 371
+capabilities:
+  site:
+    mediaTypes:
+      - native
+      - banner
+userSync:
+  redirect:
+    url: https://dmp.nativendo.de/set-uuid?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&redirect_url={{.RedirectURL}}
+    userMacro: $UID
diff --git a/static/bidder-info/yssp.yaml b/static/bidder-info/yssp.yaml
deleted file mode 100644
index a1a20e0c..00000000
--- a/static/bidder-info/yssp.yaml
+++ /dev/null
@@ -1,15 +0,0 @@
-maintainer:
-  email: "hb-fe-tech@oath.com"
-gvlVendorID: 25
-capabilities:
-  app:
-    mediaTypes:
-      - banner
-  site:
-    mediaTypes:
-      - banner
-userSync:
-  # yssp supports user syncing, but requires configuration by the host. contact this
-  # bidder directly at the email address in this file to ask about enabling user sync.
-  supports:
-    - redirect
\ No newline at end of file
diff --git a/static/bidder-params/adnuntius.json b/static/bidder-params/adnuntius.json
index a8e65bea..ff975501 100644
--- a/static/bidder-params/adnuntius.json
+++ b/static/bidder-params/adnuntius.json
@@ -12,6 +12,10 @@
         "network": {
             "type": "string",
             "description": "Network if required"
+        },
+        "noCookies": {
+            "type": "boolean",
+            "description": "Disable cookies being set by the ad server."
         }
     },
 
diff --git a/static/bidder-params/andbeyondmedia.json b/static/bidder-params/andbeyondmedia.json
new file mode 100644
index 00000000..999e1f37
--- /dev/null
+++ b/static/bidder-params/andbeyondmedia.json
@@ -0,0 +1,15 @@
+{
+    "$schema": "http://json-schema.org/draft-04/schema#",
+    "title": "AndBeyond.Media Adapter Params",
+    "description": "A schema which validates params accepted by the AndBeyond.Media adapter",
+    "type": "object",
+  
+    "properties": {
+      "placementId": {
+        "type": "string",
+        "minLength": 1,
+        "description": "Placement ID"
+      }
+    },
+    "required": ["placementId"]
+  }
\ No newline at end of file
diff --git a/static/bidder-params/seedingAlliance.json b/static/bidder-params/seedingAlliance.json
new file mode 100644
index 00000000..cf9aa375
--- /dev/null
+++ b/static/bidder-params/seedingAlliance.json
@@ -0,0 +1,16 @@
+{
+    "$schema": "http://json-schema.org/draft-04/schema#",
+    "title": "Seeding Alliance Adapter Params",
+    "description": "A schema which validates params accepted by the Seeding Alliance adapter",
+    "type": "object",
+    "properties": {
+        "adUnitId": {
+            "type": "string",
+            "description": "Ad Unit ID",
+            "minLength": 1
+        }
+    },
+    "required": [
+        "adUnitId"
+    ]
+}
\ No newline at end of file
diff --git a/static/bidder-params/yssp.json b/static/bidder-params/yssp.json
deleted file mode 100644
index fdadc747..00000000
--- a/static/bidder-params/yssp.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
-	"$schema": "http://json-schema.org/draft-04/schema#",
-	"title": "YSSP Adapter Params",
-	"description": "A schema which validates params accepted by the YSSP adapter",
-	"type": "object",
-	"properties": {
-		"dcn": {
-			"type": "string",
-			"minLength": 1,
-			"description": "Site ID provided by One Mobile"
-		},
-		"pos": {
-			"type": "string",
-			"minLength": 1,
-			"description": "Placement ID"
-		}
-	},
-	"required": ["dcn", "pos"]
-}


Test patch:
diff --git a/adapters/adnuntius/adnuntiustest/supplemental/check-noCookies-parameter.json b/adapters/adnuntius/adnuntiustest/supplemental/check-noCookies-parameter.json
new file mode 100644
index 00000000..b0d74565
--- /dev/null
+++ b/adapters/adnuntius/adnuntiustest/supplemental/check-noCookies-parameter.json
@@ -0,0 +1,104 @@
+{
+	"mockBidRequest": {
+		"id": "test-request-id",
+		"user": {
+			"id": "1kjh3429kjh295jkl"
+		},
+		"imp": [
+			{
+				"id": "test-imp-id",
+				"banner": {
+					"format": [
+						{
+							"w": 300,
+							"h": 250
+						},
+						{
+							"w": 300,
+							"h": 600
+						}
+					]
+				},
+				"ext": {
+					"bidder": {
+						"auId": "123",
+						"noCookies": true
+					}
+				}
+			}
+		]
+	},
+	"httpCalls": [
+		{
+			"expectedRequest": {
+				"uri": "http://whatever.url?format=json&noCookies=true&tzo=0",
+				"body": {
+					"adUnits": [
+						{
+							"auId": "123",
+							"targetId": "123-test-imp-id",
+							"dimensions": [[300,250],[300,600]]
+						}
+					],
+					"metaData": {
+						"usi": "1kjh3429kjh295jkl"
+					},
+					"context": "unknown"
+				}
+			},
+			"mockResponse": {
+				"status": 200,
+				"body": {
+					"adUnits": [
+						{
+							"auId": "0000000000000123",
+							"targetId": "123-test-imp-id",
+							"html": "<ADCODE>",
+							"responseId": "adn-rsp-900646517",
+							"ads": [
+								{
+									"destinationUrls": {
+										"url": "http://www.google.com"
+									},
+									"bid": {
+										"amount": 20.0,
+										"currency": "NOK"
+									},
+									"adId": "adn-id-1559784094",
+									"creativeWidth": "980",
+									"creativeHeight": "240",
+									"creativeId": "jn9hpzvlsf8cpdmm",
+									"lineItemId": "q7y9qm5b0xt9htrv"
+								}
+							]
+						}
+					]
+				}
+			}
+		}
+	],
+	"expectedBidResponses": [
+		{
+			"bids": [
+				{
+					"bid": {
+						"id": "adn-id-1559784094",
+						"impid": "test-imp-id",
+						"price": 20000,
+						"adm": "<ADCODE>",
+						"adid": "adn-id-1559784094",
+						"adomain": [
+							"google.com"
+						],
+						"cid": "q7y9qm5b0xt9htrv",
+						"crid": "jn9hpzvlsf8cpdmm",
+						"w": 980,
+						"h": 240
+					},
+					"type": "banner"
+				}
+			],
+			"currency": "NOK"
+		}
+	]
+}
diff --git a/adapters/andbeyondmedia/andbeyondmedia_test.go b/adapters/andbeyondmedia/andbeyondmedia_test.go
new file mode 100644
index 00000000..bba86e4f
--- /dev/null
+++ b/adapters/andbeyondmedia/andbeyondmedia_test.go
@@ -0,0 +1,20 @@
+package andbeyondmedia
+
+import (
+	"testing"
+
+	"github.com/prebid/prebid-server/adapters/adapterstest"
+	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+func TestJsonSamples(t *testing.T) {
+	bidder, buildErr := Builder(openrtb_ext.BidderAndBeyondMedia, config.Adapter{
+		Endpoint: "http://backend.andbeyond.media/pserver"})
+
+	if buildErr != nil {
+		t.Fatalf("Builder returned unexpected error %v", buildErr)
+	}
+
+	adapterstest.RunJSONBidderTest(t, "andbeyondmediatest", bidder)
+}
diff --git a/adapters/andbeyondmedia/andbeyondmediatest/exemplary/simple-banner.json b/adapters/andbeyondmedia/andbeyondmediatest/exemplary/simple-banner.json
new file mode 100644
index 00000000..136fc666
--- /dev/null
+++ b/adapters/andbeyondmedia/andbeyondmediatest/exemplary/simple-banner.json
@@ -0,0 +1,133 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "device": {
+            "ip": "123.123.123.123",
+            "ua": "iPad"
+        },
+        "app": {
+            "id": "1",
+            "bundle": "com.wls.testwlsapplication"
+        },
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "tagid": "test",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 300,
+                            "h": 250
+                        },
+                        {
+                            "w": 300,
+                            "h": 600
+                        }
+                    ]
+                },
+                "ext": {
+                    "bidder": {
+                        "placementId": "test"
+                    }
+                }
+            }
+        ]
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "http://backend.andbeyond.media/pserver",
+                "body": {
+                    "id": "test-request-id",
+                    "imp": [
+                        {
+                            "id": "test-imp-id",
+                            "tagid": "test",
+                            "banner": {
+                                "format": [
+                                    {
+                                        "w": 300,
+                                        "h": 250
+                                    },
+                                    {
+                                        "w": 300,
+                                        "h": 600
+                                    }
+                                ]
+                            },
+                            "ext": {
+                                "bidder": {
+                                    "placementId": "test",
+                                    "type": "publisher"
+                                }
+                            }
+                        }
+                    ],
+                    "app": {
+                        "id": "1",
+                        "bundle": "com.wls.testwlsapplication"
+                    },
+                    "device": {
+                        "ip": "123.123.123.123",
+                        "ua": "iPad"
+                    }
+                }
+            },
+            "mockResponse": {
+                "status": 200,
+                "body": {
+                    "id": "test-request-id",
+                    "seatbid": [
+                        {
+                            "bid": [
+                                {
+                                    "id": "test_bid_id",
+                                    "impid": "test-imp-id",
+                                    "price": 0.27543,
+                                    "adm": "<iframe id=\"adm-banner-16\" width=\"300\" height=\"250\" frameborder=\"0\" marginheight=\"0\" marginwidth=\"0\" style=\"{overflow:hidden}\" src=\"http://backend.andbeyond.media/pserver&k=882b2510ed6d6c94fa69c99aa522a708\"></iframe>",
+                                    "cid": "test_cid",
+                                    "crid": "test_crid",
+                                    "dealid": "test_dealid",
+                                    "w": 300,
+                                    "h": 250,
+                                    "ext": {
+                                        "prebid": {
+                                            "type": "banner"
+                                        }
+                                    }
+                                }
+                            ],
+                            "seat": "andbeyondmedia"
+                        }
+                    ],
+                    "cur": "USD"
+                }
+            }
+        }
+    ],
+    "expectedBidResponses": [
+        {
+            "bids": [
+                {
+                    "bid": {
+                        "id": "test_bid_id",
+                        "impid": "test-imp-id",
+                        "price": 0.27543,
+                        "adm": "<iframe id=\"adm-banner-16\" width=\"300\" height=\"250\" frameborder=\"0\" marginheight=\"0\" marginwidth=\"0\" style=\"{overflow:hidden}\" src=\"http://backend.andbeyond.media/pserver&k=882b2510ed6d6c94fa69c99aa522a708\"></iframe>",
+                        "cid": "test_cid",
+                        "crid": "test_crid",
+                        "dealid": "test_dealid",
+                        "w": 300,
+                        "h": 250,
+                        "ext": {
+                            "prebid": {
+                                "type": "banner"
+                            }
+                        }
+                    },
+                    "type": "banner"
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/adapters/andbeyondmedia/andbeyondmediatest/exemplary/simple-native.json b/adapters/andbeyondmedia/andbeyondmediatest/exemplary/simple-native.json
new file mode 100644
index 00000000..95f7f335
--- /dev/null
+++ b/adapters/andbeyondmedia/andbeyondmediatest/exemplary/simple-native.json
@@ -0,0 +1,117 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "device": {
+            "ip": "123.123.123.123",
+            "ua": "iPad"
+        },
+        "app": {
+            "id": "1",
+            "bundle": "com.wls.testwlsapplication"
+        },
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "tagid": "test",
+                "native": {
+                    "request": "{\"ver\":\"1.1\",\"layout\":1,\"adunit\":2,\"plcmtcnt\":6,\"plcmttype\":4,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":75}},{\"id\":2,\"required\":1,\"img\":{\"wmin\":492,\"hmin\":328,\"type\":3,\"mimes\":[\"image/jpeg\",\"image/jpg\",\"image/png\"]}},{\"id\":4,\"required\":0,\"data\":{\"type\":6}},{\"id\":5,\"required\":0,\"data\":{\"type\":7}},{\"id\":6,\"required\":0,\"data\":{\"type\":1,\"len\":20}}]}",
+                    "ver": "1.1"
+                },
+                "ext": {
+                    "bidder": {
+                        "placementId": "test"
+                    }
+                }
+            }
+        ]
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "http://backend.andbeyond.media/pserver",
+                "body": {
+                    "id": "test-request-id",
+                    "imp": [
+                        {
+                            "id": "test-imp-id",
+                            "tagid": "test",
+                            "native": {
+                                "request": "{\"ver\":\"1.1\",\"layout\":1,\"adunit\":2,\"plcmtcnt\":6,\"plcmttype\":4,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":75}},{\"id\":2,\"required\":1,\"img\":{\"wmin\":492,\"hmin\":328,\"type\":3,\"mimes\":[\"image/jpeg\",\"image/jpg\",\"image/png\"]}},{\"id\":4,\"required\":0,\"data\":{\"type\":6}},{\"id\":5,\"required\":0,\"data\":{\"type\":7}},{\"id\":6,\"required\":0,\"data\":{\"type\":1,\"len\":20}}]}",
+                                "ver": "1.1"
+                            },
+                            "ext": {
+                                "bidder": {
+                                    "placementId": "test",
+                                    "type": "publisher"
+                                }
+                            }
+                        }
+                    ],
+                    "app": {
+                        "id": "1",
+                        "bundle": "com.wls.testwlsapplication"
+                    },
+                    "device": {
+                        "ip": "123.123.123.123",
+                        "ua": "iPad"
+                    }
+                }
+            },
+            "mockResponse": {
+                "status": 200,
+                "body": {
+                    "id": "test-request-id",
+                    "seatbid": [
+                        {
+                            "bid": [
+                                {
+                                    "id": "test_bid_id",
+                                    "impid": "test-imp-id",
+                                    "price": 0.27543,
+                                    "adm": "<iframe id=\"adm-banner-16\" width=\"300\" height=\"250\" frameborder=\"0\" marginheight=\"0\" marginwidth=\"0\" style=\"{overflow:hidden}\" src=\"http://backend.andbeyond.media/pserver&k=882b2510ed6d6c94fa69c99aa522a708\"></iframe>",
+                                    "cid": "test_cid",
+                                    "crid": "test_crid",
+                                    "dealid": "test_dealid",
+                                    "w": 300,
+                                    "h": 250,
+                                    "ext": {
+                                        "prebid": {
+                                            "type": "native"
+                                        }
+                                    }
+                                }
+                            ],
+                            "seat": "andbeyondmedia"
+                        }
+                    ],
+                    "cur": "USD"
+                }
+            }
+        }
+    ],
+    "expectedBidResponses": [
+        {
+            "bids": [
+                {
+                    "bid": {
+                        "id": "test_bid_id",
+                        "impid": "test-imp-id",
+                        "price": 0.27543,
+                        "adm": "<iframe id=\"adm-banner-16\" width=\"300\" height=\"250\" frameborder=\"0\" marginheight=\"0\" marginwidth=\"0\" style=\"{overflow:hidden}\" src=\"http://backend.andbeyond.media/pserver&k=882b2510ed6d6c94fa69c99aa522a708\"></iframe>",
+                        "cid": "test_cid",
+                        "crid": "test_crid",
+                        "dealid": "test_dealid",
+                        "w": 300,
+                        "h": 250,
+                        "ext": {
+                            "prebid": {
+                                "type": "native"
+                            }
+                        }
+                    },
+                    "type": "native"
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/adapters/andbeyondmedia/andbeyondmediatest/exemplary/simple-video.json b/adapters/andbeyondmedia/andbeyondmediatest/exemplary/simple-video.json
new file mode 100644
index 00000000..e11129f4
--- /dev/null
+++ b/adapters/andbeyondmedia/andbeyondmediatest/exemplary/simple-video.json
@@ -0,0 +1,128 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "device": {
+            "ip": "123.123.123.123",
+            "ua": "iPad"
+        },
+        "app": {
+            "id": "1",
+            "bundle": "com.wls.testwlsapplication"
+        },
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "tagid": "test",
+                "video": {
+                    "mimes": [
+                        "video/mp4"
+                    ],
+                    "protocols": [
+                        2,
+                        5
+                    ],
+                    "w": 1024,
+                    "h": 576
+                },
+                "ext": {
+                    "bidder": {
+                        "placementId": "test"
+                    }
+                }
+            }
+        ]
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "http://backend.andbeyond.media/pserver",
+                "body": {
+                    "id": "test-request-id",
+                    "device": {
+                        "ip": "123.123.123.123",
+                        "ua": "iPad"
+                    },
+                    "app": {
+                        "id": "1",
+                        "bundle": "com.wls.testwlsapplication"
+                    },
+                    "imp": [
+                        {
+                            "id": "test-imp-id",
+                            "tagid": "test",
+                            "video": {
+                                "mimes": [
+                                    "video/mp4"
+                                ],
+                                "protocols": [
+                                    2,
+                                    5
+                                ],
+                                "w": 1024,
+                                "h": 576
+                            },
+                            "ext": {
+                                "bidder": {
+                                    "placementId": "test",
+                                    "type": "publisher"
+                                }
+                            }
+                        }
+                    ]
+                }
+            },
+            "mockResponse": {
+                "status": 200,
+                "body": {
+                    "id": "test-request-id",
+                    "seatbid": [
+                        {
+                            "bid": [
+                                {
+                                    "id": "test_bid_id",
+                                    "impid": "test-imp-id",
+                                    "price": 0.27543,
+                                    "adm": "<VAST version=\"3.0\"><Ad id=\"20001\" sequence=\"1\"><InLine><AdSystem version=\"4.0\"><![CDATA[iabtechlab]]></AdSystem><AdTitle><![CDATA[Inline Simple Ad]]></AdTitle><Impression><![CDATA[]]></Impression><Creatives><Creative id=\"5480\" sequence=\"1\"><Linear><Duration>00:01:00</Duration><MediaFiles><MediaFile id=\"5246\" delivery=\"progressive\" type=\"video/mp4\" bitrate=\"600\" width=\"640\" height=\"360\" minBitrate=\"500\" maxBitrate=\"700\" scalable=\"1\" maintainAspectRatio=\"1\" codec=\"0\"><![CDATA[https://s0.2mdn.net/4253510/google_ddm_animation_480P.mp4]]></MediaFile></MediaFiles><VideoClicks><ClickThrough id=\"blog\"><![CDATA[https://example.com]]></ClickThrough></VideoClicks></Linear></Creative></Creatives></InLine></Ad></VAST>",
+                                    "cid": "test_cid",
+                                    "crid": "test_crid",
+                                    "dealid": "test_dealid",
+                                    "ext": {
+                                        "prebid": {
+                                            "type": "video"
+                                        }
+                                    }
+                                }
+                            ],
+                            "seat": "andbeyondmedia"
+                        }
+                    ],
+                    "cur": "USD"
+                }
+            }
+        }
+    ],
+    "expectedBidResponses": [
+        {
+            "currency": "USD",
+            "bids": [
+                {
+                    "bid": {
+                        "id": "test_bid_id",
+                        "impid": "test-imp-id",
+                        "price": 0.27543,
+                        "adm": "<VAST version=\"3.0\"><Ad id=\"20001\" sequence=\"1\"><InLine><AdSystem version=\"4.0\"><![CDATA[iabtechlab]]></AdSystem><AdTitle><![CDATA[Inline Simple Ad]]></AdTitle><Impression><![CDATA[]]></Impression><Creatives><Creative id=\"5480\" sequence=\"1\"><Linear><Duration>00:01:00</Duration><MediaFiles><MediaFile id=\"5246\" delivery=\"progressive\" type=\"video/mp4\" bitrate=\"600\" width=\"640\" height=\"360\" minBitrate=\"500\" maxBitrate=\"700\" scalable=\"1\" maintainAspectRatio=\"1\" codec=\"0\"><![CDATA[https://s0.2mdn.net/4253510/google_ddm_animation_480P.mp4]]></MediaFile></MediaFiles><VideoClicks><ClickThrough id=\"blog\"><![CDATA[https://example.com]]></ClickThrough></VideoClicks></Linear></Creative></Creatives></InLine></Ad></VAST>",
+                        "cid": "test_cid",
+                        "crid": "test_crid",
+                        "dealid": "test_dealid",
+                        "ext": {
+                            "prebid": {
+                                "type": "video"
+                            }
+                        }
+                    },
+                    "type": "video"
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/adapters/andbeyondmedia/andbeyondmediatest/exemplary/simple-web-banner.json b/adapters/andbeyondmedia/andbeyondmediatest/exemplary/simple-web-banner.json
new file mode 100644
index 00000000..cdd905c5
--- /dev/null
+++ b/adapters/andbeyondmedia/andbeyondmediatest/exemplary/simple-web-banner.json
@@ -0,0 +1,133 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "tagid": "test",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 300,
+                            "h": 250
+                        },
+                        {
+                            "w": 300,
+                            "h": 600
+                        }
+                    ]
+                },
+                "ext": {
+                    "bidder": {
+                        "placementId": "test"
+                    }
+                }
+            }
+        ],
+        "site": {
+            "id": "1",
+            "domain": "test.com"
+        },
+        "device": {
+            "ip": "123.123.123.123",
+            "ua": "Ubuntu"
+        }
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "http://backend.andbeyond.media/pserver",
+                "body": {
+                    "id": "test-request-id",
+                    "imp": [
+                        {
+                            "id": "test-imp-id",
+                            "tagid": "test",
+                            "banner": {
+                                "format": [
+                                    {
+                                        "w": 300,
+                                        "h": 250
+                                    },
+                                    {
+                                        "w": 300,
+                                        "h": 600
+                                    }
+                                ]
+                            },
+                            "ext": {
+                                "bidder": {
+                                    "placementId": "test",
+                                    "type": "publisher"
+                                }
+                            }
+                        }
+                    ],
+                    "site": {
+                        "id": "1",
+                        "domain": "test.com"
+                    },
+                    "device": {
+                        "ip": "123.123.123.123",
+                        "ua": "Ubuntu"
+                    }
+                }
+            },
+            "mockResponse": {
+                "status": 200,
+                "body": {
+                    "id": "test-request-id",
+                    "seatbid": [
+                        {
+                            "bid": [
+                                {
+                                    "id": "test_bid_id",
+                                    "impid": "test-imp-id",
+                                    "price": 0.27543,
+                                    "adm": "<iframe id=\"adm-banner-1\" width=\"468\" height=\"60\" frameborder=\"0\" marginheight=\"0\" marginwidth=\"0\" style=\"{overflow:hidden}\" src=\"http://backend.andbeyond.media/pserver&k=bc2d316f39931a07d9a8dd249bf85fc0\"></iframe>",
+                                    "cid": "test_cid",
+                                    "crid": "test_crid",
+                                    "dealid": "test_dealid",
+                                    "w": 468,
+                                    "h": 60,
+                                    "ext": {
+                                        "prebid": {
+                                            "type": "banner"
+                                        }
+                                    }
+                                }
+                            ],
+                            "seat": "andbeyondmedia"
+                        }
+                    ],
+                    "cur": "USD"
+                }
+            }
+        }
+    ],
+    "expectedBidResponses": [
+        {
+            "bids": [
+                {
+                    "bid": {
+                        "id": "test_bid_id",
+                        "impid": "test-imp-id",
+                        "price": 0.27543,
+                        "adm": "<iframe id=\"adm-banner-1\" width=\"468\" height=\"60\" frameborder=\"0\" marginheight=\"0\" marginwidth=\"0\" style=\"{overflow:hidden}\" src=\"http://backend.andbeyond.media/pserver&k=bc2d316f39931a07d9a8dd249bf85fc0\"></iframe>",
+                        "cid": "test_cid",
+                        "crid": "test_crid",
+                        "dealid": "test_dealid",
+                        "w": 468,
+                        "h": 60,
+                        "ext": {
+                            "prebid": {
+                                "type": "banner"
+                            }
+                        }
+                    },
+                    "type": "banner"
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/adapters/andbeyondmedia/andbeyondmediatest/supplemental/bad_media_type.json b/adapters/andbeyondmedia/andbeyondmediatest/supplemental/bad_media_type.json
new file mode 100644
index 00000000..e61b19b7
--- /dev/null
+++ b/adapters/andbeyondmedia/andbeyondmediatest/supplemental/bad_media_type.json
@@ -0,0 +1,86 @@
+{
+	"mockBidRequest": {
+        "id": "test-request-id",
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "ext": {
+                    "bidder": {
+                        "placementId": "test"
+                    }
+                }
+            }
+        ],
+        "app": {
+            "id": "1",
+            "bundle": "com.wls.testwlsapplication"
+        },
+        "device": {
+            "ip": "123.123.123.123",
+            "ifa": "sdjfksdf-dfsds-dsdg-dsgg"
+        }
+    },
+	"httpCalls": [{
+		"expectedRequest": {
+			"uri": "http://backend.andbeyond.media/pserver",
+			"body": {
+                "id": "test-request-id",
+                "imp": [
+                    {
+                        "id": "test-imp-id",
+                        "ext": {
+                            "bidder": {
+                                "placementId": "test",
+                                "type": "publisher"
+                            }
+                        }
+                    }
+                ],
+                "app": {
+                    "id": "1",
+                    "bundle": "com.wls.testwlsapplication"
+                },
+                "device": {
+                    "ip": "123.123.123.123",
+                    "ifa": "sdjfksdf-dfsds-dsdg-dsgg"
+                }
+            }
+		},
+		"mockResponse": {
+			"status": 200,
+			"body": {
+                "id": "test-request-id",
+                "seatbid": [
+                    {
+                        "bid": [
+                            {
+                                "id": "test_bid_id",
+                                "impid": "test-imp-id",
+                                "price": 0.27543,
+                                "adm": "<iframe id=\"adm-banner-16\" width=\"300\" height=\"250\" frameborder=\"0\" marginheight=\"0\" marginwidth=\"0\" style=\"{overflow:hidden}\" src=\"http://backend.andbeyond.media/pserver&k=882b2510ed6d6c94fa69c99aa522a708\"></iframe>",
+                                "cid": "test_cid",
+                                "crid": "test_crid",
+                                "dealid": "test_dealid",
+                                "w": 300,
+                                "h": 250,
+                                "ext": {
+                                    "prebid": {
+                                        "type": "banner"
+                                    }
+                                }
+                            }
+                        ],
+                        "seat": "andbeyondmedia"
+                    }
+                ],
+                "cur": "USD"
+            }
+        }
+    }],
+    "expectedMakeBidsErrors": [
+        {
+          "value": "Failed to find impression \"test-imp-id\"",
+          "comparison": "literal"
+        }
+    ]
+}
diff --git a/adapters/andbeyondmedia/andbeyondmediatest/supplemental/bad_response.json b/adapters/andbeyondmedia/andbeyondmediatest/supplemental/bad_response.json
new file mode 100644
index 00000000..e003ff89
--- /dev/null
+++ b/adapters/andbeyondmedia/andbeyondmediatest/supplemental/bad_response.json
@@ -0,0 +1,84 @@
+{
+	"mockBidRequest": {
+        "id": "test-request-id",
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 300,
+                            "h": 250
+                        },
+                        {
+                            "w": 300,
+                            "h": 600
+                        }
+                    ]
+                },
+                "ext": {
+                    "bidder": {
+                        "placementId": "test"
+                    }
+                }
+            }
+        ],
+        "app": {
+            "id": "1",
+            "bundle": "com.wls.testwlsapplication"
+        },
+        "device": {
+            "ip": "123.123.123.123",
+            "ifa": "sdjfksdf-dfsds-dsdg-dsgg"
+        }
+    },
+	"httpCalls": [{
+		"expectedRequest": {
+			"uri": "http://backend.andbeyond.media/pserver",
+			"body": {
+                "id": "test-request-id",
+                "imp": [
+                    {
+                        "id": "test-imp-id",
+                        "banner": {
+                            "format": [
+                                {
+                                    "w": 300,
+                                    "h": 250
+                                },
+                                {
+                                    "w": 300,
+                                    "h": 600
+                                }
+                            ]
+                        },
+                        "ext": {
+                            "bidder": {
+                                "placementId": "test",
+                                "type": "publisher"
+                            }
+                        }
+                    }
+                ],
+                "app": {
+                    "id": "1",
+                    "bundle": "com.wls.testwlsapplication"
+                },
+                "device": {
+                    "ip": "123.123.123.123",
+                    "ifa": "sdjfksdf-dfsds-dsdg-dsgg"
+                }
+            }
+		},
+		"mockResponse": {
+			"status": 200,
+			"body": ""
+        }
+    }],
+    "expectedMakeBidsErrors": [
+        {
+          "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse",
+          "comparison": "literal"
+        }
+    ]
+}
diff --git a/adapters/andbeyondmedia/andbeyondmediatest/supplemental/status-204.json b/adapters/andbeyondmedia/andbeyondmediatest/supplemental/status-204.json
new file mode 100644
index 00000000..1223dd01
--- /dev/null
+++ b/adapters/andbeyondmedia/andbeyondmediatest/supplemental/status-204.json
@@ -0,0 +1,79 @@
+{
+	"mockBidRequest": {
+        "id": "test-request-id",
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 300,
+                            "h": 250
+                        },
+                        {
+                            "w": 300,
+                            "h": 600
+                        }
+                    ]
+                },
+                "ext": {
+                    "bidder": {
+                        "placementId": "test"
+                    }
+                }
+            }
+        ],
+        "app": {
+            "id": "1",
+            "bundle": "com.wls.testwlsapplication"
+        },
+        "device": {
+            "ip": "123.123.123.123",
+            "ifa": "sdjfksdf-dfsds-dsdg-dsgg"
+        }
+    },
+	"httpCalls": [{
+		"expectedRequest": {
+			"uri": "http://backend.andbeyond.media/pserver",
+			"body": {
+                "id": "test-request-id",
+                "imp": [
+                    {
+                        "id": "test-imp-id",
+                        "banner": {
+                            "format": [
+                                {
+                                    "w": 300,
+                                    "h": 250
+                                },
+                                {
+                                    "w": 300,
+                                    "h": 600
+                                }
+                            ]
+                        },
+                        "ext": {
+                            "bidder": {
+                                "placementId": "test",
+                                "type": "publisher"
+                            }
+                        }
+                    }
+                ],
+                "app": {
+                    "id": "1",
+                    "bundle": "com.wls.testwlsapplication"
+                },
+                "device": {
+                    "ip": "123.123.123.123",
+                    "ifa": "sdjfksdf-dfsds-dsdg-dsgg"
+                }
+            }
+		},
+		"mockResponse": {
+			"status": 204,
+			"body": {}
+		}
+    }],
+    "expectedBidResponses": []
+}
diff --git a/adapters/andbeyondmedia/andbeyondmediatest/supplemental/status-not-200.json b/adapters/andbeyondmedia/andbeyondmediatest/supplemental/status-not-200.json
new file mode 100644
index 00000000..731b33de
--- /dev/null
+++ b/adapters/andbeyondmedia/andbeyondmediatest/supplemental/status-not-200.json
@@ -0,0 +1,84 @@
+{
+	"mockBidRequest": {
+        "id": "test-request-id",
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 300,
+                            "h": 250
+                        },
+                        {
+                            "w": 300,
+                            "h": 600
+                        }
+                    ]
+                },
+                "ext": {
+                    "bidder": {
+                        "placementId": "test"
+                    }
+                }
+            }
+        ],
+        "app": {
+            "id": "1",
+            "bundle": "com.wls.testwlsapplication"
+        },
+        "device": {
+            "ip": "123.123.123.123",
+            "ifa": "sdjfksdf-dfsds-dsdg-dsgg"
+        }
+    },
+	"httpCalls": [{
+		"expectedRequest": {
+			"uri": "http://backend.andbeyond.media/pserver",
+			"body": {
+                "id": "test-request-id",
+                "imp": [
+                    {
+                        "id": "test-imp-id",
+                        "banner": {
+                            "format": [
+                                {
+                                    "w": 300,
+                                    "h": 250
+                                },
+                                {
+                                    "w": 300,
+                                    "h": 600
+                                }
+                            ]
+                        },
+                        "ext": {
+                            "bidder": {
+                                "placementId": "test",
+                                "type": "publisher"
+                            }
+                        }
+                    }
+                ],
+                "app": {
+                    "id": "1",
+                    "bundle": "com.wls.testwlsapplication"
+                },
+                "device": {
+                    "ip": "123.123.123.123",
+                    "ifa": "sdjfksdf-dfsds-dsdg-dsgg"
+                }
+            }
+		},
+		"mockResponse": {
+			"status": 404,
+			"body": {}
+		}
+    }],
+    "expectedMakeBidsErrors": [
+        {
+          "value": "Unexpected status code: 404. Run with request.debug = 1 for more info.",
+          "comparison": "literal"
+        }
+    ]
+}
diff --git a/adapters/andbeyondmedia/params_test.go b/adapters/andbeyondmedia/params_test.go
new file mode 100644
index 00000000..6a09fdb6
--- /dev/null
+++ b/adapters/andbeyondmedia/params_test.go
@@ -0,0 +1,45 @@
+package andbeyondmedia
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+func TestValidParams(t *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+	if err != nil {
+		t.Fatalf("Failed to fetch the json schema. %v", err)
+	}
+
+	for _, p := range validParams {
+		if err := validator.Validate(openrtb_ext.BidderAndBeyondMedia, json.RawMessage(p)); err != nil {
+			t.Errorf("Schema rejected valid params: %s", p)
+		}
+	}
+}
+
+func TestInvalidParams(t *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+	if err != nil {
+		t.Fatalf("Failed to fetch the json schema. %v", err)
+	}
+
+	for _, p := range invalidParams {
+		if err := validator.Validate(openrtb_ext.BidderAndBeyondMedia, json.RawMessage(p)); err == nil {
+			t.Errorf("Schema allowed invalid params: %s", p)
+		}
+	}
+}
+
+var validParams = []string{
+	`{"placementId": "test"}`,
+	`{"placementId": "1"}`,
+}
+
+var invalidParams = []string{
+	`{}`,
+	`{"placementId": 42}`,
+	`{"endpointId": "1"}`,
+}
diff --git a/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-empty-group-id.json b/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-empty-group-id.json
index dd9ee32d..7a628d0f 100644
--- a/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-empty-group-id.json
+++ b/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-empty-group-id.json
@@ -38,7 +38,31 @@
                 "id": "7819"
               }
             }
-          }
+          },
+          "imp": [
+            {
+              "banner": {
+                "format": [
+                  {
+                    "h": 250,
+                    "w": 320
+                  },
+                  {
+                    "h": 300,
+                    "w": 320
+                  }
+                ]
+              },
+              "ext": {
+                "prebid": {
+                  "storedrequest": {
+                    "id": "7819"
+                  }
+                }
+              },
+              "id": "testimpid"
+            }
+          ]
         }
       },
       "mockResponse": {
diff --git a/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-with-group-id.json b/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-with-group-id.json
index 72d22b28..35381130 100644
--- a/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-with-group-id.json
+++ b/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-with-group-id.json
@@ -49,6 +49,42 @@
                 "id": "g7819;320x250;www.example.com"
               }
             }
+          },
+          "imp": [
+            {
+              "banner": {
+                "format": [
+                  {
+                    "h": 250,
+                    "w": 320
+                  },
+                  {
+                    "h": 300,
+                    "w": 320
+                  }
+                ],
+                "h": 250,
+                "w": 320
+              },
+              "ext": {
+                "prebid": {
+                  "storedrequest": {
+                    "id": "g7819;320x250;www.example.com"
+                  }
+                }
+              },
+              "id": "testimpid"
+            }
+          ],
+          "site": {
+            "domain": "www.example.com",
+            "ext": {
+              "amp": 0
+            },
+            "page": "http://www.example.com",
+            "publisher": {
+              "domain": "example.com"
+            }
           }
         }
       },
diff --git a/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-with-group-id_app.json b/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-with-group-id_app.json
index 6633a063..25c1f36d 100644
--- a/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-with-group-id_app.json
+++ b/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-with-group-id_app.json
@@ -36,13 +36,42 @@
         "uri": "https://pbs.nextmillmedia.com/openrtb2/auction",
         "body":{
           "id": "testid",
+          "app": {
+            "domain": "www.example.com"
+          },
           "ext": {
             "prebid": {
               "storedrequest": {
                 "id": "g7819;320x250;www.example.com"
               }
             }
-          }
+          },
+          "imp": [
+            {
+              "banner": {
+                "format": [
+                  {
+                    "h": 250,
+                    "w": 320
+                  },
+                  {
+                    "h": 300,
+                    "w": 320
+                  }
+                ],
+                "h": 250,
+                "w": 320
+              },
+              "ext": {
+                "prebid": {
+                  "storedrequest": {
+                    "id": "g7819;320x250;www.example.com"
+                  }
+                }
+              },
+              "id": "testimpid"
+            }
+          ]
         }
       },
       "mockResponse": {
diff --git a/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-with-only-width.json b/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-with-only-width.json
index 2dc1f3b6..79c9894f 100644
--- a/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-with-only-width.json
+++ b/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-with-only-width.json
@@ -25,13 +25,31 @@
         "uri": "https://pbs.nextmillmedia.com/openrtb2/auction",
         "body":{
           "id": "testid",
+          "app": {
+            "domain": "www.example.com"
+          },
           "ext": {
             "prebid": {
               "storedrequest": {
                 "id": "g7819;;www.example.com"
               }
             }
-          }
+          },
+          "imp": [
+            {
+              "banner": {
+                "w": 320
+              },
+              "ext": {
+                "prebid": {
+                  "storedrequest": {
+                    "id": "g7819;;www.example.com"
+                  }
+                }
+              },
+              "id": "testimpid"
+            }
+          ]
         }
       },
       "mockResponse": {
diff --git a/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-with-wh.json b/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-with-wh.json
index 9e3385fd..6af7816e 100644
--- a/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-with-wh.json
+++ b/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-with-wh.json
@@ -26,13 +26,32 @@
         "uri": "https://pbs.nextmillmedia.com/openrtb2/auction",
         "body":{
           "id": "testid",
+          "app": {
+            "domain": "www.example.com"
+          },
           "ext": {
             "prebid": {
               "storedrequest": {
                 "id": "g7819;320x250;www.example.com"
               }
             }
-          }
+          },
+          "imp": [
+            {
+              "banner": {
+                "h": 250,
+                "w": 320
+              },
+              "ext": {
+                "prebid": {
+                  "storedrequest": {
+                    "id": "g7819;320x250;www.example.com"
+                  }
+                }
+              },
+              "id": "testimpid"
+            }
+          ]
         }
       },
       "mockResponse": {
diff --git a/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-wo-domain.json b/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-wo-domain.json
index 6a1694ba..181dab35 100644
--- a/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-wo-domain.json
+++ b/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-wo-domain.json
@@ -39,7 +39,33 @@
                 "id": "g7819;320x250;"
               }
             }
-          }
+          },
+          "imp": [
+            {
+              "banner": {
+                "format": [
+                  {
+                    "h": 250,
+                    "w": 320
+                  },
+                  {
+                    "h": 300,
+                    "w": 320
+                  }
+                ],
+                "h": 250,
+                "w": 320
+              },
+              "ext": {
+                "prebid": {
+                  "storedrequest": {
+                    "id": "g7819;320x250;"
+                  }
+                }
+              },
+              "id": "testimpid"
+            }
+          ]
         }
       },
       "mockResponse": {
diff --git a/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-wo-size.json b/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-wo-size.json
index 9da3ccf9..f2422033 100644
--- a/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-wo-size.json
+++ b/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-wo-size.json
@@ -37,7 +37,31 @@
                 "id": "g7819;320x250;"
               }
             }
-          }
+          },
+          "imp": [
+            {
+              "banner": {
+                "format": [
+                  {
+                    "h": 250,
+                    "w": 320
+                  },
+                  {
+                    "h": 300,
+                    "w": 320
+                  }
+                ]
+              },
+              "ext": {
+                "prebid": {
+                  "storedrequest": {
+                    "id": "g7819;320x250;"
+                  }
+                }
+              },
+              "id": "testimpid"
+            }
+          ]
         }
       },
       "mockResponse": {
diff --git a/adapters/nextmillennium/nextmillenniumtest/exemplary/banner.json b/adapters/nextmillennium/nextmillenniumtest/exemplary/banner.json
index ff6386d3..fa8834fa 100644
--- a/adapters/nextmillennium/nextmillenniumtest/exemplary/banner.json
+++ b/adapters/nextmillennium/nextmillenniumtest/exemplary/banner.json
@@ -39,7 +39,33 @@
                 "id": "7819"
               }
             }
-          }
+          },
+          "imp": [
+            {
+              "banner": {
+                "format": [
+                  {
+                    "h": 250,
+                    "w": 320
+                  },
+                  {
+                    "h": 300,
+                    "w": 320
+                  }
+                ],
+                "h": 250,
+                "w": 320
+              },
+              "ext": {
+                "prebid": {
+                  "storedrequest": {
+                    "id": "7819"
+                  }
+                }
+              },
+              "id": "testimpid"
+            }
+          ]
         }
       },
       "mockResponse": {
diff --git a/adapters/nextmillennium/nextmillenniumtest/exemplary/empty-banner-obj.json b/adapters/nextmillennium/nextmillenniumtest/exemplary/empty-banner-obj.json
index 52f53bdb..cc9f9eb9 100644
--- a/adapters/nextmillennium/nextmillenniumtest/exemplary/empty-banner-obj.json
+++ b/adapters/nextmillennium/nextmillenniumtest/exemplary/empty-banner-obj.json
@@ -26,7 +26,20 @@
                 "id": "g7819;;"
               }
             }
-          }
+          },
+          "imp": [
+            {
+              "banner": {},
+              "ext": {
+                "prebid": {
+                  "storedrequest": {
+                    "id": "g7819;;"
+                  }
+                }
+              },
+              "id": "testimpid"
+            }
+          ]
         }
       },
       "mockResponse": {
diff --git a/adapters/nextmillennium/nextmillenniumtest/supplemental/empty-seatbid.json b/adapters/nextmillennium/nextmillenniumtest/supplemental/empty-seatbid.json
index 8c344d57..dd070d62 100644
--- a/adapters/nextmillennium/nextmillenniumtest/supplemental/empty-seatbid.json
+++ b/adapters/nextmillennium/nextmillenniumtest/supplemental/empty-seatbid.json
@@ -38,7 +38,33 @@
                 "id": "7819"
               }
             }
-          }
+          },
+          "imp": [
+            {
+              "banner": {
+                "format": [
+                  {
+                    "h": 250,
+                    "w": 320
+                  },
+                  {
+                    "h": 300,
+                    "w": 320
+                  }
+                ],
+                "h": 250,
+                "w": 320
+              },
+              "ext": {
+                "prebid": {
+                  "storedrequest": {
+                    "id": "7819"
+                  }
+                }
+              },
+              "id": "testimpid"
+            }
+          ]
         }
       },
       "mockResponse": {
diff --git a/adapters/nextmillennium/nextmillenniumtest/supplemental/error-response.json b/adapters/nextmillennium/nextmillenniumtest/supplemental/error-response.json
index 1fa4b195..3e8047fa 100644
--- a/adapters/nextmillennium/nextmillenniumtest/supplemental/error-response.json
+++ b/adapters/nextmillennium/nextmillenniumtest/supplemental/error-response.json
@@ -34,7 +34,29 @@
                 "id": "7819"
               }
             }
-          }
+          },
+          "imp": [
+            {
+              "banner": {
+                "format": [
+                  {
+                    "h": 300,
+                    "w": 300
+                  }
+                ],
+                "h": 250,
+                "w": 320
+              },
+              "ext": {
+                "prebid": {
+                  "storedrequest": {
+                    "id": "7819"
+                  }
+                }
+              },
+              "id": "test-imp-id"
+            }
+          ]
         }
       },
       "mockResponse": {
diff --git a/adapters/nextmillennium/nextmillenniumtest/supplemental/no-content.json b/adapters/nextmillennium/nextmillenniumtest/supplemental/no-content.json
index a12897a4..c4710a37 100644
--- a/adapters/nextmillennium/nextmillenniumtest/supplemental/no-content.json
+++ b/adapters/nextmillennium/nextmillenniumtest/supplemental/no-content.json
@@ -34,7 +34,29 @@
                 "id": "7819"
               }
             }
-          }
+          },
+          "imp": [
+            {
+              "banner": {
+                "format": [
+                  {
+                    "h": 300,
+                    "w": 300
+                  }
+                ],
+                "h": 250,
+                "w": 320
+              },
+              "ext": {
+                "prebid": {
+                  "storedrequest": {
+                    "id": "7819"
+                  }
+                }
+              },
+              "id": "test-imp-id"
+            }
+          ]
         }
       },
       "mockResponse": {
diff --git a/adapters/pubmatic/pubmatictest/supplemental/native_invalid_adm.json b/adapters/pubmatic/pubmatictest/supplemental/native_invalid_adm.json
new file mode 100644
index 00000000..a6192f75
--- /dev/null
+++ b/adapters/pubmatic/pubmatictest/supplemental/native_invalid_adm.json
@@ -0,0 +1,122 @@
+{
+  "mockBidRequest": {
+      "id": "test-native-request",
+      "imp": [
+          {
+              "id": "test-native-imp",
+              "native": {
+                  "request": "{\"assets\":[{\"id\":1,\"img\":{\"ext\":{\"image1\":\"image2\"},\"h\": 250,\"mimes\":[\"image\/gif\",\"image\/png\"],\"type\":3,\"w\":300},\"required\":1}]}"
+              },
+              "ext": {
+                  "bidder": {
+                      "publisherId": "999",
+                      "wrapper": {
+                          "version": 1,
+                          "profile": 5123
+                      }
+                  }
+              }
+          }
+      ],
+      "ext": {
+        "prebid": {}
+      },
+      "device": {
+          "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36"
+      },
+      "site": {
+          "id": "siteID",
+          "publisher": {
+              "id": "1234"
+          }
+      }
+  },
+  "httpCalls": [
+      {
+          "expectedRequest": {
+              "uri": "https://hbopenbid.pubmatic.com/translator?source=prebid-server",
+              "body": {
+                  "id": "test-native-request",
+                  "imp": [
+                      {
+                          "id": "test-native-imp",
+                          "native": {
+                              "request": "{\"assets\":[{\"id\":1,\"img\":{\"ext\":{\"image1\":\"image2\"},\"h\": 250,\"mimes\":[\"image\/gif\",\"image\/png\"],\"type\":3,\"w\":300},\"required\":1}]}"
+                          }
+                      }
+                  ],
+                  "device": {
+                      "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36"
+                  },
+                  "site": {
+                      "id": "siteID",
+                      "publisher": {
+                          "id": "999"
+                      }
+                  },
+                  "ext": {
+                      "wrapper": {
+                          "profile": 5123,
+                          "version": 1
+                      }
+                  }
+              }
+          },
+          "mockResponse": {
+              "status": 200,
+              "body": {
+                  "id": "test-native-request",
+                  "seatbid": [
+                      {
+                          "seat": "958",
+                          "bid": [
+                              {
+                                "id": "test-native-invalid-adm-json",
+                                  "impid": "test-native-imp-invalid-adm",
+                                  "price": 0.500000,
+                                  "adid": "some-test-id",
+                                  "adm": "{",
+                                  "crid": "29681110",
+                                  "ext": {
+                                      "deal_channel": 1,
+                                      "bidtype": 2
+                                  }
+                              }
+                          ]
+                      }
+                  ],
+                  "bidid": "5778926625248726496",
+                  "cur": "USD"
+              }
+          }
+      }
+  ],
+  "expectedBidResponses": [
+      {
+          "currency": "USD",
+          "bids": [
+              {
+                "bid": {
+                    "id": "test-native-invalid-adm-json",
+                    "impid": "test-native-imp-invalid-adm",
+                    "price": 0.5,
+                    "adid": "some-test-id",
+                    "adm": "{",
+                    "crid": "29681110",
+                    "ext": {
+                        "deal_channel": 1,
+                        "bidtype": 2
+                    }
+                },
+                "type": "native"
+              }
+          ]
+      }
+  ],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "unable to unmarshal native adm",
+      "comparison": "literal"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/seedingAlliance/params_test.go b/adapters/seedingAlliance/params_test.go
new file mode 100644
index 00000000..cdf29aed
--- /dev/null
+++ b/adapters/seedingAlliance/params_test.go
@@ -0,0 +1,43 @@
+package seedingAlliance
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+func TestValidParams(t *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+	if err != nil {
+		t.Fatalf("Failed to fetch the json schema. %v", err)
+	}
+
+	for _, p := range validParams {
+		if err := validator.Validate(openrtb_ext.BidderSeedingAlliance, json.RawMessage(p)); err != nil {
+			t.Errorf("Schema rejected valid params: %s", p)
+		}
+	}
+}
+
+func TestInvalidParams(t *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+	if err != nil {
+		t.Fatalf("Failed to fetch the json schema. %v", err)
+	}
+
+	for _, p := range invalidParams {
+		if err := validator.Validate(openrtb_ext.BidderSeedingAlliance, json.RawMessage(p)); err == nil {
+			t.Errorf("Schema allowed invalid params: %s", p)
+		}
+	}
+}
+
+var validParams = []string{
+	`{"adUnitId": "1234"}`,
+	`{"adUnitId": "AB12"}`,
+}
+
+var invalidParams = []string{
+	`{"adUnitId": 42}`,
+}
diff --git a/adapters/seedingAlliance/seedingAlliance_test.go b/adapters/seedingAlliance/seedingAlliance_test.go
new file mode 100644
index 00000000..aba589e1
--- /dev/null
+++ b/adapters/seedingAlliance/seedingAlliance_test.go
@@ -0,0 +1,138 @@
+package seedingAlliance
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/mxmCherry/openrtb/v16/openrtb2"
+	"github.com/prebid/prebid-server/adapters"
+	"github.com/prebid/prebid-server/adapters/adapterstest"
+	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/openrtb_ext"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestJsonSamples(t *testing.T) {
+	bidder, buildErr := Builder(openrtb_ext.BidderSeedingAlliance, config.Adapter{
+		Endpoint: "https://mockup.seeding-alliance.de/",
+	})
+	if buildErr != nil {
+		t.Fatalf("Builder returned unexpected error %v", buildErr)
+	}
+
+	adapterstest.RunJSONBidderTest(t, "seedingAlliancetest", bidder)
+}
+
+func TestResolvePriceMacro(t *testing.T) {
+	_, buildErr := Builder(openrtb_ext.BidderSeedingAlliance, config.Adapter{
+		Endpoint: "https://mockup.seeding-alliance.de/",
+	})
+	if buildErr != nil {
+		t.Fatalf("Builder returned unexpected error %v", buildErr)
+	}
+
+	adm := `{"link":{"url":"https://some_url.com/abc123?wp=${AUCTION_PRICE}"}`
+	want := `{"link":{"url":"https://some_url.com/abc123?wp=12.34"}`
+
+	bid := openrtb2.Bid{AdM: adm, Price: 12.34}
+	resolvePriceMacro(&bid)
+
+	if bid.AdM != want {
+		t.Fatalf("want: %v, got: %v", want, bid.AdM)
+	}
+}
+
+func TestGetMediaTypeForImp(t *testing.T) {
+	_, buildErr := Builder(openrtb_ext.BidderSeedingAlliance, config.Adapter{
+		Endpoint: "https://mockup.seeding-alliance.de/ssp-testing/native.html",
+	})
+	if buildErr != nil {
+		t.Fatalf("Builder returned unexpected error %v", buildErr)
+	}
+
+	impID := "some_id"
+
+	tests := []struct {
+		name string
+		want openrtb_ext.BidType
+		data openrtb2.Imp
+	}{
+		{"native", openrtb_ext.BidTypeNative, openrtb2.Imp{ID: impID, Native: &openrtb2.Native{}}},
+		{"banner", openrtb_ext.BidTypeBanner, openrtb2.Imp{ID: impID, Banner: &openrtb2.Banner{}}},
+		{"video", "unknown", openrtb2.Imp{ID: impID, Video: &openrtb2.Video{}}},
+		{"audio", "unknown", openrtb2.Imp{ID: impID, Audio: &openrtb2.Audio{}}},
+		{"unknown", "unknown", openrtb2.Imp{ID: impID}},
+		{"no imp", "unknown", openrtb2.Imp{}},
+	}
+
+	for _, test := range tests {
+		got := getMediaTypeForImp(impID, []openrtb2.Imp{test.data})
+		assert.Equal(t, test.want, got)
+	}
+}
+
+func TestAddTagID(t *testing.T) {
+	_, buildErr := Builder(openrtb_ext.BidderSeedingAlliance, config.Adapter{
+		Endpoint: "https://mockup.seeding-alliance.de/ssp-testing/native.html",
+	})
+	if buildErr != nil {
+		t.Fatalf("Builder returned unexpected error %v", buildErr)
+	}
+
+	tests := []struct {
+		name    string
+		want    string
+		data    string
+		wantErr bool
+	}{
+		{"regular case", "abc123", "abc123", false},
+		{"nil case", "", "", false},
+		{"unmarshal err case", "", "", true},
+	}
+
+	for _, test := range tests {
+		extSA, err := json.Marshal(openrtb_ext.ImpExtSeedingAlliance{AdUnitID: test.data})
+		if err != nil {
+			t.Fatalf("unexpected error %v", err)
+		}
+
+		extBidder, err := json.Marshal(adapters.ExtImpBidder{Bidder: extSA})
+		if err != nil {
+			t.Fatalf("unexpected error %v", err)
+		}
+
+		if test.wantErr {
+			extBidder = []byte{}
+		}
+
+		ortbImp := openrtb2.Imp{Ext: extBidder}
+
+		if err := addTagID(&ortbImp); err != nil {
+			if test.wantErr {
+				continue
+			}
+			t.Fatalf("unexpected error %v", err)
+		}
+
+		if test.want != ortbImp.TagID {
+			t.Fatalf("want: %v, got: %v", test.want, ortbImp.TagID)
+		}
+	}
+}
+
+func TestCurExists(t *testing.T) {
+	tests := []struct {
+		name string
+		cur  string
+		data []string
+		want bool
+	}{
+		{"no eur", "EUR", []string{"USD"}, false},
+		{"eur exists", "EUR", []string{"USD", "EUR"}, true},
+	}
+
+	for _, test := range tests {
+		got := curExists(test.data, test.cur)
+		assert.Equal(t, test.want, got)
+	}
+}
diff --git a/adapters/seedingAlliance/seedingAlliancetest/exemplary/banner.json b/adapters/seedingAlliance/seedingAlliancetest/exemplary/banner.json
new file mode 100644
index 00000000..9eb19f50
--- /dev/null
+++ b/adapters/seedingAlliance/seedingAlliancetest/exemplary/banner.json
@@ -0,0 +1,130 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "site": {
+            "publisher": {
+                "id": "foo",
+                "name": "foo"
+            }
+        },
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 300,
+                            "h": 250
+                        },
+                        {
+                            "w": 300,
+                            "h": 600
+                        }
+                    ]
+                },
+                "ext": {
+                    "bidder": {
+                        "adUnitId": "example-tag-id"
+                    }
+                }
+            }
+        ]
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "https://mockup.seeding-alliance.de/",
+                "body": {
+                    "cur": [
+                        "EUR"
+                    ],
+                    "id": "test-request-id",
+                    "imp": [
+                        {
+                            "id": "test-imp-id",
+                            "banner": {
+                                "format": [
+                                    {
+                                        "w": 300,
+                                        "h": 250
+                                    },
+                                    {
+                                        "w": 300,
+                                        "h": 600
+                                    }
+                                ]
+                            },
+                            "tagid": "example-tag-id",
+                            "ext": {
+                                "bidder": {
+                                    "adUnitId": "example-tag-id"
+                                }
+                            }
+                        }
+                    ],
+                    "site": {
+                        "publisher": {
+                            "id": "foo",
+                            "name": "foo"
+                        }
+                    }
+                }
+            },
+            "mockResponse": {
+                "status": 200,
+                "body": {
+                    "id": "test-request-id",
+                    "seatbid": [
+                        {
+                            "seat": "123",
+                            "bid": [
+                                {
+                                    "id": "7706636740145184841",
+                                    "impid": "test-imp-id",
+                                    "price": 0.5,
+                                    "adid": "12341234",
+                                    "adm": "some-test-ad",
+                                    "adomain": [
+                                        "domain.com"
+                                    ],
+                                    "iurl": "http://abc.com/cr?id=12341234",
+                                    "cid": "123",
+                                    "crid": "12341234",
+                                    "h": 250,
+                                    "w": 300
+                                }
+                            ]
+                        }
+                    ],
+                    "bidid": "5778926625248726496",
+                    "cur": "EUR"
+                }
+            }
+        }
+    ],
+    "expectedBidResponses": [
+        {
+            "currency": "EUR",
+            "bids": [
+                {
+                    "bid": {
+                        "id": "7706636740145184841",
+                        "impid": "test-imp-id",
+                        "price": 0.5,
+                        "adm": "some-test-ad",
+                        "adid": "12341234",
+                        "adomain": [
+                            "domain.com"
+                        ],
+                        "iurl": "http://abc.com/cr?id=12341234",
+                        "cid": "123",
+                        "crid": "12341234",
+                        "w": 300,
+                        "h": 250
+                    },
+                    "type": "banner"
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/adapters/seedingAlliance/seedingAlliancetest/exemplary/native.json b/adapters/seedingAlliance/seedingAlliancetest/exemplary/native.json
new file mode 100644
index 00000000..b3391a56
--- /dev/null
+++ b/adapters/seedingAlliance/seedingAlliancetest/exemplary/native.json
@@ -0,0 +1,101 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "site": {
+            "publisher": {
+                "id": "foo",
+                "name": "foo"
+            }
+        },
+        "cur": [
+            "EUR"
+        ],
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "native": {
+                    "request": "{\"plcmtcnt\":1,\"plcmttype\":2,\"privacy\":1,\"context\":1,\"contextsubtype\":12,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]},{\"event\":2,\"methods\":[1]}],\"assets\":[{\"data\":{\"type\":12},\"required\":1},{\"title\":{\"len\":50},\"required\":1},{\"img\":{\"w\":80,\"h\":80,\"type\":1},\"required\":1},{\"img\":{\"w\":1200,\"h\":627,\"type\":3},\"required\":1},{\"data\":{\"type\":3},\"required\":0},{\"data\":{\"len\":100,\"type\":2},\"required\":1},{\"video\":{\"mimes\":[\"video/mpeg\",\"video/mp4\"],\"minduration\":2,\"protocols\":[2,5],\"maxduration\":2,\"ext\":{\"playbackmethod\":[1,2]}},\"required\":1}],\"ver\":\"1.2\"}"
+                },
+                "ext": {
+                    "bidder": {
+                        "adUnitId": "example-tag-id"
+                    }
+                }
+            }
+        ]
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "https://mockup.seeding-alliance.de/",
+                "body": {
+                    "cur": [
+                        "EUR"
+                    ],
+                    "id": "test-request-id",
+                    "imp": [
+                        {
+                            "tagid": "example-tag-id",
+                            "id": "test-imp-id",
+                            "native": {
+                                "request": "{\"plcmtcnt\":1,\"plcmttype\":2,\"privacy\":1,\"context\":1,\"contextsubtype\":12,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]},{\"event\":2,\"methods\":[1]}],\"assets\":[{\"data\":{\"type\":12},\"required\":1},{\"title\":{\"len\":50},\"required\":1},{\"img\":{\"w\":80,\"h\":80,\"type\":1},\"required\":1},{\"img\":{\"w\":1200,\"h\":627,\"type\":3},\"required\":1},{\"data\":{\"type\":3},\"required\":0},{\"data\":{\"len\":100,\"type\":2},\"required\":1},{\"video\":{\"mimes\":[\"video/mpeg\",\"video/mp4\"],\"minduration\":2,\"protocols\":[2,5],\"maxduration\":2,\"ext\":{\"playbackmethod\":[1,2]}},\"required\":1}],\"ver\":\"1.2\"}"
+                            },
+                            "ext": {
+                                "bidder": {
+                                    "adUnitId": "example-tag-id"
+                                }
+                            }
+                        }
+                    ],
+                    "site": {
+                        "publisher": {
+                            "id": "foo",
+                            "name": "foo"
+                        }
+                    }
+                }
+            },
+            "mockResponse": {
+                "status": 200,
+                "body": {
+                    "id": "test-request-id",
+                    "seatbid": [
+                        {
+                            "bid": [
+                                {
+                                    "id": "some-id",
+                                    "impid": "test-imp-id",
+                                    "price": 1,
+                                    "adid": "69595837",
+                                    "adm": "{\"assets\":[{\"id\": 2,\"img\":{\"url\":\"http://example.com/p/creative-image/5e/b6/de/c3/5eb6dec3-4854-4dcd-980a-347f36ab502e.jpg\",\"w\": 3000,\"h\": 2250}},{\"id\": 1,\"title\":{\"text\":\"This is an example Prebid Native creative\"}},{\"id\": 3,\"data\":{\"value\":\"Prebid.org\"}},{\"id\": 4,\"data\":{\"value\":\"This is a Prebid Native Creative. There are many like it, but this one is mine.\"}}],\"link\":{\"url\":\"http://example.com/click?AAAAAAAA8D8AAAAAAADwPwAAAAAAAAAAAAAAAAAA8D8AAAAAAADwPwhdYz3ZyNFNG3fXpZUyLXNZ0o5aAAAAACrElgC-AwAAvgMAAAIAAAC98iUEeP4QAAAAAABVU0QAVVNEAAEAAQARIAAAAAABAgQCAAAAAAEAhBaSXgAAAAA./pp=${AUCTION_PRICE}/cnd=%21OwwGAQiGmooHEL3llyEY-PxDIAQoADoRZGVmYXVsdCNOWU0yOjQwMjM./bn=75922/test=1/referrer=prebid.org/clickenc=http%3A%2F%2Fprebid.org%2Fdev-docs%2Fshow-native-ads.html\"},\"imptrackers\":[\"http://example.com/openrtb_win?e=wqT_3QLFBqBFAwAAAwDWAAUBCNmku9QFEIi6jeuTm_LoTRib7t2u2tLMlnMqNgkAAAECCPA_EQEHEAAA8D8ZCQkIAAAhCQkI8D8pEQkAMQkJqAAAMKqI2wQ4vgdAvgdIAlC95ZchWPj8Q2AAaJFAeJLRBIABAYoBA1VTRJIFBvBQmAEBoAEBqAEBsAEAuAECwAEEyAEC0AEJ2AEA4AEB8AEAigI7dWYoJ2EnLCAxMzc2ODYwLCAxNTE5MzA5NDAxKTt1ZigncicsIDY5NTk1ODM3Nh4A8IqSAvUBIXRETkdfUWlHbW9vSEVMM2xseUVZQUNENF9FTXdBRGdBUUFSSXZnZFFxb2piQkZnQVlMTURhQUJ3QUhnQWdBRUFpQUVBa0FFQm1BRUJvQUVCcUFFRHNBRUF1UUVwaTRpREFBRHdQOEVCS1l1SWd3QUE4RF9KQVhfelYzek1zXzBfMlFFQUFBAQMkRHdQLUFCQVBVQgEOLEFKZ0NBS0FDQUxVQwUQBEwwCQjwTE1BQ0FNZ0NBT0FDQU9nQ0FQZ0NBSUFEQVpBREFKZ0RBYWdEaHBxS0I3b0RFV1JsWm1GMWJIUWpUbGxOTWpvME1ESXqaAjkhT3d3R0FRNvgA8E4tUHhESUFRb0FEb1JaR1ZtWVhWc2RDTk9XVTB5T2pRd01qTS7YAugH4ALH0wHqAgpwcmViaWQub3Jn8gIRCgZBRFZfSUQSBzEzNzY4NjDyARQMQ1BHXwEUNDM1MDMwOTjyAhEKBUNQARPwmQgxNDg0NzIzOIADAYgDAZADAJgDFKADAaoDAMADkBzIAwDYAwDgAwDoAwD4AwOABACSBAkvb3BlbnJ0YjKYBACiBAwxNTIuMTkzLjYuNzSoBJrMI7IEDAgAEAAYACAAMAA4ALgEAMAEAMgEANIEEWRlZmF1bHQjTllNMjo0MDIz2gQCCADgBADwBL3llyGIBQGYBQCgBf____8FA1ABqgULc29tZS1yZXEtaWTABQDJBQAFARTwP9IFCQkFC2QAAADYBQHgBQHwBd4C-gUECAAQAJAGAZgGAA..&s=08b1535744639c904684afe46e3c6c0e4786089f&test=1&referrer=prebid.org&pp=${AUCTION_PRICE}\"],\"jstracker\":\"<script src=\\\"http://www.dummyurl.js\\\"></script>\"}",
+                                    "ext": {}
+                                }
+                            ],
+                            "seat": "123"
+                        }
+                    ],
+                    "bidid": "8141327771600527856",
+                    "cur": "EUR"
+                }
+            }
+        }
+    ],
+    "expectedBidResponses": [
+        {
+            "currency": "EUR",
+            "bids": [
+                {
+                    "bid": {
+                        "id": "some-id",
+                        "impid": "test-imp-id",
+                        "price": 1,
+                        "adid": "69595837",
+                        "adm": "{\"assets\":[{\"id\": 2,\"img\":{\"url\":\"http://example.com/p/creative-image/5e/b6/de/c3/5eb6dec3-4854-4dcd-980a-347f36ab502e.jpg\",\"w\": 3000,\"h\": 2250}},{\"id\": 1,\"title\":{\"text\":\"This is an example Prebid Native creative\"}},{\"id\": 3,\"data\":{\"value\":\"Prebid.org\"}},{\"id\": 4,\"data\":{\"value\":\"This is a Prebid Native Creative. There are many like it, but this one is mine.\"}}],\"link\":{\"url\":\"http://example.com/click?AAAAAAAA8D8AAAAAAADwPwAAAAAAAAAAAAAAAAAA8D8AAAAAAADwPwhdYz3ZyNFNG3fXpZUyLXNZ0o5aAAAAACrElgC-AwAAvgMAAAIAAAC98iUEeP4QAAAAAABVU0QAVVNEAAEAAQARIAAAAAABAgQCAAAAAAEAhBaSXgAAAAA./pp=1/cnd=%21OwwGAQiGmooHEL3llyEY-PxDIAQoADoRZGVmYXVsdCNOWU0yOjQwMjM./bn=75922/test=1/referrer=prebid.org/clickenc=http%3A%2F%2Fprebid.org%2Fdev-docs%2Fshow-native-ads.html\"},\"imptrackers\":[\"http://example.com/openrtb_win?e=wqT_3QLFBqBFAwAAAwDWAAUBCNmku9QFEIi6jeuTm_LoTRib7t2u2tLMlnMqNgkAAAECCPA_EQEHEAAA8D8ZCQkIAAAhCQkI8D8pEQkAMQkJqAAAMKqI2wQ4vgdAvgdIAlC95ZchWPj8Q2AAaJFAeJLRBIABAYoBA1VTRJIFBvBQmAEBoAEBqAEBsAEAuAECwAEEyAEC0AEJ2AEA4AEB8AEAigI7dWYoJ2EnLCAxMzc2ODYwLCAxNTE5MzA5NDAxKTt1ZigncicsIDY5NTk1ODM3Nh4A8IqSAvUBIXRETkdfUWlHbW9vSEVMM2xseUVZQUNENF9FTXdBRGdBUUFSSXZnZFFxb2piQkZnQVlMTURhQUJ3QUhnQWdBRUFpQUVBa0FFQm1BRUJvQUVCcUFFRHNBRUF1UUVwaTRpREFBRHdQOEVCS1l1SWd3QUE4RF9KQVhfelYzek1zXzBfMlFFQUFBAQMkRHdQLUFCQVBVQgEOLEFKZ0NBS0FDQUxVQwUQBEwwCQjwTE1BQ0FNZ0NBT0FDQU9nQ0FQZ0NBSUFEQVpBREFKZ0RBYWdEaHBxS0I3b0RFV1JsWm1GMWJIUWpUbGxOTWpvME1ESXqaAjkhT3d3R0FRNvgA8E4tUHhESUFRb0FEb1JaR1ZtWVhWc2RDTk9XVTB5T2pRd01qTS7YAugH4ALH0wHqAgpwcmViaWQub3Jn8gIRCgZBRFZfSUQSBzEzNzY4NjDyARQMQ1BHXwEUNDM1MDMwOTjyAhEKBUNQARPwmQgxNDg0NzIzOIADAYgDAZADAJgDFKADAaoDAMADkBzIAwDYAwDgAwDoAwD4AwOABACSBAkvb3BlbnJ0YjKYBACiBAwxNTIuMTkzLjYuNzSoBJrMI7IEDAgAEAAYACAAMAA4ALgEAMAEAMgEANIEEWRlZmF1bHQjTllNMjo0MDIz2gQCCADgBADwBL3llyGIBQGYBQCgBf____8FA1ABqgULc29tZS1yZXEtaWTABQDJBQAFARTwP9IFCQkFC2QAAADYBQHgBQHwBd4C-gUECAAQAJAGAZgGAA..&s=08b1535744639c904684afe46e3c6c0e4786089f&test=1&referrer=prebid.org&pp=1\"],\"jstracker\":\"<script src=\\\"http://www.dummyurl.js\\\"></script>\"}",
+                        "ext": {}
+                    },
+                    "type": "native"
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/adapters/seedingAlliance/seedingAlliancetest/supplemental/invalid_tag_id.json b/adapters/seedingAlliance/seedingAlliancetest/supplemental/invalid_tag_id.json
new file mode 100644
index 00000000..47d4f1f1
--- /dev/null
+++ b/adapters/seedingAlliance/seedingAlliancetest/supplemental/invalid_tag_id.json
@@ -0,0 +1,30 @@
+{
+    "expectedMakeRequestsErrors": [
+        {
+            "value": "could not unmarshal openrtb_ext.ImpExtSeedingAlliance: json: cannot unmarshal number into Go struct field ImpExtSeedingAlliance.adUnitID of type string",
+            "comparison": "literal"
+        }
+    ],
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "site": {
+            "publisher": {
+                "id": "foo",
+                "name": "foo"
+            }
+        },
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "native": {
+                    "request": "{\"plcmtcnt\":1,\"plcmttype\":2,\"privacy\":1,\"context\":1,\"contextsubtype\":12,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]},{\"event\":2,\"methods\":[1]}],\"assets\":[{\"data\":{\"type\":12},\"required\":1},{\"title\":{\"len\":50},\"required\":1},{\"img\":{\"w\":80,\"h\":80,\"type\":1},\"required\":1},{\"img\":{\"w\":1200,\"h\":627,\"type\":3},\"required\":1},{\"data\":{\"type\":3},\"required\":0},{\"data\":{\"len\":100,\"type\":2},\"required\":1},{\"video\":{\"mimes\":[\"video/mpeg\",\"video/mp4\"],\"minduration\":2,\"protocols\":[2,5],\"maxduration\":2,\"ext\":{\"playbackmethod\":[1,2]}},\"required\":1}],\"ver\":\"1.2\"}"
+                },
+                "ext": {
+                    "bidder": {
+                        "adUnitId": 1234
+                    }
+                }
+            }
+        ]
+    }
+}
\ No newline at end of file
diff --git a/adapters/seedingAlliance/seedingAlliancetest/supplemental/status_bad_request.json b/adapters/seedingAlliance/seedingAlliancetest/supplemental/status_bad_request.json
new file mode 100644
index 00000000..55f35ccb
--- /dev/null
+++ b/adapters/seedingAlliance/seedingAlliancetest/supplemental/status_bad_request.json
@@ -0,0 +1,72 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 300,
+                            "h": 250
+                        },
+                        {
+                            "w": 300,
+                            "h": 600
+                        }
+                    ]
+                },
+                "ext": {
+                    "bidder": {
+                        "adUnitId": "example-tag-id"
+                    }
+                }
+            }
+        ]
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "https://mockup.seeding-alliance.de/",
+                "body": {
+                    "cur": [
+                        "EUR"
+                    ],
+                    "id": "test-request-id",
+                    "imp": [
+                        {
+                            "id": "test-imp-id",
+                            "banner": {
+                                "format": [
+                                    {
+                                        "w": 300,
+                                        "h": 250
+                                    },
+                                    {
+                                        "w": 300,
+                                        "h": 600
+                                    }
+                                ]
+                            },
+                            "tagid": "example-tag-id",
+                            "ext": {
+                                "bidder": {
+                                    "adUnitId": "example-tag-id"
+                                }
+                            }
+                        }
+                    ]
+                }
+            },
+            "mockResponse": {
+                "status": 400
+            }
+        }
+    ],
+    "expectedMakeBidsErrors": [
+        {
+            "value": "Unexpected status code: 400. Bad request from publisher. Run with request.debug = 1 for more info.",
+            "comparison": "literal"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/adapters/seedingAlliance/seedingAlliancetest/supplemental/status_no_content.json b/adapters/seedingAlliance/seedingAlliancetest/supplemental/status_no_content.json
new file mode 100644
index 00000000..f51aa12e
--- /dev/null
+++ b/adapters/seedingAlliance/seedingAlliancetest/supplemental/status_no_content.json
@@ -0,0 +1,66 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 300,
+                            "h": 250
+                        },
+                        {
+                            "w": 300,
+                            "h": 600
+                        }
+                    ]
+                },
+                "ext": {
+                    "bidder": {
+                        "adUnitId": "example-tag-id"
+                    }
+                }
+            }
+        ]
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "https://mockup.seeding-alliance.de/",
+                "body": {
+                    "cur": [
+                        "EUR"
+                    ],
+                    "id": "test-request-id",
+                    "imp": [
+                        {
+                            "id": "test-imp-id",
+                            "banner": {
+                                "format": [
+                                    {
+                                        "w": 300,
+                                        "h": 250
+                                    },
+                                    {
+                                        "w": 300,
+                                        "h": 600
+                                    }
+                                ]
+                            },
+                            "tagid": "example-tag-id",
+                            "ext": {
+                                "bidder": {
+                                    "adUnitId": "example-tag-id"
+                                }
+                            }
+                        }
+                    ]
+                }
+            },
+            "mockResponse": {
+                "status": 204
+            }
+        }
+    ]
+}
\ No newline at end of file
diff --git a/adapters/seedingAlliance/seedingAlliancetest/supplemental/status_not_ok.json b/adapters/seedingAlliance/seedingAlliancetest/supplemental/status_not_ok.json
new file mode 100644
index 00000000..78d99bc1
--- /dev/null
+++ b/adapters/seedingAlliance/seedingAlliancetest/supplemental/status_not_ok.json
@@ -0,0 +1,72 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 300,
+                            "h": 250
+                        },
+                        {
+                            "w": 300,
+                            "h": 600
+                        }
+                    ]
+                },
+                "ext": {
+                    "bidder": {
+                        "adUnitId": "example-tag-id"
+                    }
+                }
+            }
+        ]
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "https://mockup.seeding-alliance.de/",
+                "body": {
+                    "cur": [
+                        "EUR"
+                    ],
+                    "id": "test-request-id",
+                    "imp": [
+                        {
+                            "id": "test-imp-id",
+                            "banner": {
+                                "format": [
+                                    {
+                                        "w": 300,
+                                        "h": 250
+                                    },
+                                    {
+                                        "w": 300,
+                                        "h": 600
+                                    }
+                                ]
+                            },
+                            "tagid": "example-tag-id",
+                            "ext": {
+                                "bidder": {
+                                    "adUnitId": "example-tag-id"
+                                }
+                            }
+                        }
+                    ]
+                }
+            },
+            "mockResponse": {
+                "status": 404
+            }
+        }
+    ],
+    "expectedMakeBidsErrors": [
+        {
+            "value": "Unexpected status code: 404. Run with request.debug = 1 for more info.",
+            "comparison": "literal"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/config/bidderinfo_test.go b/config/bidderinfo_test.go
index df5b8591..b59f87dc 100644
--- a/config/bidderinfo_test.go
+++ b/config/bidderinfo_test.go
@@ -15,6 +15,42 @@ maintainer:
   email: "some-email@domain.com"
 gvlVendorID: 42
 `
+const fullBidderYAMLConfig = `
+maintainer:
+  email: "some-email@domain.com"
+capabilities:
+  app:
+    mediaTypes:
+      - banner
+      - video
+      - native
+  site:
+    mediaTypes:
+      - banner
+      - video
+      - native
+modifyingVastXmlAllowed: true
+debug:
+  allow: true
+gvlVendorID: 42
+userSync:
+  iframe:
+    url: "someURL"
+    userMacro: "aValue"
+  redirect:
+    url: "anotherURL"
+    userMacro: "anotherValue"
+  key: "aKey"
+  supports:
+    - item
+    - item2
+  externalUrl: oneMoreUrl
+  supportCors: true
+experiment:
+  adsCert:
+    enabled: true
+endpointCompression: "GZIP"
+`
 
 func TestLoadBidderInfoFromDisk(t *testing.T) {
 	bidder := "someBidder"
@@ -344,3 +380,48 @@ func TestToGVLVendorIDMap(t *testing.T) {
 	result := givenBidderInfos.ToGVLVendorIDMap()
 	assert.Equal(t, expectedGVLVendorIDMap, result)
 }
+
+func TestReadFullYamlBidderConfig(t *testing.T) {
+	bidder := "someBidder"
+	trueValue := true
+	r := fakeInfoReader{fullBidderYAMLConfig, nil}
+	actualBidderInfo, err := loadBidderInfo(r, map[string]Adapter{strings.ToLower(bidder): {}}, []string{bidder})
+	assert.NoError(t, err, "Error wasn't expected")
+
+	expectedBidderInfo := BidderInfos{
+		bidder: {
+			Enabled: true,
+			Maintainer: &MaintainerInfo{
+				Email: "some-email@domain.com",
+			},
+			GVLVendorID: 42,
+			Capabilities: &CapabilitiesInfo{
+				App: &PlatformInfo{
+					MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner, openrtb_ext.BidTypeVideo, openrtb_ext.BidTypeNative},
+				},
+				Site: &PlatformInfo{
+					MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner, openrtb_ext.BidTypeVideo, openrtb_ext.BidTypeNative},
+				},
+			},
+			Debug:                   &DebugInfo{Allow: true},
+			ModifyingVastXmlAllowed: true,
+			Syncer: &Syncer{
+				Key: "aKey",
+				IFrame: &SyncerEndpoint{
+					URL:       "someURL",
+					UserMacro: "aValue",
+				},
+				Redirect: &SyncerEndpoint{
+					URL:       "anotherURL",
+					UserMacro: "anotherValue",
+				},
+				Supports:    []string{"item", "item2"},
+				SupportCORS: &trueValue,
+				ExternalURL: "oneMoreUrl",
+			},
+			Experiment:          BidderInfoExperiment{AdsCert: BidderAdsCert{Enabled: true}},
+			EndpointCompression: "GZIP",
+		},
+	}
+	assert.Equalf(t, expectedBidderInfo, actualBidderInfo, "Bidder info objects aren't matching")
+}
diff --git a/endpoints/openrtb2/test_utils.go b/endpoints/openrtb2/test_utils.go
index 38497581..c5410cbd 100644
--- a/endpoints/openrtb2/test_utils.go
+++ b/endpoints/openrtb2/test_utils.go
@@ -1185,7 +1185,7 @@ func buildTestExchange(testCfg *testConfigValues, adapterMap map[openrtb_ext.Bid
 		bidderAdapter := mockAdapter{mockServerURL: bidServer.URL}
 		bidderName := openrtb_ext.BidderName(mockBidder.BidderName)
 
-		adapterMap[bidderName] = exchange.AdaptBidder(bidderAdapter, bidServer.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, bidderName, nil)
+		adapterMap[bidderName] = exchange.AdaptBidder(bidderAdapter, bidServer.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, bidderName, nil, "")
 		mockBidServersArray = append(mockBidServersArray, bidServer)
 	}
 
diff --git a/exchange/adapter_util_test.go b/exchange/adapter_util_test.go
index c4949663..5266e67e 100644
--- a/exchange/adapter_util_test.go
+++ b/exchange/adapter_util_test.go
@@ -26,12 +26,12 @@ func TestBuildAdapters(t *testing.T) {
 
 	appnexusBidder, _ := appnexus.Builder(openrtb_ext.BidderAppnexus, config.Adapter{})
 	appnexusBidderWithInfo := adapters.BuildInfoAwareBidder(appnexusBidder, infoEnabled)
-	appnexusBidderAdapted := AdaptBidder(appnexusBidderWithInfo, client, &config.Configuration{}, metricEngine, openrtb_ext.BidderAppnexus, nil)
+	appnexusBidderAdapted := AdaptBidder(appnexusBidderWithInfo, client, &config.Configuration{}, metricEngine, openrtb_ext.BidderAppnexus, nil, "")
 	appnexusValidated := addValidatedBidderMiddleware(appnexusBidderAdapted)
 
 	rubiconBidder, _ := rubicon.Builder(openrtb_ext.BidderRubicon, config.Adapter{})
 	rubiconBidderWithInfo := adapters.BuildInfoAwareBidder(rubiconBidder, infoEnabled)
-	rubiconBidderAdapted := AdaptBidder(rubiconBidderWithInfo, client, &config.Configuration{}, metricEngine, openrtb_ext.BidderRubicon, nil)
+	rubiconBidderAdapted := AdaptBidder(rubiconBidderWithInfo, client, &config.Configuration{}, metricEngine, openrtb_ext.BidderRubicon, nil, "")
 	rubiconbidderValidated := addValidatedBidderMiddleware(rubiconBidderAdapted)
 
 	testCases := []struct {
@@ -240,6 +240,7 @@ func TestGetDisabledBiddersErrorMessages(t *testing.T) {
 				"lifestreet":   `Bidder "lifestreet" is no longer available in Prebid Server. Please update your configuration.`,
 				"adagio":       `Bidder "adagio" is no longer available in Prebid Server. Please update your configuration.`,
 				"somoaudience": `Bidder "somoaudience" is no longer available in Prebid Server. Please update your configuration.`,
+				"yssp":         `Bidder "yssp" is no longer available in Prebid Server. If you're looking to use the Yahoo SSP adapter, please rename it to "yahoossp" in your configuration.`,
 			},
 		},
 		{
@@ -249,6 +250,7 @@ func TestGetDisabledBiddersErrorMessages(t *testing.T) {
 				"lifestreet":   `Bidder "lifestreet" is no longer available in Prebid Server. Please update your configuration.`,
 				"adagio":       `Bidder "adagio" is no longer available in Prebid Server. Please update your configuration.`,
 				"somoaudience": `Bidder "somoaudience" is no longer available in Prebid Server. Please update your configuration.`,
+				"yssp":         `Bidder "yssp" is no longer available in Prebid Server. If you're looking to use the Yahoo SSP adapter, please rename it to "yahoossp" in your configuration.`,
 			},
 		},
 		{
@@ -258,6 +260,7 @@ func TestGetDisabledBiddersErrorMessages(t *testing.T) {
 				"lifestreet":   `Bidder "lifestreet" is no longer available in Prebid Server. Please update your configuration.`,
 				"adagio":       `Bidder "adagio" is no longer available in Prebid Server. Please update your configuration.`,
 				"somoaudience": `Bidder "somoaudience" is no longer available in Prebid Server. Please update your configuration.`,
+				"yssp":         `Bidder "yssp" is no longer available in Prebid Server. If you're looking to use the Yahoo SSP adapter, please rename it to "yahoossp" in your configuration.`,
 				"appnexus":     `Bidder "appnexus" has been disabled on this instance of Prebid Server. Please work with the PBS host to enable this bidder again.`,
 			},
 		},
@@ -268,6 +271,7 @@ func TestGetDisabledBiddersErrorMessages(t *testing.T) {
 				"lifestreet":   `Bidder "lifestreet" is no longer available in Prebid Server. Please update your configuration.`,
 				"adagio":       `Bidder "adagio" is no longer available in Prebid Server. Please update your configuration.`,
 				"somoaudience": `Bidder "somoaudience" is no longer available in Prebid Server. Please update your configuration.`,
+				"yssp":         `Bidder "yssp" is no longer available in Prebid Server. If you're looking to use the Yahoo SSP adapter, please rename it to "yahoossp" in your configuration.`,
 				"appnexus":     `Bidder "appnexus" has been disabled on this instance of Prebid Server. Please work with the PBS host to enable this bidder again.`,
 			},
 		},
diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go
index 2d5d4e69..2da7c5a2 100644
--- a/exchange/bidder_test.go
+++ b/exchange/bidder_test.go
@@ -93,7 +93,7 @@ func TestSingleBidder(t *testing.T) {
 		}
 		bidderImpl.bidResponse = mockBidderResponse
 
-		bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, test.debugInfo)
+		bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, test.debugInfo, "")
 		currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
 
 		bidderReq := BidderRequest{
@@ -159,6 +159,132 @@ func TestSingleBidder(t *testing.T) {
 	}
 }
 
+func TestSingleBidderGzip(t *testing.T) {
+	type aTest struct {
+		debugInfo    *config.DebugInfo
+		httpCallsLen int
+	}
+
+	testCases := []*aTest{
+		{&config.DebugInfo{Allow: false}, 0},
+		{&config.DebugInfo{Allow: true}, 1},
+	}
+
+	respStatus := 200
+	respBody := "{\"bid\":false}"
+	server := httptest.NewServer(mockHandler(respStatus, "getBody", respBody))
+	defer server.Close()
+
+	requestHeaders := http.Header{}
+	requestHeaders.Add("Content-Type", "application/json")
+
+	bidAdjustments := map[string]float64{"test": 2.0}
+	firstInitialPrice := 3.0
+	secondInitialPrice := 4.0
+
+	bidderImpl := &goodSingleBidder{
+		httpRequest: &adapters.RequestData{
+			Method:  "POST",
+			Uri:     server.URL,
+			Body:    []byte(`{"key":"val"}`),
+			Headers: http.Header{},
+		},
+		bidResponse: nil,
+	}
+
+	ctx := context.Background()
+
+	for _, test := range testCases {
+		mockBidderResponse := &adapters.BidderResponse{
+			Bids: []*adapters.TypedBid{
+				{
+					Bid: &openrtb2.Bid{
+						Price: firstInitialPrice,
+					},
+					BidType:      openrtb_ext.BidTypeBanner,
+					DealPriority: 4,
+				},
+				{
+					Bid: &openrtb2.Bid{
+						Price: secondInitialPrice,
+					},
+					BidType:      openrtb_ext.BidTypeVideo,
+					DealPriority: 5,
+				},
+			},
+		}
+		bidderImpl.bidResponse = mockBidderResponse
+
+		bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, test.debugInfo, "GZIP")
+		currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
+
+		bidderReq := BidderRequest{
+			BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}},
+			BidderName: "test",
+		}
+		bidReqOptions := bidRequestOptions{
+			accountDebugAllowed: true,
+			headerDebugAllowed:  false,
+			addCallSignHeader:   false,
+			bidAdjustments:      bidAdjustments,
+		}
+		seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, config.AlternateBidderCodes{})
+		assert.Len(t, seatBids, 1)
+		seatBid := seatBids[0]
+
+		// Make sure the goodSingleBidder was called with the expected arguments.
+		if bidderImpl.httpResponse == nil {
+			t.Errorf("The Bidder should be called with the server's response.")
+		}
+		if bidderImpl.httpResponse.StatusCode != respStatus {
+			t.Errorf("Bad response status. Expected %d, got %d", respStatus, bidderImpl.httpResponse.StatusCode)
+		}
+		if string(bidderImpl.httpResponse.Body) != respBody {
+			t.Errorf("Bad response body. Expected %s, got %s", respBody, string(bidderImpl.httpResponse.Body))
+		}
+
+		// Make sure the returned values are what we expect
+		if len(errortypes.FatalOnly(errs)) != 0 {
+			t.Errorf("bidder.Bid returned %d errors. Expected 0", len(errs))
+		}
+
+		if !test.debugInfo.Allow && len(errortypes.WarningOnly(errs)) != 1 {
+			t.Errorf("bidder.Bid returned %d warnings. Expected 1", len(errs))
+		}
+		if len(seatBid.bids) != len(mockBidderResponse.Bids) {
+			t.Fatalf("Expected %d bids. Got %d", len(mockBidderResponse.Bids), len(seatBid.bids))
+		}
+		for index, typedBid := range mockBidderResponse.Bids {
+			if typedBid.Bid != seatBid.bids[index].bid {
+				t.Errorf("Bid %d did not point to the same bid returned by the Bidder.", index)
+			}
+			if typedBid.BidType != seatBid.bids[index].bidType {
+				t.Errorf("Bid %d did not have the right type. Expected %s, got %s", index, typedBid.BidType, seatBid.bids[index].bidType)
+			}
+			if typedBid.DealPriority != seatBid.bids[index].dealPriority {
+				t.Errorf("Bid %d did not have the right deal priority. Expected %s, got %s", index, typedBid.BidType, seatBid.bids[index].bidType)
+			}
+		}
+		bidAdjustment := bidAdjustments["test"]
+		if mockBidderResponse.Bids[0].Bid.Price != bidAdjustment*firstInitialPrice {
+			t.Errorf("Bid[0].Price was not adjusted properly. Expected %f, got %f", bidAdjustment*firstInitialPrice, mockBidderResponse.Bids[0].Bid.Price)
+		}
+		if mockBidderResponse.Bids[1].Bid.Price != bidAdjustment*secondInitialPrice {
+			t.Errorf("Bid[1].Price was not adjusted properly. Expected %f, got %f", bidAdjustment*secondInitialPrice, mockBidderResponse.Bids[1].Bid.Price)
+		}
+		if len(seatBid.httpCalls) != test.httpCallsLen {
+			t.Errorf("The bidder shouldn't log HttpCalls when request.test == 0. Found %d", len(seatBid.httpCalls))
+		}
+		if test.debugInfo.Allow && len(seatBid.httpCalls) > 0 {
+			assert.Equalf(t, "gzip", seatBid.httpCalls[0].RequestHeaders["Content-Encoding"][0], "Mismatched headers")
+			assert.Equalf(t, "{\"key\":\"val\"}", seatBid.httpCalls[0].RequestBody, "Mismatched request bodies")
+		}
+		for index, bid := range seatBid.bids {
+			assert.NotEqual(t, mockBidderResponse.Bids[index].Bid.Price, bid.originalBidCPM, "The bid price was adjusted, so the originally bid CPM should be different")
+		}
+	}
+}
+
 func TestRequestBidRemovesSensitiveHeaders(t *testing.T) {
 	server := httptest.NewServer(mockHandler(200, "getBody", "responseJson"))
 	defer server.Close()
@@ -188,7 +314,7 @@ func TestRequestBidRemovesSensitiveHeaders(t *testing.T) {
 	debugInfo := &config.DebugInfo{Allow: true}
 	ctx := context.Background()
 
-	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, debugInfo)
+	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, debugInfo, "")
 	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
 
 	bidderReq := BidderRequest{
@@ -241,7 +367,7 @@ func TestSetGPCHeader(t *testing.T) {
 	debugInfo := &config.DebugInfo{Allow: true}
 	ctx := context.Background()
 
-	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, debugInfo)
+	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, debugInfo, "")
 	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
 	bidderReq := BidderRequest{
 		BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}},
@@ -290,7 +416,7 @@ func TestSetGPCHeaderNil(t *testing.T) {
 	debugInfo := &config.DebugInfo{Allow: true}
 	ctx := context.Background()
 
-	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, debugInfo)
+	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, debugInfo, "")
 	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
 
 	bidderReq := BidderRequest{
@@ -361,7 +487,7 @@ func TestMultiBidder(t *testing.T) {
 			}},
 		bidResponse: mockBidderResponse,
 	}
-	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil)
+	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil, "")
 	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
 	bidderReq := BidderRequest{
 		BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}},
@@ -727,7 +853,7 @@ func TestMultiCurrencies(t *testing.T) {
 		)
 
 		// Execute:
-		bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil)
+		bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil, "")
 		currencyConverter := currency.NewRateConverter(
 			&http.Client{},
 			mockedHTTPServer.URL,
@@ -892,7 +1018,7 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) {
 		}
 
 		// Execute:
-		bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil)
+		bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil, "")
 		currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
 		bidderReq := BidderRequest{
 			BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}},
@@ -1065,7 +1191,7 @@ func TestMultiCurrencies_RequestCurrencyPick(t *testing.T) {
 		}
 
 		// Execute:
-		bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil)
+		bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil, "")
 		currencyConverter := currency.NewRateConverter(
 			&http.Client{},
 			mockedHTTPServer.URL,
@@ -1384,7 +1510,7 @@ func TestMobileNativeTypes(t *testing.T) {
 			},
 			bidResponse: tc.mockBidderResponse,
 		}
-		bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil)
+		bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil, "")
 		currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
 
 		bidderReq := BidderRequest{
@@ -1500,7 +1626,7 @@ func TestRequestBidsStoredBidResponses(t *testing.T) {
 	for _, tc := range testCases {
 
 		bidderImpl := &goodSingleBidderWithStoredBidResp{}
-		bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil)
+		bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil, "")
 		currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
 
 		bidderReq := BidderRequest{
@@ -1536,7 +1662,7 @@ func TestRequestBidsStoredBidResponses(t *testing.T) {
 }
 
 func TestErrorReporting(t *testing.T) {
-	bidder := AdaptBidder(&bidRejector{}, nil, &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil)
+	bidder := AdaptBidder(&bidRejector{}, nil, &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil, "")
 	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
 	bidderReq := BidderRequest{
 		BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}},
@@ -1768,7 +1894,7 @@ func TestCallRecordAdapterConnections(t *testing.T) {
 	metrics.On("RecordAdapterConnections", expectedAdapterName, false, mock.MatchedBy(compareConnWaitTime)).Once()
 
 	// Run requestBid using an http.Client with a mock handler
-	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, metrics, openrtb_ext.BidderAppnexus, nil)
+	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, metrics, openrtb_ext.BidderAppnexus, nil, "")
 	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
 
 	bidderReq := BidderRequest{
@@ -2009,7 +2135,7 @@ func TestRequestBidsWithAdsCertsSigner(t *testing.T) {
 		},
 	}
 
-	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{Allow: false})
+	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{Allow: false}, "")
 	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
 
 	bidderReq := BidderRequest{
@@ -2222,7 +2348,7 @@ func TestExtraBid(t *testing.T) {
 		},
 	}
 
-	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{})
+	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{}, "")
 	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
 
 	bidderReq := BidderRequest{
@@ -2335,7 +2461,7 @@ func TestExtraBidWithAlternateBidderCodeDisabled(t *testing.T) {
 		},
 	}
 
-	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{})
+	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{}, "")
 	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
 
 	bidderReq := BidderRequest{
@@ -2440,7 +2566,7 @@ func TestExtraBidWithBidAdjustments(t *testing.T) {
 		},
 	}
 
-	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{})
+	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{}, "")
 	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
 
 	bidderReq := BidderRequest{
@@ -2552,7 +2678,7 @@ func TestExtraBidWithBidAdjustmentsUsingAdapterCode(t *testing.T) {
 		},
 	}
 
-	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{})
+	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{}, "")
 	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
 
 	bidderReq := BidderRequest{
diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go
index 6c649d30..8ef79e30 100644
--- a/exchange/exchange_test.go
+++ b/exchange/exchange_test.go
@@ -92,13 +92,13 @@ func TestNewExchange(t *testing.T) {
 // The objective is to get to execute e.buildBidResponse(ctx.Background(), liveA... ) (*openrtb2.BidResponse, error)
 // and check whether the returned request successfully prints any '&' characters as it should
 // To do so, we:
-// 	1) Write the endpoint adapter URL with an '&' character into a new config,Configuration struct
-// 	   as specified in https://github.com/prebid/prebid-server/issues/465
-// 	2) Initialize a new exchange with said configuration
-// 	3) Build all the parameters e.buildBidResponse(ctx.Background(), liveA... ) needs including the
-// 	   sample request as specified in https://github.com/prebid/prebid-server/issues/465
-// 	4) Build a BidResponse struct using exchange.buildBidResponse(ctx.Background(), liveA... )
-// 	5) Assert we have no '&' characters in the response that exchange.buildBidResponse returns
+//  1. Write the endpoint adapter URL with an '&' character into a new config,Configuration struct
+//     as specified in https://github.com/prebid/prebid-server/issues/465
+//  2. Initialize a new exchange with said configuration
+//  3. Build all the parameters e.buildBidResponse(ctx.Background(), liveA... ) needs including the
+//     sample request as specified in https://github.com/prebid/prebid-server/issues/465
+//  4. Build a BidResponse struct using exchange.buildBidResponse(ctx.Background(), liveA... )
+//  5. Assert we have no '&' characters in the response that exchange.buildBidResponse returns
 func TestCharacterEscape(t *testing.T) {
 	// 1) Adapter with a '& char in its endpoint property
 	//    https://github.com/prebid/prebid-server/issues/465
@@ -348,7 +348,7 @@ func TestDebugBehaviour(t *testing.T) {
 	for _, test := range testCases {
 
 		e.adapterMap = map[openrtb_ext.BidderName]AdaptedBidder{
-			openrtb_ext.BidderAppnexus: AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{Allow: test.debugData.bidderLevelDebugAllowed}),
+			openrtb_ext.BidderAppnexus: AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{Allow: test.debugData.bidderLevelDebugAllowed}, ""),
 		}
 
 		bidRequest.Test = test.in.test
@@ -532,8 +532,8 @@ func TestTwoBiddersDebugDisabledAndEnabled(t *testing.T) {
 		}
 
 		e.adapterMap = map[openrtb_ext.BidderName]AdaptedBidder{
-			openrtb_ext.BidderAppnexus: AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{Allow: testCase.bidder1DebugEnabled}),
-			openrtb_ext.BidderTelaria:  AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{Allow: testCase.bidder2DebugEnabled}),
+			openrtb_ext.BidderAppnexus: AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{Allow: testCase.bidder1DebugEnabled}, ""),
+			openrtb_ext.BidderTelaria:  AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{Allow: testCase.bidder2DebugEnabled}, ""),
 		}
 		// Run test
 		outBidResponse, err := e.HoldAuction(context.Background(), auctionRequest, &debugLog)
@@ -694,7 +694,7 @@ func TestOverrideWithCustomCurrency(t *testing.T) {
 		}
 
 		e.adapterMap = map[openrtb_ext.BidderName]AdaptedBidder{
-			openrtb_ext.BidderAppnexus: AdaptBidder(oneDollarBidBidder, mockAppnexusBidService.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil),
+			openrtb_ext.BidderAppnexus: AdaptBidder(oneDollarBidBidder, mockAppnexusBidService.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil, ""),
 		}
 
 		// Set custom rates in extension
@@ -771,7 +771,7 @@ func TestAdapterCurrency(t *testing.T) {
 		categoriesFetcher: nilCategoryFetcher{},
 		bidIDGenerator:    &mockBidIDGenerator{false, false},
 		adapterMap: map[openrtb_ext.BidderName]AdaptedBidder{
-			openrtb_ext.BidderName("foo"): AdaptBidder(mockBidder, nil, &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderName("foo"), nil),
+			openrtb_ext.BidderName("foo"): AdaptBidder(mockBidder, nil, &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderName("foo"), nil, ""),
 		},
 	}
 
@@ -1152,7 +1152,7 @@ func TestReturnCreativeEndToEnd(t *testing.T) {
 
 	e := new(exchange)
 	e.adapterMap = map[openrtb_ext.BidderName]AdaptedBidder{
-		openrtb_ext.BidderAppnexus: AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil),
+		openrtb_ext.BidderAppnexus: AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil, ""),
 	}
 	e.cache = &wellBehavedCache{}
 	e.me = &metricsConf.NilMetricsEngine{}
@@ -1459,7 +1459,7 @@ func TestBidReturnsCreative(t *testing.T) {
 	}
 	e := new(exchange)
 	e.adapterMap = map[openrtb_ext.BidderName]AdaptedBidder{
-		openrtb_ext.BidderAppnexus: AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil),
+		openrtb_ext.BidderAppnexus: AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil, ""),
 	}
 	e.cache = &wellBehavedCache{}
 	e.me = &metricsConf.NilMetricsEngine{}
diff --git a/exchange/targeting_test.go b/exchange/targeting_test.go
index 95860ae7..0973f705 100644
--- a/exchange/targeting_test.go
+++ b/exchange/targeting_test.go
@@ -142,7 +142,7 @@ func buildAdapterMap(bids map[openrtb_ext.BidderName][]*openrtb2.Bid, mockServer
 		adapterMap[bidder] = AdaptBidder(&mockTargetingBidder{
 			mockServerURL: mockServerURL,
 			bids:          bids,
-		}, client, &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil)
+		}, client, &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil, "")
 	}
 	return adapterMap
 }
diff --git a/metrics/go_metrics_test.go b/metrics/go_metrics_test.go
index e82e7c65..709519e0 100644
--- a/metrics/go_metrics_test.go
+++ b/metrics/go_metrics_test.go
@@ -74,6 +74,9 @@ func TestNewMetrics(t *testing.T) {
 	ensureContains(t, registry, "syncer.foo.request.type_not_supported", m.SyncerRequestsMeter["foo"][SyncerCookieSyncTypeNotSupported])
 	ensureContains(t, registry, "syncer.foo.set.ok", m.SyncerSetsMeter["foo"][SyncerSetUidOK])
 	ensureContains(t, registry, "syncer.foo.set.cleared", m.SyncerSetsMeter["foo"][SyncerSetUidCleared])
+
+	ensureContains(t, registry, "ads_cert_requests.ok", m.AdsCertRequestsSuccess)
+	ensureContains(t, registry, "ads_cert_requests.failed", m.AdsCertRequestsFailure)
 }
 
 func TestRecordBidType(t *testing.T) {
@@ -789,6 +792,70 @@ func TestStoredResponses(t *testing.T) {
 	}
 }
 
+func TestRecordAdsCertSignTime(t *testing.T) {
+	testCases := []struct {
+		description           string
+		inAdsCertSignDuration time.Duration
+		outExpDuration        time.Duration
+	}{
+		{
+			description:           "Five second AdsCertSign time",
+			inAdsCertSignDuration: time.Second * 5,
+			outExpDuration:        time.Second * 5,
+		},
+		{
+			description:           "Five millisecond AdsCertSign time",
+			inAdsCertSignDuration: time.Millisecond * 5,
+			outExpDuration:        time.Millisecond * 5,
+		},
+		{
+			description:           "Zero AdsCertSign time",
+			inAdsCertSignDuration: time.Duration(0),
+			outExpDuration:        time.Duration(0),
+		},
+	}
+	for _, test := range testCases {
+		registry := metrics.NewRegistry()
+		m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{}, nil)
+
+		m.RecordAdsCertSignTime(test.inAdsCertSignDuration)
+
+		assert.Equal(t, test.outExpDuration.Nanoseconds(), m.adsCertSignTimer.Sum(), test.description)
+	}
+}
+
+func TestRecordAdsCertReqMetric(t *testing.T) {
+	testCases := []struct {
+		description                  string
+		requestSuccess               bool
+		expectedSuccessRequestsCount int64
+		expectedFailedRequestsCount  int64
+	}{
+		{
+			description:                  "Record failed request, expected success request count is 0 and failed request count is 1",
+			requestSuccess:               false,
+			expectedSuccessRequestsCount: 0,
+			expectedFailedRequestsCount:  1,
+		},
+		{
+			description:                  "Record successful request, expected success request count is 1 and failed request count is 0",
+			requestSuccess:               true,
+			expectedSuccessRequestsCount: 1,
+			expectedFailedRequestsCount:  0,
+		},
+	}
+
+	for _, test := range testCases {
+		registry := metrics.NewRegistry()
+		m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{}, nil)
+
+		m.RecordAdsCertReq(test.requestSuccess)
+
+		assert.Equal(t, test.expectedSuccessRequestsCount, m.AdsCertRequestsSuccess.Count(), test.description)
+		assert.Equal(t, test.expectedFailedRequestsCount, m.AdsCertRequestsFailure.Count(), test.description)
+	}
+}
+
 func ensureContainsBidTypeMetrics(t *testing.T, registry metrics.Registry, prefix string, mdm map[openrtb_ext.BidType]*MarkupDeliveryMetrics) {
 	ensureContains(t, registry, prefix+".banner.adm_bids_received", mdm[openrtb_ext.BidTypeBanner].AdmMeter)
 	ensureContains(t, registry, prefix+".banner.nurl_bids_received", mdm[openrtb_ext.BidTypeBanner].NurlMeter)
diff --git a/metrics/prometheus/prometheus_test.go b/metrics/prometheus/prometheus_test.go
index 2b1704b7..84516b4d 100644
--- a/metrics/prometheus/prometheus_test.go
+++ b/metrics/prometheus/prometheus_test.go
@@ -1690,3 +1690,87 @@ func TestStoredResponsesMetric(t *testing.T) {
 		assertCounterValue(t, "", "stored responses", m.storedResponses, test.expectedStoredResponsesCount)
 	}
 }
+
+func TestRecordAdsCertReqMetric(t *testing.T) {
+	testCases := []struct {
+		description                  string
+		requestSuccess               bool
+		expectedSuccessRequestsCount float64
+		expectedFailedRequestsCount  float64
+	}{
+		{
+			description:                  "Record failed request, expected success request count is 0 and failed request count is 1",
+			requestSuccess:               false,
+			expectedSuccessRequestsCount: 0,
+			expectedFailedRequestsCount:  1,
+		},
+		{
+			description:                  "Record successful request, expected success request count is 1 and failed request count is 0",
+			requestSuccess:               true,
+			expectedSuccessRequestsCount: 1,
+			expectedFailedRequestsCount:  0,
+		},
+	}
+
+	for _, test := range testCases {
+		m := createMetricsForTesting()
+		m.RecordAdsCertReq(test.requestSuccess)
+		assertCounterVecValue(t, test.description, "successfully signed requests", m.adsCertRequests, test.expectedSuccessRequestsCount, prometheus.Labels{successLabel: requestSuccessful})
+		assertCounterVecValue(t, test.description, "unsuccessfully signed requests", m.adsCertRequests, test.expectedFailedRequestsCount, prometheus.Labels{successLabel: requestFailed})
+	}
+}
+
+func TestRecordAdsCertSignTime(t *testing.T) {
+	type testIn struct {
+		adsCertSignDuration time.Duration
+	}
+	type testOut struct {
+		expDuration float64
+		expCount    uint64
+	}
+	testCases := []struct {
+		description string
+		in          testIn
+		out         testOut
+	}{
+		{
+			description: "Five second AdsCert sign time",
+			in: testIn{
+				adsCertSignDuration: time.Second * 5,
+			},
+			out: testOut{
+				expDuration: 5,
+				expCount:    1,
+			},
+		},
+		{
+			description: "Five millisecond AdsCert sign time",
+			in: testIn{
+				adsCertSignDuration: time.Millisecond * 5,
+			},
+			out: testOut{
+				expDuration: 0.005,
+				expCount:    1,
+			},
+		},
+		{
+			description: "Zero AdsCert sign time",
+			in:          testIn{},
+			out: testOut{
+				expDuration: 0,
+				expCount:    1,
+			},
+		},
+	}
+	for i, test := range testCases {
+		pm := createMetricsForTesting()
+		pm.RecordAdsCertSignTime(test.in.adsCertSignDuration)
+
+		m := dto.Metric{}
+		pm.adsCertSignTimer.Write(&m)
+		histogram := *m.GetHistogram()
+
+		assert.Equal(t, test.out.expCount, histogram.GetSampleCount(), "[%d] Incorrect number of histogram entries. Desc: %s\n", i, test.description)
+		assert.Equal(t, test.out.expDuration, histogram.GetSampleSum(), "[%d] Incorrect number of histogram cumulative values. Desc: %s\n", i, test.description)
+	}
+}
diff --git a/openrtb_ext/convert_down_test.go b/openrtb_ext/convert_down_test.go
new file mode 100644
index 00000000..2ffd2098
--- /dev/null
+++ b/openrtb_ext/convert_down_test.go
@@ -0,0 +1,354 @@
+package openrtb_ext
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/mxmCherry/openrtb/v16/openrtb2"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestConvertDownTo25(t *testing.T) {
+	testCases := []struct {
+		description     string
+		givenRequest    openrtb2.BidRequest
+		expectedRequest openrtb2.BidRequest
+		expectedErr     string
+	}{
+		{
+			description: "2.6 -> 2.5",
+			givenRequest: openrtb2.BidRequest{
+				ID:     "anyID",
+				Source: &openrtb2.Source{SChain: &openrtb2.SupplyChain{Complete: 1, Nodes: []openrtb2.SupplyChainNode{}, Ver: "2"}},
+				Regs:   &openrtb2.Regs{GDPR: openrtb2.Int8Ptr(1), USPrivacy: "3"},
+				User:   &openrtb2.User{Consent: "1", EIDs: []openrtb2.EID{{Source: "42"}}},
+			},
+			expectedRequest: openrtb2.BidRequest{
+				ID:     "anyID",
+				Source: &openrtb2.Source{Ext: json.RawMessage(`{"schain":{"complete":1,"nodes":[],"ver":"2"}}`)},
+				Regs:   &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr":1,"us_privacy":"3"}`)},
+				User:   &openrtb2.User{Ext: json.RawMessage(`{"consent":"1","eids":[{"source":"42"}]}`)},
+			},
+		},
+		{
+			description: "2.6 -> 2.5 + Other Ext Fields",
+			givenRequest: openrtb2.BidRequest{
+				ID:     "anyID",
+				Ext:    json.RawMessage(`{"other":"otherExt"}`),
+				Source: &openrtb2.Source{SChain: &openrtb2.SupplyChain{Complete: 1, Nodes: []openrtb2.SupplyChainNode{}, Ver: "2"}, Ext: json.RawMessage(`{"other":"otherSource"}`)},
+				Regs:   &openrtb2.Regs{GDPR: openrtb2.Int8Ptr(1), USPrivacy: "3", Ext: json.RawMessage(`{"other":"otherRegs"}`)},
+				User:   &openrtb2.User{Consent: "1", EIDs: []openrtb2.EID{{Source: "42"}}, Ext: json.RawMessage(`{"other":"otherUser"}`)},
+			},
+			expectedRequest: openrtb2.BidRequest{
+				ID:     "anyID",
+				Ext:    json.RawMessage(`{"other":"otherExt"}`),
+				Source: &openrtb2.Source{Ext: json.RawMessage(`{"other":"otherSource","schain":{"complete":1,"nodes":[],"ver":"2"}}`)},
+				Regs:   &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr":1,"other":"otherRegs","us_privacy":"3"}`)},
+				User:   &openrtb2.User{Ext: json.RawMessage(`{"consent":"1","eids":[{"source":"42"}],"other":"otherUser"}`)},
+			},
+		},
+		{
+			description: "Malformed - SChain",
+			givenRequest: openrtb2.BidRequest{
+				ID:     "anyID",
+				Source: &openrtb2.Source{SChain: &openrtb2.SupplyChain{Complete: 1, Nodes: []openrtb2.SupplyChainNode{}, Ver: "2"}, Ext: json.RawMessage(`malformed`)},
+			},
+			expectedErr: "invalid character 'm' looking for beginning of value",
+		},
+		{
+			description: "Malformed - GDPR",
+			givenRequest: openrtb2.BidRequest{
+				ID:   "anyID",
+				Regs: &openrtb2.Regs{GDPR: openrtb2.Int8Ptr(1), Ext: json.RawMessage(`malformed`)},
+			},
+			expectedErr: "invalid character 'm' looking for beginning of value",
+		},
+		{
+			description: "Malformed - Consent",
+			givenRequest: openrtb2.BidRequest{
+				ID:   "anyID",
+				User: &openrtb2.User{Consent: "1", Ext: json.RawMessage(`malformed`)},
+			},
+			expectedErr: "invalid character 'm' looking for beginning of value",
+		},
+		{
+			description: "Malformed - USPrivacy",
+			givenRequest: openrtb2.BidRequest{
+				ID:   "anyID",
+				Regs: &openrtb2.Regs{USPrivacy: "3", Ext: json.RawMessage(`malformed`)},
+			},
+			expectedErr: "invalid character 'm' looking for beginning of value",
+		},
+		{
+			description: "Malformed - EID",
+			givenRequest: openrtb2.BidRequest{
+				ID:   "anyID",
+				User: &openrtb2.User{EIDs: []openrtb2.EID{{Source: "42"}}, Ext: json.RawMessage(`malformed`)},
+			},
+			expectedErr: "invalid character 'm' looking for beginning of value",
+		},
+	}
+
+	for _, test := range testCases {
+		w := &RequestWrapper{BidRequest: &test.givenRequest}
+		err := ConvertDownTo25(w)
+		if len(test.expectedErr) > 0 {
+			assert.EqualError(t, err, test.expectedErr, test.description)
+		} else {
+			assert.NoError(t, w.RebuildRequest(), test.description)
+			assert.Equal(t, test.expectedRequest, *w.BidRequest, test.description)
+		}
+	}
+}
+
+func TestMoveSupplyChainFrom26To25(t *testing.T) {
+	var (
+		schain1     = &openrtb2.SupplyChain{Complete: 1, Nodes: []openrtb2.SupplyChainNode{}, Ver: "1"}
+		schain1Json = json.RawMessage(`{"schain":{"complete":1,"nodes":[],"ver":"1"}}`)
+		schain2Json = json.RawMessage(`{"schain":{"complete":1,"nodes":[],"ver":"2"}}`)
+	)
+
+	testCases := []struct {
+		description     string
+		givenRequest    openrtb2.BidRequest
+		expectedRequest openrtb2.BidRequest
+		expectedErr     string
+	}{
+		{
+			description:     "Not Present - Source",
+			givenRequest:    openrtb2.BidRequest{},
+			expectedRequest: openrtb2.BidRequest{},
+		},
+		{
+			description:     "Not Present - Source Schain",
+			givenRequest:    openrtb2.BidRequest{Source: &openrtb2.Source{}},
+			expectedRequest: openrtb2.BidRequest{Source: &openrtb2.Source{}},
+		},
+		{
+			description:     "2.6 Migrated To 2.5",
+			givenRequest:    openrtb2.BidRequest{Source: &openrtb2.Source{SChain: schain1}},
+			expectedRequest: openrtb2.BidRequest{Source: &openrtb2.Source{Ext: schain1Json}},
+		},
+		{
+			description:     "2.5 Overwritten",
+			givenRequest:    openrtb2.BidRequest{Source: &openrtb2.Source{SChain: schain1, Ext: schain2Json}},
+			expectedRequest: openrtb2.BidRequest{Source: &openrtb2.Source{Ext: schain1Json}},
+		},
+		{
+			description:  "Malformed",
+			givenRequest: openrtb2.BidRequest{Source: &openrtb2.Source{SChain: schain1, Ext: json.RawMessage(`malformed`)}},
+			expectedErr:  "invalid character 'm' looking for beginning of value",
+		},
+	}
+
+	for _, test := range testCases {
+		w := &RequestWrapper{BidRequest: &test.givenRequest}
+		err := moveSupplyChainFrom26To25(w)
+
+		if len(test.expectedErr) > 0 {
+			assert.EqualError(t, err, test.expectedErr, test.description)
+		} else {
+			assert.NoError(t, w.RebuildRequest(), test.description)
+			assert.Equal(t, test.expectedRequest, *w.BidRequest, test.description)
+		}
+	}
+}
+
+func TestMoveGDPRFrom26To25(t *testing.T) {
+	testCases := []struct {
+		description     string
+		givenRequest    openrtb2.BidRequest
+		expectedRequest openrtb2.BidRequest
+		expectedErr     string
+	}{
+		{
+			description:     "Not Present - Regs",
+			givenRequest:    openrtb2.BidRequest{},
+			expectedRequest: openrtb2.BidRequest{},
+		},
+		{
+			description:     "Not Present - Regs GDPR",
+			givenRequest:    openrtb2.BidRequest{Regs: &openrtb2.Regs{}},
+			expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{}},
+		},
+		{
+			description:     "2.6 Migrated To 2.5",
+			givenRequest:    openrtb2.BidRequest{Regs: &openrtb2.Regs{GDPR: openrtb2.Int8Ptr(0)}},
+			expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr":0}`)}},
+		},
+		{
+			description:     "2.5 Overwritten",
+			givenRequest:    openrtb2.BidRequest{Regs: &openrtb2.Regs{GDPR: openrtb2.Int8Ptr(0), Ext: json.RawMessage(`{"gdpr":1}`)}},
+			expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr":0}`)}},
+		},
+		{
+			description:  "Malformed",
+			givenRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{GDPR: openrtb2.Int8Ptr(0), Ext: json.RawMessage(`malformed`)}},
+			expectedErr:  "invalid character 'm' looking for beginning of value",
+		},
+	}
+
+	for _, test := range testCases {
+		w := &RequestWrapper{BidRequest: &test.givenRequest}
+		err := moveGDPRFrom26To25(w)
+
+		if len(test.expectedErr) > 0 {
+			assert.EqualError(t, err, test.expectedErr, test.description)
+		} else {
+			assert.NoError(t, w.RebuildRequest(), test.description)
+			assert.Equal(t, test.expectedRequest, *w.BidRequest, test.description)
+		}
+	}
+}
+
+func TestMoveConsentFrom26To25(t *testing.T) {
+	testCases := []struct {
+		description     string
+		givenRequest    openrtb2.BidRequest
+		expectedRequest openrtb2.BidRequest
+		expectedErr     string
+	}{
+		{
+			description:     "Not Present - User",
+			givenRequest:    openrtb2.BidRequest{},
+			expectedRequest: openrtb2.BidRequest{},
+		},
+		{
+			description:     "Not Present - User Consent",
+			givenRequest:    openrtb2.BidRequest{User: &openrtb2.User{}},
+			expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{}},
+		},
+		{
+			description:     "2.6 Migrated To 2.5",
+			givenRequest:    openrtb2.BidRequest{User: &openrtb2.User{Consent: "1"}},
+			expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consent":"1"}`)}},
+		},
+		{
+			description:     "2.5 Overwritten",
+			givenRequest:    openrtb2.BidRequest{User: &openrtb2.User{Consent: "1", Ext: json.RawMessage(`{"consent":"2"}`)}},
+			expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consent":"1"}`)}},
+		},
+		{
+			description:  "Malformed",
+			givenRequest: openrtb2.BidRequest{User: &openrtb2.User{Consent: "1", Ext: json.RawMessage(`malformed`)}},
+			expectedErr:  "invalid character 'm' looking for beginning of value",
+		},
+	}
+
+	for _, test := range testCases {
+		w := &RequestWrapper{BidRequest: &test.givenRequest}
+		err := moveConsentFrom26To25(w)
+
+		if len(test.expectedErr) > 0 {
+			assert.EqualError(t, err, test.expectedErr, test.description)
+		} else {
+			assert.NoError(t, w.RebuildRequest(), test.description)
+			assert.Equal(t, test.expectedRequest, *w.BidRequest, test.description)
+		}
+	}
+}
+
+func TestMoveUSPrivacyFrom26To25(t *testing.T) {
+	testCases := []struct {
+		description     string
+		givenRequest    openrtb2.BidRequest
+		expectedRequest openrtb2.BidRequest
+		expectedErr     string
+	}{
+		{
+			description:     "Not Present - Regs",
+			givenRequest:    openrtb2.BidRequest{},
+			expectedRequest: openrtb2.BidRequest{},
+		},
+		{
+			description:     "Not Present - Regs USPrivacy",
+			givenRequest:    openrtb2.BidRequest{Regs: &openrtb2.Regs{}},
+			expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{}},
+		},
+		{
+			description:     "2.6 Migrated To 2.5",
+			givenRequest:    openrtb2.BidRequest{Regs: &openrtb2.Regs{USPrivacy: "1"}},
+			expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"1"}`)}},
+		},
+		{
+			description:     "2.5 Overwritten",
+			givenRequest:    openrtb2.BidRequest{Regs: &openrtb2.Regs{USPrivacy: "1", Ext: json.RawMessage(`{"us_privacy":"2"}`)}},
+			expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"1"}`)}},
+		},
+		{
+			description:  "Malformed",
+			givenRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{USPrivacy: "1", Ext: json.RawMessage(`malformed`)}},
+			expectedErr:  "invalid character 'm' looking for beginning of value",
+		},
+	}
+
+	for _, test := range testCases {
+		w := &RequestWrapper{BidRequest: &test.givenRequest}
+		err := moveUSPrivacyFrom26To25(w)
+
+		if len(test.expectedErr) > 0 {
+			assert.EqualError(t, err, test.expectedErr, test.description)
+		} else {
+			assert.NoError(t, w.RebuildRequest(), test.description)
+			assert.Equal(t, test.expectedRequest, *w.BidRequest, test.description)
+		}
+	}
+}
+
+func TestMoveEIDFrom26To25(t *testing.T) {
+	var (
+		eid1     = []openrtb2.EID{{Source: "1"}}
+		eid1Json = json.RawMessage(`{"eids":[{"source":"1"}]}`)
+		eid2Json = json.RawMessage(`{"eids":[{"source":"2"}]}`)
+	)
+
+	testCases := []struct {
+		description     string
+		givenRequest    openrtb2.BidRequest
+		expectedRequest openrtb2.BidRequest
+		expectedErr     string
+	}{
+		{
+			description:     "Not Present - User",
+			givenRequest:    openrtb2.BidRequest{},
+			expectedRequest: openrtb2.BidRequest{},
+		},
+		{
+			description:     "Not Present - User EIDs",
+			givenRequest:    openrtb2.BidRequest{User: &openrtb2.User{}},
+			expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{}},
+		},
+		{
+			description:     "2.6 Migrated To 2.5",
+			givenRequest:    openrtb2.BidRequest{User: &openrtb2.User{EIDs: eid1}},
+			expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{Ext: eid1Json}},
+		},
+		{
+			description:     "2.6 Migrated To 2.5 - Empty",
+			givenRequest:    openrtb2.BidRequest{User: &openrtb2.User{EIDs: []openrtb2.EID{}}},
+			expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{}},
+		},
+		{
+			description:     "2.5 Overwritten",
+			givenRequest:    openrtb2.BidRequest{User: &openrtb2.User{EIDs: eid1, Ext: eid2Json}},
+			expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{Ext: eid1Json}},
+		},
+		{
+			description:  "Malformed",
+			givenRequest: openrtb2.BidRequest{User: &openrtb2.User{EIDs: eid1, Ext: json.RawMessage(`malformed`)}},
+			expectedErr:  "invalid character 'm' looking for beginning of value",
+		},
+	}
+
+	for _, test := range testCases {
+		w := &RequestWrapper{BidRequest: &test.givenRequest}
+		err := moveEIDFrom26To25(w)
+
+		if len(test.expectedErr) > 0 {
+			assert.EqualError(t, err, test.expectedErr, test.description)
+		} else {
+			assert.NoError(t, w.RebuildRequest(), test.description)
+			assert.Equal(t, test.expectedRequest, *w.BidRequest, test.description)
+		}
+	}
+}
diff --git a/openrtb_ext/convert_up_test.go b/openrtb_ext/convert_up_test.go
new file mode 100644
index 00000000..075d4f82
--- /dev/null
+++ b/openrtb_ext/convert_up_test.go
@@ -0,0 +1,381 @@
+package openrtb_ext
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/mxmCherry/openrtb/v16/openrtb2"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestConvertUpTo26(t *testing.T) {
+	testCases := []struct {
+		description     string
+		givenRequest    openrtb2.BidRequest
+		expectedRequest openrtb2.BidRequest
+		expectedErr     string
+	}{
+		{
+			description: "Malformed",
+			givenRequest: openrtb2.BidRequest{
+				Ext: json.RawMessage(`malformed`),
+			},
+			expectedErr: "req.ext is invalid: invalid character 'm' looking for beginning of value",
+		},
+		{
+			description: "2.4 -> 2.6",
+			givenRequest: openrtb2.BidRequest{
+				ID:   "anyID",
+				Ext:  json.RawMessage(`{"schain":{"complete":1,"nodes":[],"ver":"2"}}`),
+				Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr":1,"us_privacy":"3"}`)},
+				User: &openrtb2.User{Ext: json.RawMessage(`{"consent":"1","eids":[{"source":"42"}]}`)},
+			},
+			expectedRequest: openrtb2.BidRequest{
+				ID:     "anyID",
+				Source: &openrtb2.Source{SChain: &openrtb2.SupplyChain{Complete: 1, Nodes: []openrtb2.SupplyChainNode{}, Ver: "2"}},
+				Regs:   &openrtb2.Regs{GDPR: openrtb2.Int8Ptr(1), USPrivacy: "3"},
+				User:   &openrtb2.User{Consent: "1", EIDs: []openrtb2.EID{{Source: "42"}}},
+			},
+		},
+		{
+			description: "2.4 -> 2.6 + Other Ext Fields",
+			givenRequest: openrtb2.BidRequest{
+				ID:   "anyID",
+				Ext:  json.RawMessage(`{"schain":{"complete":1,"nodes":[],"ver":"2"},"other":"otherExt"}`),
+				Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr":1,"other":"otherRegs","us_privacy":"3"}`)},
+				User: &openrtb2.User{Ext: json.RawMessage(`{"consent":"1","eids":[{"source":"42"}],"other":"otherUser"}`)},
+			},
+			expectedRequest: openrtb2.BidRequest{
+				ID:     "anyID",
+				Ext:    json.RawMessage(`{"other":"otherExt"}`),
+				Source: &openrtb2.Source{SChain: &openrtb2.SupplyChain{Complete: 1, Nodes: []openrtb2.SupplyChainNode{}, Ver: "2"}},
+				Regs:   &openrtb2.Regs{GDPR: openrtb2.Int8Ptr(1), USPrivacy: "3", Ext: json.RawMessage(`{"other":"otherRegs"}`)},
+				User:   &openrtb2.User{Consent: "1", EIDs: []openrtb2.EID{{Source: "42"}}, Ext: json.RawMessage(`{"other":"otherUser"}`)},
+			},
+		},
+		{
+			description: "2.5 -> 2.6",
+			givenRequest: openrtb2.BidRequest{
+				ID:     "anyID",
+				Source: &openrtb2.Source{Ext: json.RawMessage(`{"schain":{"complete":1,"nodes":[],"ver":"2"}}`)},
+				Regs:   &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr":1,"us_privacy":"3"}`)},
+				User:   &openrtb2.User{Ext: json.RawMessage(`{"consent":"1","eids":[{"source":"42"}]}`)},
+			},
+			expectedRequest: openrtb2.BidRequest{
+				ID:     "anyID",
+				Source: &openrtb2.Source{SChain: &openrtb2.SupplyChain{Complete: 1, Nodes: []openrtb2.SupplyChainNode{}, Ver: "2"}},
+				Regs:   &openrtb2.Regs{GDPR: openrtb2.Int8Ptr(1), USPrivacy: "3"},
+				User:   &openrtb2.User{Consent: "1", EIDs: []openrtb2.EID{{Source: "42"}}},
+			},
+		},
+		{
+			description: "2.5 -> 2.6 + Other Ext Fields",
+			givenRequest: openrtb2.BidRequest{
+				ID:     "anyID",
+				Ext:    json.RawMessage(`{"other":"otherExt"}`),
+				Source: &openrtb2.Source{Ext: json.RawMessage(`{"schain":{"complete":1,"nodes":[],"ver":"2"},"other":"otherSource"}`)},
+				Regs:   &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr":1,"us_privacy":"3","other":"otherRegs"}`)},
+				User:   &openrtb2.User{Ext: json.RawMessage(`{"consent":"1","eids":[{"source":"42"}],"other":"otherUser"}`)},
+			},
+			expectedRequest: openrtb2.BidRequest{
+				ID:     "anyID",
+				Ext:    json.RawMessage(`{"other":"otherExt"}`),
+				Source: &openrtb2.Source{SChain: &openrtb2.SupplyChain{Complete: 1, Nodes: []openrtb2.SupplyChainNode{}, Ver: "2"}, Ext: json.RawMessage(`{"other":"otherSource"}`)},
+				Regs:   &openrtb2.Regs{GDPR: openrtb2.Int8Ptr(1), USPrivacy: "3", Ext: json.RawMessage(`{"other":"otherRegs"}`)},
+				User:   &openrtb2.User{Consent: "1", EIDs: []openrtb2.EID{{Source: "42"}}, Ext: json.RawMessage(`{"other":"otherUser"}`)},
+			},
+		},
+	}
+
+	for _, test := range testCases {
+		w := &RequestWrapper{BidRequest: &test.givenRequest}
+		err := ConvertUpTo26(w)
+		if len(test.expectedErr) > 0 {
+			assert.EqualError(t, err, test.expectedErr, test.description)
+		} else {
+			assert.NoError(t, w.RebuildRequest(), test.description)
+			assert.Equal(t, test.expectedRequest, *w.BidRequest, test.description)
+		}
+	}
+}
+
+func TestConvertUpEnsureExt(t *testing.T) {
+	testCases := []struct {
+		description  string
+		givenRequest openrtb2.BidRequest
+		expectedErr  string
+	}{
+		{
+			description:  "Empty",
+			givenRequest: openrtb2.BidRequest{},
+		},
+		{
+			description:  "Ext",
+			givenRequest: openrtb2.BidRequest{Ext: json.RawMessage("malformed")},
+			expectedErr:  "req.ext is invalid: invalid character 'm' looking for beginning of value",
+		},
+		{
+			description:  "Source.Ext",
+			givenRequest: openrtb2.BidRequest{Source: &openrtb2.Source{Ext: json.RawMessage("malformed")}},
+			expectedErr:  "req.source.ext is invalid: invalid character 'm' looking for beginning of value",
+		},
+		{
+			description:  "Regs.Ext",
+			givenRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage("malformed")}},
+			expectedErr:  "req.regs.ext is invalid: invalid character 'm' looking for beginning of value",
+		},
+		{
+			description:  "User.Ext",
+			givenRequest: openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage("malformed")}},
+			expectedErr:  "req.user.ext is invalid: invalid character 'm' looking for beginning of value",
+		},
+	}
+
+	for _, test := range testCases {
+		w := &RequestWrapper{BidRequest: &test.givenRequest}
+		err := convertUpEnsureExt(w)
+		if len(test.expectedErr) > 0 {
+			assert.EqualError(t, err, test.expectedErr, test.description)
+		} else {
+			assert.NoError(t, err, test.description)
+		}
+	}
+}
+
+func TestMoveSupplyChainFrom24To25(t *testing.T) {
+	var (
+		schain1 = json.RawMessage(`{"schain":{"complete":1,"nodes":[],"ver":"1"}}`)
+		schain2 = json.RawMessage(`{"schain":{"complete":1,"nodes":[],"ver":"2"}}`)
+	)
+
+	testCases := []struct {
+		description     string
+		givenRequest    openrtb2.BidRequest
+		expectedRequest openrtb2.BidRequest
+	}{
+		{
+			description:     "Not Present",
+			givenRequest:    openrtb2.BidRequest{},
+			expectedRequest: openrtb2.BidRequest{},
+		},
+		{
+			description:     "2.4 Migrated To 2.5 - Source Doesn't Exist",
+			givenRequest:    openrtb2.BidRequest{Ext: schain1},
+			expectedRequest: openrtb2.BidRequest{Source: &openrtb2.Source{Ext: schain1}},
+		},
+		{
+			description:     "2.4 Migrated To 2.5 - Source Exists",
+			givenRequest:    openrtb2.BidRequest{Source: &openrtb2.Source{}, Ext: schain1},
+			expectedRequest: openrtb2.BidRequest{Source: &openrtb2.Source{Ext: schain1}},
+		},
+		{
+			description:     "2.4 Dropped",
+			givenRequest:    openrtb2.BidRequest{Source: &openrtb2.Source{Ext: schain1}, Ext: schain2},
+			expectedRequest: openrtb2.BidRequest{Source: &openrtb2.Source{Ext: schain1}},
+		},
+		{
+			description:     "2.5 Left Alone",
+			givenRequest:    openrtb2.BidRequest{Source: &openrtb2.Source{Ext: schain1}},
+			expectedRequest: openrtb2.BidRequest{Source: &openrtb2.Source{Ext: schain1}},
+		},
+	}
+
+	for _, test := range testCases {
+		w := &RequestWrapper{BidRequest: &test.givenRequest}
+		moveSupplyChainFrom24To25(w)
+		assert.NoError(t, w.RebuildRequest(), test.description)
+		assert.Equal(t, test.expectedRequest, *w.BidRequest, test.description)
+	}
+}
+
+func TestConvertSupplyChainFrom25To26(t *testing.T) {
+	var (
+		schain1     = &openrtb2.SupplyChain{Complete: 1, Nodes: []openrtb2.SupplyChainNode{}, Ver: "1"}
+		schain1Json = json.RawMessage(`{"schain":{"complete":1,"nodes":[],"ver":"1"}}`)
+		schain2Json = json.RawMessage(`{"schain":{"complete":1,"nodes":[],"ver":"2"}}`)
+	)
+
+	testCases := []struct {
+		description     string
+		givenRequest    openrtb2.BidRequest
+		expectedRequest openrtb2.BidRequest
+	}{
+		{
+			description:     "Not Present",
+			givenRequest:    openrtb2.BidRequest{},
+			expectedRequest: openrtb2.BidRequest{},
+		},
+		{
+			description:     "2.5 Migrated To 2.6",
+			givenRequest:    openrtb2.BidRequest{Source: &openrtb2.Source{Ext: schain1Json}},
+			expectedRequest: openrtb2.BidRequest{Source: &openrtb2.Source{SChain: schain1}},
+		},
+		{
+			description:     "2.5 Dropped",
+			givenRequest:    openrtb2.BidRequest{Source: &openrtb2.Source{SChain: schain1, Ext: schain2Json}},
+			expectedRequest: openrtb2.BidRequest{Source: &openrtb2.Source{SChain: schain1}},
+		},
+		{
+			description:     "2.6 Left Alone",
+			givenRequest:    openrtb2.BidRequest{Source: &openrtb2.Source{SChain: schain1}},
+			expectedRequest: openrtb2.BidRequest{Source: &openrtb2.Source{SChain: schain1}},
+		},
+	}
+
+	for _, test := range testCases {
+		w := &RequestWrapper{BidRequest: &test.givenRequest}
+		moveSupplyChainFrom25To26(w)
+		assert.NoError(t, w.RebuildRequest(), test.description)
+		assert.Equal(t, test.expectedRequest, *w.BidRequest, test.description)
+	}
+}
+
+func TestMoveGDPRFrom25To26(t *testing.T) {
+	testCases := []struct {
+		description     string
+		givenRequest    openrtb2.BidRequest
+		expectedRequest openrtb2.BidRequest
+	}{
+		{
+			description:     "Not Present",
+			givenRequest:    openrtb2.BidRequest{},
+			expectedRequest: openrtb2.BidRequest{},
+		},
+		{
+			description:     "2.5 Migrated To 2.6",
+			givenRequest:    openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr":0}`)}},
+			expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{GDPR: openrtb2.Int8Ptr(0)}},
+		},
+		{
+			description:     "2.5 Dropped",
+			givenRequest:    openrtb2.BidRequest{Regs: &openrtb2.Regs{GDPR: openrtb2.Int8Ptr(0), Ext: json.RawMessage(`{"gdpr":1}`)}},
+			expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{GDPR: openrtb2.Int8Ptr(0)}},
+		},
+		{
+			description:     "2.6 Left Alone",
+			givenRequest:    openrtb2.BidRequest{Regs: &openrtb2.Regs{GDPR: openrtb2.Int8Ptr(0)}},
+			expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{GDPR: openrtb2.Int8Ptr(0)}},
+		},
+	}
+
+	for _, test := range testCases {
+		w := &RequestWrapper{BidRequest: &test.givenRequest}
+		moveGDPRFrom25To26(w)
+		assert.NoError(t, w.RebuildRequest(), test.description)
+		assert.Equal(t, test.expectedRequest, *w.BidRequest, test.description)
+	}
+}
+
+func TestMoveConsentFrom25To26(t *testing.T) {
+	testCases := []struct {
+		description     string
+		givenRequest    openrtb2.BidRequest
+		expectedRequest openrtb2.BidRequest
+	}{
+		{
+			description:     "Not Present",
+			givenRequest:    openrtb2.BidRequest{},
+			expectedRequest: openrtb2.BidRequest{},
+		},
+		{
+			description:     "2.5 Migrated To 2.6",
+			givenRequest:    openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consent":"1"}`)}},
+			expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{Consent: "1"}},
+		},
+		{
+			description:     "2.5 Dropped",
+			givenRequest:    openrtb2.BidRequest{User: &openrtb2.User{Consent: "1", Ext: json.RawMessage(`{"consent":2}`)}},
+			expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{Consent: "1"}},
+		},
+		{
+			description:     "2.6 Left Alone",
+			givenRequest:    openrtb2.BidRequest{User: &openrtb2.User{Consent: "1"}},
+			expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{Consent: "1"}},
+		},
+	}
+
+	for _, test := range testCases {
+		w := &RequestWrapper{BidRequest: &test.givenRequest}
+		moveConsentFrom25To26(w)
+		assert.NoError(t, w.RebuildRequest(), test.description)
+		assert.Equal(t, test.expectedRequest, *w.BidRequest, test.description)
+	}
+}
+
+func TestMoveUSPrivacyFrom25To26(t *testing.T) {
+	testCases := []struct {
+		description     string
+		givenRequest    openrtb2.BidRequest
+		expectedRequest openrtb2.BidRequest
+	}{
+		{
+			description:     "Not Present",
+			givenRequest:    openrtb2.BidRequest{},
+			expectedRequest: openrtb2.BidRequest{},
+		},
+		{
+			description:     "2.5 Migrated To 2.6",
+			givenRequest:    openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"1"}`)}},
+			expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{USPrivacy: "1"}},
+		},
+		{
+			description:     "2.5 Dropped",
+			givenRequest:    openrtb2.BidRequest{Regs: &openrtb2.Regs{USPrivacy: "1", Ext: json.RawMessage(`{"us_privacy":"2"}`)}},
+			expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{USPrivacy: "1"}},
+		},
+		{
+			description:     "2.6 Left Alone",
+			givenRequest:    openrtb2.BidRequest{Regs: &openrtb2.Regs{USPrivacy: "1"}},
+			expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{USPrivacy: "1"}},
+		},
+	}
+
+	for _, test := range testCases {
+		w := &RequestWrapper{BidRequest: &test.givenRequest}
+		moveUSPrivacyFrom25To26(w)
+		assert.NoError(t, w.RebuildRequest(), test.description)
+		assert.Equal(t, test.expectedRequest, *w.BidRequest, test.description)
+	}
+}
+
+func TestMoveEIDFrom25To26(t *testing.T) {
+	var (
+		eid1     = []openrtb2.EID{{Source: "1"}}
+		eid1Json = json.RawMessage(`{"eids":[{"source":"1"}]}`)
+		eid2Json = json.RawMessage(`{"eids":[{"source":"2"}]}`)
+	)
+
+	testCases := []struct {
+		description     string
+		givenRequest    openrtb2.BidRequest
+		expectedRequest openrtb2.BidRequest
+	}{
+		{
+			description:     "Not Present",
+			givenRequest:    openrtb2.BidRequest{},
+			expectedRequest: openrtb2.BidRequest{},
+		},
+		{
+			description:     "2.5 Migrated To 2.6",
+			givenRequest:    openrtb2.BidRequest{User: &openrtb2.User{Ext: eid1Json}},
+			expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{EIDs: eid1}},
+		},
+		{
+			description:     "2.5 Dropped",
+			givenRequest:    openrtb2.BidRequest{User: &openrtb2.User{EIDs: eid1, Ext: eid2Json}},
+			expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{EIDs: eid1}},
+		},
+		{
+			description:     "2.6 Left Alone",
+			givenRequest:    openrtb2.BidRequest{User: &openrtb2.User{EIDs: eid1}},
+			expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{EIDs: eid1}},
+		},
+	}
+
+	for _, test := range testCases {
+		w := &RequestWrapper{BidRequest: &test.givenRequest}
+		moveEIDFrom25To26(w)
+		assert.NoError(t, w.RebuildRequest(), test.description)
+		assert.Equal(t, test.expectedRequest, *w.BidRequest, test.description)
+	}
+}