diff --git a/adapters/thetradedesk/params_test.go b/adapters/thetradedesk/params_test.go
new file mode 100644
index 00000000000..f18daed3d56
--- /dev/null
+++ b/adapters/thetradedesk/params_test.go
@@ -0,0 +1,53 @@
+package thetradedesk
+
+import (
+ "encoding/json"
+ "testing"
+
+ "github.com/prebid/prebid-server/v2/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.BidderTheTradeDesk, json.RawMessage(validParam)); err != nil {
+ t.Errorf("Schema rejected TheTradeDesk params: %s", validParam)
+ }
+ }
+}
+
+// TestInvalidParams makes sure that the TheTradeDesk 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.BidderTheTradeDesk, json.RawMessage(invalidParam)); err == nil {
+ t.Errorf("Schema allowed unexpected params: %s", invalidParam)
+ }
+ }
+}
+
+var validParams = []string{
+ `{"publisherId": "123456"}`,
+ `{"publisherId": "pub-123456"}`,
+ `{"publisherId": "publisherIDAllString"}`,
+}
+
+var invalidParams = []string{
+ ``,
+ `null`,
+ `true`,
+ `5`,
+ `4.2`,
+ `[]`,
+ `{}`,
+ `{"publisherId": 123456}`,
+ `{"publisherId": 0}`,
+}
diff --git a/adapters/thetradedesk/thetradedesk.go b/adapters/thetradedesk/thetradedesk.go
new file mode 100644
index 00000000000..1ef48a70d4f
--- /dev/null
+++ b/adapters/thetradedesk/thetradedesk.go
@@ -0,0 +1,196 @@
+package thetradedesk
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "net/http"
+ "regexp"
+ "text/template"
+
+ "github.com/prebid/prebid-server/v2/adapters"
+ "github.com/prebid/prebid-server/v2/config"
+ "github.com/prebid/prebid-server/v2/macros"
+ "github.com/prebid/prebid-server/v2/openrtb_ext"
+
+ "github.com/prebid/openrtb/v20/openrtb2"
+)
+
+const PREBID_INTEGRATION_TYPE = "1"
+
+type adapter struct {
+ bidderEndpoint string
+}
+
+type ExtImpBidderTheTradeDesk struct {
+ adapters.ExtImpBidder
+}
+
+func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+ pubID, err := getPublisherId(request.Imp)
+
+ if err != nil {
+ return nil, []error{err}
+ }
+
+ modifiedImps := make([]openrtb2.Imp, 0, len(request.Imp))
+
+ for _, imp := range request.Imp {
+
+ if imp.Banner != nil {
+ if len(imp.Banner.Format) > 0 {
+ firstFormat := imp.Banner.Format[0]
+ bannerCopy := *imp.Banner
+ bannerCopy.H = &firstFormat.H
+ bannerCopy.W = &firstFormat.W
+ imp.Banner = &bannerCopy
+
+ }
+ }
+
+ modifiedImps = append(modifiedImps, imp)
+ }
+
+ request.Imp = modifiedImps
+
+ if request.Site != nil {
+ siteCopy := *request.Site
+ if siteCopy.Publisher != nil {
+ publisherCopy := *siteCopy.Publisher
+ if pubID != "" {
+ publisherCopy.ID = pubID
+ }
+ siteCopy.Publisher = &publisherCopy
+ } else {
+ siteCopy.Publisher = &openrtb2.Publisher{ID: pubID}
+ }
+ request.Site = &siteCopy
+ } else if request.App != nil {
+ appCopy := *request.App
+ if appCopy.Publisher != nil {
+ publisherCopy := *appCopy.Publisher
+ if pubID != "" {
+ publisherCopy.ID = pubID
+ }
+ appCopy.Publisher = &publisherCopy
+ } else {
+ appCopy.Publisher = &openrtb2.Publisher{ID: pubID}
+ }
+ request.App = &appCopy
+ }
+
+ errs := make([]error, 0, len(request.Imp))
+ reqJSON, err := json.Marshal(request)
+ if err != nil {
+ errs = append(errs, err)
+ return nil, errs
+ }
+
+ headers := http.Header{}
+ headers.Add("Content-Type", "application/json;charset=utf-8")
+ headers.Add("Accept", "application/json")
+ headers.Add("x-integration-type", PREBID_INTEGRATION_TYPE)
+ return []*adapters.RequestData{{
+ Method: "POST",
+ Uri: a.bidderEndpoint,
+ Body: reqJSON,
+ Headers: headers,
+ ImpIDs: openrtb_ext.GetImpIDs(request.Imp),
+ }}, errs
+}
+
+func getPublisherId(impressions []openrtb2.Imp) (string, error) {
+ for _, imp := range impressions {
+
+ var bidderExt ExtImpBidderTheTradeDesk
+ if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
+ return "", err
+ }
+
+ var ttdExt openrtb_ext.ExtImpTheTradeDesk
+ if err := json.Unmarshal(bidderExt.Bidder, &ttdExt); err != nil {
+ return "", err
+ }
+
+ if ttdExt.PublisherId != "" {
+ return ttdExt.PublisherId, nil
+ }
+ }
+ return "", nil
+}
+
+func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+ if adapters.IsResponseStatusCodeNoContent(response) {
+ return adapters.NewBidderResponse(), nil
+ }
+
+ if err := adapters.CheckResponseStatusCodeForErrors(response); err != nil {
+ return nil, []error{err}
+ }
+
+ var bidResponse openrtb2.BidResponse
+ if err := json.Unmarshal(response.Body, &bidResponse); err != nil {
+ return nil, []error{err}
+ }
+
+ bidderResponse := adapters.NewBidderResponse()
+ bidderResponse.Currency = bidResponse.Cur
+
+ for _, seatBid := range bidResponse.SeatBid {
+ for _, bid := range seatBid.Bid {
+ bid := bid
+
+ bidType, err := getBidType(bid.MType)
+
+ if err != nil {
+ return nil, []error{err}
+ }
+
+ b := &adapters.TypedBid{
+ Bid: &bid,
+ BidType: bidType,
+ }
+ bidderResponse.Bids = append(bidderResponse.Bids, b)
+ }
+ }
+
+ return bidderResponse, nil
+}
+
+func getBidType(markupType openrtb2.MarkupType) (openrtb_ext.BidType, error) {
+ switch markupType {
+ case openrtb2.MarkupBanner:
+ return openrtb_ext.BidTypeBanner, nil
+ case openrtb2.MarkupVideo:
+ return openrtb_ext.BidTypeVideo, nil
+ case openrtb2.MarkupNative:
+ return openrtb_ext.BidTypeNative, nil
+ default:
+ return "", fmt.Errorf("unsupported mtype: %d", markupType)
+ }
+}
+
+func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (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)
+ }
+
+ if len(config.ExtraAdapterInfo) > 0 {
+ isValidEndpoint, err := regexp.Match("([a-z]+)$", []byte(config.ExtraAdapterInfo))
+ if !isValidEndpoint || err != nil {
+ return nil, errors.New("ExtraAdapterInfo must be a simple string provided by TheTradeDesk")
+ }
+ }
+
+ urlParams := macros.EndpointTemplateParams{SupplyId: config.ExtraAdapterInfo}
+ bidderEndpoint, err := macros.ResolveMacros(template, urlParams)
+
+ if err != nil {
+ return nil, fmt.Errorf("unable to resolve endpoint macros: %v", err)
+ }
+
+ return &adapter{
+ bidderEndpoint: bidderEndpoint,
+ }, nil
+}
diff --git a/adapters/thetradedesk/thetradedesk_test.go b/adapters/thetradedesk/thetradedesk_test.go
new file mode 100644
index 00000000000..d53b2f860e4
--- /dev/null
+++ b/adapters/thetradedesk/thetradedesk_test.go
@@ -0,0 +1,385 @@
+package thetradedesk
+
+import (
+ "encoding/json"
+ "net/http"
+ "testing"
+
+ "github.com/prebid/openrtb/v20/openrtb2"
+ "github.com/prebid/prebid-server/v2/adapters"
+ "github.com/prebid/prebid-server/v2/adapters/adapterstest"
+ "github.com/prebid/prebid-server/v2/config"
+ "github.com/prebid/prebid-server/v2/openrtb_ext"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestEndpointTemplateMalformed(t *testing.T) {
+ _, buildErr := Builder(openrtb_ext.BidderTheTradeDesk, config.Adapter{
+ Endpoint: "{{Malformed}}"},
+ config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"})
+
+ assert.Error(t, buildErr)
+}
+
+func TestBadConfig(t *testing.T) {
+ _, buildErr := Builder(openrtb_ext.BidderTheTradeDesk, config.Adapter{
+ Endpoint: `http://it.doesnt.matter/bid`,
+ ExtraAdapterInfo: "12365217635",
+ },
+ config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"})
+
+ assert.Error(t, buildErr)
+}
+
+func TestCorrectConfig(t *testing.T) {
+ bidder, buildErr := Builder(openrtb_ext.BidderTheTradeDesk, config.Adapter{
+ Endpoint: `http://it.doesnt.matter/bid`,
+ ExtraAdapterInfo: `abcde`,
+ },
+ config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"})
+
+ assert.NoError(t, buildErr)
+ assert.NotNil(t, bidder)
+}
+
+func TestEmptyConfig(t *testing.T) {
+ bidder, buildErr := Builder(openrtb_ext.BidderTheTradeDesk, config.Adapter{
+ Endpoint: `https://direct.adsrvr.org/bid/bidder/{{.SupplyId}}`,
+ ExtraAdapterInfo: `ttd`,
+ },
+ config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"})
+
+ assert.NoError(t, buildErr)
+ assert.NotNil(t, bidder)
+}
+
+func TestJsonSamples(t *testing.T) {
+ bidder, err := Builder(
+ openrtb_ext.BidderTheTradeDesk,
+ config.Adapter{Endpoint: "https://direct.adsrvr.org/bid/bidder/{{.SupplyId}}", ExtraAdapterInfo: "ttd"},
+ config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "1"},
+ )
+ assert.Nil(t, err)
+ adapterstest.RunJSONBidderTest(t, "thetradedesktest", bidder)
+}
+
+func TestGetBidType(t *testing.T) {
+ type args struct {
+ markupType openrtb2.MarkupType
+ }
+ tests := []struct {
+ name string
+ args args
+ markupType openrtb2.MarkupType
+ expectedBidTypeId openrtb_ext.BidType
+ wantErr bool
+ }{
+ {
+ name: "banner",
+ args: args{
+ markupType: openrtb2.MarkupBanner,
+ },
+ expectedBidTypeId: openrtb_ext.BidTypeBanner,
+ wantErr: false,
+ },
+ {
+ name: "video",
+ args: args{
+ markupType: openrtb2.MarkupVideo,
+ },
+ expectedBidTypeId: openrtb_ext.BidTypeVideo,
+ wantErr: false,
+ },
+ {
+ name: "native",
+ args: args{
+ markupType: openrtb2.MarkupNative,
+ },
+ expectedBidTypeId: openrtb_ext.BidTypeNative,
+ wantErr: false,
+ },
+ {
+ name: "invalid",
+ args: args{
+ markupType: -1,
+ },
+ expectedBidTypeId: "",
+ wantErr: true,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ bidType, err := getBidType(tt.args.markupType)
+ assert.Equal(t, tt.wantErr, err != nil)
+ assert.Equal(t, tt.expectedBidTypeId, bidType)
+ })
+ }
+}
+
+func TestGetPublisherId(t *testing.T) {
+ type args struct {
+ impressions []openrtb2.Imp
+ }
+ tests := []struct {
+ name string
+ args args
+ expectedPublisherId string
+ wantErr bool
+ }{
+ {
+ name: "valid_publisher_Id",
+ args: args{
+ impressions: []openrtb2.Imp{
+ {
+ Video: &openrtb2.Video{},
+ Ext: json.RawMessage(`{"bidder":{"publisherId":"1"}}`),
+ },
+ },
+ },
+ expectedPublisherId: "1",
+ wantErr: false,
+ },
+ {
+ name: "multiple_valid_publisher_Id",
+ args: args{
+ impressions: []openrtb2.Imp{
+ {
+ Video: &openrtb2.Video{},
+ Ext: json.RawMessage(`{"bidder":{"publisherId":"1"}}`),
+ },
+ {
+ Video: &openrtb2.Video{},
+ Ext: json.RawMessage(`{"bidder":{"publisherId":"2"}}`),
+ },
+ },
+ },
+ expectedPublisherId: "1",
+ wantErr: false,
+ },
+ {
+ name: "not_publisherId_present",
+ args: args{
+ impressions: []openrtb2.Imp{
+ {
+ Video: &openrtb2.Video{},
+ Ext: json.RawMessage(`{"bidder":{}}`),
+ },
+ },
+ },
+ expectedPublisherId: "",
+ wantErr: false,
+ },
+ {
+ name: "nil_publisherId_present",
+ args: args{
+ impressions: []openrtb2.Imp{
+ {
+ Video: &openrtb2.Video{},
+ Ext: json.RawMessage(`{"bidder":{"publisherId":""}}`),
+ },
+ },
+ },
+ expectedPublisherId: "",
+ wantErr: false,
+ },
+ {
+ name: "no_impressions",
+ args: args{
+ impressions: []openrtb2.Imp{},
+ },
+ expectedPublisherId: "",
+ wantErr: false,
+ },
+ {
+ name: "invalid_bidder_object",
+ args: args{
+ impressions: []openrtb2.Imp{
+ {
+ Video: &openrtb2.Video{},
+ Ext: json.RawMessage(`{"bidder":{"doesnotexistprop":""}}`),
+ },
+ },
+ },
+ expectedPublisherId: "",
+ wantErr: false,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ publisherId, err := getPublisherId(tt.args.impressions)
+ assert.Equal(t, tt.wantErr, err != nil)
+ assert.Equal(t, tt.expectedPublisherId, publisherId)
+ })
+ }
+}
+
+func TestTheTradeDeskAdapter_MakeRequests(t *testing.T) {
+ type fields struct {
+ URI string
+ }
+ type args struct {
+ request *openrtb2.BidRequest
+ reqInfo *adapters.ExtraRequestInfo
+ }
+ tests := []struct {
+ name string
+ fields fields
+ args args
+ expectedReqData []*adapters.RequestData
+ wantErr bool
+ }{
+ {
+ name: "invalid_bidderparams",
+ args: args{
+ request: &openrtb2.BidRequest{Ext: json.RawMessage(`{"prebid":{"bidderparams":{:"123"}}}`)},
+ },
+ wantErr: true,
+ },
+ {
+ name: "request_with_App",
+ args: args{
+ request: &openrtb2.BidRequest{
+ App: &openrtb2.App{},
+ Ext: json.RawMessage(`{"prebid":{"bidderparams":{"wrapper":"123"}}}`),
+ },
+ },
+ wantErr: false,
+ },
+ {
+ name: "request_with_App_and_publisher",
+ args: args{
+ request: &openrtb2.BidRequest{
+ App: &openrtb2.App{Publisher: &openrtb2.Publisher{}},
+ Ext: json.RawMessage(`{"prebid":{"bidderparams":{"wrapper":"123"}}}`),
+ },
+ },
+ wantErr: false,
+ },
+ {
+ name: "request_with_Site",
+ args: args{
+ request: &openrtb2.BidRequest{
+ Site: &openrtb2.Site{},
+ Ext: json.RawMessage(`{"prebid":{"bidderparams":{"wrapper":"123"}}}`),
+ },
+ },
+ wantErr: false,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ a := &adapter{
+ bidderEndpoint: tt.fields.URI,
+ }
+ gotReqData, gotErr := a.MakeRequests(tt.args.request, tt.args.reqInfo)
+ assert.Equal(t, tt.wantErr, len(gotErr) != 0)
+ if tt.wantErr == false {
+ assert.NotNil(t, gotReqData)
+ }
+ })
+ }
+}
+
+func TestTheTradeDeskAdapter_MakeBids(t *testing.T) {
+ type fields struct {
+ URI string
+ }
+ type args struct {
+ internalRequest *openrtb2.BidRequest
+ externalRequest *adapters.RequestData
+ response *adapters.ResponseData
+ }
+ tests := []struct {
+ name string
+ fields fields
+ args args
+ wantErr []error
+ wantResp *adapters.BidderResponse
+ }{
+ {
+ name: "happy_path_valid_response_with_all_bid_params",
+ args: args{
+ response: &adapters.ResponseData{
+ StatusCode: http.StatusOK,
+ Body: []byte(`{"id": "test-request-id", "seatbid":[{"seat": "958", "bid":[{"mtype": 1, "id": "7706636740145184841", "impid": "test-imp-id", "price": 0.500000, "adid": "29681110", "adm": "some-test-ad", "adomain":["ttd.com"], "crid": "29681110", "h": 250, "w": 300, "dealid": "testdeal", "ext":{"dspid": 6, "deal_channel": 1, "prebiddealpriority": 1}}]}], "bidid": "5778926625248726496", "cur": "USD"}`),
+ },
+ },
+ wantErr: nil,
+ wantResp: &adapters.BidderResponse{
+ Bids: []*adapters.TypedBid{
+ {
+ Bid: &openrtb2.Bid{
+ ID: "7706636740145184841",
+ ImpID: "test-imp-id",
+ Price: 0.500000,
+ AdID: "29681110",
+ AdM: "some-test-ad",
+ ADomain: []string{"ttd.com"},
+ CrID: "29681110",
+ H: 250,
+ W: 300,
+ DealID: "testdeal",
+ Ext: json.RawMessage(`{"dspid": 6, "deal_channel": 1, "prebiddealpriority": 1}`),
+ MType: openrtb2.MarkupBanner,
+ },
+ BidType: openrtb_ext.BidTypeBanner,
+ },
+ },
+ Currency: "USD",
+ },
+ },
+ {
+ name: "ignore_invalid_prebiddealpriority",
+ args: args{
+ response: &adapters.ResponseData{
+ StatusCode: http.StatusOK,
+ Body: []byte(`{"id": "test-request-id", "seatbid":[{"seat": "958", "bid":[{"mtype": 2, "id": "7706636740145184841", "impid": "test-imp-id", "price": 0.500000, "adid": "29681110", "adm": "some-test-ad", "adomain":["ttd.com"], "crid": "29681110", "h": 250, "w": 300, "dealid": "testdeal", "ext":{"dspid": 6, "deal_channel": 1, "prebiddealpriority": -1}}]}], "bidid": "5778926625248726496", "cur": "USD"}`),
+ },
+ },
+ wantErr: nil,
+ wantResp: &adapters.BidderResponse{
+ Bids: []*adapters.TypedBid{
+ {
+ Bid: &openrtb2.Bid{
+ ID: "7706636740145184841",
+ ImpID: "test-imp-id",
+ Price: 0.500000,
+ AdID: "29681110",
+ AdM: "some-test-ad",
+ ADomain: []string{"ttd.com"},
+ CrID: "29681110",
+ H: 250,
+ W: 300,
+ DealID: "testdeal",
+ Ext: json.RawMessage(`{"dspid": 6, "deal_channel": 1, "prebiddealpriority": -1}`),
+ MType: openrtb2.MarkupVideo,
+ },
+ BidType: openrtb_ext.BidTypeVideo,
+ },
+ },
+ Currency: "USD",
+ },
+ },
+ {
+ name: "no_content_response",
+ args: args{
+ response: &adapters.ResponseData{
+ StatusCode: http.StatusNoContent,
+ Body: nil,
+ },
+ },
+ wantErr: nil,
+ wantResp: adapters.NewBidderResponse(),
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ a := &adapter{
+ bidderEndpoint: tt.fields.URI,
+ }
+ gotResp, gotErr := a.MakeBids(tt.args.internalRequest, tt.args.externalRequest, tt.args.response)
+ assert.Equal(t, tt.wantErr, gotErr, gotErr)
+ assert.Equal(t, tt.wantResp, gotResp)
+ })
+ }
+}
diff --git a/adapters/thetradedesk/thetradedesktest/exemplary/simple-banner-inapp.json b/adapters/thetradedesk/thetradedesktest/exemplary/simple-banner-inapp.json
new file mode 100644
index 00000000000..e10445382c8
--- /dev/null
+++ b/adapters/thetradedesk/thetradedesktest/exemplary/simple-banner-inapp.json
@@ -0,0 +1,144 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "app": {
+ "bundle": "test.app.bundle",
+ "publisher": {
+ "id": "this_id_will_be_replaced"
+ }
+ },
+ "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
+ }
+ ],
+ "w": 100,
+ "h": 150
+ },
+ "ext": {
+ "bidder": {
+ "publisherId": "123456"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://direct.adsrvr.org/bid/bidder/ttd",
+ "headers": {
+ "Content-Type": [
+ "application/json;charset=utf-8"
+ ],
+ "Accept": [
+ "application/json"
+ ],
+ "X-Integration-Type": ["1"]
+ },
+ "body": {
+ "id": "test-request-id",
+ "app": {
+ "bundle": "test.app.bundle",
+ "publisher": {
+ "id": "123456"
+ }
+ },
+ "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
+ }
+ ],
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "bidder": {
+ "publisherId": "123456"
+ }
+ }
+ }
+ ]
+ },
+ "impIDs":["test-imp-id"]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "currency": "USD",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "test-slot-id",
+ "impid": "test-imp-id",
+ "price": 0.1,
+ "crid": "creative-123",
+ "adm": "",
+ "w": 300,
+ "h": 250,
+ "ext": {
+ "prebid": {
+ "type": "banner",
+ "networkName": "TheTradeDesk"
+ }
+ },
+ "mtype": 1
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "test-slot-id",
+ "impid": "test-imp-id",
+ "price": 0.1,
+ "crid": "creative-123",
+ "adm": "",
+ "w": 300,
+ "h": 250,
+ "ext": {
+ "prebid": {
+ "type": "banner",
+ "networkName": "TheTradeDesk"
+ }
+ },
+ "mtype": 1
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
+
diff --git a/adapters/thetradedesk/thetradedesktest/exemplary/simple-banner-multiple-bids-and-formats.json b/adapters/thetradedesk/thetradedesktest/exemplary/simple-banner-multiple-bids-and-formats.json
new file mode 100644
index 00000000000..0180d48dde2
--- /dev/null
+++ b/adapters/thetradedesk/thetradedesktest/exemplary/simple-banner-multiple-bids-and-formats.json
@@ -0,0 +1,231 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id-multiple-bids",
+ "site": {
+ "id": "site-id",
+ "page": "ttd.com",
+ "publisher": {
+ "id": "123456"
+ }
+ },
+ "device": {
+ "os": "android",
+ "ip": "91.199.242.236",
+ "ua": "random user agent"
+ },
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ },
+ {
+ "w": 500,
+ "h": 300
+ }
+ ],
+ "w": 55,
+ "h": 33
+ },
+ "ext": {
+ "bidder": {
+ "publisherId": "123456"
+ }
+ }
+ },
+ {
+ "id": "test-imp-id2",
+ "banner": {
+ "format": [
+ {
+ "w": 900,
+ "h": 450
+ },
+ {
+ "w": 500,
+ "h": 300
+ }
+ ],
+ "w": 88,
+ "h": 99
+ },
+ "ext": {
+ "bidder": {
+ "publisherId": "123456"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://direct.adsrvr.org/bid/bidder/ttd",
+ "headers": {
+ "Content-Type": [
+ "application/json;charset=utf-8"
+ ],
+ "Accept": [
+ "application/json"
+ ],
+ "X-Integration-Type": ["1"]
+ },
+ "body": {
+ "id": "test-request-id-multiple-bids",
+ "site": {
+ "id": "site-id",
+ "page": "ttd.com",
+ "publisher": {
+ "id": "123456"
+ }
+ },
+ "device": {
+ "os": "android",
+ "ip": "91.199.242.236",
+ "ua": "random user agent"
+ },
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ },
+ {
+ "w": 500,
+ "h": 300
+ }
+ ],
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "bidder": {
+ "publisherId": "123456"
+ }
+ }
+ },
+ {
+ "id": "test-imp-id2",
+ "banner": {
+ "format": [
+ {
+ "w": 900,
+ "h": 450
+ },
+ {
+ "w": 500,
+ "h": 300
+ }
+ ],
+ "w": 900,
+ "h": 450
+ },
+ "ext": {
+ "bidder": {
+ "publisherId": "123456"
+ }
+ }
+ }
+ ]
+ },
+ "impIDs":["test-imp-id","test-imp-id2"]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "currency": "USD",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "test-slot-id",
+ "impid": "test-imp-id",
+ "price": 0.1,
+ "crid": "creative-123",
+ "adm": "",
+ "w": 300,
+ "h": 250,
+ "ext": {
+ "prebid": {
+ "type": "banner"
+ }
+ },
+ "mtype": 1
+ }
+ ]
+ },
+ {
+ "bid": [
+ {
+ "id": "test-slot-id2",
+ "impid": "test-imp-id2",
+ "price": 0.5,
+ "crid": "creative-123",
+ "adm": "",
+ "w": 900,
+ "h": 450,
+ "ext": {
+ "prebid": {
+ "type": "banner"
+ }
+ },
+ "mtype": 1
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "test-slot-id",
+ "impid": "test-imp-id",
+ "price": 0.1,
+ "crid": "creative-123",
+ "adm": "",
+ "w": 300,
+ "h": 250,
+ "ext": {
+ "prebid": {
+ "type": "banner"
+ }
+ },
+ "mtype": 1
+ },
+ "type": "banner"
+ },
+ {
+ "bid": {
+ "id": "test-slot-id2",
+ "impid": "test-imp-id2",
+ "price": 0.5,
+ "crid": "creative-123",
+ "adm": "",
+ "w": 900,
+ "h": 450,
+ "ext": {
+ "prebid": {
+ "type": "banner"
+ }
+ },
+ "mtype": 1
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
+
diff --git a/adapters/thetradedesk/thetradedesktest/exemplary/simple-banner-multiple-bids.json b/adapters/thetradedesk/thetradedesktest/exemplary/simple-banner-multiple-bids.json
new file mode 100644
index 00000000000..4d46fa6907b
--- /dev/null
+++ b/adapters/thetradedesk/thetradedesktest/exemplary/simple-banner-multiple-bids.json
@@ -0,0 +1,215 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id-multiple-bids",
+ "site": {
+ "id": "site-id",
+ "page": "ttd.com",
+ "publisher": {
+ "id": "123456"
+ }
+ },
+ "device": {
+ "os": "android",
+ "ip": "91.199.242.236",
+ "ua": "random user agent"
+ },
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ],
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "bidder": {
+ "publisherId": "123456"
+ }
+ }
+ },
+ {
+ "id": "test-imp-id2",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ],
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "bidder": {
+ "publisherId": "123456"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://direct.adsrvr.org/bid/bidder/ttd",
+ "headers": {
+ "Content-Type": [
+ "application/json;charset=utf-8"
+ ],
+ "Accept": [
+ "application/json"
+ ],
+ "X-Integration-Type": ["1"]
+ },
+ "body": {
+ "id": "test-request-id-multiple-bids",
+ "site": {
+ "id": "site-id",
+ "page": "ttd.com",
+ "publisher": {
+ "id": "123456"
+ }
+ },
+ "device": {
+ "os": "android",
+ "ip": "91.199.242.236",
+ "ua": "random user agent"
+ },
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ],
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "bidder": {
+ "publisherId": "123456"
+ }
+ }
+ },
+ {
+ "id": "test-imp-id2",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ],
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "bidder": {
+ "publisherId": "123456"
+ }
+ }
+ }
+ ]
+ },
+ "impIDs":["test-imp-id","test-imp-id2"]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "currency": "USD",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "test-slot-id",
+ "impid": "test-imp-id",
+ "price": 0.1,
+ "crid": "creative-123",
+ "adm": "",
+ "w": 300,
+ "h": 250,
+ "ext": {
+ "prebid": {
+ "type": "banner"
+ }
+ },
+ "mtype": 1
+ }
+ ]
+ },
+ {
+ "bid": [
+ {
+ "id": "test-slot-id2",
+ "impid": "test-imp-id2",
+ "price": 0.5,
+ "crid": "creative-123",
+ "adm": "",
+ "w": 300,
+ "h": 250,
+ "ext": {
+ "prebid": {
+ "type": "banner"
+ }
+ },
+ "mtype": 1
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "test-slot-id",
+ "impid": "test-imp-id",
+ "price": 0.1,
+ "crid": "creative-123",
+ "adm": "",
+ "w": 300,
+ "h": 250,
+ "ext": {
+ "prebid": {
+ "type": "banner"
+ }
+ },
+ "mtype": 1
+ },
+ "type": "banner"
+ },
+ {
+ "bid": {
+ "id": "test-slot-id2",
+ "impid": "test-imp-id2",
+ "price": 0.5,
+ "crid": "creative-123",
+ "adm": "",
+ "w": 300,
+ "h": 250,
+ "ext": {
+ "prebid": {
+ "type": "banner"
+ }
+ },
+ "mtype": 1
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
+
diff --git a/adapters/thetradedesk/thetradedesktest/exemplary/simple-banner.json b/adapters/thetradedesk/thetradedesktest/exemplary/simple-banner.json
new file mode 100644
index 00000000000..4ac6acb0556
--- /dev/null
+++ b/adapters/thetradedesk/thetradedesktest/exemplary/simple-banner.json
@@ -0,0 +1,142 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "site": {
+ "id": "site-id",
+ "page": "ttd.com",
+ "publisher": {
+ "id": "123456"
+ }
+ },
+ "device": {
+ "os": "android",
+ "ip": "91.199.242.236",
+ "ua": "random user agent"
+ },
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ],
+ "w": 100,
+ "h": 150
+ },
+ "ext": {
+ "bidder": {
+ "publisherId": "123456"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://direct.adsrvr.org/bid/bidder/ttd",
+ "headers": {
+ "Content-Type": [
+ "application/json;charset=utf-8"
+ ],
+ "Accept": [
+ "application/json"
+ ],
+ "X-Integration-Type": ["1"]
+ },
+ "body": {
+ "id": "test-request-id",
+ "site": {
+ "id": "site-id",
+ "page": "ttd.com",
+ "publisher": {
+ "id": "123456"
+ }
+ },
+ "device": {
+ "os": "android",
+ "ip": "91.199.242.236",
+ "ua": "random user agent"
+ },
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ],
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "bidder": {
+ "publisherId": "123456"
+ }
+ }
+ }
+ ]
+ },
+ "impIDs":["test-imp-id"]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "currency": "USD",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "test-slot-id",
+ "impid": "test-imp-id",
+ "price": 0.1,
+ "crid": "creative-123",
+ "adm": "",
+ "w": 300,
+ "h": 250,
+ "ext": {
+ "prebid": {
+ "type": "banner"
+ }
+ },
+ "mtype": 1
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "test-slot-id",
+ "impid": "test-imp-id",
+ "price": 0.1,
+ "crid": "creative-123",
+ "adm": "",
+ "w": 300,
+ "h": 250,
+ "ext": {
+ "prebid": {
+ "type": "banner"
+ }
+ },
+ "mtype": 1
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
+
diff --git a/adapters/thetradedesk/thetradedesktest/exemplary/simple-empty-publisherId.json b/adapters/thetradedesk/thetradedesktest/exemplary/simple-empty-publisherId.json
new file mode 100644
index 00000000000..ef6f6695553
--- /dev/null
+++ b/adapters/thetradedesk/thetradedesktest/exemplary/simple-empty-publisherId.json
@@ -0,0 +1,142 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "site": {
+ "id": "site-id",
+ "page": "ttd.com",
+ "publisher": {
+ "id": "did_not_override"
+ }
+ },
+ "device": {
+ "os": "android",
+ "ip": "91.199.242.236",
+ "ua": "random user agent"
+ },
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ],
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "bidder": {
+ "publisherId": ""
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://direct.adsrvr.org/bid/bidder/ttd",
+ "headers": {
+ "Content-Type": [
+ "application/json;charset=utf-8"
+ ],
+ "Accept": [
+ "application/json"
+ ],
+ "X-Integration-Type": ["1"]
+ },
+ "body": {
+ "id": "test-request-id",
+ "site": {
+ "id": "site-id",
+ "page": "ttd.com",
+ "publisher": {
+ "id": "did_not_override"
+ }
+ },
+ "device": {
+ "os": "android",
+ "ip": "91.199.242.236",
+ "ua": "random user agent"
+ },
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ],
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "bidder": {
+ "publisherId": ""
+ }
+ }
+ }
+ ]
+ },
+ "impIDs":["test-imp-id"]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "currency": "USD",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "test-slot-id",
+ "impid": "test-imp-id",
+ "price": 0.1,
+ "crid": "creative-123",
+ "adm": "",
+ "w": 300,
+ "h": 250,
+ "ext": {
+ "prebid": {
+ "type": "banner"
+ }
+ },
+ "mtype": 1
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "test-slot-id",
+ "impid": "test-imp-id",
+ "price": 0.1,
+ "crid": "creative-123",
+ "adm": "",
+ "w": 300,
+ "h": 250,
+ "ext": {
+ "prebid": {
+ "type": "banner"
+ }
+ },
+ "mtype": 1
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
+
diff --git a/adapters/thetradedesk/thetradedesktest/exemplary/simple-multi-type-banner.json b/adapters/thetradedesk/thetradedesktest/exemplary/simple-multi-type-banner.json
new file mode 100644
index 00000000000..10286c96081
--- /dev/null
+++ b/adapters/thetradedesk/thetradedesktest/exemplary/simple-multi-type-banner.json
@@ -0,0 +1,151 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "site": {
+ "id": "site-id",
+ "page": "ttd.com"
+ },
+ "device": {
+ "os": "android",
+ "ip": "91.199.242.236",
+ "ua": "random user agent"
+ },
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "video": {
+ "mimes": ["video/mp4"],
+ "protocols": [2, 5],
+ "w": 300,
+ "h": 250
+ },
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ],
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "bidder": {
+ "publisherId": "123456"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://direct.adsrvr.org/bid/bidder/ttd",
+ "headers": {
+ "Content-Type": [
+ "application/json;charset=utf-8"
+ ],
+ "Accept": [
+ "application/json"
+ ],
+ "X-Integration-Type": ["1"]
+ },
+ "body": {
+ "id": "test-request-id",
+ "site": {
+ "id": "site-id",
+ "page": "ttd.com",
+ "publisher": {
+ "id": "123456"
+ }
+ },
+ "device": {
+ "os": "android",
+ "ip": "91.199.242.236",
+ "ua": "random user agent"
+ },
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "video": {
+ "mimes": ["video/mp4"],
+ "protocols": [2, 5],
+ "w": 300,
+ "h": 250
+ },
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ],
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "bidder": {
+ "publisherId": "123456"
+ }
+ }
+ }
+ ]
+ },
+ "impIDs":["test-imp-id"]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "currency": "USD",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "test-slot-id",
+ "impid": "test-imp-id",
+ "price": 0.1,
+ "crid": "creative-123",
+ "adm": "",
+ "h": 250,
+ "w": 300,
+ "ext": {
+ "prebid": {
+ "type": "banner"
+ }
+ },
+ "mtype": 1
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "test-slot-id",
+ "impid": "test-imp-id",
+ "price": 0.1,
+ "crid": "creative-123",
+ "adm": "",
+ "h": 250,
+ "w": 300,
+ "ext": {
+ "prebid": {
+ "type": "banner"
+ }
+ },
+ "mtype": 1
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
+
diff --git a/adapters/thetradedesk/thetradedesktest/exemplary/simple-multi-type-video.json b/adapters/thetradedesk/thetradedesktest/exemplary/simple-multi-type-video.json
new file mode 100644
index 00000000000..e8f4bbca57c
--- /dev/null
+++ b/adapters/thetradedesk/thetradedesktest/exemplary/simple-multi-type-video.json
@@ -0,0 +1,151 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "site": {
+ "id": "site-id",
+ "page": "ttd.com"
+ },
+ "device": {
+ "os": "android",
+ "ip": "91.199.242.236",
+ "ua": "random user agent"
+ },
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "video": {
+ "mimes": ["video/mp4"],
+ "protocols": [2, 5],
+ "w": 300,
+ "h": 250
+ },
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ],
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "bidder": {
+ "publisherId": "123456"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://direct.adsrvr.org/bid/bidder/ttd",
+ "headers": {
+ "Content-Type": [
+ "application/json;charset=utf-8"
+ ],
+ "Accept": [
+ "application/json"
+ ],
+ "X-Integration-Type": ["1"]
+ },
+ "body": {
+ "id": "test-request-id",
+ "site": {
+ "id": "site-id",
+ "page": "ttd.com",
+ "publisher": {
+ "id": "123456"
+ }
+ },
+ "device": {
+ "os": "android",
+ "ip": "91.199.242.236",
+ "ua": "random user agent"
+ },
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "video": {
+ "mimes": ["video/mp4"],
+ "protocols": [2, 5],
+ "w": 300,
+ "h": 250
+ },
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ],
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "bidder": {
+ "publisherId": "123456"
+ }
+ }
+ }
+ ]
+ },
+ "impIDs":["test-imp-id"]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "currency": "USD",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "test-slot-id",
+ "impid": "test-imp-id",
+ "price": 0.1,
+ "crid": "creative-123",
+ "adm": "some-test-ad-vast",
+ "h": 250,
+ "w": 300,
+ "ext": {
+ "prebid": {
+ "type": "video"
+ }
+ },
+ "mtype": 2
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "test-slot-id",
+ "impid": "test-imp-id",
+ "price": 0.1,
+ "crid": "creative-123",
+ "adm": "some-test-ad-vast",
+ "h": 250,
+ "w": 300,
+ "ext": {
+ "prebid": {
+ "type": "video"
+ }
+ },
+ "mtype": 2
+ },
+ "type": "video"
+ }
+ ]
+ }
+ ]
+}
+
diff --git a/adapters/thetradedesk/thetradedesktest/exemplary/simple-native.json b/adapters/thetradedesk/thetradedesktest/exemplary/simple-native.json
new file mode 100644
index 00000000000..4d1b77ab0fb
--- /dev/null
+++ b/adapters/thetradedesk/thetradedesktest/exemplary/simple-native.json
@@ -0,0 +1,115 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "site": {
+ "id": "site-id",
+ "page": "ttd.com"
+ },
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "native": {
+ "request": ""
+ },
+ "ext": {
+ "bidder": {
+ "publisherId": "123456"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://direct.adsrvr.org/bid/bidder/ttd",
+ "headers": {
+ "Content-Type": [
+ "application/json;charset=utf-8"
+ ],
+ "Accept": [
+ "application/json"
+ ],
+ "X-Integration-Type": ["1"]
+ },
+ "body": {
+ "id": "test-request-id",
+ "site": {
+ "id": "site-id",
+ "page": "ttd.com",
+ "publisher": {
+ "id": "123456"
+ }
+ },
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "native": {
+ "request": ""
+ },
+ "ext": {
+ "bidder": {
+ "publisherId": "123456"
+ }
+ }
+ }
+ ]
+ },
+ "impIDs":["test-imp-id"]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "currency": "USD",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "test-slot-id",
+ "impid": "test-imp-id",
+ "price": 0.1,
+ "crid": "creative-123",
+ "adm": "",
+ "h": 250,
+ "w": 300,
+ "ext": {
+ "prebid": {
+ "type": "native"
+ }
+ },
+ "mtype": 4
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "test-slot-id",
+ "impid": "test-imp-id",
+ "price": 0.1,
+ "crid": "creative-123",
+ "adm": "",
+ "h": 250,
+ "w": 300,
+ "ext": {
+ "prebid": {
+ "type": "native"
+ }
+ },
+ "mtype": 4
+ },
+ "type": "native"
+ }
+ ]
+ }
+ ]
+}
+
diff --git a/adapters/thetradedesk/thetradedesktest/exemplary/simple-video.json b/adapters/thetradedesk/thetradedesktest/exemplary/simple-video.json
new file mode 100644
index 00000000000..5dc945bab1f
--- /dev/null
+++ b/adapters/thetradedesk/thetradedesktest/exemplary/simple-video.json
@@ -0,0 +1,154 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "site": {
+ "id": "site-id",
+ "page": "ttd.com"
+ },
+ "device": {
+ "os": "android",
+ "ip": "91.199.242.236",
+ "ua": "random user agent"
+ },
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "tagid": "pbs-local/preroll",
+ "video": {
+ "minduration": 0,
+ "maxduration": 60,
+ "api": [1,2],
+ "mimes": [
+ "video/mp4",
+ "video/webm",
+ "application/javascript"
+ ],
+ "placement": 1,
+ "protocols": [2,3,4,5,6],
+ "w": 300,
+ "h": 250,
+ "playbackmethod": [1,2,3,4,5,6],
+ "plcmt": 1,
+ "skip": 1
+ },
+ "ext": {
+ "bidder": {
+ "publisherId": "123456"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://direct.adsrvr.org/bid/bidder/ttd",
+ "headers": {
+ "Content-Type": [
+ "application/json;charset=utf-8"
+ ],
+ "Accept": [
+ "application/json"
+ ],
+ "X-Integration-Type": ["1"]
+ },
+ "body": {
+ "id": "test-request-id",
+ "site": {
+ "id": "site-id",
+ "page": "ttd.com",
+ "publisher": {
+ "id": "123456"
+ }
+ },
+ "device": {
+ "os": "android",
+ "ip": "91.199.242.236",
+ "ua": "random user agent"
+ },
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "tagid": "pbs-local/preroll",
+ "video": {
+ "maxduration": 60,
+ "api": [1,2],
+ "mimes": [
+ "video/mp4",
+ "video/webm",
+ "application/javascript"
+ ],
+ "placement": 1,
+ "protocols": [2,3,4,5,6],
+ "w": 300,
+ "h": 250,
+ "playbackmethod": [1,2,3,4,5,6],
+ "plcmt": 1,
+ "skip": 1
+ },
+ "ext": {
+ "bidder": {
+ "publisherId": "123456"
+ }
+ }
+ }
+ ]
+ },
+ "impIDs":["test-imp-id"]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "currency": "USD",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "test-slot-id",
+ "impid": "test-imp-id",
+ "price": 0.1,
+ "crid": "creative-123",
+ "adm": "some-test-ad-vast",
+ "h": 250,
+ "w": 300,
+ "ext": {
+ "prebid": {
+ "type": "video"
+ }
+ },
+ "mtype": 2
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "test-slot-id",
+ "impid": "test-imp-id",
+ "price": 0.1,
+ "crid": "creative-123",
+ "adm": "some-test-ad-vast",
+ "h": 250,
+ "w": 300,
+ "ext": {
+ "prebid": {
+ "type": "video"
+ }
+ },
+ "mtype": 2
+ },
+ "type": "video"
+ }
+ ]
+ }
+ ]
+}
+
diff --git a/adapters/thetradedesk/thetradedesktest/supplemental/200-response-from-target.json b/adapters/thetradedesk/thetradedesktest/supplemental/200-response-from-target.json
new file mode 100644
index 00000000000..9769f0499c3
--- /dev/null
+++ b/adapters/thetradedesk/thetradedesktest/supplemental/200-response-from-target.json
@@ -0,0 +1,105 @@
+{
+ "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
+ }
+ ],
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "bidder": {
+ "publisherId": "123456"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://direct.adsrvr.org/bid/bidder/ttd",
+ "headers": {
+ "Content-Type": [
+ "application/json;charset=utf-8"
+ ],
+ "Accept": [
+ "application/json"
+ ],
+ "X-Integration-Type": ["1"]
+ },
+ "body": {
+ "id": "test-request-id",
+ "app": {
+ "bundle": "test.app.bundle",
+ "publisher": {
+ "id": "123456"
+ }
+ },
+ "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
+ }
+ ],
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "bidder": {
+ "publisherId": "123456"
+ }
+ }
+ }
+ ]
+ },
+ "impIDs":["test-imp-id"]
+ },
+ "mockResponse": {
+ "status": 200
+ }
+ }
+ ],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "unexpected end of JSON input",
+ "comparison": "literal"
+ }
+ ]
+ }
diff --git a/adapters/thetradedesk/thetradedesktest/supplemental/204-response-from-target.json b/adapters/thetradedesk/thetradedesktest/supplemental/204-response-from-target.json
new file mode 100644
index 00000000000..a329982ea50
--- /dev/null
+++ b/adapters/thetradedesk/thetradedesktest/supplemental/204-response-from-target.json
@@ -0,0 +1,105 @@
+{
+ "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
+ }
+ ],
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "bidder": {
+ "publisherId": "123456"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://direct.adsrvr.org/bid/bidder/ttd",
+ "headers": {
+ "Content-Type": [
+ "application/json;charset=utf-8"
+ ],
+ "Accept": [
+ "application/json"
+ ],
+ "X-Integration-Type": ["1"]
+ },
+ "body": {
+ "id": "test-request-id",
+ "app": {
+ "bundle": "test.app.bundle",
+ "publisher": {
+ "id": "123456"
+ }
+ },
+ "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
+ }
+ ],
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "bidder": {
+ "publisherId": "123456"
+ }
+ }
+ }
+ ]
+ },
+ "impIDs":["test-imp-id"]
+ },
+ "mockResponse": {
+ "status": 204
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": []
+ }
+ ]
+ }
diff --git a/adapters/thetradedesk/thetradedesktest/supplemental/400-response-from-target.json b/adapters/thetradedesk/thetradedesktest/supplemental/400-response-from-target.json
new file mode 100644
index 00000000000..ad5ffc62b51
--- /dev/null
+++ b/adapters/thetradedesk/thetradedesktest/supplemental/400-response-from-target.json
@@ -0,0 +1,105 @@
+{
+ "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
+ }
+ ],
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "bidder": {
+ "publisherId": "123456"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://direct.adsrvr.org/bid/bidder/ttd",
+ "headers": {
+ "Content-Type": [
+ "application/json;charset=utf-8"
+ ],
+ "Accept": [
+ "application/json"
+ ],
+ "X-Integration-Type": ["1"]
+ },
+ "body": {
+ "id": "test-request-id",
+ "app": {
+ "bundle": "test.app.bundle",
+ "publisher": {
+ "id": "123456"
+ }
+ },
+ "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
+ }
+ ],
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "bidder": {
+ "publisherId": "123456"
+ }
+ }
+ }
+ ]
+ },
+ "impIDs":["test-imp-id"]
+ },
+ "mockResponse": {
+ "status": 400
+ }
+ }
+ ],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "Unexpected status code: 400. Run with request.debug = 1 for more info",
+ "comparison": "literal"
+ }
+ ]
+ }
diff --git a/adapters/thetradedesk/thetradedesktest/supplemental/500-response-from-target.json b/adapters/thetradedesk/thetradedesktest/supplemental/500-response-from-target.json
new file mode 100644
index 00000000000..f2ccb342113
--- /dev/null
+++ b/adapters/thetradedesk/thetradedesktest/supplemental/500-response-from-target.json
@@ -0,0 +1,105 @@
+{
+ "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
+ }
+ ],
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "bidder": {
+ "publisherId": "123456"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://direct.adsrvr.org/bid/bidder/ttd",
+ "headers": {
+ "Content-Type": [
+ "application/json;charset=utf-8"
+ ],
+ "Accept": [
+ "application/json"
+ ],
+ "X-Integration-Type": ["1"]
+ },
+ "body": {
+ "id": "test-request-id",
+ "app": {
+ "bundle": "test.app.bundle",
+ "publisher": {
+ "id": "123456"
+ }
+ },
+ "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
+ }
+ ],
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "bidder": {
+ "publisherId": "123456"
+ }
+ }
+ }
+ ]
+ },
+ "impIDs":["test-imp-id"]
+ },
+ "mockResponse": {
+ "status": 500
+ }
+ }
+ ],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "Unexpected status code: 500. Run with request.debug = 1 for more info",
+ "comparison": "literal"
+ }
+ ]
+ }
diff --git a/adapters/thetradedesk/thetradedesktest/supplemental/invalid-mtype.json b/adapters/thetradedesk/thetradedesktest/supplemental/invalid-mtype.json
new file mode 100644
index 00000000000..52b392eb835
--- /dev/null
+++ b/adapters/thetradedesk/thetradedesktest/supplemental/invalid-mtype.json
@@ -0,0 +1,86 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "native": {
+ "request": ""
+ },
+ "ext": {
+ "bidder": {
+ "publisherId": "123456"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://direct.adsrvr.org/bid/bidder/ttd",
+ "headers": {
+ "Content-Type": [
+ "application/json;charset=utf-8"
+ ],
+ "Accept": [
+ "application/json"
+ ],
+ "X-Integration-Type": ["1"]
+ },
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "native": {
+ "request": ""
+ },
+ "ext": {
+ "bidder": {
+ "publisherId": "123456"
+ }
+ }
+ }
+ ]
+ },
+ "impIDs":["test-imp-id"]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "currency": "USD",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "test-slot-id",
+ "impid": "test-imp-id",
+ "price": 0.1,
+ "crid": "creative-123",
+ "adm": "",
+ "h": 250,
+ "w": 300,
+ "ext": {
+ "prebid": {
+ "type": "native"
+ }
+ },
+ "mtype": -1
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "unsupported mtype: -1",
+ "comparison": "literal"
+ }
+ ]
+}
+
diff --git a/adapters/thetradedesk/thetradedesktest/supplemental/invalid-publisher.json b/adapters/thetradedesk/thetradedesktest/supplemental/invalid-publisher.json
new file mode 100644
index 00000000000..9e936f38f20
--- /dev/null
+++ b/adapters/thetradedesk/thetradedesktest/supplemental/invalid-publisher.json
@@ -0,0 +1,45 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "app": {
+ "bundle": "test.app.bundle",
+ "publisher": {
+ "id": "this_id_will_be_replaced"
+ }
+ },
+ "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
+ }
+ ],
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "bidder": {
+ "publisherId": 123456
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [],
+ "expectedMakeRequestsErrors": [
+ {
+ "value": "json: cannot unmarshal number into Go struct field ExtImpTheTradeDesk.publisherId of type string",
+ "comparison": "literal"
+ }
+ ]
+}
+
diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go
index 09c7bf83777..45d91aaa6eb 100755
--- a/exchange/adapter_builders.go
+++ b/exchange/adapter_builders.go
@@ -178,6 +178,7 @@ import (
"github.com/prebid/prebid-server/v2/adapters/teads"
"github.com/prebid/prebid-server/v2/adapters/telaria"
"github.com/prebid/prebid-server/v2/adapters/theadx"
+ "github.com/prebid/prebid-server/v2/adapters/thetradedesk"
"github.com/prebid/prebid-server/v2/adapters/tpmn"
"github.com/prebid/prebid-server/v2/adapters/trafficgate"
"github.com/prebid/prebid-server/v2/adapters/triplelift"
@@ -390,6 +391,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder {
openrtb_ext.BidderTeads: teads.Builder,
openrtb_ext.BidderTelaria: telaria.Builder,
openrtb_ext.BidderTheadx: theadx.Builder,
+ openrtb_ext.BidderTheTradeDesk: thetradedesk.Builder,
openrtb_ext.BidderTpmn: tpmn.Builder,
openrtb_ext.BidderTrafficGate: trafficgate.Builder,
openrtb_ext.BidderTriplelift: triplelift.Builder,
diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go
index b6cda02b89c..e11cc7754a1 100644
--- a/openrtb_ext/bidders.go
+++ b/openrtb_ext/bidders.go
@@ -196,6 +196,7 @@ var coreBidderNames []BidderName = []BidderName{
BidderTeads,
BidderTelaria,
BidderTheadx,
+ BidderTheTradeDesk,
BidderTpmn,
BidderTrafficGate,
BidderTriplelift,
@@ -506,6 +507,7 @@ const (
BidderTeads BidderName = "teads"
BidderTelaria BidderName = "telaria"
BidderTheadx BidderName = "theadx"
+ BidderTheTradeDesk BidderName = "thetradedesk"
BidderTpmn BidderName = "tpmn"
BidderTrafficGate BidderName = "trafficgate"
BidderTriplelift BidderName = "triplelift"
diff --git a/openrtb_ext/imp_thetradedesk.go b/openrtb_ext/imp_thetradedesk.go
new file mode 100644
index 00000000000..89cca83f65e
--- /dev/null
+++ b/openrtb_ext/imp_thetradedesk.go
@@ -0,0 +1,8 @@
+package openrtb_ext
+
+// ExtImpTheTradeDesk defines the contract for bidrequest.imp[i].ext
+// PublisherId is mandatory parameters, others are optional parameters
+
+type ExtImpTheTradeDesk struct {
+ PublisherId string `json:"publisherId"`
+}
diff --git a/static/bidder-info/thetradedesk.yaml b/static/bidder-info/thetradedesk.yaml
new file mode 100644
index 00000000000..e1354c86ddc
--- /dev/null
+++ b/static/bidder-info/thetradedesk.yaml
@@ -0,0 +1,23 @@
+endpoint: "https://direct.adsrvr.org/bid/bidder/{{.SupplyId}}"
+maintainer:
+ email: "Prebid-Maintainers@thetradedesk.com"
+gvlVendorID: 21
+capabilities:
+ app:
+ mediaTypes:
+ - banner
+ - video
+ - native
+ site:
+ mediaTypes:
+ - banner
+ - video
+ - native
+userSync:
+ # TheTradeDesk supports user syncing, but requires configuration by the host. Contact a technical account manager
+ # or this bidder directly at the email address in this file to ask about enabling user sync.
+ supports:
+ - redirect
+ - iframe
+openrtb:
+ gpp-supported: true
diff --git a/static/bidder-params/thetradedesk.json b/static/bidder-params/thetradedesk.json
new file mode 100644
index 00000000000..5a85cf2f516
--- /dev/null
+++ b/static/bidder-params/thetradedesk.json
@@ -0,0 +1,13 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "title": "The Trade Desk Adapter Params",
+ "description": "A schema which validates params accepted by the The Trade Desk adapter",
+ "type": "object",
+ "properties": {
+ "publisherId": {
+ "type": "string",
+ "description": "An ID which identifies the publisher"
+ }
+ },
+ "required": ["publisherId"]
+}