Repository: prebid/prebid-server
Pull request #1644 (2021-01-07 17:43:33)

Idsync removal
Removing id sync since we need a server setup on our side to provide all client.
Another update to support Ext for publisher that are using the puslisher.id field in site & app



Patch:
diff --git a/adapters/adot/adot.go b/adapters/adot/adot.go
new file mode 100644
index 00000000..c58c4a1e
--- /dev/null
+++ b/adapters/adot/adot.go
@@ -0,0 +1,117 @@
+package adot
+
+import (
+	"encoding/json"
+	"fmt"
+	"github.com/mxmCherry/openrtb"
+	"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"
+	"net/http"
+)
+
+type adapter struct {
+	endpoint string
+}
+
+type adotBidExt struct {
+	Adot bidExt `json:"adot"`
+}
+
+type bidExt struct {
+	MediaType string `json:"media_type"`
+}
+
+// Builder builds a new instance of the Adot adapter for the given bidder with the given config.
+func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) {
+	bidder := &adapter{
+		endpoint: config.Endpoint,
+	}
+	return bidder, nil
+}
+
+// MakeRequests makes the HTTP requests which should be made to fetch bids.
+func (a *adapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+	var reqJSON []byte
+	var err error
+
+	if reqJSON, err = json.Marshal(request); err != nil {
+		return nil, []error{fmt.Errorf("unable to marshal openrtb request (%v)", err)}
+	}
+
+	headers := http.Header{}
+	headers.Add("Content-Type", "application/json;charset=utf-8")
+
+	return []*adapters.RequestData{{
+		Method:  "POST",
+		Uri:     a.endpoint,
+		Body:    reqJSON,
+		Headers: headers,
+	}}, nil
+}
+
+// MakeBids unpacks the server's response into Bids.
+// The bidder return a status code 204 when it cannot delivery an ad.
+func (a *adapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+	if response.StatusCode == http.StatusNoContent {
+		return nil, nil
+	}
+
+	if response.StatusCode == http.StatusBadRequest {
+		return nil, []error{&errortypes.BadInput{
+			Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode),
+		}}
+	}
+
+	if response.StatusCode != http.StatusOK {
+		return nil, []error{&errortypes.BadServerResponse{
+			Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode),
+		}}
+	}
+
+	var bidResp openrtb.BidResponse
+	if err := json.Unmarshal(response.Body, &bidResp); err != nil {
+		return nil, []error{err}
+	}
+
+	bidsCapacity := 1
+	if len(bidResp.SeatBid) > 0 {
+		bidsCapacity = len(bidResp.SeatBid[0].Bid)
+	}
+	bidResponse := adapters.NewBidderResponseWithBidsCapacity(bidsCapacity)
+
+	for _, sb := range bidResp.SeatBid {
+		for i := range sb.Bid {
+			if bidType, err := getMediaTypeForBid(&sb.Bid[i]); err == nil {
+				bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
+					Bid:     &sb.Bid[i],
+					BidType: bidType,
+				})
+			}
+		}
+	}
+
+	return bidResponse, nil
+}
+
+// getMediaTypeForBid determines which type of bid.
+func getMediaTypeForBid(bid *openrtb.Bid) (openrtb_ext.BidType, error) {
+	if bid == nil {
+		return "", fmt.Errorf("the bid request object is nil")
+	}
+
+	var impExt adotBidExt
+	if err := json.Unmarshal(bid.Ext, &impExt); err == nil {
+		switch impExt.Adot.MediaType {
+		case string(openrtb_ext.BidTypeBanner):
+			return openrtb_ext.BidTypeBanner, nil
+		case string(openrtb_ext.BidTypeVideo):
+			return openrtb_ext.BidTypeVideo, nil
+		case string(openrtb_ext.BidTypeNative):
+			return openrtb_ext.BidTypeNative, nil
+		}
+	}
+
+	return "", fmt.Errorf("unrecognized bid type in response from adot")
+}
diff --git a/adapters/dmx/dmx.go b/adapters/dmx/dmx.go
index 24966b82..c66c6bf3 100644
--- a/adapters/dmx/dmx.go
+++ b/adapters/dmx/dmx.go
@@ -31,12 +31,21 @@ type dmxExt struct {
 	Bidder dmxParams `json:"bidder"`
 }
 
+type dmxPubExt struct {
+	Dmx dmxPubExtId `json:"dmx,omitempty"`
+}
+
+type dmxPubExtId struct {
+	Id string `json:"id,omitempty"`
+}
+
 type dmxParams struct {
-	TagId       string `json:"tagid,omitempty"`
-	DmxId       string `json:"dmxid,omitempty"`
-	MemberId    string `json:"memberid,omitempty"`
-	PublisherId string `json:"publisher_id,omitempty"`
-	SellerId    string `json:"seller_id,omitempty"`
+	TagId       string  `json:"tagid,omitempty"`
+	DmxId       string  `json:"dmxid,omitempty"`
+	MemberId    string  `json:"memberid,omitempty"`
+	PublisherId string  `json:"publisher_id,omitempty"`
+	SellerId    string  `json:"seller_id,omitempty"`
+	Bidfloor    float64 `json:"bidfloor,omitempty"`
 }
 
 func UserSellerOrPubId(str1, str2 string) string {
@@ -55,6 +64,7 @@ func (adapter *DmxAdapter) MakeRequests(request *openrtb.BidRequest, req *adapte
 	var anyHasId = false
 	var reqCopy openrtb.BidRequest = *request
 	var dmxReq *openrtb.BidRequest = &reqCopy
+	var dmxRawPubId dmxPubExt
 
 	if request.User == nil {
 		if request.App == nil {
@@ -77,6 +87,16 @@ func (adapter *DmxAdapter) MakeRequests(request *openrtb.BidRequest, req *adapte
 		appPublisherCopy := *request.App.Publisher
 		dmxReq.App = &appCopy
 		dmxReq.App.Publisher = &appPublisherCopy
+		if dmxReq.App.Publisher.ID == "" {
+			dmxReq.App.Publisher.ID = publisherId
+		}
+		dmxRawPubId.Dmx.Id = UserSellerOrPubId(rootExtInfo.Bidder.PublisherId, rootExtInfo.Bidder.MemberId)
+		ext, err := json.Marshal(dmxRawPubId)
+		if err != nil {
+			errs = append(errs, fmt.Errorf("unable to marshal ext, %v", err))
+			return nil, errs
+		}
+		dmxReq.App.Publisher.Ext = ext
 		if dmxReq.App.ID != "" {
 			anyHasId = true
 		}
@@ -90,7 +110,16 @@ func (adapter *DmxAdapter) MakeRequests(request *openrtb.BidRequest, req *adapte
 		dmxReq.Site = &siteCopy
 		dmxReq.Site.Publisher = &sitePublisherCopy
 		if dmxReq.Site.Publisher != nil {
-			dmxReq.Site.Publisher.ID = publisherId
+			if dmxReq.Site.Publisher.ID == "" {
+				dmxReq.Site.Publisher.ID = publisherId
+			}
+			dmxRawPubId.Dmx.Id = UserSellerOrPubId(rootExtInfo.Bidder.PublisherId, rootExtInfo.Bidder.MemberId)
+			ext, err := json.Marshal(dmxRawPubId)
+			if err != nil {
+				errs = append(errs, fmt.Errorf("unable to marshal ext, %v", err))
+				return nil, errs
+			}
+			dmxReq.Site.Publisher.Ext = ext
 		} else {
 			dmxReq.Site.Publisher = &openrtb.Publisher{ID: publisherId}
 		}
@@ -229,6 +258,9 @@ func (adapter *DmxAdapter) MakeBids(request *openrtb.BidRequest, externalRequest
 func fetchParams(params dmxExt, inst openrtb.Imp, ins openrtb.Imp, imps []openrtb.Imp, banner *openrtb.Banner, video *openrtb.Video, intVal int8) []openrtb.Imp {
 	var tempimp openrtb.Imp
 	tempimp = inst
+	if params.Bidder.Bidfloor != 0 {
+		tempimp.BidFloor = params.Bidder.Bidfloor
+	}
 	if params.Bidder.TagId != "" {
 		tempimp.TagID = params.Bidder.TagId
 		tempimp.Secure = &intVal
diff --git a/adapters/mediafuse/usersync.go b/adapters/mediafuse/usersync.go
new file mode 100644
index 00000000..5381bf36
--- /dev/null
+++ b/adapters/mediafuse/usersync.go
@@ -0,0 +1,12 @@
+package mediafuse
+
+import (
+	"text/template"
+
+	"github.com/prebid/prebid-server/adapters"
+	"github.com/prebid/prebid-server/usersync"
+)
+
+func NewMediafuseSyncer(temp *template.Template) usersync.Usersyncer {
+	return adapters.NewSyncer("mediafuse", 411, temp, adapters.SyncTypeRedirect)
+}
diff --git a/adapters/revcontent/revcontent.go b/adapters/revcontent/revcontent.go
new file mode 100644
index 00000000..5a34f3ef
--- /dev/null
+++ b/adapters/revcontent/revcontent.go
@@ -0,0 +1,105 @@
+package revcontent
+
+import (
+	"encoding/json"
+	"fmt"
+	"github.com/mxmCherry/openrtb"
+	"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"
+	"net/http"
+)
+
+type adapter struct {
+	endpoint string
+}
+
+// Builder builds a new instance of the Revcontent adapter for the given bidder with the given config.
+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 *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+	reqBody, err := json.Marshal(request)
+
+	if err != nil {
+		return nil, []error{err}
+	}
+
+	if err := checkRequest(request); err != nil {
+		return nil, []error{err}
+	}
+
+	headers := http.Header{}
+	headers.Add("Content-Type", "application/json;charset=utf-8")
+
+	req := &adapters.RequestData{
+		Method:  "POST",
+		Uri:     a.endpoint,
+		Body:    reqBody,
+		Headers: headers,
+	}
+	return []*adapters.RequestData{req}, nil
+}
+
+func checkRequest(request *openrtb.BidRequest) error {
+	if (request.App == nil || len(request.App.Name) == 0) && (request.Site == nil || len(request.Site.Domain) == 0) {
+		return &errortypes.BadInput{
+			Message: "Impression is missing app name or site domain, and must contain one.",
+		}
+	}
+
+	return nil
+}
+
+// MakeBids make the bids for the bid response.
+func (a *adapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+	if response.StatusCode == http.StatusNoContent {
+		return nil, nil
+	}
+
+	if response.StatusCode == http.StatusBadRequest {
+		return nil, []error{&errortypes.BadInput{
+			Message: fmt.Sprintf("unexpected status code: %d.", response.StatusCode),
+		}}
+	}
+
+	if response.StatusCode != http.StatusOK {
+		return nil, []error{&errortypes.BadServerResponse{
+			Message: fmt.Sprintf("unexpected status code: %d.", response.StatusCode),
+		}}
+	}
+
+	var bidResp openrtb.BidResponse
+
+	if err := json.Unmarshal(response.Body, &bidResp); err != nil {
+		return nil, []error{err}
+	}
+
+	bidResponse := adapters.NewBidderResponseWithBidsCapacity(5)
+
+	for _, sb := range bidResp.SeatBid {
+		for i := range sb.Bid {
+			var mediaType = getBidType(sb.Bid[i].AdM)
+			bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
+				Bid:     &sb.Bid[i],
+				BidType: mediaType,
+			})
+		}
+	}
+	return bidResponse, nil
+
+}
+
+func getBidType(bidAdm string) openrtb_ext.BidType {
+	// native: {"ver":"1.1","assets":...
+	// banner: <div id='rtb-widget...
+	if bidAdm != "" && bidAdm[:1] == "<" {
+		return openrtb_ext.BidTypeBanner
+	}
+	return openrtb_ext.BidTypeNative
+}
diff --git a/amp/parse.go b/amp/parse.go
new file mode 100644
index 00000000..9e0e019f
--- /dev/null
+++ b/amp/parse.go
@@ -0,0 +1,110 @@
+package amp
+
+import (
+	"errors"
+	"net/http"
+	"strconv"
+	"strings"
+
+	"github.com/mxmCherry/openrtb"
+)
+
+// Params defines the paramters of an AMP request.
+type Params struct {
+	Account         string
+	CanonicalURL    string
+	Consent         string
+	Debug           bool
+	Origin          string
+	Size            Size
+	Slot            string
+	StoredRequestID string
+	Timeout         *uint64
+}
+
+// Size defines size information of an AMP request.
+type Size struct {
+	Height         uint64
+	Multisize      []openrtb.Format
+	OverrideHeight uint64
+	OverrideWidth  uint64
+	Width          uint64
+}
+
+// ParseParams parses the AMP paramters from a HTTP request.
+func ParseParams(httpRequest *http.Request) (Params, error) {
+	query := httpRequest.URL.Query()
+
+	tagID := query.Get("tag_id")
+	if len(tagID) == 0 {
+		return Params{}, errors.New("AMP requests require an AMP tag_id")
+	}
+
+	params := Params{
+		Account:      query.Get("account"),
+		CanonicalURL: query.Get("curl"),
+		Consent:      chooseConsent(query.Get("consent_string"), query.Get("gdpr_consent")),
+		Debug:        query.Get("debug") == "1",
+		Origin:       query.Get("__amp_source_origin"),
+		Size: Size{
+			Height:         parseInt(query.Get("h")),
+			Multisize:      parseMultisize(query.Get("ms")),
+			OverrideHeight: parseInt(query.Get("oh")),
+			OverrideWidth:  parseInt(query.Get("ow")),
+			Width:          parseInt(query.Get("w")),
+		},
+		Slot:            query.Get("slot"),
+		StoredRequestID: tagID,
+		Timeout:         parseIntPtr(query.Get("timeout")),
+	}
+	return params, nil
+}
+
+func parseIntPtr(value string) *uint64 {
+	if parsed, err := strconv.ParseUint(value, 10, 64); err == nil {
+		return &parsed
+	}
+	return nil
+}
+
+func parseInt(value string) uint64 {
+	if parsed, err := strconv.ParseUint(value, 10, 64); err == nil {
+		return parsed
+	}
+	return 0
+}
+
+func parseMultisize(multisize string) []openrtb.Format {
+	if multisize == "" {
+		return nil
+	}
+
+	sizeStrings := strings.Split(multisize, ",")
+	sizes := make([]openrtb.Format, 0, len(sizeStrings))
+	for _, sizeString := range sizeStrings {
+		wh := strings.Split(sizeString, "x")
+		if len(wh) != 2 {
+			return nil
+		}
+		f := openrtb.Format{
+			W: parseInt(wh[0]),
+			H: parseInt(wh[1]),
+		}
+		if f.W == 0 && f.H == 0 {
+			return nil
+		}
+
+		sizes = append(sizes, f)
+	}
+	return sizes
+}
+
+func chooseConsent(consent, gdprConsent string) string {
+	if len(consent) > 0 {
+		return consent
+	}
+
+	// Fallback to 'gdpr_consent' for compatability until it's no longer used. This was our original
+	// implementation before the same AMP macro was reused for CCPA.
+	return gdprConsent
+}
diff --git a/config/config.go b/config/config.go
index f720d841..d1186688 100755
--- a/config/config.go
+++ b/config/config.go
@@ -587,7 +587,7 @@ func (cfg *Configuration) setDerivedDefaults() {
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderConversant, "https://prebid-match.dotomi.com/match/bounce/current?version=1&networkId=72582&rurl="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dconversant%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D")
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderCpmstar, "https://server.cpmstar.com/usersync.aspx?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dcpmstar%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID")
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderDatablocks, "https://sync.v5prebid.datablocks.net/s2ssync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Ddatablocks%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7Buid%7D")
-	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderDmx, "https://dmx.districtm.io/s/v1/img/s/10007?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Ddatablocks%26gdpr%3D%24%7Bgdpr%7D%26gdpr_consent%3D%24%7Bgdpr_consent%7D%26uid%3D%24%7Buid%7D")
+	// openrtb_ext.BidderDMX doesn't have a good default.
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderDeepintent, "https://match.deepintent.com/usersync/136?id=unk&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Ddeepintent%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5BUID%5D")
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderEmxDigital, "https://cs.emxdgt.com/um?ssp=pbs&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Demx_digital%26uid%3D%24UID")
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderEngageBDR, "https://match.bnmla.com/usersync/s2s_sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dengagebdr%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUUID%7D")
@@ -606,6 +606,7 @@ func (cfg *Configuration) setDerivedDefaults() {
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderLogicad, "https://cr-p31.ladsp.jp/cookiesender/31?r=true&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ru="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dlogicad%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID")
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderLunaMedia, "https://api.lunamedia.io/xp/user-sync?redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dlunamedia%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID")
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderMarsmedia, "https://dmp.rtbsrv.com/dmp/profiles/cm?p_id=179&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dmarsmedia%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUUID%7D")
+	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderMediafuse, "https://sync.hbmp.mediafuse.com/csync?t=p&ep=0&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dmediafuse%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7Buid%7D")
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderMgid, "https://cm.mgid.com/m?cdsp=363893&adu="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dmgid%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7Bmuidn%7D")
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderNanoInteractive, "https://ad.audiencemanager.de/hbs/cookie_sync?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirectUri="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dnanointeractive%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID")
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderNinthDecimal, "https://rtb.ninthdecimal.com/xp/user-sync?acctid={aid}&&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dninthdecimal%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID")
@@ -799,6 +800,7 @@ func SetupViper(v *viper.Viper, filename string) {
 	v.SetDefault("adapters.admixer.endpoint", "http://inv-nets.admixer.net/pbs.aspx")
 	v.SetDefault("adapters.adocean.endpoint", "https://{{.Host}}")
 	v.SetDefault("adapters.adoppler.endpoint", "http://{{.AccountID}}.trustedmarketplace.io/ads/processHeaderBid/{{.AdUnit}}")
+	v.SetDefault("adapters.adot.endpoint", "https://dsp.adotmob.com/headerbidding/bidrequest")
 	v.SetDefault("adapters.adpone.endpoint", "http://rtb.adpone.com/bid-request?src=prebid_server")
 	v.SetDefault("adapters.adprime.endpoint", "http://delta.adprime.com/?c=o&m=ortb")
 	v.SetDefault("adapters.adtarget.endpoint", "http://ghb.console.adtarget.com.tr/pbs/ortb")
@@ -844,6 +846,7 @@ func SetupViper(v *viper.Viper, filename string) {
 	v.SetDefault("adapters.logicad.endpoint", "https://pbs.ladsp.com/adrequest/prebidserver")
 	v.SetDefault("adapters.lunamedia.endpoint", "http://api.lunamedia.io/xp/get?pubid={{.PublisherID}}")
 	v.SetDefault("adapters.marsmedia.endpoint", "https://bid306.rtbsrv.com/bidder/?bid=f3xtet")
+	v.SetDefault("adapters.mediafuse.endpoint", "http://ghb.hbmp.mediafuse.com/pbs/ortb")
 	v.SetDefault("adapters.mgid.endpoint", "https://prebid.mgid.com/prebid/")
 	v.SetDefault("adapters.mobilefuse.endpoint", "http://mfx.mobilefuse.com/openrtb?pub_id={{.PublisherID}}")
 	v.SetDefault("adapters.mobfoxpb.endpoint", "http://bes.mobfox.com/?c=o&m=ortb")
@@ -855,6 +858,8 @@ func SetupViper(v *viper.Viper, filename string) {
 	v.SetDefault("adapters.pubmatic.endpoint", "https://hbopenbid.pubmatic.com/translator?source=prebid-server")
 	v.SetDefault("adapters.pubnative.endpoint", "http://dsp.pubnative.net/bid/v1/request")
 	v.SetDefault("adapters.pulsepoint.endpoint", "http://bid.contextweb.com/header/s/ortb/prebid-s2s")
+	v.SetDefault("adapters.revcontent.disabled", true)
+	v.SetDefault("adapters.revcontent.endpoint", "https://trends.revcontent.com/rtb")
 	v.SetDefault("adapters.rhythmone.endpoint", "http://tag.1rx.io/rmp")
 	v.SetDefault("adapters.rtbhouse.endpoint", "http://prebidserver-s2s-ams.creativecdn.com/bidder/prebidserver/bids")
 	v.SetDefault("adapters.rubicon.disabled", true)
diff --git a/endpoints/openrtb2/amp_auction.go b/endpoints/openrtb2/amp_auction.go
index afc32e5e..dc66163a 100644
--- a/endpoints/openrtb2/amp_auction.go
+++ b/endpoints/openrtb2/amp_auction.go
@@ -8,7 +8,6 @@ import (
 	"fmt"
 	"net/http"
 	"net/url"
-	"strconv"
 	"strings"
 	"time"
 
@@ -17,6 +16,7 @@ import (
 	"github.com/julienschmidt/httprouter"
 	"github.com/mxmCherry/openrtb"
 	accountService "github.com/prebid/prebid-server/account"
+	"github.com/prebid/prebid-server/amp"
 	"github.com/prebid/prebid-server/analytics"
 	"github.com/prebid/prebid-server/config"
 	"github.com/prebid/prebid-server/errortypes"
@@ -314,43 +314,41 @@ func (deps *endpointDeps) loadRequestJSONForAmp(httpRequest *http.Request) (req
 	req = &openrtb.BidRequest{}
 	errs = nil
 
-	ampID := httpRequest.FormValue("tag_id")
-	if ampID == "" {
-		errs = []error{errors.New("AMP requests require an AMP tag_id")}
-		return
+	ampParams, err := amp.ParseParams(httpRequest)
+	if err != nil {
+		return nil, []error{err}
 	}
 
 	ctx, cancel := context.WithTimeout(context.Background(), time.Duration(storedRequestTimeoutMillis)*time.Millisecond)
 	defer cancel()
 
-	storedRequests, _, errs := deps.storedReqFetcher.FetchRequests(ctx, []string{ampID}, nil)
+	storedRequests, _, errs := deps.storedReqFetcher.FetchRequests(ctx, []string{ampParams.StoredRequestID}, nil)
 	if len(errs) > 0 {
 		return nil, errs
 	}
 	if len(storedRequests) == 0 {
-		errs = []error{fmt.Errorf("No AMP config found for tag_id '%s'", ampID)}
+		errs = []error{fmt.Errorf("No AMP config found for tag_id '%s'", ampParams.StoredRequestID)}
 		return
 	}
 
 	// The fetched config becomes the entire OpenRTB request
-	requestJSON := storedRequests[ampID]
+	requestJSON := storedRequests[ampParams.StoredRequestID]
 	if err := json.Unmarshal(requestJSON, req); err != nil {
 		errs = []error{err}
 		return
 	}
 
-	debugParam := httpRequest.FormValue("debug")
-	if debugParam == "1" {
+	if ampParams.Debug {
 		req.Test = 1
 	}
 
 	// Two checks so users know which way the Imp check failed.
 	if len(req.Imp) == 0 {
-		errs = []error{fmt.Errorf("data for tag_id='%s' does not define the required imp array", ampID)}
+		errs = []error{fmt.Errorf("data for tag_id='%s' does not define the required imp array", ampParams.StoredRequestID)}
 		return
 	}
 	if len(req.Imp) > 1 {
-		errs = []error{fmt.Errorf("data for tag_id '%s' includes %d imp elements. Only one is allowed", ampID, len(req.Imp))}
+		errs = []error{fmt.Errorf("data for tag_id '%s' includes %d imp elements. Only one is allowed", ampParams.StoredRequestID, len(req.Imp))}
 		return
 	}
 
@@ -367,35 +365,30 @@ func (deps *endpointDeps) loadRequestJSONForAmp(httpRequest *http.Request) (req
 		*req.Imp[0].Secure = 1
 	}
 
-	errs = deps.overrideWithParams(httpRequest, req)
+	errs = deps.overrideWithParams(ampParams, req)
 	return
 }
 
-func (deps *endpointDeps) overrideWithParams(httpRequest *http.Request, req *openrtb.BidRequest) []error {
+func (deps *endpointDeps) overrideWithParams(ampParams amp.Params, req *openrtb.BidRequest) []error {
 	if req.Site == nil {
 		req.Site = &openrtb.Site{}
 	}
 
 	// Override the stored request sizes with AMP ones, if they exist.
 	if req.Imp[0].Banner != nil {
-		width := parseFormInt(httpRequest, "w", 0)
-		height := parseFormInt(httpRequest, "h", 0)
-		overrideWidth := parseFormInt(httpRequest, "ow", 0)
-		overrideHeight := parseFormInt(httpRequest, "oh", 0)
-		if format := makeFormatReplacement(overrideWidth, overrideHeight, width, height, httpRequest.FormValue("ms")); len(format) != 0 {
+		if format := makeFormatReplacement(ampParams.Size); len(format) != 0 {
 			req.Imp[0].Banner.Format = format
-		} else if width != 0 {
-			setWidths(req.Imp[0].Banner.Format, width)
-		} else if height != 0 {
-			setHeights(req.Imp[0].Banner.Format, height)
+		} else if ampParams.Size.Width != 0 {
+			setWidths(req.Imp[0].Banner.Format, ampParams.Size.Width)
+		} else if ampParams.Size.Height != 0 {
+			setHeights(req.Imp[0].Banner.Format, ampParams.Size.Height)
 		}
 	}
 
-	canonicalURL := httpRequest.FormValue("curl")
-	if canonicalURL != "" {
-		req.Site.Page = canonicalURL
+	if ampParams.CanonicalURL != "" {
+		req.Site.Page = ampParams.CanonicalURL
 		// Fixes #683
-		if parsedURL, err := url.Parse(canonicalURL); err == nil {
+		if parsedURL, err := url.Parse(ampParams.CanonicalURL); err == nil {
 			domain := parsedURL.Host
 			if colonIndex := strings.LastIndex(domain, ":"); colonIndex != -1 {
 				domain = domain[:colonIndex]
@@ -406,14 +399,13 @@ func (deps *endpointDeps) overrideWithParams(httpRequest *http.Request, req *ope
 
 	setAmpExt(req.Site, "1")
 
-	setEffectiveAmpPubID(req, httpRequest.URL.Query())
+	setEffectiveAmpPubID(req, ampParams.Account)
 
-	slot := httpRequest.FormValue("slot")
-	if slot != "" {
-		req.Imp[0].TagID = slot
+	if ampParams.Slot != "" {
+		req.Imp[0].TagID = ampParams.Slot
 	}
 
-	policyWriter, policyWriterErr := readPolicyFromUrl(httpRequest.URL)
+	policyWriter, policyWriterErr := readPolicy(ampParams.Consent)
 	if policyWriterErr != nil {
 		return []error{policyWriterErr}
 	}
@@ -421,42 +413,38 @@ func (deps *endpointDeps) overrideWithParams(httpRequest *http.Request, req *ope
 		return []error{err}
 	}
 
-	if timeout, err := strconv.ParseInt(httpRequest.FormValue("timeout"), 10, 64); err == nil {
-		req.TMax = timeout - deps.cfg.AMPTimeoutAdjustment
+	if ampParams.Timeout != nil {
+		req.TMax = int64(*ampParams.Timeout) - deps.cfg.AMPTimeoutAdjustment
 	}
 
 	return nil
 }
 
-func makeFormatReplacement(overrideWidth uint64, overrideHeight uint64, width uint64, height uint64, multisize string) []openrtb.Format {
+func makeFormatReplacement(size amp.Size) []openrtb.Format {
 	var formats []openrtb.Format
-	if overrideWidth != 0 && overrideHeight != 0 {
+	if size.OverrideWidth != 0 && size.OverrideHeight != 0 {
 		formats = []openrtb.Format{{
-			W: overrideWidth,
-			H: overrideHeight,
+			W: size.OverrideWidth,
+			H: size.OverrideHeight,
 		}}
-	} else if overrideWidth != 0 && height != 0 {
+	} else if size.OverrideWidth != 0 && size.Height != 0 {
 		formats = []openrtb.Format{{
-			W: overrideWidth,
-			H: height,
+			W: size.OverrideWidth,
+			H: size.Height,
 		}}
-	} else if width != 0 && overrideHeight != 0 {
+	} else if size.Width != 0 && size.OverrideHeight != 0 {
 		formats = []openrtb.Format{{
-			W: width,
-			H: overrideHeight,
+			W: size.Width,
+			H: size.OverrideHeight,
 		}}
-	} else if width != 0 && height != 0 {
+	} else if size.Width != 0 && size.Height != 0 {
 		formats = []openrtb.Format{{
-			W: width,
-			H: height,
+			W: size.Width,
+			H: size.Height,
 		}}
 	}
 
-	if parsedSizes := parseMultisize(multisize); len(parsedSizes) != 0 {
-		formats = append(formats, parsedSizes...)
-	}
-
-	return formats
+	return append(formats, size.Multisize...)
 }
 
 func setWidths(formats []openrtb.Format, width uint64) {
@@ -471,42 +459,6 @@ func setHeights(formats []openrtb.Format, height uint64) {
 	}
 }
 
-func parseMultisize(multisize string) []openrtb.Format {
-	if multisize == "" {
-		return nil
-	}
-
-	sizeStrings := strings.Split(multisize, ",")
-	sizes := make([]openrtb.Format, 0, len(sizeStrings))
-	for _, sizeString := range sizeStrings {
-		wh := strings.Split(sizeString, "x")
-		if len(wh) != 2 {
-			return nil
-		}
-		f := openrtb.Format{
-			W: parseIntErrorless(wh[0], 0),
-			H: parseIntErrorless(wh[1], 0),
-		}
-		if f.W == 0 && f.H == 0 {
-			return nil
-		}
-
-		sizes = append(sizes, f)
-	}
-	return sizes
-}
-
-func parseFormInt(req *http.Request, value string, defaultTo uint64) uint64 {
-	return parseIntErrorless(req.FormValue(value), defaultTo)
-}
-
-func parseIntErrorless(value string, defaultTo uint64) uint64 {
-	if parsed, err := strconv.ParseUint(value, 10, 64); err == nil {
-		return parsed
-	}
-	return defaultTo
-}
-
 // AMP won't function unless ext.prebid.targeting and ext.prebid.cache.bids are defined.
 // If the user didn't include them, default those here.
 func defaultRequestExt(req *openrtb.BidRequest) (errs []error) {
@@ -563,9 +515,7 @@ func setAmpExt(site *openrtb.Site, value string) {
 	}
 }
 
-func readPolicyFromUrl(url *url.URL) (privacy.PolicyWriter, error) {
-	consent := readConsentFromURL(url)
-
+func readPolicy(consent string) (privacy.PolicyWriter, error) {
 	if len(consent) == 0 {
 		return privacy.NilPolicyWriter{}, nil
 	}
@@ -583,17 +533,8 @@ func readPolicyFromUrl(url *url.URL) (privacy.PolicyWriter, error) {
 	}
 }
 
-func readConsentFromURL(url *url.URL) string {
-	if v := url.Query().Get("consent_string"); v != "" {
-		return v
-	}
-
-	// Fallback to 'gdpr_consent' for compatability until it's no longer used by AMP.
-	return url.Query().Get("gdpr_consent")
-}
-
 // Sets the effective publisher ID for amp request
-func setEffectiveAmpPubID(req *openrtb.BidRequest, urlQueryParams url.Values) {
+func setEffectiveAmpPubID(req *openrtb.BidRequest, account string) {
 	var pub *openrtb.Publisher
 	if req.App != nil {
 		if req.App.Publisher == nil {
@@ -608,10 +549,9 @@ func setEffectiveAmpPubID(req *openrtb.BidRequest, urlQueryParams url.Values) {
 	}
 
 	if pub.ID == "" {
-		// For amp requests, the publisher ID could be sent via the account
-		// query string
-		if acc := urlQueryParams.Get("account"); acc != "" && acc != "ACCOUNT_ID" {
-			pub.ID = acc
+		// ACCOUNT_ID is the unresolved macro name and should be ignored.
+		if account != "" && account != "ACCOUNT_ID" {
+			pub.ID = account
 		}
 	}
 }
diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go
index 21fa2c8a..04e0c2cf 100644
--- a/endpoints/openrtb2/auction.go
+++ b/endpoints/openrtb2/auction.go
@@ -331,6 +331,10 @@ func (deps *endpointDeps) validateRequest(req *openrtb.BidRequest) []error {
 		if err := validateSChains(bidExt); err != nil {
 			return []error{err}
 		}
+
+		if err := deps.validateEidPermissions(bidExt, aliases); err != nil {
+			return []error{err}
+		}
 	}
 
 	if (req.Site == nil && req.App == nil) || (req.Site != nil && req.App != nil) {
@@ -419,6 +423,51 @@ func validateSChains(req *openrtb_ext.ExtRequest) error {
 	return err
 }
 
+func (deps *endpointDeps) validateEidPermissions(req *openrtb_ext.ExtRequest, aliases map[string]string) error {
+	if req == nil || req.Prebid.Data == nil {
+		return nil
+	}
+
+	uniqueSources := make(map[string]struct{}, len(req.Prebid.Data.EidPermissions))
+	for i, eid := range req.Prebid.Data.EidPermissions {
+		if len(eid.Source) == 0 {
+			return fmt.Errorf(`request.ext.prebid.data.eidpermissions[%d] missing required field: "source"`, i)
+		}
+
+		if _, exists := uniqueSources[eid.Source]; exists {
+			return fmt.Errorf(`request.ext.prebid.data.eidpermissions[%d] duplicate entry with field: "source"`, i)
+		}
+		uniqueSources[eid.Source] = struct{}{}
+
+		if len(eid.Bidders) == 0 {
+			return fmt.Errorf(`request.ext.prebid.data.eidpermissions[%d] missing or empty required field: "bidders"`, i)
+		}
+
+		if err := validateBidders(eid.Bidders, deps.bidderMap, aliases); err != nil {
+			return fmt.Errorf(`request.ext.prebid.data.eidpermissions[%d] contains %v`, i, err)
+		}
+	}
+
+	return nil
+}
+
+func validateBidders(bidders []string, knownBidders map[string]openrtb_ext.BidderName, knownAliases map[string]string) error {
+	for _, bidder := range bidders {
+		if bidder == "*" {
+			if len(bidders) > 1 {
+				return errors.New(`bidder wildcard "*" mixed with specific bidders`)
+			}
+		} else {
+			_, isCoreBidder := knownBidders[bidder]
+			_, isAlias := knownAliases[bidder]
+			if !isCoreBidder && !isAlias {
+				return fmt.Errorf(`unrecognized bidder "%v"`, bidder)
+			}
+		}
+	}
+	return nil
+}
+
 func (deps *endpointDeps) validateImp(imp *openrtb.Imp, aliases map[string]string, index int) []error {
 	if imp.ID == "" {
 		return []error{fmt.Errorf("request.imp[%d] missing required field: \"id\"", index)}
diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go
index 9095ec1e..31342a4c 100755
--- a/exchange/adapter_builders.go
+++ b/exchange/adapter_builders.go
@@ -13,6 +13,7 @@ import (
 	"github.com/prebid/prebid-server/adapters/admixer"
 	"github.com/prebid/prebid-server/adapters/adocean"
 	"github.com/prebid/prebid-server/adapters/adoppler"
+	"github.com/prebid/prebid-server/adapters/adot"
 	"github.com/prebid/prebid-server/adapters/adpone"
 	"github.com/prebid/prebid-server/adapters/adprime"
 	"github.com/prebid/prebid-server/adapters/adtarget"
@@ -64,6 +65,7 @@ import (
 	"github.com/prebid/prebid-server/adapters/orbidder"
 	"github.com/prebid/prebid-server/adapters/pubmatic"
 	"github.com/prebid/prebid-server/adapters/pubnative"
+	"github.com/prebid/prebid-server/adapters/revcontent"
 	"github.com/prebid/prebid-server/adapters/rhythmone"
 	"github.com/prebid/prebid-server/adapters/rtbhouse"
 	"github.com/prebid/prebid-server/adapters/rubicon"
@@ -112,6 +114,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder {
 		openrtb_ext.BidderAdOcean:          adocean.Builder,
 		openrtb_ext.BidderAdoppler:         adoppler.Builder,
 		openrtb_ext.BidderAdpone:           adpone.Builder,
+		openrtb_ext.BidderAdot:             adot.Builder,
 		openrtb_ext.BidderAdprime:          adprime.Builder,
 		openrtb_ext.BidderAdtarget:         adtarget.Builder,
 		openrtb_ext.BidderAdtelligent:      adtelligent.Builder,
@@ -152,6 +155,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder {
 		openrtb_ext.BidderLogicad:          logicad.Builder,
 		openrtb_ext.BidderLunaMedia:        lunamedia.Builder,
 		openrtb_ext.BidderMarsmedia:        marsmedia.Builder,
+		openrtb_ext.BidderMediafuse:        adtelligent.Builder,
 		openrtb_ext.BidderMgid:             mgid.Builder,
 		openrtb_ext.BidderMobfoxpb:         mobfoxpb.Builder,
 		openrtb_ext.BidderMobileFuse:       mobilefuse.Builder,
@@ -162,6 +166,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder {
 		openrtb_ext.BidderOrbidder:         orbidder.Builder,
 		openrtb_ext.BidderPubmatic:         pubmatic.Builder,
 		openrtb_ext.BidderPubnative:        pubnative.Builder,
+		openrtb_ext.BidderRevcontent:       revcontent.Builder,
 		openrtb_ext.BidderRhythmone:        rhythmone.Builder,
 		openrtb_ext.BidderRTBHouse:         rtbhouse.Builder,
 		openrtb_ext.BidderRubicon:          rubicon.Builder,
diff --git a/exchange/utils.go b/exchange/utils.go
index 21c31a29..af4dfb05 100644
--- a/exchange/utils.go
+++ b/exchange/utils.go
@@ -118,11 +118,13 @@ func cleanOpenRTBRequests(ctx context.Context,
 		if gdprEnforced {
 			var publisherID = req.LegacyLabels.PubID
 			_, geo, id, err := gDPR.PersonalInfoAllowed(ctx, bidderRequest.BidderCoreName, publisherID, gdprSignal, consent)
-			privacyEnforcement.GDPRGeo = !geo && err == nil
-			privacyEnforcement.GDPRID = !id && err == nil
-		} else {
-			privacyEnforcement.GDPRGeo = false
-			privacyEnforcement.GDPRID = false
+			if err == nil {
+				privacyEnforcement.GDPRGeo = !geo
+				privacyEnforcement.GDPRID = !id
+			} else {
+				privacyEnforcement.GDPRGeo = true
+				privacyEnforcement.GDPRID = true
+			}
 		}
 
 		privacyEnforcement.Apply(bidderRequest.BidRequest)
@@ -195,15 +197,22 @@ func getAuctionBidderRequests(req AuctionRequest,
 		return nil, []error{err}
 	}
 
+	var errs []error
 	for bidder, imps := range impsByBidder {
 		coreBidder := resolveBidder(bidder, aliases)
 
 		reqCopy := *req.BidRequest
 		reqCopy.Imp = imps
 		reqCopy.Ext = reqExt
+
 		prepareSource(&reqCopy, bidder, sChainsByBidder)
 
-		bidder := BidderRequest{
+		if err := removeUnpermissionedEids(&reqCopy, bidder, requestExt); err != nil {
+			errs = append(errs, fmt.Errorf("unable to enforce request.ext.prebid.data.eidpermissions because %v", err))
+			continue
+		}
+
+		bidderRequest := BidderRequest{
 			BidderName:     openrtb_ext.BidderName(bidder),
 			BidderCoreName: coreBidder,
 			BidRequest:     &reqCopy,
@@ -216,15 +225,16 @@ func getAuctionBidderRequests(req AuctionRequest,
 				AdapterBids: metrics.AdapterBidPresent,
 			},
 		}
-		if hadSync := prepareUser(&reqCopy, bidder.BidderName.String(), coreBidder, explicitBuyerUIDs, req.UserSyncs); !hadSync && req.BidRequest.App == nil {
-			bidder.BidderLabels.CookieFlag = metrics.CookieFlagNo
+
+		if hadSync := prepareUser(&reqCopy, bidder, coreBidder, explicitBuyerUIDs, req.UserSyncs); !hadSync && req.BidRequest.App == nil {
+			bidderRequest.BidderLabels.CookieFlag = metrics.CookieFlagNo
 		} else {
-			bidder.BidderLabels.CookieFlag = metrics.CookieFlagYes
+			bidderRequest.BidderLabels.CookieFlag = metrics.CookieFlagYes
 		}
 
-		bidderRequests = append(bidderRequests, bidder)
+		bidderRequests = append(bidderRequests, bidderRequest)
 	}
-	return bidderRequests, nil
+	return bidderRequests, errs
 }
 
 func getExtJson(req *openrtb.BidRequest, unpackedExt *openrtb_ext.ExtRequest) (json.RawMessage, error) {
@@ -290,7 +300,9 @@ func extractBuyerUIDs(user *openrtb.User) (map[string]string, error) {
 	// as long as user.ext.prebid exists.
 	buyerUIDs := userExt.Prebid.BuyerUIDs
 	userExt.Prebid = nil
-	if userExt.Consent != "" || userExt.DigiTrust != nil {
+
+	// Remarshal (instead of removing) if the ext has other known fields
+	if userExt.Consent != "" || userExt.DigiTrust != nil || len(userExt.Eids) > 0 {
 		if newUserExtBytes, err := json.Marshal(userExt); err != nil {
 			return nil, err
 		} else {
@@ -445,6 +457,100 @@ func copyWithBuyerUID(user *openrtb.User, buyerUID string) *openrtb.User {
 	return user
 }
 
+// removeUnpermissionedEids modifies the request to remove any request.user.ext.eids not permissions for the specific bidder
+func removeUnpermissionedEids(request *openrtb.BidRequest, bidder string, requestExt *openrtb_ext.ExtRequest) error {
+	// ensure request might have eids (as much as we can check before unmarshalling)
+	if request.User == nil || len(request.User.Ext) == 0 {
+		return nil
+	}
+
+	// ensure request has eid permissions to enforce
+	if requestExt == nil || requestExt.Prebid.Data == nil || len(requestExt.Prebid.Data.EidPermissions) == 0 {
+		return nil
+	}
+
+	// low level unmarshal to preserve other request.user.ext values. prebid server is non-destructive.
+	var userExt map[string]json.RawMessage
+	if err := json.Unmarshal(request.User.Ext, &userExt); err != nil {
+		return err
+	}
+
+	eidsJSON, eidsSpecified := userExt["eids"]
+	if !eidsSpecified {
+		return nil
+	}
+
+	var eids []openrtb_ext.ExtUserEid
+	if err := json.Unmarshal(eidsJSON, &eids); err != nil {
+		return err
+	}
+
+	// exit early if there are no eids (empty array)
+	if len(eids) == 0 {
+		return nil
+	}
+
+	// translate eid permissions to a map for quick lookup
+	eidRules := make(map[string][]string)
+	for _, p := range requestExt.Prebid.Data.EidPermissions {
+		eidRules[p.Source] = p.Bidders
+	}
+
+	eidsAllowed := make([]openrtb_ext.ExtUserEid, 0, len(eids))
+	for _, eid := range eids {
+		allowed := false
+		if rule, hasRule := eidRules[eid.Source]; hasRule {
+			for _, ruleBidder := range rule {
+				if ruleBidder == "*" || ruleBidder == bidder {
+					allowed = true
+					break
+				}
+			}
+		} else {
+			allowed = true
+		}
+
+		if allowed {
+			eidsAllowed = append(eidsAllowed, eid)
+		}
+	}
+
+	// exit early if all eids are allowed and nothing needs to be removed
+	if len(eids) == len(eidsAllowed) {
+		return nil
+	}
+
+	// marshal eidsAllowed back to userExt
+	if len(eidsAllowed) == 0 {
+		delete(userExt, "eids")
+	} else {
+		eidsRaw, err := json.Marshal(eidsAllowed)
+		if err != nil {
+			return err
+		}
+		userExt["eids"] = eidsRaw
+	}
+
+	// exit early if userExt is empty
+	if len(userExt) == 0 {
+		setUserExtWithCopy(request, nil)
+		return nil
+	}
+
+	userExtJSON, err := json.Marshal(userExt)
+	if err != nil {
+		return err
+	}
+	setUserExtWithCopy(request, userExtJSON)
+	return nil
+}
+
+func setUserExtWithCopy(request *openrtb.BidRequest, userExtJSON json.RawMessage) {
+	userCopy := *request.User
+	userCopy.Ext = userExtJSON
+	request.User = &userCopy
+}
+
 // resolveBidder returns the known BidderName associated with bidder, if bidder is an alias. If it's not an alias, the bidder is returned.
 func resolveBidder(bidder string, aliases map[string]string) openrtb_ext.BidderName {
 	if coreBidder, ok := aliases[bidder]; ok {
diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go
index f7f83125..c0839632 100755
--- a/openrtb_ext/bidders.go
+++ b/openrtb_ext/bidders.go
@@ -51,6 +51,7 @@ const (
 	BidderAdmixer          BidderName = "admixer"
 	BidderAdOcean          BidderName = "adocean"
 	BidderAdoppler         BidderName = "adoppler"
+	BidderAdot             BidderName = "adot"
 	BidderAdpone           BidderName = "adpone"
 	BidderAdprime          BidderName = "adprime"
 	BidderAdtarget         BidderName = "adtarget"
@@ -93,6 +94,7 @@ const (
 	BidderLogicad          BidderName = "logicad"
 	BidderLunaMedia        BidderName = "lunamedia"
 	BidderMarsmedia        BidderName = "marsmedia"
+	BidderMediafuse        BidderName = "mediafuse"
 	BidderMgid             BidderName = "mgid"
 	BidderMobfoxpb         BidderName = "mobfoxpb"
 	BidderMobileFuse       BidderName = "mobilefuse"
@@ -104,6 +106,7 @@ const (
 	BidderPubmatic         BidderName = "pubmatic"
 	BidderPubnative        BidderName = "pubnative"
 	BidderPulsepoint       BidderName = "pulsepoint"
+	BidderRevcontent       BidderName = "revcontent"
 	BidderRhythmone        BidderName = "rhythmone"
 	BidderRTBHouse         BidderName = "rtbhouse"
 	BidderRubicon          BidderName = "rubicon"
@@ -148,6 +151,7 @@ func CoreBidderNames() []BidderName {
 		BidderAdmixer,
 		BidderAdOcean,
 		BidderAdoppler,
+		BidderAdot,
 		BidderAdpone,
 		BidderAdprime,
 		BidderAdtarget,
@@ -190,6 +194,7 @@ func CoreBidderNames() []BidderName {
 		BidderLogicad,
 		BidderLunaMedia,
 		BidderMarsmedia,
+		BidderMediafuse,
 		BidderMgid,
 		BidderMobfoxpb,
 		BidderMobileFuse,
@@ -201,6 +206,7 @@ func CoreBidderNames() []BidderName {
 		BidderPubmatic,
 		BidderPubnative,
 		BidderPulsepoint,
+		BidderRevcontent,
 		BidderRhythmone,
 		BidderRTBHouse,
 		BidderRubicon,
diff --git a/openrtb_ext/imp_adot.go b/openrtb_ext/imp_adot.go
new file mode 100644
index 00000000..5d741589
--- /dev/null
+++ b/openrtb_ext/imp_adot.go
@@ -0,0 +1,6 @@
+package openrtb_ext
+
+type ExtImpAdot struct {
+	Parallax    bool   `json:"parallax,omitempty"`
+	PlacementId string `json:"placementId,omitempty"`
+}
diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go
index 1199edc7..79dc3554 100644
--- a/openrtb_ext/request.go
+++ b/openrtb_ext/request.go
@@ -20,11 +20,12 @@ type ExtRequestPrebid struct {
 	Aliases              map[string]string         `json:"aliases,omitempty"`
 	BidAdjustmentFactors map[string]float64        `json:"bidadjustmentfactors,omitempty"`
 	Cache                *ExtRequestPrebidCache    `json:"cache,omitempty"`
+	Data                 *ExtRequestPrebidData     `json:"data,omitempty"`
+	Debug                bool                      `json:"debug,omitempty"`
 	SChains              []*ExtRequestPrebidSChain `json:"schains,omitempty"`
 	StoredRequest        *ExtStoredRequest         `json:"storedrequest,omitempty"`
-	Targeting            *ExtRequestTargeting      `json:"targeting,omitempty"`
 	SupportDeals         bool                      `json:"supportdeals,omitempty"`
-	Debug                bool                      `json:"debug,omitempty"`
+	Targeting            *ExtRequestTargeting      `json:"targeting,omitempty"`
 
 	// NoSale specifies bidders with whom the publisher has a legal relationship where the
 	// passing of personally identifiable information doesn't constitute a sale per CCPA law.
@@ -289,3 +290,14 @@ var priceGranularityAuto = PriceGranularity{
 		},
 	},
 }
+
+// ExtRequestPrebidData defines Prebid's First Party Data (FPD) and related bid request options.
+type ExtRequestPrebidData struct {
+	EidPermissions []ExtRequestPrebidDataEidPermission `json:"eidpermissions"`
+}
+
+// ExtRequestPrebidDataEidPermission defines a filter rule for filter user.ext.eids
+type ExtRequestPrebidDataEidPermission struct {
+	Source  string   `json:"source"`
+	Bidders []string `json:"bidders"`
+}
diff --git a/static/bidder-info/adot.yaml b/static/bidder-info/adot.yaml
new file mode 100644
index 00000000..529ff3ae
--- /dev/null
+++ b/static/bidder-info/adot.yaml
@@ -0,0 +1,14 @@
+maintainer:
+  email: "admin@we-are-adot.com"
+modifyingVastXmlAllowed: false
+capabilities:
+  app:
+    mediaTypes:
+      - banner
+      - video
+      - native
+  site:
+    mediaTypes:
+      - banner
+      - video
+      - native
diff --git a/static/bidder-info/dmx.yaml b/static/bidder-info/dmx.yaml
index d6e54178..3ecb452f 100644
--- a/static/bidder-info/dmx.yaml
+++ b/static/bidder-info/dmx.yaml
@@ -1,5 +1,5 @@
 maintainer:
-  email: "steve@districtm.net"
+  email: "prebid@districtm.net"
 capabilities:
   site:
     mediaTypes:
diff --git a/static/bidder-info/mediafuse.yaml b/static/bidder-info/mediafuse.yaml
new file mode 100644
index 00000000..112f67fe
--- /dev/null
+++ b/static/bidder-info/mediafuse.yaml
@@ -0,0 +1,11 @@
+maintainer:
+  email: "support@mediafuse.com"
+capabilities:
+  app:
+    mediaTypes:
+      - banner
+      - video
+  site:
+    mediaTypes:
+      - banner
+      - video
diff --git a/static/bidder-info/revcontent.yaml b/static/bidder-info/revcontent.yaml
new file mode 100644
index 00000000..c0216dfc
--- /dev/null
+++ b/static/bidder-info/revcontent.yaml
@@ -0,0 +1,11 @@
+maintainer:
+  email: "developers@revcontent.com"
+capabilities:
+  app:
+    mediaTypes:
+      - banner
+      - native
+  site:
+    mediaTypes:
+      - banner
+      - native
\ No newline at end of file
diff --git a/static/bidder-params/adot.json b/static/bidder-params/adot.json
new file mode 100644
index 00000000..fa2b8533
--- /dev/null
+++ b/static/bidder-params/adot.json
@@ -0,0 +1,17 @@
+{
+    "$schema": "http://json-schema.org/draft-04/schema#",
+    "title": "The Adot Adapter Params",
+    "description": "A schema which validates params accepted by Adot adapter",
+    "type": "object",
+    "properties": {
+        "placementId": {
+            "type": "string",
+            "description": "An ID which identifies this placement of the impression"
+        },
+        "parallax": {
+            "type": "boolean",
+            "description": "It determines if the wanted advertising format is a parallax."
+        }
+    },
+    "required": []
+}
\ No newline at end of file
diff --git a/static/bidder-params/dmx.json b/static/bidder-params/dmx.json
index 4c0df65e..3c76e507 100644
--- a/static/bidder-params/dmx.json
+++ b/static/bidder-params/dmx.json
@@ -8,13 +8,25 @@
       "type": "string",
       "description": "Represent boost MemberId from districtm UI"
     },
+    "placement_id" : {
+      "type": "string",
+      "description": "memberid replacement / alternative value or equivalent"
+    },
+    "seller_id" : {
+      "type": "string",
+      "description": "Represent DMX Partner when you get onboarded, this is for specific setup BURL vs NURL"
+    },
+    "dmxid": {
+      "type": "string",
+      "description": "Represent the placement ID dmxid equivalent to 'tagid', this value is optional"
+    },
     "tagid": {
       "type": "string",
-      "description": "Represent the placement ID, this value is optional"
+      "description": "Represent the placement ID tagid equivalent to 'dmxid', this value is optional"
     },
     "bidfloor": {
-      "type": "string",
-      "description": "The minimum price acceptable for a bid"
+      "type": "number",
+      "description": "The minimum price acceptable for a bid, this is optional since we do get the one from the original openrtb request"
     }
   },
 
diff --git a/static/bidder-params/mediafuse.json b/static/bidder-params/mediafuse.json
new file mode 100644
index 00000000..4f38f89d
--- /dev/null
+++ b/static/bidder-params/mediafuse.json
@@ -0,0 +1,26 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "title": "Mediafuse Adapter Params",
+  "description": "A schema which validates params accepted by the Mediafuse adapter",
+
+  "type": "object",
+  "properties": {
+    "placementId": {
+      "type": "integer",
+      "description": "An ID which identifies this placement of the impression"
+    },
+    "siteId": {
+      "type": "integer",
+      "description": "An ID which identifies the site selling the impression"
+    },
+    "aid": {
+      "type": "integer",
+      "description": "An ID which identifies the channel"
+    },
+    "bidFloor": {
+      "type": "number",
+      "description": "BidFloor, US Dollars"
+    }
+  },
+  "required": ["aid"]
+}
diff --git a/static/bidder-params/revcontent.json b/static/bidder-params/revcontent.json
new file mode 100644
index 00000000..e6f99cb2
--- /dev/null
+++ b/static/bidder-params/revcontent.json
@@ -0,0 +1,9 @@
+
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "title": "Revcontent Adapter Params",
+  "description": "A schema which validates params accepted by the Revcontent adapter",
+  "type": "object",
+  "properties": {},
+  "required": []
+}
\ No newline at end of file
diff --git a/static/bidder-params/unruly.json b/static/bidder-params/unruly.json
index f101e53e..da293f1d 100644
--- a/static/bidder-params/unruly.json
+++ b/static/bidder-params/unruly.json
@@ -13,5 +13,5 @@
         "description": "ID for publisher site"
       }
     },
-  "required": ["UUID", "SiteId"]
+  "required": ["uuid", "siteid"]
 }
diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go
index 84044e85..c41f7c6c 100755
--- a/usersync/usersyncers/syncer.go
+++ b/usersync/usersyncers/syncer.go
@@ -50,6 +50,7 @@ import (
 	"github.com/prebid/prebid-server/adapters/logicad"
 	"github.com/prebid/prebid-server/adapters/lunamedia"
 	"github.com/prebid/prebid-server/adapters/marsmedia"
+	"github.com/prebid/prebid-server/adapters/mediafuse"
 	"github.com/prebid/prebid-server/adapters/mgid"
 	"github.com/prebid/prebid-server/adapters/nanointeractive"
 	"github.com/prebid/prebid-server/adapters/ninthdecimal"
@@ -136,6 +137,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderLogicad, logicad.NewLogicadSyncer)
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderLunaMedia, lunamedia.NewLunaMediaSyncer)
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderMarsmedia, marsmedia.NewMarsmediaSyncer)
+	insertIntoMap(cfg, syncers, openrtb_ext.BidderMediafuse, mediafuse.NewMediafuseSyncer)
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderMgid, mgid.NewMgidSyncer)
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderNanoInteractive, nanointeractive.NewNanoInteractiveSyncer)
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderNinthDecimal, ninthdecimal.NewNinthDecimalSyncer)


Test patch:
diff --git a/adapters/adot/adot_test.go b/adapters/adot/adot_test.go
new file mode 100644
index 00000000..fca6b303
--- /dev/null
+++ b/adapters/adot/adot_test.go
@@ -0,0 +1,76 @@
+package adot
+
+import (
+	"encoding/json"
+	"github.com/mxmCherry/openrtb"
+	"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"
+	"testing"
+)
+
+const testsBidderEndpoint = "https://dsp.adotmob.com/headerbidding/bidrequest"
+
+func TestJsonSamples(t *testing.T) {
+	bidder, buildErr := Builder(openrtb_ext.BidderAdot, config.Adapter{
+		Endpoint: testsBidderEndpoint})
+
+	if buildErr != nil {
+		t.Fatalf("Builder returned unexpected error %v", buildErr)
+	}
+
+	adapterstest.RunJSONBidderTest(t, "adottest", bidder)
+}
+
+//Test the media type error
+func TestMediaTypeError(t *testing.T) {
+	_, err := getMediaTypeForBid(nil)
+
+	assert.Error(t, err)
+
+	byteInvalid, _ := json.Marshal(&adotBidExt{Adot: bidExt{"invalid"}})
+	_, err = getMediaTypeForBid(&openrtb.Bid{Ext: json.RawMessage(byteInvalid)})
+
+	assert.Error(t, err)
+}
+
+//Test the bid response when the bidder return a status code 204
+func TestBidResponseNoContent(t *testing.T) {
+	bidder, buildErr := Builder(openrtb_ext.BidderAdot, config.Adapter{
+		Endpoint: "https://dsp.adotmob.com/headerbidding/bidrequest"})
+
+	if buildErr != nil {
+		t.Fatalf("Builder returned unexpected error %v", buildErr)
+	}
+
+	bidResponse, err := bidder.MakeBids(nil, nil, &adapters.ResponseData{StatusCode: 204})
+	if bidResponse != nil {
+		t.Fatalf("the bid response should be nil since the bidder status is No Content")
+	} else if err != nil {
+		t.Fatalf("the error should be nil since the bidder status is 204 : No Content")
+	}
+}
+
+//Test the media type for a bid response
+func TestMediaTypeForBid(t *testing.T) {
+	byteBanner, _ := json.Marshal(&adotBidExt{Adot: bidExt{"banner"}})
+	byteVideo, _ := json.Marshal(&adotBidExt{Adot: bidExt{"video"}})
+	byteNative, _ := json.Marshal(&adotBidExt{Adot: bidExt{"native"}})
+
+	bidTypeBanner, _ := getMediaTypeForBid(&openrtb.Bid{Ext: json.RawMessage(byteBanner)})
+	if bidTypeBanner != openrtb_ext.BidTypeBanner {
+		t.Errorf("the type is not the valid one. actual: %v, expected: %v", bidTypeBanner, openrtb_ext.BidTypeBanner)
+	}
+
+	bidTypeVideo, _ := getMediaTypeForBid(&openrtb.Bid{Ext: json.RawMessage(byteVideo)})
+	if bidTypeVideo != openrtb_ext.BidTypeVideo {
+		t.Errorf("the type is not the valid one. actual: %v, expected: %v", bidTypeVideo, openrtb_ext.BidTypeVideo)
+	}
+
+	bidTypeNative, _ := getMediaTypeForBid(&openrtb.Bid{Ext: json.RawMessage(byteNative)})
+	if bidTypeNative != openrtb_ext.BidTypeNative {
+		t.Errorf("the type is not the valid one. actual: %v, expected: %v", bidTypeNative, openrtb_ext.BidTypeVideo)
+	}
+}
diff --git a/adapters/adot/adottest/exemplary/simple-banner.json b/adapters/adot/adottest/exemplary/simple-banner.json
new file mode 100644
index 00000000..1b8cb986
--- /dev/null
+++ b/adapters/adot/adottest/exemplary/simple-banner.json
@@ -0,0 +1,94 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-banner-id",
+    "imp": [
+      {
+        "id": "test-imp-banner-id",
+        "banner": {
+          "format": [
+            {
+              "w": 320,
+              "h": 250
+            }
+          ],
+          "w": 320,
+          "h": 250
+        },
+        "ext": {
+          "adot": {}
+        }
+      }
+    ]
+  },
+  "httpCalls": [{
+    "expectedRequest": {
+      "uri": "https://dsp.adotmob.com/headerbidding/bidrequest",
+      "body": {
+        "id": "test-request-banner-id",
+        "imp": [
+          {
+            "id": "test-imp-banner-id",
+            "banner": {
+              "format": [
+                {
+                  "w": 320,
+                  "h": 250
+                }
+              ],
+              "w": 320,
+              "h": 250
+            },
+            "ext": {
+              "adot": {}
+            }
+          }
+        ]
+      }
+    },
+    "mockResponse": {
+      "status": 200,
+      "body": {
+        "id": "test-request-banner-id",
+        "seatbid": [{
+          "seat": "adot",
+          "bid": [{
+            "id": "test-request-banner-id",
+            "impid": "test-imp-banner-id",
+            "price": 1.16346,
+            "adm": "some-test-ad",
+            "w": 320,
+            "h": 50,
+            "ext": {
+              "adot": {
+                "media_type": "banner"
+              }
+            }
+          }]
+        }
+        ],
+        "cur": "USD"
+      }
+    }
+  }],
+
+  "expectedBidResponses": [{
+      "currency": "USD",
+      "bids": [{
+          "bid": {
+            "id": "test-request-banner-id",
+            "impid": "test-imp-banner-id",
+            "price": 1.16346,
+            "adm": "some-test-ad",
+            "w": 320,
+            "h": 50,
+            "ext": {
+              "adot": {
+                "media_type": "banner"
+              }
+            }
+          },
+        "type": "banner"
+        }]
+    }]
+}
+
diff --git a/adapters/adot/adottest/exemplary/simple-interstitial.json b/adapters/adot/adottest/exemplary/simple-interstitial.json
new file mode 100644
index 00000000..0e9b573a
--- /dev/null
+++ b/adapters/adot/adottest/exemplary/simple-interstitial.json
@@ -0,0 +1,101 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-inter-id",
+    "imp": [
+      {
+        "id": "test-imp-inter-id",
+        "banner": {
+          "format": [
+            {
+              "w": 320,
+              "h": 480
+            }
+          ],
+          "w": 320,
+          "h": 480
+        },
+        "instl":1,
+        "ext": {
+          "adot": {}
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://dsp.adotmob.com/headerbidding/bidrequest",
+        "body": {
+          "id": "test-request-inter-id",
+          "imp": [
+            {
+              "id": "test-imp-inter-id",
+              "banner": {
+                "format": [
+                  {
+                    "w": 320,
+                    "h": 480
+                  }
+                ],
+                "w": 320,
+                "h": 480
+              },
+              "instl":1,
+              "ext": {
+                "adot": {}
+              }
+            }
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "seatbid": [
+            {
+              "seat": "adot",
+              "bid": [{
+                "id": "test-request-inter-id",
+                "impid": "test-imp-inter-id",
+                "adm": "some-test-ad",
+                "price": 1.16346,
+                "w": 320,
+                "h": 480,
+                "ext": {
+                  "adot": {
+                    "media_type": "banner"
+                  }
+                }
+              }]
+            }
+          ],
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+
+  "expectedBidResponses": [
+    {
+        "bids": [{
+            "bid": {
+                "id": "test-request-inter-id",
+                "impid": "test-imp-inter-id",
+                "price":  1.16346,
+                "adm": "some-test-ad",
+                "w": 320,
+                "h": 480,
+                "ext": {
+                    "adot": {
+                        "media_type": "banner"
+                    }
+                }
+            },
+            "type": "banner"
+        }
+      ]
+    }
+  ]
+}
+
diff --git a/adapters/adot/adottest/exemplary/simple-native.json b/adapters/adot/adottest/exemplary/simple-native.json
new file mode 100644
index 00000000..5fa3c70f
--- /dev/null
+++ b/adapters/adot/adottest/exemplary/simple-native.json
@@ -0,0 +1,88 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-native-id",
+    "imp": [
+      {
+        "id": "test-imp-native-id",
+        "native": {
+          "request": "test-native",
+          "ver": "1.1"
+        },
+        "ext": {
+          "adot": {}
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://dsp.adotmob.com/headerbidding/bidrequest",
+        "body": {
+          "id": "test-request-native-id",
+          "imp": [
+            {
+              "id": "test-imp-native-id",
+              "native": {
+                "request": "test-native",
+                "ver": "1.1"
+              },
+              "ext": {
+                "adot": {}
+              }
+            }
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "seatbid": [
+            {
+              "seat": "adot",
+              "bid": [{
+                "id": "test-request-native-id",
+                "impid": "test-imp-native-id",
+                "price": 1.16346,
+                "adm" : "native-ad",
+                "w": 300,
+                "h": 250,
+                "ext": {
+                  "adot": {
+                    "media_type": "native"
+                  }
+                }
+              }]
+            }
+          ],
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+
+  "expectedBidResponses": [
+    {
+      "bids": [
+        {
+          "bid": {
+            "id": "test-request-native-id",
+            "impid": "test-imp-native-id",
+            "price": 1.16346,
+            "adm" : "native-ad",
+            "w": 300,
+            "h": 250,
+            "ext": {
+              "adot": {
+                "media_type": "native"
+              }
+            }
+          },
+          "type": "native"
+        }
+      ]
+    }
+  ]
+}
+
diff --git a/adapters/adot/adottest/exemplary/simple-video.json b/adapters/adot/adottest/exemplary/simple-video.json
new file mode 100644
index 00000000..a453c4b9
--- /dev/null
+++ b/adapters/adot/adottest/exemplary/simple-video.json
@@ -0,0 +1,120 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-video-id",
+    "imp": [
+      {
+        "id": "test-imp-video-id",
+        "video": {
+          "w": 300,
+          "h": 250,
+          "maxduration": 60,
+          "minduration": 1,
+          "api": [
+            1,
+            2,
+            5,
+            6,
+            7
+          ],
+          "mimes": [
+            "video\/mp4"
+          ],
+          "placement": 4,
+          "protocols": [
+            2
+          ]
+        },
+        "ext": {
+          "adot": {}
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://dsp.adotmob.com/headerbidding/bidrequest",
+        "body": {
+          "id": "test-request-video-id",
+          "imp": [
+            {
+              "id": "test-imp-video-id",
+              "video": {
+                "w": 300,
+                "h": 250,
+                "maxduration": 60,
+                "minduration": 1,
+                "api": [
+                  1,
+                  2,
+                  5,
+                  6,
+                  7
+                ],
+                "mimes": [
+                  "video\/mp4"
+                ],
+                "placement": 4,
+                "protocols": [
+                  2
+                ]
+              },
+              "ext": {
+                "adot": {}
+              }
+            }
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "seatbid": [
+            {
+              "seat": "adot",
+              "bid": [{
+                "id": "test-request-video-id",
+                "impid": "test-imp-video-id",
+                "price": 1.16346,
+                "adm": "some-test-ad",
+                "w": 300,
+                "h": 250,
+                "ext": {
+                  "adot": {
+                    "media_type": "video"
+                  }
+                }
+              }]
+            }
+          ],
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+
+  "expectedBidResponses": [
+    {
+      "bids": [
+        {
+          "bid": {
+            "id": "test-request-video-id",
+            "impid": "test-imp-video-id",
+            "price": 1.16346,
+            "adm": "some-test-ad",
+            "w": 300,
+            "h": 250,
+            "ext": {
+              "adot": {
+                "media_type": "video"
+              }
+            }
+          },
+          "type": "video"
+        }
+      ]
+    }
+  ]
+}
+
diff --git a/adapters/adot/adottest/params/race/banner.json b/adapters/adot/adottest/params/race/banner.json
new file mode 100644
index 00000000..ada77aa4
--- /dev/null
+++ b/adapters/adot/adottest/params/race/banner.json
@@ -0,0 +1,3 @@
+{
+  "placementId": "ee234aac-113"
+}
\ No newline at end of file
diff --git a/adapters/adot/adottest/params/race/video.json b/adapters/adot/adottest/params/race/video.json
new file mode 100644
index 00000000..37808cd2
--- /dev/null
+++ b/adapters/adot/adottest/params/race/video.json
@@ -0,0 +1,3 @@
+{
+  "placementId": "ee234aac-112"
+}
\ No newline at end of file
diff --git a/adapters/adot/adottest/supplemental/simple-audio.json b/adapters/adot/adottest/supplemental/simple-audio.json
new file mode 100644
index 00000000..9be53c4c
--- /dev/null
+++ b/adapters/adot/adottest/supplemental/simple-audio.json
@@ -0,0 +1,66 @@
+{
+  "mockBidRequest": {
+    "id": "unsupported-audio-request",
+    "imp": [
+      {
+        "id": "unsupported-audio-imp",
+        "audio": {
+          "mimes": ["video/mp4"]
+        },
+        "ext": {
+          "bidder": {
+            "placementId": 1
+          }
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://dsp.adotmob.com/headerbidding/bidrequest",
+        "body": {
+          "id": "unsupported-audio-request",
+          "imp": [
+            {
+              "id": "unsupported-audio-imp",
+              "audio": {
+                "mimes": ["video/mp4"]
+              },
+              "ext": {
+                "bidder": {
+                  "placementId": 1
+                }
+              }
+            }
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "seatbid": [
+            {
+              "seat": "adot",
+              "bid": [{
+                "id": "test-request-audio-id",
+                "impid": "test-imp-audio-id",
+                "price": 1.16346,
+                "adm": "some-audio-ad",
+                "ext": {
+                  "adot": {
+                    "media_type": "audio"
+                  }
+                }
+              }]
+            }
+          ],
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+
+  "expectedBidResponses": []
+}
diff --git a/adapters/adot/adottest/supplemental/simple-parallax.json b/adapters/adot/adottest/supplemental/simple-parallax.json
new file mode 100644
index 00000000..4ee2ebc2
--- /dev/null
+++ b/adapters/adot/adottest/supplemental/simple-parallax.json
@@ -0,0 +1,103 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-parallax-id",
+    "imp": [
+      {
+        "id": "test-imp-parallax-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 600
+            }
+          ],
+          "w": 300,
+          "h": 600
+        },
+        "ext": {
+          "adot": {
+            "parallax": true
+          }
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://dsp.adotmob.com/headerbidding/bidrequest",
+        "body": {
+          "id": "test-request-parallax-id",
+          "imp": [
+            {
+              "id": "test-imp-parallax-id",
+              "banner": {
+                "format": [
+                  {
+                    "w": 300,
+                    "h": 600
+                  }
+                ],
+                "w": 300,
+                "h": 600
+              },
+              "ext": {
+                "adot": {
+                  "parallax": true
+                }
+              }
+            }
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "seatbid": [
+            {
+              "seat": "adot",
+              "bid": [{
+                "id": "test-request-parallax-id",
+                "impid": "test-imp-parallax-id",
+                "price": 0.5,
+                "adm": "some-test-ad",
+                "w": 300,
+                "h": 600,
+                "ext": {
+                  "adot": {
+                    "media_type": "banner"
+                  }
+                }
+              }]
+            }
+          ],
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+
+  "expectedBidResponses": [
+    {
+      "bids": [
+        {
+          "bid": {
+            "id": "test-request-parallax-id",
+            "impid": "test-imp-parallax-id",
+            "price": 0.5,
+            "adm": "some-test-ad",
+            "w": 300,
+            "h": 600,
+            "ext": {
+              "adot": {
+                "media_type": "banner"
+              }
+            }
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/adot/adottest/supplemental/status_204.json b/adapters/adot/adottest/supplemental/status_204.json
new file mode 100644
index 00000000..44a895b5
--- /dev/null
+++ b/adapters/adot/adottest/supplemental/status_204.json
@@ -0,0 +1,39 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-banner-id",
+    "imp": [
+      {
+        "id": "test-imp-banner-id",
+        "banner": {},
+        "ext": {
+          "adot": {}
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://dsp.adotmob.com/headerbidding/bidrequest",
+        "body": {
+          "id": "test-request-banner-id",
+          "imp": [
+            {
+              "id": "test-imp-banner-id",
+              "banner": {},
+              "ext": {
+                "adot": {}
+              }
+            }
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 204,
+        "body": {}
+      }
+    }
+  ],
+  "expectedBidResponses": []
+}
+
diff --git a/adapters/adot/adottest/supplemental/status_400.json b/adapters/adot/adottest/supplemental/status_400.json
new file mode 100644
index 00000000..22328fd9
--- /dev/null
+++ b/adapters/adot/adottest/supplemental/status_400.json
@@ -0,0 +1,62 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-banner-id",
+    "imp": [
+      {
+        "id": "test-imp-banner-id",
+        "banner": {
+          "format": [
+            {
+              "w": 320,
+              "h": 250
+            }
+          ],
+          "w": 320,
+          "h": 250
+        },
+        "ext": {
+          "adot": {}
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://dsp.adotmob.com/headerbidding/bidrequest",
+        "body": {
+          "id": "test-request-banner-id",
+          "imp": [
+            {
+              "id": "test-imp-banner-id",
+              "banner": {
+                "format": [
+                  {
+                    "w": 320,
+                    "h": 250
+                  }
+                ],
+                "w": 320,
+                "h": 250
+              },
+              "ext": {
+                "adot": {}
+              }
+            }
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 400,
+        "body": {}
+      }
+    }
+  ],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "Unexpected status code: 400. Run with request.debug = 1 for more info",
+      "comparison": "literal"
+    }
+  ]
+}
+
diff --git a/adapters/adot/adottest/supplemental/status_500.json b/adapters/adot/adottest/supplemental/status_500.json
new file mode 100644
index 00000000..879bb8c5
--- /dev/null
+++ b/adapters/adot/adottest/supplemental/status_500.json
@@ -0,0 +1,62 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-banner-id",
+    "imp": [
+      {
+        "id": "test-imp-banner-id",
+        "banner": {
+          "format": [
+            {
+              "w": 320,
+              "h": 250
+            }
+          ],
+          "w": 320,
+          "h": 250
+        },
+        "ext": {
+          "adot": {}
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://dsp.adotmob.com/headerbidding/bidrequest",
+        "body": {
+          "id": "test-request-banner-id",
+          "imp": [
+            {
+              "id": "test-imp-banner-id",
+              "banner": {
+                "format": [
+                  {
+                    "w": 320,
+                    "h": 250
+                  }
+                ],
+                "w": 320,
+                "h": 250
+              },
+              "ext": {
+                "adot": {}
+              }
+            }
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 500,
+        "body": {}
+      }
+    }
+  ],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "Unexpected status code: 500. Run with request.debug = 1 for more info",
+      "comparison": "literal"
+    }
+  ]
+}
+
diff --git a/adapters/adot/adottest/supplemental/unmarshal_error.json b/adapters/adot/adottest/supplemental/unmarshal_error.json
new file mode 100644
index 00000000..a87e1189
--- /dev/null
+++ b/adapters/adot/adottest/supplemental/unmarshal_error.json
@@ -0,0 +1,62 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-banner-id",
+    "imp": [
+      {
+        "id": "test-imp-banner-id",
+        "banner": {
+          "format": [
+            {
+              "w": 320,
+              "h": 250
+            }
+          ],
+          "w": 320,
+          "h": 250
+        },
+        "ext": {
+          "adot": {}
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://dsp.adotmob.com/headerbidding/bidrequest",
+        "body": {
+          "id": "test-request-banner-id",
+          "imp": [
+            {
+              "id": "test-imp-banner-id",
+              "banner": {
+                "format": [
+                  {
+                    "w": 320,
+                    "h": 250
+                  }
+                ],
+                "w": 320,
+                "h": 250
+              },
+              "ext": {
+                "adot": {}
+              }
+            }
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": "fail for unmarshal"
+        }
+    }],
+
+  "expectedMakeBidsErrors": [
+    {
+      "value": "json: cannot unmarshal string into Go value of type openrtb.BidResponse",
+      "comparison": "literal"
+    }
+  ]
+}
+
diff --git a/adapters/adot/params_test.go b/adapters/adot/params_test.go
new file mode 100644
index 00000000..2f7b4b9a
--- /dev/null
+++ b/adapters/adot/params_test.go
@@ -0,0 +1,53 @@
+package adot
+
+import (
+	"encoding/json"
+	"github.com/prebid/prebid-server/openrtb_ext"
+	"testing"
+)
+
+// This file actually intends to test static/bidder-params/adot.json
+//
+// These also validate the format of the external API: request.imp[i].ext.adot
+
+// TestValidParams makes sure that the adot schema accepts all imp.ext fields which we intend to support.
+func TestValidParams(t *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+	if err != nil {
+		t.Fatalf("Failed to fetch the json-schemas. %v", err)
+	}
+
+	for _, validParam := range validParams {
+		if err := validator.Validate(openrtb_ext.BidderAdot, json.RawMessage(validParam)); err != nil {
+			t.Errorf("Schema rejected adot params: %s", validParam)
+		}
+	}
+}
+
+func TestInvalidParams(t *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+	if err != nil {
+		t.Fatalf("Failed to fetch the json-schemas. %v", err)
+	}
+
+	for _, invalidParam := range invalidParams {
+		if err := validator.Validate(openrtb_ext.BidderAdot, json.RawMessage(invalidParam)); err == nil {
+			t.Errorf("Schema allowed unexpected params: %s", invalidParam)
+		}
+	}
+}
+
+var validParams = []string{
+	`{}`,
+	`{"placementId": "test-114"}`,
+	`{"placementId": "test-113", "parallax": true}`,
+	`{"placementId": "test-113", "parallax": false}`,
+}
+
+var invalidParams = []string{
+	`{"parallax": 1}`,
+	`{"placementId": 135123}`,
+	`{"placementId": 113, "parallax": 1}`,
+	`{"placementId": 142, "parallax": true}`,
+	`{"placementId": "test-114", "parallax": 1}`,
+}
diff --git a/adapters/dmx/dmxtest/exemplary/imp-populated-banner.json b/adapters/dmx/dmxtest/exemplary/imp-populated-banner.json
index f2b30bb4..b39b722d 100644
--- a/adapters/dmx/dmxtest/exemplary/imp-populated-banner.json
+++ b/adapters/dmx/dmxtest/exemplary/imp-populated-banner.json
@@ -49,7 +49,12 @@
             "id":"ed6207cefff74c14878963566683c070",
             "name":"Skout - iOS Match Buy",
             "publisher":{
-              "id":"10400"
+              "id":"10400",
+              "ext": {
+                "dmx": {
+                  "id": "10400"
+                }
+              }
             },
             "storeurl":"https://itunes.apple.com/app/id302324249"
           },
diff --git a/adapters/dmx/dmxtest/exemplary/simple-app.json b/adapters/dmx/dmxtest/exemplary/simple-app.json
index a2d57163..3c226f5e 100644
--- a/adapters/dmx/dmxtest/exemplary/simple-app.json
+++ b/adapters/dmx/dmxtest/exemplary/simple-app.json
@@ -49,7 +49,12 @@
             "id":"ed6207cefff74c14878963566683c070",
             "name":"Skout - iOS Match Buy",
             "publisher":{
-              "id":"10400"
+              "id":"10400",
+              "ext": {
+                "dmx": {
+                  "id": "10400"
+                }
+              }
             },
             "storeurl":"https://itunes.apple.com/app/id302324249"
           },
diff --git a/adapters/dmx/params_test.go b/adapters/dmx/params_test.go
new file mode 100644
index 00000000..0e5250b1
--- /dev/null
+++ b/adapters/dmx/params_test.go
@@ -0,0 +1,53 @@
+package dmx
+
+import (
+	"encoding/json"
+	"github.com/prebid/prebid-server/openrtb_ext"
+	"testing"
+)
+
+func TestValidParams(t *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+	if err != nil {
+		t.Fatalf("Failed to fetch the json-schemas. %v", err)
+	}
+
+	for _, validParam := range validParams {
+		if err := validator.Validate(openrtb_ext.BidderDmx, json.RawMessage(validParam)); err != nil {
+			t.Errorf("Schema rejected dmx params: %s", validParam)
+		}
+	}
+	for _, invalidParam := range invalidParams {
+		if err := validator.Validate(openrtb_ext.BidderDmx, json.RawMessage(invalidParam)); err == nil {
+			t.Errorf("Schema was not supposed to be valid: %s", invalidParam)
+		}
+	}
+}
+
+var validParams = []string{
+	`{"memberid": "anyMemberId", "placement_id":"anyPlacementId", "seller_id":"anySellerId", "dmxid":"anyDmxid", "tagid": "25251", "bidfloor": 0.01}`,
+	`{"memberid": "anyMemberId", "seller_id": "anySellerId", "dmxid": "anyDmxid", "tagid": "25251", "bidfloor": 0.01}`,
+	`{"memberid": "anyMemberId", "placement_id": "anyPlacementId", "dmxid": "anyDmxid", "tagid": "25251", "bidfloor": 0.01}`,
+	`{"memberid": "anyMemberId", "placement_id": "anyPlacementId", "seller_id": "anySellerId", "tagid": "25251", "bidfloor": 0.01}`,
+	`{"memberid": "anyMemberId", "placement_id": "anyPlacementId", "seller_id": "anySellerId", "dmxid": "anyDmxid", "bidfloor": 0.01}`,
+	`{"memberid": "anyMemberId", "placement_id": "anyPlacementId", "seller_id": "anySellerId", "dmxid": "anyDmxid", "tagid": "25251"}`,
+	`{"memberid": "anyMemberId"}`,
+}
+
+var invalidParams = []string{
+	`null`,
+	`nil`,
+	``,
+	`[]`,
+	`true`,
+	`{}`,
+	// Invalid because missing "memberid"
+	`{"placement_id":"anyPlacementId", "seller_id":"anySellerId", "dmxid":"anyDmxid", "tagid": "25251", "bidfloor": 0.01}`,
+	// Invalid because "memberid" not a string
+	`{"memberid": 5}`,
+	// Invalid since dmxid need to be a string
+	`{"memberid": "1222", "dmxid": 432423}`,
+	//
+	// ...more invalid param scenarios
+	//
+}
diff --git a/adapters/mediafuse/usersync_test.go b/adapters/mediafuse/usersync_test.go
new file mode 100644
index 00000000..30b5b535
--- /dev/null
+++ b/adapters/mediafuse/usersync_test.go
@@ -0,0 +1,30 @@
+package mediafuse
+
+import (
+	"testing"
+	"text/template"
+
+	"github.com/prebid/prebid-server/privacy"
+	"github.com/prebid/prebid-server/privacy/gdpr"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestMediafuseSyncer(t *testing.T) {
+	syncURL := "//sync.hbmp.mediafuse.com/csync?t=p&ep=0&redir=localhost%2Fsetuid%3Fbidder%3Dmediafuse%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7Buid%7D"
+	syncURLTemplate := template.Must(
+		template.New("sync-template").Parse(syncURL),
+	)
+
+	syncer := NewMediafuseSyncer(syncURLTemplate)
+	syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{
+		GDPR: gdpr.Policy{
+			Signal: "0",
+		},
+	})
+
+	assert.NoError(t, err)
+	assert.Equal(t, "//sync.hbmp.mediafuse.com/csync?t=p&ep=0&redir=localhost%2Fsetuid%3Fbidder%3Dmediafuse%26gdpr%3D0%26gdpr_consent%3D%26uid%3D%7Buid%7D", syncInfo.URL)
+	assert.Equal(t, "redirect", syncInfo.Type)
+	assert.EqualValues(t, 411, syncer.GDPRVendorID())
+	assert.Equal(t, false, syncInfo.SupportCORS)
+}
diff --git a/adapters/revcontent/revcontent_test.go b/adapters/revcontent/revcontent_test.go
new file mode 100644
index 00000000..836ef138
--- /dev/null
+++ b/adapters/revcontent/revcontent_test.go
@@ -0,0 +1,21 @@
+package revcontent
+
+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.BidderRevcontent, config.Adapter{
+		Endpoint: "https://trends.revcontent.com/rtb?userId=1234&apiKey=abcd",
+	})
+
+	if buildErr != nil {
+		t.Fatalf("Builder returned unexpected error %v", buildErr)
+	}
+
+	adapterstest.RunJSONBidderTest(t, "revcontenttest", bidder)
+}
diff --git a/adapters/revcontent/revcontenttest/exemplary/no-bid.json b/adapters/revcontent/revcontenttest/exemplary/no-bid.json
new file mode 100644
index 00000000..b71c3a93
--- /dev/null
+++ b/adapters/revcontent/revcontenttest/exemplary/no-bid.json
@@ -0,0 +1,45 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [{"w": 300, "h": 50}]
+        }
+      }
+    ],
+    "site": {
+      "domain": "test.com"
+    }
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://trends.revcontent.com/rtb?userId=1234&apiKey=abcd",
+        "body": {
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id":"test-imp-id",
+              "banner": {
+                "format": [{"w": 300, "h": 50}]
+              }
+            }
+          ],
+          "site": {
+            "domain": "test.com"
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 204,
+        "body": {}
+      }
+    }
+  ],
+
+  "expectedBidResponses": []
+
+}
diff --git a/adapters/revcontent/revcontenttest/exemplary/simple-banner.json b/adapters/revcontent/revcontenttest/exemplary/simple-banner.json
new file mode 100644
index 00000000..a63f9ac8
--- /dev/null
+++ b/adapters/revcontent/revcontenttest/exemplary/simple-banner.json
@@ -0,0 +1,85 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [{"w": 300, "h": 50}]
+        }
+      }
+    ],
+    "site": {
+      "id": "example",
+      "domain": "example.com",
+      "page": "example.com",
+      "publisher": {
+        "id": "example"
+      }
+    }
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://trends.revcontent.com/rtb?userId=1234&apiKey=abcd",
+        "body": {
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id":"test-imp-id",
+              "banner": {
+                "format": [{"w": 300, "h": 50}]
+              }
+            }
+          ],
+          "site": {
+            "id": "example",
+            "domain": "example.com",
+            "page": "example.com",
+            "publisher": {
+              "id": "example"
+            }
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "seatbid": [
+            {
+              "seat": "ttx",
+              "bid": [{
+                "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800",
+                "impid": "test-imp-id",
+                "price": 1.2,
+                "adm": "<div></div>",
+                "crid": "crid_testid"
+              }]
+            }
+          ],
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800",
+            "impid": "test-imp-id",
+            "price": 1.2,
+            "adm": "<div></div>",
+            "crid": "crid_testid"
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/revcontent/revcontenttest/exemplary/simple-native.json b/adapters/revcontent/revcontenttest/exemplary/simple-native.json
new file mode 100644
index 00000000..8cd20913
--- /dev/null
+++ b/adapters/revcontent/revcontenttest/exemplary/simple-native.json
@@ -0,0 +1,76 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "native": {
+          "request": "{\"ver\":\"1.2\",\"context\":1,\"plcmttype\":4,\"plcmtcnt\":1,\"assets\":[{\"id\":2,\"required\":1,\"title\":{\"len\":90}},{\"id\":6,\"required\":1,\"img\":{\"type\":3,\"wmin\":128,\"hmin\":128,\"mimes\":[\"image/jpg\",\"image/jpeg\",\"image/png\"]}},{\"id\":7,\"required\":1,\"data\":{\"type\":2,\"len\":120}}]}",
+          "ver": "1.2"
+        }
+      }
+    ],
+    "site": {
+      "domain": "test.com"
+    }
+  },
+  "httpcalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://trends.revcontent.com/rtb?userId=1234&apiKey=abcd",
+        "body": {
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id": "test-imp-id",
+              "native": {
+                "request": "{\"ver\":\"1.2\",\"context\":1,\"plcmttype\":4,\"plcmtcnt\":1,\"assets\":[{\"id\":2,\"required\":1,\"title\":{\"len\":90}},{\"id\":6,\"required\":1,\"img\":{\"type\":3,\"wmin\":128,\"hmin\":128,\"mimes\":[\"image/jpg\",\"image/jpeg\",\"image/png\"]}},{\"id\":7,\"required\":1,\"data\":{\"type\":2,\"len\":120}}]}",
+                "ver": "1.2"
+              }
+            }
+          ],
+          "site": {
+            "domain": "test.com"
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "8400d766-58b3-47d4-80d7-6658b337d403",
+                  "impid": "test-imp-id",
+                  "price": 1.2,
+                  "adm": "{\\\"ver\\\":\\\"1.1\\\",\\\"assets\\\":[]}",
+                  "crid": "crid_testid"
+                }
+              ]
+            }
+          ]
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "8400d766-58b3-47d4-80d7-6658b337d403",
+            "impid": "test-imp-id",
+            "price": 1.2,
+            "adm": "{\\\"ver\\\":\\\"1.1\\\",\\\"assets\\\":[]}",
+            "crid": "crid_testid"
+
+          },
+          "type": "native"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/revcontent/revcontenttest/supplemental/bad_response.json b/adapters/revcontent/revcontenttest/supplemental/bad_response.json
new file mode 100644
index 00000000..bd562373
--- /dev/null
+++ b/adapters/revcontent/revcontenttest/supplemental/bad_response.json
@@ -0,0 +1,49 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [{"w": 300, "h": 50}]
+        }
+      }
+    ],
+    "site": {
+      "domain": "test.com"
+    }
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://trends.revcontent.com/rtb?userId=1234&apiKey=abcd",
+        "body": {
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id":"test-imp-id",
+              "banner": {
+                "format": [{"w": 300, "h": 50}]
+              }
+            }
+          ],
+          "site": {
+            "domain": "test.com"
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": "{\"id\":test-request-id"
+      }
+    }
+  ],
+
+  "expectedMakeBidsErrors": [
+    {
+      "comparison": "literal",
+      "value": "json: cannot unmarshal string into Go value of type openrtb.BidResponse"
+    }
+  ]
+}
diff --git a/adapters/revcontent/revcontenttest/supplemental/missing_app_name.json b/adapters/revcontent/revcontenttest/supplemental/missing_app_name.json
new file mode 100644
index 00000000..800690fc
--- /dev/null
+++ b/adapters/revcontent/revcontenttest/supplemental/missing_app_name.json
@@ -0,0 +1,23 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [{"w": 300, "h": 50}]
+        }
+      }
+    ],
+    "site": {
+      "id":"test-imp-id"
+    }
+  },
+
+  "expectedMakeRequestsErrors": [
+    {
+      "value": "Impression is missing app name or site domain, and must contain one.",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/revcontent/revcontenttest/supplemental/missing_domain.json b/adapters/revcontent/revcontenttest/supplemental/missing_domain.json
new file mode 100644
index 00000000..8b0c68d8
--- /dev/null
+++ b/adapters/revcontent/revcontenttest/supplemental/missing_domain.json
@@ -0,0 +1,23 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [{"w": 300, "h": 50}]
+        }
+      }
+    ],
+    "app": {
+      "id":"test-imp-id"
+    }
+  },
+
+  "expectedMakeRequestsErrors": [
+    {
+      "value": "Impression is missing app name or site domain, and must contain one.",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/revcontent/revcontenttest/supplemental/status_400.json b/adapters/revcontent/revcontenttest/supplemental/status_400.json
new file mode 100644
index 00000000..5ded1415
--- /dev/null
+++ b/adapters/revcontent/revcontenttest/supplemental/status_400.json
@@ -0,0 +1,49 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [{"w": 300, "h": 50}]
+        }
+      }
+    ],
+    "site": {
+      "domain": "test.com"
+    }
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://trends.revcontent.com/rtb?userId=1234&apiKey=abcd",
+        "body": {
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id":"test-imp-id",
+              "banner": {
+                "format": [{"w": 300, "h": 50}]
+              }
+            }
+          ],
+          "site": {
+            "domain": "test.com"
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 400,
+        "body": {}
+      }
+    }
+  ],
+
+  "expectedMakeBidsErrors": [
+    {
+      "comparison": "literal",
+      "value": "unexpected status code: 400."
+    }
+  ]
+}
diff --git a/adapters/revcontent/revcontenttest/supplemental/status_500.json b/adapters/revcontent/revcontenttest/supplemental/status_500.json
new file mode 100644
index 00000000..3732abbd
--- /dev/null
+++ b/adapters/revcontent/revcontenttest/supplemental/status_500.json
@@ -0,0 +1,49 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [{"w": 300, "h": 50}]
+        }
+      }
+    ],
+    "site": {
+      "domain": "test.com"
+    }
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://trends.revcontent.com/rtb?userId=1234&apiKey=abcd",
+        "body": {
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id":"test-imp-id",
+              "banner": {
+                "format": [{"w": 300, "h": 50}]
+              }
+            }
+          ],
+          "site": {
+            "domain": "test.com"
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 500,
+        "body": {}
+      }
+    }
+  ],
+
+  "expectedMakeBidsErrors": [
+    {
+      "comparison": "literal",
+      "value": "unexpected status code: 500."
+    }
+  ]
+}
diff --git a/adapters/unruly/params_test.go b/adapters/unruly/params_test.go
new file mode 100644
index 00000000..9b8f3110
--- /dev/null
+++ b/adapters/unruly/params_test.go
@@ -0,0 +1,47 @@
+package unruly
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+func TestValidParams(t *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+	if err != nil {
+		t.Fatalf("Failed to fetch the json-schemas. %v", err)
+	}
+
+	for _, validParam := range validParams {
+		if err := validator.Validate(openrtb_ext.BidderUnruly, json.RawMessage(validParam)); err != nil {
+			t.Errorf("Schema rejected Unruly params: %s", validParam)
+		}
+	}
+}
+
+func TestInvalidParams(t *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+	if err != nil {
+		t.Fatalf("Failed to fetch the json-schemas. %v", err)
+	}
+
+	for _, invalidParam := range invalidParams {
+		if err := validator.Validate(openrtb_ext.BidderUnruly, json.RawMessage(invalidParam)); err == nil {
+			t.Errorf("Schema allowed unexpected params: %s", invalidParam)
+		}
+	}
+}
+
+var validParams = []string{
+	`{"uuid": "123", "siteid": "abc"}`,
+}
+
+var invalidParams = []string{
+	`{}`,                               // Missing required uuid + siteId
+	`{"siteId": "123"}`,                // Missing required uuid
+	`{"uuid": "123"}`,                  // Missing required siteId
+	`{"uuid": 123, "siteid": "abc"}`,   // Wrong uuid data type
+	`{"uuid": "123", "siteid": 123}`,   // Wrong siteid data type
+	`{"UUID": "123", "SiteId": "abc"}`, // Invalid capitalization (json is case sensitive)
+}
diff --git a/adapters/unruly/unrulytest/params/race/video.json b/adapters/unruly/unrulytest/params/race/video.json
index 0e82f166..f0b1b20a 100644
--- a/adapters/unruly/unrulytest/params/race/video.json
+++ b/adapters/unruly/unrulytest/params/race/video.json
@@ -1,4 +1,4 @@
 {
-  "UUID": 1234,
-  "SiteId": 1234
+  "uuid": 1234,
+  "siteid": 1234
 }
diff --git a/amp/parse_test.go b/amp/parse_test.go
new file mode 100644
index 00000000..e2c82d71
--- /dev/null
+++ b/amp/parse_test.go
@@ -0,0 +1,168 @@
+package amp
+
+import (
+	"net/http"
+	"testing"
+
+	"github.com/mxmCherry/openrtb"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestParseParams(t *testing.T) {
+	var expectedTimeout uint64 = 42
+
+	testCases := []struct {
+		description    string
+		query          string
+		expectedParams Params
+		expectedError  string
+	}{
+		{
+			description:   "Empty",
+			query:         "",
+			expectedError: "AMP requests require an AMP tag_id",
+		},
+		{
+			description: "All Fields",
+			query: "tag_id=anyTagID&account=anyAccount&curl=anyCurl&consent_string=anyConsent&debug=1&__amp_source_origin=anyOrigin" +
+				"&slot=anySlot&timeout=42&h=1&w=2&oh=3&ow=4&ms=10x11,12x13",
+			expectedParams: Params{
+				Account:         "anyAccount",
+				CanonicalURL:    "anyCurl",
+				Consent:         "anyConsent",
+				Debug:           true,
+				Origin:          "anyOrigin",
+				Slot:            "anySlot",
+				StoredRequestID: "anyTagID",
+				Timeout:         &expectedTimeout,
+				Size: Size{
+					Height:         1,
+					OverrideHeight: 3,
+					OverrideWidth:  4,
+					Width:          2,
+					Multisize: []openrtb.Format{
+						{W: 10, H: 11}, {W: 12, H: 13},
+					},
+				},
+			},
+		},
+		{
+			description:    "Integer Values Ignored If Invalid",
+			query:          "tag_id=anyTagID&h=invalid&w=invalid&oh=invalid&ow=invalid&ms=invalid",
+			expectedParams: Params{StoredRequestID: "anyTagID"},
+		},
+		{
+			description:    "consent_string Preferred Over gdpr_consent",
+			query:          "tag_id=anyTagID&consent_string=consent1&gdpr_consent=consent2",
+			expectedParams: Params{StoredRequestID: "anyTagID", Consent: "consent1"},
+		},
+		{
+			description:    "consent_string Preferred Over gdpr_consent - Order Doesn't Matter",
+			query:          "tag_id=anyTagID&gdpr_consent=consent2&consent_string=consent1",
+			expectedParams: Params{StoredRequestID: "anyTagID", Consent: "consent1"},
+		},
+		{
+			description:    "Just gdpr_consent",
+			query:          "tag_id=anyTagID&gdpr_consent=consent2",
+			expectedParams: Params{StoredRequestID: "anyTagID", Consent: "consent2"},
+		},
+		{
+			description:    "Debug 0",
+			query:          "tag_id=anyTagID&debug=0",
+			expectedParams: Params{StoredRequestID: "anyTagID", Debug: false},
+		},
+		{
+			description:    "Debug Ignored If Invalid",
+			query:          "tag_id=anyTagID&debug=invalid",
+			expectedParams: Params{StoredRequestID: "anyTagID", Debug: false},
+		},
+	}
+
+	for _, test := range testCases {
+		httpRequest, err := http.NewRequest("GET", "http://any.url/anypage?"+test.query, nil)
+		assert.NoError(t, err, test.description+":request")
+
+		params, err := ParseParams(httpRequest)
+		assert.Equal(t, test.expectedParams, params, test.description+":params")
+		if test.expectedError == "" {
+			assert.NoError(t, err, test.description+":err")
+		} else {
+			assert.EqualError(t, err, test.expectedError)
+		}
+	}
+}
+
+func TestParseMultisize(t *testing.T) {
+	testCases := []struct {
+		description     string
+		multisize       string
+		expectedFormats []openrtb.Format
+	}{
+		{
+			description:     "Empty",
+			multisize:       "",
+			expectedFormats: nil,
+		},
+		{
+			description:     "One",
+			multisize:       "1x2",
+			expectedFormats: []openrtb.Format{{W: 1, H: 2}},
+		},
+		{
+			description:     "Many",
+			multisize:       "1x2,3x4",
+			expectedFormats: []openrtb.Format{{W: 1, H: 2}, {W: 3, H: 4}},
+		},
+		{
+			// Existing Behavior: The " 3" token in the second size is parsed as 0.
+			description:     "Many With Space - Quirky Result",
+			multisize:       "1x2, 3x4",
+			expectedFormats: []openrtb.Format{{W: 1, H: 2}, {W: 0, H: 4}},
+		},
+		{
+			description:     "One - Zero Size - Ignored",
+			multisize:       "0x0",
+			expectedFormats: nil,
+		},
+		{
+			description:     "Many - Zero Size - All Ignored",
+			multisize:       "0x0,3x4",
+			expectedFormats: nil,
+		},
+		{
+			description:     "One - Extra Dimension - Ignored",
+			multisize:       "1x2x3",
+			expectedFormats: nil,
+		},
+		{
+			description:     "Many - Extra Dimension - All Ignored",
+			multisize:       "1x2x3,4x5",
+			expectedFormats: nil,
+		},
+		{
+			description:     "One - Invalid Values - Ignored",
+			multisize:       "INVALIDxINVALID",
+			expectedFormats: nil,
+		},
+		{
+			description:     "Many - Invalid Values - All Ignored",
+			multisize:       "1x2,INVALIDxINVALID",
+			expectedFormats: nil,
+		},
+		{
+			description:     "One - No Pair - Ignored",
+			multisize:       "INVALID",
+			expectedFormats: nil,
+		},
+		{
+			description:     "Many - No Pair - All Ignored",
+			multisize:       "1x2,INVALID",
+			expectedFormats: nil,
+		},
+	}
+
+	for _, test := range testCases {
+		result := parseMultisize(test.multisize)
+		assert.ElementsMatch(t, test.expectedFormats, result, test.description)
+	}
+}
diff --git a/endpoints/openrtb2/amp_auction_test.go b/endpoints/openrtb2/amp_auction_test.go
index 746d436d..97e99c6b 100644
--- a/endpoints/openrtb2/amp_auction_test.go
+++ b/endpoints/openrtb2/amp_auction_test.go
@@ -1031,14 +1031,12 @@ func getTestBidRequest(nilUser bool, userExt *openrtb_ext.ExtUser, nilRegs bool,
 
 func TestSetEffectiveAmpPubID(t *testing.T) {
 	testPubID := "test-pub"
-	testURLQueryParams := url.Values{}
-	testURLQueryParams.Add("account", testPubID)
 
 	testCases := []struct {
-		description    string
-		req            *openrtb.BidRequest
-		urlQueryParams url.Values
-		expectedPubID  string
+		description   string
+		req           *openrtb.BidRequest
+		account       string
+		expectedPubID string
 	}{
 		{
 			description: "No publisher ID provided",
@@ -1072,7 +1070,7 @@ func TestSetEffectiveAmpPubID(t *testing.T) {
 			expectedPubID: testPubID,
 		},
 		{
-			description: "Publisher ID present in account query parameter",
+			description: "Publisher ID present in account parameter",
 			req: &openrtb.BidRequest{
 				App: &openrtb.App{
 					Publisher: &openrtb.Publisher{
@@ -1080,8 +1078,8 @@ func TestSetEffectiveAmpPubID(t *testing.T) {
 					},
 				},
 			},
-			urlQueryParams: testURLQueryParams,
-			expectedPubID:  testPubID,
+			account:       testPubID,
+			expectedPubID: testPubID,
 		},
 		{
 			description: "req.Site.Publisher present but ID set to empty string",
@@ -1097,7 +1095,7 @@ func TestSetEffectiveAmpPubID(t *testing.T) {
 	}
 
 	for _, test := range testCases {
-		setEffectiveAmpPubID(test.req, test.urlQueryParams)
+		setEffectiveAmpPubID(test.req, test.account)
 		if test.req.Site != nil {
 			assert.Equal(t, test.expectedPubID, test.req.Site.Publisher.ID,
 				"should return the expected Publisher ID for test case: %s", test.description)
diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go
index ade3c14a..413d7995 100644
--- a/endpoints/openrtb2/auction_test.go
+++ b/endpoints/openrtb2/auction_test.go
@@ -1440,19 +1440,19 @@ func TestCurrencyTrunc(t *testing.T) {
 
 	ui := uint64(1)
 	req := openrtb.BidRequest{
-		ID: "someID",
+		ID: "anyRequestID",
 		Imp: []openrtb.Imp{
 			{
-				ID: "imp-ID",
+				ID: "anyImpID",
 				Banner: &openrtb.Banner{
 					W: &ui,
 					H: &ui,
 				},
-				Ext: json.RawMessage("{\"appnexus\": {\"placementId\": 5667}}"),
+				Ext: json.RawMessage(`{"appnexus": {"placementId": 5667}}`),
 			},
 		},
 		Site: &openrtb.Site{
-			ID: "myID",
+			ID: "anySiteID",
 		},
 		Cur: []string{"USD", "EUR"},
 	}
@@ -1484,10 +1484,10 @@ func TestCCPAInvalid(t *testing.T) {
 
 	ui := uint64(1)
 	req := openrtb.BidRequest{
-		ID: "someID",
+		ID: "anyRequestID",
 		Imp: []openrtb.Imp{
 			{
-				ID: "imp-ID",
+				ID: "anyImpID",
 				Banner: &openrtb.Banner{
 					W: &ui,
 					H: &ui,
@@ -1496,10 +1496,10 @@ func TestCCPAInvalid(t *testing.T) {
 			},
 		},
 		Site: &openrtb.Site{
-			ID: "myID",
+			ID: "anySiteID",
 		},
 		Regs: &openrtb.Regs{
-			Ext: json.RawMessage(`{"us_privacy":"invalid by length"}`),
+			Ext: json.RawMessage(`{"us_privacy": "invalid by length"}`),
 		},
 	}
 
@@ -1532,10 +1532,10 @@ func TestNoSaleInvalid(t *testing.T) {
 
 	ui := uint64(1)
 	req := openrtb.BidRequest{
-		ID: "someID",
+		ID: "anyRequestID",
 		Imp: []openrtb.Imp{
 			{
-				ID: "imp-ID",
+				ID: "anyImpID",
 				Banner: &openrtb.Banner{
 					W: &ui,
 					H: &ui,
@@ -1544,12 +1544,12 @@ func TestNoSaleInvalid(t *testing.T) {
 			},
 		},
 		Site: &openrtb.Site{
-			ID: "myID",
+			ID: "anySiteID",
 		},
 		Regs: &openrtb.Regs{
-			Ext: json.RawMessage(`{"us_privacy":"1NYN"}`),
+			Ext: json.RawMessage(`{"us_privacy": "1NYN"}`),
 		},
-		Ext: json.RawMessage(`{"prebid":{"nosale":["*", "appnexus"]}}`),
+		Ext: json.RawMessage(`{"prebid": {"nosale": ["*", "appnexus"]} }`),
 	}
 
 	errL := deps.validateRequest(&req)
@@ -1583,10 +1583,10 @@ func TestValidateSourceTID(t *testing.T) {
 
 	ui := uint64(1)
 	req := openrtb.BidRequest{
-		ID: "someID",
+		ID: "anyRequestID",
 		Imp: []openrtb.Imp{
 			{
-				ID: "imp-ID",
+				ID: "anyImpID",
 				Banner: &openrtb.Banner{
 					W: &ui,
 					H: &ui,
@@ -1595,10 +1595,7 @@ func TestValidateSourceTID(t *testing.T) {
 			},
 		},
 		Site: &openrtb.Site{
-			ID: "myID",
-		},
-		Regs: &openrtb.Regs{
-			Ext: json.RawMessage(`{"us_privacy":"invalid by length"}`),
+			ID: "anySiteID",
 		},
 	}
 
@@ -1627,10 +1624,10 @@ func TestSChainInvalid(t *testing.T) {
 
 	ui := uint64(1)
 	req := openrtb.BidRequest{
-		ID: "someID",
+		ID: "anyRequestID",
 		Imp: []openrtb.Imp{
 			{
-				ID: "imp-ID",
+				ID: "anyImpID",
 				Banner: &openrtb.Banner{
 					W: &ui,
 					H: &ui,
@@ -1639,17 +1636,14 @@ func TestSChainInvalid(t *testing.T) {
 			},
 		},
 		Site: &openrtb.Site{
-			ID: "myID",
-		},
-		Regs: &openrtb.Regs{
-			Ext: json.RawMessage(`{"us_privacy":"abcd"}`),
+			ID: "anySiteID",
 		},
 		Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller1.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}, {"bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller2.com","sid":"00002","rid":"BidRequest2","hp":1}],"ver":"1.0"}}]}}`),
 	}
 
 	errL := deps.validateRequest(&req)
 
-	expectedError := fmt.Errorf("request.ext.prebid.schains contains multiple schains for bidder appnexus; it must contain no more than one per bidder.")
+	expectedError := errors.New("request.ext.prebid.schains contains multiple schains for bidder appnexus; it must contain no more than one per bidder.")
 	assert.ElementsMatch(t, errL, []error{expectedError})
 }
 
@@ -1828,6 +1822,260 @@ func TestValidateAndFillSourceTID(t *testing.T) {
 	}
 }
 
+func TestEidPermissionsInvalid(t *testing.T) {
+	deps := &endpointDeps{
+		&nobidExchange{},
+		newParamsValidator(t),
+		&mockStoredReqFetcher{},
+		empty_fetcher.EmptyFetcher{},
+		empty_fetcher.EmptyFetcher{},
+		&config.Configuration{},
+		newTestMetrics(),
+		analyticsConf.NewPBSAnalytics(&config.Analytics{}),
+		map[string]string{},
+		false,
+		[]byte{},
+		openrtb_ext.BuildBidderMap(),
+		nil,
+		nil,
+		hardcodedResponseIPValidator{response: true},
+	}
+
+	ui := uint64(1)
+	req := openrtb.BidRequest{
+		ID: "anyRequestID",
+		Imp: []openrtb.Imp{
+			{
+				ID: "anyImpID",
+				Banner: &openrtb.Banner{
+					W: &ui,
+					H: &ui,
+				},
+				Ext: json.RawMessage(`{"appnexus": {"placementId": 5667}}`),
+			},
+		},
+		Site: &openrtb.Site{
+			ID: "anySiteID",
+		},
+		Ext: json.RawMessage(`{"prebid": {"data": {"eidpermissions": [{"source":"a", "bidders":[]}]} } }`),
+	}
+
+	errL := deps.validateRequest(&req)
+
+	expectedError := errors.New(`request.ext.prebid.data.eidpermissions[0] missing or empty required field: "bidders"`)
+	assert.ElementsMatch(t, errL, []error{expectedError})
+}
+
+func TestValidateEidPermissions(t *testing.T) {
+	knownBidders := map[string]openrtb_ext.BidderName{"a": openrtb_ext.BidderName("a")}
+	knownAliases := map[string]string{"b": "b"}
+
+	testCases := []struct {
+		description   string
+		request       *openrtb_ext.ExtRequest
+		expectedError error
+	}{
+		{
+			description:   "Valid - Nil ext",
+			request:       nil,
+			expectedError: nil,
+		},
+		{
+			description:   "Valid - Empty ext",
+			request:       &openrtb_ext.ExtRequest{},
+			expectedError: nil,
+		},
+		{
+			description:   "Valid - Nil ext.prebid.data",
+			request:       &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{}},
+			expectedError: nil,
+		},
+		{
+			description:   "Valid - Empty ext.prebid.data",
+			request:       &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{}}},
+			expectedError: nil,
+		},
+		{
+			description:   "Valid - Nil ext.prebid.data.eidpermissions",
+			request:       &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{EidPermissions: nil}}},
+			expectedError: nil,
+		},
+		{
+			description:   "Valid - None",
+			request:       &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{}}}},
+			expectedError: nil,
+		},
+		{
+			description: "Valid - One",
+			request: &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{
+				{Source: "sourceA", Bidders: []string{"a"}},
+			}}}},
+			expectedError: nil,
+		},
+		{
+			description: "Valid - Many",
+			request: &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{
+				{Source: "sourceA", Bidders: []string{"a"}},
+				{Source: "sourceB", Bidders: []string{"a"}},
+			}}}},
+			expectedError: nil,
+		},
+		{
+			description: "Invalid - Missing Source",
+			request: &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{
+				{Source: "sourceA", Bidders: []string{"a"}},
+				{Bidders: []string{"a"}},
+			}}}},
+			expectedError: errors.New(`request.ext.prebid.data.eidpermissions[1] missing required field: "source"`),
+		},
+		{
+			description: "Invalid - Duplicate Source",
+			request: &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{
+				{Source: "sourceA", Bidders: []string{"a"}},
+				{Source: "sourceA", Bidders: []string{"a"}},
+			}}}},
+			expectedError: errors.New(`request.ext.prebid.data.eidpermissions[1] duplicate entry with field: "source"`),
+		},
+		{
+			description: "Invalid - Missing Bidders - Nil",
+			request: &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{
+				{Source: "sourceA", Bidders: []string{"a"}},
+				{Source: "sourceB"},
+			}}}},
+			expectedError: errors.New(`request.ext.prebid.data.eidpermissions[1] missing or empty required field: "bidders"`),
+		},
+		{
+			description: "Invalid - Missing Bidders - Empty",
+			request: &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{
+				{Source: "sourceA", Bidders: []string{"a"}},
+				{Source: "sourceB", Bidders: []string{}},
+			}}}},
+			expectedError: errors.New(`request.ext.prebid.data.eidpermissions[1] missing or empty required field: "bidders"`),
+		},
+		{
+			description: "Invalid - Invalid Bidders",
+			request: &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{
+				{Source: "sourceA", Bidders: []string{"a"}},
+				{Source: "sourceB", Bidders: []string{"z"}},
+			}}}},
+			expectedError: errors.New(`request.ext.prebid.data.eidpermissions[1] contains unrecognized bidder "z"`),
+		},
+	}
+
+	endpoint := &endpointDeps{bidderMap: knownBidders}
+	for _, test := range testCases {
+		result := endpoint.validateEidPermissions(test.request, knownAliases)
+		assert.Equal(t, test.expectedError, result, test.description)
+	}
+}
+
+func TestValidateBidders(t *testing.T) {
+	testCases := []struct {
+		description   string
+		bidders       []string
+		knownBidders  map[string]openrtb_ext.BidderName
+		knownAliases  map[string]string
+		expectedError error
+	}{
+		{
+			description:   "Valid - No Bidders",
+			bidders:       []string{},
+			knownBidders:  map[string]openrtb_ext.BidderName{"a": openrtb_ext.BidderName("a")},
+			knownAliases:  map[string]string{"c": "c"},
+			expectedError: nil,
+		},
+		{
+			description:   "Valid - All Bidders",
+			bidders:       []string{"*"},
+			knownBidders:  map[string]openrtb_ext.BidderName{"a": openrtb_ext.BidderName("a")},
+			knownAliases:  map[string]string{"c": "c"},
+			expectedError: nil,
+		},
+		{
+			description:   "Valid - One Core Bidder",
+			bidders:       []string{"a"},
+			knownBidders:  map[string]openrtb_ext.BidderName{"a": openrtb_ext.BidderName("a")},
+			knownAliases:  map[string]string{"c": "c"},
+			expectedError: nil,
+		},
+		{
+			description:   "Valid - Many Core Bidders",
+			bidders:       []string{"a", "b"},
+			knownBidders:  map[string]openrtb_ext.BidderName{"a": openrtb_ext.BidderName("a"), "b": openrtb_ext.BidderName("b")},
+			knownAliases:  map[string]string{"c": "c"},
+			expectedError: nil,
+		},
+		{
+			description:   "Valid - One Alias Bidder",
+			bidders:       []string{"c"},
+			knownBidders:  map[string]openrtb_ext.BidderName{"a": openrtb_ext.BidderName("a")},
+			knownAliases:  map[string]string{"c": "c"},
+			expectedError: nil,
+		},
+		{
+			description:   "Valid - Many Alias Bidders",
+			bidders:       []string{"c", "d"},
+			knownBidders:  map[string]openrtb_ext.BidderName{"a": openrtb_ext.BidderName("a")},
+			knownAliases:  map[string]string{"c": "c", "d": "d"},
+			expectedError: nil,
+		},
+		{
+			description:   "Valid - Mixed Core + Alias Bidders",
+			bidders:       []string{"a", "c"},
+			knownBidders:  map[string]openrtb_ext.BidderName{"a": openrtb_ext.BidderName("a")},
+			knownAliases:  map[string]string{"c": "c"},
+			expectedError: nil,
+		},
+		{
+			description:   "Invalid - Unknown Bidder",
+			bidders:       []string{"z"},
+			knownBidders:  map[string]openrtb_ext.BidderName{"a": openrtb_ext.BidderName("a")},
+			knownAliases:  map[string]string{"c": "c"},
+			expectedError: errors.New(`unrecognized bidder "z"`),
+		},
+		{
+			description:   "Invalid - Unknown Bidder Case Sensitive",
+			bidders:       []string{"A"},
+			knownBidders:  map[string]openrtb_ext.BidderName{"a": openrtb_ext.BidderName("a")},
+			knownAliases:  map[string]string{"c": "c"},
+			expectedError: errors.New(`unrecognized bidder "A"`),
+		},
+		{
+			description:   "Invalid - Unknown Bidder With Known Bidders",
+			bidders:       []string{"a", "c", "z"},
+			knownBidders:  map[string]openrtb_ext.BidderName{"a": openrtb_ext.BidderName("a")},
+			knownAliases:  map[string]string{"c": "c"},
+			expectedError: errors.New(`unrecognized bidder "z"`),
+		},
+		{
+			description:   "Invalid - All Bidders With Known Bidder",
+			bidders:       []string{"*", "a"},
+			knownBidders:  map[string]openrtb_ext.BidderName{"a": openrtb_ext.BidderName("a")},
+			knownAliases:  map[string]string{"c": "c"},
+			expectedError: errors.New(`bidder wildcard "*" mixed with specific bidders`),
+		},
+		{
+			description:   "Invalid - Returns First Error - All Bidders",
+			bidders:       []string{"*", "z"},
+			knownBidders:  map[string]openrtb_ext.BidderName{"a": openrtb_ext.BidderName("a")},
+			knownAliases:  map[string]string{"c": "c"},
+			expectedError: errors.New(`bidder wildcard "*" mixed with specific bidders`),
+		},
+		{
+			description:   "Invalid - Returns First Error - Unknown Bidder",
+			bidders:       []string{"z", "*"},
+			knownBidders:  map[string]openrtb_ext.BidderName{"a": openrtb_ext.BidderName("a")},
+			knownAliases:  map[string]string{"c": "c"},
+			expectedError: errors.New(`unrecognized bidder "z"`),
+		},
+	}
+
+	for _, test := range testCases {
+		result := validateBidders(test.bidders, test.knownBidders, test.knownAliases)
+		assert.Equal(t, test.expectedError, result, test.description)
+	}
+}
+
 // nobidExchange is a well-behaved exchange which always bids "no bid".
 type nobidExchange struct {
 	gotRequest *openrtb.BidRequest
diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go
index c013f587..5ebe1cc6 100644
--- a/exchange/exchange_test.go
+++ b/exchange/exchange_test.go
@@ -1494,6 +1494,7 @@ func newExchangeForTests(t *testing.T, filename string, expectations map[string]
 			}
 		}
 	}
+
 	for alias, coreBidder := range aliases {
 		if spec, ok := expectations[alias]; ok {
 			if bidder, ok := adapters[openrtb_ext.BidderName(coreBidder)]; ok {
@@ -1504,8 +1505,8 @@ func newExchangeForTests(t *testing.T, filename string, expectations map[string]
 					t:             t,
 					fileName:      filename,
 					bidderName:    coreBidder,
-					expectations:  map[string]*bidderRequest{coreBidder: spec.ExpectedRequest},
-					mockResponses: map[string]bidderResponse{coreBidder: spec.MockResponse},
+					expectations:  map[string]*bidderRequest{alias: spec.ExpectedRequest},
+					mockResponses: map[string]bidderResponse{alias: spec.MockResponse},
 				}
 			}
 		}
diff --git a/exchange/exchangetest/eidpermissions-allowed-alias.json b/exchange/exchangetest/eidpermissions-allowed-alias.json
new file mode 100644
index 00000000..24c9af7c
--- /dev/null
+++ b/exchange/exchangetest/eidpermissions-allowed-alias.json
@@ -0,0 +1,123 @@
+{
+    "incomingRequest": {
+        "ortbRequest": {
+            "id": "some-request-id",
+            "site": {
+                "page": "test.somepage.com"
+            },
+            "user": {
+                "ext": {
+                    "eids": [{
+                        "source": "source1",
+                        "id": "anyId"
+                    }]
+                }
+            },
+            "ext": {
+                "prebid": {
+                    "aliases": {
+                        "foo": "appnexus"
+                    },
+                    "data": {
+                        "eidpermissions": [{
+                            "source": "source1",
+                            "bidders": ["foo"]
+                        }]
+                    }
+                }
+            },
+            "imp": [{
+                "id": "my-imp-id",
+                "video": {
+                    "mimes": ["video/mp4"]
+                },
+                "ext": {
+                    "foo": {
+                        "placementId": 1
+                    }
+                }
+            }]
+        }
+    },
+    "outgoingRequests": {
+        "foo": {
+            "expectRequest": {
+                "ortbRequest": {
+                    "id": "some-request-id",
+                    "site": {
+                        "page": "test.somepage.com"
+                    },
+                    "user": {
+                        "ext": {
+                            "eids": [{
+                                "source": "source1",
+                                "id": "anyId"
+                            }]
+                        }
+                    },
+                    "ext": {
+                        "prebid": {
+                            "aliases": {
+                                "foo": "appnexus"
+                            },
+                            "data": {
+                                "eidpermissions": [{
+                                    "source": "source1",
+                                    "bidders": ["foo"]
+                                }]
+                            }
+                        }
+                    },
+                    "imp": [{
+                        "id": "my-imp-id",
+                        "video": {
+                            "mimes": ["video/mp4"]
+                        },
+                        "ext": {
+                            "bidder": {
+                                "placementId": 1
+                            }
+                        }
+                    }]
+                },
+                "bidAdjustment": 1.0
+            },
+            "mockResponse": {
+                "pbsSeatBid": {
+                    "pbsBids": [{
+                        "ortbBid": {
+                            "id": "apn-bid",
+                            "impid": "my-imp-id",
+                            "price": 0.3,
+                            "w": 200,
+                            "h": 250,
+                            "crid": "creative-1"
+                        },
+                        "bidType": "video"
+                    }]
+                }
+            }
+        }
+    },
+    "response": {
+        "bids": {
+            "id": "some-request-id",
+            "seatbid": [{
+                "seat": "foo",
+                "bid": [{
+                    "id": "apn-bid",
+                    "impid": "my-imp-id",
+                    "price": 0.3,
+                    "w": 200,
+                    "h": 250,
+                    "crid": "creative-1",
+                    "ext": {
+                        "prebid": {
+                            "type": "video"
+                        }
+                    }
+                }]
+            }]
+        }
+    }
+}
\ No newline at end of file
diff --git a/exchange/exchangetest/eidpermissions-allowed.json b/exchange/exchangetest/eidpermissions-allowed.json
new file mode 100644
index 00000000..851c98dd
--- /dev/null
+++ b/exchange/exchangetest/eidpermissions-allowed.json
@@ -0,0 +1,117 @@
+{
+    "incomingRequest": {
+        "ortbRequest": {
+            "id": "some-request-id",
+            "site": {
+                "page": "test.somepage.com"
+            },
+            "user": {
+                "ext": {
+                    "eids": [{
+                        "source": "source1",
+                        "id": "anyId"
+                    }]
+                }
+            },
+            "ext": {
+                "prebid": {
+                    "data": {
+                        "eidpermissions": [{
+                            "source": "source1",
+                            "bidders": ["appnexus"]
+                        }]
+                    }
+                }
+            },
+            "imp": [{
+                "id": "my-imp-id",
+                "video": {
+                    "mimes": ["video/mp4"]
+                },
+                "ext": {
+                    "appnexus": {
+                        "placementId": 1
+                    }
+                }
+            }]
+        }
+    },
+    "outgoingRequests": {
+        "appnexus": {
+            "expectRequest": {
+                "ortbRequest": {
+                    "id": "some-request-id",
+                    "site": {
+                        "page": "test.somepage.com"
+                    },
+                    "user": {
+                        "ext": {
+                            "eids": [{
+                                "source": "source1",
+                                "id": "anyId"
+                            }]
+                        }
+                    },
+                    "ext": {
+                        "prebid": {
+                            "data": {
+                                "eidpermissions": [{
+                                    "source": "source1",
+                                    "bidders": ["appnexus"]
+                                }]
+                            }
+                        }
+                    },
+                    "imp": [{
+                        "id": "my-imp-id",
+                        "video": {
+                            "mimes": ["video/mp4"]
+                        },
+                        "ext": {
+                            "bidder": {
+                                "placementId": 1
+                            }
+                        }
+                    }]
+                },
+                "bidAdjustment": 1.0
+            },
+            "mockResponse": {
+                "pbsSeatBid": {
+                    "pbsBids": [{
+                        "ortbBid": {
+                            "id": "apn-bid",
+                            "impid": "my-imp-id",
+                            "price": 0.3,
+                            "w": 200,
+                            "h": 250,
+                            "crid": "creative-1"
+                        },
+                        "bidType": "video"
+                    }]
+                }
+            }
+        }
+    },
+    "response": {
+        "bids": {
+            "id": "some-request-id",
+            "seatbid": [{
+                "seat": "appnexus",
+                "bid": [{
+                    "id": "apn-bid",
+                    "impid": "my-imp-id",
+                    "price": 0.3,
+                    "w": 200,
+                    "h": 250,
+                    "crid": "creative-1",
+                    "ext": {
+                        "prebid": {
+                            "type": "video"
+                        }
+                    }
+                }]
+            }]
+        }
+    }
+}
\ No newline at end of file
diff --git a/exchange/exchangetest/eidpermissions-denied.json b/exchange/exchangetest/eidpermissions-denied.json
new file mode 100644
index 00000000..38127191
--- /dev/null
+++ b/exchange/exchangetest/eidpermissions-denied.json
@@ -0,0 +1,111 @@
+{
+    "incomingRequest": {
+        "ortbRequest": {
+            "id": "some-request-id",
+            "site": {
+                "page": "test.somepage.com"
+            },
+            "user": {
+                "ext": {
+                    "eids": [{
+                        "source": "source1",
+                        "id": "anyId"
+                    }]
+                }
+            },
+            "ext": {
+                "prebid": {
+                    "data": {
+                        "eidpermissions": [{
+                            "source": "source1",
+                            "bidders": ["otherBidder"]
+                        }]
+                    }
+                }
+            },
+            "imp": [{
+                "id": "my-imp-id",
+                "video": {
+                    "mimes": ["video/mp4"]
+                },
+                "ext": {
+                    "appnexus": {
+                        "placementId": 1
+                    }
+                }
+            }]
+        }
+    },
+    "outgoingRequests": {
+        "appnexus": {
+            "expectRequest": {
+                "ortbRequest": {
+                    "id": "some-request-id",
+                    "site": {
+                        "page": "test.somepage.com"
+                    },
+                    "user": {
+                    },
+                    "ext": {
+                        "prebid": {
+                            "data": {
+                                "eidpermissions": [{
+                                    "source": "source1",
+                                    "bidders": ["otherBidder"]
+                                }]
+                            }
+                        }
+                    },
+                    "imp": [{
+                        "id": "my-imp-id",
+                        "video": {
+                            "mimes": ["video/mp4"]
+                        },
+                        "ext": {
+                            "bidder": {
+                                "placementId": 1
+                            }
+                        }
+                    }]
+                },
+                "bidAdjustment": 1.0
+            },
+            "mockResponse": {
+                "pbsSeatBid": {
+                    "pbsBids": [{
+                        "ortbBid": {
+                            "id": "apn-bid",
+                            "impid": "my-imp-id",
+                            "price": 0.3,
+                            "w": 200,
+                            "h": 250,
+                            "crid": "creative-1"
+                        },
+                        "bidType": "video"
+                    }]
+                }
+            }
+        }
+    },
+    "response": {
+        "bids": {
+            "id": "some-request-id",
+            "seatbid": [{
+                "seat": "appnexus",
+                "bid": [{
+                    "id": "apn-bid",
+                    "impid": "my-imp-id",
+                    "price": 0.3,
+                    "w": 200,
+                    "h": 250,
+                    "crid": "creative-1",
+                    "ext": {
+                        "prebid": {
+                            "type": "video"
+                        }
+                    }
+                }]
+            }]
+        }
+    }
+}
\ No newline at end of file
diff --git a/exchange/utils_test.go b/exchange/utils_test.go
index f9a04f25..6dab5b00 100644
--- a/exchange/utils_test.go
+++ b/exchange/utils_test.go
@@ -20,7 +20,8 @@ import (
 //
 // It only allows appnexus for GDPR consent
 type permissionsMock struct {
-	personalInfoAllowed bool
+	personalInfoAllowed      bool
+	personalInfoAllowedError error
 }
 
 func (p *permissionsMock) HostCookiesAllowed(ctx context.Context, consent string) (bool, error) {
@@ -32,7 +33,7 @@ func (p *permissionsMock) BidderSyncAllowed(ctx context.Context, bidder openrtb_
 }
 
 func (p *permissionsMock) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdpr gdpr.Signal, consent string) (bool, bool, bool, error) {
-	return p.personalInfoAllowed, p.personalInfoAllowed, p.personalInfoAllowed, nil
+	return p.personalInfoAllowed, p.personalInfoAllowed, p.personalInfoAllowed, p.personalInfoAllowedError
 }
 
 func assertReq(t *testing.T, bidderRequests []BidderRequest,
@@ -1045,6 +1046,8 @@ func TestCleanOpenRTBRequestsLMT(t *testing.T) {
 }
 
 func TestCleanOpenRTBRequestsGDPR(t *testing.T) {
+	tcf1Consent := "BONV8oqONXwgmADACHENAO7pqzAAppY"
+	tcf2Consent := "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA"
 	trueValue, falseValue := true, false
 
 	testCases := []struct {
@@ -1054,6 +1057,7 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) {
 		gdpr                string
 		gdprConsent         string
 		gdprScrub           bool
+		permissionsError    error
 		userSyncIfAmbiguous bool
 		expectPrivacyLabels metrics.PrivacyLabels
 	}{
@@ -1074,7 +1078,7 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) {
 			gdprAccountEnabled: &trueValue,
 			gdprHostEnabled:    true,
 			gdpr:               "1",
-			gdprConsent:        "BONV8oqONXwgmADACHENAO7pqzAAppY",
+			gdprConsent:        tcf1Consent,
 			gdprScrub:          true,
 			expectPrivacyLabels: metrics.PrivacyLabels{
 				GDPREnforced:   true,
@@ -1086,7 +1090,7 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) {
 			gdprAccountEnabled: &trueValue,
 			gdprHostEnabled:    true,
 			gdpr:               "1",
-			gdprConsent:        "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA",
+			gdprConsent:        tcf2Consent,
 			gdprScrub:          true,
 			expectPrivacyLabels: metrics.PrivacyLabels{
 				GDPREnforced:   true,
@@ -1098,7 +1102,7 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) {
 			gdprAccountEnabled: &trueValue,
 			gdprHostEnabled:    true,
 			gdpr:               "0",
-			gdprConsent:        "BONV8oqONXwgmADACHENAO7pqzAAppY",
+			gdprConsent:        tcf1Consent,
 			gdprScrub:          false,
 			expectPrivacyLabels: metrics.PrivacyLabels{
 				GDPREnforced:   false,
@@ -1110,7 +1114,7 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) {
 			gdprAccountEnabled: &trueValue,
 			gdprHostEnabled:    false,
 			gdpr:               "1",
-			gdprConsent:        "BONV8oqONXwgmADACHENAO7pqzAAppY",
+			gdprConsent:        tcf1Consent,
 			gdprScrub:          true,
 			expectPrivacyLabels: metrics.PrivacyLabels{
 				GDPREnforced:   true,
@@ -1122,7 +1126,7 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) {
 			gdprAccountEnabled: &falseValue,
 			gdprHostEnabled:    true,
 			gdpr:               "1",
-			gdprConsent:        "BONV8oqONXwgmADACHENAO7pqzAAppY",
+			gdprConsent:        tcf1Consent,
 			gdprScrub:          false,
 			expectPrivacyLabels: metrics.PrivacyLabels{
 				GDPREnforced:   false,
@@ -1134,7 +1138,7 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) {
 			gdprAccountEnabled: nil,
 			gdprHostEnabled:    true,
 			gdpr:               "1",
-			gdprConsent:        "BONV8oqONXwgmADACHENAO7pqzAAppY",
+			gdprConsent:        tcf1Consent,
 			gdprScrub:          true,
 			expectPrivacyLabels: metrics.PrivacyLabels{
 				GDPREnforced:   true,
@@ -1146,7 +1150,7 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) {
 			gdprAccountEnabled: nil,
 			gdprHostEnabled:    false,
 			gdpr:               "1",
-			gdprConsent:        "BONV8oqONXwgmADACHENAO7pqzAAppY",
+			gdprConsent:        tcf1Consent,
 			gdprScrub:          false,
 			expectPrivacyLabels: metrics.PrivacyLabels{
 				GDPREnforced:   false,
@@ -1158,7 +1162,7 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) {
 			gdprAccountEnabled:  nil,
 			gdprHostEnabled:     true,
 			gdpr:                "null",
-			gdprConsent:         "BONV8oqONXwgmADACHENAO7pqzAAppY",
+			gdprConsent:         tcf1Consent,
 			gdprScrub:           true,
 			userSyncIfAmbiguous: false,
 			expectPrivacyLabels: metrics.PrivacyLabels{
@@ -1171,7 +1175,7 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) {
 			gdprAccountEnabled:  nil,
 			gdprHostEnabled:     true,
 			gdpr:                "null",
-			gdprConsent:         "BONV8oqONXwgmADACHENAO7pqzAAppY",
+			gdprConsent:         tcf1Consent,
 			gdprScrub:           false,
 			userSyncIfAmbiguous: true,
 			expectPrivacyLabels: metrics.PrivacyLabels{
@@ -1179,6 +1183,19 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) {
 				GDPRTCFVersion: "",
 			},
 		},
+		{
+			description:        "Enforce - error while checking if personal info is allowed",
+			gdprAccountEnabled: nil,
+			gdprHostEnabled:    true,
+			gdpr:               "1",
+			gdprConsent:        tcf1Consent,
+			gdprScrub:          true,
+			permissionsError:   errors.New("Some error"),
+			expectPrivacyLabels: metrics.PrivacyLabels{
+				GDPREnforced:   true,
+				GDPRTCFVersion: metrics.TCFVersionV1,
+			},
+		},
 	}
 
 	for _, test := range testCases {
@@ -1214,7 +1231,7 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) {
 			context.Background(),
 			auctionReq,
 			nil,
-			&permissionsMock{personalInfoAllowed: !test.gdprScrub},
+			&permissionsMock{personalInfoAllowed: !test.gdprScrub, personalInfoAllowedError: test.permissionsError},
 			test.userSyncIfAmbiguous,
 			privacyConfig)
 		result := results[0]
@@ -1433,3 +1450,254 @@ func TestBidderToPrebidChainsZeroLengthSChains(t *testing.T) {
 	assert.Nil(t, err)
 	assert.Equal(t, len(output), 0)
 }
+
+func TestRemoveUnpermissionedEids(t *testing.T) {
+	bidder := "bidderA"
+
+	testCases := []struct {
+		description     string
+		userExt         json.RawMessage
+		eidPermissions  []openrtb_ext.ExtRequestPrebidDataEidPermission
+		expectedUserExt json.RawMessage
+	}{
+		{
+			description: "Extension Nil",
+			userExt:     nil,
+			eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{
+				{Source: "source1", Bidders: []string{"bidderA"}},
+			},
+			expectedUserExt: nil,
+		},
+		{
+			description: "Extension Empty",
+			userExt:     json.RawMessage(`{}`),
+			eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{
+				{Source: "source1", Bidders: []string{"bidderA"}},
+			},
+			expectedUserExt: json.RawMessage(`{}`),
+		},
+		{
+			description: "Extension Empty - Keep Other Data",
+			userExt:     json.RawMessage(`{"other":42}`),
+			eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{
+				{Source: "source1", Bidders: []string{"bidderA"}},
+			},
+			expectedUserExt: json.RawMessage(`{"other":42}`),
+		},
+		{
+			description: "Eids Empty",
+			userExt:     json.RawMessage(`{"eids":[]}`),
+			eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{
+				{Source: "source1", Bidders: []string{"bidderA"}},
+			},
+			expectedUserExt: json.RawMessage(`{"eids":[]}`),
+		},
+		{
+			description: "Eids Empty - Keep Other Data",
+			userExt:     json.RawMessage(`{"eids":[],"other":42}`),
+			eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{
+				{Source: "source1", Bidders: []string{"bidderA"}},
+			},
+			expectedUserExt: json.RawMessage(`{"eids":[],"other":42}`),
+		},
+		{
+			description:     "Allowed By Nil Permissions",
+			userExt:         json.RawMessage(`{"eids":[{"source":"source1","id":"anyID"}]}`),
+			eidPermissions:  nil,
+			expectedUserExt: json.RawMessage(`{"eids":[{"source":"source1","id":"anyID"}]}`),
+		},
+		{
+			description:     "Allowed By Empty Permissions",
+			userExt:         json.RawMessage(`{"eids":[{"source":"source1","id":"anyID"}]}`),
+			eidPermissions:  []openrtb_ext.ExtRequestPrebidDataEidPermission{},
+			expectedUserExt: json.RawMessage(`{"eids":[{"source":"source1","id":"anyID"}]}`),
+		},
+		{
+			description: "Allowed By Specific Bidder",
+			userExt:     json.RawMessage(`{"eids":[{"source":"source1","id":"anyID"}]}`),
+			eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{
+				{Source: "source1", Bidders: []string{"bidderA"}},
+			},
+			expectedUserExt: json.RawMessage(`{"eids":[{"source":"source1","id":"anyID"}]}`),
+		},
+		{
+			description: "Allowed By All Bidders",
+			userExt:     json.RawMessage(`{"eids":[{"source":"source1","id":"anyID"}]}`),
+			eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{
+				{Source: "source1", Bidders: []string{"*"}},
+			},
+			expectedUserExt: json.RawMessage(`{"eids":[{"source":"source1","id":"anyID"}]}`),
+		},
+		{
+			description: "Allowed By Lack Of Matching Source",
+			userExt:     json.RawMessage(`{"eids":[{"source":"source1","id":"anyID"}]}`),
+			eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{
+				{Source: "source2", Bidders: []string{"otherBidder"}},
+			},
+			expectedUserExt: json.RawMessage(`{"eids":[{"source":"source1","id":"anyID"}]}`),
+		},
+		{
+			description: "Allowed - Keep Other Data",
+			userExt:     json.RawMessage(`{"eids":[{"source":"source1","id":"anyID"}],"other":42}`),
+			eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{
+				{Source: "source1", Bidders: []string{"bidderA"}},
+			},
+			expectedUserExt: json.RawMessage(`{"eids":[{"source":"source1","id":"anyID"}],"other":42}`),
+		},
+		{
+			description: "Denied",
+			userExt:     json.RawMessage(`{"eids":[{"source":"source1","id":"anyID"}]}`),
+			eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{
+				{Source: "source1", Bidders: []string{"otherBidder"}},
+			},
+			expectedUserExt: nil,
+		},
+		{
+			description: "Denied - Keep Other Data",
+			userExt:     json.RawMessage(`{"eids":[{"source":"source1","id":"anyID"}],"otherdata":42}`),
+			eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{
+				{Source: "source1", Bidders: []string{"otherBidder"}},
+			},
+			expectedUserExt: json.RawMessage(`{"otherdata":42}`),
+		},
+		{
+			description: "Mix Of Allowed By Specific Bidder, Allowed By Lack Of Matching Source, Denied, Keep Other Data",
+			userExt:     json.RawMessage(`{"eids":[{"source":"source1","id":"anyID"},{"source":"source2","id":"anyID"},{"source":"source3","id":"anyID"}],"other":42}`),
+			eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{
+				{Source: "source1", Bidders: []string{"bidderA"}},
+				{Source: "source3", Bidders: []string{"otherBidder"}},
+			},
+			expectedUserExt: json.RawMessage(`{"eids":[{"source":"source1","id":"anyID"},{"source":"source2","id":"anyID"}],"other":42}`),
+		},
+	}
+
+	for _, test := range testCases {
+		request := &openrtb.BidRequest{
+			User: &openrtb.User{Ext: test.userExt},
+		}
+
+		requestExt := &openrtb_ext.ExtRequest{
+			Prebid: openrtb_ext.ExtRequestPrebid{
+				Data: &openrtb_ext.ExtRequestPrebidData{
+					EidPermissions: test.eidPermissions,
+				},
+			},
+		}
+
+		expectedRequest := &openrtb.BidRequest{
+			User: &openrtb.User{Ext: test.expectedUserExt},
+		}
+
+		resultErr := removeUnpermissionedEids(request, bidder, requestExt)
+		assert.NoError(t, resultErr, test.description)
+		assert.Equal(t, expectedRequest, request, test.description)
+	}
+}
+
+func TestRemoveUnpermissionedEidsUnmarshalErrors(t *testing.T) {
+	testCases := []struct {
+		description string
+		userExt     json.RawMessage
+		expectedErr string
+	}{
+		{
+			description: "Malformed Ext",
+			userExt:     json.RawMessage(`malformed`),
+			expectedErr: "invalid character 'm' looking for beginning of value",
+		},
+		{
+			description: "Malformed Eid Array Type",
+			userExt:     json.RawMessage(`{"eids":[42]}`),
+			expectedErr: "json: cannot unmarshal number into Go value of type openrtb_ext.ExtUserEid",
+		},
+		{
+			description: "Malformed Eid Item Type",
+			userExt:     json.RawMessage(`{"eids":[{"source":42,"id":"anyID"}]}`),
+			expectedErr: "json: cannot unmarshal number into Go struct field ExtUserEid.source of type string",
+		},
+	}
+
+	for _, test := range testCases {
+		request := &openrtb.BidRequest{
+			User: &openrtb.User{Ext: test.userExt},
+		}
+
+		requestExt := &openrtb_ext.ExtRequest{
+			Prebid: openrtb_ext.ExtRequestPrebid{
+				Data: &openrtb_ext.ExtRequestPrebidData{
+					EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{
+						{Source: "source1", Bidders: []string{"*"}},
+					},
+				},
+			},
+		}
+
+		resultErr := removeUnpermissionedEids(request, "bidderA", requestExt)
+		assert.EqualError(t, resultErr, test.expectedErr, test.description)
+	}
+}
+
+func TestRemoveUnpermissionedEidsEmptyValidations(t *testing.T) {
+	testCases := []struct {
+		description string
+		request     *openrtb.BidRequest
+		requestExt  *openrtb_ext.ExtRequest
+	}{
+		{
+			description: "Nil User",
+			request: &openrtb.BidRequest{
+				User: nil,
+			},
+			requestExt: &openrtb_ext.ExtRequest{
+				Prebid: openrtb_ext.ExtRequestPrebid{
+					Data: &openrtb_ext.ExtRequestPrebidData{
+						EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{
+							{Source: "source1", Bidders: []string{"*"}},
+						},
+					},
+				},
+			},
+		},
+		{
+			description: "Empty User",
+			request: &openrtb.BidRequest{
+				User: &openrtb.User{},
+			},
+			requestExt: &openrtb_ext.ExtRequest{
+				Prebid: openrtb_ext.ExtRequestPrebid{
+					Data: &openrtb_ext.ExtRequestPrebidData{
+						EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{
+							{Source: "source1", Bidders: []string{"*"}},
+						},
+					},
+				},
+			},
+		},
+		{
+			description: "Nil Ext",
+			request: &openrtb.BidRequest{
+				User: &openrtb.User{Ext: json.RawMessage(`{"eids":[{"source":"source1","id":"anyID"}]}`)},
+			},
+			requestExt: nil,
+		},
+		{
+			description: "Nil Prebid Data",
+			request: &openrtb.BidRequest{
+				User: &openrtb.User{Ext: json.RawMessage(`{"eids":[{"source":"source1","id":"anyID"}]}`)},
+			},
+			requestExt: &openrtb_ext.ExtRequest{
+				Prebid: openrtb_ext.ExtRequestPrebid{
+					Data: nil,
+				},
+			},
+		},
+	}
+
+	for _, test := range testCases {
+		requestExpected := *test.request
+
+		resultErr := removeUnpermissionedEids(test.request, "bidderA", test.requestExt)
+		assert.NoError(t, resultErr, test.description+":err")
+		assert.Equal(t, &requestExpected, test.request, test.description+":request")
+	}
+}
diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go
index 60ab2478..bca8623d 100755
--- a/usersync/usersyncers/syncer_test.go
+++ b/usersync/usersyncers/syncer_test.go
@@ -59,6 +59,7 @@ func TestNewSyncerMap(t *testing.T) {
 			string(openrtb_ext.BidderLogicad):          syncConfig,
 			string(openrtb_ext.BidderLunaMedia):        syncConfig,
 			string(openrtb_ext.BidderMarsmedia):        syncConfig,
+			string(openrtb_ext.BidderMediafuse):        syncConfig,
 			string(openrtb_ext.BidderMgid):             syncConfig,
 			string(openrtb_ext.BidderNanoInteractive):  syncConfig,
 			string(openrtb_ext.BidderNinthDecimal):     syncConfig,
@@ -97,6 +98,8 @@ func TestNewSyncerMap(t *testing.T) {
 		openrtb_ext.BidderAdgeneration: true,
 		openrtb_ext.BidderAdhese:       true,
 		openrtb_ext.BidderAdoppler:     true,
+		openrtb_ext.BidderAdot:         true,
+		openrtb_ext.BidderAdprime:      true,
 		openrtb_ext.BidderApplogy:      true,
 		openrtb_ext.BidderInMobi:       true,
 		openrtb_ext.BidderKidoz:        true,
@@ -105,11 +108,11 @@ func TestNewSyncerMap(t *testing.T) {
 		openrtb_ext.BidderMobileFuse:   true,
 		openrtb_ext.BidderOrbidder:     true,
 		openrtb_ext.BidderPubnative:    true,
+		openrtb_ext.BidderRevcontent:   true,
 		openrtb_ext.BidderSilverMob:    true,
 		openrtb_ext.BidderSmaato:       true,
 		openrtb_ext.BidderTappx:        true,
 		openrtb_ext.BidderYeahmobi:     true,
-		openrtb_ext.BidderAdprime:      true,
 	}
 
 	for bidder, config := range cfg.Adapters {