Repository: prebid/prebid-server
Pull request #1775 (2021-03-24 17:03:01)

New Adapter: Criteo
Hello PBS Team :)

This PR aims to plug Criteo's adapter. We still have couple things to test on our end but would love to have a review on this adapter in the meantime.

One question, we will be using `user.ext.eids` to retrieve Criteo's user ID. While testing locally, I was trying to get outgoing bid request to Criteo but somehow, I was never able to have anything populated inside PBS `user.ext.eids`, I might miss something here, if you have any idea it would help me to do an end to end test.

Here's the bid request I send to PBS:
```
{
   "id":"test-request-id",
   "site":{
      "id":"site-id",
      "page":"criteo.com"
   },
   "device":{
      "os":"android",
      "ip":"91.199.242.236",
      "ua":"random user agent"
   },
   "user":{
      "ext":{
         "eids":[
            {
               "source":"criteo.com",
               "uids":[
                  {
                     "id":"criteo-eid"
                  }
               ]
            },
            {
                "source": "pubcommon",
                "id":"11111111"
            }
         ]
      }
   },
   "imp":[
      {
         "id":"test-imp-id",
         "banner":{
            "format":[
               {
                  "w":300,
                  "h":250
               }
            ]
         },
         "ext":{
            "criteo":{
               "zoneid":123456,
               "networkid":78910
            }
         }
      }
   ]
}
```

Also, we would like to be off by default, and publishers we are already working / whiling to work with us would have to reach their Criteo point of contact. I guess this should be described in adapter's description on prebid documentation website, if so, I will edit the description and link it to this PR. 

Cheers !

benjaminch: Hey @SyntaxNode , thanks for the detailed review, still working on it :)

Regarding the weird user Eids disappearing before reaching the adapter, here's the bid request I am using to test:

```
{
  "id": "a573034d-b427-4bbe-9678-2877c0857dd1",
  "test": 1,
  "debug": 1,
  "source": {
    "tid": "a573034d-b427-4bbe-9678-2877c0857dd1"
  },
  "tmax": 500,
  "imp": [
    {
      "id": "div-gpt-ad-1478190851177-0",
      "ext": {
        "criteo": {
          "networkId": 72
        }
      },
      "banner": {
        "format": [
          {
            "w": 728,
            "h": 90
          }
        ]
      }
    }
  ],
  "ext": {
    "prebid": {
      "auctiontimestamp": 1616768550862,
      "targeting": {
        "includewinners": true,
        "includebidderkeys": false
      }
    }
  },
  "site": {
    "publisher": {
      "id": "1"
    },
    "page": "https://test.com/test"
  },
  "device": {
    "w": 1386,
    "h": 473
  },
  "user": {
    "ext": {
      "eids": [
        {
          "source": "criteo.com",
          "uids": [
            {
              "id": "2awJ7V9MJTJCQmI1Um9nNU5JVTdDS21YQ1BPRWdFNiUyRiUyQnhrUzlyTXRNTGFiSWtSOTB1RGU0OHpwVjNTSXBOUHBubHlDMU5SU3d3MG9MaGtSVWxDVno4T3NpeWVvUSUzRCUzRA",
              "atype": 1
            }
          ]
        }
      ]
    }
  }
}
```

But here's the request the adapter receives:

```
{
    "id": "a573034d-b427-4bbe-9678-2877c0857dd1",
    "imp": [
        {
            "id": "div-gpt-ad-1478190851177-0",
            "banner": {
                "format": [
                    {
                        "w": 728,
                        "h": 90
                    }
                ]
            },
            "ext": {
                "bidder": {
                    "networkId": 72
                }
            }
        }
    ],
    "site": {
        "page": "https://publisherdirect.criteo.com/publishertag/testleo/examples/prebid-server/prebid.html?pbtUrl=//publisherdirect.criteo.com/publishertag/preprodtest/publishertag.min.js&prebidUrl=//publisherdirect.criteo.com/publishertag/examples/prebid-bundles/prebid4.20.0.js&cdbUrl=//directbidder-test-app.par.preprod.crto.in/&pbjs_debug=true&pbt_debug=1&pbt_nolog=1",
        "publisher": {
            "id": "1"
        },
        "ext": {
            "amp": 0
        }
    },
    "device": {
        "ua": "curl/7.54.0",
        "h": 473,
        "w": 1386
    },
    "user": {
        "ext": {}
    },
    "test": 1,
    "at": 1,
    "tmax": 500,
    "source": {
        "tid": "a573034d-b427-4bbe-9678-2877c0857dd1"
    },
    "ext": {
        "prebid": {
            "targeting": {
                "pricegranularity": {
                    "precision": 2,
                    "ranges": [
                        {
                            "min": 0,
                            "max": 20,
                            "increment": 0.1
                        }
                    ]
                },
                "includewinners": true,
                "includebidderkeys": false,
                "includebrandcategory": null,
                "includeformat": false,
                "durationrangesec": null,
                "preferdeals": false
            }
        }
    }
}
```

User object (especially extension) is empty, I think I miss either a config or a trick in the code but cannot find what though ...
Do you have any idea?

Cheers ! :)
SyntaxNode: > Hey @SyntaxNode , thanks for the detailed review, still working on it :)

Awesome. No rush.

Thank you for sharing your sample request. The EID problem is being caused by Prebid Server's default GDPR policy. To fix it, change the `gdpr.usersync_if_ambiguous` setting to `true` from the default of `false`.

Let's dig a little deeper into what's going on here. A GDPR signal is considered 'ambiguous` by Prebid Server if a bid request does not explicitly contain a GDPR value of 0 or 1. With `usersync_if_ambiguous` set to `false`, Prebid Server interprets an ambiguous GDPR signal as the user not providing consent and removes personal information from the bid request, including EIDs. The original intent was for hosts to set this to `false` for European data centers and `true` for non-GDPR data centers, but we have since added more precise geo filtering enforcement defaults.

I don't think `false` is a good default, especially when it causes confusion. The setting was ignored out of the box previously, but due to relatively recent changes of how Prebid Server interprets an unconfigured vendor id for https://github.com/prebid/prebid-server/issues/1393., the setting is now active. This is a side affect we missed. I'll propose changing to it `true` with the team next week.


benjaminch: Hey @SyntaxNode !

Thanks a lot, indeed the GDPR config did the traffic, that's awesome :)

One other random question, I would like to generate a random UUID in the adapter and pass it to Criteo backend. 
Is there anything I can use / do to make it testable and working with the PBS bid request end to end tests framework ?

Thanks !
SyntaxNode: > Thanks a lot, indeed the GDPR config did the traffic, that's awesome :)

Yay! Glad that worked. I wonder if this is related to an issue that was reported today. If so, I think we'll be changing that default.

> One other random question, I would like to generate a random UUID in the adapter and pass it to Criteo backend.
> Is there anything I can use / do to make it testable and working with the PBS bid request end to end tests framework ?

We prefer to use the `github.com/gofrs/uuid` package for uuid generation. For testing, you'll want to wrap the uuid generator in an interface and override it for the test. Something along the lines of:

```
type idGenerator interface {
  New() string, error
}
```

```
type uuidGenerator struct {}

func (uuidGenerator) New() (string, error) {
  return uuid.NewV4()
}
```

```
type adapter struct {
  URI string
  idGenerator idGenerator
}
```

```
func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) {
  return &adapter{
    URI: config.Endpoint,
    idGenerator: uuidGenerator{},
  }, nil
}
```

```
func (a *adapter) MakeRequests(request *openrtb2.BidRequest, extraRequestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
  ...
  id, err := a.idGenerator.New()
  if err != nil {
    // handle error properly
  }
  ...
}
```

and then in your tests, replace the random id generator with a hardcoded one set with a known value:

```
type fakeIDGenerator struct {}

func (fakeIDGenerator) New() (string, error) {
  return "someUUID", nil
}

func TestJsonSamples(t *testing.T) {
  bidder, buildErr := Builder(
    openrtb_ext.BidderAdyoulike,
      config.Adapter{
        Endpoint: "https://bidder.criteo.com/cdb?profileId=230",
      },
  )

  if buildErr != nil {
    t.Fatalf("Builder returned unexpected error %v", buildErr)
  }

  bidderCriteo, _ := bidder.(*adapter)
  bidderCriteo.idGenerator = fakeIDGenerator{}

  adapterstest.RunJSONBidderTest(t, "criteotest", bidder)
}
```
benjaminch: Thanks @SyntaxNode 

> We prefer to use the github.com/gofrs/uuid package for uuid generation. For testing, you'll want to wrap the uuid generator in an interface and override it for the test. Something along the lines of:

Indeed, that's what I was starting to do but still, the end to end tests will generate a response with random GUID. Unless there is a way to update the adapter builder used in end to end tests, I am not sure it will works. 
I am talking about the `/exemplary` & `/supplemental` tests.
That's the one failing now.

Thanks again for your help :)
SyntaxNode: > Indeed, that's what I was starting to do but still, the end to end tests will generate a response with random GUID. Unless there is a way to update the adapter builder used in end to end tests, I am not sure it will works.
> I am talking about the `/exemplary` & `/supplemental` tests.

The end to end tests use the instance of the `bidder` passed to `RunJSONBidderTest`. If you replace the real random uuid generator with a fake one, the `/exemplary` and `/supplemental` tests will be fine. Look to the Consumable tests in `consumable_test.go` as an example. They replace a real clock with a fake one.

> Thanks again for your help :)

👍 
benjaminch: > The end to end tests use the instance of the `bidder` passed to `RunJSONBidderTest`. If you replace the real random uuid generator with a fake one, the `/exemplary` and `/supplemental` tests will be fine. Look to the Consumable tests in `consumable_test.go` as an example. They replace a real clock with a fake one.

Oups, I missed this one :(
It will work perfectly then, thanks a lot, I will do it ASAP !

Cheers :)


benjaminch: Hey @SyntaxNode,

So I guess I fixed most of the issues you raised, please feel free to add yet another round of those if needed :)
I also submitted a PR on Prebid docs to update Criteo documentation: https://github.com/prebid/prebid.github.io/pull/2813.

I there anything I have to do to make Criteo adapter not activated by default?

Thanks a lot :) 
benjaminch: Thanks @SyntaxNode again, and sorry for those back and forth !
Just pushed another patch fixing your previous comments :)

Cheers
SyntaxNode: > Thanks @SyntaxNode again, and sorry for those back and forth !

Not a problem.

SyntaxNode: @benjaminch In regards to the docs PR https://github.com/prebid/prebid.github.io/pull/2813, I see that nativeCallback and integrationMode are listed as bidder parameters but are not included in this adapter. Could you please update the docs to make it clear those are for Prebid.js only?
benjaminch: > @benjaminch In regards to the docs PR [prebid/prebid.github.io#2813](https://github.com/prebid/prebid.github.io/pull/2813), I see that nativeCallback and integrationMode are listed as bidder parameters but are not included in this adapter. Could you please update the docs to make it clear those are for Prebid.js only?

Sure ! Just updated :) @SyntaxNode  let me know if it's clear enough.
https://github.com/prebid/prebid.github.io/pull/2813/files#diff-6366d20916fd15256725f7e4cd42547e7916c1241e26d3f88201d5baa317b0edR35
benjaminch: Hey @SyntaxNode, sorry for the last minute change, but I updated the adapter to be activated by default, we will do the filtering on Criteo side.

Thanks a lot :)
bsardo: @benjaminch And could you please push any changes from this point as a new change set so we can just look at the diff? 🙂 Thanks! 
benjaminch: > @benjaminch And could you please push any changes from this point as a new change set so we can just look at the diff? 🙂 Thanks!

Yes, sorry about that !

Patch:
diff --git a/adapters/bidmachine/bidmachine.go b/adapters/bidmachine/bidmachine.go
new file mode 100644
index 00000000..3c9a5b67
--- /dev/null
+++ b/adapters/bidmachine/bidmachine.go
@@ -0,0 +1,219 @@
+package bidmachine
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"net/url"
+	"path"
+	"strconv"
+	"text/template"
+
+	"github.com/mxmCherry/openrtb/v14/openrtb2"
+	"github.com/prebid/prebid-server/adapters"
+	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/errortypes"
+	"github.com/prebid/prebid-server/macros"
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+type adapter struct {
+	endpoint template.Template
+}
+
+func (a *adapter) MakeRequests(request *openrtb2.BidRequest, _ *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+	headers := http.Header{}
+	headers.Add("Content-Type", "application/json")
+	headers.Add("Accept", "application/json")
+	headers.Add("X-Openrtb-Version", "2.5")
+
+	impressions := request.Imp
+	result := make([]*adapters.RequestData, 0, len(impressions))
+	errs := make([]error, 0, len(impressions))
+
+	for _, impression := range impressions {
+		if impression.Banner != nil {
+			banner := impression.Banner
+			if banner.W == nil && banner.H == nil {
+				if banner.Format == nil {
+					errs = append(errs, &errortypes.BadInput{
+						Message: "Impression with id: " + impression.ID + " has following error: Banner width and height is not provided and banner format is missing. At least one is required",
+					})
+					continue
+				}
+				if len(banner.Format) == 0 {
+					errs = append(errs, &errortypes.BadInput{
+						Message: "Impression with id: " + impression.ID + " has following error: Banner width and height is not provided and banner format array is empty. At least one is required",
+					})
+					continue
+				}
+			}
+
+		}
+
+		var bidderExt adapters.ExtImpBidder
+		err := json.Unmarshal(impression.Ext, &bidderExt)
+		if err != nil {
+			errs = append(errs, err)
+			continue
+		}
+
+		var impressionExt openrtb_ext.ExtImpBidmachine
+		err = json.Unmarshal(bidderExt.Bidder, &impressionExt)
+		if err != nil {
+			errs = append(errs, err)
+			continue
+		}
+		url, err := a.buildEndpointURL(impressionExt)
+		if err != nil {
+			errs = append(errs, err)
+			continue
+		}
+		if bidderExt.Prebid != nil && bidderExt.Prebid.IsRewardedInventory == 1 {
+			if impression.Banner != nil && !hasRewardedBattr(impression.Banner.BAttr) {
+				bannerCopy := *impression.Banner
+				bannerCopy.BAttr = copyBAttrWithRewardedInventory(bannerCopy.BAttr)
+				impression.Banner = &bannerCopy
+			}
+			if impression.Video != nil && !hasRewardedBattr(impression.Video.BAttr) {
+				videoCopy := *impression.Video
+				videoCopy.BAttr = copyBAttrWithRewardedInventory(videoCopy.BAttr)
+				impression.Video = &videoCopy
+			}
+		}
+		request.Imp = []openrtb2.Imp{impression}
+		body, err := json.Marshal(request)
+		if err != nil {
+			errs = append(errs, err)
+			continue
+		}
+		result = append(result, &adapters.RequestData{
+			Method:  "POST",
+			Uri:     url,
+			Body:    body,
+			Headers: headers,
+		})
+	}
+
+	request.Imp = impressions
+
+	return result, errs
+}
+
+func hasRewardedBattr(attr []openrtb2.CreativeAttribute) bool {
+	for i := 0; i < len(attr); i++ {
+		if attr[i] == openrtb2.CreativeAttribute(16) {
+			return true
+		}
+	}
+	return false
+}
+
+func (a *adapter) MakeBids(request *openrtb2.BidRequest, _ *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+	var errs []error
+
+	switch responseData.StatusCode {
+	case http.StatusNoContent:
+		return nil, nil
+	case http.StatusServiceUnavailable:
+		fallthrough
+	case http.StatusBadRequest:
+		fallthrough
+	case http.StatusUnauthorized:
+		fallthrough
+	case http.StatusForbidden:
+		return nil, []error{&errortypes.BadInput{
+			Message: "unexpected status code: " + strconv.Itoa(responseData.StatusCode) + " " + string(responseData.Body),
+		}}
+	case http.StatusOK:
+		break
+	default:
+		return nil, []error{&errortypes.BadServerResponse{
+			Message: "unexpected status code: " + strconv.Itoa(responseData.StatusCode) + " " + string(responseData.Body),
+		}}
+	}
+
+	var bidResponse openrtb2.BidResponse
+	err := json.Unmarshal(responseData.Body, &bidResponse)
+	if err != nil {
+		return nil, []error{&errortypes.BadServerResponse{
+			Message: err.Error(),
+		}}
+	}
+
+	response := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp))
+
+	for _, seatBid := range bidResponse.SeatBid {
+		for _, bid := range seatBid.Bid {
+			thisBid := bid
+			bidType := GetMediaTypeForImp(bid.ImpID, request.Imp)
+			if bidType == UndefinedMediaType {
+				errs = append(errs, &errortypes.BadServerResponse{
+					Message: "ignoring bid id=" + bid.ID + ", request doesn't contain any valid impression with id=" + bid.ImpID,
+				})
+				continue
+			}
+			response.Bids = append(response.Bids, &adapters.TypedBid{
+				Bid:     &thisBid,
+				BidType: bidType,
+			})
+		}
+	}
+
+	return response, errs
+}
+
+// Builder builds a new instance of the Bidmachine adapter for the given bidder with the given config.
+func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) {
+	template, err := template.New("endpointTemplate").Parse(config.Endpoint)
+	if err != nil {
+		return nil, fmt.Errorf("unable to parse endpoint url template: %v", err)
+	}
+
+	bidder := &adapter{
+		endpoint: *template,
+	}
+
+	return bidder, nil
+}
+
+const UndefinedMediaType = openrtb_ext.BidType("")
+
+func (a *adapter) buildEndpointURL(params openrtb_ext.ExtImpBidmachine) (string, error) {
+	endpointParams := macros.EndpointTemplateParams{Host: params.Host}
+	uriString, errMacros := macros.ResolveMacros(a.endpoint, endpointParams)
+	if errMacros != nil {
+		return "", &errortypes.BadInput{
+			Message: "Failed to resolve host macros",
+		}
+	}
+	uri, errUrl := url.Parse(uriString)
+	if errUrl != nil || uri.Scheme == "" || uri.Host == "" {
+		return "", &errortypes.BadInput{
+			Message: "Failed to create final URL with provided host",
+		}
+	}
+	uri.Path = path.Join(uri.Path, params.Path)
+	uri.Path = path.Join(uri.Path, params.SellerID)
+	return uri.String(), nil
+}
+
+func copyBAttrWithRewardedInventory(src []openrtb2.CreativeAttribute) []openrtb2.CreativeAttribute {
+	dst := make([]openrtb2.CreativeAttribute, len(src))
+	copy(dst, src)
+	dst = append(dst, openrtb2.CreativeAttribute(16))
+	return dst
+}
+
+func GetMediaTypeForImp(impID string, imps []openrtb2.Imp) openrtb_ext.BidType {
+	mediaType := openrtb_ext.BidTypeBanner
+	for _, imp := range imps {
+		if imp.ID == impID {
+			if imp.Banner == nil && imp.Video != nil {
+				mediaType = openrtb_ext.BidTypeVideo
+			}
+			return mediaType
+		}
+	}
+	return UndefinedMediaType
+}
diff --git a/adapters/criteo/criteo.go b/adapters/criteo/criteo.go
new file mode 100644
index 00000000..f045c9f0
--- /dev/null
+++ b/adapters/criteo/criteo.go
@@ -0,0 +1,114 @@
+package criteo
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+
+	"github.com/mxmCherry/openrtb/v14/openrtb2"
+	"github.com/prebid/prebid-server/adapters"
+	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/errortypes"
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+type adapter struct {
+	uri             string
+	slotIDGenerator slotIDGenerator
+}
+
+func (a *adapter) MakeRequests(request *openrtb2.BidRequest, extraRequestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+	criteoRequest, errs := newCriteoRequest(a.slotIDGenerator, request)
+	if len(errs) > 0 {
+		return nil, errs
+	}
+
+	jsonRequest, err := json.Marshal(criteoRequest)
+	if err != nil {
+		return nil, []error{err}
+	}
+
+	rqData := adapters.RequestData{
+		Method:  "POST",
+		Uri:     a.uri,
+		Body:    jsonRequest,
+		Headers: getCriteoRequestHeaders(&criteoRequest),
+	}
+
+	return []*adapters.RequestData{&rqData}, nil
+}
+
+func getCriteoRequestHeaders(criteoRequest *criteoRequest) http.Header {
+	headers := http.Header{}
+
+	// criteoRequest is known not to be nil
+	// If there was an error generating it from newCriteoRequest, the errors will be returned immediately
+	// and this method won't be called
+
+	if criteoRequest.User.CookieID != "" {
+		headers.Add("Cookie", "uid="+criteoRequest.User.CookieID)
+	}
+
+	if criteoRequest.User.IP != "" {
+		headers.Add("X-Forwarded-For", criteoRequest.User.IP)
+	}
+
+	if criteoRequest.User.IPv6 != "" {
+		headers.Add("X-Forwarded-For", criteoRequest.User.IPv6)
+	}
+
+	if criteoRequest.User.UA != "" {
+		headers.Add("User-Agent", criteoRequest.User.UA)
+	}
+
+	return headers
+}
+
+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. Run with request.debug = 1 for more info", response.StatusCode),
+		}}
+	}
+
+	bidResponse, err := newCriteoResponseFromBytes(response.Body)
+	if err != nil {
+		return nil, []error{err}
+	}
+
+	bidderResponse := adapters.NewBidderResponse()
+	bidderResponse.Bids = make([]*adapters.TypedBid, len(bidResponse.Slots))
+
+	for i := 0; i < len(bidResponse.Slots); i++ {
+		bidderResponse.Bids[i] = &adapters.TypedBid{
+			Bid: &openrtb2.Bid{
+				ID:    bidResponse.Slots[i].ID,
+				ImpID: bidResponse.Slots[i].ImpID,
+				Price: bidResponse.Slots[i].CPM,
+				AdM:   bidResponse.Slots[i].Creative,
+				W:     bidResponse.Slots[i].Width,
+				H:     bidResponse.Slots[i].Height,
+				CrID:  bidResponse.Slots[i].CreativeID,
+			},
+			BidType: openrtb_ext.BidTypeBanner,
+		}
+	}
+
+	return bidderResponse, nil
+}
+
+// Builder builds a new instance of the Criteo adapter for the given bidder with the given config.
+func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) {
+	return builderWithGuidGenerator(bidderName, config, newRandomSlotIDGenerator())
+}
+
+func builderWithGuidGenerator(bidderName openrtb_ext.BidderName, config config.Adapter, slotIDGenerator slotIDGenerator) (adapters.Bidder, error) {
+	return &adapter{
+		uri:             config.Endpoint,
+		slotIDGenerator: slotIDGenerator,
+	}, nil
+}
diff --git a/adapters/criteo/generators.go b/adapters/criteo/generators.go
new file mode 100644
index 00000000..0ae0ceca
--- /dev/null
+++ b/adapters/criteo/generators.go
@@ -0,0 +1,36 @@
+package criteo
+
+import "github.com/gofrs/uuid"
+
+type slotIDGenerator interface {
+	NewSlotID() (string, error)
+}
+
+type randomSlotIDGenerator struct{}
+
+func newRandomSlotIDGenerator() randomSlotIDGenerator {
+	return randomSlotIDGenerator{}
+}
+
+func (g randomSlotIDGenerator) NewSlotID() (string, error) {
+	guid, err := uuid.NewV4()
+	if err != nil {
+		return "", err
+	}
+
+	return guid.String(), nil
+}
+
+type fakeSlotIDGenerator struct {
+	fakeSlotID string
+}
+
+func newFakeGuidGenerator(fakeSlotID string) fakeSlotIDGenerator {
+	return fakeSlotIDGenerator{
+		fakeSlotID,
+	}
+}
+
+func (f fakeSlotIDGenerator) NewSlotID() (string, error) {
+	return f.fakeSlotID, nil
+}
diff --git a/adapters/criteo/models.go b/adapters/criteo/models.go
new file mode 100644
index 00000000..64f6081d
--- /dev/null
+++ b/adapters/criteo/models.go
@@ -0,0 +1,299 @@
+package criteo
+
+import (
+	"encoding/json"
+	"fmt"
+	"strings"
+
+	"github.com/mxmCherry/openrtb/v14/openrtb2"
+	"github.com/prebid/prebid-server/adapters"
+	"github.com/prebid/prebid-server/errortypes"
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+type criteoRequest struct {
+	ID          string                   `json:"id,omitempty"`
+	Publisher   criteoPublisher          `json:"publisher,omitempty"`
+	User        criteoUser               `json:"user,omitempty"`
+	GdprConsent criteoGdprConsent        `json:"gdprconsent,omitempty"`
+	Slots       []criteoRequestSlot      `json:"slots,omitempty"`
+	Eids        []openrtb_ext.ExtUserEid `json:"eids,omitempty"`
+}
+
+func newCriteoRequest(slotIDGenerator slotIDGenerator, request *openrtb2.BidRequest) (criteoRequest, []error) {
+	var errs []error
+
+	// request cannot be nil by design
+
+	criteoRequest := criteoRequest{}
+
+	criteoRequest.ID = request.ID
+
+	// Extracting request slots
+	if len(request.Imp) > 0 {
+		criteoSlots, slotsErr := newCriteoRequestSlots(slotIDGenerator, request.Imp)
+		if len(slotsErr) > 0 {
+			return criteoRequest, slotsErr
+		}
+		criteoRequest.Slots = criteoSlots
+	}
+
+	var networkId *int64
+	for _, criteoSlot := range criteoRequest.Slots {
+		if networkId == nil && criteoSlot.NetworkID != nil && *criteoSlot.NetworkID > 0 {
+			networkId = criteoSlot.NetworkID
+		} else if networkId != nil && criteoSlot.NetworkID != nil && *criteoSlot.NetworkID != *networkId {
+			return criteoRequest, []error{&errortypes.BadInput{
+				Message: "Bid request has slots coming with several network IDs which is not allowed",
+			}}
+		}
+	}
+
+	criteoRequest.Publisher = newCriteoPublisher(networkId, request.App, request.Site)
+
+	var regsExt *openrtb_ext.ExtRegs
+	if request.Regs != nil && request.Regs.Ext != nil {
+		if err := json.Unmarshal(request.Regs.Ext, &regsExt); err != nil {
+			errs = append(errs, err)
+		}
+	}
+
+	criteoRequest.User = newCriteoUser(request.User, request.Device, regsExt)
+
+	if gdprConsent, err := newCriteoGdprConsent(request.User, regsExt); err != nil {
+		errs = append(errs, err)
+	} else {
+		criteoRequest.GdprConsent = gdprConsent
+	}
+
+	if request.User != nil && request.User.Ext != nil {
+		var extUser openrtb_ext.ExtUser
+		if err := json.Unmarshal(request.User.Ext, &extUser); err != nil {
+			errs = append(errs, err)
+		} else {
+			criteoRequest.Eids = extUser.Eids
+		}
+	}
+
+	return criteoRequest, errs
+}
+
+type criteoPublisher struct {
+	SiteID    string `json:"siteid,omitempty"`
+	BundleID  string `json:"bundleid,omitempty"`
+	URL       string `json:"url,omitempty"`
+	NetworkID *int64 `json:"networkid,omitempty"`
+}
+
+func newCriteoPublisher(networkId *int64, app *openrtb2.App, site *openrtb2.Site) criteoPublisher {
+	// Both app and site cannot be nil at the same time by design in PBS
+
+	criteoPublisher := criteoPublisher{}
+
+	if networkId != nil && *networkId > 0 {
+		criteoPublisher.NetworkID = networkId
+	}
+
+	if app != nil {
+		criteoPublisher.BundleID = app.Bundle
+	}
+
+	if site != nil {
+		criteoPublisher.SiteID = site.ID
+		criteoPublisher.URL = site.Page
+	}
+
+	return criteoPublisher
+}
+
+type criteoUser struct {
+	DeviceID     string `json:"deviceid,omitempty"`
+	DeviceOS     string `json:"deviceos,omitempty"`
+	DeviceIDType string `json:"deviceidtype,omitempty"`
+	CookieID     string `json:"cookieuid,omitempty"`
+	UID          string `json:"uid,omitempty"`
+	IP           string `json:"ip,omitempty"`
+	IPv6         string `json:"ipv6,omitempty"`
+	UA           string `json:"ua,omitempty"`
+	UspIab       string `json:"uspIab,omitempty"`
+}
+
+func newCriteoUser(user *openrtb2.User, device *openrtb2.Device, regsExt *openrtb_ext.ExtRegs) criteoUser {
+	criteoUser := criteoUser{}
+
+	if user == nil && device == nil {
+		return criteoUser
+	}
+
+	if user != nil {
+		criteoUser.CookieID = user.BuyerUID
+	}
+
+	if device != nil {
+		deviceType := getDeviceType(device.OS)
+		criteoUser.DeviceIDType = deviceType
+
+		criteoUser.DeviceOS = device.OS
+		criteoUser.DeviceID = device.IFA
+		criteoUser.IP = device.IP
+		criteoUser.IPv6 = device.IPv6
+		criteoUser.UA = device.UA
+	}
+
+	if regsExt != nil {
+		criteoUser.UspIab = regsExt.USPrivacy // CCPA
+	}
+
+	return criteoUser
+}
+
+type criteoGdprConsent struct {
+	GdprApplies *bool  `json:"gdprapplies,omitempty"`
+	ConsentData string `json:"consentdata,omitempty"`
+}
+
+func newCriteoGdprConsent(user *openrtb2.User, regsExt *openrtb_ext.ExtRegs) (criteoGdprConsent, error) {
+	consent := criteoGdprConsent{}
+
+	if user == nil && regsExt == nil {
+		return consent, nil
+	}
+
+	if user != nil && user.Ext != nil {
+		var userExt *openrtb_ext.ExtUser
+		if err := json.Unmarshal(user.Ext, &userExt); err != nil {
+			return consent, err
+		}
+		consent.ConsentData = userExt.Consent
+	}
+
+	if regsExt != nil {
+		if regsExt.GDPR != nil {
+			gdprApplies := bool((*regsExt.GDPR & 1) == 1)
+			consent.GdprApplies = &gdprApplies
+		}
+	}
+
+	return consent, nil
+}
+
+type criteoRequestSlot struct {
+	SlotID    string              `json:"slotid,omitempty"`
+	ImpID     string              `json:"impid,omitempty"`
+	ZoneID    *int64              `json:"zoneid,omitempty"`
+	NetworkID *int64              `json:"networkid,omitempty"`
+	Sizes     []criteoRequestSize `json:"sizes,omitempty"`
+}
+
+func newCriteoRequestSlots(slotIDGenerator slotIDGenerator, impressions []openrtb2.Imp) ([]criteoRequestSlot, []error) {
+	var errs []error
+
+	// `impressions` known not to be nil or empty by design, PBS checks it upstream.
+
+	// Criteo slot should comes with any of (both are ok as well):
+	//   - `zoneid`
+	//   - `networkid`, `slotid`, `sizes`
+	//
+	// if not, criteo will reject the slot.
+
+	var criteoSlots = make([]criteoRequestSlot, len(impressions))
+
+	for i := 0; i < len(impressions); i++ {
+		criteoSlots[i] = criteoRequestSlot{}
+
+		criteoSlots[i].ImpID = impressions[i].ID
+
+		// Generating a random slot ID
+		generatedSlotID, err := slotIDGenerator.NewSlotID()
+		if err != nil {
+			errs = append(errs, err)
+			continue
+		}
+		criteoSlots[i].SlotID = generatedSlotID
+
+		if impressions[i].Banner != nil {
+			if impressions[i].Banner.Format != nil {
+				criteoSlots[i].Sizes = make([]criteoRequestSize, len(impressions[i].Banner.Format))
+				for idx, format := range impressions[i].Banner.Format {
+					criteoSlots[i].Sizes[idx] = newCriteoRequestSize(format.W, format.H)
+				}
+			} else if impressions[i].Banner.W != nil && *impressions[i].Banner.W > 0 && impressions[i].Banner.H != nil && *impressions[i].Banner.H > 0 {
+				criteoSlots[i].Sizes = make([]criteoRequestSize, 1)
+				criteoSlots[i].Sizes[0] = newCriteoRequestSize(*impressions[i].Banner.W, *impressions[i].Banner.H)
+			}
+		}
+
+		var bidderExt adapters.ExtImpBidder
+		if err := json.Unmarshal(impressions[i].Ext, &bidderExt); err != nil {
+			errs = append(errs, err)
+			continue
+		}
+
+		if bidderExt.Bidder != nil {
+			var criteoExt openrtb_ext.ExtImpCriteo
+			if err := json.Unmarshal(bidderExt.Bidder, &criteoExt); err != nil {
+				errs = append(errs, err)
+				continue
+			}
+			if criteoExt.ZoneID > 0 {
+				criteoSlots[i].ZoneID = &criteoExt.ZoneID
+			}
+			if criteoExt.NetworkID > 0 {
+				criteoSlots[i].NetworkID = &criteoExt.NetworkID
+			}
+		}
+	}
+
+	return criteoSlots, errs
+}
+
+type criteoRequestSize = string
+
+func newCriteoRequestSize(width int64, height int64) criteoRequestSize {
+	return fmt.Sprintf("%dx%d", width, height)
+}
+
+var deviceType = map[string]string{
+	"ios":     "idfa",
+	"android": "gaid",
+	"unknown": "unknown",
+}
+
+func getDeviceType(os string) string {
+	if os != "" {
+		if dtype, ok := deviceType[strings.ToLower(os)]; ok {
+			return dtype
+		}
+	}
+
+	return deviceType["unknown"]
+}
+
+type criteoResponse struct {
+	ID    string               `json:"id,omitempty"`
+	Slots []criteoResponseSlot `json:"slots,omitempty"`
+}
+
+func newCriteoResponseFromBytes(bytes []byte) (criteoResponse, error) {
+	var err error
+	var bidResponse criteoResponse
+
+	if err = json.Unmarshal(bytes, &bidResponse); err != nil {
+		return bidResponse, err
+	}
+
+	return bidResponse, nil
+}
+
+type criteoResponseSlot struct {
+	ID         string  `json:"id,omitempty"`
+	ImpID      string  `json:"impid,omitempty"`
+	ZoneID     int64   `json:"zoneid,omitempty"`
+	NetworkID  int64   `json:"networkid,omitempty"`
+	CPM        float64 `json:"cpm,omitempty"`
+	Currency   string  `json:"currency,omitempty"`
+	Width      int64   `json:"width,omitempty"`
+	Height     int64   `json:"height,omitempty"`
+	Creative   string  `json:"creative,omitempty"`
+	CreativeID string  `json:"creativeid,omitempty"`
+}
diff --git a/adapters/criteo/usersync.go b/adapters/criteo/usersync.go
new file mode 100644
index 00000000..3deba085
--- /dev/null
+++ b/adapters/criteo/usersync.go
@@ -0,0 +1,14 @@
+package criteo
+
+import (
+	"text/template"
+
+	"github.com/prebid/prebid-server/adapters"
+	"github.com/prebid/prebid-server/usersync"
+)
+
+// NewCriteoSyncer user syncer for Criteo
+// Criteo doesn't need user synchronization yet.
+func NewCriteoSyncer(temp *template.Template) usersync.Usersyncer {
+	return adapters.NewSyncer("criteo", temp, adapters.SyncTypeRedirect)
+}
diff --git a/config/config.go b/config/config.go
index bc15a947..00aceba9 100644
--- a/config/config.go
+++ b/config/config.go
@@ -829,12 +829,14 @@ func SetupViper(v *viper.Viper, filename string) {
 	v.SetDefault("adapters.beachfront.extra_info", "{\"video_endpoint\":\"https://reachms.bfmio.com/bid.json?exchange_id\"}")
 	v.SetDefault("adapters.beintoo.endpoint", "https://ib.beintoo.com/um")
 	v.SetDefault("adapters.between.endpoint", "http://{{.Host}}.betweendigital.com/openrtb_bid?sspId={{.PublisherID}}")
+	v.SetDefault("adapters.bidmachine.endpoint", "https://{{.Host}}.bidmachine.io")
 	v.SetDefault("adapters.brightroll.endpoint", "http://east-bid.ybp.yahoo.com/bid/appnexuspbs")
 	v.SetDefault("adapters.colossus.endpoint", "http://colossusssp.com/?c=o&m=rtb")
 	v.SetDefault("adapters.connectad.endpoint", "http://bidder.connectad.io/API?src=pbs")
 	v.SetDefault("adapters.consumable.endpoint", "https://e.serverbid.com/api/v2")
 	v.SetDefault("adapters.conversant.endpoint", "http://api.hb.ad.cpe.dotomi.com/cvx/server/hb/ortb/25")
 	v.SetDefault("adapters.cpmstar.endpoint", "https://server.cpmstar.com/openrtbbidrq.aspx")
+	v.SetDefault("adapters.criteo.endpoint", "https://bidder.criteo.com/cdb?profileId=230")
 	v.SetDefault("adapters.datablocks.endpoint", "http://{{.Host}}/openrtb2?sid={{.SourceId}}")
 	v.SetDefault("adapters.decenterads.endpoint", "http://supply.decenterads.com/?c=o&m=rtb")
 	v.SetDefault("adapters.deepintent.endpoint", "https://prebid.deepintent.com/prebid")
diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go
index 46c96706..0ea28f8a 100755
--- a/exchange/adapter_builders.go
+++ b/exchange/adapter_builders.go
@@ -29,12 +29,14 @@ import (
 	"github.com/prebid/prebid-server/adapters/beachfront"
 	"github.com/prebid/prebid-server/adapters/beintoo"
 	"github.com/prebid/prebid-server/adapters/between"
+	"github.com/prebid/prebid-server/adapters/bidmachine"
 	"github.com/prebid/prebid-server/adapters/brightroll"
 	"github.com/prebid/prebid-server/adapters/colossus"
 	"github.com/prebid/prebid-server/adapters/connectad"
 	"github.com/prebid/prebid-server/adapters/consumable"
 	"github.com/prebid/prebid-server/adapters/conversant"
 	"github.com/prebid/prebid-server/adapters/cpmstar"
+	"github.com/prebid/prebid-server/adapters/criteo"
 	"github.com/prebid/prebid-server/adapters/datablocks"
 	"github.com/prebid/prebid-server/adapters/decenterads"
 	"github.com/prebid/prebid-server/adapters/deepintent"
@@ -137,12 +139,14 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder {
 		openrtb_ext.BidderBeachfront:       beachfront.Builder,
 		openrtb_ext.BidderBeintoo:          beintoo.Builder,
 		openrtb_ext.BidderBetween:          between.Builder,
+		openrtb_ext.BidderBidmachine:       bidmachine.Builder,
 		openrtb_ext.BidderBrightroll:       brightroll.Builder,
 		openrtb_ext.BidderColossus:         colossus.Builder,
 		openrtb_ext.BidderConnectAd:        connectad.Builder,
 		openrtb_ext.BidderConsumable:       consumable.Builder,
 		openrtb_ext.BidderConversant:       conversant.Builder,
 		openrtb_ext.BidderCpmstar:          cpmstar.Builder,
+		openrtb_ext.BidderCriteo:           criteo.Builder,
 		openrtb_ext.BidderDatablocks:       datablocks.Builder,
 		openrtb_ext.BidderDecenterAds:      decenterads.Builder,
 		openrtb_ext.BidderDeepintent:       deepintent.Builder,
diff --git a/exchange/bidder.go b/exchange/bidder.go
index d6b8028c..5b6da499 100644
--- a/exchange/bidder.go
+++ b/exchange/bidder.go
@@ -167,6 +167,10 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, request *openrtb2.B
 		if debugInfo := ctx.Value(DebugContextKey); debugInfo != nil && debugInfo.(bool) {
 			if accountDebugAllowed {
 				if bidder.config.DebugInfo.Allow {
+					// it's safe to mutate the request headers since from this point on the
+					// information is only used for debugging.
+					removeSensitiveHeaders(httpInfo.request.Headers)
+
 					seatBid.httpCalls = append(seatBid.httpCalls, makeExt(httpInfo))
 				} else {
 					debugDisabledWarning := errortypes.Warning{
@@ -325,25 +329,29 @@ func getAssetByID(id int64, assets []nativeRequests.Asset) (nativeRequests.Asset
 	return nativeRequests.Asset{}, fmt.Errorf("Unable to find asset with ID:%d in the request", id)
 }
 
+var authorizationHeader = http.CanonicalHeaderKey("authorization")
+
+// removeSensitiveHeaders mutates the http header object to remove sensitive information.
+func removeSensitiveHeaders(h http.Header) {
+	h.Del(authorizationHeader)
+}
+
 // makeExt transforms information about the HTTP call into the contract class for the PBS response.
 func makeExt(httpInfo *httpCallInfo) *openrtb_ext.ExtHttpCall {
-	if httpInfo.err == nil {
-		return &openrtb_ext.ExtHttpCall{
-			Uri:            httpInfo.request.Uri,
-			RequestBody:    string(httpInfo.request.Body),
-			ResponseBody:   string(httpInfo.response.Body),
-			Status:         httpInfo.response.StatusCode,
-			RequestHeaders: httpInfo.request.Headers,
-		}
-	} else if httpInfo.request == nil {
-		return &openrtb_ext.ExtHttpCall{}
-	} else {
-		return &openrtb_ext.ExtHttpCall{
-			Uri:            httpInfo.request.Uri,
-			RequestBody:    string(httpInfo.request.Body),
-			RequestHeaders: httpInfo.request.Headers,
+	ext := &openrtb_ext.ExtHttpCall{}
+
+	if httpInfo != nil && httpInfo.request != nil {
+		ext.Uri = httpInfo.request.Uri
+		ext.RequestBody = string(httpInfo.request.Body)
+		ext.RequestHeaders = httpInfo.request.Headers
+
+		if httpInfo.err == nil && httpInfo.response != nil {
+			ext.ResponseBody = string(httpInfo.response.Body)
+			ext.Status = httpInfo.response.StatusCode
 		}
 	}
+
+	return ext
 }
 
 // doRequest makes a request, handles the response, and returns the data needed by the
diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go
index e03c234b..4d95babc 100755
--- a/openrtb_ext/bidders.go
+++ b/openrtb_ext/bidders.go
@@ -100,12 +100,14 @@ const (
 	BidderBeachfront       BidderName = "beachfront"
 	BidderBeintoo          BidderName = "beintoo"
 	BidderBetween          BidderName = "between"
+	BidderBidmachine       BidderName = "bidmachine"
 	BidderBrightroll       BidderName = "brightroll"
 	BidderColossus         BidderName = "colossus"
 	BidderConnectAd        BidderName = "connectad"
 	BidderConsumable       BidderName = "consumable"
 	BidderConversant       BidderName = "conversant"
 	BidderCpmstar          BidderName = "cpmstar"
+	BidderCriteo           BidderName = "criteo"
 	BidderDatablocks       BidderName = "datablocks"
 	BidderDmx              BidderName = "dmx"
 	BidderDecenterAds      BidderName = "decenterads"
@@ -208,12 +210,14 @@ func CoreBidderNames() []BidderName {
 		BidderBeachfront,
 		BidderBeintoo,
 		BidderBetween,
+		BidderBidmachine,
 		BidderBrightroll,
 		BidderColossus,
 		BidderConnectAd,
 		BidderConsumable,
 		BidderConversant,
 		BidderCpmstar,
+		BidderCriteo,
 		BidderDatablocks,
 		BidderDecenterAds,
 		BidderDeepintent,
diff --git a/openrtb_ext/imp_bidmachine.go b/openrtb_ext/imp_bidmachine.go
new file mode 100644
index 00000000..20491d25
--- /dev/null
+++ b/openrtb_ext/imp_bidmachine.go
@@ -0,0 +1,7 @@
+package openrtb_ext
+
+type ExtImpBidmachine struct {
+	Host     string `json:"host"`
+	Path     string `json:"path"`
+	SellerID string `json:"seller_id"`
+}
diff --git a/openrtb_ext/imp_criteo.go b/openrtb_ext/imp_criteo.go
new file mode 100644
index 00000000..e200aace
--- /dev/null
+++ b/openrtb_ext/imp_criteo.go
@@ -0,0 +1,7 @@
+package openrtb_ext
+
+// ExtImpCriteo defines the contract for bidrequest.imp[i].ext.criteo
+type ExtImpCriteo struct {
+	ZoneID    int64 `json:"zoneId"`
+	NetworkID int64 `json:"networkId"`
+}
diff --git a/static/bidder-info/bidmachine.yaml b/static/bidder-info/bidmachine.yaml
new file mode 100644
index 00000000..6868125b
--- /dev/null
+++ b/static/bidder-info/bidmachine.yaml
@@ -0,0 +1,8 @@
+maintainer:
+  email: hi@bidmachine.io
+gvlVendorID: 736
+capabilities:
+  app:
+    mediaTypes:
+      - banner
+      - video
\ No newline at end of file
diff --git a/static/bidder-info/criteo.yaml b/static/bidder-info/criteo.yaml
new file mode 100644
index 00000000..b27e1fae
--- /dev/null
+++ b/static/bidder-info/criteo.yaml
@@ -0,0 +1,10 @@
+maintainer:
+  email: "pi-direct@criteo.com"
+gvlVendorID: 91
+capabilities:
+  app:
+    mediaTypes:
+      - banner
+  site:
+    mediaTypes:
+      - banner
\ No newline at end of file
diff --git a/static/bidder-params/bidmachine.json b/static/bidder-params/bidmachine.json
new file mode 100644
index 00000000..f3971c00
--- /dev/null
+++ b/static/bidder-params/bidmachine.json
@@ -0,0 +1,31 @@
+{
+    "$schema": "http://json-schema.org/draft-04/schema#",
+    "title": "Bidmachine Adapter Params",
+    "description": "A schema which validates params accepted by the Kidoz adapter",
+    "type": "object",
+    "properties": {
+        "host": {
+            "$ref": "#/definitions/non-empty-string",
+            "description": "Host"
+        },
+        "path": {
+            "$ref": "#/definitions/non-empty-string",
+            "description": "URL path"
+        },
+        "seller_id": {
+            "$ref": "#/definitions/non-empty-string",
+            "description": "Seller Identifier"
+        }
+    },
+    "definitions": {
+        "non-empty-string": {
+            "type": "string",
+            "minLength": 1
+        }
+    },
+    "required": [
+        "host",
+        "path",
+        "seller_id"
+    ]
+}
\ No newline at end of file
diff --git a/static/bidder-params/criteo.json b/static/bidder-params/criteo.json
new file mode 100644
index 00000000..9d348a7e
--- /dev/null
+++ b/static/bidder-params/criteo.json
@@ -0,0 +1,30 @@
+{
+    "$schema": "http://json-schema.org/draft-04/schema#",
+    "title": "Criteo adapter params",
+    "description": "The schema to validate Criteo specific params accepted by Criteo adapter",
+    "type": "object",
+    "properties": {
+        "zoneid": {
+            "type": "number",
+            "description": "Impression's zone ID.",
+            "minimum": 0
+        },
+        "networkid": {
+            "type": "number",
+            "description": "Impression's network ID.",
+            "minimum": 0
+        }
+    },
+    "anyOf": [
+        {
+            "required": [
+                "zoneid"
+            ]
+        },
+        {
+            "required": [
+                "networkid"
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go
index 7467eb26..f01e4e4b 100644
--- a/usersync/usersyncers/syncer.go
+++ b/usersync/usersyncers/syncer.go
@@ -32,6 +32,7 @@ import (
 	"github.com/prebid/prebid-server/adapters/consumable"
 	"github.com/prebid/prebid-server/adapters/conversant"
 	"github.com/prebid/prebid-server/adapters/cpmstar"
+	"github.com/prebid/prebid-server/adapters/criteo"
 	"github.com/prebid/prebid-server/adapters/datablocks"
 	"github.com/prebid/prebid-server/adapters/deepintent"
 	"github.com/prebid/prebid-server/adapters/dmx"
@@ -122,6 +123,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderConnectAd, connectad.NewConnectAdSyncer)
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderConsumable, consumable.NewConsumableSyncer)
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderConversant, conversant.NewConversantSyncer)
+	insertIntoMap(cfg, syncers, openrtb_ext.BidderCriteo, criteo.NewCriteoSyncer)
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderCpmstar, cpmstar.NewCpmstarSyncer)
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderDatablocks, datablocks.NewDatablocksSyncer)
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderDeepintent, deepintent.NewDeepintentSyncer)


Test patch:
diff --git a/adapters/bidmachine/bidmachine_test.go b/adapters/bidmachine/bidmachine_test.go
new file mode 100644
index 00000000..9dc82150
--- /dev/null
+++ b/adapters/bidmachine/bidmachine_test.go
@@ -0,0 +1,28 @@
+package bidmachine
+
+import (
+	"testing"
+
+	"github.com/prebid/prebid-server/adapters/adapterstest"
+	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/openrtb_ext"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestJsonSamples(t *testing.T) {
+	bidder, buildErr := Builder(openrtb_ext.BidderBidmachine, config.Adapter{
+		Endpoint: "https://{{.Host}}.bidmachine.io"})
+
+	if buildErr != nil {
+		t.Fatalf("Builder returned unexpected error %v", buildErr)
+	}
+
+	adapterstest.RunJSONBidderTest(t, "bidmachinetest", bidder)
+}
+
+func TestEndpointTemplateMalformed(t *testing.T) {
+	_, buildErr := Builder(openrtb_ext.BidderBidmachine, config.Adapter{
+		Endpoint: "{{Malformed}}"})
+
+	assert.Error(t, buildErr)
+}
diff --git a/adapters/bidmachine/bidmachinetest/exemplary/rewarded_interstitial_no_battr.json b/adapters/bidmachine/bidmachinetest/exemplary/rewarded_interstitial_no_battr.json
new file mode 100644
index 00000000..96bf06be
--- /dev/null
+++ b/adapters/bidmachine/bidmachinetest/exemplary/rewarded_interstitial_no_battr.json
@@ -0,0 +1,128 @@
+{
+    "mockBidRequest":
+    {
+        "id": "test-request-id",
+        "imp": [
+        {
+            "id": "test-imp-id",
+            "instl": 1,
+            "banner":
+            {
+                "battr": [1],
+                "mimes": [
+                    "image/jpeg",
+                    "image/jpg",
+                    "image/gif",
+                    "image/png"
+                ],
+                "w": 320,
+                "h": 480
+            },
+            "ext":
+            {
+                "prebid":
+                {
+                    "is_rewarded_inventory": 1
+                },
+                "bidder":
+                {
+                    "host": "api-us",
+                    "path": "auction/rtb/v2",
+                    "seller_id": "1"
+                }
+            }
+        }]
+    },
+    "httpCalls": [
+    {
+        "expectedRequest":
+        {
+            "uri": "https://api-us.bidmachine.io/auction/rtb/v2/1",
+            "headers":
+            {
+                "Content-Type": [
+                    "application/json"
+                ],
+                "Accept": [
+                    "application/json"
+                ],
+                "X-Openrtb-Version": [
+                    "2.5"
+                ]
+            },
+            "body":
+            {
+                "id": "test-request-id",
+                "imp": [
+                {
+                    "id": "test-imp-id",
+                    "instl": 1,
+                    "banner":
+                    {
+                        "battr": [1, 16],
+                        "mimes": [
+                            "image/jpeg",
+                            "image/jpg",
+                            "image/gif",
+                            "image/png"
+                        ],
+                        "w": 320,
+                        "h": 480
+                    },
+                    "ext":
+                    {
+                        "prebid":
+                        {
+                            "is_rewarded_inventory": 1
+                        },
+                        "bidder":
+                        {
+                            "host": "api-us",
+                            "path": "auction/rtb/v2",
+                            "seller_id": "1"
+                        }
+                    }
+                }]
+            }
+        },
+        "mockResponse":
+        {
+            "status": 200,
+            "body":
+            {
+                "id": "test-request-id",
+                "seatbid": [
+                {
+                    "bid": [
+                    {
+                        "id": "test-bid-id",
+                        "impid": "test-imp-id",
+                        "price": 100,
+                        "adm": "test-adm",
+                        "cid": "test-cid",
+                        "crid": "test-crid"
+                    }],
+                    "seat": "bidmachine"
+                }],
+                "cur": "USD"
+            }
+        }
+    }],
+    "expectedBidResponses": [
+    {
+        "currency": "USD",
+        "bids": [
+        {
+            "bid":
+            {
+                "id": "test-bid-id",
+                "impid": "test-imp-id",
+                "price": 100,
+                "adm": "test-adm",
+                "cid": "test-cid",
+                "crid": "test-crid"
+            },
+            "type": "banner"
+        }]
+    }]
+}
\ No newline at end of file
diff --git a/adapters/bidmachine/bidmachinetest/exemplary/rewarded_interstitial_w_battr.json b/adapters/bidmachine/bidmachinetest/exemplary/rewarded_interstitial_w_battr.json
new file mode 100644
index 00000000..08b187e5
--- /dev/null
+++ b/adapters/bidmachine/bidmachinetest/exemplary/rewarded_interstitial_w_battr.json
@@ -0,0 +1,128 @@
+{
+    "mockBidRequest":
+    {
+        "id": "test-request-id",
+        "imp": [
+        {
+            "id": "test-imp-id",
+            "instl": 1,
+            "banner":
+            {
+                "battr": [1, 16],
+                "mimes": [
+                    "image/jpeg",
+                    "image/jpg",
+                    "image/gif",
+                    "image/png"
+                ],
+                "w": 320,
+                "h": 480
+            },
+            "ext":
+            {
+                "prebid":
+                {
+                    "is_rewarded_inventory": 1
+                },
+                "bidder":
+                {
+                    "host": "api-us",
+                    "path": "auction/rtb/v2",
+                    "seller_id": "1"
+                }
+            }
+        }]
+    },
+    "httpCalls": [
+    {
+        "expectedRequest":
+        {
+            "uri": "https://api-us.bidmachine.io/auction/rtb/v2/1",
+            "headers":
+            {
+                "Content-Type": [
+                    "application/json"
+                ],
+                "Accept": [
+                    "application/json"
+                ],
+                "X-Openrtb-Version": [
+                    "2.5"
+                ]
+            },
+            "body":
+            {
+                "id": "test-request-id",
+                "imp": [
+                {
+                    "id": "test-imp-id",
+                    "instl": 1,
+                    "banner":
+                    {
+                        "battr": [1, 16],
+                        "mimes": [
+                            "image/jpeg",
+                            "image/jpg",
+                            "image/gif",
+                            "image/png"
+                        ],
+                        "w": 320,
+                        "h": 480
+                    },
+                    "ext":
+                    {
+                        "prebid":
+                        {
+                            "is_rewarded_inventory": 1
+                        },
+                        "bidder":
+                        {
+                            "host": "api-us",
+                            "path": "auction/rtb/v2",
+                            "seller_id": "1"
+                        }
+                    }
+                }]
+            }
+        },
+        "mockResponse":
+        {
+            "status": 200,
+            "body":
+            {
+                "id": "test-request-id",
+                "seatbid": [
+                {
+                    "bid": [
+                    {
+                        "id": "test-bid-id",
+                        "impid": "test-imp-id",
+                        "price": 100,
+                        "adm": "test-adm",
+                        "cid": "test-cid",
+                        "crid": "test-crid"
+                    }],
+                    "seat": "bidmachine"
+                }],
+                "cur": "USD"
+            }
+        }
+    }],
+    "expectedBidResponses": [
+    {
+        "currency": "USD",
+        "bids": [
+        {
+            "bid":
+            {
+                "id": "test-bid-id",
+                "impid": "test-imp-id",
+                "price": 100,
+                "adm": "test-adm",
+                "cid": "test-cid",
+                "crid": "test-crid"
+            },
+            "type": "banner"
+        }]
+    }]
+}
\ No newline at end of file
diff --git a/adapters/bidmachine/bidmachinetest/exemplary/rewarded_video_no_battr.json b/adapters/bidmachine/bidmachinetest/exemplary/rewarded_video_no_battr.json
new file mode 100644
index 00000000..72bf59d7
--- /dev/null
+++ b/adapters/bidmachine/bidmachinetest/exemplary/rewarded_video_no_battr.json
@@ -0,0 +1,132 @@
+{
+    "mockBidRequest":
+    {
+        "id": "test-request-id",
+        "imp": [
+        {
+            "id": "test-imp-id",
+            "video":
+            {
+                "battr": [1],
+                "mimes": [
+                    "video/mp4",
+                    "video/3gpp"
+                ],
+                "protocols": [
+                    1,
+                    2,
+                    3
+                ],
+                "w": 320,
+                "h": 480
+            },
+            "ext":
+            {
+                "prebid":
+                {
+                    "is_rewarded_inventory": 1
+                },
+                "bidder":
+                {
+                    "host": "api-us",
+                    "path": "auction/rtb/v2",
+                    "seller_id": "1"
+                }
+            }
+        }]
+    },
+    "httpCalls": [
+    {
+        "expectedRequest":
+        {
+            "uri": "https://api-us.bidmachine.io/auction/rtb/v2/1",
+            "headers":
+            {
+                "Content-Type": [
+                    "application/json"
+                ],
+                "Accept": [
+                    "application/json"
+                ],
+                "X-Openrtb-Version": [
+                    "2.5"
+                ]
+            },
+            "body":
+            {
+                "id": "test-request-id",
+                "imp": [
+                {
+                    "id": "test-imp-id",
+                    "video":
+                    {
+                        "battr": [1, 16],
+                        "mimes": [
+                            "video/mp4",
+                            "video/3gpp"
+                        ],
+                        "protocols": [
+                            1,
+                            2,
+                            3
+                        ],
+                        "w": 320,
+                        "h": 480
+                    },
+                    "ext":
+                    {
+                        "prebid":
+                        {
+                            "is_rewarded_inventory": 1
+                        },
+                        "bidder":
+                        {
+                            "host": "api-us",
+                            "path": "auction/rtb/v2",
+                            "seller_id": "1"
+                        }
+                    }
+                }]
+            }
+        },
+        "mockResponse":
+        {
+            "status": 200,
+            "body":
+            {
+                "id": "test-request-id",
+                "seatbid": [
+                {
+                    "bid": [
+                    {
+                        "id": "test-bid-id",
+                        "impid": "test-imp-id",
+                        "price": 100,
+                        "adm": "test-adm",
+                        "cid": "test-cid",
+                        "crid": "test-crid"
+                    }],
+                    "seat": "bidmachine"
+                }],
+                "cur": "USD"
+            }
+        }
+    }],
+    "expectedBidResponses": [
+    {
+        "currency": "USD",
+        "bids": [
+        {
+            "bid":
+            {
+                "id": "test-bid-id",
+                "impid": "test-imp-id",
+                "price": 100,
+                "adm": "test-adm",
+                "cid": "test-cid",
+                "crid": "test-crid"
+            },
+            "type": "video"
+        }]
+    }]
+}
\ No newline at end of file
diff --git a/adapters/bidmachine/bidmachinetest/exemplary/rewarded_video_w_battr.json b/adapters/bidmachine/bidmachinetest/exemplary/rewarded_video_w_battr.json
new file mode 100644
index 00000000..0d681e16
--- /dev/null
+++ b/adapters/bidmachine/bidmachinetest/exemplary/rewarded_video_w_battr.json
@@ -0,0 +1,132 @@
+{
+    "mockBidRequest":
+    {
+        "id": "test-request-id",
+        "imp": [
+        {
+            "id": "test-imp-id",
+            "video":
+            {
+                "battr": [1, 16],
+                "mimes": [
+                    "video/mp4",
+                    "video/3gpp"
+                ],
+                "protocols": [
+                    1,
+                    2,
+                    3
+                ],
+                "w": 320,
+                "h": 480
+            },
+            "ext":
+            {
+                "prebid":
+                {
+                    "is_rewarded_inventory": 1
+                },
+                "bidder":
+                {
+                    "host": "api-us",
+                    "path": "auction/rtb/v2",
+                    "seller_id": "1"
+                }
+            }
+        }]
+    },
+    "httpCalls": [
+    {
+        "expectedRequest":
+        {
+            "uri": "https://api-us.bidmachine.io/auction/rtb/v2/1",
+            "headers":
+            {
+                "Content-Type": [
+                    "application/json"
+                ],
+                "Accept": [
+                    "application/json"
+                ],
+                "X-Openrtb-Version": [
+                    "2.5"
+                ]
+            },
+            "body":
+            {
+                "id": "test-request-id",
+                "imp": [
+                {
+                    "id": "test-imp-id",
+                    "video":
+                    {
+                        "battr": [1, 16],
+                        "mimes": [
+                            "video/mp4",
+                            "video/3gpp"
+                        ],
+                        "protocols": [
+                            1,
+                            2,
+                            3
+                        ],
+                        "w": 320,
+                        "h": 480
+                    },
+                    "ext":
+                    {
+                        "prebid":
+                        {
+                            "is_rewarded_inventory": 1
+                        },
+                        "bidder":
+                        {
+                            "host": "api-us",
+                            "path": "auction/rtb/v2",
+                            "seller_id": "1"
+                        }
+                    }
+                }]
+            }
+        },
+        "mockResponse":
+        {
+            "status": 200,
+            "body":
+            {
+                "id": "test-request-id",
+                "seatbid": [
+                {
+                    "bid": [
+                    {
+                        "id": "test-bid-id",
+                        "impid": "test-imp-id",
+                        "price": 100,
+                        "adm": "test-adm",
+                        "cid": "test-cid",
+                        "crid": "test-crid"
+                    }],
+                    "seat": "bidmachine"
+                }],
+                "cur": "USD"
+            }
+        }
+    }],
+    "expectedBidResponses": [
+    {
+        "currency": "USD",
+        "bids": [
+        {
+            "bid":
+            {
+                "id": "test-bid-id",
+                "impid": "test-imp-id",
+                "price": 100,
+                "adm": "test-adm",
+                "cid": "test-cid",
+                "crid": "test-crid"
+            },
+            "type": "video"
+        }]
+    }]
+}
\ No newline at end of file
diff --git a/adapters/bidmachine/bidmachinetest/exemplary/simple_banner.json b/adapters/bidmachine/bidmachinetest/exemplary/simple_banner.json
new file mode 100644
index 00000000..bde25edc
--- /dev/null
+++ b/adapters/bidmachine/bidmachinetest/exemplary/simple_banner.json
@@ -0,0 +1,118 @@
+{
+    "mockBidRequest":
+    {
+        "id": "test-request-id",
+        "imp": [
+        {
+            "id": "test-imp-id",
+            "banner":
+            {
+                "battr": [1],
+                "mimes": [
+                    "image/jpeg",
+                    "image/jpg",
+                    "image/gif",
+                    "image/png"
+                ],
+                "w": 320,
+                "h": 50
+            },
+            "ext":
+            {
+                "bidder":
+                {
+                    "host": "api-us",
+                    "path": "auction/rtb/v2",
+                    "seller_id": "1"
+                }
+            }
+        }]
+    },
+    "httpCalls": [
+    {
+        "expectedRequest":
+        {
+            "uri": "https://api-us.bidmachine.io/auction/rtb/v2/1",
+            "headers":
+            {
+                "Content-Type": [
+                    "application/json"
+                ],
+                "Accept": [
+                    "application/json"
+                ],
+                "X-Openrtb-Version": [
+                    "2.5"
+                ]
+            },
+            "body":
+            {
+                "id": "test-request-id",
+                "imp": [
+                {
+                    "id": "test-imp-id",
+                    "banner":
+                    {
+                        "battr": [1],
+                        "mimes": [
+                            "image/jpeg",
+                            "image/jpg",
+                            "image/gif",
+                            "image/png"
+                        ],
+                        "w": 320,
+                        "h": 50
+                    },
+                    "ext":
+                    {
+                        "bidder":
+                        {
+                            "host": "api-us",
+                            "path": "auction/rtb/v2",
+                            "seller_id": "1"
+                        }
+                    }
+                }]
+            }
+        },
+        "mockResponse":
+        {
+            "status": 200,
+            "body":
+            {
+                "id": "test-request-id",
+                "seatbid": [
+                {
+                    "bid": [
+                    {
+                        "id": "test-bid-id",
+                        "impid": "test-imp-id",
+                        "price": 100,
+                        "adm": "test-adm",
+                        "cid": "test-cid",
+                        "crid": "test-crid"
+                    }],
+                    "seat": "bidmachine"
+                }],
+                "cur": "USD"
+            }
+        }
+    }],
+    "expectedBidResponses": [
+    {
+        "currency": "USD",
+        "bids": [
+        {
+            "bid":
+            {
+                "id": "test-bid-id",
+                "impid": "test-imp-id",
+                "price": 100,
+                "adm": "test-adm",
+                "cid": "test-cid",
+                "crid": "test-crid"
+            },
+            "type": "banner"
+        }]
+    }]
+}
\ No newline at end of file
diff --git a/adapters/bidmachine/bidmachinetest/exemplary/simple_interstitial.json b/adapters/bidmachine/bidmachinetest/exemplary/simple_interstitial.json
new file mode 100644
index 00000000..b49c85e5
--- /dev/null
+++ b/adapters/bidmachine/bidmachinetest/exemplary/simple_interstitial.json
@@ -0,0 +1,120 @@
+{
+    "mockBidRequest":
+    {
+        "id": "test-request-id",
+        "imp": [
+        {
+            "id": "test-imp-id",
+            "instl": 1,
+            "banner":
+            {
+                "battr": [1],
+                "mimes": [
+                    "image/jpeg",
+                    "image/jpg",
+                    "image/gif",
+                    "image/png"
+                ],
+                "w": 320,
+                "h": 480
+            },
+            "ext":
+            {
+                "bidder":
+                {
+                    "host": "api-us",
+                    "path": "auction/rtb/v2",
+                    "seller_id": "1"
+                }
+            }
+        }]
+    },
+    "httpCalls": [
+    {
+        "expectedRequest":
+        {
+            "uri": "https://api-us.bidmachine.io/auction/rtb/v2/1",
+            "headers":
+            {
+                "Content-Type": [
+                    "application/json"
+                ],
+                "Accept": [
+                    "application/json"
+                ],
+                "X-Openrtb-Version": [
+                    "2.5"
+                ]
+            },
+            "body":
+            {
+                "id": "test-request-id",
+                "imp": [
+                {
+                    "id": "test-imp-id",
+                    "instl": 1,
+                    "banner":
+                    {
+                        "battr": [1],
+                        "mimes": [
+                            "image/jpeg",
+                            "image/jpg",
+                            "image/gif",
+                            "image/png"
+                        ],
+                        "w": 320,
+                        "h": 480
+                    },
+                    "ext":
+                    {
+                        "bidder":
+                        {
+                            "host": "api-us",
+                            "path": "auction/rtb/v2",
+                            "seller_id": "1"
+                        }
+                    }
+                }]
+            }
+        },
+        "mockResponse":
+        {
+            "status": 200,
+            "body":
+            {
+                "id": "test-request-id",
+                "seatbid": [
+                {
+                    "bid": [
+                    {
+                        "id": "test-bid-id",
+                        "impid": "test-imp-id",
+                        "price": 100,
+                        "adm": "test-adm",
+                        "cid": "test-cid",
+                        "crid": "test-crid"
+                    }],
+                    "seat": "bidmachine"
+                }],
+                "cur": "USD"
+            }
+        }
+    }],
+    "expectedBidResponses": [
+    {
+        "currency": "USD",
+        "bids": [
+        {
+            "bid":
+            {
+                "id": "test-bid-id",
+                "impid": "test-imp-id",
+                "price": 100,
+                "adm": "test-adm",
+                "cid": "test-cid",
+                "crid": "test-crid"
+            },
+            "type": "banner"
+        }]
+    }]
+}
\ No newline at end of file
diff --git a/adapters/bidmachine/bidmachinetest/exemplary/simple_video.json b/adapters/bidmachine/bidmachinetest/exemplary/simple_video.json
new file mode 100644
index 00000000..e6e1d2dc
--- /dev/null
+++ b/adapters/bidmachine/bidmachinetest/exemplary/simple_video.json
@@ -0,0 +1,124 @@
+{
+    "mockBidRequest":
+    {
+        "id": "test-request-id",
+        "imp": [
+        {
+            "id": "test-imp-id",
+            "video":
+            {
+                "battr": [1],
+                "mimes": [
+                    "video/mp4",
+                    "video/3gpp"
+                ],
+                "protocols": [
+                    1,
+                    2,
+                    3
+                ],
+                "w": 320,
+                "h": 480
+            },
+            "ext":
+            {
+                "bidder":
+                {
+                    "host": "api-us",
+                    "path": "auction/rtb/v2",
+                    "seller_id": "1"
+                }
+            }
+        }]
+    },
+    "httpCalls": [
+    {
+        "expectedRequest":
+        {
+            "uri": "https://api-us.bidmachine.io/auction/rtb/v2/1",
+            "headers":
+            {
+                "Content-Type": [
+                    "application/json"
+                ],
+                "Accept": [
+                    "application/json"
+                ],
+                "X-Openrtb-Version": [
+                    "2.5"
+                ]
+            },
+            "body":
+            {
+                "id": "test-request-id",
+                "imp": [
+                {
+                    "id": "test-imp-id",
+                    "video":
+                    {
+                        "battr": [1],
+                        "mimes": [
+                            "video/mp4",
+                            "video/3gpp"
+                        ],
+                        "protocols": [
+                            1,
+                            2,
+                            3
+                        ],
+                        "w": 320,
+                        "h": 480
+                    },
+                    "ext":
+                    {
+                        "bidder":
+                        {
+                            "host": "api-us",
+                            "path": "auction/rtb/v2",
+                            "seller_id": "1"
+                        }
+                    }
+                }]
+            }
+        },
+        "mockResponse":
+        {
+            "status": 200,
+            "body":
+            {
+                "id": "test-request-id",
+                "seatbid": [
+                {
+                    "bid": [
+                    {
+                        "id": "test-bid-id",
+                        "impid": "test-imp-id",
+                        "price": 100,
+                        "adm": "test-adm",
+                        "cid": "test-cid",
+                        "crid": "test-crid"
+                    }],
+                    "seat": "bidmachine"
+                }],
+                "cur": "USD"
+            }
+        }
+    }],
+    "expectedBidResponses": [
+    {
+        "currency": "USD",
+        "bids": [
+        {
+            "bid":
+            {
+                "id": "test-bid-id",
+                "impid": "test-imp-id",
+                "price": 100,
+                "adm": "test-adm",
+                "cid": "test-cid",
+                "crid": "test-crid"
+            },
+            "type": "video"
+        }]
+    }]
+}
\ No newline at end of file
diff --git a/adapters/bidmachine/bidmachinetest/params/race/banner.json b/adapters/bidmachine/bidmachinetest/params/race/banner.json
new file mode 100644
index 00000000..0402e4a1
--- /dev/null
+++ b/adapters/bidmachine/bidmachinetest/params/race/banner.json
@@ -0,0 +1,5 @@
+{
+  "seller_id": "1",
+  "path": "auction/rtb/v2",
+  "host": "api-eu"
+}
\ No newline at end of file
diff --git a/adapters/bidmachine/bidmachinetest/params/race/video.json b/adapters/bidmachine/bidmachinetest/params/race/video.json
new file mode 100644
index 00000000..0402e4a1
--- /dev/null
+++ b/adapters/bidmachine/bidmachinetest/params/race/video.json
@@ -0,0 +1,5 @@
+{
+  "seller_id": "1",
+  "path": "auction/rtb/v2",
+  "host": "api-eu"
+}
\ No newline at end of file
diff --git a/adapters/bidmachine/bidmachinetest/supplemental/empty-banner-format .json b/adapters/bidmachine/bidmachinetest/supplemental/empty-banner-format .json
new file mode 100644
index 00000000..b454ce3d
--- /dev/null
+++ b/adapters/bidmachine/bidmachinetest/supplemental/empty-banner-format .json
@@ -0,0 +1,19 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-impression-id-1",
+        "banner": {
+          "format": []
+        }
+      }
+    ]
+  },
+  "expectedMakeRequestsErrors": [
+    {
+      "value": "Impression with id: test-impression-id-1 has following error: Banner width and height is not provided and banner format array is empty. At least one is required",
+      "comparison": "literal"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/bidmachine/bidmachinetest/supplemental/missing-banner-format.json b/adapters/bidmachine/bidmachinetest/supplemental/missing-banner-format.json
new file mode 100644
index 00000000..563737e1
--- /dev/null
+++ b/adapters/bidmachine/bidmachinetest/supplemental/missing-banner-format.json
@@ -0,0 +1,26 @@
+{
+  "mockBidRequest":
+  {
+    "id": "test-request-id",
+    "imp": [
+    {
+      "id": "test-impression-id-1",
+      "banner":
+      {},
+      "ext":
+      {
+        "bidder":
+        {
+          "seller_id": "1",
+          "host": "api-eu",
+          "path": "auction/rtb/v2"
+        }
+      }
+    }]
+  },
+  "expectedMakeRequestsErrors": [
+  {
+    "value": "Impression with id: test-impression-id-1 has following error: Banner width and height is not provided and banner format is missing. At least one is required",
+    "comparison": "literal"
+  }]
+}
\ No newline at end of file
diff --git a/adapters/bidmachine/bidmachinetest/supplemental/status-204.json b/adapters/bidmachine/bidmachinetest/supplemental/status-204.json
new file mode 100644
index 00000000..daa7f00a
--- /dev/null
+++ b/adapters/bidmachine/bidmachinetest/supplemental/status-204.json
@@ -0,0 +1,66 @@
+{
+  "mockBidRequest":
+  {
+    "id": "test-request-id",
+    "imp": [
+    {
+      "id": "test-impression-id-1",
+      "banner":
+      {
+        "format": [
+        {
+          "h": 250,
+          "w": 300
+        }]
+      },
+      "ext":
+      {
+        "bidder":
+        {
+          "seller_id": "1",
+          "host": "api-eu",
+          "path": "auction/rtb/v2"
+        }
+      }
+    }]
+  },
+  "httpCalls": [
+  {
+    "expectedRequest":
+    {
+      "uri": "https://api-eu.bidmachine.io/auction/rtb/v2/1",
+      "body":
+      {
+        "id": "test-request-id",
+        "imp": [
+        {
+          "id": "test-impression-id-1",
+          "banner":
+          {
+            "format": [
+            {
+              "h": 250,
+              "w": 300
+            }]
+          },
+          "ext":
+          {
+            "bidder":
+            {
+              "seller_id": "1",
+              "host": "api-eu",
+              "path": "auction/rtb/v2"
+            }
+          }
+        }]
+      }
+    },
+    "mockResponse":
+    {
+      "status": 204,
+      "body":
+      {}
+    }
+  }],
+  "expectedBidResponses": []
+}
\ No newline at end of file
diff --git a/adapters/bidmachine/bidmachinetest/supplemental/status-400.json b/adapters/bidmachine/bidmachinetest/supplemental/status-400.json
new file mode 100644
index 00000000..5eb4ccfc
--- /dev/null
+++ b/adapters/bidmachine/bidmachinetest/supplemental/status-400.json
@@ -0,0 +1,69 @@
+{
+  "mockBidRequest":
+  {
+    "id": "test-request-id",
+    "imp": [
+    {
+      "id": "test-impression-id-1",
+      "banner":
+      {
+        "format": [
+        {
+          "h": 250,
+          "w": 300
+        }]
+      },
+      "ext":
+      {
+        "bidder":
+        {
+          "seller_id": "1",
+          "host": "api-eu",
+          "path": "auction/rtb/v2"
+        }
+      }
+    }]
+  },
+  "httpCalls": [
+  {
+    "expectedRequest":
+    {
+      "uri": "https://api-eu.bidmachine.io/auction/rtb/v2/1",
+      "body":
+      {
+        "id": "test-request-id",
+        "imp": [
+        {
+          "id": "test-impression-id-1",
+          "banner":
+          {
+            "format": [
+            {
+              "h": 250,
+              "w": 300
+            }]
+          },
+          "ext":
+          {
+            "bidder":
+            {
+              "seller_id": "1",
+              "host": "api-eu",
+              "path": "auction/rtb/v2"
+            }
+          }
+        }]
+      }
+    },
+    "mockResponse":
+    {
+      "status": 400,
+      "body": "server text here"
+    }
+  }],
+  "expectedMakeBidsErrors": [
+  {
+    "value": "unexpected status code: 400 \"server text here\"",
+    "comparison": "literal"
+  }]
+}
\ No newline at end of file
diff --git a/adapters/bidmachine/bidmachinetest/supplemental/status-403.json b/adapters/bidmachine/bidmachinetest/supplemental/status-403.json
new file mode 100644
index 00000000..3434275b
--- /dev/null
+++ b/adapters/bidmachine/bidmachinetest/supplemental/status-403.json
@@ -0,0 +1,69 @@
+{
+  "mockBidRequest":
+  {
+    "id": "test-request-id",
+    "imp": [
+    {
+      "id": "test-impression-id-1",
+      "banner":
+      {
+        "format": [
+        {
+          "h": 250,
+          "w": 300
+        }]
+      },
+      "ext":
+      {
+        "bidder":
+        {
+          "seller_id": "1",
+          "host": "api-eu",
+          "path": "auction/rtb/v2"
+        }
+      }
+    }]
+  },
+  "httpCalls": [
+  {
+    "expectedRequest":
+    {
+      "uri": "https://api-eu.bidmachine.io/auction/rtb/v2/1",
+      "body":
+      {
+        "id": "test-request-id",
+        "imp": [
+        {
+          "id": "test-impression-id-1",
+          "banner":
+          {
+            "format": [
+            {
+              "h": 250,
+              "w": 300
+            }]
+          },
+          "ext":
+          {
+            "bidder":
+            {
+              "seller_id": "1",
+              "host": "api-eu",
+              "path": "auction/rtb/v2"
+            }
+          }
+        }]
+      }
+    },
+    "mockResponse":
+    {
+      "status": 403,
+      "body": "server text here"
+    }
+  }],
+  "expectedMakeBidsErrors": [
+  {
+    "value": "unexpected status code: 403 \"server text here\"",
+    "comparison": "literal"
+  }]
+}
\ No newline at end of file
diff --git a/adapters/bidmachine/bidmachinetest/supplemental/status-408.json b/adapters/bidmachine/bidmachinetest/supplemental/status-408.json
new file mode 100644
index 00000000..51965461
--- /dev/null
+++ b/adapters/bidmachine/bidmachinetest/supplemental/status-408.json
@@ -0,0 +1,69 @@
+{
+  "mockBidRequest":
+  {
+    "id": "test-request-id",
+    "imp": [
+    {
+      "id": "test-impression-id-1",
+      "banner":
+      {
+        "format": [
+        {
+          "h": 250,
+          "w": 300
+        }]
+      },
+      "ext":
+      {
+        "bidder":
+        {
+          "seller_id": "1",
+          "host": "api-eu",
+          "path": "auction/rtb/v2"
+        }
+      }
+    }]
+  },
+  "httpCalls": [
+  {
+    "expectedRequest":
+    {
+      "uri": "https://api-eu.bidmachine.io/auction/rtb/v2/1",
+      "body":
+      {
+        "id": "test-request-id",
+        "imp": [
+        {
+          "id": "test-impression-id-1",
+          "banner":
+          {
+            "format": [
+            {
+              "h": 250,
+              "w": 300
+            }]
+          },
+          "ext":
+          {
+            "bidder":
+            {
+              "seller_id": "1",
+              "host": "api-eu",
+              "path": "auction/rtb/v2"
+            }
+          }
+        }]
+      }
+    },
+    "mockResponse":
+    {
+      "status": 408,
+      "body": "server text here"
+    }
+  }],
+  "expectedMakeBidsErrors": [
+  {
+    "value": "unexpected status code: 408 \"server text here\"",
+    "comparison": "literal"
+  }]
+}
\ No newline at end of file
diff --git a/adapters/bidmachine/bidmachinetest/supplemental/status-500.json b/adapters/bidmachine/bidmachinetest/supplemental/status-500.json
new file mode 100644
index 00000000..f7abfb0f
--- /dev/null
+++ b/adapters/bidmachine/bidmachinetest/supplemental/status-500.json
@@ -0,0 +1,69 @@
+{
+  "mockBidRequest":
+  {
+    "id": "test-request-id",
+    "imp": [
+    {
+      "id": "test-impression-id-1",
+      "banner":
+      {
+        "format": [
+        {
+          "h": 250,
+          "w": 300
+        }]
+      },
+      "ext":
+      {
+        "bidder":
+        {
+          "seller_id": "1",
+          "host": "api-eu",
+          "path": "auction/rtb/v2"
+        }
+      }
+    }]
+  },
+  "httpCalls": [
+  {
+    "expectedRequest":
+    {
+      "uri": "https://api-eu.bidmachine.io/auction/rtb/v2/1",
+      "body":
+      {
+        "id": "test-request-id",
+        "imp": [
+        {
+          "id": "test-impression-id-1",
+          "banner":
+          {
+            "format": [
+            {
+              "h": 250,
+              "w": 300
+            }]
+          },
+          "ext":
+          {
+            "bidder":
+            {
+              "seller_id": "1",
+              "host": "api-eu",
+              "path": "auction/rtb/v2"
+            }
+          }
+        }]
+      }
+    },
+    "mockResponse":
+    {
+      "status": 500,
+      "body": "server text here"
+    }
+  }],
+  "expectedMakeBidsErrors": [
+  {
+    "value": "unexpected status code: 500 \"server text here\"",
+    "comparison": "literal"
+  }]
+}
\ No newline at end of file
diff --git a/adapters/bidmachine/bidmachinetest/supplemental/status-502.json b/adapters/bidmachine/bidmachinetest/supplemental/status-502.json
new file mode 100644
index 00000000..44dabb05
--- /dev/null
+++ b/adapters/bidmachine/bidmachinetest/supplemental/status-502.json
@@ -0,0 +1,69 @@
+{
+  "mockBidRequest":
+  {
+    "id": "test-request-id",
+    "imp": [
+    {
+      "id": "test-impression-id-1",
+      "banner":
+      {
+        "format": [
+        {
+          "h": 250,
+          "w": 300
+        }]
+      },
+      "ext":
+      {
+        "bidder":
+        {
+          "seller_id": "1",
+          "host": "api-eu",
+          "path": "auction/rtb/v2"
+        }
+      }
+    }]
+  },
+  "httpCalls": [
+  {
+    "expectedRequest":
+    {
+      "uri": "https://api-eu.bidmachine.io/auction/rtb/v2/1",
+      "body":
+      {
+        "id": "test-request-id",
+        "imp": [
+        {
+          "id": "test-impression-id-1",
+          "banner":
+          {
+            "format": [
+            {
+              "h": 250,
+              "w": 300
+            }]
+          },
+          "ext":
+          {
+            "bidder":
+            {
+              "seller_id": "1",
+              "host": "api-eu",
+              "path": "auction/rtb/v2"
+            }
+          }
+        }]
+      }
+    },
+    "mockResponse":
+    {
+      "status": 502,
+      "body": "server text here"
+    }
+  }],
+  "expectedMakeBidsErrors": [
+  {
+    "value": "unexpected status code: 502 \"server text here\"",
+    "comparison": "literal"
+  }]
+}
\ No newline at end of file
diff --git a/adapters/bidmachine/bidmachinetest/supplemental/status-503.json b/adapters/bidmachine/bidmachinetest/supplemental/status-503.json
new file mode 100644
index 00000000..979ac4f6
--- /dev/null
+++ b/adapters/bidmachine/bidmachinetest/supplemental/status-503.json
@@ -0,0 +1,69 @@
+{
+  "mockBidRequest":
+  {
+    "id": "test-request-id",
+    "imp": [
+    {
+      "id": "test-impression-id-1",
+      "banner":
+      {
+        "format": [
+        {
+          "h": 250,
+          "w": 300
+        }]
+      },
+      "ext":
+      {
+        "bidder":
+        {
+          "seller_id": "1",
+          "host": "api-eu",
+          "path": "auction/rtb/v2"
+        }
+      }
+    }]
+  },
+  "httpCalls": [
+  {
+    "expectedRequest":
+    {
+      "uri": "https://api-eu.bidmachine.io/auction/rtb/v2/1",
+      "body":
+      {
+        "id": "test-request-id",
+        "imp": [
+        {
+          "id": "test-impression-id-1",
+          "banner":
+          {
+            "format": [
+            {
+              "h": 250,
+              "w": 300
+            }]
+          },
+          "ext":
+          {
+            "bidder":
+            {
+              "seller_id": "1",
+              "host": "api-eu",
+              "path": "auction/rtb/v2"
+            }
+          }
+        }]
+      }
+    },
+    "mockResponse":
+    {
+      "status": 503,
+      "body": "server text here"
+    }
+  }],
+  "expectedMakeBidsErrors": [
+  {
+    "value": "unexpected status code: 503 \"server text here\"",
+    "comparison": "literal"
+  }]
+}
\ No newline at end of file
diff --git a/adapters/bidmachine/bidmachinetest/supplemental/status-504.json b/adapters/bidmachine/bidmachinetest/supplemental/status-504.json
new file mode 100644
index 00000000..918ada2c
--- /dev/null
+++ b/adapters/bidmachine/bidmachinetest/supplemental/status-504.json
@@ -0,0 +1,69 @@
+{
+  "mockBidRequest":
+  {
+    "id": "test-request-id",
+    "imp": [
+    {
+      "id": "test-impression-id-1",
+      "banner":
+      {
+        "format": [
+        {
+          "h": 250,
+          "w": 300
+        }]
+      },
+      "ext":
+      {
+        "bidder":
+        {
+          "seller_id": "1",
+          "host": "api-eu",
+          "path": "auction/rtb/v2"
+        }
+      }
+    }]
+  },
+  "httpCalls": [
+  {
+    "expectedRequest":
+    {
+      "uri": "https://api-eu.bidmachine.io/auction/rtb/v2/1",
+      "body":
+      {
+        "id": "test-request-id",
+        "imp": [
+        {
+          "id": "test-impression-id-1",
+          "banner":
+          {
+            "format": [
+            {
+              "h": 250,
+              "w": 300
+            }]
+          },
+          "ext":
+          {
+            "bidder":
+            {
+              "seller_id": "1",
+              "host": "api-eu",
+              "path": "auction/rtb/v2"
+            }
+          }
+        }]
+      }
+    },
+    "mockResponse":
+    {
+      "status": 504,
+      "body": "server text here"
+    }
+  }],
+  "expectedMakeBidsErrors": [
+  {
+    "value": "unexpected status code: 504 \"server text here\"",
+    "comparison": "literal"
+  }]
+}
\ No newline at end of file
diff --git a/adapters/bidmachine/bidmachinetest/supplemental/wrong-host.json b/adapters/bidmachine/bidmachinetest/supplemental/wrong-host.json
new file mode 100644
index 00000000..82ee3269
--- /dev/null
+++ b/adapters/bidmachine/bidmachinetest/supplemental/wrong-host.json
@@ -0,0 +1,35 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-impression-id-1",
+        "banner": {
+          "format": [
+            {
+              "w": 320,
+              "h": 50
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "host": "`",
+            "path": "auction/rtb/v2",
+            "seller_id": "1"
+          }
+        }
+      }
+    ]
+  },
+  "httpCalls": [],
+  "expectedBidResponses": [],
+  "expectedMakeRequestsErrors": [
+    {
+      "value": "Failed to create final URL with provided host",
+      "comparison": "literal"
+    }
+  ],
+  "expectedMakeBidsErrors": []
+}
+
diff --git a/adapters/bidmachine/params_test.go b/adapters/bidmachine/params_test.go
new file mode 100644
index 00000000..15ebbcbf
--- /dev/null
+++ b/adapters/bidmachine/params_test.go
@@ -0,0 +1,78 @@
+package bidmachine
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+func TestValidParams(t *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+	if err != nil {
+		t.Fatalf("Failed to fetch the json-schemas. %v", err)
+	}
+
+	for _, validParam := range validParams {
+		if err := validator.Validate(openrtb_ext.BidderBidmachine, json.RawMessage(validParam)); err != nil {
+			t.Errorf("Schema rejected Bidmachine params: %s \n Error: %s", validParam, err)
+		}
+	}
+}
+
+func TestInvalidParams(t *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+	if err != nil {
+		t.Fatalf("Failed to fetch the json-schemas. %v", err)
+	}
+
+	for _, invalidParam := range invalidParams {
+		if err := validator.Validate(openrtb_ext.BidderBidmachine, json.RawMessage(invalidParam)); err == nil {
+			t.Errorf("Schema allowed unexpected params: %s", invalidParam)
+		}
+	}
+}
+
+var validParams = []string{
+	`{"seller_id":"1", "host":"api-eu", "path":"auction/rtb/v2"}`,
+	`{"seller_id":"1", "host":"api-us", "path":"auction/rtb/v2"}`,
+}
+
+var invalidParams = []string{
+	``,
+	`null`,
+	`true`,
+	`5`,
+	`4.2`,
+	`[]`,
+	`{}`,
+	`{"some_random_field":""}`,
+	`{"seller_id":""}`,
+	`{"seller_id": 1}`,
+	`{"seller_id": 1.2}`,
+	`{"seller_id": null}`,
+	`{"seller_id": true}`,
+	`{"seller_id": []}`,
+	`{"seller_id": {}}`,
+	`{"host":""}`,
+	`{"host": 1}`,
+	`{"host": 1.2}`,
+	`{"host": null}`,
+	`{"host": true}`,
+	`{"host": []}`,
+	`{"host": {}}`,
+	`{"path":""}`,
+	`{"path": 1}`,
+	`{"path": 1.2}`,
+	`{"path": null}`,
+	`{"path": true}`,
+	`{"path": []}`,
+	`{"path": {}}`,
+	`{"seller_id":"", "path": "", host: ""}`,
+	`{"seller_id": 1, "path": 2, host: 3}`,
+	`{"seller_id": 1.2}, "path": 5.5, host: 3.3`,
+	`{"seller_id": null, "path": null, host: null}`,
+	`{"seller_id": true, "path": false, host: true}`,
+	`{"seller_id": [], "path": [], host: []}`,
+	`{"seller_id": {}, "path": {}, host: {}}`,
+}
diff --git a/adapters/criteo/criteo_test.go b/adapters/criteo/criteo_test.go
new file mode 100644
index 00000000..0a700734
--- /dev/null
+++ b/adapters/criteo/criteo_test.go
@@ -0,0 +1,28 @@
+package criteo
+
+import (
+	"testing"
+
+	"github.com/prebid/prebid-server/adapters/adapterstest"
+	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+func TestJsonSamples(t *testing.T) {
+
+	// Setup:
+	bidder, buildErr := builderWithGuidGenerator(
+		openrtb_ext.BidderCriteo,
+		config.Adapter{
+			Endpoint: "https://bidder.criteo.com/cdb?profileId=230",
+		},
+		newFakeGuidGenerator("00000000-0000-0000-00000000"),
+	)
+
+	if buildErr != nil {
+		t.Fatalf("Builder returned unexpected error %v", buildErr)
+	}
+
+	// Execute & Verify:
+	adapterstest.RunJSONBidderTest(t, "criteotest", bidder)
+}
diff --git a/adapters/criteo/criteotest/exemplary/simple-banner-cookie-uid.json b/adapters/criteo/criteotest/exemplary/simple-banner-cookie-uid.json
new file mode 100755
index 00000000..cb152f47
--- /dev/null
+++ b/adapters/criteo/criteotest/exemplary/simple-banner-cookie-uid.json
@@ -0,0 +1,115 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "site": {
+      "id": "site-id",
+      "page": "criteo.com"
+    },
+    "device": {
+      "os": "android",
+      "ip": "91.199.242.236",
+      "ua": "random user agent"
+    },
+    "user": {
+      "buyeruid": "criteo-user-id"
+    },
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "zoneid": 123456,
+            "networkid": 78910
+          }
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://bidder.criteo.com/cdb?profileId=230",
+        "headers": {
+          "X-Forwarded-For": ["91.199.242.236"],
+          "User-Agent": ["random user agent"],
+          "Cookie": ["uid=criteo-user-id"]
+        },
+        "body": {
+          "id": "test-request-id",
+          "publisher": {
+            "siteid": "site-id",
+            "url": "criteo.com",
+            "networkid": 78910
+          },
+          "user": {
+            "cookieuid": "criteo-user-id",
+            "ip": "91.199.242.236",
+            "ua": "random user agent",
+            "deviceos": "android",
+            "deviceidtype": "gaid"
+          },
+          "gdprconsent": {},
+          "slots": [
+            {
+              "slotid": "00000000-0000-0000-00000000",
+              "impid": "test-imp-id",
+              "zoneid": 123456,
+              "networkid": 78910,
+              "sizes": [
+                "300x250"
+              ]
+            }
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "slots": [
+            {
+              "id": "test-slot-id",
+              "impid": "test-imp-id",
+              "zoneid": 123456,
+              "networkid": 78910,
+              "cpm": 0.1,
+              "currency": "USD",
+              "width": 300,
+              "height": 250,
+              "creativeid": "creative-123",
+              "creative": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>"
+            }
+          ]
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "test-slot-id",
+            "impid": "test-imp-id",
+            "price": 0.1,
+            "crid": "creative-123",
+            "adm": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>",
+            "w": 300,
+            "h": 250
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
+
diff --git a/adapters/criteo/criteotest/exemplary/simple-banner-inapp.json b/adapters/criteo/criteotest/exemplary/simple-banner-inapp.json
new file mode 100755
index 00000000..0ec1b7f8
--- /dev/null
+++ b/adapters/criteo/criteotest/exemplary/simple-banner-inapp.json
@@ -0,0 +1,110 @@
+{
+    "mockBidRequest": {
+      "id": "test-request-id",
+      "app": {
+        "bundle": "test.app.bundle"
+      },
+      "device": {
+        "ifa": "test-ifa-123456",
+        "os": "android",
+        "ip": "91.199.242.236",
+        "ua": "random user agent"
+      },
+      "imp": [
+        {
+          "id": "test-imp-id",
+          "banner": {
+            "format": [
+              {
+                "w": 300,
+                "h": 250
+              }
+            ]
+          },
+          "ext": {
+            "bidder": {
+              "zoneid": 123456,
+              "networkid": 78910
+            }
+          }
+        }
+      ]
+    },
+    "httpCalls": [
+      {
+        "expectedRequest": {
+          "uri": "https://bidder.criteo.com/cdb?profileId=230",
+          "headers": {
+            "X-Forwarded-For": ["91.199.242.236"],
+            "User-Agent": ["random user agent"]
+          },
+          "body": {
+            "id": "test-request-id",
+            "publisher": {
+              "bundleid": "test.app.bundle",
+              "networkid": 78910
+            },
+            "user": {
+              "deviceid": "test-ifa-123456",
+              "deviceos": "android",
+              "deviceidtype": "gaid",
+              "ip": "91.199.242.236",
+              "ua": "random user agent"
+            },
+            "gdprconsent": {},
+            "slots": [
+              {
+                "slotid": "00000000-0000-0000-00000000",
+                "impid": "test-imp-id",
+                "zoneid": 123456,
+                "networkid": 78910,
+                "sizes": [
+                  "300x250"
+                ]
+              }
+            ]
+          }
+        },
+        "mockResponse": {
+          "status": 200,
+          "body": {
+            "id": "test-request-id",
+            "slots": [
+              {
+                "id": "test-slot-id",
+                "impid": "test-imp-id",
+                "zoneid": 123456,
+                "networkid": 78910,
+                "cpm": 0.1,
+                "currency": "USD",
+                "width": 300,
+                "height": 250,
+                "creativeid": "creative-123",
+                "creative": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>"
+              }
+            ]
+          }
+        }
+      }
+    ],
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "test-slot-id",
+            "impid": "test-imp-id",
+            "price": 0.1,
+            "crid": "creative-123",
+            "adm": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>",
+            "w": 300,
+            "h": 250
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
+  
diff --git a/adapters/criteo/criteotest/exemplary/simple-banner-uid.json b/adapters/criteo/criteotest/exemplary/simple-banner-uid.json
new file mode 100755
index 00000000..2499790b
--- /dev/null
+++ b/adapters/criteo/criteotest/exemplary/simple-banner-uid.json
@@ -0,0 +1,132 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "site": {
+      "id": "site-id",
+      "page": "criteo.com"
+    },
+    "device": {
+      "os": "android",
+      "ip": "91.199.242.236",
+      "ua": "random user agent"
+    },
+    "user": {
+      "ip": "91.199.242.236",
+      "ua": "random user agent",
+      "ext": {
+        "eids": [{
+          "source": "criteo.com",
+          "uids": [{
+            "id": "criteo-eid"
+          }]
+        }]
+      }
+    },
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "zoneid": 123456,
+            "networkid": 78910
+          }
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://bidder.criteo.com/cdb?profileId=230",
+        "headers": {
+          "X-Forwarded-For": ["91.199.242.236"],
+          "User-Agent": ["random user agent"]
+        },
+        "body": {
+          "id": "test-request-id",
+          "publisher": {
+            "siteid": "site-id",
+            "url": "criteo.com",
+            "networkid": 78910
+          },
+          "user": {
+            "ip": "91.199.242.236",
+            "ua": "random user agent",
+            "deviceos": "android",
+            "deviceidtype": "gaid"
+          },
+          "gdprconsent": {},
+          "slots": [
+            {
+              "slotid": "00000000-0000-0000-00000000",
+              "impid": "test-imp-id",
+              "zoneid": 123456,
+              "networkid": 78910,
+              "sizes": [
+                "300x250"
+              ]
+            }
+          ],
+          "eids": [
+            {
+              "source": "criteo.com",
+              "uids": [
+                {
+                  "id": "criteo-eid"
+                }
+              ]
+            }
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "slots": [
+            {
+              "id": "test-slot-id",
+              "impid": "test-imp-id",
+              "zoneid": 123456,
+              "networkid": 78910,
+              "cpm": 0.1,
+              "currency": "USD",
+              "width": 300,
+              "height": 250,
+              "creativeid": "creative-123",
+              "creative": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>"
+            }
+          ]
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "test-slot-id",
+            "impid": "test-imp-id",
+            "price": 0.1,
+            "crid": "creative-123",
+            "adm": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>",
+            "w": 300,
+            "h": 250
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
+
diff --git a/adapters/criteo/criteotest/supplemental/204-response-from-target.json b/adapters/criteo/criteotest/supplemental/204-response-from-target.json
new file mode 100755
index 00000000..9ac14ff6
--- /dev/null
+++ b/adapters/criteo/criteotest/supplemental/204-response-from-target.json
@@ -0,0 +1,81 @@
+{
+    "mockBidRequest": {
+      "id": "test-request-id",
+      "app": {
+        "bundle": "test.app.bundle"
+      },
+      "device": {
+        "ifa": "test-ifa-123456",
+        "ip": "91.199.242.236",
+        "ua": "random user agent",
+        "os": "android"
+      },
+      "regs": {
+        "ext": {
+          "us_privacy": "1YYY"
+        }
+      },
+      "imp": [
+        {
+          "id": "test-imp-id",
+          "banner": {
+            "format": [
+              {
+                "w": 300,
+                "h": 250
+              }
+            ]
+          },
+          "ext": {
+            "bidder": {
+              "zoneid": 123456,
+              "networkid": 78910
+            }
+          }
+        }
+      ]
+    },
+    "httpCalls": [
+      {
+        "expectedRequest": {
+          "uri": "https://bidder.criteo.com/cdb?profileId=230",
+          "headers": {
+            "X-Forwarded-For": ["91.199.242.236"],
+            "User-Agent": ["random user agent"]
+          },
+          "body": {
+            "id": "test-request-id",
+            "publisher": {
+              "bundleid": "test.app.bundle",
+              "networkid": 78910
+            },
+            "user": {
+              "deviceid": "test-ifa-123456",
+              "deviceos": "android",
+              "deviceidtype": "gaid",
+              "uspIab": "1YYY",
+              "ip": "91.199.242.236",
+              "ua": "random user agent"
+            },
+            "gdprconsent": {},
+            "slots": [
+              {
+                "slotid": "00000000-0000-0000-00000000",
+                "impid": "test-imp-id",
+                "zoneid": 123456,
+                "networkid": 78910,
+                "sizes": [
+                  "300x250"
+                ]
+              }
+            ]
+          }
+        },
+        "mockResponse": {
+          "status": 204
+        }
+      }
+    ],
+    "expectedMakeBidsErrors": []
+  }
+  
\ No newline at end of file
diff --git a/adapters/criteo/criteotest/supplemental/400-response-from-target.json b/adapters/criteo/criteotest/supplemental/400-response-from-target.json
new file mode 100755
index 00000000..ce3a4b62
--- /dev/null
+++ b/adapters/criteo/criteotest/supplemental/400-response-from-target.json
@@ -0,0 +1,86 @@
+{
+    "mockBidRequest": {
+      "id": "test-request-id",
+      "app": {
+        "bundle": "test.app.bundle"
+      },
+      "device": {
+        "ifa": "test-ifa-123456",
+        "ip": "91.199.242.236",
+        "ua": "random user agent",
+        "os": "android"
+      },
+      "regs": {
+        "ext": {
+          "us_privacy": "1YYY"
+        }
+      },
+      "imp": [
+        {
+          "id": "test-imp-id",
+          "banner": {
+            "format": [
+              {
+                "w": 300,
+                "h": 250
+              }
+            ]
+          },
+          "ext": {
+            "bidder": {
+              "zoneid": 123456,
+              "networkid": 78910
+            }
+          }
+        }
+      ]
+    },
+    "httpCalls": [
+      {
+        "expectedRequest": {
+          "uri": "https://bidder.criteo.com/cdb?profileId=230",
+          "headers": {
+            "X-Forwarded-For": ["91.199.242.236"],
+            "User-Agent": ["random user agent"]
+          },
+          "body": {
+            "id": "test-request-id",
+            "publisher": {
+              "bundleid": "test.app.bundle",
+              "networkid": 78910
+            },
+            "user": {
+              "deviceid": "test-ifa-123456",
+              "deviceos": "android",
+              "deviceidtype": "gaid",
+              "uspIab": "1YYY",
+              "ip": "91.199.242.236",
+              "ua": "random user agent"
+            },
+            "gdprconsent": {},
+            "slots": [
+              {
+                "slotid": "00000000-0000-0000-00000000",
+                "impid": "test-imp-id",
+                "zoneid": 123456,
+                "networkid": 78910,
+                "sizes": [
+                  "300x250"
+                ]
+              }
+            ]
+          }
+        },
+        "mockResponse": {
+          "status": 400
+        }
+      }
+    ],
+    "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/criteo/criteotest/supplemental/500-response-from-target.json b/adapters/criteo/criteotest/supplemental/500-response-from-target.json
new file mode 100755
index 00000000..db6c16a3
--- /dev/null
+++ b/adapters/criteo/criteotest/supplemental/500-response-from-target.json
@@ -0,0 +1,86 @@
+{
+    "mockBidRequest": {
+      "id": "test-request-id",
+      "app": {
+        "bundle": "test.app.bundle"
+      },
+      "device": {
+        "ifa": "test-ifa-123456",
+        "ip": "91.199.242.236",
+        "ua": "random user agent",
+        "os": "android"
+      },
+      "regs": {
+        "ext": {
+          "us_privacy": "1YYY"
+        }
+      },
+      "imp": [
+        {
+          "id": "test-imp-id",
+          "banner": {
+            "format": [
+              {
+                "w": 300,
+                "h": 250
+              }
+            ]
+          },
+          "ext": {
+            "bidder": {
+              "zoneid": 123456,
+              "networkid": 78910
+            }
+          }
+        }
+      ]
+    },
+    "httpCalls": [
+      {
+        "expectedRequest": {
+          "uri": "https://bidder.criteo.com/cdb?profileId=230",
+          "headers": {
+            "X-Forwarded-For": ["91.199.242.236"],
+            "User-Agent": ["random user agent"]
+          },
+          "body": {
+            "id": "test-request-id",
+            "publisher": {
+              "bundleid": "test.app.bundle",
+              "networkid": 78910
+            },
+            "user": {
+              "deviceid": "test-ifa-123456",
+              "deviceos": "android",
+              "deviceidtype": "gaid",
+              "uspIab": "1YYY",
+              "ip": "91.199.242.236",
+              "ua": "random user agent"
+            },
+            "gdprconsent": {},
+            "slots": [
+              {
+                "slotid": "00000000-0000-0000-00000000",
+                "impid": "test-imp-id",
+                "zoneid": 123456,
+                "networkid": 78910,
+                "sizes": [
+                  "300x250"
+                ]
+              }
+            ]
+          }
+        },
+        "mockResponse": {
+          "status": 500
+        }
+      }
+    ],
+    "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/criteo/criteotest/supplemental/ccpa-with-consent-simple-banner-inapp.json b/adapters/criteo/criteotest/supplemental/ccpa-with-consent-simple-banner-inapp.json
new file mode 100755
index 00000000..b047fa18
--- /dev/null
+++ b/adapters/criteo/criteotest/supplemental/ccpa-with-consent-simple-banner-inapp.json
@@ -0,0 +1,115 @@
+{
+    "mockBidRequest": {
+      "id": "test-request-id",
+      "app": {
+        "bundle": "test.app.bundle"
+      },
+      "device": {
+        "ifa": "test-ifa-123456",
+        "ip": "91.199.242.236",
+        "ua": "random user agent",
+        "os": "android"
+      },
+      "regs": {
+        "ext": {
+          "us_privacy": "1YYY"
+        }
+      },
+      "imp": [
+        {
+          "id": "test-imp-id",
+          "banner": {
+            "format": [
+              {
+                "w": 300,
+                "h": 250
+              }
+            ]
+          },
+          "ext": {
+            "bidder": {
+              "zoneid": 123456,
+              "networkid": 78910
+            }
+          }
+        }
+      ]
+    },
+    "httpCalls": [
+      {
+        "expectedRequest": {
+          "uri": "https://bidder.criteo.com/cdb?profileId=230",
+          "headers": {
+            "X-Forwarded-For": ["91.199.242.236"],
+            "User-Agent": ["random user agent"]
+          },
+          "body": {
+            "id": "test-request-id",
+            "publisher": {
+              "bundleid": "test.app.bundle",
+              "networkid": 78910
+            },
+            "user": {
+              "deviceid": "test-ifa-123456",
+              "deviceos": "android",
+              "deviceidtype": "gaid",
+              "uspIab": "1YYY",
+              "ip": "91.199.242.236",
+              "ua": "random user agent"
+            },
+            "gdprconsent": {},
+            "slots": [
+              {
+                "slotid": "00000000-0000-0000-00000000",
+                "impid": "test-imp-id",
+                "zoneid": 123456,
+                "networkid": 78910,
+                "sizes": [
+                  "300x250"
+                ]
+              }
+            ]
+          }
+        },
+        "mockResponse": {
+          "status": 200,
+          "body": {
+            "slots": [
+              {
+                "id": "test-slot-id",
+                "impid": "test-imp-id",
+                "zoneid": 123456,
+                "networkid": 78910,
+                "cpm": 0.1,
+                "currency": "USD",
+                "width": 300,
+                "height": 250,
+                "creativeid": "creative-123",
+                "creative": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>"
+              }
+            ]
+          }
+        }
+      }
+    ],
+    "expectedBidResponses": [
+      {
+        "currency": "USD",
+        "bids": [
+          {
+            "bid": {
+              "id": "test-slot-id",
+              "impid": "test-imp-id",
+              "price": 0.1,
+              "crid": "creative-123",
+              "adm": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>",
+              "w": 300,
+              "h": 250
+            },
+            "type": "banner"
+          }
+        ]
+      }
+    ]
+  }
+  
\ No newline at end of file
diff --git a/adapters/criteo/criteotest/supplemental/ccpa-with-consent-simple-banner-uid.json b/adapters/criteo/criteotest/supplemental/ccpa-with-consent-simple-banner-uid.json
new file mode 100755
index 00000000..f839624a
--- /dev/null
+++ b/adapters/criteo/criteotest/supplemental/ccpa-with-consent-simple-banner-uid.json
@@ -0,0 +1,139 @@
+{
+    "mockBidRequest": {
+      "id": "test-request-id",
+      "site": {
+        "id": "site-id",
+        "page": "criteo.com"
+      },
+      "device": {
+        "os": "android",
+        "ip": "91.199.242.236",
+        "ua": "random user agent"
+      },
+      "user": {
+        "ext": {
+          "eids": [{
+            "source": "criteo.com",
+            "uids": [{
+              "id": "criteo-eid"
+            }]
+          }]
+        }
+      },
+      "regs": {
+        "ext": {
+          "us_privacy": "1YYY"
+        }
+      },
+      "imp": [
+        {
+          "id": "test-imp-id",
+          "banner": {
+            "format": [
+              {
+                "w": 300,
+                "h": 250
+              }
+            ]
+          },
+          "ext": {
+            "bidder": {
+              "zoneid": 123456,
+              "networkid": 78910
+            }
+          }
+        }
+      ]
+    },
+    "httpCalls": [
+      {
+        "expectedRequest": {
+          "uri": "https://bidder.criteo.com/cdb?profileId=230",
+          "headers": {
+            "X-Forwarded-For": [
+              "91.199.242.236"
+            ],
+            "User-Agent": [
+              "random user agent"
+            ]
+          },
+          "body": {
+            "id": "test-request-id",
+            "publisher": {
+              "siteid": "site-id",
+              "url": "criteo.com",
+              "networkid": 78910
+            },
+            "user": {
+              "ip": "91.199.242.236",
+              "ua": "random user agent",
+              "deviceos": "android",
+              "deviceidtype": "gaid",
+              "uspIab": "1YYY"
+            },
+            "gdprconsent": {},
+            "slots": [
+              {
+                "slotid": "00000000-0000-0000-00000000",
+                "impid": "test-imp-id",
+                "zoneid": 123456,
+                "networkid": 78910,
+                "sizes": [
+                  "300x250"
+                ]
+              }
+            ],
+            "eids": [
+              {
+                "source": "criteo.com",
+                "uids": [
+                  {
+                    "id": "criteo-eid"
+                  }
+                ]
+              }
+            ]
+          }
+        },
+        "mockResponse": {
+          "status": 200,
+          "body": {
+            "slots": [
+              {
+                "id": "test-slot-id",
+                "impid": "test-imp-id",
+                "zoneid": 123456,
+                "networkid": 78910,
+                "cpm": 0.1,
+                "currency": "USD",
+                "width": 300,
+                "height": 250,
+                "creativeid": "creative-123",
+                "creative": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>"
+              }
+            ]
+          }
+        }
+      }
+    ],
+    "expectedBidResponses": [
+      {
+        "currency": "USD",
+        "bids": [
+          {
+            "bid": {
+              "id": "test-slot-id",
+              "impid": "test-imp-id",
+              "price": 0.1,
+              "crid": "creative-123",
+              "adm": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>",
+              "w": 300,
+              "h": 250
+            },
+            "type": "banner"
+          }
+        ]
+      }
+    ]
+  }
+  
\ No newline at end of file
diff --git a/adapters/criteo/criteotest/supplemental/ccpa-without-consent-simple-banner-inapp.json b/adapters/criteo/criteotest/supplemental/ccpa-without-consent-simple-banner-inapp.json
new file mode 100755
index 00000000..77a37361
--- /dev/null
+++ b/adapters/criteo/criteotest/supplemental/ccpa-without-consent-simple-banner-inapp.json
@@ -0,0 +1,85 @@
+{
+    "mockBidRequest": {
+      "id": "test-request-id",
+      "app": {
+        "bundle": "test.app.bundle"
+      },
+      "device": {
+        "ifa": "test-ifa-123456",
+        "ip": "91.199.242.236",
+        "ua": "random user agent",
+        "os": "android"
+      },
+      "regs": {
+        "ext": {
+          "us_privacy": "1YNN"
+        }
+      },
+      "imp": [
+        {
+          "id": "test-imp-id",
+          "banner": {
+            "format": [
+              {
+                "w": 300,
+                "h": 250
+              }
+            ]
+          },
+          "ext": {
+            "bidder": {
+              "zoneid": 123456,
+              "networkid": 78910
+            }
+          }
+        }
+      ]
+    },
+    "httpCalls": [
+      {
+        "expectedRequest": {
+          "uri": "https://bidder.criteo.com/cdb?profileId=230",
+          "headers": {
+            "X-Forwarded-For": [
+              "91.199.242.236"
+            ],
+            "User-Agent": [
+              "random user agent"
+            ]
+          },
+          "body": {
+            "id": "test-request-id",
+            "publisher": {
+              "bundleid": "test.app.bundle",
+              "networkid": 78910
+            },
+            "user": {
+              "deviceid": "test-ifa-123456",
+              "deviceos": "android",
+              "deviceidtype": "gaid",
+              "uspIab": "1YNN",
+              "ip": "91.199.242.236",
+              "ua": "random user agent"
+            },
+            "gdprconsent": {},
+            "slots": [
+              {
+                "slotid": "00000000-0000-0000-00000000",
+                "impid": "test-imp-id",
+                "zoneid": 123456,
+                "networkid": 78910,
+                "sizes": [
+                  "300x250"
+                ]
+              }
+            ]
+          }
+        },
+        "mockResponse": {
+          "status": 204
+        }
+      }
+    ],
+    "expectedBidResponses": []
+  }
+  
\ No newline at end of file
diff --git a/adapters/criteo/criteotest/supplemental/ccpa-without-consent-simple-banner-uid.json b/adapters/criteo/criteotest/supplemental/ccpa-without-consent-simple-banner-uid.json
new file mode 100755
index 00000000..31c14fc4
--- /dev/null
+++ b/adapters/criteo/criteotest/supplemental/ccpa-without-consent-simple-banner-uid.json
@@ -0,0 +1,105 @@
+{
+    "mockBidRequest": {
+      "id": "test-request-id",
+      "site": {
+        "id": "site-id",
+        "page": "criteo.com"
+      },
+      "device": {
+        "os": "android",
+        "ip": "91.199.242.236",
+        "ua": "random user agent"
+      },
+      "user": {
+        "ext": {
+          "eids": [{
+            "source": "criteo.com",
+            "uids": [{
+              "id": "criteo-eid"
+            }]
+          }]
+        }
+      },
+      "regs": {
+        "ext": {
+          "us_privacy": "1YNN"
+        }
+      },
+      "imp": [
+        {
+          "id": "test-imp-id",
+          "banner": {
+            "format": [
+              {
+                "w": 300,
+                "h": 250
+              }
+            ]
+          },
+          "ext": {
+            "bidder": {
+              "zoneid": 123456,
+              "networkid": 78910
+            }
+          }
+        }
+      ]
+    },
+    "httpCalls": [
+      {
+        "expectedRequest": {
+          "uri": "https://bidder.criteo.com/cdb?profileId=230",
+          "headers": {
+            "X-Forwarded-For": [
+              "91.199.242.236"
+            ],
+            "User-Agent": [
+              "random user agent"
+            ]
+          },
+          "body": {
+            "id": "test-request-id",
+            "publisher": {
+              "siteid": "site-id",
+              "url": "criteo.com",
+              "networkid": 78910
+            },
+            "user": {
+              "ip": "91.199.242.236",
+              "ua": "random user agent",
+              "deviceos": "android",
+              "deviceidtype": "gaid",
+              "uspIab": "1YNN"
+            },
+            "gdprconsent": {},
+            "slots": [
+              {
+                "slotid": "00000000-0000-0000-00000000",
+                "impid": "test-imp-id",
+                "zoneid": 123456,
+                "networkid": 78910,
+                "sizes": [
+                  "300x250"
+                ]
+              }
+            ],
+            "eids": [
+              {
+                "source": "criteo.com",
+                "uids": [
+                  {
+                    "id": "criteo-eid"
+                  }
+                ]
+              }
+            ]
+          }
+        },
+        "mockResponse": {
+          "status": 204
+        }
+      }
+    ],
+    "expectedBidResponses": []
+  }
+  
\ No newline at end of file
diff --git a/adapters/criteo/criteotest/supplemental/gdpr-with-consent-simple-banner-inapp.json b/adapters/criteo/criteotest/supplemental/gdpr-with-consent-simple-banner-inapp.json
new file mode 100755
index 00000000..416053db
--- /dev/null
+++ b/adapters/criteo/criteotest/supplemental/gdpr-with-consent-simple-banner-inapp.json
@@ -0,0 +1,126 @@
+{
+    "mockBidRequest": {
+      "id": "test-request-id",
+      "app": {
+        "bundle": "test.app.bundle"
+      },
+      "device": {
+        "ifa": "test-ifa-123456",
+        "os": "android",
+        "ip": "91.199.242.236",
+        "ua": "random user agent"
+      },
+      "user": {
+        "ext": {
+          "consent": "iabconsentstringwithconsent"
+        }
+      },
+      "regs": {
+        "ext": {
+          "gdpr": 1
+        }
+      },
+      "imp": [
+        {
+          "id": "test-imp-id",
+          "banner": {
+            "format": [
+              {
+                "w": 300,
+                "h": 250
+              }
+            ]
+          },
+          "ext": {
+            "bidder": {
+              "zoneid": 123456,
+              "networkid": 78910
+            }
+          }
+        }
+      ]
+    },
+    "httpCalls": [
+      {
+        "expectedRequest": {
+          "uri": "https://bidder.criteo.com/cdb?profileId=230",
+          "headers": {
+            "X-Forwarded-For": [
+              "91.199.242.236"
+            ],
+            "User-Agent": [
+              "random user agent"
+            ]
+          },
+          "body": {
+            "id": "test-request-id",
+            "publisher": {
+              "bundleid": "test.app.bundle",
+              "networkid": 78910
+            },
+            "user": {
+              "deviceid": "test-ifa-123456",
+              "deviceos": "android",
+              "deviceidtype": "gaid",
+              "ip": "91.199.242.236",
+              "ua": "random user agent"
+            },
+            "gdprconsent": {
+              "consentdata": "iabconsentstringwithconsent",
+              "gdprapplies": true
+            },
+            "slots": [
+              {
+                "slotid": "00000000-0000-0000-00000000",
+                "impid": "test-imp-id",
+                "zoneid": 123456,
+                "networkid": 78910,
+                "sizes": [
+                  "300x250"
+                ]
+              }
+            ]
+          }
+        },
+        "mockResponse": {
+          "status": 200,
+          "body": {
+            "slots": [
+              {
+                "id": "test-slot-id",
+                "impid": "test-imp-id",
+                "zoneid": 123456,
+                "networkid": 78910,
+                "cpm": 0.1,
+                "currency": "USD",
+                "width": 300,
+                "height": 250,
+                "creativeid": "creative-123",
+                "creative": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>"
+              }
+            ]
+          }
+        }
+      }
+    ],
+    "expectedBidResponses": [
+      {
+        "currency": "USD",
+        "bids": [
+          {
+            "bid": {
+              "id": "test-slot-id",
+              "impid": "test-imp-id",
+              "price": 0.1,
+              "crid": "creative-123",
+              "adm": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>",
+              "w": 300,
+              "h": 250
+            },
+            "type": "banner"
+          }
+        ]
+      }
+    ]
+  }
+  
\ No newline at end of file
diff --git a/adapters/criteo/criteotest/supplemental/gdpr-with-consent-simple-banner-uid.json b/adapters/criteo/criteotest/supplemental/gdpr-with-consent-simple-banner-uid.json
new file mode 100755
index 00000000..e8374616
--- /dev/null
+++ b/adapters/criteo/criteotest/supplemental/gdpr-with-consent-simple-banner-uid.json
@@ -0,0 +1,142 @@
+{
+    "mockBidRequest": {
+      "id": "test-request-id",
+      "site": {
+        "id": "site-id",
+        "page": "criteo.com"
+      },
+      "device": {
+        "os": "android",
+        "ip": "91.199.242.236",
+        "ua": "random user agent"
+      },
+      "user": {
+        "ext": {
+          "consent": "iabconsentstringwithconsent",
+          "eids": [{
+            "source": "criteo.com",
+            "uids": [{
+              "id": "criteo-eid"
+            }]
+          }]
+        }
+      },
+      "regs": {
+        "ext": {
+          "gdpr": 1
+        }
+      },
+      "imp": [
+        {
+          "id": "test-imp-id",
+          "banner": {
+            "format": [
+              {
+                "w": 300,
+                "h": 250
+              }
+            ]
+          },
+          "ext": {
+            "bidder": {
+              "zoneid": 123456,
+              "networkid": 78910
+            }
+          }
+        }
+      ]
+    },
+    "httpCalls": [
+      {
+        "expectedRequest": {
+          "uri": "https://bidder.criteo.com/cdb?profileId=230",
+          "headers": {
+            "X-Forwarded-For": [
+              "91.199.242.236"
+            ],
+            "User-Agent": [
+              "random user agent"
+            ]
+          },
+          "body": {
+            "id": "test-request-id",
+            "publisher": {
+              "siteid": "site-id",
+              "url": "criteo.com",
+              "networkid": 78910
+            },
+            "user": {
+              "ip": "91.199.242.236",
+              "ua": "random user agent",
+              "deviceos": "android",
+              "deviceidtype": "gaid"
+            },
+            "gdprconsent": {
+              "consentdata": "iabconsentstringwithconsent",
+              "gdprapplies": true
+            },
+            "slots": [
+              {
+                "slotid": "00000000-0000-0000-00000000",
+                "impid": "test-imp-id",
+                "zoneid": 123456,
+                "networkid": 78910,
+                "sizes": [
+                  "300x250"
+                ]
+              }
+            ],
+            "eids": [
+              {
+                "source": "criteo.com",
+                "uids": [
+                  {
+                    "id": "criteo-eid"
+                  }
+                ]
+              }
+            ]
+          }
+        },
+        "mockResponse": {
+          "status": 200,
+          "body": {
+            "slots": [
+              {
+                "id": "test-slot-id",
+                "impid": "test-imp-id",
+                "zoneid": 123456,
+                "networkid": 78910,
+                "cpm": 0.1,
+                "currency": "USD",
+                "width": 300,
+                "height": 250,
+                "creativeid": "creative-123",
+                "creative": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>"
+              }
+            ]
+          }
+        }
+      }
+    ],
+    "expectedBidResponses": [
+      {
+        "currency": "USD",
+        "bids": [
+          {
+            "bid": {
+              "id": "test-slot-id",
+              "impid": "test-imp-id",
+              "price": 0.1,
+              "crid": "creative-123",
+              "adm": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>",
+              "w": 300,
+              "h": 250
+            },
+            "type": "banner"
+          }
+        ]
+      }
+    ]
+  }
+  
\ No newline at end of file
diff --git a/adapters/criteo/criteotest/supplemental/gdpr-without-consent-simple-banner-inapp.json b/adapters/criteo/criteotest/supplemental/gdpr-without-consent-simple-banner-inapp.json
new file mode 100755
index 00000000..7385b937
--- /dev/null
+++ b/adapters/criteo/criteotest/supplemental/gdpr-without-consent-simple-banner-inapp.json
@@ -0,0 +1,92 @@
+{
+    "mockBidRequest": {
+      "id": "test-request-id",
+      "app": {
+        "bundle": "test.app.bundle"
+      },
+      "device": {
+        "ifa": "test-ifa-123456",
+        "os": "android",
+        "ip": "91.199.242.236",
+        "ua": "random user agent"
+      },
+      "user": {
+        "ext": {
+          "consent": "iabconsentstringwithnoconsent"
+        }
+      },
+      "regs": {
+        "ext": {
+          "gdpr": 1
+        }
+      },
+      "imp": [
+        {
+          "id": "test-imp-id",
+          "banner": {
+            "format": [
+              {
+                "w": 300,
+                "h": 250
+              }
+            ]
+          },
+          "ext": {
+            "bidder": {
+              "zoneid": 123456,
+              "networkid": 78910
+            }
+          }
+        }
+      ]
+    },
+    "httpCalls": [
+      {
+        "expectedRequest": {
+          "uri": "https://bidder.criteo.com/cdb?profileId=230",
+          "headers": {
+            "X-Forwarded-For": [
+              "91.199.242.236"
+            ],
+            "User-Agent": [
+              "random user agent"
+            ]
+          },
+          "body": {
+            "id": "test-request-id",
+            "publisher": {
+              "bundleid": "test.app.bundle",
+              "networkid": 78910
+            },
+            "user": {
+              "deviceid": "test-ifa-123456",
+              "deviceos": "android",
+              "deviceidtype": "gaid",
+              "ip": "91.199.242.236",
+              "ua": "random user agent"
+            },
+            "gdprconsent": {
+              "consentdata": "iabconsentstringwithnoconsent",
+              "gdprapplies": true
+            },
+            "slots": [
+              {
+                "slotid": "00000000-0000-0000-00000000",
+                "impid": "test-imp-id",
+                "zoneid": 123456,
+                "networkid": 78910,
+                "sizes": [
+                  "300x250"
+                ]
+              }
+            ]
+          }
+        },
+        "mockResponse": {
+          "status": 204
+        }
+      }
+    ],
+    "expectedBidResponses": []
+  }
+  
\ No newline at end of file
diff --git a/adapters/criteo/criteotest/supplemental/gdpr-without-consent-simple-banner-uid.json b/adapters/criteo/criteotest/supplemental/gdpr-without-consent-simple-banner-uid.json
new file mode 100755
index 00000000..dbc5fb32
--- /dev/null
+++ b/adapters/criteo/criteotest/supplemental/gdpr-without-consent-simple-banner-uid.json
@@ -0,0 +1,108 @@
+{
+    "mockBidRequest": {
+      "id": "test-request-id",
+      "site": {
+        "id": "site-id",
+        "page": "criteo.com"
+      },
+      "device": {
+        "os": "android",
+        "ip": "91.199.242.236",
+        "ua": "random user agent"
+      },
+      "user": {
+        "ext": {
+          "consent": "iabconsentstringwithoutconsent",
+          "eids": [{
+            "source": "criteo.com",
+            "uids": [{
+              "id": "criteo-eid"
+            }]
+          }]
+        }
+      },
+      "regs": {
+        "ext": {
+          "gdpr": 1
+        }
+      },
+      "imp": [
+        {
+          "id": "test-imp-id",
+          "banner": {
+            "format": [
+              {
+                "w": 300,
+                "h": 250
+              }
+            ]
+          },
+          "ext": {
+            "bidder": {
+              "zoneid": 123456,
+              "networkid": 78910
+            }
+          }
+        }
+      ]
+    },
+    "httpCalls": [
+      {
+        "expectedRequest": {
+          "uri": "https://bidder.criteo.com/cdb?profileId=230",
+          "headers": {
+            "X-Forwarded-For": [
+              "91.199.242.236"
+            ],
+            "User-Agent": [
+              "random user agent"
+            ]
+          },
+          "body": {
+            "id": "test-request-id",
+            "publisher": {
+              "siteid": "site-id",
+              "url": "criteo.com",
+              "networkid": 78910
+            },
+            "user": {
+              "ip": "91.199.242.236",
+              "ua": "random user agent",
+              "deviceos": "android",
+              "deviceidtype": "gaid"
+            },
+            "gdprconsent": {
+              "consentdata": "iabconsentstringwithoutconsent",
+              "gdprapplies": true
+            },
+            "slots": [
+              {
+                "slotid": "00000000-0000-0000-00000000",
+                "impid": "test-imp-id",
+                "zoneid": 123456,
+                "networkid": 78910,
+                "sizes": [
+                  "300x250"
+                ]
+              }
+            ],
+            "eids": [
+              {
+                "source": "criteo.com",
+                "uids": [
+                  {
+                    "id": "criteo-eid"
+                  }
+                ]
+              }
+            ]
+          }
+        },
+        "mockResponse": {
+          "status": 204
+        }
+      }
+    ],
+    "expectedBidResponses": []
+  }
+  
\ No newline at end of file
diff --git a/adapters/criteo/criteotest/supplemental/multislots-simple-banner-inapp.json b/adapters/criteo/criteotest/supplemental/multislots-simple-banner-inapp.json
new file mode 100755
index 00000000..75f4118e
--- /dev/null
+++ b/adapters/criteo/criteotest/supplemental/multislots-simple-banner-inapp.json
@@ -0,0 +1,213 @@
+{
+    "mockBidRequest": {
+      "id": "test-request-id",
+      "app": {
+        "bundle": "test.app.bundle"
+      },
+      "device": {
+        "ip": "91.199.242.236",
+        "ua": "random user agent",
+        "ifa": "test-ifa-123456",
+        "os": "android"
+      },
+      "imp": [
+        {
+          "id": "test-imp-id-1",
+          "banner": {
+            "format": [
+              {
+                "w": 300,
+                "h": 250
+              }
+            ]
+          },
+          "ext": {
+            "bidder": {
+              "zoneid": 123456,
+              "networkid": 78910
+            }
+          }
+        },
+        {
+          "id": "test-imp-id-2",
+          "banner": {
+            "format": [
+              {
+                "w": 300,
+                "h": 250
+              }
+            ]
+          },
+          "ext": {
+            "bidder": {
+              "zoneid": 7891011,
+              "networkid": 78910
+            }
+          }
+        },
+        {
+          "id": "test-imp-id-3",
+          "banner": {
+            "format": [
+              {
+                "w": 300,
+                "h": 250
+              }
+            ]
+          },
+          "ext": {
+            "bidder": {
+              "zoneid": 121314,
+              "networkid": 78910
+            }
+          }
+        }
+      ]
+    },
+    "httpCalls": [
+      {
+        "expectedRequest": {
+          "uri": "https://bidder.criteo.com/cdb?profileId=230",
+          "headers": {
+            "X-Forwarded-For": [
+              "91.199.242.236"
+            ],
+            "User-Agent": [
+              "random user agent"
+            ]
+          },
+          "body": {
+            "id": "test-request-id",
+            "publisher": {
+              "bundleid": "test.app.bundle",
+              "networkid": 78910
+            },
+            "user": {
+              "deviceid": "test-ifa-123456",
+              "deviceos": "android",
+              "deviceidtype": "gaid",
+              "ip": "91.199.242.236",
+              "ua": "random user agent"
+            },
+            "gdprconsent": {},
+            "slots": [
+              {
+                "slotid": "00000000-0000-0000-00000000",
+                "impid": "test-imp-id-1",
+                "zoneid": 123456,
+                "networkid": 78910,
+                "sizes": [
+                  "300x250"
+                ]
+              },
+              {
+                "slotid": "00000000-0000-0000-00000000",
+                "impid": "test-imp-id-2",
+                "zoneid": 7891011,
+                "networkid": 78910,
+                "sizes": [
+                  "300x250"
+                ]
+              },
+              {
+                "slotid": "00000000-0000-0000-00000000",
+                "impid": "test-imp-id-3",
+                "zoneid": 121314,
+                "networkid": 78910,
+                "sizes": [
+                  "300x250"
+                ]
+              }
+            ]
+          }
+        },
+        "mockResponse": {
+          "status": 200,
+          "body": {
+            "slots": [
+              {
+                "id": "test-slot-id-1",
+                "impid": "test-imp-id-1",
+                "zoneid": 123456,
+                "networkid": 78910,
+                "cpm": 0.1,
+                "currency": "USD",
+                "width": 300,
+                "height": 250,
+                "creativeid": "creative-123",
+                "creative": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>"
+              },
+              {
+                "id": "test-slot-id-2",
+                "impid": "test-imp-id-2",
+                "zoneid": 7891011,
+                "networkid": 78910,
+                "cpm": 0.2,
+                "currency": "USD",
+                "width": 320,
+                "height": 50,
+                "creativeid": "creative-123",
+                "creative": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>"
+              },
+              {
+                "id": "test-slot-id-3",
+                "impid": "test-imp-id-3",
+                "zoneid": 121314,
+                "networkid": 78910,
+                "cpm": 0.3,
+                "currency": "USD",
+                "width": 300,
+                "height": 600,
+                "creativeid": "creative-123",
+                "creative": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>"
+              }
+            ]
+          }
+        }
+      }
+    ],
+    "expectedBidResponses": [
+      {
+        "currency": "USD",
+        "bids": [
+          {
+            "bid": {
+              "id": "test-slot-id-1",
+              "impid": "test-imp-id-1",
+              "price": 0.1,
+              "crid": "creative-123",
+              "adm": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>",
+              "w": 300,
+              "h": 250
+            },
+            "type": "banner"
+          },
+          {
+            "bid": {
+              "id": "test-slot-id-2",
+              "impid": "test-imp-id-2",
+              "price": 0.2,
+              "crid": "creative-123",
+              "adm": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>",
+              "w": 320,
+              "h": 50
+            },
+            "type": "banner"
+          },
+          {
+            "bid": {
+              "id": "test-slot-id-3",
+              "impid": "test-imp-id-3",
+              "price": 0.3,
+              "crid": "creative-123",
+              "adm": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>",
+              "w": 300,
+              "h": 600
+            },
+            "type": "banner"
+          }
+        ]
+      }
+    ]
+  }
+  
\ No newline at end of file
diff --git a/adapters/criteo/criteotest/supplemental/multislots-simple-banner-uid-different-network-ids.json b/adapters/criteo/criteotest/supplemental/multislots-simple-banner-uid-different-network-ids.json
new file mode 100755
index 00000000..dcb51825
--- /dev/null
+++ b/adapters/criteo/criteotest/supplemental/multislots-simple-banner-uid-different-network-ids.json
@@ -0,0 +1,84 @@
+{
+    "mockBidRequest": {
+      "id": "test-request-id",
+      "site": {
+        "id": "site-id",
+        "page": "criteo.com"
+      },
+      "device": {
+        "os": "android",
+        "ip": "91.199.242.236",
+        "ua": "random user agent"
+      },
+      "user": {
+        "ext": {
+          "eids": [{
+            "source": "criteo.com",
+            "uids": [{
+              "id": "criteo-eid"
+            }]
+          }]
+        }
+      },
+      "imp": [
+        {
+          "id": "test-imp-id-1",
+          "banner": {
+            "format": [
+              {
+                "w": 300,
+                "h": 250
+              }
+            ]
+          },
+          "ext": {
+            "bidder": {
+              "zoneid": 123456,
+              "networkid": 78910
+            }
+          }
+        },
+        {
+          "id": "test-imp-id-2",
+          "banner": {
+            "format": [
+              {
+                "w": 300,
+                "h": 250
+              }
+            ]
+          },
+          "ext": {
+            "bidder": {
+              "zoneid": 7891011,
+              "networkid": 123456
+            }
+          }
+        },
+        {
+          "id": "test-imp-id-3",
+          "banner": {
+            "format": [
+              {
+                "w": 300,
+                "h": 250
+              }
+            ]
+          },
+          "ext": {
+            "bidder": {
+              "zoneid": 121314,
+              "networkid": 467890
+            }
+          }
+        }
+      ]
+    },
+      "expectedMakeRequestsErrors": [
+        {
+          "value": "Bid request has slots coming with several network IDs which is not allowed",
+          "comparison": "literal"
+        }
+      ]
+  }
+  
\ No newline at end of file
diff --git a/adapters/criteo/criteotest/supplemental/multislots-simple-banner-uid.json b/adapters/criteo/criteotest/supplemental/multislots-simple-banner-uid.json
new file mode 100755
index 00000000..e5de75e0
--- /dev/null
+++ b/adapters/criteo/criteotest/supplemental/multislots-simple-banner-uid.json
@@ -0,0 +1,233 @@
+{
+    "mockBidRequest": {
+      "id": "test-request-id",
+      "site": {
+        "id": "site-id",
+        "page": "criteo.com"
+      },
+      "device": {
+        "os": "android",
+        "ip": "91.199.242.236",
+        "ua": "random user agent"
+      },
+      "user": {
+        "ext": {
+          "eids": [{
+            "source": "criteo.com",
+            "uids": [{
+              "id": "criteo-eid"
+            }]
+          }]
+        }
+      },
+      "imp": [
+        {
+          "id": "test-imp-id-1",
+          "banner": {
+            "format": [
+              {
+                "w": 300,
+                "h": 250
+              }
+            ]
+          },
+          "ext": {
+            "bidder": {
+              "zoneid": 123456,
+              "networkid": 78910
+            }
+          }
+        },
+        {
+          "id": "test-imp-id-2",
+          "banner": {
+            "format": [
+              {
+                "w": 300,
+                "h": 250
+              }
+            ]
+          },
+          "ext": {
+            "bidder": {
+              "zoneid": 7891011,
+              "networkid": 78910
+            }
+          }
+        },
+        {
+          "id": "test-imp-id-3",
+          "banner": {
+            "format": [
+              {
+                "w": 300,
+                "h": 250
+              }
+            ]
+          },
+          "ext": {
+            "bidder": {
+              "zoneid": 121314,
+              "networkid": 78910
+            }
+          }
+        }
+      ]
+    },
+    "httpCalls": [
+      {
+        "expectedRequest": {
+          "uri": "https://bidder.criteo.com/cdb?profileId=230",
+          "headers": {
+            "X-Forwarded-For": [
+              "91.199.242.236"
+            ],
+            "User-Agent": [
+              "random user agent"
+            ]
+          },
+          "body": {
+            "id": "test-request-id",
+            "publisher": {
+              "siteid": "site-id",
+              "url": "criteo.com",
+              "networkid": 78910
+            },
+            "user": {
+              "ip": "91.199.242.236",
+              "ua": "random user agent",
+              "deviceos": "android",
+              "deviceidtype": "gaid"
+            },
+            "gdprconsent": {},
+            "slots": [
+              {
+                "slotid": "00000000-0000-0000-00000000",
+                "impid": "test-imp-id-1",
+                "zoneid": 123456,
+                "networkid": 78910,
+                "sizes": [
+                  "300x250"
+                ]
+              },
+              {
+                "slotid": "00000000-0000-0000-00000000",
+                "impid": "test-imp-id-2",
+                "zoneid": 7891011,
+                "networkid": 78910,
+                "sizes": [
+                  "300x250"
+                ]
+              },
+              {
+                "slotid": "00000000-0000-0000-00000000",
+                "impid": "test-imp-id-3",
+                "zoneid": 121314,
+                "networkid": 78910,
+                "sizes": [
+                  "300x250"
+                ]
+              }
+            ],
+            "eids": [
+              {
+                "source": "criteo.com",
+                "uids": [
+                  {
+                    "id": "criteo-eid"
+                  }
+                ]
+              }
+            ]
+          }
+        },
+        "mockResponse": {
+          "status": 200,
+          "body": {
+            "slots": [
+              {
+                "id": "test-slot-id-1",
+                "impid": "test-imp-id-1",
+                "zoneid": 123456,
+                "networkid": 78910,
+                "cpm": 0.1,
+                "currency": "USD",
+                "width": 300,
+                "height": 250,
+                "creativeid": "creative-123",
+                "creative": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>"
+              },
+              {
+                "id": "test-slot-id-2",
+                "impid": "test-imp-id-2",
+                "zoneid": 7891011,
+                "networkid": 78910,
+                "cpm": 0.2,
+                "currency": "USD",
+                "width": 320,
+                "height": 50,
+                "creativeid": "creative-123",
+                "creative": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>"
+              },
+              {
+                "id": "test-slot-id-3",
+                "impid": "test-imp-id-3",
+                "zoneid": 121314,
+                "networkid": 78910,
+                "cpm": 0.3,
+                "currency": "USD",
+                "width": 300,
+                "height": 600,
+                "creativeid": "creative-123",
+                "creative": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>"
+              }
+            ]
+          }
+        }
+      }
+    ],
+    "expectedBidResponses": [
+      {
+        "currency": "USD",
+        "bids": [
+          {
+            "bid": {
+              "id": "test-slot-id-1",
+              "impid": "test-imp-id-1",
+              "price": 0.1,
+              "crid": "creative-123",
+              "adm": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>",
+              "w": 300,
+              "h": 250
+            },
+            "type": "banner"
+          },
+          {
+            "bid": {
+              "id": "test-slot-id-2",
+              "impid": "test-imp-id-2",
+              "price": 0.2,
+              "crid": "creative-123",
+              "adm": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>",
+              "w": 320,
+              "h": 50
+            },
+            "type": "banner"
+          },
+          {
+            "bid": {
+              "id": "test-slot-id-3",
+              "impid": "test-imp-id-3",
+              "price": 0.3,
+              "crid": "creative-123",
+              "adm": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>",
+              "w": 300,
+              "h": 600
+            },
+            "type": "banner"
+          }
+        ]
+      }
+    ]
+  }
+  
\ No newline at end of file
diff --git a/adapters/criteo/criteotest/supplemental/simple-banner-with-direct-size-and-formats-not-included.json b/adapters/criteo/criteotest/supplemental/simple-banner-with-direct-size-and-formats-not-included.json
new file mode 100755
index 00000000..a0cc53d0
--- /dev/null
+++ b/adapters/criteo/criteotest/supplemental/simple-banner-with-direct-size-and-formats-not-included.json
@@ -0,0 +1,132 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "site": {
+      "id": "site-id",
+      "page": "criteo.com"
+    },
+    "device": {
+      "os": "android",
+      "ip": "91.199.242.236",
+      "ua": "random user agent"
+    },
+    "user": {
+      "ext": {
+        "eids": [{
+          "source": "criteo.com",
+          "uids": [{
+            "id": "criteo-eid"
+          }]
+        }]
+      }
+    },
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "w": 300,
+          "h": 250,
+          "format": [
+            {
+              "w": 250,
+              "h": 250
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "zoneid": 123456,
+            "networkid": 78910
+          }
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://bidder.criteo.com/cdb?profileId=230",
+        "headers": {
+          "X-Forwarded-For": ["91.199.242.236"],
+          "User-Agent": ["random user agent"]
+        },
+        "body": {
+          "id": "test-request-id",
+          "publisher": {
+            "siteid": "site-id",
+            "url": "criteo.com",
+            "networkid": 78910
+          },
+          "user": {
+            "ip": "91.199.242.236",
+            "ua": "random user agent",
+            "deviceos": "android",
+            "deviceidtype": "gaid"
+          },
+          "gdprconsent": {},
+          "slots": [
+            {
+              "slotid": "00000000-0000-0000-00000000",
+              "impid": "test-imp-id",
+              "zoneid": 123456,
+              "networkid": 78910,
+              "sizes": [
+                "250x250"
+              ]
+            }
+          ],
+          "eids": [
+            {
+              "source": "criteo.com",
+              "uids": [
+                {
+                  "id": "criteo-eid"
+                }
+              ]
+            }
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "slots": [
+            {
+              "id": "test-slot-id",
+              "impid": "test-imp-id",
+              "zoneid": 123456,
+              "networkid": 78910,
+              "cpm": 0.1,
+              "currency": "USD",
+              "width": 300,
+              "height": 250,
+              "creativeid": "creative-123",
+              "creative": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>"
+            }
+          ]
+        }
+      }
+    }
+  ],
+    "expectedBidResponses": [
+      {
+        "currency": "USD",
+        "bids": [
+          {
+            "bid": {
+              "id": "test-slot-id",
+              "impid": "test-imp-id",
+              "price": 0.1,
+              "crid": "creative-123",
+              "adm": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>",
+              "w": 300,
+              "h": 250
+            },
+            "type": "banner"
+          }
+        ]
+      }
+  ]
+}
+
diff --git a/adapters/criteo/criteotest/supplemental/simple-banner-with-direct-size-and-formats.json b/adapters/criteo/criteotest/supplemental/simple-banner-with-direct-size-and-formats.json
new file mode 100755
index 00000000..016c16a8
--- /dev/null
+++ b/adapters/criteo/criteotest/supplemental/simple-banner-with-direct-size-and-formats.json
@@ -0,0 +1,132 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "site": {
+      "id": "site-id",
+      "page": "criteo.com"
+    },
+    "device": {
+      "os": "android",
+      "ip": "91.199.242.236",
+      "ua": "random user agent"
+    },
+    "user": {
+      "ext": {
+        "eids": [{
+          "source": "criteo.com",
+          "uids": [{
+            "id": "criteo-eid"
+          }]
+        }]
+      }
+    },
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "w": 300,
+          "h": 250,
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "zoneid": 123456,
+            "networkid": 78910
+          }
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://bidder.criteo.com/cdb?profileId=230",
+        "headers": {
+          "X-Forwarded-For": ["91.199.242.236"],
+          "User-Agent": ["random user agent"]
+        },
+        "body": {
+          "id": "test-request-id",
+          "publisher": {
+            "siteid": "site-id",
+            "url": "criteo.com",
+            "networkid": 78910
+          },
+          "user": {
+            "ip": "91.199.242.236",
+            "ua": "random user agent",
+            "deviceos": "android",
+            "deviceidtype": "gaid"
+          },
+          "gdprconsent": {},
+          "slots": [
+            {
+              "slotid": "00000000-0000-0000-00000000",
+              "impid": "test-imp-id",
+              "zoneid": 123456,
+              "networkid": 78910,
+              "sizes": [
+                "300x250"
+              ]
+            }
+          ],
+          "eids": [
+            {
+              "source": "criteo.com",
+              "uids": [
+                {
+                  "id": "criteo-eid"
+                }
+              ]
+            }
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "slots": [
+            {
+              "id": "test-slot-id",
+              "impid": "test-imp-id",
+              "zoneid": 123456,
+              "networkid": 78910,
+              "cpm": 0.1,
+              "currency": "USD",
+              "width": 300,
+              "height": 250,
+              "creativeid": "creative-123",
+              "creative": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>"
+            }
+          ]
+        }
+      }
+    }
+  ],
+    "expectedBidResponses": [
+      {
+        "currency": "USD",
+        "bids": [
+          {
+            "bid": {
+              "id": "test-slot-id",
+              "impid": "test-imp-id",
+              "price": 0.1,
+              "crid": "creative-123",
+              "adm": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>",
+              "w": 300,
+              "h": 250
+            },
+            "type": "banner"
+          }
+        ]
+      }
+  ]
+}
+
diff --git a/adapters/criteo/criteotest/supplemental/simple-banner-with-direct-size.json b/adapters/criteo/criteotest/supplemental/simple-banner-with-direct-size.json
new file mode 100755
index 00000000..68e45da9
--- /dev/null
+++ b/adapters/criteo/criteotest/supplemental/simple-banner-with-direct-size.json
@@ -0,0 +1,126 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "site": {
+      "id": "site-id",
+      "page": "criteo.com"
+    },
+    "device": {
+      "os": "android",
+      "ip": "91.199.242.236",
+      "ua": "random user agent"
+    },
+    "user": {
+      "ext": {
+        "eids": [{
+          "source": "criteo.com",
+          "uids": [{
+            "id": "criteo-eid"
+          }]
+        }]
+      }
+    },
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "w": 300,
+          "h": 250
+        },
+        "ext": {
+          "bidder": {
+            "zoneid": 123456,
+            "networkid": 78910
+          }
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://bidder.criteo.com/cdb?profileId=230",
+        "headers": {
+          "X-Forwarded-For": ["91.199.242.236"],
+          "User-Agent": ["random user agent"]
+        },
+        "body": {
+          "id": "test-request-id",
+          "publisher": {
+            "siteid": "site-id",
+            "url": "criteo.com",
+            "networkid": 78910
+          },
+          "user": {
+            "ip": "91.199.242.236",
+            "ua": "random user agent",
+            "deviceos": "android",
+            "deviceidtype": "gaid"
+          },
+          "gdprconsent": {},
+          "slots": [
+            {
+              "slotid": "00000000-0000-0000-00000000",
+              "impid": "test-imp-id",
+              "zoneid": 123456,
+              "networkid": 78910,
+              "sizes": [
+                "300x250"
+              ]
+            }
+          ],
+          "eids": [
+            {
+              "source": "criteo.com",
+              "uids": [
+                {
+                  "id": "criteo-eid"
+                }
+              ]
+            }
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "slots": [
+            {
+              "id": "test-slot-id",
+              "impid": "test-imp-id",
+              "zoneid": 123456,
+              "networkid": 78910,
+              "cpm": 0.1,
+              "currency": "USD",
+              "width": 300,
+              "height": 250,
+              "creativeid": "creative-123",
+              "creative": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>"
+            }
+          ]
+        }
+      }
+    }
+  ],
+    "expectedBidResponses": [
+      {
+        "currency": "USD",
+        "bids": [
+          {
+            "bid": {
+              "id": "test-slot-id",
+              "impid": "test-imp-id",
+              "price": 0.1,
+              "crid": "creative-123",
+              "adm": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>",
+              "w": 300,
+              "h": 250
+            },
+            "type": "banner"
+          }
+        ]
+      }
+  ]
+}
+
diff --git a/adapters/criteo/criteotest/supplemental/simple-banner-with-ipv6.json b/adapters/criteo/criteotest/supplemental/simple-banner-with-ipv6.json
new file mode 100755
index 00000000..e248db9b
--- /dev/null
+++ b/adapters/criteo/criteotest/supplemental/simple-banner-with-ipv6.json
@@ -0,0 +1,126 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "site": {
+      "id": "site-id",
+      "page": "criteo.com"
+    },
+    "device": {
+      "os": "android",
+      "ipv6": "fd36:ce97:0fa1:dec0:0000:0000:0000:0000",
+      "ua": "random user agent"
+    },
+    "user": {
+      "ext": {
+        "eids": [{
+          "source": "criteo.com",
+          "uids": [{
+            "id": "criteo-eid"
+          }]
+        }]
+      }
+    },
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "w": 300,
+          "h": 250
+        },
+        "ext": {
+          "bidder": {
+            "zoneid": 123456,
+            "networkid": 78910
+          }
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://bidder.criteo.com/cdb?profileId=230",
+        "headers": {
+          "X-Forwarded-For": ["fd36:ce97:0fa1:dec0:0000:0000:0000:0000"],
+          "User-Agent": ["random user agent"]
+        },
+        "body": {
+          "id": "test-request-id",
+          "publisher": {
+            "siteid": "site-id",
+            "url": "criteo.com",
+            "networkid": 78910
+          },
+          "user": {
+            "ipv6": "fd36:ce97:0fa1:dec0:0000:0000:0000:0000",
+            "ua": "random user agent",
+            "deviceos": "android",
+            "deviceidtype": "gaid"
+          },
+          "gdprconsent": {},
+          "slots": [
+            {
+              "slotid": "00000000-0000-0000-00000000",
+              "impid": "test-imp-id",
+              "zoneid": 123456,
+              "networkid": 78910,
+              "sizes": [
+                "300x250"
+              ]
+            }
+          ],
+          "eids": [
+            {
+              "source": "criteo.com",
+              "uids": [
+                {
+                  "id": "criteo-eid"
+                }
+              ]
+            }
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "slots": [
+            {
+              "id": "test-slot-id",
+              "impid": "test-imp-id",
+              "zoneid": 123456,
+              "networkid": 78910,
+              "cpm": 0.1,
+              "currency": "USD",
+              "width": 300,
+              "height": 250,
+              "creativeid": "creative-123",
+              "creative": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>"
+            }
+          ]
+        }
+      }
+    }
+  ],
+    "expectedBidResponses": [
+      {
+        "currency": "USD",
+        "bids": [
+          {
+            "bid": {
+              "id": "test-slot-id",
+              "impid": "test-imp-id",
+              "price": 0.1,
+              "crid": "creative-123",
+              "adm": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>",
+              "w": 300,
+              "h": 250
+            },
+            "type": "banner"
+          }
+        ]
+      }
+  ]
+}
+
diff --git a/adapters/criteo/models_test.go b/adapters/criteo/models_test.go
new file mode 100644
index 00000000..9da7bda2
--- /dev/null
+++ b/adapters/criteo/models_test.go
@@ -0,0 +1,452 @@
+package criteo
+
+import (
+	"encoding/json"
+	"reflect"
+	"testing"
+
+	"github.com/mxmCherry/openrtb/v14/openrtb2"
+	"github.com/prebid/prebid-server/adapters"
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+func TestGetDeviceType(t *testing.T) {
+
+	// Setup:
+	deviceTypeCases := []struct {
+		deviceType string
+		expected   string
+	}{
+		{"ios", "idfa"},
+		{"Ios", "idfa"},
+		{"IOS", "idfa"},
+		{"android", "gaid"},
+		{"unknown", "unknown"},
+		{"", "unknown"},
+		{"qwerty", "unknown"},
+		{"qWerty", "unknown"},
+		{"abc", "unknown"},
+	}
+
+	for _, uc := range deviceTypeCases {
+		// Execute:
+		result := getDeviceType(uc.deviceType)
+
+		// Verify:
+		if uc.expected != result {
+			t.Errorf("Bad getDeviceType for '%s'. Expected: %s, got %s", uc.deviceType, uc.expected, result)
+		}
+	}
+}
+
+func TestNewCriteoRequest(t *testing.T) {
+	// Setup:
+	var (
+		dummyRequestID         = "random request ID"
+		dummyPublisherBundleID = "bundleid"
+		dummyPublisherURL      = "test.com"
+		dummyPublisherSiteID   = "siteid"
+		dummyUserDeviceID      = "random-device-id"
+		dummyUserDeviceOS      = "android"
+		dummyUserDeviceIDType  = "gaid"
+		dummyUserCookieID      = "random-cookie-id"
+		dummyUserIP            = "1.1.1.1"
+		dummyUserUA            = "random UA"
+		dummyGdprApplies       = true
+		dummyGdprAppliesUint   = int8(1)
+		dummyGdprConsentData   = "randomconsentdata"
+		dummySlotID            = "11111111-1111-1111-11111111"
+		dummySlotImpID         = "fake-imp-id-1"
+		dummySlotZoneID        = int64(1)
+	)
+
+	fakeSlotIDGenerator := newFakeGuidGenerator(dummySlotID)
+
+	// The request doesn't make any sense but aims to fill every single criteo request fields
+	expectedCriteoRequest := criteoRequest{
+		ID: dummyRequestID,
+		Publisher: criteoPublisher{
+			SiteID:   dummyPublisherSiteID,
+			BundleID: dummyPublisherBundleID,
+			URL:      dummyPublisherURL,
+		},
+		User: criteoUser{
+			DeviceID:     dummyUserDeviceID,
+			DeviceOS:     dummyUserDeviceOS,
+			DeviceIDType: dummyUserDeviceIDType,
+			CookieID:     dummyUserCookieID,
+			IP:           dummyUserIP,
+			UA:           dummyUserUA,
+		},
+		GdprConsent: criteoGdprConsent{
+			GdprApplies: &dummyGdprApplies,
+			ConsentData: dummyGdprConsentData,
+		},
+		Slots: []criteoRequestSlot{
+			{
+				SlotID: dummySlotID,
+				ImpID:  dummySlotImpID,
+				ZoneID: &dummySlotZoneID,
+			},
+		},
+	}
+
+	userExtJSON, _ := json.Marshal(&openrtb_ext.ExtUser{
+		Consent: dummyGdprConsentData,
+	})
+	regsExtJSON, _ := json.Marshal(&openrtb_ext.ExtRegs{
+		GDPR: &dummyGdprAppliesUint,
+	})
+	bidderExtJSON, _ := json.Marshal(&openrtb_ext.ExtImpCriteo{
+		ZoneID: dummySlotZoneID,
+	})
+	impExtJSON, _ := json.Marshal(&adapters.ExtImpBidder{
+		Bidder: bidderExtJSON,
+	})
+	incomingRequest := &openrtb2.BidRequest{
+		ID: dummyRequestID,
+		App: &openrtb2.App{
+			Bundle: dummyPublisherBundleID,
+		},
+		Site: &openrtb2.Site{
+			ID:   dummyPublisherSiteID,
+			Page: dummyPublisherURL,
+		},
+		User: &openrtb2.User{
+			BuyerUID: dummyUserCookieID,
+			Ext:      userExtJSON,
+		},
+		Regs: &openrtb2.Regs{
+			Ext: regsExtJSON,
+		},
+		Device: &openrtb2.Device{
+			IFA: dummyUserDeviceID,
+			OS:  dummyUserDeviceOS,
+			IP:  dummyUserIP,
+			UA:  dummyUserUA,
+		},
+		Imp: []openrtb2.Imp{
+			{
+				ID:  dummySlotImpID,
+				Ext: impExtJSON,
+			},
+		},
+	}
+
+	// Execute:
+	result, err := newCriteoRequest(fakeSlotIDGenerator, incomingRequest)
+
+	// Verify:
+	if err != nil {
+		t.Errorf("newCriteoRequest has errors: %s", err)
+	}
+
+	if expectedCriteoRequest.ID != result.ID ||
+		!reflect.DeepEqual(expectedCriteoRequest, result) ||
+		!reflect.DeepEqual(expectedCriteoRequest.Publisher, result.Publisher) ||
+		!reflect.DeepEqual(expectedCriteoRequest.User, result.User) ||
+		!reflect.DeepEqual(expectedCriteoRequest.GdprConsent, result.GdprConsent) ||
+		len(expectedCriteoRequest.Slots) != len(result.Slots) ||
+		!reflect.DeepEqual(expectedCriteoRequest.Slots[0], result.Slots[0]) {
+		actualResultJSON, _ := json.Marshal(result)
+		expectedResultJSON, _ := json.Marshal(expectedCriteoRequest)
+		t.Errorf("newCriteoRequest was incorrect, got '%s', want '%s'.", actualResultJSON, expectedResultJSON)
+	}
+}
+
+func TestGetGdprConsent(t *testing.T) {
+	// Setup:
+	var (
+		dummyGdprApplies     = true
+		dummyGdprConsentData = "randomconsentdata"
+		dummyGdprAppliesUint = int8(1)
+	)
+
+	expectedCriteoRequest := criteoRequest{
+		GdprConsent: criteoGdprConsent{
+			GdprApplies: &dummyGdprApplies,
+			ConsentData: dummyGdprConsentData,
+		},
+	}
+
+	userExtJSON, _ := json.Marshal(&openrtb_ext.ExtUser{
+		Consent: dummyGdprConsentData,
+	})
+	regsExtJSON, _ := json.Marshal(&openrtb_ext.ExtRegs{
+		GDPR: &dummyGdprAppliesUint,
+	})
+	incomingRequest := &openrtb2.BidRequest{
+		User: &openrtb2.User{
+			Ext: userExtJSON,
+		},
+		Regs: &openrtb2.Regs{
+			Ext: regsExtJSON,
+		},
+	}
+
+	var regsExt *openrtb_ext.ExtRegs
+	if incomingRequest.Regs != nil {
+		json.Unmarshal(incomingRequest.Regs.Ext, &regsExt)
+	}
+
+	// Execute:
+	gdprConsent, _ := newCriteoGdprConsent(incomingRequest.User, regsExt)
+	result := criteoRequest{
+		GdprConsent: gdprConsent,
+	}
+
+	// Verify:
+	if !reflect.DeepEqual(expectedCriteoRequest, result) {
+		actualResultJSON, _ := json.Marshal(result)
+		expectedResultJSON, _ := json.Marshal(expectedCriteoRequest)
+		t.Errorf("getGdprConsent was incorrect, got '%s', want '%s'.", actualResultJSON, expectedResultJSON)
+	}
+}
+
+func TestGetUser(t *testing.T) {
+	// Setup:
+	var (
+		dummyUserDeviceID     = "random-device-id"
+		dummyUserDeviceOS     = "android"
+		dummyUserDeviceIDType = "gaid"
+		dummyUserCookieID     = "random-cookie-id"
+		dummyUserIP           = "1.1.1.1"
+		dummyUserUA           = "random UA"
+		dummyCcpaString       = "1YYY"
+	)
+	expectedCriteoRequest := &criteoRequest{
+		User: criteoUser{
+			DeviceID:     dummyUserDeviceID,
+			DeviceOS:     dummyUserDeviceOS,
+			DeviceIDType: dummyUserDeviceIDType,
+			CookieID:     dummyUserCookieID,
+			IP:           dummyUserIP,
+			UA:           dummyUserUA,
+			UspIab:       dummyCcpaString,
+		},
+	}
+
+	regsExt := &openrtb_ext.ExtRegs{
+		USPrivacy: dummyCcpaString,
+	}
+	regsExtData, err := json.Marshal(regsExt)
+	if err != nil {
+		t.Errorf("cannot marshal regsExt data")
+	}
+
+	incomingRequest := &openrtb2.BidRequest{
+		User: &openrtb2.User{
+			BuyerUID: dummyUserCookieID,
+		},
+		Device: &openrtb2.Device{
+			IFA: dummyUserDeviceID,
+			OS:  dummyUserDeviceOS,
+			IP:  dummyUserIP,
+			UA:  dummyUserUA,
+		},
+		Regs: &openrtb2.Regs{
+			Ext: regsExtData,
+		},
+	}
+
+	// Execute:
+	result := &criteoRequest{
+		User: newCriteoUser(incomingRequest.User, incomingRequest.Device, regsExt),
+	}
+
+	// Verify:
+	if !reflect.DeepEqual(expectedCriteoRequest.User, result.User) {
+		actualResultJSON, _ := json.Marshal(result)
+		expectedResultJSON, _ := json.Marshal(expectedCriteoRequest)
+		t.Errorf("getUser was incorrect, got '%s', want '%s'.", actualResultJSON, expectedResultJSON)
+	}
+}
+
+func TestGetUser_NilUserAndDevice(t *testing.T) {
+	// Setup:
+	var dummyCcpaString = "1YYY"
+
+	expectedCriteoRequest := &criteoRequest{}
+
+	regsExt := &openrtb_ext.ExtRegs{
+		USPrivacy: dummyCcpaString,
+	}
+	regsExtData, err := json.Marshal(regsExt)
+	if err != nil {
+		t.Errorf("cannot marshal regsExt data")
+	}
+
+	incomingRequest := &openrtb2.BidRequest{
+		Regs: &openrtb2.Regs{
+			Ext: regsExtData,
+		},
+	}
+
+	// Execute:
+	result := &criteoRequest{
+		User: newCriteoUser(incomingRequest.User, incomingRequest.Device, regsExt),
+	}
+
+	// Verify:
+	if !reflect.DeepEqual(expectedCriteoRequest.User, result.User) {
+		actualResultJSON, _ := json.Marshal(result)
+		expectedResultJSON, _ := json.Marshal(expectedCriteoRequest)
+		t.Errorf("getUser was incorrect, got '%s', want '%s'.", actualResultJSON, expectedResultJSON)
+	}
+}
+
+func TestPublisher(t *testing.T) {
+	// Setup:
+	var (
+		dummyPublisherSiteID         = "siteid"
+		dummyPublisherBundleID       = "bundleid"
+		dummyPublisherURL            = "test.com"
+		dummyNetworkID         int64 = 1234567
+	)
+	expectedCriteoRequest := &criteoRequest{
+		Publisher: criteoPublisher{
+			SiteID:    dummyPublisherSiteID,
+			BundleID:  dummyPublisherBundleID,
+			URL:       dummyPublisherURL,
+			NetworkID: &dummyNetworkID,
+		},
+	}
+
+	incomingRequest := &openrtb2.BidRequest{
+		App: &openrtb2.App{
+			Bundle: dummyPublisherBundleID,
+		},
+		Site: &openrtb2.Site{
+			ID:   dummyPublisherSiteID,
+			Page: dummyPublisherURL,
+		},
+	}
+
+	// Execute:
+	result := &criteoRequest{
+		Publisher: newCriteoPublisher(&dummyNetworkID, incomingRequest.App, incomingRequest.Site),
+	}
+
+	// Verify:
+	if !reflect.DeepEqual(expectedCriteoRequest.Publisher, result.Publisher) {
+		actualResultJSON, _ := json.Marshal(result)
+		expectedResultJSON, _ := json.Marshal(expectedCriteoRequest)
+		t.Errorf("getPublisher was incorrect, got '%s', want '%s'.", actualResultJSON, expectedResultJSON)
+	}
+}
+
+func TestGetRequestSlots(t *testing.T) {
+	// Setup:
+	var (
+		dummySlotImpID  = "fake-imp-id-1"
+		dummySlotZoneID = int64(1)
+		dummySlotID     = "22222222-2222-2222-22222222"
+	)
+
+	fakeSlotIDGenerator := newFakeGuidGenerator(dummySlotID)
+
+	expectedCriteoRequest := &criteoRequest{
+		Slots: []criteoRequestSlot{
+			{
+				SlotID: dummySlotID,
+				ImpID:  dummySlotImpID,
+				ZoneID: &dummySlotZoneID,
+			},
+		},
+	}
+
+	bidderExtJSON, _ := json.Marshal(&openrtb_ext.ExtImpCriteo{
+		ZoneID: dummySlotZoneID,
+	})
+	impExtJSON, _ := json.Marshal(&adapters.ExtImpBidder{
+		Bidder: bidderExtJSON,
+	})
+	incomingRequest := &openrtb2.BidRequest{
+		Imp: []openrtb2.Imp{
+			{
+				ID:  dummySlotImpID,
+				Ext: impExtJSON,
+			},
+		},
+	}
+
+	// Execute:
+	slots, err := newCriteoRequestSlots(fakeSlotIDGenerator, incomingRequest.Imp)
+	result := &criteoRequest{
+		Slots: slots,
+	}
+
+	// Verify:
+	if err != nil {
+		t.Errorf("newCriteoRequestSlots has errors: %s", err)
+	}
+
+	if len(expectedCriteoRequest.Slots) != len(result.Slots) ||
+		!reflect.DeepEqual(expectedCriteoRequest.Slots[0], result.Slots[0]) {
+		actualResultJSON, _ := json.Marshal(result)
+		expectedResultJSON, _ := json.Marshal(expectedCriteoRequest)
+		t.Errorf("newCriteoRequest was incorrect, got '%s', want '%s'.", actualResultJSON, expectedResultJSON)
+	}
+}
+
+func TestGetRequestMultipleSlots(t *testing.T) {
+	// Setup:
+	dummySlots := []struct {
+		ID     string
+		ZoneID int64
+	}{
+		{"fake-imp-id-1", 1},
+		{"fake-imp-id-2", 2},
+		{"fake-imp-id-3", 3},
+		{"fake-imp-id-4", 4},
+		{"fake-imp-id-5", 5},
+	}
+
+	incomingRequest := &openrtb2.BidRequest{
+		Imp: make([]openrtb2.Imp, len(dummySlots)),
+	}
+	slots := make([]criteoRequestSlot, len(dummySlots))
+
+	for i := range dummySlots {
+		// Build expected slots
+		slots[i] = criteoRequestSlot{
+			ImpID:  dummySlots[i].ID,
+			ZoneID: &dummySlots[i].ZoneID,
+		}
+
+		// Build incoming request imps
+		bidderExtJSON, _ := json.Marshal(&openrtb_ext.ExtImpCriteo{
+			ZoneID: dummySlots[i].ZoneID,
+		})
+		impExtJSON, _ := json.Marshal(&adapters.ExtImpBidder{
+			Bidder: bidderExtJSON,
+		})
+		incomingRequest.Imp[i] = openrtb2.Imp{
+			ID:  dummySlots[i].ID,
+			Ext: impExtJSON,
+		}
+	}
+
+	expectedCriteoRequestSlots, err := newCriteoRequestSlots(newFakeGuidGenerator(""), incomingRequest.Imp)
+	expectedCriteoRequest := &criteoRequest{
+		Slots: expectedCriteoRequestSlots,
+	}
+
+	// Execute:
+	slotsResult, err := newCriteoRequestSlots(newFakeGuidGenerator(""), incomingRequest.Imp)
+	result := &criteoRequest{
+		Slots: slotsResult,
+	}
+
+	// Verify:
+	if err != nil {
+		t.Errorf("newCriteoRequestSlots has errors: %s", err)
+	}
+
+	if len(expectedCriteoRequest.Slots) != len(result.Slots) ||
+		!reflect.DeepEqual(expectedCriteoRequest.Slots, result.Slots) {
+		actualResultJSON, _ := json.Marshal(result)
+		expectedResultJSON, _ := json.Marshal(expectedCriteoRequest)
+		t.Errorf("newCriteoRequest was incorrect, got '%s', want '%s'.", actualResultJSON, expectedResultJSON)
+	}
+}
diff --git a/adapters/criteo/params_test.go b/adapters/criteo/params_test.go
new file mode 100644
index 00000000..73ace617
--- /dev/null
+++ b/adapters/criteo/params_test.go
@@ -0,0 +1,63 @@
+package criteo
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+// This file actually intends to test static/bidder-params/criteo.json
+//
+// These also validate the format of the external API: request.imp[i].ext.criteo
+
+// TestValidParams makes sure that the criteo 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.BidderCriteo, json.RawMessage(validParam)); err != nil {
+			t.Errorf("Schema rejected criteo params: %s", validParam)
+		}
+	}
+}
+
+// TestInvalidParams makes sure that the criteo 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.BidderCriteo, json.RawMessage(invalidParam)); err == nil {
+			t.Errorf("Schema allowed unexpected params: %s", invalidParam)
+		}
+	}
+}
+
+var validParams = []string{
+	`{"zoneid": 123456}`,
+	`{"networkid": 78910}`,
+	`{"zoneid": 123456, "networkid": 78910}`,
+	`{"zoneid": 0, "networkid": 0}`,
+}
+
+var invalidParams = []string{
+	``,
+	`null`,
+	`true`,
+	`5`,
+	`4.2`,
+	`[]`,
+	`{}`,
+	`{"zoneid": -123}`,
+	`{"networkid": -321}`,
+	`{"zoneid": -123, "networkid": -321}`,
+	`{"zoneid": -1}`,
+	`{"networkid": -1}`,
+	`{"zoneid": -1, "networkid": -1}`,
+}
diff --git a/adapters/criteo/usersync_test.go b/adapters/criteo/usersync_test.go
new file mode 100644
index 00000000..9010d8bb
--- /dev/null
+++ b/adapters/criteo/usersync_test.go
@@ -0,0 +1,26 @@
+package criteo
+
+import (
+	"testing"
+	"text/template"
+
+	"github.com/prebid/prebid-server/adapters"
+	"github.com/prebid/prebid-server/privacy"
+	"github.com/prebid/prebid-server/privacy/gdpr"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestCriteoSyncer(t *testing.T) {
+	url := "//prebidserver/getuid?https%3A%2F%2Fprebidserver%2Fpbs%2Fv1%2Fsetuid%3Fbidder%3Dcriteo%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID"
+	temp := template.Must(template.New("sync-template").Parse(url))
+	syncer := NewCriteoSyncer(temp)
+	info, err := syncer.GetUsersyncInfo(privacy.Policies{
+		GDPR: gdpr.Policy{
+			Signal: "1",
+		},
+	})
+	assert.NoError(t, err)
+	assert.EqualValues(t, adapters.SyncTypeRedirect, info.Type)
+	assert.Equal(t, false, info.SupportCORS)
+	assert.Equal(t, "//prebidserver/getuid?https%3A%2F%2Fprebidserver%2Fpbs%2Fv1%2Fsetuid%3Fbidder%3Dcriteo%26gdpr%3D1%26gdpr_consent%3D%26uid%3D%24UID", info.URL)
+}
diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go
index 8cc5b5e9..7c01cd84 100644
--- a/exchange/bidder_test.go
+++ b/exchange/bidder_test.go
@@ -71,7 +71,6 @@ func TestSingleBidder(t *testing.T) {
 	ctx = context.WithValue(ctx, DebugContextKey, true)
 
 	for _, test := range testCases {
-
 		mockBidderResponse := &adapters.BidderResponse{
 			Bids: []*adapters.TypedBid{
 				{
@@ -142,6 +141,48 @@ func TestSingleBidder(t *testing.T) {
 	}
 }
 
+func TestRequestBidRemovesSensitiveHeaders(t *testing.T) {
+	server := httptest.NewServer(mockHandler(200, "getBody", "responseJson"))
+	defer server.Close()
+
+	requestHeaders := http.Header{}
+	requestHeaders.Add("Content-Type", "application/json")
+	requestHeaders.Add("Authorization", "anySecret")
+
+	bidderImpl := &goodSingleBidder{
+		httpRequest: &adapters.RequestData{
+			Method:  "POST",
+			Uri:     server.URL,
+			Body:    []byte("requestJson"),
+			Headers: requestHeaders,
+		},
+		bidResponse: &adapters.BidderResponse{
+			Bids: []*adapters.TypedBid{},
+		},
+	}
+
+	debugInfo := &config.DebugInfo{Allow: true}
+	ctx := context.Background()
+	ctx = context.WithValue(ctx, DebugContextKey, true)
+
+	bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, debugInfo)
+	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
+	seatBid, errs := bidder.requestBid(ctx, &openrtb2.BidRequest{}, "test", 1, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true)
+
+	expectedHttpCalls := []*openrtb_ext.ExtHttpCall{
+		{
+			Uri:            server.URL,
+			RequestBody:    "requestJson",
+			RequestHeaders: map[string][]string{"Content-Type": {"application/json"}},
+			ResponseBody:   "responseJson",
+			Status:         200,
+		},
+	}
+
+	assert.Empty(t, errs)
+	assert.ElementsMatch(t, seatBid.httpCalls, expectedHttpCalls)
+}
+
 // TestMultiBidder makes sure all the requests get sent, and the responses processed.
 // Because this is done in parallel, it should be run under the race detector.
 func TestMultiBidder(t *testing.T) {
@@ -891,86 +932,160 @@ func TestMultiCurrencies_RequestCurrencyPick(t *testing.T) {
 	}
 }
 
-// TestBadResponseLogging makes sure that openrtb_ext works properly on malformed HTTP requests.
-func TestBadRequestLogging(t *testing.T) {
-	info := &httpCallInfo{
-		err: errors.New("Bad request"),
-	}
-	ext := makeExt(info)
-	if ext.Uri != "" {
-		t.Errorf("The URI should be empty. Got %s", ext.Uri)
-	}
-	if ext.RequestBody != "" {
-		t.Errorf("The request body should be empty. Got %s", ext.RequestBody)
-	}
-	if ext.ResponseBody != "" {
-		t.Errorf("The response body should be empty. Got %s", ext.ResponseBody)
-	}
-	if ext.Status != 0 {
-		t.Errorf("The Status code should be 0. Got %d", ext.Status)
-	}
-	if len(ext.RequestHeaders) > 0 {
-		t.Errorf("The request headers should be empty. Got %s", ext.RequestHeaders)
-	}
-}
-
-// TestBadResponseLogging makes sure that openrtb_ext works properly if we don't get a sensible HTTP response.
-func TestBadResponseLogging(t *testing.T) {
-	info := &httpCallInfo{
-		request: &adapters.RequestData{
-			Uri:  "test.com",
-			Body: []byte("request body"),
-			Headers: http.Header{
-				"header-1": []string{"value-1"},
+func TestMakeExt(t *testing.T) {
+	testCases := []struct {
+		description string
+		given       *httpCallInfo
+		expected    *openrtb_ext.ExtHttpCall
+	}{
+		{
+			description: "Nil",
+			given:       nil,
+			expected:    &openrtb_ext.ExtHttpCall{},
+		},
+		{
+			description: "Empty",
+			given: &httpCallInfo{
+				err:      nil,
+				response: nil,
+				request:  nil,
 			},
+			expected: &openrtb_ext.ExtHttpCall{},
+		},
+		{
+			description: "Request & Response - No Error",
+			given: &httpCallInfo{
+				err: nil,
+				request: &adapters.RequestData{
+					Uri:     "requestUri",
+					Body:    []byte("requestBody"),
+					Headers: makeHeader(map[string][]string{"Key1": {"value1", "value2"}}),
+				},
+				response: &adapters.ResponseData{
+					Body:       []byte("responseBody"),
+					StatusCode: 999,
+				},
+			},
+			expected: &openrtb_ext.ExtHttpCall{
+				Uri:            "requestUri",
+				RequestBody:    "requestBody",
+				RequestHeaders: map[string][]string{"Key1": {"value1", "value2"}},
+				ResponseBody:   "responseBody",
+				Status:         999,
+			},
+		},
+		{
+			description: "Request & Response - Error",
+			given: &httpCallInfo{
+				err: errors.New("error"),
+				request: &adapters.RequestData{
+					Uri:     "requestUri",
+					Body:    []byte("requestBody"),
+					Headers: makeHeader(map[string][]string{"Key1": {"value1", "value2"}}),
+				},
+				response: &adapters.ResponseData{
+					Body:       []byte("responseBody"),
+					StatusCode: 999,
+				},
+			},
+			expected: &openrtb_ext.ExtHttpCall{
+				Uri:            "requestUri",
+				RequestBody:    "requestBody",
+				RequestHeaders: map[string][]string{"Key1": {"value1", "value2"}},
+			},
+		},
+		{
+			description: "Request Only",
+			given: &httpCallInfo{
+				err: nil,
+				request: &adapters.RequestData{
+					Uri:     "requestUri",
+					Body:    []byte("requestBody"),
+					Headers: makeHeader(map[string][]string{"Key1": {"value1", "value2"}}),
+				},
+				response: nil,
+			},
+			expected: &openrtb_ext.ExtHttpCall{
+				Uri:            "requestUri",
+				RequestBody:    "requestBody",
+				RequestHeaders: map[string][]string{"Key1": {"value1", "value2"}},
+			},
+		}, {
+			description: "Response Only",
+			given: &httpCallInfo{
+				err: nil,
+				response: &adapters.ResponseData{
+					Body:       []byte("responseBody"),
+					StatusCode: 999,
+				},
+			},
+			expected: &openrtb_ext.ExtHttpCall{},
 		},
-		err: errors.New("Bad response"),
-	}
-	ext := makeExt(info)
-	if ext.Uri != info.request.Uri {
-		t.Errorf("The URI should be test.com. Got %s", ext.Uri)
-	}
-	if ext.RequestBody != string(info.request.Body) {
-		t.Errorf("The request body should be empty. Got %s", ext.RequestBody)
-	}
-	if ext.ResponseBody != "" {
-		t.Errorf("The response body should be empty. Got %s", ext.ResponseBody)
 	}
-	if ext.Status != 0 {
-		t.Errorf("The Status code should be 0. Got %d", ext.Status)
+
+	for _, test := range testCases {
+		result := makeExt(test.given)
+		assert.Equal(t, test.expected, result, test.description)
 	}
-	assert.Equal(t, info.request.Headers, http.Header(ext.RequestHeaders), "The request headers should be \"header-1:value-1\"")
 }
 
-// TestSuccessfulResponseLogging makes sure that openrtb_ext works properly if the HTTP request is successful.
-func TestSuccessfulResponseLogging(t *testing.T) {
-	info := &httpCallInfo{
-		request: &adapters.RequestData{
-			Uri:  "test.com",
-			Body: []byte("request body"),
-			Headers: http.Header{
-				"header-1": []string{"value-1", "value-2"},
-			},
+func TestRemoveSensitiveHeaders(t *testing.T) {
+	testCases := []struct {
+		description string
+		given       http.Header
+		expected    http.Header
+	}{
+		{
+			description: "Nil",
+			given:       nil,
+			expected:    nil,
 		},
-		response: &adapters.ResponseData{
-			StatusCode: 200,
-			Body:       []byte("response body"),
+		{
+			description: "Empty",
+			given:       http.Header{},
+			expected:    map[string][]string{},
+		},
+		{
+			description: "One",
+			given:       makeHeader(map[string][]string{"Key1": {"value1"}}),
+			expected:    makeHeader(map[string][]string{"Key1": {"value1"}}),
+		},
+		{
+			description: "Many",
+			given:       makeHeader(map[string][]string{"Key1": {"value1"}, "Key2": {"value2a", "value2b"}}),
+			expected:    makeHeader(map[string][]string{"Key1": {"value1"}, "Key2": {"value2a", "value2b"}}),
+		},
+		{
+			description: "Authorization Header Omitted",
+			given:       makeHeader(map[string][]string{"authorization": {"secret"}}),
+			expected:    http.Header{},
+		},
+		{
+			description: "Authorization Header Omitted - Case Insensitive",
+			given:       makeHeader(map[string][]string{"AuThOrIzAtIoN": {"secret"}}),
+			expected:    http.Header{},
+		},
+		{
+			description: "Authorization Header Omitted + Other Keys",
+			given:       makeHeader(map[string][]string{"authorization": {"secret"}, "Key1": {"value1"}}),
+			expected:    makeHeader(map[string][]string{"Key1": {"value1"}}),
 		},
 	}
-	ext := makeExt(info)
-	if ext.Uri != info.request.Uri {
-		t.Errorf("The URI should be test.com. Got %s", ext.Uri)
-	}
-	if ext.RequestBody != string(info.request.Body) {
-		t.Errorf("The request body should be \"request body\". Got %s", ext.RequestBody)
-	}
-	if ext.ResponseBody != string(info.response.Body) {
-		t.Errorf("The response body should be \"response body\". Got %s", ext.ResponseBody)
+
+	for _, test := range testCases {
+		removeSensitiveHeaders(test.given)
+		assert.Equal(t, test.expected, test.given, test.description)
 	}
-	if ext.Status != info.response.StatusCode {
-		t.Errorf("The Status code should be 0. Got %d", ext.Status)
+}
+
+func makeHeader(v map[string][]string) http.Header {
+	h := http.Header{}
+	for key, values := range v {
+		for _, value := range values {
+			h.Add(key, value)
+		}
 	}
-	assert.Equal(t, info.request.Headers, http.Header(ext.RequestHeaders), "The request headers should be \"%s\". Got %s", info.request.Headers, ext.RequestHeaders)
+	return h
 }
 
 func TestMobileNativeTypes(t *testing.T) {
diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go
index 5b2e8046..a389a007 100755
--- a/usersync/usersyncers/syncer_test.go
+++ b/usersync/usersyncers/syncer_test.go
@@ -41,6 +41,7 @@ func TestNewSyncerMap(t *testing.T) {
 			string(openrtb_ext.BidderConsumable):       syncConfig,
 			string(openrtb_ext.BidderConversant):       syncConfig,
 			string(openrtb_ext.BidderCpmstar):          syncConfig,
+			string(openrtb_ext.BidderCriteo):           syncConfig,
 			string(openrtb_ext.BidderDatablocks):       syncConfig,
 			string(openrtb_ext.BidderDmx):              syncConfig,
 			string(openrtb_ext.BidderDeepintent):       syncConfig,
@@ -106,6 +107,7 @@ func TestNewSyncerMap(t *testing.T) {
 		openrtb_ext.BidderAdot:         true,
 		openrtb_ext.BidderAdprime:      true,
 		openrtb_ext.BidderApplogy:      true,
+		openrtb_ext.BidderBidmachine:   true,
 		openrtb_ext.BidderEpom:         true,
 		openrtb_ext.BidderDecenterAds:  true,
 		openrtb_ext.BidderInMobi:       true,