Repository: prebid/prebid-server
Pull request #4294: TheTradeDesk: Dynamically construct endpoint using supplySourceId
Author: login: andre-gielow-ttd
Labels: name: adapter
Created: 2025-04-10T15:56:32Z | Merged: 2025-04-23T15:59:43Z



Diff:
diff --git a/adapters/thetradedesk/params_test.go b/adapters/thetradedesk/params_test.go
index 05febe45fab..b0829fb290c 100644
--- a/adapters/thetradedesk/params_test.go
+++ b/adapters/thetradedesk/params_test.go
@@ -35,9 +35,9 @@ func TestInvalidParams(t *testing.T) {
 }
 
 var validParams = []string{
-	`{"publisherId": "123456"}`,
-	`{"publisherId": "pub-123456"}`,
-	`{"publisherId": "publisherIDAllString"}`,
+	`{"publisherId": "123456", "supplySourceId": "xyz_123"}`,
+	`{"publisherId": "pub-123456", "supplySourceId": "direct_123"}`,
+	`{"publisherId": "publisherIDAllString", "supplySourceId": "direct123"}`,
 }
 
 var invalidParams = []string{
@@ -48,6 +48,6 @@ var invalidParams = []string{
 	`4.2`,
 	`[]`,
 	`{}`,
-	`{"publisherId": 123456}`,
-	`{"publisherId": 0}`,
+	`{"publisherId": 123456, "supplySourceId": 123}`,
+	`{"publisherId": 0, "supplySourceId": 0}`,
 }
diff --git a/adapters/thetradedesk/thetradedesk.go b/adapters/thetradedesk/thetradedesk.go
index 0288b4bb097..47fb9ee0fde 100644
--- a/adapters/thetradedesk/thetradedesk.go
+++ b/adapters/thetradedesk/thetradedesk.go
@@ -17,18 +17,16 @@ import (
 	"github.com/prebid/openrtb/v20/openrtb2"
 )
 
-const PREBID_INTEGRATION_TYPE = "1"
+//const PREBID_INTEGRATION_TYPE = "1"
 
 type adapter struct {
-	bidderEndpoint string
-}
-
-type ExtImpBidderTheTradeDesk struct {
-	adapters.ExtImpBidder
+	bidderEndpointTemplate string
+	defaultEndpoint        string
+	templateEndpoint       *template.Template
 }
 
 func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
-	pubID, err := getPublisherId(request.Imp)
+	pubID, supplySourceId, err := getExtensionInfo(request.Imp)
 
 	if err != nil {
 		return nil, []error{err}
@@ -87,37 +85,74 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.E
 		return nil, errs
 	}
 
+	bidderEndpoint, err := a.buildEndpointURL(supplySourceId)
+	if err != nil {
+		return nil, []error{errors.New("Failed to build endpoint URL")}
+	}
+
 	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)
+	//headers.Add("x-integration-type", PREBID_INTEGRATION_TYPE) this will be parsed and added conditionally later
 	return []*adapters.RequestData{{
 		Method:  "POST",
-		Uri:     a.bidderEndpoint,
+		Uri:     bidderEndpoint,
 		Body:    reqJSON,
 		Headers: headers,
 		ImpIDs:  openrtb_ext.GetImpIDs(request.Imp),
 	}}, errs
 }
 
-func getPublisherId(impressions []openrtb2.Imp) (string, error) {
+func (a *adapter) buildEndpointURL(supplySourceId string) (string, error) {
+	if supplySourceId == "" {
+		return a.defaultEndpoint, nil
+	}
+
+	urlParams := macros.EndpointTemplateParams{SupplyId: supplySourceId}
+	bidderEndpoint, err := macros.ResolveMacros(a.templateEndpoint, urlParams)
+
+	if err != nil {
+		return "", fmt.Errorf("unable to resolve endpoint macros: %v", err)
+	}
+
+	return bidderEndpoint, nil
+}
+
+func getExtensionInfo(impressions []openrtb2.Imp) (string, string, error) {
+	publisherId := ""
+	supplySourceId := ""
 	for _, imp := range impressions {
+		var ttdExt, err = getImpressionExt(&imp)
+		if err != nil {
+			return "", "", err
+		}
 
-		var bidderExt ExtImpBidderTheTradeDesk
-		if err := jsonutil.Unmarshal(imp.Ext, &bidderExt); err != nil {
-			return "", err
+		if ttdExt.PublisherId != "" && publisherId == "" {
+			publisherId = ttdExt.PublisherId
 		}
 
-		var ttdExt openrtb_ext.ExtImpTheTradeDesk
-		if err := jsonutil.Unmarshal(bidderExt.Bidder, &ttdExt); err != nil {
-			return "", err
+		if ttdExt.SupplySourceId != "" && supplySourceId == "" {
+			supplySourceId = ttdExt.SupplySourceId
 		}
 
-		if ttdExt.PublisherId != "" {
-			return ttdExt.PublisherId, nil
+		if publisherId != "" && supplySourceId != "" {
+			break
 		}
 	}
-	return "", nil
+	return publisherId, supplySourceId, nil
+}
+
+func getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ExtImpTheTradeDesk, error) {
+	var bidderExt adapters.ExtImpBidder
+	if err := jsonutil.Unmarshal(imp.Ext, &bidderExt); err != nil {
+		return nil, err
+	}
+
+	var ttdExt openrtb_ext.ExtImpTheTradeDesk
+	if err := jsonutil.Unmarshal(bidderExt.Bidder, &ttdExt); err != nil {
+		return nil, err
+	}
+	return &ttdExt, nil
 }
 
 func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
@@ -172,11 +207,6 @@ func getBidType(markupType openrtb2.MarkupType) (openrtb_ext.BidType, error) {
 }
 
 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 {
@@ -184,14 +214,21 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server co
 		}
 	}
 
+	template, err := template.New("endpointTemplate").Parse(config.Endpoint)
+	if err != nil {
+		return nil, fmt.Errorf("unable to parse endpoint url template: %v", err)
+	}
+
 	urlParams := macros.EndpointTemplateParams{SupplyId: config.ExtraAdapterInfo}
-	bidderEndpoint, err := macros.ResolveMacros(template, urlParams)
+	defaultEndpoint, err := macros.ResolveMacros(template, urlParams)
 
 	if err != nil {
 		return nil, fmt.Errorf("unable to resolve endpoint macros: %v", err)
 	}
 
 	return &adapter{
-		bidderEndpoint: bidderEndpoint,
+		bidderEndpointTemplate: config.Endpoint,
+		defaultEndpoint:        defaultEndpoint,
+		templateEndpoint:       template,
 	}, nil
 }
diff --git a/adapters/thetradedesk/thetradedesk_test.go b/adapters/thetradedesk/thetradedesk_test.go
index 658d812bcac..42166c4b76c 100644
--- a/adapters/thetradedesk/thetradedesk_test.go
+++ b/adapters/thetradedesk/thetradedesk_test.go
@@ -2,12 +2,13 @@ package thetradedesk
 
 import (
 	"encoding/json"
+	"github.com/prebid/prebid-server/v3/adapters/adapterstest"
 	"net/http"
 	"testing"
+	"text/template"
 
 	"github.com/prebid/openrtb/v20/openrtb2"
 	"github.com/prebid/prebid-server/v3/adapters"
-	"github.com/prebid/prebid-server/v3/adapters/adapterstest"
 	"github.com/prebid/prebid-server/v3/config"
 	"github.com/prebid/prebid-server/v3/openrtb_ext"
 	"github.com/stretchr/testify/assert"
@@ -116,15 +117,16 @@ func TestGetBidType(t *testing.T) {
 	}
 }
 
-func TestGetPublisherId(t *testing.T) {
+func TestGetExtensionInfo(t *testing.T) {
 	type args struct {
 		impressions []openrtb2.Imp
 	}
 	tests := []struct {
-		name                string
-		args                args
-		expectedPublisherId string
-		wantErr             bool
+		name                   string
+		args                   args
+		expectedPublisherId    string
+		expectedSupplySourceId string
+		wantErr                bool
 	}{
 		{
 			name: "valid_publisher_Id",
@@ -132,12 +134,13 @@ func TestGetPublisherId(t *testing.T) {
 				impressions: []openrtb2.Imp{
 					{
 						Video: &openrtb2.Video{},
-						Ext:   json.RawMessage(`{"bidder":{"publisherId":"1"}}`),
+						Ext:   json.RawMessage(`{"bidder":{"publisherId":"1", "supplySourceId": "abc"}}`),
 					},
 				},
 			},
-			expectedPublisherId: "1",
-			wantErr:             false,
+			expectedPublisherId:    "1",
+			expectedSupplySourceId: "abc",
+			wantErr:                false,
 		},
 		{
 			name: "multiple_valid_publisher_Id",
@@ -145,16 +148,17 @@ func TestGetPublisherId(t *testing.T) {
 				impressions: []openrtb2.Imp{
 					{
 						Video: &openrtb2.Video{},
-						Ext:   json.RawMessage(`{"bidder":{"publisherId":"1"}}`),
+						Ext:   json.RawMessage(`{"bidder":{"publisherId":"1", "supplySourceId": "abc"}}`),
 					},
 					{
 						Video: &openrtb2.Video{},
-						Ext:   json.RawMessage(`{"bidder":{"publisherId":"2"}}`),
+						Ext:   json.RawMessage(`{"bidder":{"publisherId":"2",  "supplySourceId": "def"}}`),
 					},
 				},
 			},
-			expectedPublisherId: "1",
-			wantErr:             false,
+			expectedPublisherId:    "1",
+			expectedSupplySourceId: "abc",
+			wantErr:                false,
 		},
 		{
 			name: "not_publisherId_present",
@@ -166,8 +170,9 @@ func TestGetPublisherId(t *testing.T) {
 					},
 				},
 			},
-			expectedPublisherId: "",
-			wantErr:             false,
+			expectedPublisherId:    "",
+			expectedSupplySourceId: "",
+			wantErr:                false,
 		},
 		{
 			name: "nil_publisherId_present",
@@ -179,16 +184,18 @@ func TestGetPublisherId(t *testing.T) {
 					},
 				},
 			},
-			expectedPublisherId: "",
-			wantErr:             false,
+			expectedPublisherId:    "",
+			expectedSupplySourceId: "",
+			wantErr:                false,
 		},
 		{
 			name: "no_impressions",
 			args: args{
 				impressions: []openrtb2.Imp{},
 			},
-			expectedPublisherId: "",
-			wantErr:             false,
+			expectedPublisherId:    "",
+			expectedSupplySourceId: "",
+			wantErr:                false,
 		},
 		{
 			name: "invalid_bidder_object",
@@ -200,15 +207,17 @@ func TestGetPublisherId(t *testing.T) {
 					},
 				},
 			},
-			expectedPublisherId: "",
-			wantErr:             false,
+			expectedPublisherId:    "",
+			expectedSupplySourceId: "",
+			wantErr:                false,
 		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			publisherId, err := getPublisherId(tt.args.impressions)
+			publisherId, supplySourceId, err := getExtensionInfo(tt.args.impressions)
 			assert.Equal(t, tt.wantErr, err != nil)
 			assert.Equal(t, tt.expectedPublisherId, publisherId)
+			assert.Equal(t, tt.expectedSupplySourceId, supplySourceId)
 		})
 	}
 }
@@ -268,9 +277,12 @@ func TestTheTradeDeskAdapter_MakeRequests(t *testing.T) {
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			a := &adapter{
-				bidderEndpoint: tt.fields.URI,
-			}
+			a, buildErr := Builder(openrtb_ext.BidderTheTradeDesk, config.Adapter{
+				Endpoint:         `https://adsrvr.org/bid/bidder/{{.SupplyId}}`,
+				ExtraAdapterInfo: "test",
+			}, config.Server{})
+			assert.Nil(t, buildErr)
+
 			gotReqData, gotErr := a.MakeRequests(tt.args.request, tt.args.reqInfo)
 			assert.Equal(t, tt.wantErr, len(gotErr) != 0)
 			if tt.wantErr == false {
@@ -374,12 +386,64 @@ func TestTheTradeDeskAdapter_MakeBids(t *testing.T) {
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			a := &adapter{
-				bidderEndpoint: tt.fields.URI,
-			}
+			a, buildErr := Builder(openrtb_ext.BidderTheTradeDesk, config.Adapter{
+				Endpoint:         `https://adsrvr.org/bid/bidder/{{.SupplyId}}`,
+				ExtraAdapterInfo: "test",
+			}, config.Server{})
+			assert.Nil(t, buildErr)
 			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)
 		})
 	}
 }
+
+func TestTheTradeDeskAdapter_BuildEndpoint(t *testing.T) {
+	tests := []struct {
+		name             string
+		supplySourceId   string
+		defaultEndpoint  string
+		expectedEndpoint string
+		wantErr          []error
+	}{
+		{
+			name:             "valid_supply_source_id",
+			supplySourceId:   "pub_abc",
+			defaultEndpoint:  "https://direct.adsrvr.org/bid/bidder/default_publisher",
+			expectedEndpoint: "https://direct.adsrvr.org/bid/bidder/pub_abc",
+			wantErr:          nil,
+		},
+		{
+			name:             "empty_supply_source_id",
+			supplySourceId:   "",
+			defaultEndpoint:  "https://direct.adsrvr.org/bid/bidder/default_publisher",
+			expectedEndpoint: "https://direct.adsrvr.org/bid/bidder/default_publisher",
+			wantErr:          nil,
+		},
+		{
+			name:             "empty_ssi_and_no_default_expect_err",
+			supplySourceId:   "",
+			defaultEndpoint:  "",
+			expectedEndpoint: "",
+			wantErr:          nil,
+		},
+	}
+
+	endpointTemplate, err := template.New("endpointTemplate").Parse("https://direct.adsrvr.org/bid/bidder/{{.SupplyId}}")
+	assert.Nil(t, err)
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			a := &adapter{
+				bidderEndpointTemplate: "https://direct.adsrvr.org/bid/bidder/{{.SupplyId}}",
+				defaultEndpoint:        tt.defaultEndpoint,
+				templateEndpoint:       endpointTemplate,
+			}
+			finalEndpoint, err := a.buildEndpointURL(tt.supplySourceId)
+			if tt.wantErr != nil {
+				assert.NotNil(t, err)
+			}
+			assert.Equal(t, tt.expectedEndpoint, finalEndpoint)
+		})
+	}
+}
diff --git a/adapters/thetradedesk/thetradedesktest/exemplary/ortb_response_video_parsing.json b/adapters/thetradedesk/thetradedesktest/exemplary/ortb_response_video_parsing.json
new file mode 100644
index 00000000000..d0683a2d051
--- /dev/null
+++ b/adapters/thetradedesk/thetradedesktest/exemplary/ortb_response_video_parsing.json
@@ -0,0 +1,285 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "instl": 0,
+        "ext": {
+          "gpid": "/1030006,22910642311/bakewithzoha/recipe",
+          "bidder": {
+            "publisherId": "123456"
+          }
+        },
+        "id": "test-imp-id",
+        "bidfloorcur": "USD",
+        "bidfloor": 1.72,
+        "tagid": "pbs-local/preroll",
+        "video": {
+          "minduration": 5,
+          "startdelay": 0,
+          "api": [1, 2],
+          "w": 300,
+          "h": 169,
+          "mimes": [
+            "application/javascript",
+            "video/mp4",
+            "video/webm"
+          ],
+          "protocols": [2, 3, 5, 6, 7, 8],
+          "maxduration": 30,
+          "placement": 3,
+          "plcmt": 4,
+          "skip": 1,
+          "content": {
+            "channel": {
+              "name": "Bake",
+              "domain": "https://ttd-bake.com/chicken-lasagna/"
+            },
+            "context": 5,
+            "genre": "Food & Drink",
+            "cattax": 1,
+            "cat": ["IAB8", "IAB8", "IAB8-8"]
+          },
+          "playbackmethod": [2]
+        }
+      }
+    ],
+    "cur": ["USD"],
+    "site": {
+      "publisher": {
+        "id": "123456"
+      },
+      "mobile": 1,
+      "privacypolicy": 1,
+      "page": "https://example.com/chicken-lasagna/",
+      "domain": "ttd-bake.com",
+      "ref": "https://example.com/?s=easy+lasagna",
+      "id": "20619",
+      "name": "Bake With Zoha"
+    },
+    "device": {
+      "js": 1,
+      "w": 1680,
+      "h": 1050,
+      "osv": "NT 10.0",
+      "os": "Windows",
+      "dnt": 0,
+      "language": "en",
+      "ip": "66.129.221.46",
+      "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36",
+      "sua": {
+        "source": 1,
+        "platform": {
+          "brand": "Windows"
+        },
+        "browsers": [
+          {
+            "brand": "Google Chrome",
+            "version": ["135"]
+          }
+        ],
+        "mobile": 0
+      },
+      "devicetype": 2
+    },
+    "test": 0,
+    "source": {
+      "fd": 1,
+      "pchain": "1363c924529b3998:20619",
+      "ext": {
+        "schain": {
+          "ver": "1.0",
+          "complete": 1,
+          "nodes": [
+            {
+              "asi": "example.com",
+              "sid": "050fd4e480110f1b92d451b879e6c780",
+              "hp": 1,
+              "rid": "4555b7da05efd4d4"
+            }
+          ]
+        }
+      }
+    },
+    "bcat": ["211", "211", "181", "524", "271", "514"],
+    "cattax": 6,
+    "tmax": 750
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://direct.adsrvr.org/bid/bidder/ttd",
+        "headers": {
+          "Content-Type": [
+            "application/json;charset=utf-8"
+          ],
+          "Accept": [
+            "application/json"
+          ]
+        },
+        "body": {
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id": "test-imp-id",
+              "tagid": "pbs-local/preroll",
+              "bidfloorcur": "USD",
+              "bidfloor": 1.72,
+              "video": {
+                "minduration": 5,
+                "maxduration": 30,
+                "api": [1,2],
+                "mimes": [
+                  "application/javascript",
+                  "video/mp4",
+                  "video/webm"
+                ],
+                "placement": 3,
+                "protocols": [2, 3, 5, 6, 7, 8  ],
+                "w": 300,
+                "h": 169,
+                "playbackmethod": [2],
+                "plcmt": 4,
+                "skip": 1,
+                "startdelay": 0
+              },
+              "ext": {
+                "gpid": "/1030006,22910642311/bakewithzoha/recipe",
+                "bidder": {
+                  "publisherId": "123456"
+                }
+              }
+            }
+          ],
+          "cur": ["USD"],
+          "site": {
+            "publisher": {
+              "id": "123456"
+            },
+            "mobile": 1,
+            "privacypolicy": 1,
+            "page": "https://example.com/chicken-lasagna/",
+            "domain": "ttd-bake.com",
+            "ref": "https://example.com/?s=easy+lasagna",
+            "id": "20619",
+            "name": "Bake With Zoha"
+          },
+          "device": {
+            "js": 1,
+            "w": 1680,
+            "h": 1050,
+            "osv": "NT 10.0",
+            "os": "Windows",
+            "dnt": 0,
+            "language": "en",
+            "ip": "66.129.221.46",
+            "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36",
+            "sua": {
+              "source": 1,
+              "platform": {
+                "brand": "Windows"
+              },
+              "browsers": [
+                {
+                  "brand": "Google Chrome",
+                  "version": ["135"]
+                }
+              ],
+              "mobile": 0
+            },
+            "devicetype": 2
+          },
+          "source": {
+            "fd": 1,
+            "pchain": "1363c924529b3998:20619",
+            "ext": {
+              "schain": {
+                "ver": "1.0",
+                "complete": 1,
+                "nodes": [
+                  {
+                    "asi": "example.com",
+                    "sid": "050fd4e480110f1b92d451b879e6c780",
+                    "hp": 1,
+                    "rid": "4555b7da05efd4d4"
+                  }
+                ]
+              }
+            }
+          },
+          "bcat": ["211", "211", "181", "524", "271", "514"],
+          "cattax": 6,
+          "tmax": 750
+        },
+        "impIDs":["test-imp-id"]
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-imp-id",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "test-slot-id",
+                  "impid": "test-imp-id",
+                  "price": 0.1,
+                  "adm": "some-test-valid-adm",
+                  "cid": "test-imp-id-ortb-cid",
+                  "crid": "creative-123",
+                  "adomain": [
+                    "ttd-ortb.com"
+                  ],
+                  "w": 640,
+                  "h": 360,
+                  "cat": [
+                    "286",
+                    "287",
+                    "313"
+                  ],
+                  "cattax": 6,
+                  "mtype": 2,
+                  "dur": 30
+                }
+              ],
+              "seat": "5038"
+            }
+          ],
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "test-slot-id",
+            "impid": "test-imp-id",
+            "price": 0.1,
+            "crid": "creative-123",
+            "cid": "test-imp-id-ortb-cid",
+            "adm": "some-test-valid-adm",
+            "w": 640,
+            "h": 360,
+            "dur": 30,
+            "mtype": 2,
+            "adomain": [
+              "ttd-ortb.com"
+            ],
+            "cattax": 6,
+            "cat": [
+              "286",
+              "287",
+              "313"
+            ]
+          },
+          "type": "video"
+        }
+      ]
+    }
+  ]
+}
+
diff --git a/adapters/thetradedesk/thetradedesktest/exemplary/prebidjs_response_video_parsing.json b/adapters/thetradedesk/thetradedesktest/exemplary/prebidjs_response_video_parsing.json
new file mode 100644
index 00000000000..24a532d4368
--- /dev/null
+++ b/adapters/thetradedesk/thetradedesktest/exemplary/prebidjs_response_video_parsing.json
@@ -0,0 +1,308 @@
+{
+  "mockBidRequest": {
+    "id": "3621b7f6603b81ea08",
+    "imp": [
+      {
+        "id": "362240e73c1361aba",
+        "tagid": "/18190176,23070927648/AdThrive_Video_Coll_SOff_Smartphone/599de1450b08e2314df63095",
+        "video": {
+          "minduration": 1,
+          "maxduration": 15,
+          "api": [2, 7],
+          "mimes": ["video/mp4", "application/javascript"],
+          "placement": 1,
+          "protocols": [1, 2, 3, 4, 5, 6, 7, 8],
+          "w": 400,
+          "h": 225,
+          "playbackmethod": [6],
+          "plcmt": 2,
+          "pos": 1,
+          "startdelay": 0,
+          "linearity": 1,
+          "skip": 0,
+          "playbackend": 3
+        },
+        "bidfloor": 3.3507,
+        "bidfloorcur": "USD",
+        "secure": 1,
+        "ext": {
+          "bidder": {
+            "publisherId": "123456",
+            "supplySourceId": "pub_abc"
+          },
+          "data": {
+            "adserver": {
+              "name": "gam",
+              "adslot": "/18190176,23070927648/AdThrive_Video_Coll_SOff_Smartphone/599de1450b08e2314df63095"
+            },
+            "jwTargeting": {
+              "playerID": "s2WHEhYP",
+              "mediaID": "jJjdbKQW"
+            },
+            "pbadslot": "/18190176,23070927648/AdThrive_Video_Coll_SOff_Smartphone/599de1450b08e2314df63095"
+          },
+          "siteId": "1170765",
+          "gpid": "/18190176,23070927648/AdThrive_Video_Coll_SOff_Smartphone/599de1450b08e2314df63095"
+        }
+      }
+    ],
+    "site": {
+      "page": "ttd.com",
+      "id": "site-id"
+    },
+    "device": {
+      "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 18_3_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.3.1 Mobile/15E148 Safari/604.1",
+      "dnt": 0,
+      "language": "en",
+      "connectiontype": 0,
+      "w": 375,
+      "h": 812,
+      "ext": {
+        "vpw": 375,
+        "vph": 740
+      },
+      "js": 1
+    },
+    "user": {
+      "ext": {
+        "eids": [
+          {
+            "source": "criteo.com",
+            "uids": [
+              {
+                "id": "198lR19LYzIxODFxc1d5WGxCelQ5ZDh1NWhqNUI2NGU2UDdOYnpJdHpNSk0lMkZ3aDhJeEdmWEVhSSUyQkRUYzlJcSUyQjElMkJzWTkyeGNaRHBzd0xxNVN0WGR1UXd3blRFYzBLRG9mMlclMkJhbDY0VWhNNDdORlIyUnVPVGN5a2NqdlZwZlR4NzdYcGo",
+                "atype": 1
+              }
+            ]
+          }
+        ]
+      }
+    },
+    "at": 1,
+    "cur": ["USD"],
+    "source": {
+      "ext": {
+        "schain": {
+          "ver": "1.0",
+          "complete": 1,
+          "nodes": [
+            {
+              "asi": "example.com",
+              "sid": "599de1450b08e2314df63095",
+              "hp": 1
+            }
+          ]
+        }
+      }
+    },
+    "ext": {
+      "ttdprebid": {
+        "ver": "TTD-PREBID-2024.07.27",
+        "pbjs": "9.32.0",
+        "keywords": []
+      }
+    },
+    "bcat": [
+      "IAB8-5",
+      "IAB8-18"
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://direct.adsrvr.org/bid/bidder/pub_abc",
+        "headers": {
+          "Content-Type": [
+            "application/json;charset=utf-8"
+          ],
+          "Accept": [
+            "application/json"
+          ]
+        },
+        "body": {
+          "id": "3621b7f6603b81ea08",
+          "at": 1,
+          "cur": ["USD"],
+          "bcat": [
+            "IAB8-5",
+            "IAB8-18"
+          ],
+          "site": {
+            "id": "site-id",
+            "page": "ttd.com",
+            "publisher": {
+              "id": "123456"
+            }
+          },
+          "source": {
+            "ext": {
+              "schain": {
+                "ver": "1.0",
+                "complete": 1,
+                "nodes": [
+                  {
+                    "asi": "example.com",
+                    "sid": "599de1450b08e2314df63095",
+                    "hp": 1
+                  }
+                ]
+              }
+            }
+          },
+          "user": {
+            "ext": {
+              "eids": [
+                {
+                  "source": "criteo.com",
+                  "uids": [
+                    {
+                      "id": "198lR19LYzIxODFxc1d5WGxCelQ5ZDh1NWhqNUI2NGU2UDdOYnpJdHpNSk0lMkZ3aDhJeEdmWEVhSSUyQkRUYzlJcSUyQjElMkJzWTkyeGNaRHBzd0xxNVN0WGR1UXd3blRFYzBLRG9mMlclMkJhbDY0VWhNNDdORlIyUnVPVGN5a2NqdlZwZlR4NzdYcGo",
+                      "atype": 1
+                    }
+                  ]
+                }
+              ]
+            }
+          },
+          "ext": {
+            "ttdprebid": {
+              "ver": "TTD-PREBID-2024.07.27",
+              "pbjs": "9.32.0",
+              "keywords": []
+            }
+          },
+          "device": {
+            "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 18_3_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.3.1 Mobile/15E148 Safari/604.1",
+            "dnt": 0,
+            "language": "en",
+            "connectiontype": 0,
+            "w": 375,
+            "h": 812,
+            "ext": {
+              "vpw": 375,
+              "vph": 740
+            },
+            "js": 1
+          },
+          "imp": [
+            {
+              "id": "362240e73c1361aba",
+              "tagid": "/18190176,23070927648/AdThrive_Video_Coll_SOff_Smartphone/599de1450b08e2314df63095",
+              "video": {
+                "minduration": 1,
+                "maxduration": 15,
+                "api": [2, 7],
+                "mimes": ["video/mp4", "application/javascript"],
+                "placement": 1,
+                "protocols": [1, 2, 3, 4, 5, 6, 7, 8],
+                "w": 400,
+                "h": 225,
+                "playbackmethod": [6],
+                "plcmt": 2,
+                "pos": 1,
+                "startdelay": 0,
+                "linearity": 1,
+                "skip": 0,
+                "playbackend": 3
+              },
+              "bidfloor": 3.3507,
+              "bidfloorcur": "USD",
+              "secure": 1,
+              "ext": {
+                "bidder": {
+                  "publisherId": "123456",
+                  "supplySourceId": "pub_abc"
+                },
+                "data": {
+                  "adserver": {
+                    "name": "gam",
+                    "adslot": "/18190176,23070927648/AdThrive_Video_Coll_SOff_Smartphone/599de1450b08e2314df63095"
+                  },
+                  "jwTargeting": {
+                    "playerID": "s2WHEhYP",
+                    "mediaID": "jJjdbKQW"
+                  },
+                  "pbadslot": "/18190176,23070927648/AdThrive_Video_Coll_SOff_Smartphone/599de1450b08e2314df63095"
+                },
+                "siteId": "1170765",
+                "gpid": "/18190176,23070927648/AdThrive_Video_Coll_SOff_Smartphone/599de1450b08e2314df63095"
+              }
+            }
+          ]
+        },
+        "impIDs":["362240e73c1361aba"]
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "4555b7da05efd4d4",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "ttd-bid-1",
+                  "impid": "ttd-bid-1-impid",
+                  "price": 0.1,
+                  "adm": "some-test-ad-vast",
+                  "cid": "ttd_cid",
+                          "crid": "creative-123",
+                  "adomain": [
+                    "ttd-bid.com"
+                  ],
+                  "w": 480,
+                  "h": 360,
+                  "cat": [
+                    "IAB22"
+                  ],
+                  "mtype": 2,
+                  "ext": {
+                    "advid": "u143h4c",
+                    "duration": 15,
+                    "viewabilityvendors": [],
+                    "mediatype": 2
+                  }
+                }
+              ],
+              "seat": "1550"
+            }
+          ],
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "ttd-bid-1",
+            "impid": "ttd-bid-1-impid",
+            "price": 0.1,
+            "crid": "creative-123",
+            "adm": "some-test-ad-vast",
+            "w": 480,
+            "h": 360,
+            "adomain": [
+              "ttd-bid.com"
+            ],
+            "cat": [
+              "IAB22"
+            ],
+            "cid": "ttd_cid",
+            "mtype": 2,
+            "ext": {
+              "advid": "u143h4c",
+              "duration": 15,
+              "viewabilityvendors": [],
+              "mediatype": 2
+            }
+          },
+          "type": "video"
+        }
+      ]
+    }
+  ]
+}
+
diff --git a/adapters/thetradedesk/thetradedesktest/exemplary/simple-banner-inapp.json b/adapters/thetradedesk/thetradedesktest/exemplary/simple-banner-inapp.json
index e10445382c8..0b1ffe87001 100644
--- a/adapters/thetradedesk/thetradedesktest/exemplary/simple-banner-inapp.json
+++ b/adapters/thetradedesk/thetradedesktest/exemplary/simple-banner-inapp.json
@@ -44,8 +44,7 @@
           ],
           "Accept": [
             "application/json"
-          ],
-          "X-Integration-Type": ["1"]
+          ]
         },
         "body": {
           "id": "test-request-id",
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
index 0180d48dde2..6219c7d5ed4 100644
--- a/adapters/thetradedesk/thetradedesktest/exemplary/simple-banner-multiple-bids-and-formats.json
+++ b/adapters/thetradedesk/thetradedesktest/exemplary/simple-banner-multiple-bids-and-formats.json
@@ -70,8 +70,7 @@
           ],
           "Accept": [
             "application/json"
-          ],
-          "X-Integration-Type": ["1"]
+          ]
         },
         "body": {
           "id": "test-request-id-multiple-bids",
diff --git a/adapters/thetradedesk/thetradedesktest/exemplary/simple-banner-multiple-bids.json b/adapters/thetradedesk/thetradedesktest/exemplary/simple-banner-multiple-bids.json
index 4d46fa6907b..d9ab70a1d65 100644
--- a/adapters/thetradedesk/thetradedesktest/exemplary/simple-banner-multiple-bids.json
+++ b/adapters/thetradedesk/thetradedesktest/exemplary/simple-banner-multiple-bids.json
@@ -62,8 +62,7 @@
           ],
           "Accept": [
             "application/json"
-          ],
-          "X-Integration-Type": ["1"]
+          ]
         },
         "body": {
           "id": "test-request-id-multiple-bids",
diff --git a/adapters/thetradedesk/thetradedesktest/exemplary/simple-banner.json b/adapters/thetradedesk/thetradedesktest/exemplary/simple-banner.json
index 4ac6acb0556..c7f904e524c 100644
--- a/adapters/thetradedesk/thetradedesktest/exemplary/simple-banner.json
+++ b/adapters/thetradedesk/thetradedesktest/exemplary/simple-banner.json
@@ -44,8 +44,7 @@
           ],
           "Accept": [
             "application/json"
-          ],
-          "X-Integration-Type": ["1"]
+          ]
         },
         "body": {
           "id": "test-request-id",
diff --git a/adapters/thetradedesk/thetradedesktest/exemplary/simple-empty-publisherId.json b/adapters/thetradedesk/thetradedesktest/exemplary/simple-empty-publisherId.json
index ef6f6695553..f4ff473838d 100644
--- a/adapters/thetradedesk/thetradedesktest/exemplary/simple-empty-publisherId.json
+++ b/adapters/thetradedesk/thetradedesktest/exemplary/simple-empty-publisherId.json
@@ -44,8 +44,7 @@
           ],
           "Accept": [
             "application/json"
-          ],
-          "X-Integration-Type": ["1"]
+          ]
         },
         "body": {
           "id": "test-request-id",
diff --git a/adapters/thetradedesk/thetradedesktest/exemplary/simple-multi-type-banner.json b/adapters/thetradedesk/thetradedesktest/exemplary/simple-multi-type-banner.json
index 10286c96081..a09ed5c2f4e 100644
--- a/adapters/thetradedesk/thetradedesktest/exemplary/simple-multi-type-banner.json
+++ b/adapters/thetradedesk/thetradedesktest/exemplary/simple-multi-type-banner.json
@@ -47,8 +47,7 @@
           ],
           "Accept": [
             "application/json"
-          ],
-          "X-Integration-Type": ["1"]
+          ]
         },
         "body": {
           "id": "test-request-id",
diff --git a/adapters/thetradedesk/thetradedesktest/exemplary/simple-multi-type-video.json b/adapters/thetradedesk/thetradedesktest/exemplary/simple-multi-type-video.json
index e8f4bbca57c..01b39a4b7aa 100644
--- a/adapters/thetradedesk/thetradedesktest/exemplary/simple-multi-type-video.json
+++ b/adapters/thetradedesk/thetradedesktest/exemplary/simple-multi-type-video.json
@@ -47,8 +47,7 @@
           ],
           "Accept": [
             "application/json"
-          ],
-          "X-Integration-Type": ["1"]
+          ]
         },
         "body": {
           "id": "test-request-id",
diff --git a/adapters/thetradedesk/thetradedesktest/exemplary/simple-native.json b/adapters/thetradedesk/thetradedesktest/exemplary/simple-native.json
index 4d1b77ab0fb..8304adb1614 100644
--- a/adapters/thetradedesk/thetradedesktest/exemplary/simple-native.json
+++ b/adapters/thetradedesk/thetradedesktest/exemplary/simple-native.json
@@ -29,8 +29,7 @@
           ],
           "Accept": [
             "application/json"
-          ],
-          "X-Integration-Type": ["1"]
+          ]
         },
         "body": {
           "id": "test-request-id",
diff --git a/adapters/thetradedesk/thetradedesktest/exemplary/simple-video.json b/adapters/thetradedesk/thetradedesktest/exemplary/simple-video.json
index 5dc945bab1f..e238b34faf5 100644
--- a/adapters/thetradedesk/thetradedesktest/exemplary/simple-video.json
+++ b/adapters/thetradedesk/thetradedesktest/exemplary/simple-video.json
@@ -49,8 +49,7 @@
           ],
           "Accept": [
             "application/json"
-          ],
-          "X-Integration-Type": ["1"]
+          ]
         },
         "body": {
           "id": "test-request-id",
diff --git a/adapters/thetradedesk/thetradedesktest/exemplary/use-supply-source-id-param.json b/adapters/thetradedesk/thetradedesktest/exemplary/use-supply-source-id-param.json
new file mode 100644
index 00000000000..d7d330de57a
--- /dev/null
+++ b/adapters/thetradedesk/thetradedesktest/exemplary/use-supply-source-id-param.json
@@ -0,0 +1,208 @@
+{
+  "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",
+            "supplySourceId": "publisher_xyz"
+          }
+        }
+      },
+      {
+        "id": "test-imp-id-2",
+        "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": "987654",
+            "supplySourceId": "publisher_abc"
+          }
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://direct.adsrvr.org/bid/bidder/publisher_xyz",
+        "headers": {
+          "Content-Type": [
+            "application/json;charset=utf-8"
+          ],
+          "Accept": [
+            "application/json"
+          ]
+        },
+        "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",
+                  "supplySourceId": "publisher_xyz"
+                }
+              }
+            },
+            {
+              "id": "test-imp-id-2",
+              "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": "987654",
+                  "supplySourceId": "publisher_abc"
+                }
+              }
+            }
+          ]
+        },
+        "impIDs":["test-imp-id", "test-imp-id-2"]
+      },
+      "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
index fdbe15ac48e..fa92fd45e49 100644
--- a/adapters/thetradedesk/thetradedesktest/supplemental/200-response-from-target.json
+++ b/adapters/thetradedesk/thetradedesktest/supplemental/200-response-from-target.json
@@ -46,8 +46,7 @@
             ],
             "Accept": [
               "application/json"
-            ],
-            "X-Integration-Type": ["1"]
+            ]
           },
           "body": {
             "id": "test-request-id",
diff --git a/adapters/thetradedesk/thetradedesktest/supplemental/204-response-from-target.json b/adapters/thetradedesk/thetradedesktest/supplemental/204-response-from-target.json
index a329982ea50..06407e7cb83 100644
--- a/adapters/thetradedesk/thetradedesktest/supplemental/204-response-from-target.json
+++ b/adapters/thetradedesk/thetradedesktest/supplemental/204-response-from-target.json
@@ -46,8 +46,7 @@
             ],
             "Accept": [
               "application/json"
-            ],
-            "X-Integration-Type": ["1"]
+            ]
           },
           "body": {
             "id": "test-request-id",
diff --git a/adapters/thetradedesk/thetradedesktest/supplemental/400-response-from-target.json b/adapters/thetradedesk/thetradedesktest/supplemental/400-response-from-target.json
index ad5ffc62b51..a177c5835fe 100644
--- a/adapters/thetradedesk/thetradedesktest/supplemental/400-response-from-target.json
+++ b/adapters/thetradedesk/thetradedesktest/supplemental/400-response-from-target.json
@@ -46,8 +46,7 @@
             ],
             "Accept": [
               "application/json"
-            ],
-            "X-Integration-Type": ["1"]
+            ]
           },
           "body": {
             "id": "test-request-id",
diff --git a/adapters/thetradedesk/thetradedesktest/supplemental/500-response-from-target.json b/adapters/thetradedesk/thetradedesktest/supplemental/500-response-from-target.json
index f2ccb342113..b6526b6fd20 100644
--- a/adapters/thetradedesk/thetradedesktest/supplemental/500-response-from-target.json
+++ b/adapters/thetradedesk/thetradedesktest/supplemental/500-response-from-target.json
@@ -46,8 +46,7 @@
             ],
             "Accept": [
               "application/json"
-            ],
-            "X-Integration-Type": ["1"]
+            ]
           },
           "body": {
             "id": "test-request-id",
diff --git a/adapters/thetradedesk/thetradedesktest/supplemental/invalid-mtype.json b/adapters/thetradedesk/thetradedesktest/supplemental/invalid-mtype.json
index 52b392eb835..077587438b2 100644
--- a/adapters/thetradedesk/thetradedesktest/supplemental/invalid-mtype.json
+++ b/adapters/thetradedesk/thetradedesktest/supplemental/invalid-mtype.json
@@ -25,8 +25,7 @@
           ],
           "Accept": [
             "application/json"
-          ],
-          "X-Integration-Type": ["1"]
+          ]
         },
         "body": {
           "id": "test-request-id",
diff --git a/adapters/thetradedesk/thetradedesktest/supplemental/invalid-supplySourceId.json b/adapters/thetradedesk/thetradedesktest/supplemental/invalid-supplySourceId.json
new file mode 100644
index 00000000000..69f7898ccff
--- /dev/null
+++ b/adapters/thetradedesk/thetradedesktest/supplemental/invalid-supplySourceId.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": {
+            "supplySourceId": 55555
+          }
+        }
+      }
+    ]
+  },
+  "httpCalls": [],
+  "expectedMakeRequestsErrors": [
+    {
+      "value": "cannot unmarshal openrtb_ext.ExtImpTheTradeDesk.SupplySourceId: expects \" or n, but found 5",
+      "comparison": "literal"
+    }
+  ]
+}
+
diff --git a/openrtb_ext/imp_thetradedesk.go b/openrtb_ext/imp_thetradedesk.go
index 89cca83f65e..bb2b628d773 100644
--- a/openrtb_ext/imp_thetradedesk.go
+++ b/openrtb_ext/imp_thetradedesk.go
@@ -1,8 +1,7 @@
 package openrtb_ext
 
-// ExtImpTheTradeDesk defines the contract for bidrequest.imp[i].ext
-// PublisherId is mandatory parameters, others are optional parameters
-
+// ExtImpTheTradeDesk defines the contract for bidrequest.imp[i].ext.prebid.bidder.thetradedesk
 type ExtImpTheTradeDesk struct {
-	PublisherId string `json:"publisherId"`
+	PublisherId    string `json:"publisherId"`
+	SupplySourceId string `json:"supplySourceId"`
 }
diff --git a/static/bidder-params/thetradedesk.json b/static/bidder-params/thetradedesk.json
index 5a85cf2f516..63dae6d9785 100644
--- a/static/bidder-params/thetradedesk.json
+++ b/static/bidder-params/thetradedesk.json
@@ -6,7 +6,13 @@
 	"properties": {
 		"publisherId": {
 			"type": "string",
+			"minLength": 1,
 			"description": "An ID which identifies the publisher"
+		},
+		"supplySourceId": {
+			"type":"string",
+			"minLength": 1,
+			"description": "An ID provided by TheTradeDesk used to determine which endpoint to use"
 		}
 	},
 	"required": ["publisherId"]


Reviews:
user: login: guscarreon
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-16T02:33:55Z
user: login: andre-gielow-ttd
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-16T15:02:11Z
user: login: andre-gielow-ttd
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-16T15:04:01Z
user: login: andre-gielow-ttd
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-16T15:04:54Z
user: login: andre-gielow-ttd
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-16T15:06:28Z
user: login: guscarreon
body: It's looking good. Left a comment and also the following nitpick unrelated to the changes in this PR: A small optimization can be done in the corner case where `len(imp.Banner.Format) == 1`. In said case, `imp.Banner.Format[0]` is already the only in the `imp.Banner.Format` list. So copying and assigning is unnecessary:
```
41     for _, imp := range request.Imp {
42
43         if imp.Banner != nil {
44 -           if len(imp.Banner.Format) > 0 {
   +           if len(imp.Banner.Format) > 1 {
45                 firstFormat := imp.Banner.Format[0]
46                 bannerCopy := *imp.Banner
47                 bannerCopy.H = &firstFormat.H
48                 bannerCopy.W = &firstFormat.W
49                 imp.Banner = &bannerCopy
50
51             }
52         }
adapters/thetradedesk/thetradedesk.go
```
Do you think it's worth changing?
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-16T18:23:28Z
user: login: andre-gielow-ttd
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T13:50:58Z
user: login: guscarreon
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T20:04:00Z
user: login: andre-gielow-ttd
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-18T02:49:27Z
user: login: guscarreon
body: Looks good to me @andre-gielow-ttd . I just left a suggestion about removing, if you want, a commented line. After resolving this comment, I'm ready to approve.
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-21T18:47:07Z
user: login: andre-gielow-ttd
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-21T19:39:42Z
user: login: guscarreon
body: LGTM
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-04-21T20:29:49Z
user: login: VeronikaSolovei9
body: I left comments mostly related to how to make the code cleaner. Please review
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-21T22:11:47Z
user: login: andre-gielow-ttd
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-22T13:22:28Z
user: login: andre-gielow-ttd
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-22T13:26:20Z
user: login: VeronikaSolovei9
body: Thank you for addressing comments, approved
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-23T00:27:24Z
user: login: guscarreon
body: LGTM
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-23T14:02:53Z

Review comments:
diff_hunk: @@ -87,29 +89,57 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.E
 		return nil, errs
 	}
 
+	bidderEndpoint, err := a.buildEndpointURL(request.Imp)
path: adapters/thetradedesk/thetradedesk.go
user: login: guscarreon
body: Let `n` be equal to the length of `request.Imp`. `MakeRequests` will do all of the following in sequence:
 1. Traverse `request.Imp` in `O(n)` time inside the `getPublisherId(request.Imp)` call
 1. Traverse `request.Imp` in `O(n)` time in the `for` loop on line 41
 1. Traverse `request.Imp` in `O(n)` time inside the `a.buildEndpointURL(request.Imp)` call in line 92

Consecutive `O(n)` calls don't make for a greater time complexity but, in this case I think is worth consideration given that both `getPublisherId(request.Imp)` and `a.buildEndpointURL(request.Imp)` end up making calls to `getImpressionExt(&imp)`, which is a function that unmarshals the `request.Imp[i].ext`. Can we optimize to only traverse `request.Imp` once? The reason for this ask is that, in the worst case scenario, all `request.Imp[i].ext` get unmarshaled twice. If we put the `getPublisherId(request.Imp)` and `a.buildEndpointURL(request.Imp)` functionality inside the `for` loop on line 41, we can make for less `jsonutil.Unmarshal(imp.Ext, &bidderExt)` calls which are particularly expensive.

I guess it could look something like:
```
 32   func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
 33 -     pubID, err := getPublisherId(request.Imp)
 34 -
 35 -     if err != nil {
 36 -         return nil, []error{err}
 37 -     }
 38  
 39       modifiedImps := make([]openrtb2.Imp, 0, len(request.Imp))
    +     var pubID, supplySourceId string
 40  
 41       for _, imp := range request.Imp {
 42  
 43           if imp.Banner != nil {
 44               if len(imp.Banner.Format) > 0 {
 45                   firstFormat := imp.Banner.Format[0]
 46                   bannerCopy := *imp.Banner
 47                   bannerCopy.H = &firstFormat.H
 48                   bannerCopy.W = &firstFormat.W
 49                   imp.Banner = &bannerCopy
 50  
 51               }
 52           }
 53  
 54           modifiedImps = append(modifiedImps, imp)
    +
    +         // If pubID or supplySourceId haven't been found already, unmarshal and store them in `pubID` and `supplySourceId`
    +         if len(pubID) == 0 || len(supplySourceId) == 0 {
    +             var ttdExt, err = getImpressionExt(imp)
    +             if err != nil {
    +                 return nil, []error{err}
    +             }
    +             if len(pubID) == 0 {
    +                 pubID = ttdExt.PublisherId
    +             }
    +             if len(supplySourceId) == 0 {
    +                 supplySourceId = strings.TrimSpace(ttdExt.SupplySourceId)
    +             }
    +         }
    +
 55       }
 56  
 57      request.Imp = modifiedImps
 58 
 59      if request.Site != nil {
 60 *-- 31 lines: siteCopy := *request.Site---------------------------------------------------------------------------------------------
 91 
 92 -    bidderEndpoint, err := a.buildEndpointURL(request.Imp)
 92 +    bidderEndpoint, err := a.buildEndpointURL(request.Imp, supplySourceId) //<-- buildEndpointURL should be modified to take supplySourceId
 93      if err != nil {
 94          return nil, []error{errors.New("Failed to build endpoint URL")}
 95      }
 96 
 97      headers := http.Header{}
adapters/thetradedesk/thetradedesk.go
```
Do you agree with the approach above? Do you think it is necessary? Let me know your thoughts.

created_at: 2025-04-16T02:27:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -35,9 +35,9 @@ func TestInvalidParams(t *testing.T) {
 }
 
 var validParams = []string{
-	`{"publisherId": "123456"}`,
-	`{"publisherId": "pub-123456"}`,
-	`{"publisherId": "publisherIDAllString"}`,
+	`{"publisherId": "123456", "supplySourceId": "xyz_123"}`,
+	`{"publisherId": "pub-123456", "supplySourceId": "direct_123"}`,
+	`{"publisherId": "publisherIDAllString", "supplySourceId": "direct123"}`,
path: adapters/thetradedesk/params_test.go
user: login: guscarreon
body: Given the information in `static/bidder-params/thetradedesk.json`, we know `supplySourceId` is not required. Please include a test case in valid bidder params where `supplySourceId` is not included. 
```
37   var validParams = []string{
   +     `{"publisherId": "123456"}`,
38       `{"publisherId": "123456", "supplySourceId": "xyz_123"}`,
39       `{"publisherId": "pub-123456", "supplySourceId": "direct_123"}`,
40       `{"publisherId": "publisherIDAllString", "supplySourceId": "direct123"}`,
41   }
adapters/thetradedesk/params_test.go
```

created_at: 2025-04-16T02:31:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -48,6 +48,6 @@ var invalidParams = []string{
 	`4.2`,
 	`[]`,
 	`{}`,
-	`{"publisherId": 123456}`,
-	`{"publisherId": 0}`,
+	`{"publisherId": 123456, "supplySourceId": 123}`,
+	`{"publisherId": 0, "supplySourceId": 0}`,
path: adapters/thetradedesk/params_test.go
user: login: guscarreon
body: Given the information in `static/bidder-params/thetradedesk.json`, we know `publisherId` is required. Please include a test case in valid bidder params where `publisherId` is not included. 
```
42  
43   var invalidParams = []string{
44       ``,
45       `null`,
46       `true`,
47       `5`,
48       `4.2`,
49       `[]`,
50       `{}`,
51       `{"publisherId": 123456, "supplySourceId": 123}`,
52       `{"publisherId": 0, "supplySourceId": 0}`,
52 +     `{"supplySourceId": "publisher_xyz"}`,
53   }
adapters/thetradedesk/params_test.go
```

created_at: 2025-04-16T02:32:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -87,29 +89,57 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.E
 		return nil, errs
 	}
 
+	bidderEndpoint, err := a.buildEndpointURL(request.Imp)
+	if err != nil {
+		return nil, []error{errors.New("Failed to build endpoint URL")}
+	}
+
 	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,
+		Uri:     bidderEndpoint,
 		Body:    reqJSON,
 		Headers: headers,
 		ImpIDs:  openrtb_ext.GetImpIDs(request.Imp),
 	}}, errs
 }
 
-func getPublisherId(impressions []openrtb2.Imp) (string, error) {
-	for _, imp := range impressions {
+func (a *adapter) buildEndpointURL(impressions []openrtb2.Imp) (string, error) {
+	template, err := template.New("endpointTemplate").Parse(a.bidderEndpoint)
+	if err != nil {
+		return "", fmt.Errorf("unable to parse endpoint url template: %v", err)
+	}
 
-		var bidderExt ExtImpBidderTheTradeDesk
-		if err := jsonutil.Unmarshal(imp.Ext, &bidderExt); err != nil {
-			return "", err
+	supplySourceId := ""
+	for _, imp := range impressions {
+		supplySourceId, err = getSupplySourceId(&imp)
+		// When found just stop
+		if supplySourceId != "" {
+			break
 		}
+	}
+
+	if supplySourceId == "" {
+		supplySourceId = a.defaultSupplySourceId
+	}
+
+	urlParams := macros.EndpointTemplateParams{SupplyId: supplySourceId}
+	bidderEndpoint, err := macros.ResolveMacros(template, urlParams)
path: adapters/thetradedesk/thetradedesk.go
user: login: guscarreon
body: Now that this function is doing many of the things that `Builder()` used to do, can we please remove unnecessary processes and unused local variables from `Builder()`?
```
229   func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
230 -     template, err := template.New("endpointTemplate").Parse(config.Endpoint)
231 -     if err != nil {
232 -         return nil, fmt.Errorf("unable to parse endpoint url template: %v", err)
233 -     }
234  
235       if len(config.ExtraAdapterInfo) > 0 {
236           isValidEndpoint, err := regexp.Match("([a-z]+)$", []byte(config.ExtraAdapterInfo))
237           if !isValidEndpoint || err != nil {
238               return nil, errors.New("ExtraAdapterInfo must be a simple string provided by TheTradeDesk")
239           }
240       }
241  
242 -     urlParams := macros.EndpointTemplateParams{SupplyId: config.ExtraAdapterInfo}
243 -     _, err = macros.ResolveMacros(template, urlParams)
244 -
245 -     if err != nil {
246 -         return nil, fmt.Errorf("unable to resolve endpoint macros: %v", err)
247 -     }
248 -
249       return &adapter{
250           bidderEndpoint:        config.Endpoint,
251           defaultSupplySourceId: config.ExtraAdapterInfo,
252       }, nil
253   }
adapters/thetradedesk/thetradedesk.go
```

created_at: 2025-04-16T02:33:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -87,29 +89,57 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.E
 		return nil, errs
 	}
 
+	bidderEndpoint, err := a.buildEndpointURL(request.Imp)
+	if err != nil {
+		return nil, []error{errors.New("Failed to build endpoint URL")}
+	}
+
 	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,
+		Uri:     bidderEndpoint,
 		Body:    reqJSON,
 		Headers: headers,
 		ImpIDs:  openrtb_ext.GetImpIDs(request.Imp),
 	}}, errs
 }
 
-func getPublisherId(impressions []openrtb2.Imp) (string, error) {
-	for _, imp := range impressions {
+func (a *adapter) buildEndpointURL(impressions []openrtb2.Imp) (string, error) {
+	template, err := template.New("endpointTemplate").Parse(a.bidderEndpoint)
+	if err != nil {
+		return "", fmt.Errorf("unable to parse endpoint url template: %v", err)
+	}
 
-		var bidderExt ExtImpBidderTheTradeDesk
-		if err := jsonutil.Unmarshal(imp.Ext, &bidderExt); err != nil {
-			return "", err
+	supplySourceId := ""
+	for _, imp := range impressions {
+		supplySourceId, err = getSupplySourceId(&imp)
+		// When found just stop
+		if supplySourceId != "" {
+			break
 		}
+	}
+
+	if supplySourceId == "" {
+		supplySourceId = a.defaultSupplySourceId
+	}
+
+	urlParams := macros.EndpointTemplateParams{SupplyId: supplySourceId}
+	bidderEndpoint, err := macros.ResolveMacros(template, urlParams)
path: adapters/thetradedesk/thetradedesk.go
user: login: andre-gielow-ttd
body: The adapter should support both direct and indirect path, meaning that a `supplySourceId` parameter is not required.
I left some validation here to early detect in case some self-hosted publisher misconfigures it.
created_at: 2025-04-16T15:02:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -48,6 +48,6 @@ var invalidParams = []string{
 	`4.2`,
 	`[]`,
 	`{}`,
-	`{"publisherId": 123456}`,
-	`{"publisherId": 0}`,
+	`{"publisherId": 123456, "supplySourceId": 123}`,
+	`{"publisherId": 0, "supplySourceId": 0}`,
path: adapters/thetradedesk/params_test.go
user: login: andre-gielow-ttd
body: This should be covered in `TestGetExtensionInfo`
created_at: 2025-04-16T15:04:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -35,9 +35,9 @@ func TestInvalidParams(t *testing.T) {
 }
 
 var validParams = []string{
-	`{"publisherId": "123456"}`,
-	`{"publisherId": "pub-123456"}`,
-	`{"publisherId": "publisherIDAllString"}`,
+	`{"publisherId": "123456", "supplySourceId": "xyz_123"}`,
+	`{"publisherId": "pub-123456", "supplySourceId": "direct_123"}`,
+	`{"publisherId": "publisherIDAllString", "supplySourceId": "direct123"}`,
path: adapters/thetradedesk/params_test.go
user: login: andre-gielow-ttd
body: Test cases added:
`TestGetExtensionInfo`
`adapters/thetradedesk/thetradedesktest/exemplary/use-supply-source-id-param.json`
`adapters/thetradedesk/thetradedesktest/supplemental/invalid-supplySourceId.json`
created_at: 2025-04-16T15:04:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -87,29 +89,57 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.E
 		return nil, errs
 	}
 
+	bidderEndpoint, err := a.buildEndpointURL(request.Imp)
path: adapters/thetradedesk/thetradedesk.go
user: login: andre-gielow-ttd
body: That makes sense. Good suggestion. I combined the involved functions into `getExtensionInfo`. As soon as there are both params, they're returned.
created_at: 2025-04-16T15:06:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -87,37 +89,83 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.E
 		return nil, errs
 	}
 
+	bidderEndpoint, err := a.buildEndpointURL(supplySourceId)
+	if err != nil {
+		return nil, []error{errors.New("Failed to build endpoint URL")}
+	}
+
 	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,
+		Uri:     bidderEndpoint,
 		Body:    reqJSON,
 		Headers: headers,
 		ImpIDs:  openrtb_ext.GetImpIDs(request.Imp),
 	}}, errs
 }
 
-func getPublisherId(impressions []openrtb2.Imp) (string, error) {
+func (a *adapter) buildEndpointURL(supplySourceId string) (string, error) {
+	template, err := template.New("endpointTemplate").Parse(a.bidderEndpoint)
+	if err != nil {
+		return "", fmt.Errorf("unable to parse endpoint url template: %v", err)
+	}
+
+	if supplySourceId == "" {
+		supplySourceId = a.defaultSupplySourceId
path: adapters/thetradedesk/thetradedesk.go
user: login: guscarreon
body: Given that `Builder()` already did `macros.ResolveMacros()` with the `a.defaultSupplySourceId`, it seems unnecessary to do it again here when `supplySourceId == ""`. Can we keep, the resolved URL string instead?
```
222   func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
223       template, err := template.New("endpointTemplate").Parse(config.Endpoint)
224 *-- 10 lines: if err != nil {-------------------------------------------------------------------------------------------
234  
235       urlParams := macros.EndpointTemplateParams{SupplyId: config.ExtraAdapterInfo}
236       _, err = macros.ResolveMacros(template, urlParams)
    +     resolvedURL, err = macros.ResolveMacros(template, urlParams)
237  
238       if err != nil {
239           return nil, fmt.Errorf("unable to resolve endpoint macros: %v", err)
240       }
241  
242       return &adapter{
243           bidderEndpoint:        config.Endpoint,
244 -         defaultSupplySourceId: config.ExtraAdapterInfo,
    +         defaultSupplySourceEndpoint: resolvedURL,
245       }, nil
246   }
adapters/thetradedesk/thetradedesk.go
```
So we don't have to `macros.ResolveMacros()` again if not needed?
```
110   func (a *adapter) buildEndpointURL(supplySourceId string) (string, error) {
111       template, err := template.New("endpointTemplate").Parse(a.bidderEndpoint)
112       if err != nil {
113           return "", fmt.Errorf("unable to parse endpoint url template: %v", err)
114       }
115  
116       if supplySourceId == "" {
117 -         supplySourceId = a.defaultSupplySourceId
118 -     }
119 -
120 -     if strings.TrimSpace(supplySourceId) == "" {
121 -         return "", errors.New("ext.bidder.params.supplySourceId or defaultSupplySourceId must be provided")
    +         // supplySourceId was not provided, return a.bidderEndpoint with the defaultSupplySourceId that was already created inside Builder()
    +         return a.defaultSupplySourceEndpoint, nil
122       }
123  
124       urlParams := macros.EndpointTemplateParams{SupplyId: supplySourceId}
125       bidderEndpoint, err := macros.ResolveMacros(template, urlParams)
126  
127       if err != nil {
128           return "", fmt.Errorf("unable to resolve endpoint macros: %v", err)
129       }
130  
131       return bidderEndpoint, nil
132   }
adapters/thetradedesk/thetradedesk.go
```
What do you think?
created_at: 2025-04-16T18:06:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -87,37 +89,83 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.E
 		return nil, errs
 	}
 
+	bidderEndpoint, err := a.buildEndpointURL(supplySourceId)
+	if err != nil {
+		return nil, []error{errors.New("Failed to build endpoint URL")}
+	}
+
 	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,
+		Uri:     bidderEndpoint,
 		Body:    reqJSON,
 		Headers: headers,
 		ImpIDs:  openrtb_ext.GetImpIDs(request.Imp),
 	}}, errs
 }
 
-func getPublisherId(impressions []openrtb2.Imp) (string, error) {
+func (a *adapter) buildEndpointURL(supplySourceId string) (string, error) {
+	template, err := template.New("endpointTemplate").Parse(a.bidderEndpoint)
+	if err != nil {
+		return "", fmt.Errorf("unable to parse endpoint url template: %v", err)
+	}
+
+	if supplySourceId == "" {
+		supplySourceId = a.defaultSupplySourceId
path: adapters/thetradedesk/thetradedesk.go
user: login: andre-gielow-ttd
body: Good suggestion, that makes sense. I change the adapters to have a full default endpoint parameter. In the `buildEndpoint` func I can just return it if param `supplySourceId` is not provided.
created_at: 2025-04-17T13:50:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -87,37 +89,83 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.E
 		return nil, errs
 	}
 
+	bidderEndpoint, err := a.buildEndpointURL(supplySourceId)
+	if err != nil {
+		return nil, []error{errors.New("Failed to build endpoint URL")}
+	}
+
 	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,
+		Uri:     bidderEndpoint,
 		Body:    reqJSON,
 		Headers: headers,
 		ImpIDs:  openrtb_ext.GetImpIDs(request.Imp),
 	}}, errs
 }
 
-func getPublisherId(impressions []openrtb2.Imp) (string, error) {
+func (a *adapter) buildEndpointURL(supplySourceId string) (string, error) {
+	template, err := template.New("endpointTemplate").Parse(a.bidderEndpoint)
+	if err != nil {
+		return "", fmt.Errorf("unable to parse endpoint url template: %v", err)
+	}
+
+	if supplySourceId == "" {
+		supplySourceId = a.defaultSupplySourceId
path: adapters/thetradedesk/thetradedesk.go
user: login: guscarreon
body:  Awesome @andre-gielow-ttd thank you for addressing this. Is it possible to go one step further and avoid that `template.New("endpointTemplate").Parse(config.Endpoint)` call in `buildEndpointURL(supplySourceId string)`? Or does `buildEndpointURL(supplySourceId string)` need a fresh template at every turn?
```
 23   type adapter struct {
 24 -     bidderEndpointTemplate string
    +     template               *template.Template
 25       defaultEndpoint        string
 26   }
 27  
 28   type ExtImpBidderTheTradeDesk struct {
 29       adapters.ExtImpBidder
 30   }
 31 *-- 78 lines: func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {--
109  
110   func (a *adapter) buildEndpointURL(supplySourceId string) (string, error) {
111       if strings.TrimSpace(supplySourceId) == "" {
112           return a.defaultEndpoint, nil
113       }
114  
115 -     template, err := template.New("endpointTemplate").Parse(a.bidderEndpointTemplate)
116 -     if err != nil {
117 -         return "", fmt.Errorf("unable to parse endpoint url template: %v", err)
118 -     }
119  
120       urlParams := macros.EndpointTemplateParams{SupplyId: supplySourceId}
121 -     bidderEndpoint, err := macros.ResolveMacros(template, urlParams)
    +     bidderEndpoint, err := macros.ResolveMacros(a.template, urlParams)
122  
123       if err != nil {
124           return "", fmt.Errorf("unable to resolve endpoint macros: %v", err)
125       }
126  
127       return bidderEndpoint, nil
128 *--90 lines: }---------------------------------------------------------------------------------------------------------------------------------------
218   func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
219       if len(config.ExtraAdapterInfo) > 0 {
220           isValidEndpoint, err := regexp.Match("([a-z]+)$", []byte(config.ExtraAdapterInfo))
221           if !isValidEndpoint || err != nil {
222               return nil, errors.New("ExtraAdapterInfo must be a simple string provided by TheTradeDesk")
223           }
224       }
225
226       template, err := template.New("endpointTemplate").Parse(config.Endpoint)
227       if err != nil {
228           return nil, fmt.Errorf("unable to parse endpoint url template: %v", err)
229       }
230
231       urlParams := macros.EndpointTemplateParams{SupplyId: config.ExtraAdapterInfo}
232       defaultEndpoint, err := macros.ResolveMacros(template, urlParams)
233  
234       if err != nil {
235           return nil, fmt.Errorf("unable to resolve endpoint macros: %v", err)
236       }
237
238       return &adapter{
239 -         bidderEndpointTemplate: config.Endpoint,
    +         template:        template,
240           defaultEndpoint:        defaultEndpoint,
241       }, nil
242   }
adapters/thetradedesk/thetradedesk.go
```

created_at: 2025-04-17T20:04:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -87,37 +89,83 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.E
 		return nil, errs
 	}
 
+	bidderEndpoint, err := a.buildEndpointURL(supplySourceId)
+	if err != nil {
+		return nil, []error{errors.New("Failed to build endpoint URL")}
+	}
+
 	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,
+		Uri:     bidderEndpoint,
 		Body:    reqJSON,
 		Headers: headers,
 		ImpIDs:  openrtb_ext.GetImpIDs(request.Imp),
 	}}, errs
 }
 
-func getPublisherId(impressions []openrtb2.Imp) (string, error) {
+func (a *adapter) buildEndpointURL(supplySourceId string) (string, error) {
+	template, err := template.New("endpointTemplate").Parse(a.bidderEndpoint)
+	if err != nil {
+		return "", fmt.Errorf("unable to parse endpoint url template: %v", err)
+	}
+
+	if supplySourceId == "" {
+		supplySourceId = a.defaultSupplySourceId
path: adapters/thetradedesk/thetradedesk.go
user: login: andre-gielow-ttd
body: Changed to create the template only once.
created_at: 2025-04-18T02:49:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -87,37 +90,74 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.E
 		return nil, errs
 	}
 
+	bidderEndpoint, err := a.buildEndpointURL(supplySourceId)
+	if err != nil {
+		return nil, []error{errors.New("Failed to build endpoint URL")}
+	}
+
 	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)
+	//headers.Add("x-integration-type", PREBID_INTEGRATION_TYPE) this will be parsed and added conditionally later
path: adapters/thetradedesk/thetradedesk.go
user: login: guscarreon
body:   Nitpick: If we don't need this line anymore, can we remove?
created_at: 2025-04-21T18:44:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -87,37 +90,74 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.E
 		return nil, errs
 	}
 
+	bidderEndpoint, err := a.buildEndpointURL(supplySourceId)
+	if err != nil {
+		return nil, []error{errors.New("Failed to build endpoint URL")}
+	}
+
 	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)
+	//headers.Add("x-integration-type", PREBID_INTEGRATION_TYPE) this will be parsed and added conditionally later
path: adapters/thetradedesk/thetradedesk.go
user: login: andre-gielow-ttd
body: I will handle this in another MR. The gist is, this header will change depending on what is sending traffic to PBS.
SDK will have a different number than PrebidJS.

created_at: 2025-04-21T19:39:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -87,37 +90,74 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.E
 		return nil, errs
 	}
 
+	bidderEndpoint, err := a.buildEndpointURL(supplySourceId)
+	if err != nil {
+		return nil, []error{errors.New("Failed to build endpoint URL")}
+	}
+
 	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)
+	//headers.Add("x-integration-type", PREBID_INTEGRATION_TYPE) this will be parsed and added conditionally later
 	return []*adapters.RequestData{{
 		Method:  "POST",
-		Uri:     a.bidderEndpoint,
+		Uri:     bidderEndpoint,
 		Body:    reqJSON,
 		Headers: headers,
 		ImpIDs:  openrtb_ext.GetImpIDs(request.Imp),
 	}}, errs
 }
 
-func getPublisherId(impressions []openrtb2.Imp) (string, error) {
+func (a *adapter) buildEndpointURL(supplySourceId string) (string, error) {
+	if strings.TrimSpace(supplySourceId) == "" {
+		return a.defaultEndpoint, nil
+	}
+
+	urlParams := macros.EndpointTemplateParams{SupplyId: supplySourceId}
+	bidderEndpoint, err := macros.ResolveMacros(a.templateEndpoint, urlParams)
+
+	if err != nil {
+		return "", fmt.Errorf("unable to resolve endpoint macros: %v", err)
+	}
+
+	return bidderEndpoint, nil
+}
+
+func getExtensionInfo(impressions []openrtb2.Imp) (string, string, error) {
+	publisherId := ""
+	supplySourceId := ""
 	for _, imp := range impressions {
+		var ttdExt, err = getImpressionExt(&imp)
+		if err != nil {
+			return "", "", err
+		}
 
-		var bidderExt ExtImpBidderTheTradeDesk
-		if err := jsonutil.Unmarshal(imp.Ext, &bidderExt); err != nil {
-			return "", err
+		if strings.TrimSpace(ttdExt.PublisherId) != "" && strings.TrimSpace(publisherId) == "" {
+			publisherId = ttdExt.PublisherId
 		}
 
-		var ttdExt openrtb_ext.ExtImpTheTradeDesk
-		if err := jsonutil.Unmarshal(bidderExt.Bidder, &ttdExt); err != nil {
-			return "", err
+		if strings.TrimSpace(ttdExt.SupplySourceId) != "" && strings.TrimSpace(supplySourceId) == "" {
+			supplySourceId = ttdExt.SupplySourceId
 		}
 
-		if ttdExt.PublisherId != "" {
-			return ttdExt.PublisherId, nil
+		if strings.TrimSpace(publisherId) != "" && strings.TrimSpace(supplySourceId) != "" {
+			break
 		}
 	}
-	return "", nil
+	return publisherId, supplySourceId, nil
+}
+
+func getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ExtImpTheTradeDesk, error) {
+	var bidderExt ExtImpBidderTheTradeDesk
path: adapters/thetradedesk/thetradedesk.go
user: login: VeronikaSolovei9
body: This type is not needed. Please use `var bidderExt adapters.ExtImpBidder` instead
created_at: 2025-04-21T21:28:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -87,37 +90,74 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.E
 		return nil, errs
 	}
 
+	bidderEndpoint, err := a.buildEndpointURL(supplySourceId)
+	if err != nil {
+		return nil, []error{errors.New("Failed to build endpoint URL")}
+	}
+
 	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)
+	//headers.Add("x-integration-type", PREBID_INTEGRATION_TYPE) this will be parsed and added conditionally later
 	return []*adapters.RequestData{{
 		Method:  "POST",
-		Uri:     a.bidderEndpoint,
+		Uri:     bidderEndpoint,
 		Body:    reqJSON,
 		Headers: headers,
 		ImpIDs:  openrtb_ext.GetImpIDs(request.Imp),
 	}}, errs
 }
 
-func getPublisherId(impressions []openrtb2.Imp) (string, error) {
+func (a *adapter) buildEndpointURL(supplySourceId string) (string, error) {
+	if strings.TrimSpace(supplySourceId) == "" {
+		return a.defaultEndpoint, nil
+	}
+
+	urlParams := macros.EndpointTemplateParams{SupplyId: supplySourceId}
+	bidderEndpoint, err := macros.ResolveMacros(a.templateEndpoint, urlParams)
+
+	if err != nil {
+		return "", fmt.Errorf("unable to resolve endpoint macros: %v", err)
+	}
+
+	return bidderEndpoint, nil
+}
+
+func getExtensionInfo(impressions []openrtb2.Imp) (string, string, error) {
+	publisherId := ""
+	supplySourceId := ""
 	for _, imp := range impressions {
+		var ttdExt, err = getImpressionExt(&imp)
+		if err != nil {
+			return "", "", err
+		}
 
-		var bidderExt ExtImpBidderTheTradeDesk
-		if err := jsonutil.Unmarshal(imp.Ext, &bidderExt); err != nil {
-			return "", err
+		if strings.TrimSpace(ttdExt.PublisherId) != "" && strings.TrimSpace(publisherId) == "" {
+			publisherId = ttdExt.PublisherId
 		}
 
-		var ttdExt openrtb_ext.ExtImpTheTradeDesk
-		if err := jsonutil.Unmarshal(bidderExt.Bidder, &ttdExt); err != nil {
-			return "", err
+		if strings.TrimSpace(ttdExt.SupplySourceId) != "" && strings.TrimSpace(supplySourceId) == "" {
+			supplySourceId = ttdExt.SupplySourceId
 		}
 
-		if ttdExt.PublisherId != "" {
-			return ttdExt.PublisherId, nil
+		if strings.TrimSpace(publisherId) != "" && strings.TrimSpace(supplySourceId) != "" {
+			break
 		}
 	}
-	return "", nil
+	return publisherId, supplySourceId, nil
path: adapters/thetradedesk/thetradedesk.go
user: login: VeronikaSolovei9
body: This function contradicts with "thetraderdesk.json" -> required parameters. "publisherId" is not really a required property.
You can use "oneOf" keyword (look for examples in other bidders), for instance: https://github.com/VeronikaSolovei9/prebid-server/blob/ba9d64ed53ea964ba4d3dd3bea01d1ead8835cc7/static/bidder-params/kargo.json#L16

Or/And you can specify ""minLength": 1" for publisherId and supplySourceId, so strings.TrimSpace will not be needed.

Based on this line143: `if strings.TrimSpace(publisherId) != "" && strings.TrimSpace(supplySourceId) != ""` are both of them needed? 

Also with this logic publisherId and supplySourceId can be returned from different impressions, is this expected behavior? 

Example:

```json
{
"imp":[
{
   "id": "imp_1",
   "ext":{
      "TheTradeDesk": {
         "publisherId": "",
         "supplySourceId": "34"
      }
}},

{
   "id": "imp_2",
   "ext": {
      "TheTradeDesk": {
         "publisherId": "56",
         "supplySourceId": ""
      }
}}
]
}

```

return publisherId, supplySourceId, nil ---> "56", "34", nil
created_at: 2025-04-21T21:56:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -87,37 +90,74 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.E
 		return nil, errs
 	}
 
+	bidderEndpoint, err := a.buildEndpointURL(supplySourceId)
+	if err != nil {
+		return nil, []error{errors.New("Failed to build endpoint URL")}
+	}
+
 	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)
+	//headers.Add("x-integration-type", PREBID_INTEGRATION_TYPE) this will be parsed and added conditionally later
 	return []*adapters.RequestData{{
 		Method:  "POST",
-		Uri:     a.bidderEndpoint,
+		Uri:     bidderEndpoint,
 		Body:    reqJSON,
 		Headers: headers,
 		ImpIDs:  openrtb_ext.GetImpIDs(request.Imp),
 	}}, errs
 }
 
-func getPublisherId(impressions []openrtb2.Imp) (string, error) {
+func (a *adapter) buildEndpointURL(supplySourceId string) (string, error) {
+	if strings.TrimSpace(supplySourceId) == "" {
+		return a.defaultEndpoint, nil
+	}
+
+	urlParams := macros.EndpointTemplateParams{SupplyId: supplySourceId}
+	bidderEndpoint, err := macros.ResolveMacros(a.templateEndpoint, urlParams)
+
+	if err != nil {
+		return "", fmt.Errorf("unable to resolve endpoint macros: %v", err)
+	}
+
+	return bidderEndpoint, nil
+}
+
+func getExtensionInfo(impressions []openrtb2.Imp) (string, string, error) {
+	publisherId := ""
+	supplySourceId := ""
 	for _, imp := range impressions {
+		var ttdExt, err = getImpressionExt(&imp)
+		if err != nil {
+			return "", "", err
+		}
 
-		var bidderExt ExtImpBidderTheTradeDesk
-		if err := jsonutil.Unmarshal(imp.Ext, &bidderExt); err != nil {
-			return "", err
+		if strings.TrimSpace(ttdExt.PublisherId) != "" && strings.TrimSpace(publisherId) == "" {
+			publisherId = ttdExt.PublisherId
 		}
 
-		var ttdExt openrtb_ext.ExtImpTheTradeDesk
-		if err := jsonutil.Unmarshal(bidderExt.Bidder, &ttdExt); err != nil {
-			return "", err
+		if strings.TrimSpace(ttdExt.SupplySourceId) != "" && strings.TrimSpace(supplySourceId) == "" {
+			supplySourceId = ttdExt.SupplySourceId
 		}
 
-		if ttdExt.PublisherId != "" {
-			return ttdExt.PublisherId, nil
+		if strings.TrimSpace(publisherId) != "" && strings.TrimSpace(supplySourceId) != "" {
+			break
 		}
 	}
-	return "", nil
+	return publisherId, supplySourceId, nil
+}
+
+func getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ExtImpTheTradeDesk, error) {
+	var bidderExt ExtImpBidderTheTradeDesk
path: adapters/thetradedesk/thetradedesk.go
user: login: andre-gielow-ttd
body: Removed `ExtImpBidderTheTradeDesk` and just using `adapters.ExtImpBidder` now.
created_at: 2025-04-22T13:22:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -87,37 +90,74 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.E
 		return nil, errs
 	}
 
+	bidderEndpoint, err := a.buildEndpointURL(supplySourceId)
+	if err != nil {
+		return nil, []error{errors.New("Failed to build endpoint URL")}
+	}
+
 	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)
+	//headers.Add("x-integration-type", PREBID_INTEGRATION_TYPE) this will be parsed and added conditionally later
 	return []*adapters.RequestData{{
 		Method:  "POST",
-		Uri:     a.bidderEndpoint,
+		Uri:     bidderEndpoint,
 		Body:    reqJSON,
 		Headers: headers,
 		ImpIDs:  openrtb_ext.GetImpIDs(request.Imp),
 	}}, errs
 }
 
-func getPublisherId(impressions []openrtb2.Imp) (string, error) {
+func (a *adapter) buildEndpointURL(supplySourceId string) (string, error) {
+	if strings.TrimSpace(supplySourceId) == "" {
+		return a.defaultEndpoint, nil
+	}
+
+	urlParams := macros.EndpointTemplateParams{SupplyId: supplySourceId}
+	bidderEndpoint, err := macros.ResolveMacros(a.templateEndpoint, urlParams)
+
+	if err != nil {
+		return "", fmt.Errorf("unable to resolve endpoint macros: %v", err)
+	}
+
+	return bidderEndpoint, nil
+}
+
+func getExtensionInfo(impressions []openrtb2.Imp) (string, string, error) {
+	publisherId := ""
+	supplySourceId := ""
 	for _, imp := range impressions {
+		var ttdExt, err = getImpressionExt(&imp)
+		if err != nil {
+			return "", "", err
+		}
 
-		var bidderExt ExtImpBidderTheTradeDesk
-		if err := jsonutil.Unmarshal(imp.Ext, &bidderExt); err != nil {
-			return "", err
+		if strings.TrimSpace(ttdExt.PublisherId) != "" && strings.TrimSpace(publisherId) == "" {
+			publisherId = ttdExt.PublisherId
 		}
 
-		var ttdExt openrtb_ext.ExtImpTheTradeDesk
-		if err := jsonutil.Unmarshal(bidderExt.Bidder, &ttdExt); err != nil {
-			return "", err
+		if strings.TrimSpace(ttdExt.SupplySourceId) != "" && strings.TrimSpace(supplySourceId) == "" {
+			supplySourceId = ttdExt.SupplySourceId
 		}
 
-		if ttdExt.PublisherId != "" {
-			return ttdExt.PublisherId, nil
+		if strings.TrimSpace(publisherId) != "" && strings.TrimSpace(supplySourceId) != "" {
+			break
 		}
 	}
-	return "", nil
+	return publisherId, supplySourceId, nil
path: adapters/thetradedesk/thetradedesk.go
user: login: andre-gielow-ttd
body: 143 line is just a optimization for when we found both to exit the function.
I might remove the `publisherId` parsing in a future MR, for now I'm keeping it to have a similar behavior as our PBJS adapter: https://github.com/prebid/Prebid.js/blob/d71b0c83b7e92b2dbd14bb1b663576dea0c651fd/modules/ttdBidAdapter.js#L129

For `supplySourceId` that is used to later build the endpoint. The endpoint can be build either by `supplySourceId` (in case we have a intermediary or by `extra_info` in the config in the case the publisher self hosts it.

Your example test case is correct on the behavior.
created_at: 2025-04-22T13:26:20Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-04-15T17:56:30Z
body: ## Code coverage summary 
Note: 
- Prebid team doesn't anticipate tests covering code paths that might result in marshal and unmarshal errors 
- Coverage summary encompasses all commits leading up to the latest one, dbedfb8dc3f66072fc8c2bba554c08ee130e0953 
#### thetradedesk 
Refer [here](https://htmlpreview.github.io/?https://github.com/prebid/prebid-server/coverage-preview/.github/preview/14476139695_1744739789/thetradedesk.html) for heat map coverage report 
``` 
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:31:	MakeRequests		97.8%
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:109:	buildEndpointURL	82.4%
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:141:	getPublisherId		100.0%
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:155:	getSupplySourceId	0.0%
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:163:	getImpressionExt	85.7%
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:176:	MakeBids		100.0%
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:214:	getBidType		100.0%
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:227:	Builder			91.7%
total:										(statements)		91.3%

 ``` 

author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-15T18:16:18Z
body: ## Code coverage summary 
Note: 
- Prebid team doesn't anticipate tests covering code paths that might result in marshal and unmarshal errors 
- Coverage summary encompasses all commits leading up to the latest one, 31978bcceebb9886977acad368a5ac4db986fb42 
#### thetradedesk 
Refer [here](https://htmlpreview.github.io/?https://github.com/prebid/prebid-server/coverage-preview/.github/preview/14476507248_1744740976/thetradedesk.html) for heat map coverage report 
``` 
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:32:	MakeRequests		97.8%
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:110:	buildEndpointURL	86.7%
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:139:	getPublisherId		100.0%
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:153:	getSupplySourceId	83.3%
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:165:	getImpressionExt	85.7%
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:178:	MakeBids		100.0%
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:216:	getBidType		100.0%
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:229:	Builder			91.7%
total:										(statements)		94.8%

 ``` 

author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-16T14:33:58Z
body: ## Code coverage summary 
Note: 
- Prebid team doesn't anticipate tests covering code paths that might result in marshal and unmarshal errors 
- Coverage summary encompasses all commits leading up to the latest one, 4cfe26caf7a76b5348a969f06d7b3d0a81a685c7 
#### thetradedesk 
Refer [here](https://htmlpreview.github.io/?https://github.com/prebid/prebid-server/coverage-preview/.github/preview/14495352569_1744814036/thetradedesk.html) for heat map coverage report 
``` 
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:32:	MakeRequests		97.8%
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:110:	buildEndpointURL	80.0%
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:130:	getExtensionInfo	100.0%
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:154:	getImpressionExt	85.7%
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:167:	MakeBids		100.0%
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:205:	getBidType		100.0%
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:218:	Builder			91.7%
total:										(statements)		95.5%

 ``` 

author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-16T14:59:05Z
body: ## Code coverage summary 
Note: 
- Prebid team doesn't anticipate tests covering code paths that might result in marshal and unmarshal errors 
- Coverage summary encompasses all commits leading up to the latest one, 18e78243840fe3719b9f3d3124b11d95d94cef8a 
#### thetradedesk 
Refer [here](https://htmlpreview.github.io/?https://github.com/prebid/prebid-server/coverage-preview/.github/preview/14495883087_1744815544/thetradedesk.html) for heat map coverage report 
``` 
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:32:	MakeRequests		97.8%
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:110:	buildEndpointURL	83.3%
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:134:	getExtensionInfo	100.0%
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:158:	getImpressionExt	85.7%
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:171:	MakeBids		100.0%
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:209:	getBidType		100.0%
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:222:	Builder			91.7%
total:										(statements)		95.5%

 ``` 

author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-17T13:50:14Z
body: ## Code coverage summary 
Note: 
- Prebid team doesn't anticipate tests covering code paths that might result in marshal and unmarshal errors 
- Coverage summary encompasses all commits leading up to the latest one, cf5a6fdc003f17e829d4d115f7d116efd964b2db 
#### thetradedesk 
Refer [here](https://htmlpreview.github.io/?https://github.com/prebid/prebid-server/coverage-preview/.github/preview/14517235201_1744897813/thetradedesk.html) for heat map coverage report 
``` 
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:32:	MakeRequests		97.8%
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:110:	buildEndpointURL	80.0%
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:130:	getExtensionInfo	100.0%
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:154:	getImpressionExt	85.7%
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:167:	MakeBids		100.0%
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:205:	getBidType		100.0%
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:218:	Builder			91.7%
total:										(statements)		95.5%

 ``` 

author_association: NONE
user: login: andre-gielow-ttd
created_at: 2025-04-17T13:52:01Z
body: > It's looking good. Left a comment and also the following nitpick unrelated to the changes in this PR: A small optimization can be done in the corner case where `len(imp.Banner.Format) == 1`. In said case, `imp.Banner.Format[0]` is already the only in the `imp.Banner.Format` list. So copying and assigning is unnecessary:
> 
> ```
> 41     for _, imp := range request.Imp {
> 42
> 43         if imp.Banner != nil {
> 44 -           if len(imp.Banner.Format) > 0 {
>    +           if len(imp.Banner.Format) > 1 {
> 45                 firstFormat := imp.Banner.Format[0]
> 46                 bannerCopy := *imp.Banner
> 47                 bannerCopy.H = &firstFormat.H
> 48                 bannerCopy.W = &firstFormat.W
> 49                 imp.Banner = &bannerCopy
> 50
> 51             }
> 52         }
> adapters/thetradedesk/thetradedesk.go
> ```
> 
> Do you think it's worth changing?

I would just leave this like this. Most likely I will remove this in another MR since this is already done here:
https://github.com/prebid/Prebid.js/blob/master/modules/ttdBidAdapter.js
Then in case we integrate with SDK it should be a standard oRTB and this should not be needed.
author_association: CONTRIBUTOR
user: login: guscarreon
created_at: 2025-04-17T20:04:45Z
body: > > It's looking good. Left a comment and also the following nitpick unrelated to the changes in this PR: A small optimization can be done in the corner case where `len(imp.Banner.Format) == 1`. In said case, `imp.Banner.Format[0]` is already the only in the `imp.Banner.Format` list. So copying and assigning is unnecessary:
> > ```
> > 41     for _, imp := range request.Imp {
> > 42
> > 43         if imp.Banner != nil {
> > 44 -           if len(imp.Banner.Format) > 0 {
> >    +           if len(imp.Banner.Format) > 1 {
> > 45                 firstFormat := imp.Banner.Format[0]
> > 46                 bannerCopy := *imp.Banner
> > 47                 bannerCopy.H = &firstFormat.H
> > 48                 bannerCopy.W = &firstFormat.W
> > 49                 imp.Banner = &bannerCopy
> > 50
> > 51             }
> > 52         }
> > adapters/thetradedesk/thetradedesk.go
> > ```
> > 
> > 
> >     
> >       
> >     
> > 
> >       
> >     
> > 
> >     
> >   
> > Do you think it's worth changing?
> 
> I would just leave this like this. Most likely I will remove this in another MR since this is already done here: https://github.com/prebid/Prebid.js/blob/master/modules/ttdBidAdapter.js Then in case we integrate with SDK it should be a standard oRTB and this should not be needed.

Cool @andre-gielow-ttd. Thank you
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-04-20T22:40:03Z
body: ## Code coverage summary 
Note: 
- Prebid team doesn't anticipate tests covering code paths that might result in marshal and unmarshal errors 
- Coverage summary encompasses all commits leading up to the latest one, 30ce8a4c66b08f1dbe40003f47e6e174de967dae 
#### thetradedesk 
Refer [here](https://htmlpreview.github.io/?https://github.com/prebid/prebid-server/coverage-preview/.github/preview/14563976465_1745188801/thetradedesk.html) for heat map coverage report 
``` 
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:33:	MakeRequests		97.8%
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:111:	buildEndpointURL	85.7%
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:126:	getExtensionInfo	100.0%
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:150:	getImpressionExt	85.7%
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:163:	MakeBids		100.0%
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:201:	getBidType		100.0%
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:214:	Builder			91.7%
total:										(statements)		96.3%

 ``` 

author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-21T14:23:20Z
body: ## Code coverage summary 
Note: 
- Prebid team doesn't anticipate tests covering code paths that might result in marshal and unmarshal errors 
- Coverage summary encompasses all commits leading up to the latest one, d95dbbd568c742d9111f2b7767fd8fc0de65b8d9 
#### thetradedesk 
Refer [here](https://htmlpreview.github.io/?https://github.com/prebid/prebid-server/coverage-preview/.github/preview/14575117588_1745245398/thetradedesk.html) for heat map coverage report 
``` 
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:33:	MakeRequests		97.7%
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:111:	buildEndpointURL	85.7%
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:126:	getExtensionInfo	100.0%
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:150:	getImpressionExt	85.7%
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:163:	MakeBids		100.0%
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:201:	getBidType		100.0%
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:214:	Builder			91.7%
total:										(statements)		96.2%

 ``` 

author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-22T13:21:23Z
body: ## Code coverage summary 
Note: 
- Prebid team doesn't anticipate tests covering code paths that might result in marshal and unmarshal errors 
- Coverage summary encompasses all commits leading up to the latest one, 270663dd4a434f30e138b4a10bb69eedc1fff89f 
#### thetradedesk 
Refer [here](https://htmlpreview.github.io/?https://github.com/prebid/prebid-server/coverage-preview/.github/preview/14595845943_1745328081/thetradedesk.html) for heat map coverage report 
``` 
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:28:	MakeRequests		97.7%
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:106:	buildEndpointURL	85.7%
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:121:	getExtensionInfo	100.0%
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:145:	getImpressionExt	85.7%
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:158:	MakeBids		100.0%
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:196:	getBidType		100.0%
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:209:	Builder			91.7%
total:										(statements)		96.2%

 ``` 

author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-22T15:25:14Z
body: ## Code coverage summary 
Note: 
- Prebid team doesn't anticipate tests covering code paths that might result in marshal and unmarshal errors 
- Coverage summary encompasses all commits leading up to the latest one, 4b33275b91fe731b566603e99ccc00e2e5d52858 
#### thetradedesk 
Refer [here](https://htmlpreview.github.io/?https://github.com/prebid/prebid-server/coverage-preview/.github/preview/14598572568_1745335512/thetradedesk.html) for heat map coverage report 
``` 
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:28:	MakeRequests		97.7%
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:106:	buildEndpointURL	85.7%
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:121:	getExtensionInfo	100.0%
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:145:	getImpressionExt	85.7%
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:158:	MakeBids		100.0%
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:196:	getBidType		100.0%
github.com/prebid/prebid-server/v3/adapters/thetradedesk/thetradedesk.go:209:	Builder			91.7%
total:										(statements)		96.2%

 ``` 

author_association: NONE