Repository: prebid/prebid-server
Pull request #2117 (2021-12-22 07:44:39)

Remove Adagio adapter
This PR is just about to remove the Adagio adapter.

Thanks

SyntaxNode: The Adagio adapter was added to the Prebid Server project just 6 months ago. I'm sorry to see you leave. Could you please fill us in on your decision to remove your adapter?
osazos: Hi @SyntaxNode, 

We made this choice due the lack of consistency of Prebid Server integration by each owners. Actually we have to spent too much time to try to get a correct implementation of our adapter, which is even not always possible.

mansinahar: @osazos You will also have to add an entry for Adagio in this `disabledBidders` map here: https://github.com/prebid/prebid-server/blob/master/exchange/adapter_util.go#L81
osazos: Hi @mansinahar, it's done.

Patch:
diff --git a/adapters/adagio/adagio.go b/adapters/adagio/adagio.go
deleted file mode 100644
index 0da4d6ac..00000000
--- a/adapters/adagio/adagio.go
+++ /dev/null
@@ -1,139 +0,0 @@
-package adagio
-
-import (
-	"bytes"
-	"compress/gzip"
-	"encoding/json"
-	"fmt"
-	"github.com/mxmCherry/openrtb/v15/openrtb2"
-	"github.com/prebid/prebid-server/adapters"
-	"github.com/prebid/prebid-server/config"
-	"github.com/prebid/prebid-server/errortypes"
-	"github.com/prebid/prebid-server/openrtb_ext"
-	"net/http"
-)
-
-// Builder builds a new instance of the Adagio 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
-}
-
-type adapter struct {
-	endpoint string
-}
-
-type extBid struct {
-	Prebid *openrtb_ext.ExtBidPrebid
-}
-
-// MakeRequests prepares the HTTP requests which should be made to fetch bids.
-func (a *adapter) MakeRequests(request *openrtb2.BidRequest, _ *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
-	json, err := json.Marshal(request)
-	if err != nil {
-		return nil, []error{err}
-	}
-
-	headers := http.Header{}
-	headers.Add("Content-Type", "application/json;charset=utf-8")
-	headers.Add("Accept", "application/json")
-
-	if request.Device != nil {
-		if len(request.Device.IPv6) > 0 {
-			headers.Add("X-Forwarded-For", request.Device.IPv6)
-		}
-		if len(request.Device.IP) > 0 {
-			headers.Add("X-Forwarded-For", request.Device.IP)
-		}
-	}
-
-	if request.Test == 0 {
-		// Gzip the body
-		// Note: Gzipping could be handled natively later: https://github.com/prebid/prebid-server/issues/1812
-		var bodyBuf bytes.Buffer
-		gz := gzip.NewWriter(&bodyBuf)
-		if _, err = gz.Write(json); err == nil {
-			if err = gz.Close(); err == nil {
-				json = bodyBuf.Bytes()
-				headers.Add("Content-Encoding", "gzip")
-				// /!\ Go already sets the `Accept-Encoding: gzip` header. Never add it manually, or Go won't decompress the response.
-				//headers.Add("Accept-Encoding", "gzip")
-			}
-		}
-	}
-
-	requestToBidder := &adapters.RequestData{
-		Method:  "POST",
-		Uri:     a.endpoint,
-		Body:    json,
-		Headers: headers,
-	}
-
-	return []*adapters.RequestData{requestToBidder}, nil
-}
-
-const unexpectedStatusCodeFormat = "Unexpected status code: %d. Run with request.debug = 1 for more info"
-
-// MakeBids unpacks the server's response into Bids.
-func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, _ *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
-	switch response.StatusCode {
-	case http.StatusOK:
-		break
-	case http.StatusNoContent:
-		return nil, nil
-	case http.StatusServiceUnavailable:
-		fallthrough
-	case http.StatusBadRequest:
-		fallthrough
-	case http.StatusUnauthorized:
-		fallthrough
-	case http.StatusForbidden:
-		err := &errortypes.BadInput{
-			Message: fmt.Sprintf(unexpectedStatusCodeFormat, response.StatusCode),
-		}
-		return nil, []error{err}
-	default:
-		err := &errortypes.BadServerResponse{
-			Message: fmt.Sprintf(unexpectedStatusCodeFormat, response.StatusCode),
-		}
-		return nil, []error{err}
-	}
-
-	var openRTBBidderResponse openrtb2.BidResponse
-	if err := json.Unmarshal(response.Body, &openRTBBidderResponse); err != nil {
-		return nil, []error{err}
-	}
-
-	bidsCapacity := len(internalRequest.Imp)
-	errs := make([]error, 0, bidsCapacity)
-	bidderResponse := adapters.NewBidderResponseWithBidsCapacity(bidsCapacity)
-	var typedBid *adapters.TypedBid
-	for _, seatBid := range openRTBBidderResponse.SeatBid {
-		for _, bid := range seatBid.Bid {
-			activeBid := bid
-
-			activeExt := &extBid{}
-			if err := json.Unmarshal(activeBid.Ext, activeExt); err != nil {
-				errs = append(errs, err)
-			}
-
-			var bidType openrtb_ext.BidType
-			if activeExt.Prebid != nil && activeExt.Prebid.Type != "" {
-				bidType = activeExt.Prebid.Type
-			} else {
-				err := &errortypes.BadServerResponse{
-					Message: fmt.Sprintf("Failed to find native/banner/video mediaType \"%s\" ", activeBid.ImpID),
-				}
-				errs = append(errs, err)
-				continue
-			}
-
-			typedBid = &adapters.TypedBid{Bid: &activeBid, BidType: bidType}
-			bidderResponse.Bids = append(bidderResponse.Bids, typedBid)
-		}
-	}
-
-	return bidderResponse, nil
-}
diff --git a/adapters/adyoulike/adyoulike.go b/adapters/adyoulike/adyoulike.go
index 4cccd5dc..17c42e4a 100644
--- a/adapters/adyoulike/adyoulike.go
+++ b/adapters/adyoulike/adyoulike.go
@@ -2,16 +2,15 @@ package adyoulike
 
 import (
 	"encoding/json"
-	"errors"
 	"fmt"
-	"github.com/prebid/prebid-server/config"
-	"github.com/prebid/prebid-server/openrtb_ext"
 	"net/http"
 
 	"github.com/buger/jsonparser"
 	"github.com/mxmCherry/openrtb/v15/openrtb2"
 	"github.com/prebid/prebid-server/adapters"
+	"github.com/prebid/prebid-server/config"
 	"github.com/prebid/prebid-server/errortypes"
+	"github.com/prebid/prebid-server/openrtb_ext"
 )
 
 func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) {
@@ -88,7 +87,7 @@ func (a *adapter) MakeBids(
 	case http.StatusOK:
 		break
 	case http.StatusNoContent:
-		return nil, []error{errors.New("MakeBids error: No Content")}
+		return nil, nil
 	case http.StatusBadRequest:
 		err := &errortypes.BadInput{
 			Message: fmt.Sprintf(unexpectedStatusCodeFormat, bidderRawResponse.StatusCode),
diff --git a/adapters/algorix/algorix.go b/adapters/algorix/algorix.go
index 628207d6..d24521b4 100644
--- a/adapters/algorix/algorix.go
+++ b/adapters/algorix/algorix.go
@@ -19,6 +19,14 @@ type adapter struct {
 	EndpointTemplate *template.Template
 }
 
+type algorixVideoExt struct {
+	Rewarded int `json:"rewarded"`
+}
+
+type algorixResponseBidExt struct {
+	MediaType string `json:"mediaType"`
+}
+
 // Builder builds a new instance of the AlgoriX adapter for the given bidder with the given config.
 func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) {
 	endpoint, err := template.New("endpointTemplate").Parse(config.Endpoint)
@@ -121,6 +129,19 @@ func preProcess(request *openrtb2.BidRequest) {
 				request.Imp[i].Banner = &banner
 			}
 		}
+		if request.Imp[i].Video != nil {
+			var impExt adapters.ExtImpBidder
+			err := json.Unmarshal(request.Imp[i].Ext, &impExt)
+			if err != nil {
+				continue
+			}
+			if impExt.Prebid != nil && impExt.Prebid.IsRewardedInventory == 1 {
+				videoCopy := *request.Imp[i].Video
+				videoExt := algorixVideoExt{Rewarded: 1}
+				videoCopy.Ext, err = json.Marshal(&videoExt)
+				request.Imp[i].Video = &videoCopy
+			}
+		}
 	}
 }
 
@@ -147,32 +168,58 @@ func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest
 	}
 
 	bidResponse := adapters.NewBidderResponseWithBidsCapacity(1)
+	var errs []error
 
 	for _, seatBid := range bidResp.SeatBid {
 		for idx := range seatBid.Bid {
-			mediaType := getBidType(seatBid.Bid[idx].ImpID, internalRequest.Imp)
-			bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
-				Bid:     &seatBid.Bid[idx],
-				BidType: mediaType,
-			})
+			mediaType, err := getBidType(seatBid.Bid[idx], internalRequest.Imp)
+			if err != nil {
+				errs = append(errs, err)
+			} else {
+				bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
+					Bid:     &seatBid.Bid[idx],
+					BidType: mediaType,
+				})
+			}
 		}
 	}
-	return bidResponse, nil
+
+	return bidResponse, errs
 }
 
-func getBidType(impId string, imps []openrtb2.Imp) openrtb_ext.BidType {
+func getBidType(bid openrtb2.Bid, imps []openrtb2.Imp) (openrtb_ext.BidType, error) {
+	var bidExt algorixResponseBidExt
+	err := json.Unmarshal(bid.Ext, &bidExt)
+	if err == nil {
+		switch bidExt.MediaType {
+		case "banner":
+			return openrtb_ext.BidTypeBanner, nil
+		case "native":
+			return openrtb_ext.BidTypeNative, nil
+		case "video":
+			return openrtb_ext.BidTypeVideo, nil
+		}
+	}
+	var mediaType openrtb_ext.BidType
+	var typeCnt = 0
 	for _, imp := range imps {
-		if imp.ID == impId {
+		if imp.ID == bid.ImpID {
 			if imp.Banner != nil {
-				break
+				typeCnt += 1
+				mediaType = openrtb_ext.BidTypeBanner
 			}
 			if imp.Native != nil {
-				return openrtb_ext.BidTypeNative
+				typeCnt += 1
+				mediaType = openrtb_ext.BidTypeNative
 			}
 			if imp.Video != nil {
-				return openrtb_ext.BidTypeVideo
+				typeCnt += 1
+				mediaType = openrtb_ext.BidTypeVideo
 			}
 		}
 	}
-	return openrtb_ext.BidTypeBanner
+	if typeCnt == 1 {
+		return mediaType, nil
+	}
+	return mediaType, fmt.Errorf("unable to fetch mediaType in multi-format: %s", bid.ImpID)
 }
diff --git a/adapters/vidoomy/vidoomy.go b/adapters/vidoomy/vidoomy.go
new file mode 100644
index 00000000..57fbb09d
--- /dev/null
+++ b/adapters/vidoomy/vidoomy.go
@@ -0,0 +1,173 @@
+package vidoomy
+
+import (
+	"encoding/json"
+	"fmt"
+	"github.com/mxmCherry/openrtb/v15/openrtb2"
+	"github.com/prebid/prebid-server/adapters"
+	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/errortypes"
+	"github.com/prebid/prebid-server/openrtb_ext"
+	"net/http"
+)
+
+type adapter struct {
+	endpoint string
+}
+
+func (a adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+	var errors []error
+
+	reqs := make([]*adapters.RequestData, 0, len(request.Imp))
+
+	header := getHeaders(request)
+	for _, imp := range request.Imp {
+
+		// Split up multi-impression requests into multiple requests so that
+		// each split request is only associated to a single impression
+		reqCopy := *request
+		reqCopy.Imp = []openrtb2.Imp{imp}
+
+		if err := changeRequestForBidService(&reqCopy); err != nil {
+			errors = append(errors, err)
+			continue
+		}
+
+		reqJSON, err := json.Marshal(&reqCopy)
+		if err != nil {
+			errors = append(errors, err)
+			continue
+		}
+
+		reqs = append(reqs, &adapters.RequestData{
+			Method:  "POST",
+			Uri:     a.endpoint,
+			Body:    reqJSON,
+			Headers: header,
+		})
+	}
+
+	return reqs, errors
+}
+
+func getHeaders(request *openrtb2.BidRequest) http.Header {
+	headers := http.Header{}
+
+	headers.Add("Content-Type", "application/json;charset=utf-8")
+	headers.Add("Accept", "application/json")
+	headers.Add("x-openrtb-version", "2.5")
+
+	if request.Device == nil {
+		return headers
+	}
+
+	if request.Device.UA != "" {
+		headers.Set("User-Agent", request.Device.UA)
+	}
+
+	if len(request.Device.IPv6) > 0 {
+		headers.Add("X-Forwarded-For", request.Device.IPv6)
+	}
+
+	if len(request.Device.IP) > 0 {
+		headers.Add("X-Forwarded-For", request.Device.IP)
+	}
+
+	return headers
+}
+
+func changeRequestForBidService(request *openrtb2.BidRequest) error {
+	if request.Imp[0].Banner == nil {
+		return nil
+	}
+
+	banner := *request.Imp[0].Banner
+	request.Imp[0].Banner = &banner
+
+	if banner.W != nil && banner.H != nil {
+		if *banner.W == 0 || *banner.H == 0 {
+			return fmt.Errorf("invalid sizes provided for Banner %d x %d", *banner.W, *banner.H)
+		}
+		return nil
+	}
+
+	if len(banner.Format) == 0 {
+		return fmt.Errorf("no sizes provided for Banner %v", banner.Format)
+	}
+
+	banner.W = openrtb2.Int64Ptr(banner.Format[0].W)
+	banner.H = openrtb2.Int64Ptr(banner.Format[0].H)
+
+	return nil
+}
+
+func (a adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+	if response.StatusCode == http.StatusNoContent {
+		return nil, nil
+	}
+
+	if response.StatusCode != http.StatusOK {
+		return nil, []error{&errortypes.BadServerResponse{
+			Message: fmt.Sprintf("Unexpected status code: %d.", response.StatusCode),
+		}}
+	}
+
+	var bidResp openrtb2.BidResponse
+	if err := json.Unmarshal(response.Body, &bidResp); err != nil {
+		return nil, []error{&errortypes.BadServerResponse{
+			Message: fmt.Sprintf("Bad server response: %d.", err),
+		}}
+	}
+
+	bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(internalRequest.Imp))
+
+	for _, sb := range bidResp.SeatBid {
+		for _, bid := range sb.Bid {
+			bid := bid
+			exists, mediaType := getImpInfo(bid.ImpID, internalRequest.Imp)
+			if !exists {
+				return nil, []error{&errortypes.BadServerResponse{
+					Message: fmt.Sprintf("Unknown ad unit code '%s'", bid.ImpID),
+				}}
+			}
+
+			if openrtb_ext.BidTypeBanner != mediaType &&
+				openrtb_ext.BidTypeVideo != mediaType {
+				//only banner and video are supported, anything else is ignored
+				continue
+			}
+
+			bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
+				Bid:     &bid,
+				BidType: mediaType,
+			})
+		}
+	}
+
+	return bidResponse, nil
+}
+
+func getImpInfo(impId string, imps []openrtb2.Imp) (bool, openrtb_ext.BidType) {
+	var mediaType openrtb_ext.BidType
+	for _, imp := range imps {
+		if imp.ID == impId {
+
+			if imp.Video != nil {
+				mediaType = openrtb_ext.BidTypeVideo
+			} else if imp.Banner != nil {
+				mediaType = openrtb_ext.BidTypeBanner
+			}
+
+			return true, mediaType
+		}
+	}
+	return false, mediaType
+}
+
+// Builder builds a new instance of the Vidoomy 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
+}
diff --git a/config/config.go b/config/config.go
index 1b415fdf..e7a9effa 100644
--- a/config/config.go
+++ b/config/config.go
@@ -288,7 +288,7 @@ type TCF2 struct {
 	Purpose8            TCF2Purpose             `mapstructure:"purpose8"`
 	Purpose9            TCF2Purpose             `mapstructure:"purpose9"`
 	Purpose10           TCF2Purpose             `mapstructure:"purpose10"`
-	SpecialPurpose1     TCF2Purpose             `mapstructure:"special_purpose1"`
+	SpecialFeature1     TCF2SpecialFeature      `mapstructure:"special_feature1"`
 	PurposeOneTreatment TCF2PurposeOneTreatment `mapstructure:"purpose_one_treatment"`
 }
 
@@ -302,6 +302,13 @@ type TCF2Purpose struct {
 	VendorExceptionMap map[openrtb_ext.BidderName]struct{}
 }
 
+type TCF2SpecialFeature struct {
+	Enforce bool `mapstructure:"enforce"`
+	// Array of vendor exceptions that is used to create the hash table VendorExceptionMap so vendor names can be instantly accessed
+	VendorExceptions   []openrtb_ext.BidderName `mapstructure:"vendor_exceptions"`
+	VendorExceptionMap map[openrtb_ext.BidderName]struct{}
+}
+
 type TCF2PurposeOneTreatment struct {
 	Enabled       bool `mapstructure:"enabled"`
 	AccessAllowed bool `mapstructure:"access_allowed"`
@@ -557,7 +564,6 @@ func New(v *viper.Viper) (*Configuration, error) {
 		&c.GDPR.TCF2.Purpose8,
 		&c.GDPR.TCF2.Purpose9,
 		&c.GDPR.TCF2.Purpose10,
-		&c.GDPR.TCF2.SpecialPurpose1,
 	}
 	for c := 0; c < len(purposeConfigs); c++ {
 		purposeConfigs[c].VendorExceptionMap = make(map[openrtb_ext.BidderName]struct{})
@@ -568,6 +574,14 @@ func New(v *viper.Viper) (*Configuration, error) {
 		}
 	}
 
+	// To look for a special feature's vendor exceptions in O(1) time, we fill this hash table with bidders located in the
+	// VendorExceptions field of the GDPR.TCF2.SpecialFeature1 struct defined in this file
+	c.GDPR.TCF2.SpecialFeature1.VendorExceptionMap = make(map[openrtb_ext.BidderName]struct{})
+	for v := 0; v < len(c.GDPR.TCF2.SpecialFeature1.VendorExceptions); v++ {
+		bidderName := c.GDPR.TCF2.SpecialFeature1.VendorExceptions[v]
+		c.GDPR.TCF2.SpecialFeature1.VendorExceptionMap[bidderName] = struct{}{}
+	}
+
 	// To look for a request's app_id in O(1) time, we fill this hash table located in the
 	// the BlacklistedApps field of the Configuration struct defined in this file
 	c.BlacklistedAppMap = make(map[string]bool)
@@ -793,7 +807,6 @@ func SetupViper(v *viper.Viper, filename string) {
 	v.SetDefault("adapters.33across.partner_id", "")
 	v.SetDefault("adapters.aceex.endpoint", "http://bl-us.aceex.io/?uqhash={{.AccountID}}")
 	v.SetDefault("adapters.acuityads.endpoint", "http://{{.Host}}.admanmedia.com/bid?token={{.AccountID}}")
-	v.SetDefault("adapters.adagio.endpoint", "https://mp.4dex.io/ortb2")
 	v.SetDefault("adapters.adf.endpoint", "https://adx.adform.net/adx/openrtb")
 	v.SetDefault("adapters.adform.endpoint", "https://adx.adform.net/adx/openrtb")
 	v.SetDefault("adapters.adgeneration.endpoint", "https://d.socdm.com/adsv/v1")
@@ -929,6 +942,7 @@ func SetupViper(v *viper.Viper, filename string) {
 	v.SetDefault("adapters.valueimpression.endpoint", "http://useast.quantumdex.io/auction/pbs")
 	v.SetDefault("adapters.verizonmedia.disabled", true)
 	v.SetDefault("adapters.videobyte.endpoint", "https://x.videobyte.com/ortbhb")
+	v.SetDefault("adapters.vidoomy.endpoint", "https://p.vidoomy.com/api/rtbserver/pbs")
 	v.SetDefault("adapters.viewdeos.endpoint", "http://ghb.sync.viewdeos.com/pbs/ortb")
 	v.SetDefault("adapters.visx.endpoint", "https://t.visx.net/s2s_bid?wrapperType=s2s_prebid_standard:0.1.0")
 	v.SetDefault("adapters.vrtcal.endpoint", "http://rtb.vrtcal.com/bidder_prebid.vap?ssp=1804")
@@ -977,8 +991,6 @@ func SetupViper(v *viper.Viper, filename string) {
 	v.SetDefault("gdpr.tcf2.purpose8.vendor_exceptions", []openrtb_ext.BidderName{})
 	v.SetDefault("gdpr.tcf2.purpose9.vendor_exceptions", []openrtb_ext.BidderName{})
 	v.SetDefault("gdpr.tcf2.purpose10.vendor_exceptions", []openrtb_ext.BidderName{})
-	v.SetDefault("gdpr.tcf2.special_purpose1.enabled", true)
-	v.SetDefault("gdpr.tcf2.special_purpose1.vendor_exceptions", []openrtb_ext.BidderName{})
 	v.SetDefault("gdpr.amp_exception", false)
 	v.SetDefault("gdpr.eea_countries", []string{"ALA", "AUT", "BEL", "BGR", "HRV", "CYP", "CZE", "DNK", "EST",
 		"FIN", "FRA", "GUF", "DEU", "GIB", "GRC", "GLP", "GGY", "HUN", "ISL", "IRL", "IMN", "ITA", "JEY", "LVA",
@@ -1035,6 +1047,7 @@ func SetupViper(v *viper.Viper, filename string) {
 	// Migrate config settings to maintain compatibility with old configs
 	migrateConfig(v)
 	migrateConfigPurposeOneTreatment(v)
+	migrateConfigSpecialFeature1(v)
 	migrateConfigTCF2PurposeEnabledFlags(v)
 
 	// These defaults must be set after the migrate functions because those functions look for the presence of these
@@ -1062,6 +1075,8 @@ func SetupViper(v *viper.Viper, filename string) {
 	v.SetDefault("gdpr.tcf2.purpose10.enforce_purpose", TCF2FullEnforcement)
 	v.SetDefault("gdpr.tcf2.purpose_one_treatment.enabled", true)
 	v.SetDefault("gdpr.tcf2.purpose_one_treatment.access_allowed", true)
+	v.SetDefault("gdpr.tcf2.special_feature1.enforce", true)
+	v.SetDefault("gdpr.tcf2.special_feature1.vendor_exceptions", []openrtb_ext.BidderName{})
 }
 
 func migrateConfig(v *viper.Viper) {
@@ -1089,6 +1104,19 @@ func migrateConfigPurposeOneTreatment(v *viper.Viper) {
 	}
 }
 
+func migrateConfigSpecialFeature1(v *viper.Viper) {
+	if oldConfig, ok := v.Get("gdpr.tcf2.special_purpose1").(map[string]interface{}); ok {
+		if v.IsSet("gdpr.tcf2.special_feature1") {
+			glog.Warning("using gdpr.tcf2.special_feature1 and ignoring deprecated gdpr.tcf2.special_purpose1")
+		} else {
+			glog.Warning("gdpr.tcf2.special_purpose1.enabled is deprecated and should be changed to gdpr.tcf2.special_feature1.enforce")
+			glog.Warning("gdpr.tcf2.special_purpose1.vendor_exceptions is deprecated and should be changed to gdpr.tcf2.special_feature1.vendor_exceptions")
+			v.Set("gdpr.tcf2.special_feature1.enforce", oldConfig["enabled"])
+			v.Set("gdpr.tcf2.special_feature1.vendor_exceptions", oldConfig["vendor_exceptions"])
+		}
+	}
+}
+
 func migrateConfigTCF2PurposeEnabledFlags(v *viper.Viper) {
 	for i := 1; i <= 10; i++ {
 		oldField := fmt.Sprintf("gdpr.tcf2.purpose%d.enabled", i)
diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go
index 984d0e5d..4d68db50 100644
--- a/endpoints/openrtb2/auction.go
+++ b/endpoints/openrtb2/auction.go
@@ -35,6 +35,7 @@ import (
 	"github.com/prebid/prebid-server/prebid_cache_client"
 	"github.com/prebid/prebid-server/privacy/ccpa"
 	"github.com/prebid/prebid-server/privacy/lmt"
+	"github.com/prebid/prebid-server/schain"
 	"github.com/prebid/prebid-server/stored_requests"
 	"github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher"
 	"github.com/prebid/prebid-server/usersync"
@@ -528,6 +529,10 @@ func (deps *endpointDeps) validateRequest(req *openrtb_ext.RequestWrapper, isAmp
 		}
 	}
 
+	if err := mapSChains(req); err != nil {
+		return []error{err}
+	}
+
 	if err := validateOrFillChannel(req, isAmp); err != nil {
 		return []error{err}
 	}
@@ -592,6 +597,32 @@ func (deps *endpointDeps) validateRequest(req *openrtb_ext.RequestWrapper, isAmp
 	return errL
 }
 
+// mapSChains maps an schain defined in an ORTB 2.4 location (req.ext.schain) to the ORTB 2.5 location
+// (req.source.ext.schain) if no ORTB 2.5 schain (req.source.ext.schain, req.ext.prebid.schains) exists.
+// An ORTB 2.4 schain is always deleted from the 2.4 location regardless of whether an ORTB 2.5 schain exists.
+func mapSChains(req *openrtb_ext.RequestWrapper) error {
+	reqExt, err := req.GetRequestExt()
+	if err != nil {
+		return fmt.Errorf("req.ext is invalid: %v", err)
+	}
+	sourceExt, err := req.GetSourceExt()
+	if err != nil {
+		return fmt.Errorf("source.ext is invalid: %v", err)
+	}
+
+	reqExtSChain := reqExt.GetSChain()
+	reqExt.SetSChain(nil)
+
+	if reqPrebid := reqExt.GetPrebid(); reqPrebid != nil && reqPrebid.SChains != nil {
+		return nil
+	} else if sourceExt.GetSChain() != nil {
+		return nil
+	} else if reqExtSChain != nil {
+		sourceExt.SetSChain(reqExtSChain)
+	}
+	return nil
+}
+
 func validateAndFillSourceTID(req *openrtb2.BidRequest) error {
 	if req.Source == nil {
 		req.Source = &openrtb2.Source{}
@@ -621,7 +652,7 @@ func (deps *endpointDeps) validateBidAdjustmentFactors(adjustmentFactors map[str
 }
 
 func validateSChains(sChains []*openrtb_ext.ExtRequestPrebidSChain) error {
-	_, err := exchange.BidderToPrebidSChains(sChains)
+	_, err := schain.BidderToPrebidSChains(sChains)
 	return err
 }
 
diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go
index 8075b7f1..850b41ab 100755
--- a/exchange/adapter_builders.go
+++ b/exchange/adapter_builders.go
@@ -5,7 +5,6 @@ import (
 	"github.com/prebid/prebid-server/adapters/33across"
 	"github.com/prebid/prebid-server/adapters/aceex"
 	"github.com/prebid/prebid-server/adapters/acuityads"
-	"github.com/prebid/prebid-server/adapters/adagio"
 	"github.com/prebid/prebid-server/adapters/adf"
 	"github.com/prebid/prebid-server/adapters/adgeneration"
 	"github.com/prebid/prebid-server/adapters/adhese"
@@ -125,6 +124,7 @@ import (
 	"github.com/prebid/prebid-server/adapters/unicorn"
 	"github.com/prebid/prebid-server/adapters/unruly"
 	"github.com/prebid/prebid-server/adapters/videobyte"
+	"github.com/prebid/prebid-server/adapters/vidoomy"
 	"github.com/prebid/prebid-server/adapters/visx"
 	"github.com/prebid/prebid-server/adapters/vrtcal"
 	"github.com/prebid/prebid-server/adapters/yahoossp"
@@ -144,7 +144,6 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder {
 		openrtb_ext.Bidder33Across:          ttx.Builder,
 		openrtb_ext.BidderAceex:             aceex.Builder,
 		openrtb_ext.BidderAcuityAds:         acuityads.Builder,
-		openrtb_ext.BidderAdagio:            adagio.Builder,
 		openrtb_ext.BidderAdf:               adf.Builder,
 		openrtb_ext.BidderAdform:            adf.Builder,
 		openrtb_ext.BidderAdgeneration:      adgeneration.Builder,
@@ -272,6 +271,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder {
 		openrtb_ext.BidderValueImpression:   apacdex.Builder,
 		openrtb_ext.BidderVerizonMedia:      yahoossp.Builder,
 		openrtb_ext.BidderVideoByte:         videobyte.Builder,
+		openrtb_ext.BidderVidoomy:           vidoomy.Builder,
 		openrtb_ext.BidderViewdeos:          adtelligent.Builder,
 		openrtb_ext.BidderVisx:              visx.Builder,
 		openrtb_ext.BidderVrtcal:            vrtcal.Builder,
diff --git a/exchange/adapter_util.go b/exchange/adapter_util.go
index 9ab27561..50308dae 100644
--- a/exchange/adapter_util.go
+++ b/exchange/adapter_util.go
@@ -79,6 +79,7 @@ func GetActiveBidders(infos config.BidderInfos) map[string]openrtb_ext.BidderNam
 func GetDisabledBiddersErrorMessages(infos config.BidderInfos) map[string]string {
 	disabledBidders := map[string]string{
 		"lifestreet": `Bidder "lifestreet" is no longer available in Prebid Server. Please update your configuration.`,
+		"adagio":     `Bidder "adagio" is no longer available in Prebid Server. Please update your configuration.`,
 	}
 
 	for name, info := range infos {
diff --git a/exchange/utils.go b/exchange/utils.go
index 48f76b2a..ec5b666d 100644
--- a/exchange/utils.go
+++ b/exchange/utils.go
@@ -20,6 +20,7 @@ import (
 	"github.com/prebid/prebid-server/privacy"
 	"github.com/prebid/prebid-server/privacy/ccpa"
 	"github.com/prebid/prebid-server/privacy/lmt"
+	"github.com/prebid/prebid-server/schain"
 )
 
 var integrationTypeMap = map[metrics.RequestType]config.IntegrationType{
@@ -31,23 +32,6 @@ var integrationTypeMap = map[metrics.RequestType]config.IntegrationType{
 
 const unknownBidder string = ""
 
-func BidderToPrebidSChains(sChains []*openrtb_ext.ExtRequestPrebidSChain) (map[string]*openrtb_ext.ExtRequestPrebidSChainSChain, error) {
-	bidderToSChains := make(map[string]*openrtb_ext.ExtRequestPrebidSChainSChain)
-
-	for _, schainWrapper := range sChains {
-		for _, bidder := range schainWrapper.Bidders {
-			if _, present := bidderToSChains[bidder]; present {
-				return nil, fmt.Errorf("request.ext.prebid.schains contains multiple schains for bidder %s; "+
-					"it must contain no more than one per bidder.", bidder)
-			} else {
-				bidderToSChains[bidder] = &schainWrapper.SChain
-			}
-		}
-	}
-
-	return bidderToSChains, nil
-}
-
 // cleanOpenRTBRequests splits the input request into requests which are sanitized for each bidder. Intended behavior is:
 //
 //   1. BidRequest.Imp[].Ext will only contain the "prebid" field and a "bidder" field which has the params for the intended Bidder.
@@ -228,14 +212,9 @@ func getAuctionBidderRequests(req AuctionRequest,
 		return nil, []error{err}
 	}
 
-	var sChainsByBidder map[string]*openrtb_ext.ExtRequestPrebidSChainSChain
-
-	// Quick extra wrapper until RequestWrapper makes its way into CleanRequests
-	if requestExt != nil {
-		sChainsByBidder, err = BidderToPrebidSChains(requestExt.Prebid.SChains)
-		if err != nil {
-			return nil, []error{err}
-		}
+	sChainWriter, err := schain.NewSChainWriter(requestExt)
+	if err != nil {
+		return nil, []error{err}
 	}
 
 	var errs []error
@@ -245,7 +224,7 @@ func getAuctionBidderRequests(req AuctionRequest,
 		reqCopy := *req.BidRequest
 		reqCopy.Imp = imps
 
-		prepareSource(&reqCopy, bidder, sChainsByBidder)
+		sChainWriter.Write(&reqCopy, bidder)
 
 		if len(bidderParamsInReqExt) != 0 {
 
@@ -318,40 +297,6 @@ func getExtJson(req *openrtb2.BidRequest, unpackedExt *openrtb_ext.ExtRequest) (
 	return json.Marshal(extCopy)
 }
 
-func prepareSource(req *openrtb2.BidRequest, bidder string, sChainsByBidder map[string]*openrtb_ext.ExtRequestPrebidSChainSChain) {
-	const sChainWildCard = "*"
-	var selectedSChain *openrtb_ext.ExtRequestPrebidSChainSChain
-
-	wildCardSChain := sChainsByBidder[sChainWildCard]
-	bidderSChain := sChainsByBidder[bidder]
-
-	// source should not be modified
-	if bidderSChain == nil && wildCardSChain == nil {
-		return
-	}
-
-	if bidderSChain != nil {
-		selectedSChain = bidderSChain
-	} else {
-		selectedSChain = wildCardSChain
-	}
-
-	// set source
-	if req.Source == nil {
-		req.Source = &openrtb2.Source{}
-	} else {
-		sourceCopy := *req.Source
-		req.Source = &sourceCopy
-	}
-	schain := openrtb_ext.ExtRequestPrebidSChain{
-		SChain: *selectedSChain,
-	}
-	sourceExt, err := json.Marshal(schain)
-	if err == nil {
-		req.Source.Ext = sourceExt
-	}
-}
-
 // extractBuyerUIDs parses the values from user.ext.prebid.buyeruids, and then deletes those values from the ext.
 // This prevents a Bidder from using these values to figure out who else is involved in the Auction.
 func extractBuyerUIDs(user *openrtb2.User) (map[string]string, error) {
diff --git a/gdpr/impl.go b/gdpr/impl.go
index 380665c4..c85c9d28 100644
--- a/gdpr/impl.go
+++ b/gdpr/impl.go
@@ -132,9 +132,9 @@ func (p *permissionsImpl) allowActivities(ctx context.Context, vendorID uint16,
 		return
 	}
 
-	if p.cfg.TCF2.SpecialPurpose1.Enabled {
-		vendorException := p.isSpecialPurposeVendorException(bidder)
-		passGeo = vendorException || (consentMeta.SpecialFeatureOptIn(1) && (vendor.SpecialPurpose(1) || weakVendorEnforcement))
+	if p.cfg.TCF2.SpecialFeature1.Enforce {
+		vendorException := p.isSpecialFeatureVendorException(bidder)
+		passGeo = vendorException || (consentMeta.SpecialFeatureOptIn(1) && (vendor.SpecialFeature(1) || weakVendorEnforcement))
 	} else {
 		passGeo = true
 	}
@@ -162,8 +162,8 @@ func (p *permissionsImpl) isVendorException(purpose consentconstants.Purpose, bi
 	return
 }
 
-func (p *permissionsImpl) isSpecialPurposeVendorException(bidder openrtb_ext.BidderName) (vendorException bool) {
-	if _, ok := p.cfg.TCF2.SpecialPurpose1.VendorExceptionMap[bidder]; ok {
+func (p *permissionsImpl) isSpecialFeatureVendorException(bidder openrtb_ext.BidderName) (vendorException bool) {
+	if _, ok := p.cfg.TCF2.SpecialFeature1.VendorExceptionMap[bidder]; ok {
 		vendorException = true
 	}
 	return
diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go
index 941f1a56..7cb7a07b 100644
--- a/openrtb_ext/bidders.go
+++ b/openrtb_ext/bidders.go
@@ -79,7 +79,6 @@ const (
 	Bidder33Across          BidderName = "33across"
 	BidderAceex             BidderName = "aceex"
 	BidderAcuityAds         BidderName = "acuityads"
-	BidderAdagio            BidderName = "adagio"
 	BidderAdf               BidderName = "adf"
 	BidderAdform            BidderName = "adform"
 	BidderAdgeneration      BidderName = "adgeneration"
@@ -207,6 +206,7 @@ const (
 	BidderValueImpression   BidderName = "valueimpression"
 	BidderVerizonMedia      BidderName = "verizonmedia"
 	BidderVideoByte         BidderName = "videobyte"
+	BidderVidoomy           BidderName = "vidoomy"
 	BidderVisx              BidderName = "visx"
 	BidderViewdeos          BidderName = "viewdeos"
 	BidderVrtcal            BidderName = "vrtcal"
@@ -225,7 +225,6 @@ func CoreBidderNames() []BidderName {
 		Bidder33Across,
 		BidderAceex,
 		BidderAcuityAds,
-		BidderAdagio,
 		BidderAdf,
 		BidderAdform,
 		BidderAdgeneration,
@@ -353,6 +352,7 @@ func CoreBidderNames() []BidderName {
 		BidderValueImpression,
 		BidderVerizonMedia,
 		BidderVideoByte,
+		BidderVidoomy,
 		BidderViewdeos,
 		BidderVisx,
 		BidderVrtcal,
diff --git a/openrtb_ext/imp_vidoomy.go b/openrtb_ext/imp_vidoomy.go
new file mode 100644
index 00000000..21072dae
--- /dev/null
+++ b/openrtb_ext/imp_vidoomy.go
@@ -0,0 +1,5 @@
+package openrtb_ext
+
+type ImpExtVidoomy struct {
+	ZoneID string `json:"zoneId"`
+}
diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go
index dba30863..ecd913b1 100644
--- a/openrtb_ext/request.go
+++ b/openrtb_ext/request.go
@@ -24,7 +24,8 @@ const MaxDecimalFigures int = 15
 
 // ExtRequest defines the contract for bidrequest.ext
 type ExtRequest struct {
-	Prebid ExtRequestPrebid `json:"prebid"`
+	Prebid ExtRequestPrebid              `json:"prebid"`
+	SChain *ExtRequestPrebidSChainSChain `json:"schain,omitempty"`
 }
 
 // ExtRequestPrebid defines the contract for bidrequest.ext.prebid
@@ -96,11 +97,6 @@ type ExtRequestPrebidSChainSChainNode struct {
 	Ext    json.RawMessage `json:"ext,omitempty"`
 }
 
-// SourceExt defines the contract for bidrequest.source.ext
-type SourceExt struct {
-	SChain ExtRequestPrebidSChainSChain `json:"schain"`
-}
-
 // ExtRequestPrebidChannel defines the contract for bidrequest.ext.prebid.channel
 type ExtRequestPrebidChannel struct {
 	Name    string `json:"name"`
diff --git a/openrtb_ext/request_wrapper.go b/openrtb_ext/request_wrapper.go
index 9219bbdb..dfc2a41f 100644
--- a/openrtb_ext/request_wrapper.go
+++ b/openrtb_ext/request_wrapper.go
@@ -38,6 +38,7 @@ type RequestWrapper struct {
 	appExt     *AppExt
 	regExt     *RegExt
 	siteExt    *SiteExt
+	sourceExt  *SourceExt
 }
 
 func (rw *RequestWrapper) GetUserExt() (*UserExt, error) {
@@ -107,6 +108,17 @@ func (rw *RequestWrapper) GetSiteExt() (*SiteExt, error) {
 	return rw.siteExt, rw.siteExt.unmarshal(rw.Site.Ext)
 }
 
+func (rw *RequestWrapper) GetSourceExt() (*SourceExt, error) {
+	if rw.sourceExt != nil {
+		return rw.sourceExt, nil
+	}
+	rw.sourceExt = &SourceExt{}
+	if rw.BidRequest == nil || rw.Source == nil || rw.Source.Ext == nil {
+		return rw.sourceExt, rw.sourceExt.unmarshal(json.RawMessage{})
+	}
+	return rw.sourceExt, rw.sourceExt.unmarshal(rw.Source.Ext)
+}
+
 func (rw *RequestWrapper) RebuildRequest() error {
 	if rw.BidRequest == nil {
 		return errors.New("Requestwrapper Sync called on a nil BidRequest")
@@ -130,6 +142,9 @@ func (rw *RequestWrapper) RebuildRequest() error {
 	if err := rw.rebuildSiteExt(); err != nil {
 		return err
 	}
+	if err := rw.rebuildSourceExt(); err != nil {
+		return err
+	}
 
 	return nil
 }
@@ -215,6 +230,20 @@ func (rw *RequestWrapper) rebuildSiteExt() error {
 	return nil
 }
 
+func (rw *RequestWrapper) rebuildSourceExt() error {
+	if rw.Source == nil && rw.sourceExt != nil && rw.sourceExt.Dirty() {
+		rw.Source = &openrtb2.Source{}
+	}
+	if rw.sourceExt != nil && rw.sourceExt.Dirty() {
+		sourceJson, err := rw.sourceExt.marshal()
+		if err != nil {
+			return err
+		}
+		rw.Source.Ext = sourceJson
+	}
+	return nil
+}
+
 // ---------------------------------------------------------------
 // UserExt provides an interface for request.user.ext
 // ---------------------------------------------------------------
@@ -382,6 +411,8 @@ type RequestExt struct {
 	extDirty    bool
 	prebid      *ExtRequestPrebid
 	prebidDirty bool
+	schain      *ExtRequestPrebidSChainSChain // ORTB 2.4 location
+	schainDirty bool
 }
 
 func (re *RequestExt) unmarshal(extJson json.RawMessage) error {
@@ -401,6 +432,11 @@ func (re *RequestExt) unmarshal(extJson json.RawMessage) error {
 		re.prebid = &ExtRequestPrebid{}
 		err = json.Unmarshal(prebidJson, re.prebid)
 	}
+	schainJson, hasSChain := re.ext["schain"]
+	if hasSChain {
+		re.schain = &ExtRequestPrebidSChainSChain{}
+		err = json.Unmarshal(schainJson, re.schain)
+	}
 
 	return err
 }
@@ -419,6 +455,22 @@ func (re *RequestExt) marshal() (json.RawMessage, error) {
 		re.prebidDirty = false
 	}
 
+	if re.schainDirty {
+		if re.schain == nil {
+		}
+
+		schainJson, err := json.Marshal(re.schain)
+		if err != nil {
+			return nil, err
+		}
+		if len(schainJson) > 2 && re.schain != nil {
+			re.ext["schain"] = json.RawMessage(schainJson)
+		} else {
+			delete(re.ext, "schain")
+		}
+		re.schainDirty = false
+	}
+
 	re.extDirty = false
 	if len(re.ext) == 0 {
 		return nil, nil
@@ -427,7 +479,7 @@ func (re *RequestExt) marshal() (json.RawMessage, error) {
 }
 
 func (re *RequestExt) Dirty() bool {
-	return re.extDirty || re.prebidDirty
+	return re.extDirty || re.prebidDirty || re.schainDirty
 }
 
 func (re *RequestExt) GetExt() map[string]json.RawMessage {
@@ -456,6 +508,22 @@ func (re *RequestExt) SetPrebid(prebid *ExtRequestPrebid) {
 	re.prebidDirty = true
 }
 
+// These schain methods on the request.ext are only for ORTB 2.4 backwards compatibility and
+// should not be used for any other purposes. To access ORTB 2.5 schains, see source.ext.schain
+// or request.ext.prebid.schains.
+func (re *RequestExt) GetSChain() *ExtRequestPrebidSChainSChain {
+	if re.schain == nil {
+		return nil
+	}
+	schain := *re.schain
+	return &schain
+}
+
+func (re *RequestExt) SetSChain(schain *ExtRequestPrebidSChainSChain) {
+	re.schain = schain
+	re.schainDirty = true
+}
+
 // ---------------------------------------------------------------
 // DeviceExt provides an interface for request.device.ext
 // ---------------------------------------------------------------
@@ -785,3 +853,85 @@ func (se *SiteExt) SetUSPrivacy(amp int8) {
 	se.amp = amp
 	se.ampDirty = true
 }
+
+// ---------------------------------------------------------------
+// SourceExt provides an interface for request.source.ext
+// ---------------------------------------------------------------
+
+type SourceExt struct {
+	ext         map[string]json.RawMessage
+	extDirty    bool
+	schain      *ExtRequestPrebidSChainSChain
+	schainDirty bool
+}
+
+func (se *SourceExt) unmarshal(extJson json.RawMessage) error {
+	if len(se.ext) != 0 || se.Dirty() {
+		return nil
+	}
+	se.ext = make(map[string]json.RawMessage)
+	if len(extJson) == 0 {
+		return nil
+	}
+	err := json.Unmarshal(extJson, &se.ext)
+	if err != nil {
+		return err
+	}
+	schainJson, hasSChain := se.ext["schain"]
+	if hasSChain {
+		err = json.Unmarshal(schainJson, &se.schain)
+	}
+
+	return err
+}
+
+func (se *SourceExt) marshal() (json.RawMessage, error) {
+	if se.schainDirty {
+		schainJson, err := json.Marshal(se.schain)
+		if err != nil {
+			return nil, err
+		}
+		if len(schainJson) > 2 {
+			se.ext["schain"] = json.RawMessage(schainJson)
+		} else {
+			delete(se.ext, "schain")
+		}
+		se.schainDirty = false
+	}
+
+	se.extDirty = false
+	if len(se.ext) == 0 {
+		return nil, nil
+	}
+	return json.Marshal(se.ext)
+}
+
+func (se *SourceExt) Dirty() bool {
+	return se.extDirty || se.schainDirty
+}
+
+func (se *SourceExt) GetExt() map[string]json.RawMessage {
+	ext := make(map[string]json.RawMessage)
+	for k, v := range se.ext {
+		ext[k] = v
+	}
+	return ext
+}
+
+func (se *SourceExt) SetExt(ext map[string]json.RawMessage) {
+	se.ext = ext
+	se.extDirty = true
+}
+
+func (se *SourceExt) GetSChain() *ExtRequestPrebidSChainSChain {
+	if se.schain == nil {
+		return nil
+	}
+	schain := *se.schain
+	return &schain
+}
+
+func (se *SourceExt) SetSChain(schain *ExtRequestPrebidSChainSChain) {
+	se.schain = schain
+	se.schainDirty = true
+}
diff --git a/openrtb_ext/source.go b/openrtb_ext/source.go
new file mode 100644
index 00000000..70b291b1
--- /dev/null
+++ b/openrtb_ext/source.go
@@ -0,0 +1,6 @@
+package openrtb_ext
+
+// ExtSource defines the contract for bidrequest.source.ext
+type ExtSource struct {
+	SChain *ExtRequestPrebidSChainSChain `json:"schain"`
+}
diff --git a/schain/schain.go b/schain/schain.go
new file mode 100644
index 00000000..785c7e24
--- /dev/null
+++ b/schain/schain.go
@@ -0,0 +1,25 @@
+package schain
+
+import (
+	"fmt"
+
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+// BidderToPrebidSChains organizes the ORTB 2.5 multiple root schain nodes into a map of schain nodes by bidder
+func BidderToPrebidSChains(sChains []*openrtb_ext.ExtRequestPrebidSChain) (map[string]*openrtb_ext.ExtRequestPrebidSChainSChain, error) {
+	bidderToSChains := make(map[string]*openrtb_ext.ExtRequestPrebidSChainSChain)
+
+	for _, schainWrapper := range sChains {
+		for _, bidder := range schainWrapper.Bidders {
+			if _, present := bidderToSChains[bidder]; present {
+				return nil, fmt.Errorf("request.ext.prebid.schains contains multiple schains for bidder %s; "+
+					"it must contain no more than one per bidder.", bidder)
+			} else {
+				bidderToSChains[bidder] = &schainWrapper.SChain
+			}
+		}
+	}
+
+	return bidderToSChains, nil
+}
diff --git a/schain/schainwriter.go b/schain/schainwriter.go
new file mode 100644
index 00000000..bf2f5640
--- /dev/null
+++ b/schain/schainwriter.go
@@ -0,0 +1,78 @@
+package schain
+
+import (
+	"encoding/json"
+
+	"github.com/mxmCherry/openrtb/v15/openrtb2"
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+// NewSChainWriter creates an ORTB 2.5 schain writer instance
+func NewSChainWriter(reqExt *openrtb_ext.ExtRequest) (*SChainWriter, error) {
+	if !extPrebidSChainExists(reqExt) {
+		return &SChainWriter{}, nil
+	}
+
+	sChainsByBidder, err := BidderToPrebidSChains(reqExt.Prebid.SChains)
+	if err != nil {
+		return nil, err
+	}
+
+	writer := SChainWriter{
+		sChainsByBidder: sChainsByBidder,
+	}
+	return &writer, nil
+}
+
+// SChainWriter is used to write the appropriate schain for a particular bidder defined in the ORTB 2.5 multi-schain
+// location (req.ext.prebid.schain) to the ORTB 2.5 location (req.source.ext)
+type SChainWriter struct {
+	sChainsByBidder map[string]*openrtb_ext.ExtRequestPrebidSChainSChain
+}
+
+// Write selects an schain from the multi-schain ORTB 2.5 location (req.ext.prebid.schains) for the specified bidder
+// and copies it to the ORTB 2.5 location (req.source.ext). If no schain exists for the bidder in the multi-schain
+// location and no wildcard schain exists, the request is not modified.
+func (w SChainWriter) Write(req *openrtb2.BidRequest, bidder string) {
+	const sChainWildCard = "*"
+	var selectedSChain *openrtb_ext.ExtRequestPrebidSChainSChain
+
+	wildCardSChain := w.sChainsByBidder[sChainWildCard]
+	bidderSChain := w.sChainsByBidder[bidder]
+
+	// source should not be modified
+	if bidderSChain == nil && wildCardSChain == nil {
+		return
+	}
+
+	if bidderSChain != nil {
+		selectedSChain = bidderSChain
+	} else {
+		selectedSChain = wildCardSChain
+	}
+
+	if req.Source == nil {
+		req.Source = &openrtb2.Source{}
+	} else {
+		sourceCopy := *req.Source
+		req.Source = &sourceCopy
+	}
+	schain := openrtb_ext.ExtRequestPrebidSChain{
+		SChain: *selectedSChain,
+	}
+	sourceExt, err := json.Marshal(schain)
+	if err == nil {
+		req.Source.Ext = sourceExt
+	}
+}
+
+// extPrebidSChainExists checks if an schain exists in the ORTB 2.5 req.ext.prebid.schain location
+func extPrebidSChainExists(reqExt *openrtb_ext.ExtRequest) bool {
+	if reqExt == nil {
+		return false
+	}
+	if reqExt.Prebid.SChains == nil {
+		return false
+	}
+	return true
+}
diff --git a/static/bidder-info/adagio.yaml b/static/bidder-info/adagio.yaml
deleted file mode 100644
index 891b2a6c..00000000
--- a/static/bidder-info/adagio.yaml
+++ /dev/null
@@ -1,16 +0,0 @@
-maintainer:
-  email: "dev@adagio.io"
-gvlVendorID: 617
-modifyingVastXmlAllowed: false
-debug:
-  allow: true
-capabilities:
-  site:
-    mediaTypes:
-      - banner
-      - video
-      - native
-userSync:
-  redirect:
-    url: "https://mp.4dex.io/sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect={{.RedirectURL}}"
-    userMacro: "{{UID}}"
diff --git a/static/bidder-info/adyoulike.yaml b/static/bidder-info/adyoulike.yaml
index 0853cf10..8fdca5e2 100644
--- a/static/bidder-info/adyoulike.yaml
+++ b/static/bidder-info/adyoulike.yaml
@@ -11,8 +11,8 @@ capabilities:
 userSync:
   default: iframe
   iframe:
-    url: "http://visitor.omnitagjs.com/visitor/isync?uid=19340f4f097d16f41f34fc0274981ca4&name=PrebidServer&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&url={{.RedirectURL}}"
+    url: "https://visitor.omnitagjs.com/visitor/isync?uid=19340f4f097d16f41f34fc0274981ca4&name=PrebidServer&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&url={{.RedirectURL}}"
     userMacro: "[BUYER_USERID]"
   redirect:
-    url: "http://visitor.omnitagjs.com/visitor/bsync?uid=19340f4f097d16f41f34fc0274981ca4&name=PrebidServer&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&url={{.RedirectURL}}"
+    url: "https://visitor.omnitagjs.com/visitor/bsync?uid=19340f4f097d16f41f34fc0274981ca4&name=PrebidServer&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&url={{.RedirectURL}}"
     userMacro: "[BUYER_USERID]"
diff --git a/static/bidder-info/vidoomy.yaml b/static/bidder-info/vidoomy.yaml
new file mode 100644
index 00000000..1cd683a7
--- /dev/null
+++ b/static/bidder-info/vidoomy.yaml
@@ -0,0 +1,16 @@
+maintainer:
+  email: support@vidoomy.com
+gvlVendorID: 380
+capabilities:
+  site:
+    mediaTypes:
+      - banner
+      - video
+  app:
+    mediaTypes:
+      - banner
+      - video
+userSync:
+    iframe:
+      url: "https://vid.vidoomy.com/sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect={{.RedirectURL}}"
+      userMacro: "{{VID}}"
\ No newline at end of file
diff --git a/static/bidder-params/adagio.json b/static/bidder-params/adagio.json
deleted file mode 100644
index 955c58c7..00000000
--- a/static/bidder-params/adagio.json
+++ /dev/null
@@ -1,94 +0,0 @@
-{
-  "$schema": "http://json-schema.org/draft-04/schema#",
-  "title": "Adagio Adapter Params",
-  "description": "A schema which validates params accepted by the Adagio adapter",
-  "type": "object",
-  "required": [
-    "organizationId",
-    "site",
-    "placement"
-  ],
-  "properties": {
-    "organizationId": {
-      "type": "string",
-      "description": "Name to identify the organization",
-      "minLength": 1
-    },
-    "site": {
-      "type": "string",
-      "description": "Name to identify the site",
-      "minLength": 1
-    },
-    "placement": {
-      "type": "string",
-      "description": "Name to identify the placement",
-      "minLength": 1
-    },
-    "pageviewId": {
-      "type": "string",
-      "description": "Name to identify the pageview"
-    },
-    "pagetype": {
-      "type": "string",
-      "description": "Name to identify the page type"
-    },
-    "category": {
-      "type": "string",
-      "description": "Name to identify the category"
-    },
-    "subcategory": {
-      "type": "string",
-      "description": "Name to identify the subcategory"
-    },
-    "environment": {
-      "type": "string",
-      "description": "Name to identify the environment"
-    },
-    "features": {
-      "type": "object",
-      "patternProperties": {
-        "^[a-zA-Z_]": { "type": "string" }
-      }
-    },
-    "prebidVersion:": {
-      "type": "string",
-      "description": "Name to identify the version of Prebid.js"
-    },
-    "debug": {
-      "type": "object",
-      "properties": {
-        "enabled": {
-          "type": "boolean"
-        },
-        "cpm": {
-          "type": "number"
-        },
-        "lazyLoad": {
-          "type": "object",
-          "properties": {
-            "enabled": {
-              "type": "boolean"
-            },
-            "threshold": {
-              "type": "number"
-            },
-            "rootMargin":  {
-              "type": "string"
-            }
-          }
-        }
-      }
-    },
-    "native": {
-      "type": "object",
-      "properties": {
-        "context": {
-          "type": "number"
-        },
-        "plcmttype": {
-          "type": "number"
-        }
-      }
-    }
-  }
-}
diff --git a/static/bidder-params/vidoomy.json b/static/bidder-params/vidoomy.json
new file mode 100644
index 00000000..9c49daf7
--- /dev/null
+++ b/static/bidder-params/vidoomy.json
@@ -0,0 +1,14 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "title": "Vidoomy Adapter Params",
+  "description": "A schema which validates params accepted by the Vidoomy adapter",
+  "type": "object",
+  "properties": {
+    "zoneId": {
+      "type": "string",
+      "description": "Specific zone ID for this placement, belonging to app/site",
+      "minLength": 1
+    }
+  },
+  "required": ["zoneId"]
+}
\ No newline at end of file


Test patch:
diff --git a/adapters/adagio/adagio_test.go b/adapters/adagio/adagio_test.go
deleted file mode 100644
index d5e25c78..00000000
--- a/adapters/adagio/adagio_test.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package adagio
-
-import (
-	"encoding/json"
-	"github.com/mxmCherry/openrtb/v15/openrtb2"
-	"github.com/stretchr/testify/assert"
-	"testing"
-
-	"github.com/prebid/prebid-server/adapters/adapterstest"
-	"github.com/prebid/prebid-server/config"
-	"github.com/prebid/prebid-server/openrtb_ext"
-)
-
-func buildFakeBidRequest() openrtb2.BidRequest {
-	imp1 := openrtb2.Imp{
-		ID:     "some-impression-id",
-		Banner: &openrtb2.Banner{},
-		Ext:    json.RawMessage(`{"bidder": {"organizationId": "1000", "site": "site-name", "placement": "ban_atf"}}`),
-	}
-
-	fakeBidRequest := openrtb2.BidRequest{
-		ID:  "some-request-id",
-		Imp: []openrtb2.Imp{imp1},
-	}
-
-	return fakeBidRequest
-}
-
-func TestJsonSamples(t *testing.T) {
-	bidder, buildErr := Builder(openrtb_ext.BidderAdagio, config.Adapter{
-		Endpoint: "http://localhost/prebid_server"})
-
-	if buildErr != nil {
-		t.Fatalf("Builder returned unexpected error %v", buildErr)
-	}
-
-	adapterstest.RunJSONBidderTest(t, "adagiotest", bidder)
-}
-
-func TestMakeRequests_NoGzip(t *testing.T) {
-	fakeBidRequest := buildFakeBidRequest()
-	fakeBidRequest.Test = 1 // Do not use Gzip in Test Mode.
-
-	bidder, buildErr := Builder(openrtb_ext.BidderAdagio, config.Adapter{
-		Endpoint: "http://localhost/prebid_server"})
-
-	if buildErr != nil {
-		t.Fatalf("Builder returned unexpected error %v", buildErr)
-	}
-
-	requestData, errs := bidder.MakeRequests(&fakeBidRequest, nil)
-
-	assert.Nil(t, errs)
-	assert.Equal(t, 1, len(requestData))
-
-	body := &openrtb2.BidRequest{}
-	err := json.Unmarshal(requestData[0].Body, body)
-	assert.NoError(t, err, "Request body unmarshalling error should be nil")
-	assert.Equal(t, 1, len(body.Imp))
-}
-
-func TestMakeRequests_Gzip(t *testing.T) {
-	fakeBidRequest := buildFakeBidRequest()
-
-	bidder, buildErr := Builder(openrtb_ext.BidderAdagio, config.Adapter{
-		Endpoint: "http://localhost/prebid_server"})
-
-	if buildErr != nil {
-		t.Fatalf("Builder returned unexpected error %v", buildErr)
-	}
-
-	requestData, errs := bidder.MakeRequests(&fakeBidRequest, nil)
-	assert.Empty(t, errs, "Got errors while making requests")
-	assert.Equal(t, []string{"gzip"}, requestData[0].Headers["Content-Encoding"])
-}
diff --git a/adapters/adagio/adagiotest/exemplary/banner-web.json b/adapters/adagio/adagiotest/exemplary/banner-web.json
deleted file mode 100644
index 732b40d2..00000000
--- a/adapters/adagio/adagiotest/exemplary/banner-web.json
+++ /dev/null
@@ -1,155 +0,0 @@
-{
-  "mockBidRequest": {
-    "test": 1,
-    "id": "some-request-id",
-    "device": {
-      "ua": "test-user-agent",
-      "ip": "123.123.123.123",
-      "language": "en",
-      "dnt": 0
-    },
-    "tmax": 1000,
-    "user": {
-      "buyeruid": "awesome-user"
-    },
-    "site": {
-      "page": "test.com",
-      "publisher": {
-        "id": "123456789"
-      }
-    },
-    "imp": [
-      {
-        "id": "some-impression-id",
-        "banner": {
-          "w":320,
-          "h":50
-        },
-        "ext": {
-          "bidder": {
-            "organizationId": "1000",
-            "site": "site-name",
-            "placement": "ban_atf"
-          }
-        }
-      }
-    ]
-  },
-  "httpCalls": [
-    {
-      "expectedRequest": {
-        "headers": {
-          "Content-Type": [
-            "application/json;charset=utf-8"
-          ],
-          "Accept": [
-            "application/json"
-          ],
-          "X-Forwarded-For": [
-            "123.123.123.123"
-          ]
-        },
-        "uri": "http://localhost/prebid_server",
-        "body": {
-          "test": 1,
-          "id": "some-request-id",
-          "device": {
-            "ua": "test-user-agent",
-            "ip": "123.123.123.123",
-            "language": "en",
-            "dnt": 0
-          },
-          "imp": [
-            {
-              "id": "some-impression-id",
-              "banner": {
-                "w":320,
-                "h":50
-              },
-              "ext": {
-                "bidder": {
-                  "organizationId": "1000",
-                  "site": "site-name",
-                  "placement": "ban_atf"
-                }
-              }
-            }
-          ],
-          "site": {
-            "page": "test.com",
-            "publisher": {
-              "id": "123456789"
-            }
-          },
-          "user": {
-            "buyeruid": "awesome-user"
-          },
-          "tmax": 1000
-        }
-      },
-      "mockResponse": {
-        "status": 200,
-        "body": {
-          "id": "awesome-resp-id",
-          "seatbid": [
-            {
-              "bid": [
-                {
-                  "id": "a3ae1b4e2fc24a4fb45540082e98e161",
-                  "impid": "some-impression-id",
-                  "price": 3.5,
-                  "adm": "awesome-markup",
-                  "adomain": [
-                    "awesome.com"
-                  ],
-                  "crid": "20",
-                  "w": 320,
-                  "h": 50,
-                  "ext": {
-                    "prebid": {
-                      "type": "banner"
-                    }
-                  }
-                }
-              ],
-              "seat": "adagio"
-            }
-          ],
-          "cur": "USD",
-          "ext": {
-            "responsetimemillis": {
-              "adagio": 154
-            },
-            "tmaxrequest": 1000
-          }
-        }
-      }
-    }
-  ],
-  "expectedBidResponses": [
-    {
-      "bids":[
-        {
-          "bid":  {
-            "id": "a3ae1b4e2fc24a4fb45540082e98e161",
-            "impid": "some-impression-id",
-            "price": 3.5,
-            "adm": "awesome-markup",
-            "crid": "20",
-            "adomain": [
-              "awesome.com"
-            ],
-            "w": 320,
-            "h": 50,
-            "ext": {
-              "prebid": {
-                "type": "banner"
-              }
-            }
-          },
-          "type": "banner"
-        }
-      ]
-    }
-  ]
-}
diff --git a/adapters/adagio/adagiotest/exemplary/multi-format.json b/adapters/adagio/adagiotest/exemplary/multi-format.json
deleted file mode 100644
index 85e0be26..00000000
--- a/adapters/adagio/adagiotest/exemplary/multi-format.json
+++ /dev/null
@@ -1,165 +0,0 @@
-{
-  "mockBidRequest": {
-    "test": 1,
-    "id": "some-request-id",
-    "device": {
-      "ua": "test-user-agent",
-      "ip": "123.123.123.123",
-      "language": "en",
-      "dnt": 0
-    },
-    "tmax": 1000,
-    "user": {
-      "buyeruid": "awesome-user"
-    },
-    "site": {
-      "page": "test.com",
-      "publisher": {
-        "id": "123456789"
-      }
-    },
-    "imp": [
-      {
-        "id": "multi-format-id",
-        "banner": {
-          "w":320,
-          "h":50
-        },
-        "video": {
-          "mimes": ["video\/mp4"],
-          "w": 640,
-          "h": 480
-        },
-        "ext": {
-          "bidder": {
-            "organizationId": "1000",
-            "site": "site-name",
-            "placement": "ban_atf"
-          }
-        }
-      }
-    ]
-  },
-  "httpCalls": [
-    {
-      "expectedRequest": {
-        "headers": {
-          "Content-Type": [
-            "application/json;charset=utf-8"
-          ],
-          "Accept": [
-            "application/json"
-          ],
-          "X-Forwarded-For": [
-            "123.123.123.123"
-          ]
-        },
-        "uri": "http://localhost/prebid_server",
-        "body": {
-          "test": 1,
-          "id": "some-request-id",
-          "device": {
-            "ua": "test-user-agent",
-            "ip": "123.123.123.123",
-            "language": "en",
-            "dnt": 0
-          },
-          "imp": [
-            {
-              "id": "multi-format-id",
-              "banner": {
-                "w":320,
-                "h":50
-              },
-              "video": {
-                "mimes": ["video\/mp4"],
-                "w": 640,
-                "h": 480
-              },
-              "ext": {
-                "bidder": {
-                  "organizationId": "1000",
-                  "site": "site-name",
-                  "placement": "ban_atf"
-                }
-              }
-            }
-          ],
-          "site": {
-            "page": "test.com",
-            "publisher": {
-              "id": "123456789"
-            }
-          },
-          "user": {
-            "buyeruid": "awesome-user"
-          },
-          "tmax": 1000
-        }
-      },
-      "mockResponse": {
-        "status": 200,
-        "body": {
-          "id": "awesome-resp-id",
-          "seatbid": [
-            {
-              "bid": [
-                {
-                  "id": "a3ae1b4e2fc24a4fb45540082e98e161",
-                  "impid": "multi-format-id",
-                  "price": 3.5,
-                  "adm": "awesome-markup",
-                  "adomain": [
-                    "awesome.com"
-                  ],
-                  "crid": "20",
-                  "w": 320,
-                  "h": 50,
-                  "ext": {
-                    "prebid": {
-                      "type": "banner"
-                    }
-                  }
-                }
-              ],
-              "seat": "adagio"
-            }
-          ],
-          "cur": "USD",
-          "ext": {
-            "responsetimemillis": {
-              "adagio": 154
-            },
-            "tmaxrequest": 1000
-          }
-        }
-      }
-    }
-  ],
-  "expectedBidResponses": [
-    {
-      "bids":[
-        {
-          "bid":  {
-            "id": "a3ae1b4e2fc24a4fb45540082e98e161",
-            "impid": "multi-format-id",
-            "price": 3.5,
-            "adm": "awesome-markup",
-            "crid": "20",
-            "adomain": [
-              "awesome.com"
-            ],
-            "w": 320,
-            "h": 50,
-            "ext": {
-              "prebid": {
-                "type": "banner"
-              }
-            }
-          },
-          "type": "banner"
-        }
-      ]
-    }
-  ]
-}
diff --git a/adapters/adagio/adagiotest/exemplary/multi-imp.json b/adapters/adagio/adagiotest/exemplary/multi-imp.json
deleted file mode 100644
index 66af28ea..00000000
--- a/adapters/adagio/adagiotest/exemplary/multi-imp.json
+++ /dev/null
@@ -1,222 +0,0 @@
-{
-  "mockBidRequest": {
-    "test": 1,
-    "id": "some-request-id",
-    "device": {
-      "ua": "test-user-agent",
-      "ip": "123.123.123.123",
-      "language": "en",
-      "dnt": 0
-    },
-    "tmax": 1000,
-    "user": {
-      "buyeruid": "awesome-user"
-    },
-    "site": {
-      "page": "test.com",
-      "publisher": {
-        "id": "123456789"
-      }
-    },
-    "imp": [
-      {
-        "id": "banner-impression-id",
-        "banner": {
-          "w":320,
-          "h":50
-        },
-        "ext": {
-          "bidder": {
-            "organizationId": "1000",
-            "site": "site-name",
-            "placement": "ban_atf"
-          }
-        }
-      },
-      {
-        "id": "video-impression-id",
-        "video": {
-          "mimes": ["video\/mp4"],
-          "w": 640,
-          "h": 480
-        },
-        "ext": {
-          "bidder": {
-            "organizationId": "1000",
-            "site": "site-name",
-            "placement": "in_article"
-          }
-        }
-      }
-    ]
-  },
-  "httpCalls": [
-    {
-      "expectedRequest": {
-        "headers": {
-          "Content-Type": [
-            "application/json;charset=utf-8"
-          ],
-          "Accept": [
-            "application/json"
-          ],
-          "X-Forwarded-For": [
-            "123.123.123.123"
-          ]
-        },
-        "uri": "http://localhost/prebid_server",
-        "body": {
-          "test": 1,
-          "id": "some-request-id",
-          "device": {
-            "ua": "test-user-agent",
-            "ip": "123.123.123.123",
-            "language": "en",
-            "dnt": 0
-          },
-          "imp": [
-            {
-              "id": "banner-impression-id",
-              "banner": {
-                "w":320,
-                "h":50
-              },
-              "ext": {
-                "bidder": {
-                  "organizationId": "1000",
-                  "site": "site-name",
-                  "placement": "ban_atf"
-                }
-              }
-            },
-            {
-              "id": "video-impression-id",
-              "video": {
-                "mimes": ["video\/mp4"],
-                "w": 640,
-                "h": 480
-              },
-              "ext": {
-                "bidder": {
-                  "organizationId": "1000",
-                  "site": "site-name",
-                  "placement": "in_article"
-                }
-              }
-            }
-          ],
-          "site": {
-            "page": "test.com",
-            "publisher": {
-              "id": "123456789"
-            }
-          },
-          "user": {
-            "buyeruid": "awesome-user"
-          },
-          "tmax": 1000
-        }
-      },
-      "mockResponse": {
-        "status": 200,
-        "body": {
-          "id": "awesome-resp-id",
-          "seatbid": [
-            {
-              "bid": [
-                {
-                  "id": "a3ae1b4e2fc24a4fb45540082e98e161",
-                  "impid": "banner-impression-id",
-                  "price": 3.5,
-                  "adm": "awesome-markup",
-                  "adomain": [
-                    "awesome.com"
-                  ],
-                  "crid": "20",
-                  "w": 320,
-                  "h": 50,
-                  "ext": {
-                    "prebid": {
-                      "type": "banner"
-                    }
-                  }
-                },
-                {
-                  "id": "b4ae1b4e2fc24a4fb45540082e98e162",
-                  "impid": "video-impression-id",
-                  "price": 3.5,
-                  "adm": "awesome-markup",
-                  "adomain": [
-                    "awesome.com"
-                  ],
-                  "crid": "20",
-                  "w": 640,
-                  "h": 480,
-                  "ext": {
-                    "prebid": {
-                      "type": "video"
-                    }
-                  }
-                }
-              ],
-              "seat": "adagio"
-            }
-          ],
-          "cur": "USD",
-          "ext": {
-            "responsetimemillis": {
-              "adagio": 154
-            },
-            "tmaxrequest": 1000
-          }
-        }
-      }
-    }
-  ],
-  "expectedBidResponses": [
-    {
-      "bids":[
-        {
-          "bid":  {
-            "id": "a3ae1b4e2fc24a4fb45540082e98e161",
-            "impid": "banner-impression-id",
-            "price": 3.5,
-            "adm": "awesome-markup",
-            "crid": "20",
-            "adomain": [
-              "awesome.com"
-            ],
-            "w": 320,
-            "h": 50,
-            "ext": {
-              "prebid": {
-                "type": "banner"
-              }
-            }
-          },
-          "type": "banner"
-        },
-        {
-          "bid":  {
-            "id": "b4ae1b4e2fc24a4fb45540082e98e162",
-            "impid": "video-impression-id",
-            "price": 3.5,
-            "adm": "awesome-markup",
-            "crid": "20",
-            "adomain": [
-              "awesome.com"
-            ],
-            "w": 640,
-            "h": 480,
-            "ext": {
-              "prebid": {
-                "type": "video"
-              }
-            }
-          },
-          "type": "video"
-        }
-      ]
-    }
-  ]
-}
diff --git a/adapters/adagio/adagiotest/exemplary/native-web.json b/adapters/adagio/adagiotest/exemplary/native-web.json
deleted file mode 100644
index 0085aadd..00000000
--- a/adapters/adagio/adagiotest/exemplary/native-web.json
+++ /dev/null
@@ -1,151 +0,0 @@
-{
-  "mockBidRequest": {
-    "test": 1,
-    "id": "some-request-id",
-    "device": {
-      "ua": "test-user-agent",
-      "ipv6": "2607:fb90:f27:4512:d800:cb23:a603:e245",
-      "language": "en",
-      "dnt": 0
-    },
-    "tmax": 1000,
-    "user": {
-      "buyeruid": "awesome-user"
-    },
-    "site": {
-      "page": "test.com",
-      "publisher": {
-        "id": "123456789"
-      }
-    },
-    "imp": [
-      {
-        "id": "some-impression-id",
-        "native": {
-          "ver":"1.1",
-          "request":"{\"adunit\":2,\"assets\":[{\"id\":3,\"img\":{\"h\":120,\"hmin\":0,\"type\":3,\"w\":180,\"wmin\":0},\"required\":1},{\"id\":0,\"required\":1,\"title\":{\"len\":25}},{\"data\":{\"len\":25,\"type\":1},\"id\":4,\"required\":1},{\"data\":{\"len\":140,\"type\":2},\"id\":6,\"required\":1}],\"context\":1,\"layout\":1,\"contextsubtype\":11,\"plcmtcnt\":1,\"plcmttype\":2,\"ver\":\"1.1\",\"ext\":{\"banner\":{\"w\":320,\"h\":50}}}"
-        },
-        "ext": {
-          "bidder": {
-            "organizationId": "1000",
-            "site": "site-name",
-            "placement": "in_article"
-          }
-        }
-      }
-    ]
-  },
-  "httpCalls": [
-    {
-      "expectedRequest": {
-        "headers": {
-          "Content-Type": [
-            "application/json;charset=utf-8"
-          ],
-          "Accept": [
-            "application/json"
-          ],
-          "X-Forwarded-For": [
-            "2607:fb90:f27:4512:d800:cb23:a603:e245"
-          ]
-        },
-        "uri": "http://localhost/prebid_server",
-        "body": {
-          "test": 1,
-          "id": "some-request-id",
-          "device": {
-            "ua": "test-user-agent",
-            "ipv6": "2607:fb90:f27:4512:d800:cb23:a603:e245",
-            "language": "en",
-            "dnt": 0
-          },
-          "imp": [
-            {
-              "id": "some-impression-id",
-              "native": {
-                "ver":"1.1",
-                "request":"{\"adunit\":2,\"assets\":[{\"id\":3,\"img\":{\"h\":120,\"hmin\":0,\"type\":3,\"w\":180,\"wmin\":0},\"required\":1},{\"id\":0,\"required\":1,\"title\":{\"len\":25}},{\"data\":{\"len\":25,\"type\":1},\"id\":4,\"required\":1},{\"data\":{\"len\":140,\"type\":2},\"id\":6,\"required\":1}],\"context\":1,\"layout\":1,\"contextsubtype\":11,\"plcmtcnt\":1,\"plcmttype\":2,\"ver\":\"1.1\",\"ext\":{\"banner\":{\"w\":320,\"h\":50}}}"
-              },
-              "ext": {
-                "bidder": {
-                  "organizationId": "1000",
-                  "site": "site-name",
-                  "placement": "in_article"
-                }
-              }
-            }
-          ],
-          "site": {
-            "page": "test.com",
-            "publisher": {
-              "id": "123456789"
-            }
-          },
-          "user": {
-            "buyeruid": "awesome-user"
-          },
-          "tmax": 1000
-        }
-      },
-      "mockResponse": {
-        "status": 200,
-        "body": {
-          "id": "awesome-resp-id",
-          "seatbid": [
-            {
-              "bid": [
-                {
-                  "id": "a3ae1b4e2fc24a4fb45540082e98e161",
-                  "impid": "some-impression-id",
-                  "price": 3.5,
-                  "adm": "awesome-markup",
-                  "adomain": [
-                    "awesome.com"
-                  ],
-                  "crid": "20",
-                  "ext": {
-                    "prebid": {
-                      "type": "native"
-                    }
-                  }
-                }
-              ],
-              "seat": "adagio"
-            }
-          ],
-          "cur": "USD",
-          "ext": {
-            "responsetimemillis": {
-              "acuityads": 154
-            },
-            "tmaxrequest": 1000
-          }
-        }
-      }
-    }
-  ],
-  "expectedBidResponses": [
-    {
-      "bids":[
-        {
-          "bid":  {
-            "id": "a3ae1b4e2fc24a4fb45540082e98e161",
-            "impid": "some-impression-id",
-            "price": 3.5,
-            "adm": "awesome-markup",
-            "crid": "20",
-            "adomain": [
-              "awesome.com"
-            ],
-            "ext": {
-              "prebid": {
-                "type": "native"
-              }
-            }
-          },
-          "type": "native"
-        }
-      ]
-    }
-  ]
-}
diff --git a/adapters/adagio/adagiotest/exemplary/video-web.json b/adapters/adagio/adagiotest/exemplary/video-web.json
deleted file mode 100644
index 353420ee..00000000
--- a/adapters/adagio/adagiotest/exemplary/video-web.json
+++ /dev/null
@@ -1,175 +0,0 @@
-{
-  "mockBidRequest": {
-    "test": 1,
-    "id": "some-request-id",
-    "device": {
-      "ua": "test-user-agent",
-      "ip": "123.123.123.123",
-      "language": "en",
-      "dnt": 0
-    },
-    "tmax": 1000,
-    "user": {
-      "buyeruid": "awesome-user"
-    },
-    "site": {
-      "page": "test.com",
-      "publisher": {
-        "id": "123456789"
-      }
-    },
-    "imp": [
-      {
-        "id": "some-impression-id",
-        "video": {
-          "mimes": [
-            "video\/mp4"
-          ],
-          "w": 640,
-          "h": 480,
-          "minduration": 120,
-          "maxduration": 150
-        },
-        "ext": {
-          "bidder": {
-            "organizationId": "1000",
-            "site": "site-name",
-            "placement": "in_article"
-          }
-        }
-      }
-    ]
-  },
-  "httpCalls": [
-    {
-      "expectedRequest": {
-        "headers": {
-          "Content-Type": [
-            "application/json;charset=utf-8"
-          ],
-          "Accept": [
-            "application/json"
-          ],
-          "X-Forwarded-For": [
-            "123.123.123.123"
-          ]
-        },
-        "uri": "http://localhost/prebid_server",
-        "body": {
-          "test": 1,
-          "id": "some-request-id",
-          "device": {
-            "ua": "test-user-agent",
-            "ip": "123.123.123.123",
-            "language": "en",
-            "dnt": 0
-          },
-          "imp": [
-            {
-              "id": "some-impression-id",
-              "video": {
-                "mimes": [
-                  "video\/mp4"
-                ],
-                "minduration": 120,
-                "maxduration": 150,
-                "w": 640,
-                "h": 480
-              },
-              "ext": {
-                "bidder": {
-                  "organizationId": "1000",
-                  "site": "site-name",
-                  "placement": "in_article"
-                }
-              }
-            }
-          ],
-          "site": {
-            "page": "test.com",
-            "publisher": {
-              "id": "123456789"
-            }
-          },
-          "user": {
-            "buyeruid": "awesome-user"
-          },
-          "tmax": 1000
-        }
-      },
-      "mockResponse": {
-        "status": 200,
-        "body": {
-          "id": "awesome-resp-id",
-          "seatbid": [
-            {
-              "bid": [
-                {
-                  "id": "a3ae1b4e2fc24a4fb45540082e98e161",
-                  "impid": "some-impression-id",
-                  "price": 3.5,
-                  "adm": "awesome-markup",
-                  "adomain": [
-                    "awesome.com"
-                  ],
-                  "crid": "20",
-                  "w": 1280,
-                  "h": 720,
-                  "ext": {
-                    "adagio": {
-                      "outstream": {
-                        "bvwUrl": "https://cool.io"
-                      }
-                    },
-                    "prebid": {
-                      "type": "video"
-                    }
-                  }
-                }
-              ],
-              "seat": "adagio"
-            }
-          ],
-          "cur": "USD",
-          "ext": {
-            "responsetimemillis": {
-              "adagio": 154
-            },
-            "tmaxrequest": 1000
-          }
-        }
-      }
-    }
-  ],
-  "expectedBidResponses": [
-    {
-      "bids": [
-        {
-          "bid": {
-            "id": "a3ae1b4e2fc24a4fb45540082e98e161",
-            "impid": "some-impression-id",
-            "price": 3.5,
-            "adm": "awesome-markup",
-            "adomain": [
-              "awesome.com"
-            ],
-            "crid": "20",
-            "w": 1280,
-            "h": 720,
-            "ext": {
-              "adagio": {
-                "outstream": {
-                  "bvwUrl": "https://cool.io"
-                }
-              },
-              "prebid": {
-                "type": "video"
-              }
-            }
-          },
-          "type":"video"
-        }
-      ]
-    }
-  ]
-}
\ No newline at end of file
diff --git a/adapters/adagio/adagiotest/supplemental/response-miss-ext-bid-type.json b/adapters/adagio/adagiotest/supplemental/response-miss-ext-bid-type.json
deleted file mode 100644
index 7b1267f6..00000000
--- a/adapters/adagio/adagiotest/supplemental/response-miss-ext-bid-type.json
+++ /dev/null
@@ -1,130 +0,0 @@
-{
-  "mockBidRequest": {
-    "test": 1,
-    "id": "some-request-id",
-    "device": {
-      "ua": "test-user-agent",
-      "ip": "123.123.123.123",
-      "language": "en",
-      "dnt": 0
-    },
-    "tmax": 1000,
-    "user": {
-      "buyeruid": "awesome-user"
-    },
-    "site": {
-      "page": "test.com",
-      "publisher": {
-        "id": "123456789"
-      }
-    },
-    "imp": [
-      {
-        "id": "some-impression-id",
-        "banner": {
-          "w": 320,
-          "h": 50
-        },
-        "ext": {
-          "bidder": {
-            "organizationId": "1000",
-            "site": "site-name",
-            "placement": "ban_atf"
-          }
-        }
-      }
-    ]
-  },
-  "httpCalls": [
-    {
-      "expectedRequest": {
-        "headers": {
-          "Content-Type": [
-            "application/json;charset=utf-8"
-          ],
-          "Accept": [
-            "application/json"
-          ],
-          "X-Forwarded-For": [
-            "123.123.123.123"
-          ]
-        },
-        "uri": "http://localhost/prebid_server",
-        "body": {
-          "test": 1,
-          "id": "some-request-id",
-          "device": {
-            "ua": "test-user-agent",
-            "ip": "123.123.123.123",
-            "language": "en",
-            "dnt": 0
-          },
-          "imp": [
-            {
-              "id": "some-impression-id",
-              "banner": {
-                "w": 320,
-                "h": 50
-              },
-              "ext": {
-                "bidder": {
-                  "organizationId": "1000",
-                  "site": "site-name",
-                  "placement": "ban_atf"
-                }
-              }
-            }
-          ],
-          "site": {
-            "page": "test.com",
-            "publisher": {
-              "id": "123456789"
-            }
-          },
-          "user": {
-            "buyeruid": "awesome-user"
-          },
-          "tmax": 1000
-        }
-      },
-      "mockResponse": {
-        "status": 200,
-        "body": {
-          "id": "awesome-resp-id",
-          "seatbid": [
-            {
-              "bid": [
-                {
-                  "id": "a3ae1b4e2fc24a4fb45540082e98e161",
-                  "impid": "some-impression-id",
-                  "price": 3.5,
-                  "adm": "awesome-markup",
-                  "adomain": [
-                    "awesome.com"
-                  ],
-                  "crid": "20",
-                  "w": 320,
-                  "h": 50,
-                  "ext": {}
-                }
-              ],
-              "seat": "adagio"
-            }
-          ],
-          "cur": "USD",
-          "ext": {
-            "responsetimemillis": {
-              "adagio": 154
-            },
-            "tmaxrequest": 1000
-          }
-        }
-      }
-    }
-  ],
-  "expectedBidResponses": [
-    {
-      "bids": []
-    }
-  ]
-}
diff --git a/adapters/adagio/adagiotest/supplemental/status-204.json b/adapters/adagio/adagiotest/supplemental/status-204.json
deleted file mode 100644
index 4d604a01..00000000
--- a/adapters/adagio/adagiotest/supplemental/status-204.json
+++ /dev/null
@@ -1,62 +0,0 @@
-{
-  "mockBidRequest": {
-    "test": 1,
-    "id": "some-request-id",
-    "imp": [
-      {
-        "id": "some-impression-id",
-        "banner": {
-          "w": 320,
-          "h": 50
-        },
-        "ext": {
-          "bidder": {
-            "organizationId": "1000",
-            "site": "site-name",
-            "placement": "ban_atf"
-          }
-        }
-      }
-    ]
-  },
-  "httpCalls": [
-    {
-      "expectedRequest": {
-        "headers": {
-          "Content-Type": [
-            "application/json;charset=utf-8"
-          ],
-          "Accept": [
-            "application/json"
-          ]
-        },
-        "uri": "http://localhost/prebid_server",
-        "body": {
-          "test": 1,
-          "id": "some-request-id",
-          "imp": [
-            {
-              "id": "some-impression-id",
-              "banner": {
-                "w": 320,
-                "h": 50
-              },
-              "ext": {
-                "bidder": {
-                  "organizationId": "1000",
-                  "site": "site-name",
-                  "placement": "ban_atf"
-                }
-              }
-            }
-          ]
-        }
-      },
-      "mockResponse": {
-        "status": 204,
-        "body": {}
-      }
-    }
-  ],
-  "expectedBidResponses": []
-}
diff --git a/adapters/adagio/adagiotest/supplemental/status-400.json b/adapters/adagio/adagiotest/supplemental/status-400.json
deleted file mode 100644
index 093c5458..00000000
--- a/adapters/adagio/adagiotest/supplemental/status-400.json
+++ /dev/null
@@ -1,67 +0,0 @@
-{
-  "mockBidRequest": {
-    "test": 1,
-    "id": "some-request-id",
-    "imp": [
-      {
-        "id": "some-impression-id",
-        "banner": {
-          "w": 320,
-          "h": 50
-        },
-        "ext": {
-          "bidder": {
-            "organizationId": "1000",
-            "site": "site-name",
-            "placement": "ban_atf"
-          }
-        }
-      }
-    ]
-  },
-  "httpCalls": [
-    {
-      "expectedRequest": {
-        "headers": {
-          "Content-Type": [
-            "application/json;charset=utf-8"
-          ],
-          "Accept": [
-            "application/json"
-          ]
-        },
-        "uri": "http://localhost/prebid_server",
-        "body": {
-          "test": 1,
-          "id": "some-request-id",
-          "imp": [
-            {
-              "id": "some-impression-id",
-              "banner": {
-                "w": 320,
-                "h": 50
-              },
-              "ext": {
-                "bidder": {
-                  "organizationId": "1000",
-                  "site": "site-name",
-                  "placement": "ban_atf"
-                }
-              }
-            }
-          ]
-        }
-      },
-      "mockResponse": {
-        "status": 400,
-        "body": "bad request"
-      }
-    }
-  ],
-  "expectedMakeBidsErrors": [
-    {
-      "value": "Unexpected status code: 400. Run with request.debug = 1 for more info",
-      "comparison": "literal"
-    }
-  ]
-}
\ No newline at end of file
diff --git a/adapters/adagio/adagiotest/supplemental/status-401.json b/adapters/adagio/adagiotest/supplemental/status-401.json
deleted file mode 100644
index a33aca20..00000000
--- a/adapters/adagio/adagiotest/supplemental/status-401.json
+++ /dev/null
@@ -1,67 +0,0 @@
-{
-  "mockBidRequest": {
-    "test": 1,
-    "id": "some-request-id",
-    "imp": [
-      {
-        "id": "some-impression-id",
-        "banner": {
-          "w": 320,
-          "h": 50
-        },
-        "ext": {
-          "bidder": {
-            "organizationId": "1000",
-            "site": "site-name",
-            "placement": "ban_atf"
-          }
-        }
-      }
-    ]
-  },
-  "httpCalls": [
-    {
-      "expectedRequest": {
-        "headers": {
-          "Content-Type": [
-            "application/json;charset=utf-8"
-          ],
-          "Accept": [
-            "application/json"
-          ]
-        },
-        "uri": "http://localhost/prebid_server",
-        "body": {
-          "test": 1,
-          "id": "some-request-id",
-          "imp": [
-            {
-              "id": "some-impression-id",
-              "banner": {
-                "w": 320,
-                "h": 50
-              },
-              "ext": {
-                "bidder": {
-                  "organizationId": "1000",
-                  "site": "site-name",
-                  "placement": "ban_atf"
-                }
-              }
-            }
-          ]
-        }
-      },
-      "mockResponse": {
-        "status": 401,
-        "body": "unauthorized"
-      }
-    }
-  ],
-  "expectedMakeBidsErrors": [
-    {
-      "value": "Unexpected status code: 401. Run with request.debug = 1 for more info",
-      "comparison": "literal"
-    }
-  ]
-}
\ No newline at end of file
diff --git a/adapters/adagio/adagiotest/supplemental/status-403.json b/adapters/adagio/adagiotest/supplemental/status-403.json
deleted file mode 100644
index 59c5a9cb..00000000
--- a/adapters/adagio/adagiotest/supplemental/status-403.json
+++ /dev/null
@@ -1,67 +0,0 @@
-{
-  "mockBidRequest": {
-    "test": 1,
-    "id": "some-request-id",
-    "imp": [
-      {
-        "id": "some-impression-id",
-        "banner": {
-          "w": 320,
-          "h": 50
-        },
-        "ext": {
-          "bidder": {
-            "organizationId": "1000",
-            "site": "site-name",
-            "placement": "ban_atf"
-          }
-        }
-      }
-    ]
-  },
-  "httpCalls": [
-    {
-      "expectedRequest": {
-        "headers": {
-          "Content-Type": [
-            "application/json;charset=utf-8"
-          ],
-          "Accept": [
-            "application/json"
-          ]
-        },
-        "uri": "http://localhost/prebid_server",
-        "body": {
-          "test": 1,
-          "id": "some-request-id",
-          "imp": [
-            {
-              "id": "some-impression-id",
-              "banner": {
-                "w": 320,
-                "h": 50
-              },
-              "ext": {
-                "bidder": {
-                  "organizationId": "1000",
-                  "site": "site-name",
-                  "placement": "ban_atf"
-                }
-              }
-            }
-          ]
-        }
-      },
-      "mockResponse": {
-        "status": 403,
-        "body": "forbidden"
-      }
-    }
-  ],
-  "expectedMakeBidsErrors": [
-    {
-      "value": "Unexpected status code: 403. Run with request.debug = 1 for more info",
-      "comparison": "literal"
-    }
-  ]
-}
\ No newline at end of file
diff --git a/adapters/adagio/adagiotest/supplemental/status-500.json b/adapters/adagio/adagiotest/supplemental/status-500.json
deleted file mode 100644
index 0077d745..00000000
--- a/adapters/adagio/adagiotest/supplemental/status-500.json
+++ /dev/null
@@ -1,68 +0,0 @@
-{
-  "mockBidRequest": {
-    "test": 1,
-    "debug": 1,
-    "id": "some-request-id",
-    "imp": [
-      {
-        "id": "some-impression-id",
-        "banner": {
-          "w": 320,
-          "h": 50
-        },
-        "ext": {
-          "bidder": {
-            "organizationId": "1000",
-            "site": "site-name",
-            "placement": "ban_atf"
-          }
-        }
-      }
-    ]
-  },
-  "httpCalls": [
-    {
-      "expectedRequest": {
-        "headers": {
-          "Content-Type": [
-            "application/json;charset=utf-8"
-          ],
-          "Accept": [
-            "application/json"
-          ]
-        },
-        "uri": "http://localhost/prebid_server",
-        "body": {
-          "test": 1,
-          "id": "some-request-id",
-          "imp": [
-            {
-              "id": "some-impression-id",
-              "banner": {
-                "w": 320,
-                "h": 50
-              },
-              "ext": {
-                "bidder": {
-                  "organizationId": "1000",
-                  "site": "site-name",
-                  "placement": "ban_atf"
-                }
-              }
-            }
-          ]
-        }
-      },
-      "mockResponse": {
-        "status": 500,
-        "body": "internal error"
-      }
-    }
-  ],
-  "expectedMakeBidsErrors": [
-    {
-      "value": "Unexpected status code: 500. Run with request.debug = 1 for more info",
-      "comparison": "literal"
-    }
-  ]
-}
\ No newline at end of file
diff --git a/adapters/adagio/adagiotest/supplemental/status-503.json b/adapters/adagio/adagiotest/supplemental/status-503.json
deleted file mode 100644
index d2a52893..00000000
--- a/adapters/adagio/adagiotest/supplemental/status-503.json
+++ /dev/null
@@ -1,67 +0,0 @@
-{
-  "mockBidRequest": {
-    "test": 1,
-    "id": "some-request-id",
-    "imp": [
-      {
-        "id": "some-impression-id",
-        "banner": {
-          "w": 320,
-          "h": 50
-        },
-        "ext": {
-          "bidder": {
-            "organizationId": "1000",
-            "site": "site-name",
-            "placement": "ban_atf"
-          }
-        }
-      }
-    ]
-  },
-  "httpCalls": [
-    {
-      "expectedRequest": {
-        "headers": {
-          "Content-Type": [
-            "application/json;charset=utf-8"
-          ],
-          "Accept": [
-            "application/json"
-          ]
-        },
-        "uri": "http://localhost/prebid_server",
-        "body": {
-          "test": 1,
-          "id": "some-request-id",
-          "imp": [
-            {
-              "id": "some-impression-id",
-              "banner": {
-                "w": 320,
-                "h": 50
-              },
-              "ext": {
-                "bidder": {
-                  "organizationId": "1000",
-                  "site": "site-name",
-                  "placement": "ban_atf"
-                }
-              }
-            }
-          ]
-        }
-      },
-      "mockResponse": {
-        "status": 503,
-        "body": "Service unavailable"
-      }
-    }
-  ],
-  "expectedMakeBidsErrors": [
-    {
-      "value": "Unexpected status code: 503. Run with request.debug = 1 for more info",
-      "comparison": "literal"
-    }
-  ]
-}
diff --git a/adapters/adagio/adagiotest/supplemental/status-504.json b/adapters/adagio/adagiotest/supplemental/status-504.json
deleted file mode 100644
index 1b779d5c..00000000
--- a/adapters/adagio/adagiotest/supplemental/status-504.json
+++ /dev/null
@@ -1,67 +0,0 @@
-{
-  "mockBidRequest": {
-    "test": 1,
-    "id": "some-request-id",
-    "imp": [
-      {
-        "id": "some-impression-id",
-        "banner": {
-          "w": 320,
-          "h": 50
-        },
-        "ext": {
-          "bidder": {
-            "organizationId": "1000",
-            "site": "site-name",
-            "placement": "ban_atf"
-          }
-        }
-      }
-    ]
-  },
-  "httpCalls": [
-    {
-      "expectedRequest": {
-        "headers": {
-          "Content-Type": [
-            "application/json;charset=utf-8"
-          ],
-          "Accept": [
-            "application/json"
-          ]
-        },
-        "uri": "http://localhost/prebid_server",
-        "body": {
-          "test": 1,
-          "id": "some-request-id",
-          "imp": [
-            {
-              "id": "some-impression-id",
-              "banner": {
-                "w": 320,
-                "h": 50
-              },
-              "ext": {
-                "bidder": {
-                  "organizationId": "1000",
-                  "site": "site-name",
-                  "placement": "ban_atf"
-                }
-              }
-            }
-          ]
-        }
-      },
-      "mockResponse": {
-        "status": 504,
-        "body": "gateway timeout"
-      }
-    }
-  ],
-  "expectedMakeBidsErrors": [
-    {
-      "value": "Unexpected status code: 504. Run with request.debug = 1 for more info",
-      "comparison": "literal"
-    }
-  ]
-}
\ No newline at end of file
diff --git a/adapters/adagio/params_test.go b/adapters/adagio/params_test.go
deleted file mode 100644
index ee8f702e..00000000
--- a/adapters/adagio/params_test.go
+++ /dev/null
@@ -1,57 +0,0 @@
-package adagio
-
-import (
-	"encoding/json"
-	"testing"
-
-	"github.com/prebid/prebid-server/openrtb_ext"
-)
-
-var validParams = []string{
-	`{ "organizationId": "1234", "site": "adagio-io", "placement": "ban_atf" }`,
-	`{ "organizationId": "1234", "site": "adagio-io", "placement": "ban_atf", "_unknown": "ban_atf"}`,
-	`{ "organizationId": "1234", "site": "adagio-io", "placement": "ban_atf", "features": {"a": "a", "b": "b"}}`,
-}
-
-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.BidderAdagio, json.RawMessage(validParam)); err != nil {
-			t.Errorf("Schema rejected Adagio params: %s", validParam)
-		}
-	}
-}
-
-var invalidParams = []string{
-	``,
-	`null`,
-	`true`,
-	`5`,
-	`4.2`,
-	`[]`,
-	`{}`,
-	`{"adCode": "string", "seatCode": 5, "originalPublisherid": "string"}`,
-	`{ "organizationId": "", "site": "", "placement": "" }`,
-	`{ "organizationId": "", "site": "2", "placement": "3" }`,
-	`{ "organizationId": "1", "site": "", "placement": "3" }`,
-	`{ "organizationId": "1", "site": "2", "placement": "" }`,
-	`{ "organizationId": 1, "site": "2", "placement": "3" }`,
-	`{ "organizationId": "1234", "site": "adagio-io", "placement": "ban_atf", "features": {"a": "a", "notastring": true}}`,
-}
-
-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.BidderAdagio, json.RawMessage(invalidParam)); err == nil {
-			t.Errorf("Schema allowed unexpected params: %s", invalidParam)
-		}
-	}
-}
diff --git a/adapters/adyoulike/adyouliketest/supplemental/status-no-content.json b/adapters/adyoulike/adyouliketest/supplemental/status-no-content.json
index 9ef51e88..d7d7df19 100644
--- a/adapters/adyoulike/adyouliketest/supplemental/status-no-content.json
+++ b/adapters/adyoulike/adyouliketest/supplemental/status-no-content.json
@@ -56,11 +56,5 @@
   ],
 
   "expectedBidResponses": [],
-
-  "expectedMakeBidsErrors": [
-    {
-      "value": "MakeBids error: No Content",
-      "comparison": "literal"
-    }
-  ]
+  "expectedMakeBidsErrors": []
 }
\ No newline at end of file
diff --git a/adapters/algorix/algorixtest/exemplary/sample-banner-with-mediatype.json b/adapters/algorix/algorixtest/exemplary/sample-banner-with-mediatype.json
new file mode 100644
index 00000000..c9f9f877
--- /dev/null
+++ b/adapters/algorix/algorixtest/exemplary/sample-banner-with-mediatype.json
@@ -0,0 +1,105 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [{"w": 320, "h": 50}]
+        },
+        "video": {
+          "mimes": [
+            "video/mp4"
+          ],
+          "w": 320,
+          "h": 480
+        },
+        "ext": {
+          "bidder": {
+            "sid": "testSid",
+            "token": "testToken",
+            "region": "APAC"
+          }
+        }
+      }
+    ]
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://apac.xyz.test.com?sid=testSid&token=testToken",
+        "body": {
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id":"test-imp-id",
+              "banner": {
+                "format": [{"w": 320, "h": 50}],
+                "w": 320,
+                "h": 50
+              },
+              "video": {
+                "mimes": [
+                  "video/mp4"
+                ],
+                "w": 320,
+                "h": 480
+              },
+              "ext": {
+                "bidder": {
+                  "sid": "testSid",
+                  "token": "testToken",
+                  "region": "APAC"
+                }
+              }
+            }
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "seatbid": [
+            {
+              "seat": "ttx",
+              "bid": [{
+                "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800",
+                "impid": "test-imp-id",
+                "price": 0.5,
+                "adm": "some-ads",
+                "crid": "crid_testid",
+                "ext": {
+                  "mediaType": "video"
+                }
+              }]
+            }
+          ],
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800",
+            "impid": "test-imp-id",
+            "price": 0.5,
+            "adm": "some-ads",
+            "crid": "crid_testid",
+            "ext": {
+              "mediaType": "video"
+            }
+          },
+          "type": "video"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/algorix/algorixtest/exemplary/sample-rewarded-video.json b/adapters/algorix/algorixtest/exemplary/sample-rewarded-video.json
new file mode 100644
index 00000000..0d98de60
--- /dev/null
+++ b/adapters/algorix/algorixtest/exemplary/sample-rewarded-video.json
@@ -0,0 +1,102 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "video": {
+          "w": 320,
+          "h": 480,
+          "mimes": [
+            "video/mp4"
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "sid": "testSid",
+            "token": "testToken",
+            "placementId": "testPlacementId",
+            "appId": "testAppId"
+          },
+          "prebid": {
+            "is_rewarded_inventory": 1
+          }
+        }
+      }
+    ]
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://xyz.test.com?sid=testSid&token=testToken",
+        "body": {
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id":"test-imp-id",
+              "video": {
+                "w": 320,
+                "h": 480,
+                "mimes": [
+                  "video/mp4"
+                ],
+                "ext": {
+                  "rewarded": 1
+                }
+              },
+              "ext": {
+                "bidder": {
+                  "sid": "testSid",
+                  "token": "testToken",
+                  "placementId": "testPlacementId",
+                  "appId": "testAppId"
+                },
+                "prebid": {
+                  "is_rewarded_inventory": 1
+                }
+              }
+            }
+          ]
+        }
+      },
+      "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": "some-ads",
+                "crid": "crid_testid"
+              }]
+            }
+          ],
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800",
+            "impid": "test-imp-id",
+            "price": 1.2,
+            "adm": "some-ads",
+            "crid": "crid_testid"
+          },
+          "type": "video"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/algorix/algorixtest/supplemental/multiformat-no-mediatype-response.json b/adapters/algorix/algorixtest/supplemental/multiformat-no-mediatype-response.json
new file mode 100644
index 00000000..3828c52d
--- /dev/null
+++ b/adapters/algorix/algorixtest/supplemental/multiformat-no-mediatype-response.json
@@ -0,0 +1,90 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "video": {
+          "mimes": [
+            "video/mp4"
+          ],
+          "w": 320,
+          "h": 480
+        },
+        "banner": {
+          "format": [{"w": 320, "h": 50}]
+        },
+        "ext": {
+          "bidder": {
+            "sid": "testSid",
+            "token": "testToken",
+            "region": "APAC"
+          }
+        }
+      }
+    ]
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://apac.xyz.test.com?sid=testSid&token=testToken",
+        "body": {
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id":"test-imp-id",
+              "banner": {
+                "format": [{"w": 320, "h": 50}],
+                "w": 320,
+                "h": 50
+              },
+              "video": {
+                "mimes": [
+                  "video/mp4"
+                ],
+                "w": 320,
+                "h": 480
+              },
+              "ext": {
+                "bidder": {
+                  "sid": "testSid",
+                  "token": "testToken",
+                  "region": "APAC"
+                }
+              }
+            }
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "seatbid": [
+            {
+              "seat": "ttx",
+              "bid": [{
+                "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800",
+                "impid": "test-imp-id",
+                "price": 0.5,
+                "adm": "some-ads",
+                "crid": "crid_testid",
+                "ext": {
+                }
+              }]
+            }
+          ],
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+
+  "expectedMakeBidsErrors": [
+    {
+      "comparison": "literal",
+      "value": "unable to fetch mediaType in multi-format: test-imp-id"
+    }
+  ]
+}
diff --git a/adapters/algorix/algorixtest/supplemental/sample-banner-with-other-mediatype.json b/adapters/algorix/algorixtest/supplemental/sample-banner-with-other-mediatype.json
new file mode 100644
index 00000000..df535318
--- /dev/null
+++ b/adapters/algorix/algorixtest/supplemental/sample-banner-with-other-mediatype.json
@@ -0,0 +1,91 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [{"w": 320, "h": 50}]
+        },
+        "ext": {
+          "bidder": {
+            "sid": "testSid",
+            "token": "testToken",
+            "region": "APAC"
+          }
+        }
+      }
+    ]
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://apac.xyz.test.com?sid=testSid&token=testToken",
+        "body": {
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id":"test-imp-id",
+              "banner": {
+                "format": [{"w": 320, "h": 50}],
+                "w": 320,
+                "h": 50
+              },
+              "ext": {
+                "bidder": {
+                  "sid": "testSid",
+                  "token": "testToken",
+                  "region": "APAC"
+                }
+              }
+            }
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "seatbid": [
+            {
+              "seat": "ttx",
+              "bid": [{
+                "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800",
+                "impid": "test-imp-id",
+                "price": 0.5,
+                "adm": "some-ads",
+                "crid": "crid_testid",
+                "ext": {
+                  "mediaType": "video"
+                }
+              }]
+            }
+          ],
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800",
+            "impid": "test-imp-id",
+            "price": 0.5,
+            "adm": "some-ads",
+            "crid": "crid_testid",
+            "ext": {
+              "mediaType": "video"
+            }
+          },
+          "type": "video"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/vidoomy/params_test.go b/adapters/vidoomy/params_test.go
new file mode 100644
index 00000000..d910b654
--- /dev/null
+++ b/adapters/vidoomy/params_test.go
@@ -0,0 +1,57 @@
+package vidoomy
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+// This file actually intends to test static/bidder-params/vidoomy.json
+//
+// These also validate the format of the external API: request.imp[i].ext.vidoomy
+
+// TestValidParams makes sure that the vidoomy 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.BidderVidoomy, json.RawMessage(validParam)); err != nil {
+			t.Errorf("Schema rejected vidoomy params: %s", validParam)
+		}
+	}
+}
+
+// TestInvalidParams makes sure that the Vidoomy schema rejects all the imp.ext fields we don't support.
+func TestInvalidParams(t *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+	if err != nil {
+		t.Fatalf("Failed to fetch the json-schemas. %v", err)
+	}
+
+	for _, invalidParam := range invalidParams {
+		if err := validator.Validate(openrtb_ext.BidderVidoomy, json.RawMessage(invalidParam)); err == nil {
+			t.Errorf("Schema allowed unexpected params: %s", invalidParam)
+		}
+	}
+}
+
+var validParams = []string{
+	`{"zoneId":"123"}`,
+}
+
+var invalidParams = []string{
+	``,
+	`null`,
+	`true`,
+	`5`,
+	`4.2`,
+	`[]`,
+	`{}`,
+	`{"notmid":"123"}`,
+	`{"zoneId":123}`,
+	`{"zoneId":""}`,
+}
diff --git a/adapters/vidoomy/vidoomy_test.go b/adapters/vidoomy/vidoomy_test.go
new file mode 100644
index 00000000..c9181395
--- /dev/null
+++ b/adapters/vidoomy/vidoomy_test.go
@@ -0,0 +1,34 @@
+package vidoomy
+
+import (
+	"testing"
+
+	"github.com/influxdata/influxdb/pkg/testing/assert"
+	"github.com/prebid/prebid-server/adapters/adapterstest"
+	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+func TestVidoomyBidderEndpointConfig(t *testing.T) {
+	bidder, buildErr := Builder(openrtb_ext.BidderVidoomy, config.Adapter{
+		Endpoint: "http://localhost/bid",
+	})
+
+	if buildErr != nil {
+		t.Fatalf("Builder returned unexpected error %v", buildErr)
+	}
+
+	bidderVidoomy := bidder.(*adapter)
+
+	assert.Equal(t, "http://localhost/bid", bidderVidoomy.endpoint)
+}
+
+func TestJsonSamples(t *testing.T) {
+	bidder, buildErr := Builder(openrtb_ext.BidderVidoomy, config.Adapter{})
+
+	if buildErr != nil {
+		t.Fatalf("Builder returned unexpected error %v", buildErr)
+	}
+
+	adapterstest.RunJSONBidderTest(t, "vidoomytest", bidder)
+}
diff --git a/adapters/vidoomy/vidoomytest/exemplary/multi-impression-video-banner.json b/adapters/vidoomy/vidoomytest/exemplary/multi-impression-video-banner.json
new file mode 100644
index 00000000..723248dd
--- /dev/null
+++ b/adapters/vidoomy/vidoomytest/exemplary/multi-impression-video-banner.json
@@ -0,0 +1,226 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [{"w": 728, "h": 90}]
+        },
+        "ext": {
+          "bidder": {
+            "zoneId": "zone-id-123"
+          }
+        }
+      },
+      {
+        "id": "test-imp-video-id",
+        "video": {
+          "w": 900,
+          "h": 250,
+          "mimes": [
+            "video/x-flv",
+            "video/mp4"
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "zoneId": "zone-id-123"
+          }
+        }
+      }
+    ],
+    "device": {
+      "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36",
+      "ip": "127.0.0.1"
+    },
+    "site": {
+      "id": "test-site-id"
+    }
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "headers": {
+          "Accept": [
+            "application/json"
+          ],
+          "Content-Type": [
+            "application/json;charset=utf-8"
+          ],
+          "User-Agent": [
+            "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36"
+          ],
+          "X-Openrtb-Version": [
+            "2.5"
+          ],
+          "X-Forwarded-For": [
+            "127.0.0.1"
+          ]
+        },
+        "uri": "",
+        "body": {
+          "device": {
+            "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36",
+            "ip": "127.0.0.1"
+          },
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id": "test-imp-id",
+              "banner": {
+                "format": [{"w": 728, "h": 90}],
+                "w": 728,
+                "h": 90
+              },
+              "ext": {
+                "bidder": {
+                  "zoneId": "zone-id-123"
+                }
+              }
+            }
+          ],
+          "site": {
+            "id": "test-site-id"
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "seatbid": [
+            {
+              "seat": "vidoomy",
+              "bid": [{
+                "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800",
+                "impid": "test-imp-id",
+                "price": 0.500000,
+                "adm": "some-test-ad",
+                "crid": "crid_10",
+                "h": 90,
+                "w": 728
+              }]
+            }
+          ],
+          "cur": "USD"
+        }
+      }
+    },
+    {
+      "expectedRequest": {
+        "headers": {
+          "Accept": [
+            "application/json"
+          ],
+          "Content-Type": [
+            "application/json;charset=utf-8"
+          ],
+          "User-Agent": [
+            "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36"
+          ],
+          "X-Openrtb-Version": [
+            "2.5"
+          ],
+          "X-Forwarded-For": [
+            "127.0.0.1"
+          ]
+        },
+        "uri": "",
+        "body": {
+          "device": {
+            "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36",
+            "ip": "127.0.0.1"
+          },
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id": "test-imp-video-id",
+              "video": {
+                "w": 900,
+                "h": 250,
+                "mimes": [
+                  "video/x-flv",
+                  "video/mp4"
+                ]
+              },
+              "ext": {
+                "bidder": {
+                  "zoneId": "zone-id-123"
+                }
+              }
+            }
+          ],
+          "site": {
+            "id": "test-site-id"
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "cur": "USD",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "fe69dd6d-e85c-4186-80a7-605b530bc23b",
+                  "crid": "72745",
+                  "adomain": [
+                    "ad-domain.com"
+                  ],
+                  "price": 3,
+                  "impid": "test-imp-video-id",
+                  "adid": "564",
+                  "adm": "<?xml version=\"1.0\"?><VAST version=\"2.0\"></VAST>"
+                }
+              ],
+              "seat": "vidoomy"
+            }
+          ],
+          "id": "test-request-id"
+        }
+      }
+    }
+  ],
+
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800",
+            "impid": "test-imp-id",
+            "price": 0.5,
+            "adm": "some-test-ad",
+            "crid": "crid_10",
+            "w": 728,
+            "h": 90
+          },
+          "type": "banner"
+        }
+      ]
+    },
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "fe69dd6d-e85c-4186-80a7-605b530bc23b",
+            "crid": "72745",
+            "adomain": [
+              "ad-domain.com"
+            ],
+            "price": 3,
+            "impid": "test-imp-video-id",
+            "adid": "564",
+            "adm": "<?xml version=\"1.0\"?><VAST version=\"2.0\"></VAST>"
+          },
+          "type": "video"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/vidoomy/vidoomytest/exemplary/simple-app-banner.json b/adapters/vidoomy/vidoomytest/exemplary/simple-app-banner.json
new file mode 100644
index 00000000..145524ea
--- /dev/null
+++ b/adapters/vidoomy/vidoomytest/exemplary/simple-app-banner.json
@@ -0,0 +1,116 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [{"w": 728, "h": 90}]
+        },
+        "ext": {
+          "bidder": {
+            "zoneId": "zone-id-123"
+          }
+        }
+      }
+    ],
+    "device": {
+      "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36",
+      "ip": "127.0.0.1"
+    },
+    "app": {
+      "id": "test-app-id"
+    }
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "headers": {
+          "Accept": [
+            "application/json"
+          ],
+          "Content-Type": [
+            "application/json;charset=utf-8"
+          ],
+          "User-Agent": [
+            "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36"
+          ],
+          "X-Openrtb-Version": [
+            "2.5"
+          ],
+          "X-Forwarded-For": [
+            "127.0.0.1"
+          ]
+        },
+        "uri": "",
+        "body": {
+          "device": {
+            "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36",
+            "ip": "127.0.0.1"
+          },
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id": "test-imp-id",
+              "banner": {
+                "format": [{"w": 728, "h": 90}],
+                "w": 728,
+                "h": 90
+              },
+              "ext": {
+                "bidder": {
+                  "zoneId": "zone-id-123"
+                }
+              }
+            }
+          ],
+          "app": {
+            "id": "test-app-id"
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "seatbid": [
+            {
+              "seat": "vidoomy",
+              "bid": [{
+                "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800",
+                "impid": "test-imp-id",
+                "price": 0.500000,
+                "adm": "some-test-ad",
+                "crid": "crid_10",
+                "h": 90,
+                "w": 728
+              }]
+            }
+          ],
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800",
+            "impid": "test-imp-id",
+            "price": 0.5,
+            "adm": "some-test-ad",
+            "crid": "crid_10",
+            "w": 728,
+            "h": 90
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/vidoomy/vidoomytest/exemplary/simple-banner.json b/adapters/vidoomy/vidoomytest/exemplary/simple-banner.json
new file mode 100644
index 00000000..36d27856
--- /dev/null
+++ b/adapters/vidoomy/vidoomytest/exemplary/simple-banner.json
@@ -0,0 +1,116 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [{"w": 728, "h": 90}]
+        },
+        "ext": {
+          "bidder": {
+            "zoneId": "zone-id-123"
+          }
+        }
+      }
+    ],
+    "device": {
+      "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36",
+      "ip": "127.0.0.1"
+    },
+    "site": {
+      "id": "test-site-id"
+    }
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "headers": {
+          "Accept": [
+            "application/json"
+          ],
+          "Content-Type": [
+            "application/json;charset=utf-8"
+          ],
+          "User-Agent": [
+            "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36"
+          ],
+          "X-Openrtb-Version": [
+            "2.5"
+          ],
+          "X-Forwarded-For": [
+            "127.0.0.1"
+          ]
+        },
+        "uri": "",
+        "body": {
+          "device": {
+            "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36",
+            "ip": "127.0.0.1"
+          },
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id": "test-imp-id",
+              "banner": {
+                "format": [{"w": 728, "h": 90}],
+                "w": 728,
+                "h": 90
+              },
+              "ext": {
+                "bidder": {
+                  "zoneId": "zone-id-123"
+                }
+              }
+            }
+          ],
+          "site": {
+            "id": "test-site-id"
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "seatbid": [
+            {
+              "seat": "vidoomy",
+              "bid": [{
+                "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800",
+                "impid": "test-imp-id",
+                "price": 0.500000,
+                "adm": "some-test-ad",
+                "crid": "crid_10",
+                "h": 90,
+                "w": 728
+              }]
+            }
+          ],
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800",
+            "impid": "test-imp-id",
+            "price": 0.5,
+            "adm": "some-test-ad",
+            "crid": "crid_10",
+            "w": 728,
+            "h": 90
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/vidoomy/vidoomytest/exemplary/simple-site-video.json b/adapters/vidoomy/vidoomytest/exemplary/simple-site-video.json
new file mode 100644
index 00000000..d2d6e028
--- /dev/null
+++ b/adapters/vidoomy/vidoomytest/exemplary/simple-site-video.json
@@ -0,0 +1,121 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-video-id",
+        "video": {
+          "w": 900,
+          "h": 250,
+          "mimes": [
+            "video/x-flv",
+            "video/mp4"
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "zoneId": "zone-id-123"
+          }
+        }
+      }
+    ],
+    "site": {
+      "id": "test",
+      "publisher": {
+        "id": "123"
+      }
+    }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "method": "GET",
+        "headers": {
+          "Accept": [
+            "application/json"
+          ],
+          "Content-Type": [
+            "application/json;charset=utf-8"
+          ],
+          "X-Openrtb-Version": [
+            "2.5"
+          ]
+        },
+        "uri": "",
+        "body": {
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id": "test-imp-video-id",
+              "video": {
+                "w": 900,
+                "h": 250,
+                "mimes": [
+                  "video/x-flv",
+                  "video/mp4"
+                ]
+              },
+              "ext": {
+                "bidder": {
+                  "zoneId": "zone-id-123"
+                }
+              }
+            }
+          ],
+          "site": {
+            "id": "test",
+            "publisher": {
+              "id": "123"
+            }
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "cur": "USD",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "fe69dd6d-e85c-4186-80a7-605b530bc23b",
+                  "crid": "72745",
+                  "adomain": [
+                    "ad-domain.com"
+                  ],
+                  "price": 3,
+                  "impid": "test-imp-video-id",
+                  "adid": "564",
+                  "adm": "<?xml version=\"1.0\"?><VAST version=\"2.0\"></VAST>"
+                }
+              ],
+              "seat": "vidoomy"
+            }
+          ],
+          "id": "test-request-id"
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "fe69dd6d-e85c-4186-80a7-605b530bc23b",
+            "crid": "72745",
+            "adomain": [
+              "ad-domain.com"
+            ],
+            "price": 3,
+            "impid": "test-imp-video-id",
+            "adid": "564",
+            "adm": "<?xml version=\"1.0\"?><VAST version=\"2.0\"></VAST>"
+          },
+          "type": "video"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/vidoomy/vidoomytest/supplemental/server-error.json b/adapters/vidoomy/vidoomytest/supplemental/server-error.json
new file mode 100644
index 00000000..ebeedb14
--- /dev/null
+++ b/adapters/vidoomy/vidoomytest/supplemental/server-error.json
@@ -0,0 +1,70 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [{"w": 728, "h": 90}]
+        },
+        "ext": {
+          "bidder": {
+            "zoneId": "zone-id-123"
+          }
+        }
+      }
+    ],
+    "device": {
+      "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36",
+      "ip": "127.0.0.1"
+    },
+    "site": {
+      "id": "site-id"
+    }
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "",
+        "body": {
+          "device": {
+            "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36",
+            "ip": "127.0.0.1"
+          },
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id": "test-imp-id",
+              "banner": {
+                "format": [{"w": 728, "h": 90}],
+                "w": 728,
+                "h": 90
+              },
+              "ext": {
+                "bidder": {
+                  "zoneId": "zone-id-123"
+                }
+              }
+            }
+          ],
+          "site": {
+            "id": "site-id"
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 500,
+        "body": {
+        }
+      }
+    }
+  ],
+
+  "expectedMakeBidsErrors": [
+    {
+      "value": "Unexpected status code: 500.",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/vidoomy/vidoomytest/supplemental/server-response-wrong-impid.json b/adapters/vidoomy/vidoomytest/supplemental/server-response-wrong-impid.json
new file mode 100644
index 00000000..dad8f7cc
--- /dev/null
+++ b/adapters/vidoomy/vidoomytest/supplemental/server-response-wrong-impid.json
@@ -0,0 +1,103 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [{"w": 728, "h": 90}]
+        },
+        "ext": {
+          "bidder": {
+            "zoneId": "zone-id-123"
+          }
+        }
+      }
+    ],
+    "device": {
+      "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36",
+      "ip": "127.0.0.1"
+    },
+    "site": {
+      "id": "site-id"
+    }
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "headers": {
+          "Accept": [
+            "application/json"
+          ],
+          "Content-Type": [
+            "application/json;charset=utf-8"
+          ],
+          "User-Agent": [
+            "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36"
+          ],
+          "X-Openrtb-Version": [
+            "2.5"
+          ],
+          "X-Forwarded-For": [
+            "127.0.0.1"
+          ]
+        },
+        "uri": "",
+        "body": {
+          "device": {
+            "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36",
+            "ip": "127.0.0.1"
+          },
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id": "test-imp-id",
+              "banner": {
+                "format": [{"w": 728, "h": 90}],
+                "w": 728,
+                "h": 90
+              },
+              "ext": {
+                "bidder": {
+                  "zoneId": "zone-id-123"
+                }
+              }
+            }
+          ],
+          "site": {
+            "id": "site-id"
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "seatbid": [
+            {
+              "seat": "vidoomy",
+              "bid": [{
+                "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800",
+                "impid": "wrong",
+                "price": 0.500000,
+                "adm": "some-test-ad",
+                "crid": "crid_10",
+                "h": 90,
+                "w": 728
+              }]
+            }
+          ],
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+
+  "expectedMakeBidsErrors": [
+    {
+      "value": "Unknown ad unit code 'wrong'",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/vidoomy/vidoomytest/supplemental/simple-banner-no-response.json b/adapters/vidoomy/vidoomytest/supplemental/simple-banner-no-response.json
new file mode 100644
index 00000000..0964351a
--- /dev/null
+++ b/adapters/vidoomy/vidoomytest/supplemental/simple-banner-no-response.json
@@ -0,0 +1,85 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [{"w": 728, "h": 90}],
+          "w": 728,
+          "h": 90
+        },
+        "ext": {
+          "bidder": {
+            "zoneId": "zone-id-123"
+          }
+        }
+      }
+    ],
+    "device": {
+      "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36",
+      "ipv6": "127.0.0.1"
+    },
+    "site": {
+      "id": "test-site-id"
+    }
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "headers": {
+          "Accept": [
+            "application/json"
+          ],
+          "Content-Type": [
+            "application/json;charset=utf-8"
+          ],
+          "User-Agent": [
+            "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36"
+          ],
+          "X-Openrtb-Version": [
+            "2.5"
+          ],
+          "X-Forwarded-For": [
+            "127.0.0.1"
+          ]
+        },
+        "uri": "",
+        "body": {
+          "device": {
+            "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36",
+            "ipv6": "127.0.0.1"
+          },
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id": "test-imp-id",
+              "banner": {
+                "format": [{"w": 728, "h": 90}],
+                "w": 728,
+                "h": 90
+              },
+              "ext": {
+                "bidder": {
+                  "zoneId": "zone-id-123"
+                }
+              }
+            }
+          ],
+          "site": {
+            "id": "test-site-id"
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 204,
+        "body": {}
+      }
+    }
+  ],
+
+  "expectedBidResponses": [
+    {}
+  ]
+}
diff --git a/adapters/vidoomy/vidoomytest/supplemental/wrong-banner-format.json b/adapters/vidoomy/vidoomytest/supplemental/wrong-banner-format.json
new file mode 100644
index 00000000..e38f37f4
--- /dev/null
+++ b/adapters/vidoomy/vidoomytest/supplemental/wrong-banner-format.json
@@ -0,0 +1,32 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": []
+        },
+        "ext": {
+          "bidder": {
+            "zoneId": "zone-id-123"
+          }
+        }
+      }
+    ],
+    "device": {
+      "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36",
+      "ip": "127.0.0.1"
+    },
+    "site": {
+      "id": "test-site-id"
+    }
+  },
+
+  "expectedMakeRequestsErrors": [
+    {
+      "value": "no sizes provided for Banner []",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/vidoomy/vidoomytest/supplemental/wrong-banner-width-high.json b/adapters/vidoomy/vidoomytest/supplemental/wrong-banner-width-high.json
new file mode 100644
index 00000000..b5c22546
--- /dev/null
+++ b/adapters/vidoomy/vidoomytest/supplemental/wrong-banner-width-high.json
@@ -0,0 +1,34 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [{"w": 728, "h": 90}],
+          "w": 0,
+          "h": 720
+        },
+        "ext": {
+          "bidder": {
+            "zoneId": "zone-id-123"
+          }
+        }
+      }
+    ],
+    "device": {
+      "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36",
+      "ip": "127.0.0.1"
+    },
+    "site": {
+      "id": "test-site-id"
+    }
+  },
+
+  "expectedMakeRequestsErrors": [
+    {
+      "value": "invalid sizes provided for Banner 0 x 720",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/vidoomy/vidoomytest/supplemental/wrong-banner-with-format.json b/adapters/vidoomy/vidoomytest/supplemental/wrong-banner-with-format.json
new file mode 100644
index 00000000..e4541c9c
--- /dev/null
+++ b/adapters/vidoomy/vidoomytest/supplemental/wrong-banner-with-format.json
@@ -0,0 +1,34 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [{"w": 728, "h": 90}],
+          "w": 0,
+          "h" : 90
+        },
+        "ext": {
+          "bidder": {
+            "zoneId": "zone-id-123"
+          }
+        }
+      }
+    ],
+    "device": {
+      "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36",
+      "ip": "127.0.0.1"
+    },
+    "site": {
+      "id": "test-site-id"
+    }
+  },
+
+  "expectedMakeRequestsErrors": [
+    {
+      "value": "invalid sizes provided for Banner 0 x 90",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/config/config_test.go b/config/config_test.go
index f86f13bd..766244c4 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -217,8 +217,8 @@ func TestDefaults(t *testing.T) {
 			VendorExceptions:   []openrtb_ext.BidderName{},
 			VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{},
 		},
-		SpecialPurpose1: TCF2Purpose{
-			Enabled:            true,
+		SpecialFeature1: TCF2SpecialFeature{
+			Enforce:            true,
 			VendorExceptions:   []openrtb_ext.BidderName{},
 			VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{},
 		},
@@ -272,7 +272,7 @@ gdpr:
     purpose10:
       enforce_vendors: false
       vendor_exceptions: ["foo10"]
-    special_purpose1:
+    special_feature1:
       vendor_exceptions: ["fooSP1"]
 ccpa:
   enforce: true
@@ -540,8 +540,8 @@ func TestFullConfig(t *testing.T) {
 			VendorExceptions:   []openrtb_ext.BidderName{openrtb_ext.BidderName("foo10")},
 			VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo10"): {}},
 		},
-		SpecialPurpose1: TCF2Purpose{
-			Enabled:            true, // true by default
+		SpecialFeature1: TCF2SpecialFeature{
+			Enforce:            true, // true by default
 			VendorExceptions:   []openrtb_ext.BidderName{openrtb_ext.BidderName("fooSP1")},
 			VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("fooSP1"): {}},
 		},
@@ -765,6 +765,91 @@ func TestMigrateConfigPurposeOneTreatment(t *testing.T) {
 	}
 }
 
+func TestMigrateConfigSpecialFeature1(t *testing.T) {
+	oldSpecialFeature1Config := []byte(`
+      gdpr:
+        tcf2:
+          special_purpose1:
+            enabled: true
+            vendor_exceptions: ["appnexus"]
+    `)
+	newSpecialFeature1Config := []byte(`
+      gdpr:
+        tcf2:
+          special_feature1:
+            enforce: true
+            vendor_exceptions: ["appnexus"]
+    `)
+	oldAndNewSpecialFeature1Config := []byte(`
+      gdpr:
+        tcf2:
+          special_purpose1:
+            enabled: false
+            vendor_exceptions: ["appnexus"]
+          special_feature1:
+            enforce: true
+            vendor_exceptions: ["rubicon"]
+    `)
+
+	tests := []struct {
+		description                         string
+		config                              []byte
+		wantSpecialFeature1Enforce          bool
+		wantSpecialFeature1VendorExceptions []string
+	}{
+		{
+			description: "New config and old config not set",
+			config:      []byte{},
+		},
+		{
+			description:                         "New config not set, old config set",
+			config:                              oldSpecialFeature1Config,
+			wantSpecialFeature1Enforce:          true,
+			wantSpecialFeature1VendorExceptions: []string{"appnexus"},
+		},
+		{
+			description:                         "New config set, old config not set",
+			config:                              newSpecialFeature1Config,
+			wantSpecialFeature1Enforce:          true,
+			wantSpecialFeature1VendorExceptions: []string{"appnexus"},
+		},
+		{
+			description:                         "New config and old config set",
+			config:                              oldAndNewSpecialFeature1Config,
+			wantSpecialFeature1Enforce:          true,
+			wantSpecialFeature1VendorExceptions: []string{"rubicon"},
+		},
+	}
+
+	for _, tt := range tests {
+		v := viper.New()
+		v.SetConfigType("yaml")
+		v.ReadConfig(bytes.NewBuffer(tt.config))
+
+		migrateConfigSpecialFeature1(v)
+
+		if len(tt.config) > 0 {
+			assert.Equal(t, tt.wantSpecialFeature1Enforce, v.Get("gdpr.tcf2.special_feature1.enforce").(bool), tt.description)
+			assert.Equal(t, tt.wantSpecialFeature1VendorExceptions, v.GetStringSlice("gdpr.tcf2.special_feature1.vendor_exceptions"), tt.description)
+		} else {
+			assert.Nil(t, v.Get("gdpr.tcf2.special_feature1.enforce"), tt.description)
+			assert.Nil(t, v.Get("gdpr.tcf2.special_feature1.vendor_exceptions"), tt.description)
+		}
+
+		var c Configuration
+		err := v.Unmarshal(&c)
+		assert.NoError(t, err, tt.description)
+		assert.Equal(t, tt.wantSpecialFeature1Enforce, c.GDPR.TCF2.SpecialFeature1.Enforce, tt.description)
+
+		// convert expected vendor exceptions to type BidderName
+		expectedVendorExceptions := make([]openrtb_ext.BidderName, 0, 0)
+		for _, ve := range tt.wantSpecialFeature1VendorExceptions {
+			expectedVendorExceptions = append(expectedVendorExceptions, openrtb_ext.BidderName(ve))
+		}
+		assert.ElementsMatch(t, expectedVendorExceptions, c.GDPR.TCF2.SpecialFeature1.VendorExceptions, tt.description)
+	}
+}
+
 func TestMigrateConfigTCF2PurposeEnabledFlags(t *testing.T) {
 	trueStr := "true"
 	falseStr := "false"
@@ -1291,3 +1376,58 @@ func doTimeoutTest(t *testing.T, expected int, requested int, max uint64, def ui
 	limited := cfg.LimitAuctionTimeout(time.Duration(requested) * time.Millisecond)
 	assert.Equal(t, limited, expectedDuration, "Expected %dms timeout, got %dms", expectedDuration, limited/time.Millisecond)
 }
+
+func TestSpecialFeature1VendorExceptionMap(t *testing.T) {
+	baseConfig := []byte(`
+    gdpr:
+      default_value: 0
+      tcf2:
+        special_feature1:
+          vendor_exceptions: `)
+
+	tests := []struct {
+		description             string
+		configVendorExceptions  []byte
+		wantVendorExceptions    []openrtb_ext.BidderName
+		wantVendorExceptionsMap map[openrtb_ext.BidderName]struct{}
+	}{
+		{
+			description:             "nil vendor exceptions",
+			configVendorExceptions:  []byte(`null`),
+			wantVendorExceptions:    []openrtb_ext.BidderName{},
+			wantVendorExceptionsMap: map[openrtb_ext.BidderName]struct{}{},
+		},
+		{
+			description:             "no vendor exceptions",
+			configVendorExceptions:  []byte(`[]`),
+			wantVendorExceptions:    []openrtb_ext.BidderName{},
+			wantVendorExceptionsMap: map[openrtb_ext.BidderName]struct{}{},
+		},
+		{
+			description:             "one vendor exception",
+			configVendorExceptions:  []byte(`["vendor1"]`),
+			wantVendorExceptions:    []openrtb_ext.BidderName{openrtb_ext.BidderName("vendor1")},
+			wantVendorExceptionsMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("vendor1"): {}},
+		},
+		{
+			description:             "many exceptions",
+			configVendorExceptions:  []byte(`["vendor1","vendor2"]`),
+			wantVendorExceptions:    []openrtb_ext.BidderName{openrtb_ext.BidderName("vendor1"), openrtb_ext.BidderName("vendor2")},
+			wantVendorExceptionsMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("vendor1"): {}, openrtb_ext.BidderName("vendor2"): {}},
+		},
+	}
+
+	for _, tt := range tests {
+		config := append(baseConfig, tt.configVendorExceptions...)
+
+		v := viper.New()
+		SetupViper(v, "")
+		v.SetConfigType("yaml")
+		v.ReadConfig(bytes.NewBuffer(config))
+		cfg, err := New(v)
+		assert.NoError(t, err, "Setting up config error", tt.description)
+
+		assert.Equal(t, tt.wantVendorExceptions, cfg.GDPR.TCF2.SpecialFeature1.VendorExceptions, tt.description)
+		assert.Equal(t, tt.wantVendorExceptionsMap, cfg.GDPR.TCF2.SpecialFeature1.VendorExceptionMap, tt.description)
+	}
+}
diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go
index ad92ab7d..c42580fc 100644
--- a/endpoints/openrtb2/auction_test.go
+++ b/endpoints/openrtb2/auction_test.go
@@ -2450,6 +2450,134 @@ func TestSChainInvalid(t *testing.T) {
 	assert.ElementsMatch(t, errL, []error{expectedError})
 }
 
+func TestMapSChains(t *testing.T) {
+	const seller1SChain string = `"schain":{"complete":1,"nodes":[{"asi":"directseller1.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}`
+	const seller2SChain string = `"schain":{"complete":2,"nodes":[{"asi":"directseller2.com","sid":"00002","rid":"BidRequest2","hp":2}],"ver":"2.0"}`
+
+	seller1SChainUnpacked := openrtb_ext.ExtRequestPrebidSChainSChain{
+		Complete: 1,
+		Nodes: []*openrtb_ext.ExtRequestPrebidSChainSChainNode{{
+			ASI: "directseller1.com",
+			SID: "00001",
+			RID: "BidRequest1",
+			HP:  1,
+		}},
+		Ver: "1.0",
+	}
+
+	tests := []struct {
+		description         string
+		bidRequest          openrtb2.BidRequest
+		wantReqExtSChain    *openrtb_ext.ExtRequestPrebidSChainSChain
+		wantSourceExtSChain *openrtb_ext.ExtRequestPrebidSChainSChain
+		wantError           bool
+	}{
+		{
+			description: "invalid req.ext",
+			bidRequest: openrtb2.BidRequest{
+				Ext: json.RawMessage(`{"prebid":{"schains":invalid}}`),
+				Source: &openrtb2.Source{
+					Ext: json.RawMessage(`{}`),
+				},
+			},
+			wantError: true,
+		},
+		{
+			description: "invalid source.ext",
+			bidRequest: openrtb2.BidRequest{
+				Ext: json.RawMessage(`{}`),
+				Source: &openrtb2.Source{
+					Ext: json.RawMessage(`{"schain":invalid}}`),
+				},
+			},
+			wantError: true,
+		},
+		{
+			description: "req.ext.prebid.schains, req.source.ext.schain and req.ext.schain are nil",
+			bidRequest: openrtb2.BidRequest{
+				Ext: json.RawMessage(`{}`),
+				Source: &openrtb2.Source{
+					Ext: json.RawMessage(`{}`),
+				},
+			},
+			wantReqExtSChain:    nil,
+			wantSourceExtSChain: nil,
+		},
+		{
+			description: "req.ext.prebid.schains is not nil",
+			bidRequest: openrtb2.BidRequest{
+				Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `}]}}`),
+				Source: &openrtb2.Source{
+					Ext: json.RawMessage(`{}`),
+				},
+			},
+			wantReqExtSChain:    nil,
+			wantSourceExtSChain: nil,
+		},
+		{
+			description: "req.source.ext is not nil",
+			bidRequest: openrtb2.BidRequest{
+				Ext: json.RawMessage(`{}`),
+				Source: &openrtb2.Source{
+					Ext: json.RawMessage(`{` + seller1SChain + `}`),
+				},
+			},
+			wantReqExtSChain:    nil,
+			wantSourceExtSChain: &seller1SChainUnpacked,
+		},
+		{
+			description: "req.ext.schain is not nil",
+			bidRequest: openrtb2.BidRequest{
+				Ext: json.RawMessage(`{` + seller1SChain + `}`),
+				Source: &openrtb2.Source{
+					Ext: json.RawMessage(`{}`),
+				},
+			},
+			wantReqExtSChain:    nil,
+			wantSourceExtSChain: &seller1SChainUnpacked,
+		},
+		{
+			description: "req.source.ext.schain and req.ext.schain are not nil",
+			bidRequest: openrtb2.BidRequest{
+				Ext: json.RawMessage(`{` + seller2SChain + `}`),
+				Source: &openrtb2.Source{
+					Ext: json.RawMessage(`{` + seller1SChain + `}`),
+				},
+			},
+			wantReqExtSChain:    nil,
+			wantSourceExtSChain: &seller1SChainUnpacked,
+		},
+	}
+
+	for _, tt := range tests {
+		reqWrapper := openrtb_ext.RequestWrapper{
+			BidRequest: &tt.bidRequest,
+		}
+
+		err := mapSChains(&reqWrapper)
+
+		if tt.wantError {
+			assert.NotNil(t, err, tt.description)
+		} else {
+			assert.Nil(t, err, tt.description)
+
+			reqExt, err := reqWrapper.GetRequestExt()
+			if err != nil {
+				assert.Fail(t, "Error getting request ext from wrapper", tt.description)
+			}
+			reqExtSChain := reqExt.GetSChain()
+			assert.Equal(t, tt.wantReqExtSChain, reqExtSChain, tt.description)
+
+			sourceExt, err := reqWrapper.GetSourceExt()
+			if err != nil {
+				assert.Fail(t, "Error getting source ext from wrapper", tt.description)
+			}
+			sourceExtSChain := sourceExt.GetSChain()
+			assert.Equal(t, tt.wantSourceExtSChain, sourceExtSChain, tt.description)
+		}
+	}
+}
+
 func TestGetAccountID(t *testing.T) {
 	testPubID := "test-pub"
 	testParentAccount := "test-account"
diff --git a/exchange/adapter_util_test.go b/exchange/adapter_util_test.go
index b3a05cdc..9f2c7b8f 100644
--- a/exchange/adapter_util_test.go
+++ b/exchange/adapter_util_test.go
@@ -238,6 +238,7 @@ func TestGetDisabledBiddersErrorMessages(t *testing.T) {
 			bidderInfos: map[string]config.BidderInfo{},
 			expected: map[string]string{
 				"lifestreet": `Bidder "lifestreet" is no longer available in Prebid Server. Please update your configuration.`,
+				"adagio":     `Bidder "adagio" is no longer available in Prebid Server. Please update your configuration.`,
 			},
 		},
 		{
@@ -245,6 +246,7 @@ func TestGetDisabledBiddersErrorMessages(t *testing.T) {
 			bidderInfos: map[string]config.BidderInfo{"appnexus": infoEnabled},
 			expected: map[string]string{
 				"lifestreet": `Bidder "lifestreet" is no longer available in Prebid Server. Please update your configuration.`,
+				"adagio":     `Bidder "adagio" is no longer available in Prebid Server. Please update your configuration.`,
 			},
 		},
 		{
@@ -252,6 +254,7 @@ func TestGetDisabledBiddersErrorMessages(t *testing.T) {
 			bidderInfos: map[string]config.BidderInfo{"appnexus": infoDisabled},
 			expected: map[string]string{
 				"lifestreet": `Bidder "lifestreet" is no longer available in Prebid Server. Please update your configuration.`,
+				"adagio":     `Bidder "adagio" is no longer available in Prebid Server. Please update your configuration.`,
 				"appnexus":   `Bidder "appnexus" has been disabled on this instance of Prebid Server. Please work with the PBS host to enable this bidder again.`,
 			},
 		},
@@ -260,6 +263,7 @@ func TestGetDisabledBiddersErrorMessages(t *testing.T) {
 			bidderInfos: map[string]config.BidderInfo{"appnexus": infoDisabled, "openx": infoEnabled},
 			expected: map[string]string{
 				"lifestreet": `Bidder "lifestreet" is no longer available in Prebid Server. Please update your configuration.`,
+				"adagio":     `Bidder "adagio" is no longer available in Prebid Server. Please update your configuration.`,
 				"appnexus":   `Bidder "appnexus" has been disabled on this instance of Prebid Server. Please work with the PBS host to enable this bidder again.`,
 			},
 		},
diff --git a/exchange/utils_test.go b/exchange/utils_test.go
index c10e294f..f67b6ffb 100644
--- a/exchange/utils_test.go
+++ b/exchange/utils_test.go
@@ -837,83 +837,60 @@ func TestCleanOpenRTBRequestsCOPPA(t *testing.T) {
 }
 
 func TestCleanOpenRTBRequestsSChain(t *testing.T) {
+	const seller1SChain string = `"schain":{"complete":1,"nodes":[{"asi":"directseller1.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}`
+	const seller2SChain string = `"schain":{"complete":2,"nodes":[{"asi":"directseller2.com","sid":"00002","rid":"BidRequest2","hp":2}],"ver":"2.0"}`
+
 	testCases := []struct {
 		description   string
 		inExt         json.RawMessage
 		inSourceExt   json.RawMessage
-		outSourceExt  json.RawMessage
 		outRequestExt json.RawMessage
+		outSourceExt  json.RawMessage
 		hasError      bool
 	}{
 		{
-			description:   "Empty root ext and source ext, nil unmarshaled ext",
-			inExt:         nil,
-			inSourceExt:   json.RawMessage(``),
-			outSourceExt:  json.RawMessage(``),
-			outRequestExt: json.RawMessage(``),
-			hasError:      false,
-		},
-		{
-			description:   "Empty root ext, source ext, and unmarshaled ext",
-			inExt:         json.RawMessage(``),
-			inSourceExt:   json.RawMessage(``),
-			outSourceExt:  json.RawMessage(``),
-			outRequestExt: json.RawMessage(``),
-			hasError:      false,
+			description:   "source.ext is nil",
+			inExt:         json.RawMessage{},
+			inSourceExt:   nil,
+			outRequestExt: json.RawMessage{},
+			outSourceExt:  nil,
 		},
 		{
-			description:   "No schains in root ext and empty source ext. Unmarshaled ext is equivalent to root ext",
-			inSourceExt:   json.RawMessage(``),
-			inExt:         json.RawMessage(`{"prebid":{"schains":[]}}`),
-			outSourceExt:  json.RawMessage(``),
-			outRequestExt: json.RawMessage(`{"prebid":{}}`),
-			hasError:      false,
+			description:   "source.ext is defined with length 0",
+			inExt:         json.RawMessage{},
+			inSourceExt:   json.RawMessage{},
+			outRequestExt: json.RawMessage{},
+			outSourceExt:  json.RawMessage{},
 		},
 		{
-			description:   "Use source schain -- no bidder schain or wildcard schain in ext.prebid.schains. Unmarshaled ext is equivalent to root ext",
-			inSourceExt:   json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"example.com","sid":"example1","rid":"ExampleReq1","hp":1}],"ver":"1.0"}}`),
-			inExt:         json.RawMessage(`{"prebid":{"schains":[{"bidders":["bidder1"],"schain":{"complete":1,"nodes":[{"asi":"directseller.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}]}}`),
-			outSourceExt:  json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"example.com","sid":"example1","rid":"ExampleReq1","hp":1}],"ver":"1.0"}}`),
-			outRequestExt: json.RawMessage(`{"prebid":{}}`),
-			hasError:      false,
+			description:   "ORTB 2.5 chain at source.ext.schain",
+			inExt:         json.RawMessage{},
+			inSourceExt:   json.RawMessage(`{` + seller1SChain + `}`),
+			outRequestExt: json.RawMessage{},
+			outSourceExt:  json.RawMessage(`{` + seller1SChain + `}`),
 		},
 		{
-			description:   "Use schain for bidder in ext.prebid.schains. Unmarshaled ext is equivalent to root ext",
-			inSourceExt:   json.RawMessage(``),
-			inExt:         json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}]}}`),
-			outSourceExt:  json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"directseller.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}`),
+			description:   "ORTB 2.5 schain at request.ext.prebid.schains",
+			inExt:         json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `}]}}`),
+			inSourceExt:   json.RawMessage{},
 			outRequestExt: json.RawMessage(`{"prebid":{}}`),
-			hasError:      false,
+			outSourceExt:  json.RawMessage(`{` + seller1SChain + `}`),
 		},
 		{
-			description:   "Use wildcard schain in ext.prebid.schains. Unmarshaled ext is equivalent to root ext",
-			inSourceExt:   json.RawMessage(``),
-			inExt:         json.RawMessage(`{"prebid":{"schains":[{"bidders":["*"],"schain":{"complete":1,"nodes":[{"asi":"directseller.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}]}}`),
-			outSourceExt:  json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"directseller.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}`),
-			outRequestExt: json.RawMessage(`{"prebid":{}}`),
-			hasError:      false,
-		},
-		{
-			description:   "Use schain for bidder in ext.prebid.schains instead of wildcard. Unmarshaled ext is equivalent to root ext",
-			inSourceExt:   json.RawMessage(``),
-			inExt:         json.RawMessage(`{"prebid":{"aliases":{"appnexus":"alias1"},"schains":[{"bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}, {"bidders":["*"],"schain":{"complete":1,"nodes":[{"asi":"wildcard.com","sid":"wildcard1","rid":"WildcardReq1","hp":1}],"ver":"1.0"}} ]}}`),
-			outSourceExt:  json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"directseller.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}`),
-			outRequestExt: json.RawMessage(`{"prebid":{"aliases":{"appnexus":"alias1"}}}`),
-			hasError:      false,
-		},
-		{
-			description:   "Use source schain -- multiple (two) bidder schains in ext.prebid.schains. Unmarshaled ext is equivalent to root ext",
-			inSourceExt:   json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"example.com","sid":"example1","rid":"ExampleReq1","hp":1}],"ver":"1.0"}}`),
-			inExt:         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"}}]}}`),
-			outSourceExt:  nil,
+			description:   "schainwriter instantation error -- multiple bidder schains in ext.prebid.schains.",
+			inExt:         json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `},{"bidders":["appnexus"],` + seller2SChain + `}]}}`),
+			inSourceExt:   json.RawMessage(`{` + seller1SChain + `}`),
 			outRequestExt: nil,
+			outSourceExt:  nil,
 			hasError:      true,
 		},
 	}
 
 	for _, test := range testCases {
 		req := newBidRequest(t)
-		req.Source.Ext = test.inSourceExt
+		if test.inSourceExt != nil {
+			req.Source.Ext = test.inSourceExt
+		}
 
 		var extRequest *openrtb_ext.ExtRequest
 		if test.inExt != nil {
@@ -2173,101 +2150,6 @@ func TestRandomizeList(t *testing.T) {
 	}
 }
 
-func TestBidderToPrebidChains(t *testing.T) {
-	input := openrtb_ext.ExtRequest{
-		Prebid: openrtb_ext.ExtRequestPrebid{
-			SChains: []*openrtb_ext.ExtRequestPrebidSChain{
-				{
-					Bidders: []string{"Bidder1", "Bidder2"},
-					SChain: openrtb_ext.ExtRequestPrebidSChainSChain{
-						Complete: 1,
-						Nodes: []*openrtb_ext.ExtRequestPrebidSChainSChainNode{
-							{
-								ASI:    "asi1",
-								SID:    "sid1",
-								Name:   "name1",
-								RID:    "rid1",
-								Domain: "domain1",
-								HP:     1,
-							},
-							{
-								ASI:    "asi2",
-								SID:    "sid2",
-								Name:   "name2",
-								RID:    "rid2",
-								Domain: "domain2",
-								HP:     2,
-							},
-						},
-						Ver: "version1",
-					},
-				},
-				{
-					Bidders: []string{"Bidder3", "Bidder4"},
-					SChain:  openrtb_ext.ExtRequestPrebidSChainSChain{},
-				},
-			},
-		},
-	}
-
-	output, err := BidderToPrebidSChains(input.Prebid.SChains)
-
-	assert.Nil(t, err)
-	assert.Equal(t, len(output), 4)
-	assert.Same(t, output["Bidder1"], &input.Prebid.SChains[0].SChain)
-	assert.Same(t, output["Bidder2"], &input.Prebid.SChains[0].SChain)
-	assert.Same(t, output["Bidder3"], &input.Prebid.SChains[1].SChain)
-	assert.Same(t, output["Bidder4"], &input.Prebid.SChains[1].SChain)
-}
-
-func TestBidderToPrebidChainsDiscardMultipleChainsForBidder(t *testing.T) {
-	input := openrtb_ext.ExtRequest{
-		Prebid: openrtb_ext.ExtRequestPrebid{
-			SChains: []*openrtb_ext.ExtRequestPrebidSChain{
-				{
-					Bidders: []string{"Bidder1"},
-					SChain:  openrtb_ext.ExtRequestPrebidSChainSChain{},
-				},
-				{
-					Bidders: []string{"Bidder1", "Bidder2"},
-					SChain:  openrtb_ext.ExtRequestPrebidSChainSChain{},
-				},
-			},
-		},
-	}
-
-	output, err := BidderToPrebidSChains(input.Prebid.SChains)
-
-	assert.NotNil(t, err)
-	assert.Nil(t, output)
-}
-
-func TestBidderToPrebidChainsNilSChains(t *testing.T) {
-	input := openrtb_ext.ExtRequest{
-		Prebid: openrtb_ext.ExtRequestPrebid{
-			SChains: nil,
-		},
-	}
-
-	output, err := BidderToPrebidSChains(input.Prebid.SChains)
-
-	assert.Nil(t, err)
-	assert.Equal(t, len(output), 0)
-}
-
-func TestBidderToPrebidChainsZeroLengthSChains(t *testing.T) {
-	input := openrtb_ext.ExtRequest{
-		Prebid: openrtb_ext.ExtRequestPrebid{
-			SChains: []*openrtb_ext.ExtRequestPrebidSChain{},
-		},
-	}
-
-	output, err := BidderToPrebidSChains(input.Prebid.SChains)
-
-	assert.Nil(t, err)
-	assert.Equal(t, len(output), 0)
-}
-
 func TestRemoveUnpermissionedEids(t *testing.T) {
 	bidder := "bidderA"
 
@@ -2745,47 +2627,6 @@ func TestBuildXPrebidHeader(t *testing.T) {
 	}
 }
 
-func TestSourceExtSChainCopied(t *testing.T) {
-	bidRequest := newBidRequest(t)
-
-	bidderSchains := map[string]*openrtb_ext.ExtRequestPrebidSChainSChain{
-		"bidder1": {
-			Ver:      "1.0",
-			Complete: 1,
-			Nodes: []*openrtb_ext.ExtRequestPrebidSChainSChainNode{
-				{
-					ASI: "bidder1.com",
-					SID: "0001",
-					HP:  1,
-				},
-			},
-		},
-		"bidder2": {
-			Ver:      "1.0",
-			Complete: 1,
-			Nodes: []*openrtb_ext.ExtRequestPrebidSChainSChainNode{
-				{
-					ASI: "bidder2.com",
-					SID: "0002",
-					HP:  1,
-				},
-			},
-		},
-	}
-
-	copy1 := *bidRequest
-	originalTid := copy1.Source.TID
-	prepareSource(&copy1, "bidder1", bidderSchains)
-	copy2 := *bidRequest
-	copy2.Source.TID = "new TID"
-	prepareSource(&copy2, "bidder2", bidderSchains)
-
-	assert.Equal(t, json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"bidder1.com","sid":"0001","hp":1}],"ver":"1.0"}}`), copy1.Source.Ext, "First schain was overwritten or not set")
-	assert.Equal(t, originalTid, copy1.Source.TID, "Original TID was overwritten")
-	assert.Equal(t, json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"bidder2.com","sid":"0002","hp":1}],"ver":"1.0"}}`), copy2.Source.Ext, "Second schain was overwritten or not set")
-	assert.Equal(t, "new TID", copy2.Source.TID, "New TID was not set")
-}
-
 func TestCleanOpenRTBRequestsSChainMultipleBidders(t *testing.T) {
 	req := &openrtb2.BidRequest{
 		Site: &openrtb2.Site{},
diff --git a/gdpr/impl_test.go b/gdpr/impl_test.go
index 3064a9fd..9bdf7f85 100644
--- a/gdpr/impl_test.go
+++ b/gdpr/impl_test.go
@@ -341,7 +341,7 @@ func buildVendorList34() vendorList {
 				ID:               6,
 				Purposes:         []int{1, 2, 4},
 				LegIntPurposes:   []int{7},
-				SpecialPurposes:  []int{1},
+				SpecialFeatures:  []int{1},
 				FlexiblePurposes: []int{1, 2, 4, 7},
 			},
 			"8": {
@@ -352,7 +352,7 @@ func buildVendorList34() vendorList {
 			"10": {
 				ID:              10,
 				Purposes:        []int{2, 4, 7},
-				SpecialPurposes: []int{1},
+				SpecialFeatures: []int{1},
 			},
 			"20": {
 				ID:               20,
@@ -384,7 +384,7 @@ func allPurposesEnabledPermissions() (perms permissionsImpl) {
 				Purpose8:        config.TCF2Purpose{EnforcePurpose: config.TCF2FullEnforcement, EnforceVendors: true},
 				Purpose9:        config.TCF2Purpose{EnforcePurpose: config.TCF2FullEnforcement, EnforceVendors: true},
 				Purpose10:       config.TCF2Purpose{EnforcePurpose: config.TCF2FullEnforcement, EnforceVendors: true},
-				SpecialPurpose1: config.TCF2Purpose{Enabled: true, EnforceVendors: true},
+				SpecialFeature1: config.TCF2SpecialFeature{Enforce: true},
 			},
 		},
 	}
@@ -431,7 +431,7 @@ func TestAllowActivitiesGeoAndID(t *testing.T) {
 		}),
 	}
 
-	// COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : full consents to purposes and vendors 2, 6, 8
+	// COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : full consents to purposes and vendors 2, 6, 8 and special feature 1 opt-in
 	testDefs := []testDef{
 		{
 			description: "Appnexus vendor test, insufficient purposes claimed",
@@ -536,7 +536,7 @@ func TestAllowActivitiesPubRestrict(t *testing.T) {
 	}
 
 	// COwAdDhOwAdDhN4ABAENAPCgAAQAAv___wAAAFP_AAp_4AI6ACACAA - vendors 1-10 legit interest only,
-	// Pub restriction on purpose 7, consent only ... no allowPI will pass, no Special purpose 1 consent
+	// Pub restriction on purpose 7, consent only ... no allowPI will pass, no special feature 1 consent
 	testDefs := []testDef{
 		{
 			description: "Appnexus vendor test, insufficient purposes claimed",
@@ -840,7 +840,7 @@ func TestAllowActivitiesVendorException(t *testing.T) {
 	testDefs := []struct {
 		description           string
 		p2VendorExceptionMap  map[openrtb_ext.BidderName]struct{}
-		sp1VendorExceptionMap map[openrtb_ext.BidderName]struct{}
+		sf1VendorExceptionMap map[openrtb_ext.BidderName]struct{}
 		bidder                openrtb_ext.BidderName
 		consent               string
 		allowBid              bool
@@ -859,7 +859,7 @@ func TestAllowActivitiesVendorException(t *testing.T) {
 		{
 			description:           "Bid/ID allowed by vendor exception - p2 enabled with p2 vendor exception, pub restricts none",
 			p2VendorExceptionMap:  map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderAppnexus: {}},
-			sp1VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{},
+			sf1VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{},
 			bidder:                openrtb_ext.BidderAppnexus,
 			consent:               noPurposeOrVendorConsentAndPubRestrictsNone,
 			allowBid:              true,
@@ -867,9 +867,9 @@ func TestAllowActivitiesVendorException(t *testing.T) {
 			passID:                true,
 		},
 		{
-			description:           "Geo blocked - sp1 enabled but no consent",
+			description:           "Geo blocked - sf1 enabled but no consent",
 			p2VendorExceptionMap:  map[openrtb_ext.BidderName]struct{}{},
-			sp1VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{},
+			sf1VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{},
 			bidder:                openrtb_ext.BidderAppnexus,
 			consent:               noPurposeOrVendorConsentAndPubRestrictsNone,
 			allowBid:              false,
@@ -877,9 +877,9 @@ func TestAllowActivitiesVendorException(t *testing.T) {
 			passID:                false,
 		},
 		{
-			description:           "Geo allowed by vendor exception - sp1 enabled with sp1 vendor exception",
+			description:           "Geo allowed by vendor exception - sf1 enabled with sf1 vendor exception",
 			p2VendorExceptionMap:  map[openrtb_ext.BidderName]struct{}{},
-			sp1VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderAppnexus: {}},
+			sf1VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderAppnexus: {}},
 			bidder:                openrtb_ext.BidderAppnexus,
 			consent:               noPurposeOrVendorConsentAndPubRestrictsNone,
 			allowBid:              false,
@@ -896,7 +896,7 @@ func TestAllowActivitiesVendorException(t *testing.T) {
 				TCF2: config.TCF2{
 					Enabled:         true,
 					Purpose2:        config.TCF2Purpose{EnforcePurpose: config.TCF2FullEnforcement, VendorExceptionMap: td.p2VendorExceptionMap},
-					SpecialPurpose1: config.TCF2Purpose{Enabled: true, VendorExceptionMap: td.sp1VendorExceptionMap},
+					SpecialFeature1: config.TCF2SpecialFeature{Enforce: true, VendorExceptionMap: td.sf1VendorExceptionMap},
 				},
 			},
 			vendorIDs: map[openrtb_ext.BidderName]uint16{
diff --git a/gdpr/vendorlist-fetching_test.go b/gdpr/vendorlist-fetching_test.go
index 95529e4e..a1a926f5 100644
--- a/gdpr/vendorlist-fetching_test.go
+++ b/gdpr/vendorlist-fetching_test.go
@@ -189,7 +189,7 @@ type vendor struct {
 	Purposes         []int  `json:"purposes"`
 	LegIntPurposes   []int  `json:"legIntPurposes"`
 	FlexiblePurposes []int  `json:"flexiblePurposes"`
-	SpecialPurposes  []int  `json:"specialPurposes"`
+	SpecialFeatures  []int  `json:"specialFeatures"`
 }
 
 func MarshalVendorList(vendorList vendorList) string {
diff --git a/openrtb_ext/request_wrapper_test.go b/openrtb_ext/request_wrapper_test.go
index 06cad49a..60ef1a33 100644
--- a/openrtb_ext/request_wrapper_test.go
+++ b/openrtb_ext/request_wrapper_test.go
@@ -1,6 +1,7 @@
 package openrtb_ext
 
 import (
+	"encoding/json"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
@@ -14,9 +15,29 @@ func TestUserExt(t *testing.T) {
 	userExt.unmarshal(nil)
 	assert.Equal(t, false, userExt.Dirty(), "New UserExt should not be dirty.")
 	assert.Nil(t, userExt.GetConsent(), "Empty UserExt should have nil consent")
+	assert.Nil(t, userExt.GetEid(), "Empty UserExt should have nil eid")
+	assert.Nil(t, userExt.GetPrebid(), "Empty UserExt should have nil prebid")
 
 	newConsent := "NewConsent"
 	userExt.SetConsent(&newConsent)
-	assert.Equal(t, "NewConsent", *userExt.GetConsent())
+	assert.Equal(t, "NewConsent", *userExt.GetConsent(), "UserExt consent is incorrect")
 
+	newEid := []ExtUserEid{{}}
+	userExt.SetEid(&newEid)
+	assert.Equal(t, []ExtUserEid{{}}, *userExt.GetEid(), "UserExt eid is incorrect")
+
+	buyerIDs := map[string]string{"buyer": "id"}
+	newPrebid := ExtUserPrebid{BuyerUIDs: buyerIDs}
+	userExt.SetPrebid(&newPrebid)
+	assert.Equal(t, ExtUserPrebid{BuyerUIDs: buyerIDs}, *userExt.GetPrebid(), "UserExt prebid is icorrect")
+
+	assert.Equal(t, true, userExt.Dirty(), "UserExt should be dirty after field updates")
+
+	updatedUserExt, err := userExt.marshal()
+	assert.Nil(t, err, "Marshalling UserExt after updating should not cause an error")
+
+	expectedUserExt := json.RawMessage(`{"consent":"NewConsent","prebid":{"buyeruids":{"buyer":"id"}},"eids":[{"source":""}]}`)
+	assert.JSONEq(t, string(updatedUserExt), string(expectedUserExt), "Marshalled UserExt is incorrect")
+
+	assert.Equal(t, false, userExt.Dirty(), "UserExt should not be dirty after marshalling")
 }
diff --git a/schain/schain_test.go b/schain/schain_test.go
new file mode 100644
index 00000000..22903d69
--- /dev/null
+++ b/schain/schain_test.go
@@ -0,0 +1,103 @@
+package schain
+
+import (
+	"testing"
+
+	"github.com/prebid/prebid-server/openrtb_ext"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestBidderToPrebidChains(t *testing.T) {
+	input := openrtb_ext.ExtRequest{
+		Prebid: openrtb_ext.ExtRequestPrebid{
+			SChains: []*openrtb_ext.ExtRequestPrebidSChain{
+				{
+					Bidders: []string{"Bidder1", "Bidder2"},
+					SChain: openrtb_ext.ExtRequestPrebidSChainSChain{
+						Complete: 1,
+						Nodes: []*openrtb_ext.ExtRequestPrebidSChainSChainNode{
+							{
+								ASI:    "asi1",
+								SID:    "sid1",
+								Name:   "name1",
+								RID:    "rid1",
+								Domain: "domain1",
+								HP:     1,
+							},
+							{
+								ASI:    "asi2",
+								SID:    "sid2",
+								Name:   "name2",
+								RID:    "rid2",
+								Domain: "domain2",
+								HP:     2,
+							},
+						},
+						Ver: "version1",
+					},
+				},
+				{
+					Bidders: []string{"Bidder3", "Bidder4"},
+					SChain:  openrtb_ext.ExtRequestPrebidSChainSChain{},
+				},
+			},
+		},
+	}
+
+	output, err := BidderToPrebidSChains(input.Prebid.SChains)
+
+	assert.Nil(t, err)
+	assert.Equal(t, len(output), 4)
+	assert.Same(t, output["Bidder1"], &input.Prebid.SChains[0].SChain)
+	assert.Same(t, output["Bidder2"], &input.Prebid.SChains[0].SChain)
+	assert.Same(t, output["Bidder3"], &input.Prebid.SChains[1].SChain)
+	assert.Same(t, output["Bidder4"], &input.Prebid.SChains[1].SChain)
+}
+
+func TestBidderToPrebidChainsDiscardMultipleChainsForBidder(t *testing.T) {
+	input := openrtb_ext.ExtRequest{
+		Prebid: openrtb_ext.ExtRequestPrebid{
+			SChains: []*openrtb_ext.ExtRequestPrebidSChain{
+				{
+					Bidders: []string{"Bidder1"},
+					SChain:  openrtb_ext.ExtRequestPrebidSChainSChain{},
+				},
+				{
+					Bidders: []string{"Bidder1", "Bidder2"},
+					SChain:  openrtb_ext.ExtRequestPrebidSChainSChain{},
+				},
+			},
+		},
+	}
+
+	output, err := BidderToPrebidSChains(input.Prebid.SChains)
+
+	assert.NotNil(t, err)
+	assert.Nil(t, output)
+}
+
+func TestBidderToPrebidChainsNilSChains(t *testing.T) {
+	input := openrtb_ext.ExtRequest{
+		Prebid: openrtb_ext.ExtRequestPrebid{
+			SChains: nil,
+		},
+	}
+
+	output, err := BidderToPrebidSChains(input.Prebid.SChains)
+
+	assert.Nil(t, err)
+	assert.Equal(t, len(output), 0)
+}
+
+func TestBidderToPrebidChainsZeroLengthSChains(t *testing.T) {
+	input := openrtb_ext.ExtRequest{
+		Prebid: openrtb_ext.ExtRequestPrebid{
+			SChains: []*openrtb_ext.ExtRequestPrebidSChain{},
+		},
+	}
+
+	output, err := BidderToPrebidSChains(input.Prebid.SChains)
+
+	assert.Nil(t, err)
+	assert.Equal(t, len(output), 0)
+}
diff --git a/schain/schainwriter_test.go b/schain/schainwriter_test.go
new file mode 100644
index 00000000..92dc550d
--- /dev/null
+++ b/schain/schainwriter_test.go
@@ -0,0 +1,182 @@
+package schain
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/mxmCherry/openrtb/v15/openrtb2"
+	"github.com/prebid/prebid-server/openrtb_ext"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestSChainWriter(t *testing.T) {
+
+	const seller1SChain string = `"schain":{"complete":1,"nodes":[{"asi":"directseller1.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}`
+	const seller2SChain string = `"schain":{"complete":2,"nodes":[{"asi":"directseller2.com","sid":"00002","rid":"BidRequest2","hp":2}],"ver":"2.0"}`
+	const seller3SChain string = `"schain":{"complete":3,"nodes":[{"asi":"directseller3.com","sid":"00003","rid":"BidRequest3","hp":3}],"ver":"3.0"}`
+	const sellerWildCardSChain string = `"schain":{"complete":1,"nodes":[{"asi":"wildcard1.com","sid":"wildcard1","rid":"WildcardReq1","hp":1}],"ver":"1.0"}`
+
+	tests := []struct {
+		description string
+		giveRequest openrtb2.BidRequest
+		giveBidder  string
+		wantRequest openrtb2.BidRequest
+		wantError   bool
+	}{
+		{
+			description: "nil source and nil ext.prebid.schains",
+			giveRequest: openrtb2.BidRequest{
+				Ext:    nil,
+				Source: nil,
+			},
+			giveBidder: "appnexus",
+			wantRequest: openrtb2.BidRequest{
+				Ext:    nil,
+				Source: nil,
+			},
+		},
+		{
+			description: "Use source schain -- no bidder schain or wildcard schain in nil ext.prebid.schains",
+			giveRequest: openrtb2.BidRequest{
+				Ext: json.RawMessage(`{}`),
+				Source: &openrtb2.Source{
+					Ext: json.RawMessage(`{` + seller2SChain + `}`),
+				},
+			},
+			giveBidder: "appnexus",
+			wantRequest: openrtb2.BidRequest{
+				Ext: json.RawMessage(`{}`),
+				Source: &openrtb2.Source{
+					Ext: json.RawMessage(`{` + seller2SChain + `}`),
+				},
+			},
+		},
+		{
+			description: "Use source schain -- no bidder schain or wildcard schain in not nil ext.prebid.schains",
+			giveRequest: openrtb2.BidRequest{
+				Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `}]}}`),
+				Source: &openrtb2.Source{
+					Ext: json.RawMessage(`{` + seller2SChain + `}`),
+				},
+			},
+			giveBidder: "rubicon",
+			wantRequest: openrtb2.BidRequest{
+				Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `}]}}`),
+				Source: &openrtb2.Source{
+					Ext: json.RawMessage(`{` + seller2SChain + `}`),
+				},
+			},
+		},
+		{
+			description: "Use schain for bidder in ext.prebid.schains; ensure other ext.source field values are retained.",
+			giveRequest: openrtb2.BidRequest{
+				Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `}]}}`),
+				Source: &openrtb2.Source{
+					FD:     1,
+					TID:    "tid data",
+					PChain: "pchain data",
+					Ext:    json.RawMessage(`{` + seller2SChain + `}`),
+				},
+			},
+			giveBidder: "appnexus",
+			wantRequest: openrtb2.BidRequest{
+				Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `}]}}`),
+				Source: &openrtb2.Source{
+					FD:     1,
+					TID:    "tid data",
+					PChain: "pchain data",
+					Ext:    json.RawMessage(`{` + seller1SChain + `}`),
+				},
+			},
+		},
+		{
+			description: "Use schain for bidder in ext.prebid.schains, nil req.source ",
+			giveRequest: openrtb2.BidRequest{
+				Ext:    json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `}]}}`),
+				Source: nil,
+			},
+			giveBidder: "appnexus",
+			wantRequest: openrtb2.BidRequest{
+				Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `}]}}`),
+				Source: &openrtb2.Source{
+					Ext: json.RawMessage(`{` + seller1SChain + `}`),
+				},
+			},
+		},
+		{
+			description: "Use wildcard schain in ext.prebid.schains.",
+			giveRequest: openrtb2.BidRequest{
+				Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["*"],` + sellerWildCardSChain + `}]}}`),
+				Source: &openrtb2.Source{
+					Ext: nil,
+				},
+			},
+			giveBidder: "appnexus",
+			wantRequest: openrtb2.BidRequest{
+				Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["*"],` + sellerWildCardSChain + `}]}}`),
+				Source: &openrtb2.Source{
+					Ext: json.RawMessage(`{` + sellerWildCardSChain + `}`),
+				},
+			},
+		},
+		{
+			description: "Use schain for bidder in ext.prebid.schains instead of wildcard.",
+			giveRequest: openrtb2.BidRequest{
+				Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `},{"bidders":["*"],` + sellerWildCardSChain + `}]}}`),
+				Source: &openrtb2.Source{
+					Ext: nil,
+				},
+			},
+			giveBidder: "appnexus",
+			wantRequest: openrtb2.BidRequest{
+				Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `},{"bidders":["*"],` + sellerWildCardSChain + `}]}}`),
+				Source: &openrtb2.Source{
+					Ext: json.RawMessage(`{` + seller1SChain + `}`),
+				},
+			},
+		},
+		{
+			description: "Use source schain -- multiple (two) bidder schains in ext.prebid.schains.",
+			giveRequest: openrtb2.BidRequest{
+				Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `},{"bidders":["appnexus"],` + seller2SChain + `}]}}`),
+				Source: &openrtb2.Source{
+					Ext: json.RawMessage(`{` + seller3SChain + `}`),
+				},
+			},
+			giveBidder: "appnexus",
+			wantRequest: openrtb2.BidRequest{
+				Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `},{"bidders":["appnexus"],` + seller2SChain + `}]}}`),
+				Source: &openrtb2.Source{
+					Ext: json.RawMessage(`{` + seller3SChain + `}`),
+				},
+			},
+			wantError: true,
+		},
+	}
+
+	for _, tt := range tests {
+		// unmarshal ext to get schains object needed to initialize writer
+		var reqExt *openrtb_ext.ExtRequest
+		if tt.giveRequest.Ext != nil {
+			reqExt = &openrtb_ext.ExtRequest{}
+			err := json.Unmarshal(tt.giveRequest.Ext, reqExt)
+			if err != nil {
+				t.Error("Unable to unmarshal request.ext")
+			}
+		}
+
+		writer, err := NewSChainWriter(reqExt)
+
+		if tt.wantError {
+			assert.NotNil(t, err)
+			assert.Nil(t, writer)
+		} else {
+			assert.Nil(t, err)
+			assert.NotNil(t, writer)
+
+			writer.Write(&tt.giveRequest, tt.giveBidder)
+
+			assert.Equal(t, tt.wantRequest, tt.giveRequest, tt.description)
+		}
+	}
+}