diff --git a/adapters/bidtheatre/bidtheatre.go b/adapters/bidtheatre/bidtheatre.go
new file mode 100644
index 00000000000..8d3b149c7ef
--- /dev/null
+++ b/adapters/bidtheatre/bidtheatre.go
@@ -0,0 +1,97 @@
+package bidtheatre
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+
+ "github.com/prebid/openrtb/v20/openrtb2"
+ "github.com/prebid/prebid-server/v3/adapters"
+ "github.com/prebid/prebid-server/v3/config"
+ "github.com/prebid/prebid-server/v3/errortypes"
+ "github.com/prebid/prebid-server/v3/openrtb_ext"
+ "github.com/prebid/prebid-server/v3/util/jsonutil"
+)
+
+type adapter struct {
+ endpoint string
+}
+
+// Builder builds a new instance of the Bidtheatre adapter for the given bidder with the given config.
+func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
+ bidder := &adapter{
+ endpoint: config.Endpoint,
+ }
+
+ return bidder, nil
+}
+
+func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+ requestJSON, err := jsonutil.Marshal(request)
+ if err != nil {
+ return nil, []error{err}
+ }
+
+ requestData := &adapters.RequestData{
+ Method: "POST",
+ Uri: a.endpoint,
+ Body: requestJSON,
+ ImpIDs: openrtb_ext.GetImpIDs(request.Imp),
+ }
+
+ return []*adapters.RequestData{requestData}, nil
+}
+
+func getMediaTypeForBid(bid openrtb2.Bid) (openrtb_ext.BidType, error) {
+ var bidExt openrtb_ext.ExtBid
+ if err := jsonutil.Unmarshal(bid.Ext, &bidExt); err == nil && bidExt.Prebid != nil {
+ return openrtb_ext.ParseBidType(string(bidExt.Prebid.Type))
+ }
+
+ return "", &errortypes.BadServerResponse{
+ Message: fmt.Sprintf("Failed to parse impression \"%s\" mediatype", bid.ImpID),
+ }
+}
+
+func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+ if adapters.IsResponseStatusCodeNoContent(responseData) {
+ return nil, nil
+ }
+
+ if err := adapters.CheckResponseStatusCodeForErrors(responseData); err != nil {
+ return nil, []error{err}
+ }
+
+ var response openrtb2.BidResponse
+ if err := jsonutil.Unmarshal(responseData.Body, &response); err != nil {
+ return nil, []error{err}
+ }
+
+ bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp))
+ bidResponse.Currency = response.Cur
+ var errors []error
+ for _, seatBid := range response.SeatBid {
+ for i := range seatBid.Bid {
+ resolveMacros(&seatBid.Bid[i])
+ bidType, err := getMediaTypeForBid(seatBid.Bid[i])
+ if err != nil {
+ errors = append(errors, err)
+ continue
+ }
+ bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
+ Bid: &seatBid.Bid[i],
+ BidType: bidType,
+ })
+ }
+ }
+ return bidResponse, errors
+}
+
+func resolveMacros(bid *openrtb2.Bid) {
+ if bid == nil {
+ return
+ }
+ price := strconv.FormatFloat(bid.Price, 'f', -1, 64)
+ bid.NURL = strings.Replace(bid.NURL, "${AUCTION_PRICE}", price, -1)
+ bid.AdM = strings.Replace(bid.AdM, "${AUCTION_PRICE}", price, -1)
+}
diff --git a/adapters/bidtheatre/bidtheatre_test.go b/adapters/bidtheatre/bidtheatre_test.go
new file mode 100644
index 00000000000..8a4daf5fbe6
--- /dev/null
+++ b/adapters/bidtheatre/bidtheatre_test.go
@@ -0,0 +1,90 @@
+package bidtheatre
+
+import (
+ "github.com/prebid/openrtb/v20/openrtb2"
+ "github.com/prebid/prebid-server/v3/adapters/adapterstest"
+ "github.com/prebid/prebid-server/v3/config"
+ "github.com/prebid/prebid-server/v3/openrtb_ext"
+ "strings"
+ "testing"
+)
+
+func TestJsonSamples(t *testing.T) {
+ bidder, buildErr := Builder(openrtb_ext.BidderBidtheatre, config.Adapter{
+ Endpoint: "http://any.url"},
+ config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"})
+
+ if buildErr != nil {
+ t.Fatalf("Builder returned unexpected error %v", buildErr)
+ }
+
+ adapterstest.RunJSONBidderTest(t, "bidtheatretest", bidder)
+}
+
+func TestGetBidTypes(t *testing.T) {
+ mockBid := openrtb2.Bid{
+ ID: "mock-bid-id",
+ ImpID: "mock-imp-id",
+ Price: 1.23,
+ AdID: "mock-ad-id",
+ CrID: "mock-cr-id",
+ DealID: "mock-deal-id",
+ W: 980,
+ H: 240,
+ Ext: []byte(`{"prebid": {"type": "banner"}}`),
+ BURL: "https://example.com/win-notify",
+ Cat: []string{"IAB1"},
+ }
+
+ actualBidTypeValue, _ := getMediaTypeForBid(mockBid)
+
+ if actualBidTypeValue != openrtb_ext.BidTypeBanner {
+ t.Errorf("Expected Bid Type value was: %v, actual value is: %v", openrtb_ext.BidTypeBanner, actualBidTypeValue)
+ }
+
+ mockBid.Ext = []byte(`{"prebid": {"type": "video"}}`)
+
+ actualBidTypeValue, _ = getMediaTypeForBid(mockBid)
+
+ if actualBidTypeValue != openrtb_ext.BidTypeVideo {
+ t.Errorf("Expected Bid Type value was: %v, actual value is: %v", openrtb_ext.BidTypeVideo, actualBidTypeValue)
+ }
+
+}
+
+func TestReplaceMacros(t *testing.T) {
+ mockBid := openrtb2.Bid{
+ ID: "mock-bid-id",
+ ImpID: "mock-imp-id",
+ Price: 1.23,
+ AdID: "mock-ad-id",
+ CrID: "mock-cr-id",
+ DealID: "mock-deal-id",
+ W: 980,
+ H: 240,
+ Ext: []byte(`{"prebid": {"type": "banner"}}`),
+ BURL: "https://example.com/win-notify",
+ Cat: []string{"IAB1"},
+ AdM: "",
+ NURL: "https://adsby.bidtheatre.com/video?z=27025;a=1922926;ex=36;es=prebid.org;eb=3672319;xs=940698616;so=1;tag=unspec_640_360;kuid=1d10dda6-740d-4386-94a0-7042b2ad2a66;wp=${AUCTION_PRICE};su=unknown;iab=vast2;dealId=;ma=eyJjZCI6ZmFsc2UsInN0IjozLCJtbGF0Ijo1OS4zMjkzLCJhZGMiOi0xLCJtb3JnIjoidGVsaWEgbmV0d29yayBzZXJ2aWNlcyIsIm1sc2NvcmUiOjkuNTY5ODA3NDQzNzY3Nzg2RS00LCJtemlwIjoiMTExMjAiLCJiaXAiOiI4MS4yMjcuODIuMjgiLCJhZ2lkIjozNTYyNzAyLCJtbG1vZGVsIjoibWFzdGVyX21sX2Nsa181NDMiLCJ1YSI6ImN1cmxcLzcuODcuMCIsImJycmUiOiJhYiIsIm1sb24iOjE4LjA2ODYsIm1yZWdpb24iOiJhYiIsImR0Ijo4LCJicmNvIjoic3dlIiwibWNpdHkiOiJzdG9ja2hvbG0iLCJicmNpIjoic3RvY2tob2xtIiwicGFnZXVybCI6InByZWJpZC5vcmciLCJpbXBpZCI6IngzNl9hc3gtYi1zMV8yNTY5OTI0ODYzMjY2ODA4OTM2IiwibWNvdW50cnkiOiJzd2UiLCJ0cyI6MTczMjA5NjgyNjg5OH0%3D;cd=0;cb0=;impId=x36_asx-b-s1_2569924863266808936;gdpr=0;gdpr_consent=",
+ }
+
+ resolveMacros(&mockBid)
+
+ if !strings.Contains(mockBid.AdM, "&wp=1.23&") {
+ t.Errorf("AdM ${AUCTION_PRICE} not correctly replaced")
+ }
+
+ if strings.Contains(mockBid.AdM, "${AUCTION_PRICE}") {
+ t.Errorf("AdM ${AUCTION_PRICE} not correctly replaced")
+ }
+
+ if !strings.Contains(mockBid.NURL, ";wp=1.23;") {
+ t.Errorf("NURL ${AUCTION_PRICE} not correctly replaced")
+ }
+
+ if strings.Contains(mockBid.NURL, "${AUCTION_PRICE}") {
+ t.Errorf("NURL ${AUCTION_PRICE} not correctly replaced")
+ }
+
+}
diff --git a/adapters/bidtheatre/bidtheatretest/exemplary/simple-banner.json b/adapters/bidtheatre/bidtheatretest/exemplary/simple-banner.json
new file mode 100644
index 00000000000..eae33a05c87
--- /dev/null
+++ b/adapters/bidtheatre/bidtheatretest/exemplary/simple-banner.json
@@ -0,0 +1,128 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 980,
+ "h": 240
+ },
+ {
+ "w": 980,
+ "h": 300
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "publisherId": "73b20b3a-12a0-4869-b54e-8d42b55786ee"
+ }
+ }
+ }
+ ],
+ "site": {
+ "page": "prebid.org"
+ },
+ "device": {
+ "ip": "81.227.82.28"
+ }
+ },
+
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "http://any.url",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 980,
+ "h": 240
+ },
+ {
+ "w": 980,
+ "h": 300
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "publisherId": "73b20b3a-12a0-4869-b54e-8d42b55786ee"
+ }
+ }
+ }
+ ],
+ "site": {
+ "page": "prebid.org"
+ },
+ "device": {
+ "ip": "81.227.82.28"
+ }
+ },
+ "impIDs":["test-imp-id"]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "cur": "USD",
+ "seatbid": [
+ {
+ "seat": "5",
+ "bid": [
+ {
+ "id": "test-imp-id",
+ "impid": "test-imp-id",
+ "price": 5.08712911605835,
+ "adm": "