diff --git a/adapters/floxis/floxis.go b/adapters/floxis/floxis.go
new file mode 100644
index 00000000000..011ca4601f1
--- /dev/null
+++ b/adapters/floxis/floxis.go
@@ -0,0 +1,200 @@
+package floxis
+
+import (
+ "fmt"
+ "net/http"
+ "net/url"
+ "text/template"
+
+ "github.com/prebid/openrtb/v20/openrtb2"
+ "github.com/prebid/prebid-server/v4/adapters"
+ "github.com/prebid/prebid-server/v4/config"
+ "github.com/prebid/prebid-server/v4/errortypes"
+ "github.com/prebid/prebid-server/v4/macros"
+ "github.com/prebid/prebid-server/v4/openrtb_ext"
+ "github.com/prebid/prebid-server/v4/util/jsonutil"
+)
+
+type adapter struct {
+ endpoint *template.Template
+}
+
+const (
+ defaultRegion = "us-e"
+ defaultPartner = "floxis"
+)
+
+func resolveBidHost(region, partner string) string {
+ if region == "" {
+ region = defaultRegion
+ }
+ if partner == "" {
+ partner = defaultPartner
+ }
+ if partner == defaultPartner {
+ return region
+ }
+ return partner + "-" + region
+}
+
+// Builder builds a new instance of the Floxis adapter for the given bidder with the given config.
+func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
+ tmpl, err := template.New("endpointTemplate").Parse(config.Endpoint)
+ if err != nil {
+ return nil, fmt.Errorf("unable to parse endpoint url template: %v", err)
+ }
+ return &adapter{endpoint: tmpl}, nil
+}
+
+func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+ ext, err := parseImpExt(request.Imp[0])
+ if err != nil {
+ return nil, []error{err}
+ }
+
+ resolvedHost := resolveBidHost(ext.Region, ext.Partner)
+ for i := 1; i < len(request.Imp); i++ {
+ impExt, err := parseImpExt(request.Imp[i])
+ if err != nil {
+ return nil, []error{err}
+ }
+ impHost := resolveBidHost(impExt.Region, impExt.Partner)
+ if impExt.Seat != ext.Seat || impHost != resolvedHost {
+ return nil, []error{&errortypes.BadInput{Message: fmt.Sprintf(
+ "imp %s seat/host (%q/%q) differs from imp %s (%q/%q); split into separate requests",
+ request.Imp[i].ID, impExt.Seat, impHost,
+ request.Imp[0].ID, ext.Seat, resolvedHost)}}
+ }
+ }
+
+ endpoint, err := macros.ResolveMacros(a.endpoint, macros.EndpointTemplateParams{Host: resolvedHost})
+ if err != nil {
+ return nil, []error{err}
+ }
+ uri := fmt.Sprintf("%s?seat=%s", endpoint, url.QueryEscape(ext.Seat))
+
+ body, err := jsonutil.Marshal(request)
+ if err != nil {
+ return nil, []error{err}
+ }
+
+ headers := http.Header{}
+ headers.Add("Content-Type", "application/json;charset=utf-8")
+ headers.Add("Accept", "application/json")
+
+ return []*adapters.RequestData{{
+ Method: "POST",
+ Uri: uri,
+ Body: body,
+ Headers: headers,
+ ImpIDs: openrtb_ext.GetImpIDs(request.Imp),
+ }}, nil
+}
+
+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}
+ }
+
+ var errs []error
+ bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp))
+ if response.Cur != "" {
+ bidResponse.Currency = response.Cur
+ }
+ for _, seatBid := range response.SeatBid {
+ for i := range seatBid.Bid {
+ bidType, typeErr := getMediaTypeForBid(request.Imp, seatBid.Bid[i])
+ if typeErr != nil {
+ errs = append(errs, typeErr)
+ continue
+ }
+ bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
+ Bid: &seatBid.Bid[i],
+ BidType: bidType,
+ })
+ }
+ }
+ return bidResponse, errs
+}
+
+func parseImpExt(imp openrtb2.Imp) (openrtb_ext.ExtImpFloxis, error) {
+ var bidderExt adapters.ExtImpBidder
+ if err := jsonutil.Unmarshal(imp.Ext, &bidderExt); err != nil {
+ return openrtb_ext.ExtImpFloxis{}, &errortypes.BadInput{Message: fmt.Sprintf("invalid imp.ext for imp %s: %s", imp.ID, err)}
+ }
+ var floxisExt openrtb_ext.ExtImpFloxis
+ if err := jsonutil.Unmarshal(bidderExt.Bidder, &floxisExt); err != nil {
+ return openrtb_ext.ExtImpFloxis{}, &errortypes.BadInput{Message: fmt.Sprintf("invalid imp.ext.bidder for imp %s: %s", imp.ID, err)}
+ }
+ return floxisExt, nil
+}
+
+func getMediaTypeForBid(imps []openrtb2.Imp, bid openrtb2.Bid) (openrtb_ext.BidType, error) {
+ if bid.MType != 0 {
+ switch bid.MType {
+ case openrtb2.MarkupBanner:
+ return openrtb_ext.BidTypeBanner, nil
+ case openrtb2.MarkupVideo:
+ return openrtb_ext.BidTypeVideo, nil
+ case openrtb2.MarkupAudio:
+ return openrtb_ext.BidTypeAudio, nil
+ case openrtb2.MarkupNative:
+ return openrtb_ext.BidTypeNative, nil
+ default:
+ return "", &errortypes.BadServerResponse{
+ Message: fmt.Sprintf("unsupported bid.mtype %d for impression %s", bid.MType, bid.ImpID),
+ }
+ }
+ }
+ for _, imp := range imps {
+ if imp.ID != bid.ImpID {
+ continue
+ }
+ formats, resolved := countFormats(imp)
+ switch {
+ case formats == 1:
+ return resolved, nil
+ case formats > 1:
+ return "", &errortypes.BadServerResponse{
+ Message: fmt.Sprintf("bid for multi-format imp %s requires bid.mtype to disambiguate", bid.ImpID),
+ }
+ default:
+ return "", &errortypes.BadServerResponse{
+ Message: fmt.Sprintf("unable to resolve media type for impression %s", bid.ImpID),
+ }
+ }
+ }
+ return "", &errortypes.BadServerResponse{
+ Message: fmt.Sprintf("unable to find impression %s for bid", bid.ImpID),
+ }
+}
+
+func countFormats(imp openrtb2.Imp) (int, openrtb_ext.BidType) {
+ formats := 0
+ var resolved openrtb_ext.BidType
+ if imp.Banner != nil {
+ formats++
+ resolved = openrtb_ext.BidTypeBanner
+ }
+ if imp.Video != nil {
+ formats++
+ resolved = openrtb_ext.BidTypeVideo
+ }
+ if imp.Audio != nil {
+ formats++
+ resolved = openrtb_ext.BidTypeAudio
+ }
+ if imp.Native != nil {
+ formats++
+ resolved = openrtb_ext.BidTypeNative
+ }
+ return formats, resolved
+}
diff --git a/adapters/floxis/floxis_test.go b/adapters/floxis/floxis_test.go
new file mode 100644
index 00000000000..37104b14bb7
--- /dev/null
+++ b/adapters/floxis/floxis_test.go
@@ -0,0 +1,200 @@
+package floxis
+
+import (
+ "encoding/json"
+ "testing"
+ "text/template"
+
+ "github.com/prebid/openrtb/v20/openrtb2"
+ "github.com/prebid/prebid-server/v4/adapters"
+ "github.com/prebid/prebid-server/v4/adapters/adapterstest"
+ "github.com/prebid/prebid-server/v4/config"
+ "github.com/prebid/prebid-server/v4/openrtb_ext"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestJsonSamples(t *testing.T) {
+ bidder, buildErr := Builder(openrtb_ext.BidderFloxis, config.Adapter{
+ Endpoint: "https://{{.Host}}.floxis.tech/pbs"},
+ config.Server{ExternalUrl: "http://hosturl.com", DataCenter: "2"})
+
+ if buildErr != nil {
+ t.Fatalf("Builder returned unexpected error %v", buildErr)
+ }
+
+ adapterstest.RunJSONBidderTest(t, "floxistest", bidder)
+}
+
+func newAdapter() *adapter {
+ return &adapter{endpoint: template.Must(template.New("endpointTemplate").Parse("https://{{.Host}}.floxis.tech/pbs"))}
+}
+
+func bannerImp(ext string) openrtb2.Imp {
+ return openrtb2.Imp{
+ ID: "imp-1",
+ Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}},
+ Ext: json.RawMessage(ext),
+ }
+}
+
+func TestResolveBidHost(t *testing.T) {
+ cases := []struct {
+ region string
+ partner string
+ want string
+ }{
+ {"us-e", "floxis", "us-e"},
+ {"eu", "floxis", "eu"},
+ {"apac", "floxis", "apac"},
+ {"", "", "us-e"},
+ {"", "floxis", "us-e"},
+ {"eu", "", "eu"},
+ {"mars", "floxis", "mars"},
+ {"us-e", "acme", "acme-us-e"},
+ {"eu", "acme", "acme-eu"},
+ }
+ for _, c := range cases {
+ assert.Equal(t, c.want, resolveBidHost(c.region, c.partner), "region %q partner %q", c.region, c.partner)
+ }
+}
+
+func TestSeatIsURLEscaped(t *testing.T) {
+ req := &openrtb2.BidRequest{
+ ID: "req-1",
+ Imp: []openrtb2.Imp{bannerImp(`{"bidder":{"seat":"a b&c","region":"eu"}}`)},
+ Site: &openrtb2.Site{ID: "271"},
+ }
+ reqData, errs := newAdapter().MakeRequests(req, &adapters.ExtraRequestInfo{})
+ assert.Empty(t, errs)
+ assert.Len(t, reqData, 1)
+ assert.Equal(t, "https://eu.floxis.tech/pbs?seat=a+b%26c", reqData[0].Uri)
+}
+
+func TestPartnerPrefixesHost(t *testing.T) {
+ req := &openrtb2.BidRequest{
+ ID: "req-1",
+ Imp: []openrtb2.Imp{bannerImp(`{"bidder":{"seat":"abc","region":"us-e","partner":"acme"}}`)},
+ Site: &openrtb2.Site{ID: "271"},
+ }
+ reqData, errs := newAdapter().MakeRequests(req, &adapters.ExtraRequestInfo{})
+ assert.Empty(t, errs)
+ assert.Equal(t, "https://acme-us-e.floxis.tech/pbs?seat=abc", reqData[0].Uri)
+}
+
+func TestValidNonStandardRegionPassesThrough(t *testing.T) {
+ req := &openrtb2.BidRequest{
+ ID: "req-1",
+ Imp: []openrtb2.Imp{bannerImp(`{"bidder":{"seat":"abc","region":"mars"}}`)},
+ Site: &openrtb2.Site{ID: "271"},
+ }
+ reqData, errs := newAdapter().MakeRequests(req, &adapters.ExtraRequestInfo{})
+ assert.Empty(t, errs)
+ assert.Equal(t, "https://mars.floxis.tech/pbs?seat=abc", reqData[0].Uri)
+}
+
+func TestMissingRegionDefaultsToUSE(t *testing.T) {
+ req := &openrtb2.BidRequest{
+ ID: "req-1",
+ Imp: []openrtb2.Imp{bannerImp(`{"bidder":{"seat":"abc"}}`)},
+ Site: &openrtb2.Site{ID: "271"},
+ }
+ reqData, errs := newAdapter().MakeRequests(req, &adapters.ExtraRequestInfo{})
+ assert.Empty(t, errs)
+ assert.Equal(t, "https://us-e.floxis.tech/pbs?seat=abc", reqData[0].Uri)
+}
+
+func TestInvalidImpExt(t *testing.T) {
+ req := &openrtb2.BidRequest{
+ ID: "req-1",
+ Imp: []openrtb2.Imp{{ID: "imp-1", Banner: &openrtb2.Banner{}, Ext: json.RawMessage(`"not-an-object"`)}},
+ Site: &openrtb2.Site{ID: "271"},
+ }
+ _, errs := newAdapter().MakeRequests(req, &adapters.ExtraRequestInfo{})
+ assert.Len(t, errs, 1)
+ assert.Contains(t, errs[0].Error(), "imp.ext")
+}
+
+func TestCallerRequestNotMutated(t *testing.T) {
+ imp := openrtb2.Imp{
+ ID: "imp-1",
+ Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}},
+ Ext: json.RawMessage(`{"bidder":{"seat":"abc","region":"eu"}}`),
+ }
+ req := &openrtb2.BidRequest{
+ ID: "req-1",
+ Imp: []openrtb2.Imp{imp},
+ Site: &openrtb2.Site{ID: "271", Ext: json.RawMessage(`{"amp":0}`)},
+ }
+ before, _ := json.Marshal(req)
+
+ _, errs := newAdapter().MakeRequests(req, &adapters.ExtraRequestInfo{})
+ assert.Empty(t, errs)
+
+ after, _ := json.Marshal(req)
+ assert.JSONEq(t, string(before), string(after), "caller's request must not be mutated")
+ assert.Nil(t, req.Imp[0].Secure, "caller's imp[0].Secure must not be mutated")
+}
+
+func TestGetMediaTypeForBidByMType(t *testing.T) {
+ cases := []struct {
+ mtype openrtb2.MarkupType
+ want openrtb_ext.BidType
+ }{
+ {openrtb2.MarkupBanner, openrtb_ext.BidTypeBanner},
+ {openrtb2.MarkupVideo, openrtb_ext.BidTypeVideo},
+ {openrtb2.MarkupAudio, openrtb_ext.BidTypeAudio},
+ {openrtb2.MarkupNative, openrtb_ext.BidTypeNative},
+ }
+ for _, c := range cases {
+ bt, err := getMediaTypeForBid(nil, openrtb2.Bid{ImpID: "x", MType: c.mtype})
+ assert.NoError(t, err)
+ assert.Equal(t, c.want, bt)
+ }
+}
+
+func TestGetMediaTypeForBidUnsupportedMType(t *testing.T) {
+ _, err := getMediaTypeForBid(nil, openrtb2.Bid{ImpID: "x", MType: openrtb2.MarkupType(99)})
+ assert.Error(t, err)
+ assert.Contains(t, err.Error(), "unsupported bid.mtype")
+}
+
+func TestGetMediaTypeForBidSingleFormatFallback(t *testing.T) {
+ imps := []openrtb2.Imp{
+ {ID: "b", Banner: &openrtb2.Banner{}},
+ {ID: "v", Video: &openrtb2.Video{}},
+ {ID: "a", Audio: &openrtb2.Audio{}},
+ {ID: "n", Native: &openrtb2.Native{}},
+ }
+ expected := map[string]openrtb_ext.BidType{
+ "b": openrtb_ext.BidTypeBanner,
+ "v": openrtb_ext.BidTypeVideo,
+ "a": openrtb_ext.BidTypeAudio,
+ "n": openrtb_ext.BidTypeNative,
+ }
+ for impID, want := range expected {
+ bt, err := getMediaTypeForBid(imps, openrtb2.Bid{ImpID: impID})
+ assert.NoError(t, err, impID)
+ assert.Equal(t, want, bt, impID)
+ }
+}
+
+func TestGetMediaTypeForBidMultiFormatNeedsMType(t *testing.T) {
+ imps := []openrtb2.Imp{{ID: "m", Banner: &openrtb2.Banner{}, Video: &openrtb2.Video{}}}
+ _, err := getMediaTypeForBid(imps, openrtb2.Bid{ImpID: "m"})
+ assert.Error(t, err)
+ assert.Contains(t, err.Error(), "requires bid.mtype to disambiguate")
+}
+
+func TestGetMediaTypeForBidImpWithoutFormat(t *testing.T) {
+ imps := []openrtb2.Imp{{ID: "x"}}
+ _, err := getMediaTypeForBid(imps, openrtb2.Bid{ImpID: "x"})
+ assert.Error(t, err)
+ assert.Contains(t, err.Error(), "unable to resolve media type")
+}
+
+func TestGetMediaTypeForBidUnknownImp(t *testing.T) {
+ imps := []openrtb2.Imp{{ID: "x", Banner: &openrtb2.Banner{}}}
+ _, err := getMediaTypeForBid(imps, openrtb2.Bid{ImpID: "missing"})
+ assert.Error(t, err)
+ assert.Contains(t, err.Error(), "unable to find impression")
+}
diff --git a/adapters/floxis/floxistest/exemplary/audio-web.json b/adapters/floxis/floxistest/exemplary/audio-web.json
new file mode 100644
index 00000000000..bd59a0c655e
--- /dev/null
+++ b/adapters/floxis/floxistest/exemplary/audio-web.json
@@ -0,0 +1,96 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "audio": {
+ "mimes": ["audio/mp4"],
+ "protocols": [9, 10]
+ },
+ "ext": {
+ "bidder": {
+ "seat": "pub-seat-1"
+ }
+ }
+ }
+ ],
+ "site": {
+ "id": "271",
+ "domain": "example.com"
+ }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "headers": {
+ "Content-Type": ["application/json;charset=utf-8"],
+ "Accept": ["application/json"]
+ },
+ "uri": "https://us-e.floxis.tech/pbs?seat=pub-seat-1",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "audio": {
+ "mimes": ["audio/mp4"],
+ "protocols": [9, 10]
+ },
+ "ext": {
+ "bidder": {
+ "seat": "pub-seat-1"
+ }
+ }
+ }
+ ],
+ "site": {
+ "id": "271",
+ "domain": "example.com"
+ }
+ },
+ "impIDs": ["test-imp-id"]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "cur": "USD",
+ "seatbid": [
+ {
+ "seat": "floxis",
+ "bid": [
+ {
+ "id": "1",
+ "impid": "test-imp-id",
+ "price": 1.1,
+ "adm": "",
+ "crid": "creative-1",
+ "mtype": 3
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "1",
+ "impid": "test-imp-id",
+ "price": 1.1,
+ "adm": "",
+ "crid": "creative-1",
+ "mtype": 3
+ },
+ "type": "audio"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/floxis/floxistest/exemplary/banner-app.json b/adapters/floxis/floxistest/exemplary/banner-app.json
new file mode 100644
index 00000000000..82d25e2f379
--- /dev/null
+++ b/adapters/floxis/floxistest/exemplary/banner-app.json
@@ -0,0 +1,98 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [{ "w": 320, "h": 50 }]
+ },
+ "ext": {
+ "bidder": {
+ "seat": "pub-seat-1"
+ }
+ }
+ }
+ ],
+ "app": {
+ "id": "app-1",
+ "bundle": "com.example.app"
+ }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "headers": {
+ "Content-Type": ["application/json;charset=utf-8"],
+ "Accept": ["application/json"]
+ },
+ "uri": "https://us-e.floxis.tech/pbs?seat=pub-seat-1",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [{ "w": 320, "h": 50 }]
+ },
+ "ext": {
+ "bidder": {
+ "seat": "pub-seat-1"
+ }
+ }
+ }
+ ],
+ "app": {
+ "id": "app-1",
+ "bundle": "com.example.app"
+ }
+ },
+ "impIDs": ["test-imp-id"]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "cur": "USD",
+ "seatbid": [
+ {
+ "seat": "floxis",
+ "bid": [
+ {
+ "id": "1",
+ "impid": "test-imp-id",
+ "price": 0.9,
+ "adm": "
ad
",
+ "crid": "creative-1",
+ "w": 320,
+ "h": 50,
+ "mtype": 1
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "1",
+ "impid": "test-imp-id",
+ "price": 0.9,
+ "adm": "ad
",
+ "crid": "creative-1",
+ "w": 320,
+ "h": 50,
+ "mtype": 1
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/floxis/floxistest/exemplary/banner-web-no-mtype.json b/adapters/floxis/floxistest/exemplary/banner-web-no-mtype.json
new file mode 100644
index 00000000000..a47d5976d82
--- /dev/null
+++ b/adapters/floxis/floxistest/exemplary/banner-web-no-mtype.json
@@ -0,0 +1,96 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [{ "w": 728, "h": 90 }]
+ },
+ "ext": {
+ "bidder": {
+ "seat": "pub-seat-1"
+ }
+ }
+ }
+ ],
+ "site": {
+ "id": "271",
+ "domain": "example.com"
+ }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "headers": {
+ "Content-Type": ["application/json;charset=utf-8"],
+ "Accept": ["application/json"]
+ },
+ "uri": "https://us-e.floxis.tech/pbs?seat=pub-seat-1",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [{ "w": 728, "h": 90 }]
+ },
+ "ext": {
+ "bidder": {
+ "seat": "pub-seat-1"
+ }
+ }
+ }
+ ],
+ "site": {
+ "id": "271",
+ "domain": "example.com"
+ }
+ },
+ "impIDs": ["test-imp-id"]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "cur": "USD",
+ "seatbid": [
+ {
+ "seat": "floxis",
+ "bid": [
+ {
+ "id": "1",
+ "impid": "test-imp-id",
+ "price": 0.75,
+ "adm": "ad
",
+ "crid": "creative-1",
+ "w": 728,
+ "h": 90
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "1",
+ "impid": "test-imp-id",
+ "price": 0.75,
+ "adm": "ad
",
+ "crid": "creative-1",
+ "w": 728,
+ "h": 90
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/floxis/floxistest/exemplary/banner-web-partner.json b/adapters/floxis/floxistest/exemplary/banner-web-partner.json
new file mode 100644
index 00000000000..024f0549ad7
--- /dev/null
+++ b/adapters/floxis/floxistest/exemplary/banner-web-partner.json
@@ -0,0 +1,104 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [{ "w": 300, "h": 250 }]
+ },
+ "ext": {
+ "bidder": {
+ "seat": "pub-seat-1",
+ "region": "us-e",
+ "partner": "acme"
+ }
+ }
+ }
+ ],
+ "site": {
+ "id": "271",
+ "domain": "example.com",
+ "page": "https://example.com/"
+ }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "headers": {
+ "Content-Type": ["application/json;charset=utf-8"],
+ "Accept": ["application/json"]
+ },
+ "uri": "https://acme-us-e.floxis.tech/pbs?seat=pub-seat-1",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [{ "w": 300, "h": 250 }]
+ },
+ "ext": {
+ "bidder": {
+ "seat": "pub-seat-1",
+ "region": "us-e",
+ "partner": "acme"
+ }
+ }
+ }
+ ],
+ "site": {
+ "id": "271",
+ "domain": "example.com",
+ "page": "https://example.com/"
+ }
+ },
+ "impIDs": ["test-imp-id"]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "cur": "USD",
+ "seatbid": [
+ {
+ "seat": "acme",
+ "bid": [
+ {
+ "id": "1",
+ "impid": "test-imp-id",
+ "price": 1.25,
+ "adm": "ad
",
+ "crid": "creative-1",
+ "w": 300,
+ "h": 250,
+ "mtype": 1
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "1",
+ "impid": "test-imp-id",
+ "price": 1.25,
+ "adm": "ad
",
+ "crid": "creative-1",
+ "w": 300,
+ "h": 250,
+ "mtype": 1
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/floxis/floxistest/exemplary/banner-web.json b/adapters/floxis/floxistest/exemplary/banner-web.json
new file mode 100644
index 00000000000..71748c4ed75
--- /dev/null
+++ b/adapters/floxis/floxistest/exemplary/banner-web.json
@@ -0,0 +1,102 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [{ "w": 300, "h": 250 }]
+ },
+ "ext": {
+ "bidder": {
+ "seat": "pub-seat-1",
+ "region": "us-e"
+ }
+ }
+ }
+ ],
+ "site": {
+ "id": "271",
+ "domain": "example.com",
+ "page": "https://example.com/"
+ }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "headers": {
+ "Content-Type": ["application/json;charset=utf-8"],
+ "Accept": ["application/json"]
+ },
+ "uri": "https://us-e.floxis.tech/pbs?seat=pub-seat-1",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [{ "w": 300, "h": 250 }]
+ },
+ "ext": {
+ "bidder": {
+ "seat": "pub-seat-1",
+ "region": "us-e"
+ }
+ }
+ }
+ ],
+ "site": {
+ "id": "271",
+ "domain": "example.com",
+ "page": "https://example.com/"
+ }
+ },
+ "impIDs": ["test-imp-id"]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "cur": "USD",
+ "seatbid": [
+ {
+ "seat": "floxis",
+ "bid": [
+ {
+ "id": "1",
+ "impid": "test-imp-id",
+ "price": 1.25,
+ "adm": "ad
",
+ "crid": "creative-1",
+ "w": 300,
+ "h": 250,
+ "mtype": 1
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "1",
+ "impid": "test-imp-id",
+ "price": 1.25,
+ "adm": "ad
",
+ "crid": "creative-1",
+ "w": 300,
+ "h": 250,
+ "mtype": 1
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/floxis/floxistest/exemplary/native-app-apac.json b/adapters/floxis/floxistest/exemplary/native-app-apac.json
new file mode 100644
index 00000000000..3fa63e8e88a
--- /dev/null
+++ b/adapters/floxis/floxistest/exemplary/native-app-apac.json
@@ -0,0 +1,98 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "native": {
+ "request": "{\"ver\":\"1.2\"}",
+ "ver": "1.2"
+ },
+ "ext": {
+ "bidder": {
+ "seat": "pub-seat-apac",
+ "region": "apac"
+ }
+ }
+ }
+ ],
+ "app": {
+ "id": "app-1",
+ "bundle": "com.example.apac"
+ }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "headers": {
+ "Content-Type": ["application/json;charset=utf-8"],
+ "Accept": ["application/json"]
+ },
+ "uri": "https://apac.floxis.tech/pbs?seat=pub-seat-apac",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "native": {
+ "request": "{\"ver\":\"1.2\"}",
+ "ver": "1.2"
+ },
+ "ext": {
+ "bidder": {
+ "seat": "pub-seat-apac",
+ "region": "apac"
+ }
+ }
+ }
+ ],
+ "app": {
+ "id": "app-1",
+ "bundle": "com.example.apac"
+ }
+ },
+ "impIDs": ["test-imp-id"]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "cur": "USD",
+ "seatbid": [
+ {
+ "seat": "floxis",
+ "bid": [
+ {
+ "id": "1",
+ "impid": "test-imp-id",
+ "price": 2.0,
+ "adm": "{\"native\":{}}",
+ "crid": "creative-1",
+ "mtype": 4
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "1",
+ "impid": "test-imp-id",
+ "price": 2.0,
+ "adm": "{\"native\":{}}",
+ "crid": "creative-1",
+ "mtype": 4
+ },
+ "type": "native"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/floxis/floxistest/exemplary/privacy-passthrough.json b/adapters/floxis/floxistest/exemplary/privacy-passthrough.json
new file mode 100644
index 00000000000..4dd3055379b
--- /dev/null
+++ b/adapters/floxis/floxistest/exemplary/privacy-passthrough.json
@@ -0,0 +1,124 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [{ "w": 300, "h": 250 }]
+ },
+ "ext": {
+ "bidder": {
+ "seat": "pub-seat-1",
+ "region": "us-e"
+ }
+ }
+ }
+ ],
+ "site": {
+ "id": "271",
+ "domain": "example.com",
+ "page": "https://example.com/"
+ },
+ "user": {
+ "ext": {
+ "consent": "CONSENT-STRING-XYZ"
+ }
+ },
+ "regs": {
+ "gdpr": 1,
+ "gpp": "DBABMA~1YNN",
+ "gpp_sid": [2, 6],
+ "us_privacy": "1YNN"
+ }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "headers": {
+ "Content-Type": ["application/json;charset=utf-8"],
+ "Accept": ["application/json"]
+ },
+ "uri": "https://us-e.floxis.tech/pbs?seat=pub-seat-1",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [{ "w": 300, "h": 250 }]
+ },
+ "ext": {
+ "bidder": {
+ "seat": "pub-seat-1",
+ "region": "us-e"
+ }
+ }
+ }
+ ],
+ "site": {
+ "id": "271",
+ "domain": "example.com",
+ "page": "https://example.com/"
+ },
+ "user": {
+ "ext": {
+ "consent": "CONSENT-STRING-XYZ"
+ }
+ },
+ "regs": {
+ "gdpr": 1,
+ "gpp": "DBABMA~1YNN",
+ "gpp_sid": [2, 6],
+ "us_privacy": "1YNN"
+ }
+ },
+ "impIDs": ["test-imp-id"]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "cur": "USD",
+ "seatbid": [
+ {
+ "seat": "floxis",
+ "bid": [
+ {
+ "id": "1",
+ "impid": "test-imp-id",
+ "price": 1.25,
+ "adm": "ad
",
+ "crid": "creative-1",
+ "w": 300,
+ "h": 250,
+ "mtype": 1
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "1",
+ "impid": "test-imp-id",
+ "price": 1.25,
+ "adm": "ad
",
+ "crid": "creative-1",
+ "w": 300,
+ "h": 250,
+ "mtype": 1
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/floxis/floxistest/exemplary/video-web-eu.json b/adapters/floxis/floxistest/exemplary/video-web-eu.json
new file mode 100644
index 00000000000..571ed32508d
--- /dev/null
+++ b/adapters/floxis/floxistest/exemplary/video-web-eu.json
@@ -0,0 +1,102 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "video": {
+ "mimes": ["video/mp4"],
+ "w": 640,
+ "h": 480,
+ "protocols": [2, 3, 5, 6]
+ },
+ "ext": {
+ "bidder": {
+ "seat": "pub-seat-eu",
+ "region": "eu"
+ }
+ }
+ }
+ ],
+ "site": {
+ "id": "271",
+ "domain": "example.eu"
+ }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "headers": {
+ "Content-Type": ["application/json;charset=utf-8"],
+ "Accept": ["application/json"]
+ },
+ "uri": "https://eu.floxis.tech/pbs?seat=pub-seat-eu",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "video": {
+ "mimes": ["video/mp4"],
+ "w": 640,
+ "h": 480,
+ "protocols": [2, 3, 5, 6]
+ },
+ "ext": {
+ "bidder": {
+ "seat": "pub-seat-eu",
+ "region": "eu"
+ }
+ }
+ }
+ ],
+ "site": {
+ "id": "271",
+ "domain": "example.eu"
+ }
+ },
+ "impIDs": ["test-imp-id"]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "cur": "EUR",
+ "seatbid": [
+ {
+ "seat": "floxis",
+ "bid": [
+ {
+ "id": "1",
+ "impid": "test-imp-id",
+ "price": 3.5,
+ "adm": "",
+ "crid": "creative-1",
+ "mtype": 2
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "EUR",
+ "bids": [
+ {
+ "bid": {
+ "id": "1",
+ "impid": "test-imp-id",
+ "price": 3.5,
+ "adm": "",
+ "crid": "creative-1",
+ "mtype": 2
+ },
+ "type": "video"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/floxis/floxistest/supplemental/invalid-bidder-params.json b/adapters/floxis/floxistest/supplemental/invalid-bidder-params.json
new file mode 100644
index 00000000000..d755cf5b3c9
--- /dev/null
+++ b/adapters/floxis/floxistest/supplemental/invalid-bidder-params.json
@@ -0,0 +1,20 @@
+{
+ "expectedMakeRequestsErrors": [
+ {
+ "value": "invalid imp.ext.bidder for imp test-imp-id:.*",
+ "comparison": "regex"
+ }
+ ],
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": { "format": [{ "w": 300, "h": 250 }] },
+ "ext": { "bidder": "not-an-object" }
+ }
+ ],
+ "site": { "id": "271", "domain": "example.com" }
+ },
+ "httpCalls": []
+}
diff --git a/adapters/floxis/floxistest/supplemental/invalid-imp-ext.json b/adapters/floxis/floxistest/supplemental/invalid-imp-ext.json
new file mode 100644
index 00000000000..6d944746bea
--- /dev/null
+++ b/adapters/floxis/floxistest/supplemental/invalid-imp-ext.json
@@ -0,0 +1,20 @@
+{
+ "expectedMakeRequestsErrors": [
+ {
+ "value": "invalid imp.ext for imp test-imp-id:.*",
+ "comparison": "regex"
+ }
+ ],
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": { "format": [{ "w": 300, "h": 250 }] },
+ "ext": "not-an-object"
+ }
+ ],
+ "site": { "id": "271", "domain": "example.com" }
+ },
+ "httpCalls": []
+}
diff --git a/adapters/floxis/floxistest/supplemental/invalid-response-body.json b/adapters/floxis/floxistest/supplemental/invalid-response-body.json
new file mode 100644
index 00000000000..7516d99beb7
--- /dev/null
+++ b/adapters/floxis/floxistest/supplemental/invalid-response-body.json
@@ -0,0 +1,45 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": { "format": [{ "w": 300, "h": 250 }] },
+ "ext": { "bidder": { "seat": "pub-seat-1" } }
+ }
+ ],
+ "site": { "id": "271", "domain": "example.com" }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://us-e.floxis.tech/pbs?seat=pub-seat-1",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": { "format": [{ "w": 300, "h": 250 }] },
+ "ext": { "bidder": { "seat": "pub-seat-1" } }
+ }
+ ],
+ "site": { "id": "271", "domain": "example.com" }
+ },
+ "impIDs": ["test-imp-id"]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "seatbid": "this-should-be-an-array"
+ }
+ }
+ }
+ ],
+ "expectedMakeBidsErrors": [
+ {
+ "value": ".*",
+ "comparison": "regex"
+ }
+ ]
+}
diff --git a/adapters/floxis/floxistest/supplemental/multi-format-imp-uses-mtype.json b/adapters/floxis/floxistest/supplemental/multi-format-imp-uses-mtype.json
new file mode 100644
index 00000000000..add1f41d2e9
--- /dev/null
+++ b/adapters/floxis/floxistest/supplemental/multi-format-imp-uses-mtype.json
@@ -0,0 +1,60 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": { "format": [{ "w": 300, "h": 250 }] },
+ "video": { "mimes": ["video/mp4"], "w": 640, "h": 480 },
+ "ext": { "bidder": { "seat": "pub-seat-1" } }
+ }
+ ],
+ "site": { "id": "271", "domain": "example.com" }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://us-e.floxis.tech/pbs?seat=pub-seat-1",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": { "format": [{ "w": 300, "h": 250 }] },
+ "video": { "mimes": ["video/mp4"], "w": 640, "h": 480 },
+ "ext": { "bidder": { "seat": "pub-seat-1" } }
+ }
+ ],
+ "site": { "id": "271", "domain": "example.com" }
+ },
+ "impIDs": ["test-imp-id"]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "cur": "USD",
+ "seatbid": [
+ {
+ "seat": "floxis",
+ "bid": [
+ { "id": "v", "impid": "test-imp-id", "price": 4.0, "adm": "", "crid": "c-1", "mtype": 2 }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": { "id": "v", "impid": "test-imp-id", "price": 4.0, "adm": "", "crid": "c-1", "mtype": 2 },
+ "type": "video"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/floxis/floxistest/supplemental/multi-format-imp-without-mtype-errors.json b/adapters/floxis/floxistest/supplemental/multi-format-imp-without-mtype-errors.json
new file mode 100644
index 00000000000..a1f5840a8c0
--- /dev/null
+++ b/adapters/floxis/floxistest/supplemental/multi-format-imp-without-mtype-errors.json
@@ -0,0 +1,61 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": { "format": [{ "w": 300, "h": 250 }] },
+ "video": { "mimes": ["video/mp4"], "w": 640, "h": 480 },
+ "ext": { "bidder": { "seat": "pub-seat-1" } }
+ }
+ ],
+ "site": { "id": "271", "domain": "example.com" }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://us-e.floxis.tech/pbs?seat=pub-seat-1",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": { "format": [{ "w": 300, "h": 250 }] },
+ "video": { "mimes": ["video/mp4"], "w": 640, "h": 480 },
+ "ext": { "bidder": { "seat": "pub-seat-1" } }
+ }
+ ],
+ "site": { "id": "271", "domain": "example.com" }
+ },
+ "impIDs": ["test-imp-id"]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "cur": "USD",
+ "seatbid": [
+ {
+ "seat": "floxis",
+ "bid": [
+ { "id": "ambiguous", "impid": "test-imp-id", "price": 2.0, "adm": "?
", "crid": "c-1" }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": []
+ }
+ ],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "bid for multi-format imp test-imp-id requires bid.mtype to disambiguate",
+ "comparison": "literal"
+ }
+ ]
+}
diff --git a/adapters/floxis/floxistest/supplemental/multi-imp-default-and-explicit-region-same-host.json b/adapters/floxis/floxistest/supplemental/multi-imp-default-and-explicit-region-same-host.json
new file mode 100644
index 00000000000..0c731c7ee26
--- /dev/null
+++ b/adapters/floxis/floxistest/supplemental/multi-imp-default-and-explicit-region-same-host.json
@@ -0,0 +1,73 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "imp-1",
+ "banner": { "format": [{ "w": 300, "h": 250 }] },
+ "ext": { "bidder": { "seat": "seat-us" } }
+ },
+ {
+ "id": "imp-2",
+ "video": { "mimes": ["video/mp4"], "w": 640, "h": 480 },
+ "ext": { "bidder": { "seat": "seat-us", "region": "us-e", "partner": "floxis" } }
+ }
+ ],
+ "site": { "id": "271", "domain": "example.com" }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://us-e.floxis.tech/pbs?seat=seat-us",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "imp-1",
+ "banner": { "format": [{ "w": 300, "h": 250 }] },
+ "ext": { "bidder": { "seat": "seat-us" } }
+ },
+ {
+ "id": "imp-2",
+ "video": { "mimes": ["video/mp4"], "w": 640, "h": 480 },
+ "ext": { "bidder": { "seat": "seat-us", "region": "us-e", "partner": "floxis" } }
+ }
+ ],
+ "site": { "id": "271", "domain": "example.com" }
+ },
+ "impIDs": ["imp-1", "imp-2"]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "cur": "USD",
+ "seatbid": [
+ {
+ "seat": "floxis",
+ "bid": [
+ { "id": "1", "impid": "imp-1", "price": 1.0, "adm": "ad
", "crid": "c1", "mtype": 1 },
+ { "id": "2", "impid": "imp-2", "price": 2.0, "adm": "", "crid": "c2", "mtype": 2 }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": { "id": "1", "impid": "imp-1", "price": 1.0, "adm": "ad
", "crid": "c1", "mtype": 1 },
+ "type": "banner"
+ },
+ {
+ "bid": { "id": "2", "impid": "imp-2", "price": 2.0, "adm": "", "crid": "c2", "mtype": 2 },
+ "type": "video"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/floxis/floxistest/supplemental/multi-imp-different-partner-errors.json b/adapters/floxis/floxistest/supplemental/multi-imp-different-partner-errors.json
new file mode 100644
index 00000000000..64a269c972a
--- /dev/null
+++ b/adapters/floxis/floxistest/supplemental/multi-imp-different-partner-errors.json
@@ -0,0 +1,25 @@
+{
+ "expectedMakeRequestsErrors": [
+ {
+ "value": "imp imp-2 seat/host .* differs from imp imp-1 .*",
+ "comparison": "regex"
+ }
+ ],
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "imp-1",
+ "banner": { "format": [{ "w": 300, "h": 250 }] },
+ "ext": { "bidder": { "seat": "seat-eu", "region": "eu", "partner": "acme" } }
+ },
+ {
+ "id": "imp-2",
+ "video": { "mimes": ["video/mp4"], "w": 640, "h": 480 },
+ "ext": { "bidder": { "seat": "seat-eu", "region": "eu", "partner": "other" } }
+ }
+ ],
+ "site": { "id": "271", "domain": "example.com" }
+ },
+ "httpCalls": []
+}
diff --git a/adapters/floxis/floxistest/supplemental/multi-imp-different-seat-errors.json b/adapters/floxis/floxistest/supplemental/multi-imp-different-seat-errors.json
new file mode 100644
index 00000000000..d91f949f491
--- /dev/null
+++ b/adapters/floxis/floxistest/supplemental/multi-imp-different-seat-errors.json
@@ -0,0 +1,25 @@
+{
+ "expectedMakeRequestsErrors": [
+ {
+ "value": "imp imp-2 seat/host .* differs from imp imp-1 .*",
+ "comparison": "regex"
+ }
+ ],
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "imp-1",
+ "banner": { "format": [{ "w": 300, "h": 250 }] },
+ "ext": { "bidder": { "seat": "seat-eu", "region": "eu" } }
+ },
+ {
+ "id": "imp-2",
+ "video": { "mimes": ["video/mp4"], "w": 640, "h": 480 },
+ "ext": { "bidder": { "seat": "seat-other", "region": "apac" } }
+ }
+ ],
+ "site": { "id": "271", "domain": "example.com" }
+ },
+ "httpCalls": []
+}
diff --git a/adapters/floxis/floxistest/supplemental/multi-imp-same-seat-routes-once.json b/adapters/floxis/floxistest/supplemental/multi-imp-same-seat-routes-once.json
new file mode 100644
index 00000000000..a7b8badf405
--- /dev/null
+++ b/adapters/floxis/floxistest/supplemental/multi-imp-same-seat-routes-once.json
@@ -0,0 +1,73 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "imp-1",
+ "banner": { "format": [{ "w": 300, "h": 250 }] },
+ "ext": { "bidder": { "seat": "seat-eu", "region": "eu" } }
+ },
+ {
+ "id": "imp-2",
+ "video": { "mimes": ["video/mp4"], "w": 640, "h": 480 },
+ "ext": { "bidder": { "seat": "seat-eu", "region": "eu" } }
+ }
+ ],
+ "site": { "id": "271", "domain": "example.com" }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://eu.floxis.tech/pbs?seat=seat-eu",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "imp-1",
+ "banner": { "format": [{ "w": 300, "h": 250 }] },
+ "ext": { "bidder": { "seat": "seat-eu", "region": "eu" } }
+ },
+ {
+ "id": "imp-2",
+ "video": { "mimes": ["video/mp4"], "w": 640, "h": 480 },
+ "ext": { "bidder": { "seat": "seat-eu", "region": "eu" } }
+ }
+ ],
+ "site": { "id": "271", "domain": "example.com" }
+ },
+ "impIDs": ["imp-1", "imp-2"]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "cur": "EUR",
+ "seatbid": [
+ {
+ "seat": "floxis",
+ "bid": [
+ { "id": "1", "impid": "imp-1", "price": 1.0, "adm": "ad
", "crid": "c1", "mtype": 1 },
+ { "id": "2", "impid": "imp-2", "price": 2.0, "adm": "", "crid": "c2", "mtype": 2 }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "EUR",
+ "bids": [
+ {
+ "bid": { "id": "1", "impid": "imp-1", "price": 1.0, "adm": "ad
", "crid": "c1", "mtype": 1 },
+ "type": "banner"
+ },
+ {
+ "bid": { "id": "2", "impid": "imp-2", "price": 2.0, "adm": "", "crid": "c2", "mtype": 2 },
+ "type": "video"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/floxis/floxistest/supplemental/no-site-or-app-ok.json b/adapters/floxis/floxistest/supplemental/no-site-or-app-ok.json
new file mode 100644
index 00000000000..a2038835c4a
--- /dev/null
+++ b/adapters/floxis/floxistest/supplemental/no-site-or-app-ok.json
@@ -0,0 +1,56 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": { "format": [{ "w": 300, "h": 250 }] },
+ "ext": { "bidder": { "seat": "pub-seat-1" } }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://us-e.floxis.tech/pbs?seat=pub-seat-1",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": { "format": [{ "w": 300, "h": 250 }] },
+ "ext": { "bidder": { "seat": "pub-seat-1" } }
+ }
+ ]
+ },
+ "impIDs": ["test-imp-id"]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "cur": "USD",
+ "seatbid": [
+ {
+ "seat": "floxis",
+ "bid": [
+ { "id": "1", "impid": "test-imp-id", "price": 1.0, "adm": "ad
", "crid": "c1", "mtype": 1 }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": { "id": "1", "impid": "test-imp-id", "price": 1.0, "adm": "ad
", "crid": "c1", "mtype": 1 },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/floxis/floxistest/supplemental/region-missing-defaults-us-e.json b/adapters/floxis/floxistest/supplemental/region-missing-defaults-us-e.json
new file mode 100644
index 00000000000..e1b0f71ca6e
--- /dev/null
+++ b/adapters/floxis/floxistest/supplemental/region-missing-defaults-us-e.json
@@ -0,0 +1,58 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": { "format": [{ "w": 300, "h": 250 }] },
+ "ext": { "bidder": { "seat": "pub-seat-1" } }
+ }
+ ],
+ "site": { "id": "271", "domain": "example.com" }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://us-e.floxis.tech/pbs?seat=pub-seat-1",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": { "format": [{ "w": 300, "h": 250 }] },
+ "ext": { "bidder": { "seat": "pub-seat-1" } }
+ }
+ ],
+ "site": { "id": "271", "domain": "example.com" }
+ },
+ "impIDs": ["test-imp-id"]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "cur": "USD",
+ "seatbid": [
+ {
+ "seat": "floxis",
+ "bid": [
+ { "id": "1", "impid": "test-imp-id", "price": 1.0, "adm": "ad
", "crid": "c1", "mtype": 1 }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": { "id": "1", "impid": "test-imp-id", "price": 1.0, "adm": "ad
", "crid": "c1", "mtype": 1 },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/floxis/floxistest/supplemental/response-204.json b/adapters/floxis/floxistest/supplemental/response-204.json
new file mode 100644
index 00000000000..8b1cbfabd0a
--- /dev/null
+++ b/adapters/floxis/floxistest/supplemental/response-204.json
@@ -0,0 +1,36 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": { "format": [{ "w": 300, "h": 250 }] },
+ "ext": { "bidder": { "seat": "pub-seat-1" } }
+ }
+ ],
+ "site": { "id": "271", "domain": "example.com" }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://us-e.floxis.tech/pbs?seat=pub-seat-1",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": { "format": [{ "w": 300, "h": 250 }] },
+ "ext": { "bidder": { "seat": "pub-seat-1" } }
+ }
+ ],
+ "site": { "id": "271", "domain": "example.com" }
+ },
+ "impIDs": ["test-imp-id"]
+ },
+ "mockResponse": {
+ "status": 204
+ }
+ }
+ ],
+ "expectedBidResponses": []
+}
diff --git a/adapters/floxis/floxistest/supplemental/response-400.json b/adapters/floxis/floxistest/supplemental/response-400.json
new file mode 100644
index 00000000000..06677c0151f
--- /dev/null
+++ b/adapters/floxis/floxistest/supplemental/response-400.json
@@ -0,0 +1,41 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": { "format": [{ "w": 300, "h": 250 }] },
+ "ext": { "bidder": { "seat": "pub-seat-1" } }
+ }
+ ],
+ "site": { "id": "271", "domain": "example.com" }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://us-e.floxis.tech/pbs?seat=pub-seat-1",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": { "format": [{ "w": 300, "h": 250 }] },
+ "ext": { "bidder": { "seat": "pub-seat-1" } }
+ }
+ ],
+ "site": { "id": "271", "domain": "example.com" }
+ },
+ "impIDs": ["test-imp-id"]
+ },
+ "mockResponse": {
+ "status": 400
+ }
+ }
+ ],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "Unexpected status code: 400. Run with request.debug = 1 for more info",
+ "comparison": "literal"
+ }
+ ]
+}
diff --git a/adapters/floxis/floxistest/supplemental/response-500.json b/adapters/floxis/floxistest/supplemental/response-500.json
new file mode 100644
index 00000000000..78995f1d0cb
--- /dev/null
+++ b/adapters/floxis/floxistest/supplemental/response-500.json
@@ -0,0 +1,41 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": { "format": [{ "w": 300, "h": 250 }] },
+ "ext": { "bidder": { "seat": "pub-seat-1" } }
+ }
+ ],
+ "site": { "id": "271", "domain": "example.com" }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://us-e.floxis.tech/pbs?seat=pub-seat-1",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": { "format": [{ "w": 300, "h": 250 }] },
+ "ext": { "bidder": { "seat": "pub-seat-1" } }
+ }
+ ],
+ "site": { "id": "271", "domain": "example.com" }
+ },
+ "impIDs": ["test-imp-id"]
+ },
+ "mockResponse": {
+ "status": 500
+ }
+ }
+ ],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "Unexpected status code: 500. Run with request.debug = 1 for more info",
+ "comparison": "literal"
+ }
+ ]
+}
diff --git a/adapters/floxis/floxistest/supplemental/response-no-cur.json b/adapters/floxis/floxistest/supplemental/response-no-cur.json
new file mode 100644
index 00000000000..637a5231d36
--- /dev/null
+++ b/adapters/floxis/floxistest/supplemental/response-no-cur.json
@@ -0,0 +1,70 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": { "format": [{ "w": 300, "h": 250 }] },
+ "ext": { "bidder": { "seat": "pub-seat-1" } }
+ }
+ ],
+ "site": { "id": "271", "domain": "example.com" }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://us-e.floxis.tech/pbs?seat=pub-seat-1",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": { "format": [{ "w": 300, "h": 250 }] },
+ "ext": { "bidder": { "seat": "pub-seat-1" } }
+ }
+ ],
+ "site": { "id": "271", "domain": "example.com" }
+ },
+ "impIDs": ["test-imp-id"]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "seatbid": [
+ {
+ "seat": "floxis",
+ "bid": [
+ {
+ "id": "1",
+ "impid": "test-imp-id",
+ "price": 1.0,
+ "adm": "ad
",
+ "crid": "creative-1",
+ "mtype": 1
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "bids": [
+ {
+ "bid": {
+ "id": "1",
+ "impid": "test-imp-id",
+ "price": 1.0,
+ "adm": "ad
",
+ "crid": "creative-1",
+ "mtype": 1
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/floxis/floxistest/supplemental/unmatched-bid-impid.json b/adapters/floxis/floxistest/supplemental/unmatched-bid-impid.json
new file mode 100644
index 00000000000..de64bbbd56d
--- /dev/null
+++ b/adapters/floxis/floxistest/supplemental/unmatched-bid-impid.json
@@ -0,0 +1,59 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": { "format": [{ "w": 300, "h": 250 }] },
+ "ext": { "bidder": { "seat": "pub-seat-1" } }
+ }
+ ],
+ "site": { "id": "271", "domain": "example.com" }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://us-e.floxis.tech/pbs?seat=pub-seat-1",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": { "format": [{ "w": 300, "h": 250 }] },
+ "ext": { "bidder": { "seat": "pub-seat-1" } }
+ }
+ ],
+ "site": { "id": "271", "domain": "example.com" }
+ },
+ "impIDs": ["test-imp-id"]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "cur": "USD",
+ "seatbid": [
+ {
+ "seat": "floxis",
+ "bid": [
+ { "id": "1", "impid": "no-such-imp", "price": 1.0, "crid": "c1" }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": []
+ }
+ ],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "unable to find impression no-such-imp for bid",
+ "comparison": "literal"
+ }
+ ]
+}
diff --git a/adapters/floxis/floxistest/supplemental/unsupported-bid-mtype.json b/adapters/floxis/floxistest/supplemental/unsupported-bid-mtype.json
new file mode 100644
index 00000000000..29547943e43
--- /dev/null
+++ b/adapters/floxis/floxistest/supplemental/unsupported-bid-mtype.json
@@ -0,0 +1,59 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": { "format": [{ "w": 300, "h": 250 }] },
+ "ext": { "bidder": { "seat": "pub-seat-1" } }
+ }
+ ],
+ "site": { "id": "271", "domain": "example.com" }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://us-e.floxis.tech/pbs?seat=pub-seat-1",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": { "format": [{ "w": 300, "h": 250 }] },
+ "ext": { "bidder": { "seat": "pub-seat-1" } }
+ }
+ ],
+ "site": { "id": "271", "domain": "example.com" }
+ },
+ "impIDs": ["test-imp-id"]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "cur": "USD",
+ "seatbid": [
+ {
+ "seat": "floxis",
+ "bid": [
+ { "id": "1", "impid": "test-imp-id", "price": 1.0, "crid": "c1", "mtype": 7 }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": []
+ }
+ ],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "unsupported bid.mtype 7 for impression test-imp-id",
+ "comparison": "literal"
+ }
+ ]
+}
diff --git a/adapters/floxis/params_test.go b/adapters/floxis/params_test.go
new file mode 100644
index 00000000000..b8cdcaafc3c
--- /dev/null
+++ b/adapters/floxis/params_test.go
@@ -0,0 +1,58 @@
+package floxis
+
+import (
+ "encoding/json"
+ "testing"
+
+ "github.com/prebid/prebid-server/v4/openrtb_ext"
+)
+
+func TestValidParams(t *testing.T) {
+ validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+ if err != nil {
+ t.Fatalf("Failed to fetch the json schema. %v", err)
+ }
+
+ for _, p := range validParams {
+ if err := validator.Validate(openrtb_ext.BidderFloxis, json.RawMessage(p)); err != nil {
+ t.Errorf("Schema rejected valid params: %s", p)
+ }
+ }
+}
+
+func TestInvalidParams(t *testing.T) {
+ validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+ if err != nil {
+ t.Fatalf("Failed to fetch the json schema. %v", err)
+ }
+
+ for _, p := range invalidParams {
+ if err := validator.Validate(openrtb_ext.BidderFloxis, json.RawMessage(p)); err == nil {
+ t.Errorf("Schema allowed invalid params: %s", p)
+ }
+ }
+}
+
+var validParams = []string{
+ `{"seat":"abc"}`,
+ `{"seat":"abc","region":"us-e"}`,
+ `{"seat":"abc","region":"eu"}`,
+ `{"seat":"abc","region":"apac"}`,
+ `{"seat":"abc","region":"mars"}`,
+ `{"seat":"x","partner":"acme"}`,
+ `{"seat":"x","region":"eu","partner":"acme"}`,
+}
+
+var invalidParams = []string{
+ ``,
+ `null`,
+ `{}`,
+ `{"region":"us-e"}`,
+ `{"seat":""}`,
+ `{"seat":123}`,
+ `{"seat":"abc","region":"a.b"}`,
+ `{"seat":"abc","region":123}`,
+ `{"seat":"x","partner":"a.b/c"}`,
+ `{"seat":"x","partner":123}`,
+ `{"seat":"abc","unknownField":"foo"}`,
+}
diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go
index 2d6526a1900..1dc4bbb682d 100755
--- a/exchange/adapter_builders.go
+++ b/exchange/adapter_builders.go
@@ -108,6 +108,7 @@ import (
"github.com/prebid/prebid-server/v4/adapters/feedad"
"github.com/prebid/prebid-server/v4/adapters/flatads"
"github.com/prebid/prebid-server/v4/adapters/flipp"
+ "github.com/prebid/prebid-server/v4/adapters/floxis"
"github.com/prebid/prebid-server/v4/adapters/freewheelssp"
"github.com/prebid/prebid-server/v4/adapters/frvradn"
"github.com/prebid/prebid-server/v4/adapters/fwssp"
@@ -379,6 +380,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder {
openrtb_ext.BidderFeedAd: feedad.Builder,
openrtb_ext.BidderFlatads: flatads.Builder,
openrtb_ext.BidderFlipp: flipp.Builder,
+ openrtb_ext.BidderFloxis: floxis.Builder,
openrtb_ext.BidderFreewheelSSP: freewheelssp.Builder,
openrtb_ext.BidderFWSSP: fwssp.Builder,
openrtb_ext.BidderFRVRAdNetwork: frvradn.Builder,
diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go
index 9e9cfaa7610..e92305a0491 100644
--- a/openrtb_ext/bidders.go
+++ b/openrtb_ext/bidders.go
@@ -125,6 +125,7 @@ var coreBidderNames []BidderName = []BidderName{
BidderFeedAd,
BidderFlatads,
BidderFlipp,
+ BidderFloxis,
BidderFreewheelSSP,
BidderFWSSP,
BidderFRVRAdNetwork,
@@ -502,6 +503,7 @@ const (
BidderFeedAd BidderName = "feedad"
BidderFlatads BidderName = "flatads"
BidderFlipp BidderName = "flipp"
+ BidderFloxis BidderName = "floxis"
BidderFreewheelSSP BidderName = "freewheelssp"
BidderFWSSP BidderName = "fwssp"
BidderFRVRAdNetwork BidderName = "frvradn"
diff --git a/openrtb_ext/imp_floxis.go b/openrtb_ext/imp_floxis.go
new file mode 100644
index 00000000000..bbe97d392d2
--- /dev/null
+++ b/openrtb_ext/imp_floxis.go
@@ -0,0 +1,8 @@
+package openrtb_ext
+
+// ExtImpFloxis defines the contract for bidrequest.imp[i].ext.prebid.bidder.floxis.
+type ExtImpFloxis struct {
+ Seat string `json:"seat"`
+ Region string `json:"region,omitempty"`
+ Partner string `json:"partner,omitempty"`
+}
diff --git a/static/bidder-info/floxis.yaml b/static/bidder-info/floxis.yaml
new file mode 100644
index 00000000000..90b4021cf20
--- /dev/null
+++ b/static/bidder-info/floxis.yaml
@@ -0,0 +1,29 @@
+# {{.Host}} is the region/partner subdomain label (e.g. us-e, or acme-us-e for a named partner),
+# validated in the adapter as a single DNS host-label and prefixed onto the fixed .floxis.tech
+# domain below; request-supplied hostnames are never used. The seat query param is appended in
+# code (not a standard macro).
+endpoint: "https://{{.Host}}.floxis.tech/pbs"
+maintainer:
+ email: prebid@floxis.tech
+gvlVendorID: 1609
+modifyingVastXmlAllowed: false
+openrtb:
+ version: 2.6
+ gpp-supported: true
+capabilities:
+ app:
+ mediaTypes:
+ - banner
+ - video
+ - native
+ - audio
+ site:
+ mediaTypes:
+ - banner
+ - video
+ - native
+ - audio
+userSync:
+ redirect:
+ url: "https://px-us-e.floxis.tech/sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&us_privacy={{.USPrivacy}}&dest={{.RedirectURL}}"
+ userMacro: "${USER_ID}"
diff --git a/static/bidder-params/floxis.json b/static/bidder-params/floxis.json
new file mode 100644
index 00000000000..3fa51592251
--- /dev/null
+++ b/static/bidder-params/floxis.json
@@ -0,0 +1,25 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "title": "Floxis Adapter Params",
+ "description": "A schema which validates params accepted by the Floxis adapter",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "seat": {
+ "type": "string",
+ "minLength": 1,
+ "description": "The Floxis seat ID this publisher buys through"
+ },
+ "region": {
+ "type": "string",
+ "pattern": "^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$",
+ "description": "The Floxis RTB region as a host label (e.g. us-e, eu, apac); defaults to us-e when omitted"
+ },
+ "partner": {
+ "type": "string",
+ "pattern": "^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$",
+ "description": "Partner identifier (default floxis)"
+ }
+ },
+ "required": ["seat"]
+}