Repository: prebid/prebid-server
Pull request #1461 (2020-08-26 22:39:24)

Brightroll adapter - adding config support
Description:
Adding configuration support through extraAdapterInfo for Brightroll adapter
Moving existing publisher adapter configuration in the code to configuration

@SyntaxNode 

smithaammassamveettil: @SyntaxNode Noticed that the build here is failing on go version 1.13. Build is passing on 1.14. Why building on two versions? Not sure why it is failing on 1.13.
SyntaxNode: I'm not sure either. I kicked it off again to see if this is a repeating issue.

Patch:
diff --git a/adapters/brightroll/brightroll.go b/adapters/brightroll/brightroll.go
index 0ae95dd3..83b253a0 100644
--- a/adapters/brightroll/brightroll.go
+++ b/adapters/brightroll/brightroll.go
@@ -6,6 +6,7 @@ import (
 	"net/http"
 	"strconv"
 
+	"github.com/golang/glog"
 	"github.com/mxmCherry/openrtb"
 	"github.com/prebid/prebid-server/adapters"
 	"github.com/prebid/prebid-server/errortypes"
@@ -13,7 +14,20 @@ import (
 )
 
 type BrightrollAdapter struct {
-	URI string
+	URI       string
+	extraInfo ExtraInfo
+}
+
+type ExtraInfo struct {
+	Accounts []Account `json:"accounts"`
+}
+
+type Account struct {
+	ID       string   `json:"id"`
+	Badv     []string `json:"badv"`
+	Bcat     []string `json:"bcat"`
+	Battr    []int8   `json:"battr"`
+	BidFloor float64  `json:"bidfloor"`
 }
 
 func (a *BrightrollAdapter) MakeRequests(requestIn *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
@@ -55,6 +69,23 @@ func (a *BrightrollAdapter) MakeRequests(requestIn *openrtb.BidRequest, reqInfo
 		errors = append(errors, err)
 		return nil, errors
 	}
+
+	var account *Account
+	for _, a := range a.extraInfo.Accounts {
+		if a.ID == brightrollExt.Publisher {
+			account = &a
+			break
+		}
+	}
+
+	if account == nil {
+		err = &errortypes.BadInput{
+			Message: "Invalid publisher",
+		}
+		errors = append(errors, err)
+		return nil, errors
+	}
+
 	validImpExists := false
 	for i := 0; i < len(request.Imp); i++ {
 		//Brightroll supports only banner and video impressions as of now
@@ -65,9 +96,9 @@ func (a *BrightrollAdapter) MakeRequests(requestIn *openrtb.BidRequest, reqInfo
 				bannerCopy.W = &(firstFormat.W)
 				bannerCopy.H = &(firstFormat.H)
 			}
-			if brightrollExt.Publisher == "adthrive" {
-				bannerCopy.BAttr = getBlockedCreativetypesForAdThrive()
 
+			if len(account.Battr) > 0 {
+				bannerCopy.BAttr = getBlockedCreativetypes(account.Battr)
 			}
 			request.Imp[i].Banner = &bannerCopy
 			validImpExists = true
@@ -75,10 +106,15 @@ func (a *BrightrollAdapter) MakeRequests(requestIn *openrtb.BidRequest, reqInfo
 			validImpExists = true
 			if brightrollExt.Publisher == "adthrive" {
 				videoCopy := *request.Imp[i].Video
-				videoCopy.BAttr = getBlockedCreativetypesForAdThrive()
+				if len(account.Battr) > 0 {
+					videoCopy.BAttr = getBlockedCreativetypes(account.Battr)
+				}
 				request.Imp[i].Video = &videoCopy
 			}
 		}
+		if validImpExists && request.Imp[i].BidFloor == 0 && account.BidFloor > 0 {
+			request.Imp[i].BidFloor = account.BidFloor
+		}
 	}
 	if !validImpExists {
 		err := &errortypes.BadInput{
@@ -90,8 +126,12 @@ func (a *BrightrollAdapter) MakeRequests(requestIn *openrtb.BidRequest, reqInfo
 
 	request.AT = 1 //Defaulting to first price auction for all prebid requests
 
-	if brightrollExt.Publisher == "adthrive" {
-		request.BCat = getBlockedCategoriesForAdthrive()
+	if len(account.Bcat) > 0 {
+		request.BCat = account.Bcat
+	}
+
+	if len(account.Badv) > 0 {
+		request.BAdv = account.Badv
 	}
 	reqJSON, err := json.Marshal(request)
 	if err != nil {
@@ -159,13 +199,12 @@ func (a *BrightrollAdapter) MakeBids(internalRequest *openrtb.BidRequest, extern
 	return bidResponse, nil
 }
 
-//customized request, need following blocked categories
-func getBlockedCategoriesForAdthrive() []string {
-	return []string{"IAB8-5", "IAB8-18", "IAB15-1", "IAB7-30", "IAB14-1", "IAB22-1", "IAB3-7", "IAB7-3", "IAB14-3", "IAB11", "IAB11-1", "IAB11-2", "IAB11-3", "IAB11-4", "IAB11-5", "IAB23", "IAB23-1", "IAB23-2", "IAB23-3", "IAB23-4", "IAB23-5", "IAB23-6", "IAB23-7", "IAB23-8", "IAB23-9", "IAB23-10", "IAB7-39", "IAB9-30", "IAB7-44", "IAB25", "IAB25-1", "IAB25-2", "IAB25-3", "IAB25-4", "IAB25-5", "IAB25-6", "IAB25-7", "IAB26", "IAB26-1", "IAB26-2", "IAB26-3", "IAB26-4"}
-}
-
-func getBlockedCreativetypesForAdThrive() []openrtb.CreativeAttribute {
-	return []openrtb.CreativeAttribute{openrtb.CreativeAttribute(1), openrtb.CreativeAttribute(2), openrtb.CreativeAttribute(3), openrtb.CreativeAttribute(6), openrtb.CreativeAttribute(9), openrtb.CreativeAttribute(10)}
+func getBlockedCreativetypes(attr []int8) []openrtb.CreativeAttribute {
+	var creativeAttr []openrtb.CreativeAttribute
+	for i := 0; i < len(attr); i++ {
+		creativeAttr = append(creativeAttr, openrtb.CreativeAttribute(attr[i]))
+	}
+	return creativeAttr
 }
 
 //Adding header fields to request header
@@ -189,8 +228,18 @@ func getMediaTypeForImp(impId string, imps []openrtb.Imp) openrtb_ext.BidType {
 	return mediaType
 }
 
-func NewBrightrollBidder(endpoint string) *BrightrollAdapter {
-	return &BrightrollAdapter{
-		URI: endpoint,
+func NewBrightrollBidder(endpoint string, extraAdapterInfo string) *BrightrollAdapter {
+
+	var extraInfo ExtraInfo
+
+	if len(extraAdapterInfo) == 0 {
+		extraAdapterInfo = "{\"accounts\":[]}"
+	}
+	err := json.Unmarshal([]byte(extraAdapterInfo), &extraInfo)
+
+	if err != nil {
+		glog.Fatalf("Invalid Brightroll extra adapter info: " + err.Error())
+		return nil
 	}
+	return &BrightrollAdapter{URI: endpoint, extraInfo: extraInfo}
 }
diff --git a/adapters/rubicon/rubicon.go b/adapters/rubicon/rubicon.go
index a69530de..56ae7b2f 100644
--- a/adapters/rubicon/rubicon.go
+++ b/adapters/rubicon/rubicon.go
@@ -93,11 +93,12 @@ type rubiconExtUserTpID struct {
 }
 
 type rubiconUserExt struct {
-	Consent   string                        `json:"consent,omitempty"`
-	DigiTrust *openrtb_ext.ExtUserDigiTrust `json:"digitrust"`
-	Eids      []openrtb_ext.ExtUserEid      `json:"eids,omitempty"`
-	TpID      []rubiconExtUserTpID          `json:"tpid,omitempty"`
-	RP        rubiconUserExtRP              `json:"rp"`
+	Consent     string                        `json:"consent,omitempty"`
+	DigiTrust   *openrtb_ext.ExtUserDigiTrust `json:"digitrust"`
+	Eids        []openrtb_ext.ExtUserEid      `json:"eids,omitempty"`
+	TpID        []rubiconExtUserTpID          `json:"tpid,omitempty"`
+	RP          rubiconUserExtRP              `json:"rp"`
+	LiverampIdl string                        `json:"liveramp_idl,omitempty"`
 }
 
 type rubiconSiteExtRP struct {
@@ -247,6 +248,12 @@ type rubiconUserExtEidUidExt struct {
 	RtiPartner string `json:"rtiPartner,omitempty"`
 }
 
+type mappedRubiconUidsParam struct {
+	tpIds       []rubiconExtUserTpID
+	segments    []string
+	liverampIdl string
+}
+
 //MAS algorithm
 func findPrimary(alt []int) (int, []int) {
 	min, pos, primary := 0, 0, 0
@@ -683,13 +690,18 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adap
 
 				// set user.ext.tpid
 				if len(userExt.Eids) > 0 {
-					if tpIds, segments, errors := getTpIdsAndSegments(userExt.Eids); len(errors) > 0 {
+					mappedRubiconUidsParam, errors := getTpIdsAndSegments(userExt.Eids)
+					if len(errors) > 0 {
 						errs = append(errs, errors...)
 						continue
-					} else if err := updateUserExtWithTpIdsAndSegments(&userExtRP, tpIds, segments); err != nil {
+					}
+
+					if err := updateUserExtWithTpIdsAndSegments(&userExtRP, mappedRubiconUidsParam); err != nil {
 						errs = append(errs, err)
 						continue
 					}
+
+					userExtRP.LiverampIdl = mappedRubiconUidsParam.liverampIdl
 				}
 			}
 
@@ -793,9 +805,11 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adap
 	return requestData, errs
 }
 
-func getTpIdsAndSegments(eids []openrtb_ext.ExtUserEid) ([]rubiconExtUserTpID, []string, []error) {
-	tpIds := make([]rubiconExtUserTpID, 0)
-	segments := make([]string, 0)
+func getTpIdsAndSegments(eids []openrtb_ext.ExtUserEid) (mappedRubiconUidsParam, []error) {
+	rubiconUidsParam := mappedRubiconUidsParam{
+		tpIds:    make([]rubiconExtUserTpID, 0),
+		segments: make([]string, 0),
+	}
 	errs := make([]error, 0)
 
 	for _, eid := range eids {
@@ -815,7 +829,7 @@ func getTpIdsAndSegments(eids []openrtb_ext.ExtUserEid) ([]rubiconExtUserTpID, [
 					}
 
 					if eidUidExt.RtiPartner == "TDID" {
-						tpIds = append(tpIds, rubiconExtUserTpID{Source: "tdid", UID: uid.ID})
+						rubiconUidsParam.tpIds = append(rubiconUidsParam.tpIds, rubiconExtUserTpID{Source: "tdid", UID: uid.ID})
 					}
 				}
 			}
@@ -824,7 +838,7 @@ func getTpIdsAndSegments(eids []openrtb_ext.ExtUserEid) ([]rubiconExtUserTpID, [
 			if len(uids) > 0 {
 				uidId := uids[0].ID
 				if uidId != "" {
-					tpIds = append(tpIds, rubiconExtUserTpID{Source: "liveintent.com", UID: uidId})
+					rubiconUidsParam.tpIds = append(rubiconUidsParam.tpIds, rubiconExtUserTpID{Source: "liveintent.com", UID: uidId})
 				}
 
 				if eid.Ext != nil {
@@ -835,20 +849,28 @@ func getTpIdsAndSegments(eids []openrtb_ext.ExtUserEid) ([]rubiconExtUserTpID, [
 						})
 						continue
 					}
-					segments = eidExt.Segments
+					rubiconUidsParam.segments = eidExt.Segments
+				}
+			}
+		case "liveramp.com":
+			uids := eid.Uids
+			if len(uids) > 0 {
+				uidId := uids[0].ID
+				if uidId != "" && rubiconUidsParam.liverampIdl == "" {
+					rubiconUidsParam.liverampIdl = uidId
 				}
 			}
 		}
 	}
 
-	return tpIds, segments, errs
+	return rubiconUidsParam, errs
 }
 
-func updateUserExtWithTpIdsAndSegments(userExtRP *rubiconUserExt, tpIds []rubiconExtUserTpID, segments []string) error {
-	if len(tpIds) > 0 {
-		userExtRP.TpID = tpIds
+func updateUserExtWithTpIdsAndSegments(userExtRP *rubiconUserExt, rubiconUidsParam mappedRubiconUidsParam) error {
+	if len(rubiconUidsParam.tpIds) > 0 {
+		userExtRP.TpID = rubiconUidsParam.tpIds
 
-		if segments != nil {
+		if rubiconUidsParam.segments != nil {
 			userExtRPTarget := make(map[string]interface{})
 
 			if userExtRP.RP.Target != nil {
@@ -857,7 +879,7 @@ func updateUserExtWithTpIdsAndSegments(userExtRP *rubiconUserExt, tpIds []rubico
 				}
 			}
 
-			userExtRPTarget["LIseg"] = segments
+			userExtRPTarget["LIseg"] = rubiconUidsParam.segments
 
 			if target, err := json.Marshal(&userExtRPTarget); err != nil {
 				return &errortypes.BadInput{Message: err.Error()}
diff --git a/adapters/smaato/smaato.go b/adapters/smaato/smaato.go
index 06678d77..b48851bb 100644
--- a/adapters/smaato/smaato.go
+++ b/adapters/smaato/smaato.go
@@ -20,6 +20,7 @@ type adMarkupType string
 const (
 	smtAdTypeImg       adMarkupType = "Img"
 	smtAdTypeRichmedia adMarkupType = "Richmedia"
+	smtAdTypeVideo     adMarkupType = "Video"
 )
 
 // SmaatoAdapter describes a Smaato prebid server adapter.
@@ -63,7 +64,7 @@ func (a *SmaatoAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapt
 	}
 
 	// Use bidRequestExt of first imp to retrieve params which are valid for all imps, e.g. publisherId
-	publisherId, err := jsonparser.GetString(request.Imp[0].Ext, "bidder", "publisherId")
+	publisherID, err := jsonparser.GetString(request.Imp[0].Ext, "bidder", "publisherId")
 	if err != nil {
 		errs = append(errs, err)
 		return nil, errs
@@ -80,7 +81,7 @@ func (a *SmaatoAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapt
 	}
 	if request.Site != nil {
 		siteCopy := *request.Site
-		siteCopy.Publisher = &openrtb.Publisher{ID: publisherId}
+		siteCopy.Publisher = &openrtb.Publisher{ID: publisherID}
 
 		if request.Site.Ext != nil {
 			var siteExt siteExt
@@ -178,16 +179,25 @@ func (a *SmaatoAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRe
 		for i := 0; i < len(sb.Bid); i++ {
 			bid := sb.Bid[i]
 
+			markupType, markupTypeErr := getAdMarkupType(response, bid.AdM)
+			if markupTypeErr != nil {
+				return nil, []error{markupTypeErr}
+			}
+
 			var markupError error
-			bid.AdM, markupError = renderAdMarkup(getAdMarkupType(response, bid.AdM), bid.AdM)
+			bid.AdM, markupError = renderAdMarkup(markupType, bid.AdM)
 			if markupError != nil {
-				fmt.Println(markupError)
-				continue // no bid when broken ad markup
+				return nil, []error{markupError}
+			}
+
+			bidType, bidTypeErr := markupTypeToBidType(markupType)
+			if bidTypeErr != nil {
+				return nil, []error{bidTypeErr}
 			}
 
 			bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
 				Bid:     &bid,
-				BidType: openrtb_ext.BidTypeBanner,
+				BidType: bidType,
 			})
 		}
 	}
@@ -202,23 +212,41 @@ func renderAdMarkup(adMarkupType adMarkupType, adMarkup string) (string, error)
 		adm, markupError = extractAdmImage(adMarkup)
 	case smtAdTypeRichmedia:
 		adm, markupError = extractAdmRichMedia(adMarkup)
+	case smtAdTypeVideo:
+		adm, markupError = adMarkup, nil
 	default:
 		return "", fmt.Errorf("Unknown markup type %s", adMarkupType)
 	}
 	return adm, markupError
 }
 
-func getAdMarkupType(response *adapters.ResponseData, adMarkup string) adMarkupType {
+func markupTypeToBidType(markupType adMarkupType) (openrtb_ext.BidType, error) {
+	switch markupType {
+	case smtAdTypeImg:
+		return openrtb_ext.BidTypeBanner, nil
+	case smtAdTypeRichmedia:
+		return openrtb_ext.BidTypeBanner, nil
+	case smtAdTypeVideo:
+		return openrtb_ext.BidTypeVideo, nil
+	default:
+		return "", fmt.Errorf("Invalid markupType %s", markupType)
+	}
+}
+
+func getAdMarkupType(response *adapters.ResponseData, adMarkup string) (adMarkupType, error) {
 	if admType := adMarkupType(response.Headers.Get("X-SMT-ADTYPE")); admType != "" {
-		return admType
+		return admType, nil
 	}
 	if strings.HasPrefix(adMarkup, `{"image":`) {
-		return smtAdTypeImg
+		return smtAdTypeImg, nil
 	}
 	if strings.HasPrefix(adMarkup, `{"richmedia":`) {
-		return smtAdTypeRichmedia
+		return smtAdTypeRichmedia, nil
+	}
+	if strings.HasPrefix(adMarkup, `<?xml`) {
+		return smtAdTypeVideo, nil
 	}
-	return ""
+	return "", fmt.Errorf("Invalid ad markup %s", adMarkup)
 }
 
 func assignBannerSize(banner *openrtb.Banner) (*openrtb.Banner, error) {
@@ -255,7 +283,14 @@ func parseImpressionObject(imp *openrtb.Imp) error {
 		imp.Ext = nil
 		return nil
 	}
-	return fmt.Errorf("invalid MediaType. SMAATO only supports Banner. Ignoring ImpID=%s", imp.ID)
+
+	if imp.Video != nil {
+		imp.TagID = adSpaceID
+		imp.Ext = nil
+		return nil
+	}
+
+	return fmt.Errorf("invalid MediaType. SMAATO only supports Banner and Video. Ignoring ImpID=%s", imp.ID)
 }
 
 func extractUserExtAttributes(userExt userExt, userCopy *openrtb.User) {
diff --git a/analytics/core.go b/analytics/core.go
index 6fd5139f..737d1334 100644
--- a/analytics/core.go
+++ b/analytics/core.go
@@ -2,6 +2,7 @@ package analytics
 
 import (
 	"github.com/mxmCherry/openrtb"
+	"github.com/prebid/prebid-server/config"
 	"github.com/prebid/prebid-server/openrtb_ext"
 	"github.com/prebid/prebid-server/usersync"
 )
@@ -28,6 +29,7 @@ type AuctionObject struct {
 	Errors   []error
 	Request  *openrtb.BidRequest
 	Response *openrtb.BidResponse
+	Account  *config.Account
 }
 
 //Loggable object of a transaction at /openrtb2/amp endpoint
diff --git a/config/accounts.go b/config/accounts.go
new file mode 100644
index 00000000..11c2e10e
--- /dev/null
+++ b/config/accounts.go
@@ -0,0 +1,8 @@
+package config
+
+// Account represents a publisher account configuration
+type Account struct {
+	ID       string      `mapstructure:"id" json:"id"`
+	Disabled bool        `mapstructure:"disabled" json:"disabled"`
+	CacheTTL DefaultTTLs `mapstructure:"cache_ttl" json:"cache_ttl"`
+}
diff --git a/config/config.go b/config/config.go
index c2e7dfd8..59ba55eb 100755
--- a/config/config.go
+++ b/config/config.go
@@ -2,6 +2,7 @@ package config
 
 import (
 	"bytes"
+	"encoding/json"
 	"errors"
 	"fmt"
 	"net/url"
@@ -29,18 +30,20 @@ type Configuration struct {
 	EnableGzip  bool       `mapstructure:"enable_gzip"`
 	// StatusResponse is the string which will be returned by the /status endpoint when things are OK.
 	// If empty, it will return a 204 with no content.
-	StatusResponse  string             `mapstructure:"status_response"`
-	AuctionTimeouts AuctionTimeouts    `mapstructure:"auction_timeouts_ms"`
-	CacheURL        Cache              `mapstructure:"cache"`
-	ExtCacheURL     ExternalCache      `mapstructure:"external_cache"`
-	RecaptchaSecret string             `mapstructure:"recaptcha_secret"`
-	HostCookie      HostCookie         `mapstructure:"host_cookie"`
-	Metrics         Metrics            `mapstructure:"metrics"`
-	DataCache       DataCache          `mapstructure:"datacache"`
-	StoredRequests  StoredRequests     `mapstructure:"stored_requests"`
-	CategoryMapping StoredRequestsSlim `mapstructure:"category_mapping"`
+	StatusResponse    string          `mapstructure:"status_response"`
+	AuctionTimeouts   AuctionTimeouts `mapstructure:"auction_timeouts_ms"`
+	CacheURL          Cache           `mapstructure:"cache"`
+	ExtCacheURL       ExternalCache   `mapstructure:"external_cache"`
+	RecaptchaSecret   string          `mapstructure:"recaptcha_secret"`
+	HostCookie        HostCookie      `mapstructure:"host_cookie"`
+	Metrics           Metrics         `mapstructure:"metrics"`
+	DataCache         DataCache       `mapstructure:"datacache"`
+	StoredRequests    StoredRequests  `mapstructure:"stored_requests"`
+	StoredRequestsAMP StoredRequests  `mapstructure:"stored_amp_req"`
+	CategoryMapping   StoredRequests  `mapstructure:"category_mapping"`
+	Accounts          StoredRequests  `mapstructure:"accounts"`
 	// Note that StoredVideo refers to stored video requests, and has nothing to do with caching video creatives.
-	StoredVideo StoredRequestsSlim `mapstructure:"stored_video_req"`
+	StoredVideo StoredRequests `mapstructure:"stored_video_req"`
 
 	// Adapters should have a key for every openrtb_ext.BidderName, converted to lower-case.
 	// Se also: https://github.com/spf13/viper/issues/371#issuecomment-335388559
@@ -64,6 +67,11 @@ type Configuration struct {
 	BlacklistedAcctMap map[string]bool
 	// Is publisher/account ID required to be submitted in the OpenRTB2 request
 	AccountRequired bool `mapstructure:"account_required"`
+	// AccountDefaults defines default settings for valid accounts that are partially defined
+	// and provides a way to set global settings that can be overridden at account level.
+	AccountDefaults Account `mapstructure:"account_defaults"`
+	// accountDefaultsJSON is the internal serialized form of AccountDefaults used for json merge
+	accountDefaultsJSON json.RawMessage
 	// Local private file containing SSL certificates
 	PemCertsFile string `mapstructure:"certificates_file"`
 	// Custom headers to handle request timeouts from queueing infrastructure
@@ -72,6 +80,8 @@ type Configuration struct {
 	Debug Debug `mapstructure:"debug"`
 	// RequestValidation specifies the request validation options.
 	RequestValidation RequestValidation `mapstructure:"request_validation"`
+	// When true, PBS will assign a randomly generated UUID to req.Source.TID if it is empty
+	AutoGenSourceTID bool `mapstructure:"auto_gen_source_tid"`
 }
 
 const MIN_COOKIE_SIZE_BYTES = 500
@@ -104,6 +114,10 @@ func (cfg *Configuration) validate() configErrors {
 	var errs configErrors
 	errs = cfg.AuctionTimeouts.validate(errs)
 	errs = cfg.StoredRequests.validate(errs)
+	errs = cfg.StoredRequestsAMP.validate(errs)
+	errs = cfg.Accounts.validate(errs)
+	errs = cfg.CategoryMapping.validate(errs)
+	errs = cfg.StoredVideo.validate(errs)
 	errs = cfg.Metrics.validate(errs)
 	if cfg.MaxRequestSize < 0 {
 		errs = append(errs, fmt.Errorf("cfg.max_request_size must be >= 0. Got %d", cfg.MaxRequestSize))
@@ -113,6 +127,9 @@ func (cfg *Configuration) validate() configErrors {
 	errs = validateAdapters(cfg.Adapters, errs)
 	errs = cfg.Debug.validate(errs)
 	errs = cfg.ExtCacheURL.validate(errs)
+	if cfg.AccountDefaults.Disabled {
+		glog.Warning(`With account_defaults.disabled=true, host-defined accounts must exist and have "disabled":false. All other requests will be rejected.`)
+	}
 	return errs
 }
 
@@ -583,6 +600,12 @@ func New(v *viper.Viper) (*Configuration, error) {
 		return nil, err
 	}
 
+	// Update account defaults and generate base json for patch
+	c.AccountDefaults.CacheTTL = c.CacheURL.DefaultTTLs // comment this out to set explicitly in config
+	if err := c.MarshalAccountDefaults(); err != nil {
+		return nil, err
+	}
+
 	// To look for a request's publisher_id in the NonStandardPublishers list in
 	// O(1) time, we fill this hash table located in the NonStandardPublisherMap field of GDPR
 	c.GDPR.NonStandardPublisherMap = make(map[string]int)
@@ -604,6 +627,9 @@ func New(v *viper.Viper) (*Configuration, error) {
 		c.BlacklistedAcctMap[c.BlacklistedAccts[i]] = true
 	}
 
+	// Migrate combo stored request config to separate stored_reqs and amp stored_reqs configs.
+	resolvedStoredRequestsConfig(&c)
+
 	glog.Info("Logging the resolved configuration:")
 	logGeneral(reflect.ValueOf(c), "  \t")
 	if errs := c.validate(); len(errs) > 0 {
@@ -613,6 +639,20 @@ func New(v *viper.Viper) (*Configuration, error) {
 	return &c, nil
 }
 
+// MarshalAccountDefaults compiles AccountDefaults into the JSON format used for merge patch
+func (cfg *Configuration) MarshalAccountDefaults() error {
+	var err error
+	if cfg.accountDefaultsJSON, err = json.Marshal(cfg.AccountDefaults); err != nil {
+		glog.Warningf("converting %+v to json: %v", cfg.AccountDefaults, err)
+	}
+	return err
+}
+
+// AccountDefaultsJSON returns the precompiled JSON form of account_defaults
+func (cfg *Configuration) AccountDefaultsJSON() json.RawMessage {
+	return cfg.accountDefaultsJSON
+}
+
 //Allows for protocol relative URL if scheme is empty
 func (cfg *Cache) GetBaseURL() string {
 	cfg.Scheme = strings.ToLower(cfg.Scheme)
@@ -721,6 +761,7 @@ func SetupViper(v *viper.Viper, filename string) {
 		v.AddConfigPath(".")
 		v.AddConfigPath("/etc/config")
 	}
+
 	// Fixes #475: Some defaults will be set just so they are accessible via environment variables
 	// (basically so viper knows they exist)
 	v.SetDefault("external_url", "http://localhost:8000")
@@ -779,7 +820,8 @@ func SetupViper(v *viper.Viper, filename string) {
 	v.SetDefault("category_mapping.filesystem.enabled", true)
 	v.SetDefault("category_mapping.filesystem.directorypath", "./static/category-mapping")
 	v.SetDefault("category_mapping.http.endpoint", "")
-	v.SetDefault("stored_requests.filesystem", false)
+	v.SetDefault("stored_requests.filesystem.enabled", false)
+	v.SetDefault("stored_requests.filesystem.directorypath", "./stored_requests/data/by_id")
 	v.SetDefault("stored_requests.directorypath", "./stored_requests/data/by_id")
 	v.SetDefault("stored_requests.postgres.connection.dbname", "")
 	v.SetDefault("stored_requests.postgres.connection.host", "")
@@ -832,6 +874,10 @@ func SetupViper(v *viper.Viper, filename string) {
 	v.SetDefault("stored_video_req.http_events.refresh_rate_seconds", 0)
 	v.SetDefault("stored_video_req.http_events.timeout_ms", 0)
 
+	v.SetDefault("accounts.filesystem.enabled", false)
+	v.SetDefault("accounts.filesystem.directorypath", "./stored_requests/data/by_id")
+	v.SetDefault("accounts.in_memory_cache.type", "none")
+
 	for _, bidder := range openrtb_ext.BidderMap {
 		setBidderDefaults(v, strings.ToLower(string(bidder)))
 	}
@@ -965,7 +1011,9 @@ func SetupViper(v *viper.Viper, filename string) {
 	v.SetDefault("blacklisted_apps", []string{""})
 	v.SetDefault("blacklisted_accts", []string{""})
 	v.SetDefault("account_required", false)
+	v.SetDefault("account_defaults.disabled", false)
 	v.SetDefault("certificates_file", "")
+	v.SetDefault("auto_gen_source_tid", true)
 
 	v.SetDefault("request_timeout_headers.request_time_in_queue", "")
 	v.SetDefault("request_timeout_headers.request_timeout_in_queue", "")
@@ -995,6 +1043,22 @@ func SetupViper(v *viper.Viper, filename string) {
 	v.SetEnvPrefix("PBS")
 	v.AutomaticEnv()
 	v.ReadInConfig()
+
+	// Migrate config settings to maintain compatibility with old configs
+	migrateConfig(v)
+}
+
+func migrateConfig(v *viper.Viper) {
+	// if stored_requests.filesystem is not a map in conf file as expected from defaults,
+	// means we have old-style settings; migrate them to new filesystem map to avoid breaking viper
+	if _, ok := v.Get("stored_requests.filesystem").(map[string]interface{}); !ok {
+		glog.Warning("stored_requests.filesystem should be changed to stored_requests.filesystem.enabled")
+		glog.Warning("stored_requests.directorypath should be changed to stored_requests.filesystem.directorypath")
+		m := v.GetStringMap("stored_requests.filesystem")
+		m["enabled"] = v.GetBool("stored_requests.filesystem")
+		m["directorypath"] = v.GetString("stored_requests.directorypath")
+		v.Set("stored_requests.filesystem", m)
+	}
 }
 
 func setBidderDefaults(v *viper.Viper, bidder string) {
diff --git a/config/stored_requests.go b/config/stored_requests.go
index 04e400f9..61db7eb0 100644
--- a/config/stored_requests.go
+++ b/config/stored_requests.go
@@ -2,7 +2,6 @@ package config
 
 import (
 	"bytes"
-	"errors"
 	"fmt"
 	"strconv"
 	"strings"
@@ -11,44 +10,47 @@ import (
 	"github.com/golang/glog"
 )
 
-// StoredRequests configures the backend used to store requests on the server.
-type StoredRequests struct {
-	// Files should be true if Stored Requests should be loaded from the filesystem.
-	Files bool `mapstructure:"filesystem"`
-	//If data should be loaded from file system, path should be specified in configuration
-	Path string `mapstructure:"directorypath"`
-	// Postgres configures Fetchers and EventProducers which read from a Postgres DB.
-	// Fetchers are in stored_requests/backends/db_fetcher/postgres.go
-	// EventProducers are in stored_requests/events/postgres
-	Postgres PostgresConfig `mapstructure:"postgres"`
-	// HTTP configures an instance of stored_requests/backends/http/http_fetcher.go.
-	// If non-nil, Stored Requests will be fetched from the endpoint described there.
-	HTTP HTTPFetcherConfig `mapstructure:"http"`
-	// InMemoryCache configures an instance of stored_requests/caches/memory/cache.go.
-	// If non-nil, Stored Requests will be saved in an in-memory cache.
-	InMemoryCache InMemoryCache `mapstructure:"in_memory_cache"`
-	// CacheEventsAPI configures an instance of stored_requests/events/api/api.go.
-	// If non-nil, Stored Request Caches can be updated or invalidated through API endpoints.
-	// This is intended to be a useful development tool and not recommended for a production environment.
-	// It should not be exposed to public networks without authentication.
-	CacheEventsAPI bool `mapstructure:"cache_events_api"`
-	// HTTPEvents configures an instance of stored_requests/events/http/http.go.
-	// If non-nil, the server will use those endpoints to populate and update the cache.
-	HTTPEvents HTTPEventsConfig `mapstructure:"http_events"`
+// DataType constants
+type DataType string
+
+const (
+	RequestDataType    DataType = "Request"
+	CategoryDataType   DataType = "Category"
+	VideoDataType      DataType = "Video"
+	AMPRequestDataType DataType = "AMP Request"
+	AccountDataType    DataType = "Account"
+)
+
+// Section returns the config section this type is defined in
+func (sr *StoredRequests) Section() string {
+	return map[DataType]string{
+		RequestDataType:    "stored_requests",
+		CategoryDataType:   "categories",
+		VideoDataType:      "stored_video_req",
+		AMPRequestDataType: "stored_amp_req",
+		AccountDataType:    "accounts",
+	}[sr.dataType]
+}
+
+func (sr *StoredRequests) DataType() DataType {
+	return sr.dataType
 }
 
-// StoredRequestsSlim struct defines options for stored requests from a single endpoint
-type StoredRequestsSlim struct {
+// StoredRequests struct defines options for stored requests for each data type
+// including some amp stored_requests options
+type StoredRequests struct {
+	// dataType is a tag pushed from upstream indicating the type of object fetched here
+	dataType DataType
 	// Files should be used if Stored Requests should be loaded from the filesystem.
 	// Fetchers are in stored_requests/backends/file_system/fetcher.go
 	Files FileFetcherConfig `mapstructure:"filesystem"`
 	// Postgres configures Fetchers and EventProducers which read from a Postgres DB.
 	// Fetchers are in stored_requests/backends/db_fetcher/postgres.go
 	// EventProducers are in stored_requests/events/postgres
-	Postgres PostgresConfigSlim `mapstructure:"postgres"`
+	Postgres PostgresConfig `mapstructure:"postgres"`
 	// HTTP configures an instance of stored_requests/backends/http/http_fetcher.go.
 	// If non-nil, Stored Requests will be fetched from the endpoint described there.
-	HTTP HTTPFetcherConfigSlim `mapstructure:"http"`
+	HTTP HTTPFetcherConfig `mapstructure:"http"`
 	// InMemoryCache configures an instance of stored_requests/caches/memory/cache.go.
 	// If non-nil, Stored Requests will be saved in an in-memory cache.
 	InMemoryCache InMemoryCache `mapstructure:"in_memory_cache"`
@@ -57,14 +59,7 @@ type StoredRequestsSlim struct {
 	CacheEvents CacheEventsConfig `mapstructure:"cache_events"`
 	// HTTPEvents configures an instance of stored_requests/events/http/http.go.
 	// If non-nil, the server will use those endpoints to populate and update the cache.
-	HTTPEvents HTTPEventsConfigSlim `mapstructure:"http_events"`
-}
-
-// HTTPEventsConfigSlim configures stored_requests/events/http/http.go
-type HTTPEventsConfigSlim struct {
-	Endpoint    string `mapstructure:"endpoint"`
-	RefreshRate int64  `mapstructure:"refresh_rate_seconds"`
-	Timeout     int    `mapstructure:"timeout_ms"`
+	HTTPEvents HTTPEventsConfig `mapstructure:"http_events"`
 }
 
 // HTTPEventsConfig configures stored_requests/events/http/http.go
@@ -83,14 +78,6 @@ func (cfg HTTPEventsConfig) RefreshRateDuration() time.Duration {
 	return time.Duration(cfg.RefreshRate) * time.Second
 }
 
-func (cfg HTTPEventsConfigSlim) TimeoutDuration() time.Duration {
-	return time.Duration(cfg.Timeout) * time.Millisecond
-}
-
-func (cfg HTTPEventsConfigSlim) RefreshRateDuration() time.Duration {
-	return time.Duration(cfg.RefreshRate) * time.Second
-}
-
 // CacheEventsConfig configured stored_requests/events/api/api.go
 type CacheEventsConfig struct {
 	// Enabled should be true to enable the events api endpoint
@@ -107,48 +94,72 @@ type FileFetcherConfig struct {
 	Path string `mapstructure:"directorypath"`
 }
 
-// HTTPFetcherConfigSlim configures a stored_requests/backends/http_fetcher/fetcher.go
-type HTTPFetcherConfigSlim struct {
-	Endpoint string `mapstructure:"endpoint"`
-}
-
 // HTTPFetcherConfig configures a stored_requests/backends/http_fetcher/fetcher.go
 type HTTPFetcherConfig struct {
 	Endpoint    string `mapstructure:"endpoint"`
 	AmpEndpoint string `mapstructure:"amp_endpoint"`
 }
 
+// Migrate combined stored_requests+amp configuration to separate simple config sections
+func resolvedStoredRequestsConfig(cfg *Configuration) {
+	sr := &cfg.StoredRequests
+	amp := &cfg.StoredRequestsAMP
+
+	sr.CacheEvents.Endpoint = "/storedrequests/openrtb2" // why is this here and not SetDefault ?
+
+	// Amp uses the same config but some fields get replaced by Amp* version of similar fields
+	cfg.StoredRequestsAMP = cfg.StoredRequests
+	amp.Postgres.FetcherQueries.QueryTemplate = sr.Postgres.FetcherQueries.AmpQueryTemplate
+	amp.Postgres.CacheInitialization.Query = sr.Postgres.CacheInitialization.AmpQuery
+	amp.Postgres.PollUpdates.Query = sr.Postgres.PollUpdates.AmpQuery
+	amp.HTTP.Endpoint = sr.HTTP.AmpEndpoint
+	amp.CacheEvents.Endpoint = "/storedrequests/amp"
+	amp.HTTPEvents.Endpoint = sr.HTTPEvents.AmpEndpoint
+
+	// Set data types for each section
+	cfg.StoredRequests.dataType = RequestDataType
+	cfg.StoredRequestsAMP.dataType = AMPRequestDataType
+	cfg.StoredVideo.dataType = VideoDataType
+	cfg.CategoryMapping.dataType = CategoryDataType
+	cfg.Accounts.dataType = AccountDataType
+	return
+}
+
 func (cfg *StoredRequests) validate(errs configErrors) configErrors {
+	if cfg.DataType() == AccountDataType && cfg.HTTP.Endpoint != "" {
+		errs = append(errs, fmt.Errorf("%s.http: retrieving accounts via http not available, use accounts.files", cfg.Section()))
+	}
+	if cfg.DataType() == AccountDataType && cfg.Postgres.ConnectionInfo.Database != "" {
+		errs = append(errs, fmt.Errorf("%s.postgres: retrieving accounts via postgres not available, use accounts.files", cfg.Section()))
+	} else {
+		errs = cfg.Postgres.validate(cfg.Section(), errs)
+	}
+
+	// Categories do not use cache so none of the following checks apply
+	if cfg.DataType() == CategoryDataType {
+		return errs
+	}
+
 	if cfg.InMemoryCache.Type == "none" {
-		if cfg.CacheEventsAPI {
-			errs = append(errs, errors.New("stored_requests.cache_events_api must be false if stored_requests.in_memory_cache=none"))
+		if cfg.CacheEvents.Enabled {
+			errs = append(errs, fmt.Errorf("%s: cache_events must be disabled if in_memory_cache=none", cfg.Section()))
 		}
 
 		if cfg.HTTPEvents.RefreshRate != 0 {
-			errs = append(errs, errors.New("stored_requests.http_events.refresh_rate_seconds must be 0 if stored_requests.in_memory_cache=none"))
+			errs = append(errs, fmt.Errorf("%s: http_events.refresh_rate_seconds must be 0 if in_memory_cache=none", cfg.Section()))
 		}
 
 		if cfg.Postgres.PollUpdates.Query != "" {
-			errs = append(errs, errors.New("stored_requests.postgres.poll_for_updates.query must be empty if stored_requests.in_memory_cache=none"))
+			errs = append(errs, fmt.Errorf("%s: postgres.poll_for_updates.query must be empty if in_memory_cache=none", cfg.Section()))
 		}
 		if cfg.Postgres.CacheInitialization.Query != "" {
-			errs = append(errs, errors.New("stored_requests.postgres.initialize_caches.query must be empty if stored_requests.in_memory_cache=none"))
+			errs = append(errs, fmt.Errorf("%s: postgres.initialize_caches.query must be empty if in_memory_cache=none", cfg.Section()))
 		}
 	}
-	errs = cfg.InMemoryCache.validate(errs)
-	errs = cfg.Postgres.validate(errs)
+	errs = cfg.InMemoryCache.validate(cfg.Section(), errs)
 	return errs
 }
 
-// PostgresConfigSlim configures the Stored Request ecosystem to use Postgres. This must include a Fetcher,
-// and may optionally include some EventProducers to populate and refresh the caches.
-type PostgresConfigSlim struct {
-	ConnectionInfo      PostgresConnection           `mapstructure:"connection"`
-	FetcherQueries      PostgresFetcherQueriesSlim   `mapstructure:"fetcher"`
-	CacheInitialization PostgresCacheInitializerSlim `mapstructure:"initialize_caches"`
-	PollUpdates         PostgresUpdatePollingSlim    `mapstructure:"poll_for_updates"`
-}
-
 // PostgresConfig configures the Stored Request ecosystem to use Postgres. This must include a Fetcher,
 // and may optionally include some EventProducers to populate and refresh the caches.
 type PostgresConfig struct {
@@ -158,12 +169,12 @@ type PostgresConfig struct {
 	PollUpdates         PostgresUpdatePolling    `mapstructure:"poll_for_updates"`
 }
 
-func (cfg *PostgresConfig) validate(errs configErrors) configErrors {
+func (cfg *PostgresConfig) validate(section string, errs configErrors) configErrors {
 	if cfg.ConnectionInfo.Database == "" {
 		return errs
 	}
 
-	return cfg.PollUpdates.validate(errs)
+	return cfg.PollUpdates.validate(section, errs)
 }
 
 // PostgresConnection has options which put types to the Postgres Connection string. See:
@@ -242,32 +253,6 @@ type PostgresFetcherQueries struct {
 	AmpQueryTemplate string `mapstructure:"amp_query"`
 }
 
-type PostgresFetcherQueriesSlim struct {
-	// QueryTemplate is the Postgres Query which can be used to fetch configs from the database.
-	// It is a Template, rather than a full Query, because a single HTTP request may reference multiple Stored Requests.
-	//
-	// In the simplest case, this could be something like:
-	//   SELECT id, requestData, 'request' as type
-	//     FROM stored_requests
-	//     WHERE id in %REQUEST_ID_LIST%
-	//     UNION ALL
-	//   SELECT id, impData, 'imp' as type
-	//     FROM stored_imps
-	//     WHERE id in %IMP_ID_LIST%
-	//
-	// The MakeQuery function will transform this query into:
-	//   SELECT id, requestData, 'request' as type
-	//     FROM stored_requests
-	//     WHERE id in ($1)
-	//     UNION ALL
-	//   SELECT id, impData, 'imp' as type
-	//     FROM stored_imps
-	//     WHERE id in ($2, $3, $4, ...)
-	//
-	// ... where the number of "$x" args depends on how many IDs are nested within the HTTP request.
-	QueryTemplate string `mapstructure:"query"`
-}
-
 type PostgresCacheInitializer struct {
 	Timeout int `mapstructure:"timeout_ms"`
 	// Query should be something like:
@@ -284,69 +269,19 @@ type PostgresCacheInitializer struct {
 	AmpQuery string `mapstructure:"amp_query"`
 }
 
-type PostgresCacheInitializerSlim struct {
-	Timeout int `mapstructure:"timeout_ms"`
-	// Query should be something like:
-	//
-	// SELECT id, requestData, 'request' AS type FROM stored_requests
-	// UNION ALL
-	// SELECT id, impData, 'imp' AS type FROM stored_imps
-	//
-	// This query will be run once on startup to fetch _all_ known Stored Request data from the database.
-	//
-	// For more details on the expected format of requestData and impData, see stored_requests/events/postgres/polling.go
-	Query string `mapstructure:"query"`
-}
-
-func (cfg *PostgresCacheInitializerSlim) validate(errs configErrors) configErrors {
+func (cfg *PostgresCacheInitializer) validate(section string, errs configErrors) configErrors {
 	if cfg.Query == "" {
 		return errs
 	}
 	if cfg.Timeout <= 0 {
-		errs = append(errs, errors.New("stored_requests.postgres.initialize_caches.timeout_ms must be positive"))
+		errs = append(errs, fmt.Errorf("%s: postgres.initialize_caches.timeout_ms must be positive", section))
 	}
 	if strings.Contains(cfg.Query, "$") {
-		errs = append(errs, errors.New("stored_requests.postgres.initialize_caches.query should not contain any wildcards (e.g. $1)"))
+		errs = append(errs, fmt.Errorf("%s: postgres.initialize_caches.query should not contain any wildcards (e.g. $1)", section))
 	}
 	return errs
 }
 
-func (cfg *PostgresCacheInitializer) validate(errs configErrors) configErrors {
-	if cfg.Query == "" && cfg.AmpQuery == "" {
-		return errs
-	}
-
-	slim := &PostgresCacheInitializerSlim{Timeout: cfg.Timeout, Query: cfg.Query}
-	errs = slim.validate(errs)
-
-	if strings.Contains(cfg.AmpQuery, "$") {
-		errs = append(errs, errors.New("stored_requests.postgres.initialize_caches.amp_query should not contain any wildcards (e.g. $1)"))
-	}
-
-	return errs
-}
-
-type PostgresUpdatePollingSlim struct {
-	// RefreshRate determines how frequently the Query and AmpQuery are run.
-	RefreshRate int `mapstructure:"refresh_rate_seconds"`
-
-	// Timeout is the amount of time before a call to the database is aborted.
-	Timeout int `mapstructure:"timeout_ms"`
-
-	// An example UpdateQuery is:
-	//
-	// SELECT id, requestData, 'request' AS type
-	//   FROM stored_requests
-	//   WHERE last_updated > $1
-	// UNION ALL
-	// SELECT id, requestData, 'imp' AS type
-	//   FROM stored_imps
-	//   WHERE last_updated > $1
-	//
-	// The code will be run periodically to fetch updates from the database.
-	Query string `mapstructure:"query"`
-}
-
 type PostgresUpdatePolling struct {
 	// RefreshRate determines how frequently the Query and AmpQuery are run.
 	RefreshRate int `mapstructure:"refresh_rate_seconds"`
@@ -370,49 +305,31 @@ type PostgresUpdatePolling struct {
 	AmpQuery string `mapstructure:"amp_query"`
 }
 
-func (cfg *PostgresUpdatePollingSlim) validate(errs configErrors) configErrors {
+func (cfg *PostgresUpdatePolling) validate(section string, errs configErrors) configErrors {
 	if cfg.Query == "" {
 		return errs
 	}
 
 	if cfg.RefreshRate <= 0 {
-		errs = append(errs, errors.New("stored_requests.postgres.poll_for_updates.refresh_rate_seconds must be > 0"))
+		errs = append(errs, fmt.Errorf("%s: postgres.poll_for_updates.refresh_rate_seconds must be > 0", section))
 	}
 
 	if cfg.Timeout <= 0 {
-		errs = append(errs, errors.New("stored_requests.postgres.poll_for_updates.timeout_ms must be > 0"))
+		errs = append(errs, fmt.Errorf("%s: postgres.poll_for_updates.timeout_ms must be > 0", section))
 	}
 
 	if !strings.Contains(cfg.Query, "$1") || strings.Contains(cfg.Query, "$2") {
-		errs = append(errs, errors.New("stored_requests.postgres.poll_for_updates.query must contain exactly one wildcard"))
-	}
-	return errs
-}
-
-func (cfg *PostgresUpdatePolling) validate(errs configErrors) configErrors {
-	if cfg.Query == "" && cfg.AmpQuery == "" {
-		return errs
-	}
-	slim := &PostgresUpdatePollingSlim{RefreshRate: cfg.RefreshRate, Timeout: cfg.Timeout, Query: cfg.Query}
-	errs = slim.validate(errs)
-
-	if !strings.Contains(cfg.AmpQuery, "$1") || strings.Contains(cfg.AmpQuery, "$2") {
-		errs = append(errs, errors.New("stored_requests.postgres.poll_for_updates.amp_query must contain exactly one wildcard"))
+		errs = append(errs, fmt.Errorf("%s: postgres.poll_for_updates.query must contain exactly one wildcard", section))
 	}
 	return errs
 }
 
 // MakeQuery builds a query which can fetch numReqs Stored Requests and numImps Stored Imps.
 // See the docs on PostgresConfig.QueryTemplate for a description of how it works.
-func (cfg *PostgresFetcherQueriesSlim) MakeQuery(numReqs int, numImps int) (query string) {
+func (cfg *PostgresFetcherQueries) MakeQuery(numReqs int, numImps int) (query string) {
 	return resolve(cfg.QueryTemplate, numReqs, numImps)
 }
 
-// MakeAmpQuery is the equivalent of MakeQuery() for AMP.
-func (cfg *PostgresFetcherQueries) MakeAmpQuery(numReqs int, numImps int) string {
-	return resolve(cfg.AmpQueryTemplate, numReqs, numImps)
-}
-
 func resolve(template string, numReqs int, numImps int) (query string) {
 	numReqs = ensureNonNegative("Request", numReqs)
 	numImps = ensureNonNegative("Imp", numImps)
@@ -473,29 +390,29 @@ type InMemoryCache struct {
 	ImpCacheSize int `mapstructure:"imp_cache_size_bytes"`
 }
 
-func (cfg *InMemoryCache) validate(errs configErrors) configErrors {
+func (cfg *InMemoryCache) validate(section string, errs configErrors) configErrors {
 	switch cfg.Type {
 	case "none":
 		// No errors for no config options
 	case "unbounded":
 		if cfg.TTL != 0 {
-			errs = append(errs, fmt.Errorf("stored_requests.in_memory_cache must be 0 for unbounded caches. Got %d", cfg.TTL))
+			errs = append(errs, fmt.Errorf("%s: in_memory_cache must be 0 for unbounded caches. Got %d", section, cfg.TTL))
 		}
 		if cfg.RequestCacheSize != 0 {
-			errs = append(errs, fmt.Errorf("stored_requests.in_memory_cache.request_cache_size_bytes must be 0 for unbounded caches. Got %d", cfg.RequestCacheSize))
+			errs = append(errs, fmt.Errorf("%s: in_memory_cache.request_cache_size_bytes must be 0 for unbounded caches. Got %d", section, cfg.RequestCacheSize))
 		}
 		if cfg.ImpCacheSize != 0 {
-			errs = append(errs, fmt.Errorf("stored_requests.in_memory_cache.imp_cache_size_bytes must be 0 for unbounded caches. Got %d", cfg.ImpCacheSize))
+			errs = append(errs, fmt.Errorf("%s: in_memory_cache.imp_cache_size_bytes must be 0 for unbounded caches. Got %d", section, cfg.ImpCacheSize))
 		}
 	case "lru":
 		if cfg.RequestCacheSize <= 0 {
-			errs = append(errs, fmt.Errorf("stored_requests.in_memory_cache.request_cache_size_bytes must be >= 0 when stored_requests.in_memory_cache.type=lru. Got %d", cfg.RequestCacheSize))
+			errs = append(errs, fmt.Errorf("%s: in_memory_cache.request_cache_size_bytes must be >= 0 when in_memory_cache.type=lru. Got %d", section, cfg.RequestCacheSize))
 		}
 		if cfg.ImpCacheSize <= 0 {
-			errs = append(errs, fmt.Errorf("stored_requests.in_memory_cache.imp_cache_size_bytes must be >= 0 when stored_requests.in_memory_cache.type=lru. Got %d", cfg.ImpCacheSize))
+			errs = append(errs, fmt.Errorf("%s: in_memory_cache.imp_cache_size_bytes must be >= 0 when in_memory_cache.type=lru. Got %d", section, cfg.ImpCacheSize))
 		}
 	default:
-		errs = append(errs, fmt.Errorf("stored_requests.in_memory_cache.type %s is invalid", cfg.Type))
+		errs = append(errs, fmt.Errorf("%s: in_memory_cache.type %s is invalid", section, cfg.Type))
 	}
 	return errs
 }
diff --git a/endpoints/openrtb2/amp_auction.go b/endpoints/openrtb2/amp_auction.go
index 8efba5a9..54f47069 100644
--- a/endpoints/openrtb2/amp_auction.go
+++ b/endpoints/openrtb2/amp_auction.go
@@ -44,6 +44,7 @@ func NewAmpEndpoint(
 	ex exchange.Exchange,
 	validator openrtb_ext.BidderParamValidator,
 	requestsById stored_requests.Fetcher,
+	accounts stored_requests.AccountFetcher,
 	categories stored_requests.CategoryFetcher,
 	cfg *config.Configuration,
 	met pbsmetrics.MetricsEngine,
@@ -53,7 +54,7 @@ func NewAmpEndpoint(
 	bidderMap map[string]openrtb_ext.BidderName,
 ) (httprouter.Handle, error) {
 
-	if ex == nil || validator == nil || requestsById == nil || cfg == nil || met == nil {
+	if ex == nil || validator == nil || requestsById == nil || accounts == nil || cfg == nil || met == nil {
 		return nil, errors.New("NewAmpEndpoint requires non-nil arguments.")
 	}
 
@@ -69,6 +70,7 @@ func NewAmpEndpoint(
 		validator,
 		requestsById,
 		empty_fetcher.EmptyFetcher{},
+		accounts,
 		categories,
 		cfg,
 		met,
@@ -154,26 +156,31 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h
 	} else {
 		labels.CookieFlag = pbsmetrics.CookieFlagYes
 	}
-	labels.PubID = effectivePubID(req.Site.Publisher)
-	// Blacklist account now that we have resolved the value
-	if acctIdErr := validateAccount(deps.cfg, labels.PubID); acctIdErr != nil {
-		errL = append(errL, acctIdErr)
-		errCode := errortypes.ReadCode(acctIdErr)
-		if errCode == errortypes.BlacklistedAppErrorCode || errCode == errortypes.BlacklistedAcctErrorCode {
-			w.WriteHeader(http.StatusServiceUnavailable)
-			labels.RequestStatus = pbsmetrics.RequestStatusBlacklisted
-		} else {
-			w.WriteHeader(http.StatusBadRequest)
-			labels.RequestStatus = pbsmetrics.RequestStatusBadInput
+	labels.PubID = getAccountID(req.Site.Publisher)
+	// Look up account now that we have resolved the pubID value
+	account, acctIDErrs := deps.getAccount(ctx, labels.PubID)
+	if len(acctIDErrs) > 0 {
+		errL = append(errL, acctIDErrs...)
+		httpStatus := http.StatusBadRequest
+		metricsStatus := pbsmetrics.RequestStatusBadInput
+		for _, er := range errL {
+			errCode := errortypes.ReadCode(er)
+			if errCode == errortypes.BlacklistedAppErrorCode || errCode == errortypes.BlacklistedAcctErrorCode {
+				httpStatus = http.StatusServiceUnavailable
+				metricsStatus = pbsmetrics.RequestStatusBlacklisted
+				break
+			}
 		}
+		w.WriteHeader(httpStatus)
+		labels.RequestStatus = metricsStatus
 		for _, err := range errortypes.FatalOnly(errL) {
 			w.Write([]byte(fmt.Sprintf("Invalid request format: %s\n", err.Error())))
 		}
-		ao.Errors = append(ao.Errors, acctIdErr)
+		ao.Errors = append(ao.Errors, acctIDErrs...)
 		return
 	}
 
-	response, err := deps.ex.HoldAuction(ctx, req, usersyncs, labels, &deps.categories, nil)
+	response, err := deps.ex.HoldAuction(ctx, req, usersyncs, labels, account, &deps.categories, nil)
 	ao.AuctionResponse = response
 
 	if err != nil {
@@ -388,13 +395,7 @@ func (deps *endpointDeps) overrideWithParams(httpRequest *http.Request, req *ope
 
 	setAmpExt(req.Site, "1")
 
-	account := httpRequest.FormValue("account")
-	if account != "" {
-		if req.Site.Publisher == nil {
-			req.Site.Publisher = &openrtb.Publisher{}
-		}
-		req.Site.Publisher.ID = account
-	}
+	setEffectiveAmpPubID(req, httpRequest.URL.Query())
 
 	slot := httpRequest.FormValue("slot")
 	if slot != "" {
@@ -564,3 +565,27 @@ func readConsent(url *url.URL) string {
 	// Fallback to 'gdpr_consent' for compatability until it's no longer used by AMP.
 	return url.Query().Get("gdpr_consent")
 }
+
+// Sets the effective publisher ID for amp request
+func setEffectiveAmpPubID(req *openrtb.BidRequest, urlQueryParams url.Values) {
+	var pub *openrtb.Publisher
+	if req.App != nil {
+		if req.App.Publisher == nil {
+			req.App.Publisher = new(openrtb.Publisher)
+		}
+		pub = req.App.Publisher
+	} else if req.Site != nil {
+		if req.Site.Publisher == nil {
+			req.Site.Publisher = new(openrtb.Publisher)
+		}
+		pub = req.Site.Publisher
+	}
+
+	if pub.ID == "" {
+		// For amp requests, the publisher ID could be sent via the account
+		// query string
+		if acc := urlQueryParams.Get("account"); acc != "" && acc != "ACCOUNT_ID" {
+			pub.ID = acc
+		}
+	}
+}
diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go
index 86186fa8..bc0cd900 100644
--- a/endpoints/openrtb2/auction.go
+++ b/endpoints/openrtb2/auction.go
@@ -15,6 +15,7 @@ import (
 
 	"github.com/buger/jsonparser"
 	jsonpatch "github.com/evanphx/json-patch"
+	"github.com/gofrs/uuid"
 	"github.com/golang/glog"
 	"github.com/julienschmidt/httprouter"
 	"github.com/mssola/user_agent"
@@ -45,9 +46,9 @@ var (
 	dntEnabled  int8   = 1
 )
 
-func NewEndpoint(ex exchange.Exchange, validator openrtb_ext.BidderParamValidator, requestsById stored_requests.Fetcher, categories stored_requests.CategoryFetcher, cfg *config.Configuration, met pbsmetrics.MetricsEngine, pbsAnalytics analytics.PBSAnalyticsModule, disabledBidders map[string]string, defReqJSON []byte, bidderMap map[string]openrtb_ext.BidderName) (httprouter.Handle, error) {
+func NewEndpoint(ex exchange.Exchange, validator openrtb_ext.BidderParamValidator, requestsById stored_requests.Fetcher, accounts stored_requests.AccountFetcher, categories stored_requests.CategoryFetcher, cfg *config.Configuration, met pbsmetrics.MetricsEngine, pbsAnalytics analytics.PBSAnalyticsModule, disabledBidders map[string]string, defReqJSON []byte, bidderMap map[string]openrtb_ext.BidderName) (httprouter.Handle, error) {
 
-	if ex == nil || validator == nil || requestsById == nil || cfg == nil || met == nil {
+	if ex == nil || validator == nil || requestsById == nil || accounts == nil || cfg == nil || met == nil {
 		return nil, errors.New("NewEndpoint requires non-nil arguments.")
 	}
 
@@ -63,6 +64,7 @@ func NewEndpoint(ex exchange.Exchange, validator openrtb_ext.BidderParamValidato
 		validator,
 		requestsById,
 		empty_fetcher.EmptyFetcher{},
+		accounts,
 		categories,
 		cfg,
 		met,
@@ -81,6 +83,7 @@ type endpointDeps struct {
 	paramsValidator           openrtb_ext.BidderParamValidator
 	storedReqFetcher          stored_requests.Fetcher
 	videoFetcher              stored_requests.Fetcher
+	accounts                  stored_requests.AccountFetcher
 	categories                stored_requests.CategoryFetcher
 	cfg                       *config.Configuration
 	metricsEngine             pbsmetrics.MetricsEngine
@@ -141,7 +144,7 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http
 	if req.App != nil {
 		labels.Source = pbsmetrics.DemandApp
 		labels.RType = pbsmetrics.ReqTypeORTB2App
-		labels.PubID = effectivePubID(req.App.Publisher)
+		labels.PubID = getAccountID(req.App.Publisher)
 	} else { //req.Site != nil
 		labels.Source = pbsmetrics.DemandWeb
 		if usersyncs.LiveSyncCount() == 0 {
@@ -149,18 +152,21 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http
 		} else {
 			labels.CookieFlag = pbsmetrics.CookieFlagYes
 		}
-		labels.PubID = effectivePubID(req.Site.Publisher)
+		labels.PubID = getAccountID(req.Site.Publisher)
 	}
 
-	if acctIdErr := validateAccount(deps.cfg, labels.PubID); acctIdErr != nil {
-		errL = append(errL, acctIdErr)
+	// Look up account now that we have resolved the pubID value
+	account, acctIDErrs := deps.getAccount(ctx, labels.PubID)
+	if len(acctIDErrs) > 0 {
+		errL = append(errL, acctIDErrs...)
 		writeError(errL, w, &labels)
 		return
 	}
 
-	response, err := deps.ex.HoldAuction(ctx, req, usersyncs, labels, &deps.categories, nil)
+	response, err := deps.ex.HoldAuction(ctx, req, usersyncs, labels, account, &deps.categories, nil)
 	ao.Request = req
 	ao.Response = response
+	ao.Account = account
 	if err != nil {
 		labels.RequestStatus = pbsmetrics.RequestStatusErr
 		w.WriteHeader(http.StatusInternalServerError)
@@ -283,6 +289,14 @@ func (deps *endpointDeps) validateRequest(req *openrtb.BidRequest) []error {
 		errL = append(errL, &errortypes.Warning{Message: fmt.Sprintf("A prebid request can only process one currency. Taking the first currency in the list, %s, as the active currency", req.Cur[0])})
 	}
 
+	// If automatically filling source TID is enabled then validate that
+	// source.TID exists and If it doesn't, fill it with a randomly generated UUID
+	if deps.cfg.AutoGenSourceTID {
+		if err := validateAndFillSourceTID(req); err != nil {
+			return []error{err}
+		}
+	}
+
 	var aliases map[string]string
 	if bidExt, err := deps.parseBidExt(req.Ext); err != nil {
 		return []error{err}
@@ -358,6 +372,20 @@ func (deps *endpointDeps) validateRequest(req *openrtb.BidRequest) []error {
 	return errL
 }
 
+func validateAndFillSourceTID(req *openrtb.BidRequest) error {
+	if req.Source == nil {
+		req.Source = &openrtb.Source{}
+	}
+	if req.Source.TID == "" {
+		if rawUUID, err := uuid.NewV4(); err == nil {
+			req.Source.TID = rawUUID.String()
+		} else {
+			return errors.New("req.Source.TID missing in the req and error creating a random UID")
+		}
+	}
+	return nil
+}
+
 func validateBidAdjustmentFactors(adjustmentFactors map[string]float64, aliases map[string]string) error {
 	for bidderToAdjust, adjustmentFactor := range adjustmentFactors {
 		if adjustmentFactor <= 0 {
@@ -1265,8 +1293,8 @@ func writeError(errs []error, w http.ResponseWriter, labels *pbsmetrics.Labels)
 	return rc
 }
 
-// Returns the effective publisher ID
-func effectivePubID(pub *openrtb.Publisher) string {
+// Returns the account ID for the request
+func getAccountID(pub *openrtb.Publisher) string {
 	if pub != nil {
 		if pub.Ext != nil {
 			var pubExt openrtb_ext.ExtPublisher
@@ -1282,14 +1310,55 @@ func effectivePubID(pub *openrtb.Publisher) string {
 	return pbsmetrics.PublisherUnknown
 }
 
-func validateAccount(cfg *config.Configuration, pubID string) error {
-	var err error = nil
-	if cfg.AccountRequired && pubID == pbsmetrics.PublisherUnknown {
-		// If specified in the configuration, discard requests that don't come with an account ID.
-		err = error(&errortypes.AcctRequired{Message: fmt.Sprintf("Prebid-server has been configured to discard requests that don't come with an Account ID. Please reach out to the prebid server host.")})
-	} else if _, found := cfg.BlacklistedAcctMap[pubID]; found {
-		// Blacklist account now that we have resolved the value
-		err = error(&errortypes.BlacklistedAcct{Message: fmt.Sprintf("Prebid-server has blacklisted Account ID: %s, please reach out to the prebid server host.", pubID)})
+func (deps *endpointDeps) getAccount(ctx context.Context, pubID string) (account *config.Account, errs []error) {
+	// Check BlacklistedAcctMap until we have deprecated it
+	if _, found := deps.cfg.BlacklistedAcctMap[pubID]; found {
+		return nil, []error{&errortypes.BlacklistedAcct{
+			Message: fmt.Sprintf("Prebid-server has disabled Account ID: %s, please reach out to the prebid server host.", pubID),
+		}}
+	}
+	if deps.cfg.AccountRequired && pubID == pbsmetrics.PublisherUnknown {
+		return nil, []error{&errortypes.AcctRequired{
+			Message: fmt.Sprintf("Prebid-server has been configured to discard requests without a valid Account ID. Please reach out to the prebid server host."),
+		}}
+	}
+	if accountJSON, accErrs := deps.accounts.FetchAccount(ctx, pubID); len(accErrs) > 0 || accountJSON == nil {
+		// pubID does not reference a valid account
+		if len(accErrs) > 0 {
+			errs = append(errs, errs...)
+		}
+		if deps.cfg.AccountRequired && deps.cfg.AccountDefaults.Disabled {
+			errs = append(errs, &errortypes.AcctRequired{
+				Message: fmt.Sprintf("Prebid-server has been configured to discard requests without a valid Account ID. Please reach out to the prebid server host."),
+			})
+			return nil, errs
+		}
+		// Make a copy of AccountDefaults instead of taking a reference,
+		// to preserve original pubID in case is needed to check NonStandardPublisherMap
+		pubAccount := deps.cfg.AccountDefaults
+		pubAccount.ID = pubID
+		account = &pubAccount
+	} else {
+		// pubID resolved to a valid account, merge with AccountDefaults for a complete config
+		account = &config.Account{}
+		completeJSON, err := jsonpatch.MergePatch(deps.cfg.AccountDefaultsJSON(), accountJSON)
+		if err == nil {
+			err = json.Unmarshal(completeJSON, account)
+		}
+		if err != nil {
+			errs = append(errs, err)
+			return nil, errs
+		}
+		// Fill in ID if needed, so it can be left out of account definition
+		if len(account.ID) == 0 {
+			account.ID = pubID
+		}
 	}
-	return err
+	if account.Disabled {
+		errs = append(errs, &errortypes.BlacklistedAcct{
+			Message: fmt.Sprintf("Prebid-server has disabled Account ID: %s, please reach out to the prebid server host.", pubID),
+		})
+		return nil, errs
+	}
+	return
 }
diff --git a/endpoints/openrtb2/sample-requests/account-required/valid-acct.json b/endpoints/openrtb2/sample-requests/account-required/valid-acct.json
new file mode 100644
index 00000000..87d9d1ea
--- /dev/null
+++ b/endpoints/openrtb2/sample-requests/account-required/valid-acct.json
@@ -0,0 +1,67 @@
+{
+    "description": "This request comes with a valid account id",
+    "message": "",
+
+    "requestPayload": {
+      "id": "some-request-id",
+      "site": {
+        "publisher": { "id": "valid_acct"},
+      	"page": "test.somepage.com"
+      },
+      "user": { },
+      "imp": [
+        {
+          "id": "my-imp-id",
+          "banner": {
+            "format": [
+               {
+                 "w": 300,
+                 "h": 600
+               }
+            ]
+          },
+          "pmp": {
+            "deals": [
+              {
+                "id": "some-deal-id"
+              }
+            ]
+          },
+          "ext": {
+            "appnexus": {
+              "placementId": 12883451
+            }
+          }
+        }
+      ],
+      "tmax": 500,
+      "ext": {
+        "prebid": {
+          "aliases": {
+            "districtm": "appnexus"
+          },
+          "bidadjustmentfactors": {
+            "appnexus": 1.01,
+            "districtm": 0.98,
+            "rubicon": 0.99
+          },
+          "cache": {
+            "bids": {}
+          },
+          "targeting": {
+            "includewinners": false,
+            "pricegranularity": {
+              "precision": 2,
+              "ranges": [
+                {
+                  "max": 20,
+                  "increment": 0.10
+                }
+              ]
+            }
+          }
+        }
+      }
+    }
+  }
+
diff --git a/endpoints/openrtb2/video_auction.go b/endpoints/openrtb2/video_auction.go
index a6ca5278..f5494751 100644
--- a/endpoints/openrtb2/video_auction.go
+++ b/endpoints/openrtb2/video_auction.go
@@ -35,9 +35,9 @@ import (
 
 var defaultRequestTimeout int64 = 5000
 
-func NewVideoEndpoint(ex exchange.Exchange, validator openrtb_ext.BidderParamValidator, requestsById stored_requests.Fetcher, videoFetcher stored_requests.Fetcher, categories stored_requests.CategoryFetcher, cfg *config.Configuration, met pbsmetrics.MetricsEngine, pbsAnalytics analytics.PBSAnalyticsModule, disabledBidders map[string]string, defReqJSON []byte, bidderMap map[string]openrtb_ext.BidderName, cache prebid_cache_client.Client) (httprouter.Handle, error) {
+func NewVideoEndpoint(ex exchange.Exchange, validator openrtb_ext.BidderParamValidator, requestsById stored_requests.Fetcher, videoFetcher stored_requests.Fetcher, accounts stored_requests.AccountFetcher, categories stored_requests.CategoryFetcher, cfg *config.Configuration, met pbsmetrics.MetricsEngine, pbsAnalytics analytics.PBSAnalyticsModule, disabledBidders map[string]string, defReqJSON []byte, bidderMap map[string]openrtb_ext.BidderName, cache prebid_cache_client.Client) (httprouter.Handle, error) {
 
-	if ex == nil || validator == nil || requestsById == nil || cfg == nil || met == nil {
+	if ex == nil || validator == nil || requestsById == nil || accounts == nil || cfg == nil || met == nil {
 		return nil, errors.New("NewVideoEndpoint requires non-nil arguments.")
 	}
 
@@ -55,6 +55,7 @@ func NewVideoEndpoint(ex exchange.Exchange, validator openrtb_ext.BidderParamVal
 		validator,
 		requestsById,
 		videoFetcher,
+		accounts,
 		categories,
 		cfg,
 		met,
@@ -242,7 +243,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re
 	usersyncs := usersync.ParsePBSCookieFromRequest(r, &(deps.cfg.HostCookie))
 	if bidReq.App != nil {
 		labels.Source = pbsmetrics.DemandApp
-		labels.PubID = effectivePubID(bidReq.App.Publisher)
+		labels.PubID = getAccountID(bidReq.App.Publisher)
 	} else { // both bidReq.App == nil and bidReq.Site != nil are true
 		labels.Source = pbsmetrics.DemandWeb
 		if usersyncs.LiveSyncCount() == 0 {
@@ -250,16 +251,17 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re
 		} else {
 			labels.CookieFlag = pbsmetrics.CookieFlagYes
 		}
-		labels.PubID = effectivePubID(bidReq.Site.Publisher)
+		labels.PubID = getAccountID(bidReq.Site.Publisher)
 	}
 
-	if acctIdErr := validateAccount(deps.cfg, labels.PubID); acctIdErr != nil {
-		errL := []error{err}
-		handleError(&labels, w, errL, &vo, &debugLog)
+	// Look up account now that we have resolved the pubID value
+	account, acctIDErrs := deps.getAccount(ctx, labels.PubID)
+	if len(acctIDErrs) > 0 {
+		handleError(&labels, w, acctIDErrs, &vo, &debugLog)
 		return
 	}
 	//execute auction logic
-	response, err := deps.ex.HoldAuction(ctx, bidReq, usersyncs, labels, &deps.categories, &debugLog)
+	response, err := deps.ex.HoldAuction(ctx, bidReq, usersyncs, labels, account, &deps.categories, &debugLog)
 	vo.Request = bidReq
 	vo.Response = response
 	if err != nil {
diff --git a/exchange/adapter_map.go b/exchange/adapter_map.go
index d056de66..a160e87a 100755
--- a/exchange/adapter_map.go
+++ b/exchange/adapter_map.go
@@ -117,7 +117,7 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter
 		openrtb_ext.BidderAvocet:       avocet.NewAvocetAdapter(cfg.Adapters[string(openrtb_ext.BidderAvocet)].Endpoint),
 		openrtb_ext.BidderBeachfront:   beachfront.NewBeachfrontBidder(cfg.Adapters[string(openrtb_ext.BidderBeachfront)].Endpoint, cfg.Adapters[string(openrtb_ext.BidderBeachfront)].ExtraAdapterInfo),
 		openrtb_ext.BidderBeintoo:      beintoo.NewBeintooBidder(cfg.Adapters[string(openrtb_ext.BidderBeintoo)].Endpoint),
-		openrtb_ext.BidderBrightroll:   brightroll.NewBrightrollBidder(cfg.Adapters[string(openrtb_ext.BidderBrightroll)].Endpoint),
+		openrtb_ext.BidderBrightroll:   brightroll.NewBrightrollBidder(cfg.Adapters[string(openrtb_ext.BidderBrightroll)].Endpoint, cfg.Adapters[string(openrtb_ext.BidderBrightroll)].ExtraAdapterInfo),
 		openrtb_ext.BidderConsumable:   consumable.NewConsumableBidder(cfg.Adapters[string(openrtb_ext.BidderConsumable)].Endpoint),
 		openrtb_ext.BidderCpmstar:      cpmstar.NewCpmstarBidder(cfg.Adapters[string(openrtb_ext.BidderCpmstar)].Endpoint),
 		openrtb_ext.BidderDatablocks:   datablocks.NewDatablocksBidder(cfg.Adapters[string(openrtb_ext.BidderDatablocks)].Endpoint),
diff --git a/exchange/exchange.go b/exchange/exchange.go
index 59e87669..9d1cd20a 100644
--- a/exchange/exchange.go
+++ b/exchange/exchange.go
@@ -37,7 +37,7 @@ const DebugContextKey = ContextKey("debugInfo")
 // Exchange runs Auctions. Implementations must be threadsafe, and will be shared across many goroutines.
 type Exchange interface {
 	// HoldAuction executes an OpenRTB v2.5 Auction.
-	HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, usersyncs IdFetcher, labels pbsmetrics.Labels, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *DebugLog) (*openrtb.BidResponse, error)
+	HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, usersyncs IdFetcher, labels pbsmetrics.Labels, account *config.Account, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *DebugLog) (*openrtb.BidResponse, error)
 }
 
 // IdFetcher can find the user's ID for a specific Bidder.
@@ -54,7 +54,6 @@ type exchange struct {
 	gDPR                gdpr.Permissions
 	currencyConverter   *currencies.RateConverter
 	UsersyncIfAmbiguous bool
-	defaultTTLs         config.DefaultTTLs
 	privacyConfig       config.Privacy
 	eeaCountries        map[string]struct{}
 }
@@ -89,7 +88,6 @@ func NewExchange(client *http.Client, cache prebid_cache_client.Client, cfg *con
 	e.gDPR = gDPR
 	e.currencyConverter = currencyConverter
 	e.UsersyncIfAmbiguous = cfg.GDPR.UsersyncIfAmbiguous
-	e.defaultTTLs = cfg.CacheURL.DefaultTTLs
 	e.privacyConfig = config.Privacy{
 		CCPA: cfg.CCPA,
 		GDPR: cfg.GDPR,
@@ -98,7 +96,7 @@ func NewExchange(client *http.Client, cache prebid_cache_client.Client, cfg *con
 	return e
 }
 
-func (e *exchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, usersyncs IdFetcher, labels pbsmetrics.Labels, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *DebugLog) (*openrtb.BidResponse, error) {
+func (e *exchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, usersyncs IdFetcher, labels pbsmetrics.Labels, account *config.Account, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *DebugLog) (*openrtb.BidResponse, error) {
 
 	requestExt, err := extractBidRequestExt(bidRequest)
 	if err != nil {
@@ -203,7 +201,7 @@ func (e *exchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidReque
 				}
 			}
 
-			cacheErrs := auc.doCache(ctx, e.cache, targData, bidRequest, 60, &e.defaultTTLs, bidCategory, debugLog)
+			cacheErrs := auc.doCache(ctx, e.cache, targData, bidRequest, 60, &account.CacheTTL, bidCategory, debugLog)
 			if len(cacheErrs) > 0 {
 				errs = append(errs, cacheErrs...)
 			}
diff --git a/exchange/utils.go b/exchange/utils.go
index 2131aac5..2e9e4dc8 100644
--- a/exchange/utils.go
+++ b/exchange/utils.go
@@ -206,14 +206,15 @@ func prepareSource(req *openrtb.BidRequest, bidder string, sChainsByBidder map[s
 	}
 
 	// set source
-	var source openrtb.Source
+	if req.Source == nil {
+		req.Source = &openrtb.Source{}
+	}
 	schain := openrtb_ext.ExtRequestPrebidSChain{
 		SChain: *selectedSChain,
 	}
 	sourceExt, err := json.Marshal(schain)
 	if err == nil {
-		source.Ext = sourceExt
-		req.Source = &source
+		req.Source.Ext = sourceExt
 	}
 }
 
diff --git a/router/router.go b/router/router.go
index 30936705..4826f0e5 100644
--- a/router/router.go
+++ b/router/router.go
@@ -211,7 +211,7 @@ func New(cfg *config.Configuration, rateConvertor *currencies.RateConverter) (r
 
 	// Metrics engine
 	r.MetricsEngine = metricsConf.NewMetricsEngine(cfg, legacyBidderList)
-	db, shutdown, fetcher, ampFetcher, categoriesFetcher, videoFetcher := storedRequestsConf.NewStoredRequests(cfg, r.MetricsEngine, generalHttpClient, r.Router)
+	db, shutdown, fetcher, ampFetcher, accounts, categoriesFetcher, videoFetcher := storedRequestsConf.NewStoredRequests(cfg, r.MetricsEngine, generalHttpClient, r.Router)
 
 	// todo(zachbadgett): better shutdown
 	r.Shutdown = shutdown
@@ -243,19 +243,19 @@ func New(cfg *config.Configuration, rateConvertor *currencies.RateConverter) (r
 	cacheClient := pbc.NewClient(cacheHttpClient, &cfg.CacheURL, &cfg.ExtCacheURL, r.MetricsEngine)
 	theExchange := exchange.NewExchange(generalHttpClient, cacheClient, cfg, r.MetricsEngine, bidderInfos, gdprPerms, rateConvertor)
 
-	openrtbEndpoint, err := openrtb2.NewEndpoint(theExchange, paramsValidator, fetcher, categoriesFetcher, cfg, r.MetricsEngine, pbsAnalytics, disabledBidders, defReqJSON, activeBiddersMap)
+	openrtbEndpoint, err := openrtb2.NewEndpoint(theExchange, paramsValidator, fetcher, accounts, categoriesFetcher, cfg, r.MetricsEngine, pbsAnalytics, disabledBidders, defReqJSON, activeBiddersMap)
 
 	if err != nil {
 		glog.Fatalf("Failed to create the openrtb endpoint handler. %v", err)
 	}
 
-	ampEndpoint, err := openrtb2.NewAmpEndpoint(theExchange, paramsValidator, ampFetcher, categoriesFetcher, cfg, r.MetricsEngine, pbsAnalytics, disabledBidders, defReqJSON, activeBiddersMap)
+	ampEndpoint, err := openrtb2.NewAmpEndpoint(theExchange, paramsValidator, ampFetcher, accounts, categoriesFetcher, cfg, r.MetricsEngine, pbsAnalytics, disabledBidders, defReqJSON, activeBiddersMap)
 
 	if err != nil {
 		glog.Fatalf("Failed to create the amp endpoint handler. %v", err)
 	}
 
-	videoEndpoint, err := openrtb2.NewVideoEndpoint(theExchange, paramsValidator, fetcher, videoFetcher, categoriesFetcher, cfg, r.MetricsEngine, pbsAnalytics, disabledBidders, defReqJSON, activeBiddersMap, cacheClient)
+	videoEndpoint, err := openrtb2.NewVideoEndpoint(theExchange, paramsValidator, fetcher, videoFetcher, accounts, categoriesFetcher, cfg, r.MetricsEngine, pbsAnalytics, disabledBidders, defReqJSON, activeBiddersMap, cacheClient)
 	if err != nil {
 		glog.Fatalf("Failed to create the video endpoint handler. %v", err)
 	}
diff --git a/static/bidder-info/smaato.yaml b/static/bidder-info/smaato.yaml
index 662603fe..db3e61e5 100644
--- a/static/bidder-info/smaato.yaml
+++ b/static/bidder-info/smaato.yaml
@@ -4,6 +4,8 @@ capabilities:
   app:
     mediaTypes:
       - banner
+      - video
   site:
     mediaTypes:
-      - banner
\ No newline at end of file
+      - banner
+      - video
diff --git a/stored_requests/backends/db_fetcher/fetcher.go b/stored_requests/backends/db_fetcher/fetcher.go
index 223067c9..d8cf132d 100644
--- a/stored_requests/backends/db_fetcher/fetcher.go
+++ b/stored_requests/backends/db_fetcher/fetcher.go
@@ -93,6 +93,10 @@ func (fetcher *dbFetcher) FetchRequests(ctx context.Context, requestIDs []string
 	return storedRequestData, storedImpData, errs
 }
 
+func (fetcher *dbFetcher) FetchAccount(ctx context.Context, accountID string) (json.RawMessage, []error) {
+	return nil, []error{stored_requests.NotFoundError{accountID, "Account"}}
+}
+
 func (fetcher *dbFetcher) FetchCategories(ctx context.Context, primaryAdServer, publisherId, iabCategory string) (string, error) {
 	return "", nil
 }
diff --git a/stored_requests/backends/empty_fetcher/fetcher.go b/stored_requests/backends/empty_fetcher/fetcher.go
index 25e8ead4..ee6b98b3 100644
--- a/stored_requests/backends/empty_fetcher/fetcher.go
+++ b/stored_requests/backends/empty_fetcher/fetcher.go
@@ -3,6 +3,7 @@ package empty_fetcher
 import (
 	"context"
 	"encoding/json"
+
 	"github.com/prebid/prebid-server/stored_requests"
 )
 
@@ -27,6 +28,10 @@ func (fetcher EmptyFetcher) FetchRequests(ctx context.Context, requestIDs []stri
 	return
 }
 
+func (fetcher EmptyFetcher) FetchAccount(ctx context.Context, accountID string) (json.RawMessage, []error) {
+	return nil, []error{stored_requests.NotFoundError{accountID, "Account"}}
+}
+
 func (fetcher EmptyFetcher) FetchCategories(ctx context.Context, primaryAdServer, publisherId, iabCategory string) (string, error) {
 	return "", nil
 }
diff --git a/stored_requests/backends/file_fetcher/fetcher.go b/stored_requests/backends/file_fetcher/fetcher.go
index 60853f65..2d3b0065 100644
--- a/stored_requests/backends/file_fetcher/fetcher.go
+++ b/stored_requests/backends/file_fetcher/fetcher.go
@@ -33,6 +33,21 @@ func (fetcher *eagerFetcher) FetchRequests(ctx context.Context, requestIDs []str
 	return storedRequests, storedImpressions, errs
 }
 
+// FetchAccount fetches the host account configuration for a publisher
+func (fetcher *eagerFetcher) FetchAccount(ctx context.Context, accountID string) (json.RawMessage, []error) {
+	if len(accountID) == 0 {
+		return nil, []error{fmt.Errorf("Cannot look up an empty accountID")}
+	}
+	accountJSON, ok := fetcher.FileSystem.Directories["accounts"].Files[accountID]
+	if !ok {
+		return nil, []error{stored_requests.NotFoundError{
+			ID:       accountID,
+			DataType: "Account",
+		}}
+	}
+	return accountJSON, nil
+}
+
 func (fetcher *eagerFetcher) FetchCategories(ctx context.Context, primaryAdServer, publisherId, iabCategory string) (string, error) {
 	fileName := primaryAdServer
 
diff --git a/stored_requests/backends/http_fetcher/fetcher.go b/stored_requests/backends/http_fetcher/fetcher.go
index b7e42c9e..d533d531 100644
--- a/stored_requests/backends/http_fetcher/fetcher.go
+++ b/stored_requests/backends/http_fetcher/fetcher.go
@@ -81,6 +81,10 @@ func (fetcher *HttpFetcher) FetchRequests(ctx context.Context, requestIDs []stri
 	return
 }
 
+func (fetcher *HttpFetcher) FetchAccount(ctx context.Context, accountID string) (json.RawMessage, []error) {
+	return nil, []error{stored_requests.NotFoundError{accountID, "Account"}}
+}
+
 func (fetcher *HttpFetcher) FetchCategories(ctx context.Context, primaryAdServer, publisherId, iabCategory string) (string, error) {
 	if fetcher.Categories == nil {
 		fetcher.Categories = make(map[string]map[string]stored_requests.Category)
diff --git a/stored_requests/config/config.go b/stored_requests/config/config.go
index e8f6b0bd..e81d9667 100644
--- a/stored_requests/config/config.go
+++ b/stored_requests/config/config.go
@@ -41,25 +41,26 @@ type dbConnection struct {
 //
 // As a side-effect, it will add some endpoints to the router if the config calls for it.
 // In the future we should look for ways to simplify this so that it's not doing two things.
-func CreateStoredRequests(cfg *config.StoredRequestsSlim, metricsEngine pbsmetrics.MetricsEngine, client *http.Client, router *httprouter.Router, dbc *dbConnection) (fetcher stored_requests.AllFetcher, shutdown func()) {
+func CreateStoredRequests(cfg *config.StoredRequests, metricsEngine pbsmetrics.MetricsEngine, client *http.Client, router *httprouter.Router, dbc *dbConnection) (fetcher stored_requests.AllFetcher, shutdown func()) {
 	// Create database connection if given options for one
 	if cfg.Postgres.ConnectionInfo.Database != "" {
 		conn := cfg.Postgres.ConnectionInfo.ConnString()
 
 		if dbc.conn == "" {
-			glog.Infof("Connecting to Postgres for Stored Requests. DB=%s, host=%s, port=%d, user=%s",
+			glog.Infof("Connecting to Postgres for Stored %s. DB=%s, host=%s, port=%d, user=%s",
+				cfg.DataType(),
 				cfg.Postgres.ConnectionInfo.Database,
 				cfg.Postgres.ConnectionInfo.Host,
 				cfg.Postgres.ConnectionInfo.Port,
 				cfg.Postgres.ConnectionInfo.Username)
-			db := newPostgresDB(cfg.Postgres.ConnectionInfo)
+			db := newPostgresDB(cfg.DataType(), cfg.Postgres.ConnectionInfo)
 			dbc.conn = conn
 			dbc.db = db
 		}
 
 		// Error out if config is trying to use multiple database connections for different stored requests (not supported yet)
 		if conn != dbc.conn {
-			glog.Fatal("Multiple database connection settings found in Stored Requests config, only a single database connection is currently supported.")
+			glog.Fatal("Multiple database connection settings found in config, only a single database connection is currently supported.")
 		}
 	}
 
@@ -105,10 +106,7 @@ func CreateStoredRequests(cfg *config.StoredRequestsSlim, metricsEngine pbsmetri
 //
 // As a side-effect, it will add some endpoints to the router if the config calls for it.
 // In the future we should look for ways to simplify this so that it's not doing two things.
-func NewStoredRequests(cfg *config.Configuration, metricsEngine pbsmetrics.MetricsEngine, client *http.Client, router *httprouter.Router) (db *sql.DB, shutdown func(), fetcher stored_requests.Fetcher, ampFetcher stored_requests.Fetcher, categoriesFetcher stored_requests.CategoryFetcher, videoFetcher stored_requests.Fetcher) {
-	// Build individual slim options from combined config struct
-	slimAuction, slimAmp := resolvedStoredRequestsConfig(cfg)
-
+func NewStoredRequests(cfg *config.Configuration, metricsEngine pbsmetrics.MetricsEngine, client *http.Client, router *httprouter.Router) (db *sql.DB, shutdown func(), fetcher stored_requests.Fetcher, ampFetcher stored_requests.Fetcher, accountsFetcher stored_requests.AccountFetcher, categoriesFetcher stored_requests.CategoryFetcher, videoFetcher stored_requests.Fetcher) {
 	// TODO: Switch this to be set in config defaults
 	//if cfg.CategoryMapping.CacheEvents.Enabled && cfg.CategoryMapping.CacheEvents.Endpoint == "" {
 	//	cfg.CategoryMapping.CacheEvents.Endpoint = "/storedrequest/categorymapping"
@@ -116,10 +114,11 @@ func NewStoredRequests(cfg *config.Configuration, metricsEngine pbsmetrics.Metri
 
 	var dbc dbConnection
 
-	fetcher1, shutdown1 := CreateStoredRequests(&slimAuction, metricsEngine, client, router, &dbc)
-	fetcher2, shutdown2 := CreateStoredRequests(&slimAmp, metricsEngine, client, router, &dbc)
+	fetcher1, shutdown1 := CreateStoredRequests(&cfg.StoredRequests, metricsEngine, client, router, &dbc)
+	fetcher2, shutdown2 := CreateStoredRequests(&cfg.StoredRequestsAMP, metricsEngine, client, router, &dbc)
 	fetcher3, shutdown3 := CreateStoredRequests(&cfg.CategoryMapping, metricsEngine, client, router, &dbc)
 	fetcher4, shutdown4 := CreateStoredRequests(&cfg.StoredVideo, metricsEngine, client, router, &dbc)
+	fetcher5, shutdown5 := CreateStoredRequests(&cfg.Accounts, metricsEngine, client, router, &dbc)
 
 	db = dbc.db
 
@@ -127,59 +126,19 @@ func NewStoredRequests(cfg *config.Configuration, metricsEngine pbsmetrics.Metri
 	ampFetcher = fetcher2.(stored_requests.Fetcher)
 	categoriesFetcher = fetcher3.(stored_requests.CategoryFetcher)
 	videoFetcher = fetcher4.(stored_requests.Fetcher)
+	accountsFetcher = fetcher5.(stored_requests.AccountFetcher)
 
 	shutdown = func() {
 		shutdown1()
 		shutdown2()
 		shutdown3()
 		shutdown4()
+		shutdown5()
 	}
 
 	return
 }
 
-func resolvedStoredRequestsConfig(cfg *config.Configuration) (auc, amp config.StoredRequestsSlim) {
-	sr := &cfg.StoredRequests
-
-	// Auction endpoint uses non-Amp fields so can just copy the slin data
-	auc.Files.Enabled = sr.Files
-	auc.Files.Path = sr.Path
-	auc.Postgres.ConnectionInfo = sr.Postgres.ConnectionInfo
-	auc.Postgres.FetcherQueries.QueryTemplate = sr.Postgres.FetcherQueries.QueryTemplate
-	auc.Postgres.CacheInitialization.Timeout = sr.Postgres.CacheInitialization.Timeout
-	auc.Postgres.CacheInitialization.Query = sr.Postgres.CacheInitialization.Query
-	auc.Postgres.PollUpdates.RefreshRate = sr.Postgres.PollUpdates.RefreshRate
-	auc.Postgres.PollUpdates.Timeout = sr.Postgres.PollUpdates.Timeout
-	auc.Postgres.PollUpdates.Query = sr.Postgres.PollUpdates.Query
-	auc.HTTP.Endpoint = sr.HTTP.Endpoint
-	auc.InMemoryCache = sr.InMemoryCache
-	auc.CacheEvents.Enabled = sr.CacheEventsAPI
-	auc.CacheEvents.Endpoint = "/storedrequests/openrtb2"
-	auc.HTTPEvents.RefreshRate = sr.HTTPEvents.RefreshRate
-	auc.HTTPEvents.Timeout = sr.HTTPEvents.Timeout
-	auc.HTTPEvents.Endpoint = sr.HTTPEvents.Endpoint
-
-	// Amp endpoint uses all the slim data but some fields get replacyed by Amp* version of similar fields
-	amp.Files.Enabled = sr.Files
-	amp.Files.Path = sr.Path
-	amp.Postgres.ConnectionInfo = sr.Postgres.ConnectionInfo
-	amp.Postgres.FetcherQueries.QueryTemplate = sr.Postgres.FetcherQueries.AmpQueryTemplate
-	amp.Postgres.CacheInitialization.Timeout = sr.Postgres.CacheInitialization.Timeout
-	amp.Postgres.CacheInitialization.Query = sr.Postgres.CacheInitialization.AmpQuery
-	amp.Postgres.PollUpdates.RefreshRate = sr.Postgres.PollUpdates.RefreshRate
-	amp.Postgres.PollUpdates.Timeout = sr.Postgres.PollUpdates.Timeout
-	amp.Postgres.PollUpdates.Query = sr.Postgres.PollUpdates.AmpQuery
-	amp.HTTP.Endpoint = sr.HTTP.AmpEndpoint
-	amp.InMemoryCache = sr.InMemoryCache
-	amp.CacheEvents.Enabled = sr.CacheEventsAPI
-	amp.CacheEvents.Endpoint = "/storedrequests/amp"
-	amp.HTTPEvents.RefreshRate = sr.HTTPEvents.RefreshRate
-	amp.HTTPEvents.Timeout = sr.HTTPEvents.Timeout
-	amp.HTTPEvents.Endpoint = sr.HTTPEvents.AmpEndpoint
-
-	return
-}
-
 func addListeners(cache stored_requests.Cache, eventProducers []events.EventProducer) (shutdown func()) {
 	listeners := make([]*events.EventListener, 0, len(eventProducers))
 
@@ -196,36 +155,36 @@ func addListeners(cache stored_requests.Cache, eventProducers []events.EventProd
 	}
 }
 
-func newFetcher(cfg *config.StoredRequestsSlim, client *http.Client, db *sql.DB) (fetcher stored_requests.AllFetcher) {
+func newFetcher(cfg *config.StoredRequests, client *http.Client, db *sql.DB) (fetcher stored_requests.AllFetcher) {
 	idList := make(stored_requests.MultiFetcher, 0, 3)
 
 	if cfg.Files.Enabled {
-		fFetcher := newFilesystem(cfg.Files.Path)
+		fFetcher := newFilesystem(cfg.DataType(), cfg.Files.Path)
 		idList = append(idList, fFetcher)
 	}
 	if cfg.Postgres.FetcherQueries.QueryTemplate != "" {
-		glog.Infof("Loading Stored Requests via Postgres.\nQuery: %s", cfg.Postgres.FetcherQueries.QueryTemplate)
+		glog.Infof("Loading Stored %s data via Postgres.\nQuery: %s", cfg.DataType(), cfg.Postgres.FetcherQueries.QueryTemplate)
 		idList = append(idList, db_fetcher.NewFetcher(db, cfg.Postgres.FetcherQueries.MakeQuery))
 	}
 	if cfg.HTTP.Endpoint != "" {
-		glog.Infof("Loading Stored Requests via HTTP. endpoint=%s", cfg.HTTP.Endpoint)
+		glog.Infof("Loading Stored %s data via HTTP. endpoint=%s", cfg.DataType(), cfg.HTTP.Endpoint)
 		idList = append(idList, http_fetcher.NewFetcher(client, cfg.HTTP.Endpoint))
 	}
 
-	fetcher = consolidate(idList)
+	fetcher = consolidate(cfg.DataType(), idList)
 	return
 }
 
-func newCache(cfg *config.StoredRequestsSlim) stored_requests.Cache {
+func newCache(cfg *config.StoredRequests) stored_requests.Cache {
 	if cfg.InMemoryCache.Type == "none" {
-		glog.Info("No Stored Request cache configured. The Fetcher backend will be used for all Stored Requests.")
+		glog.Infof("No Stored %s cache configured. The %s Fetcher backend will be used for all data requests", cfg.DataType(), cfg.DataType())
 		return &nil_cache.NilCache{}
 	}
 
 	return memory.NewCache(&cfg.InMemoryCache)
 }
 
-func newEventProducers(cfg *config.StoredRequestsSlim, client *http.Client, db *sql.DB, router *httprouter.Router) (eventProducers []events.EventProducer) {
+func newEventProducers(cfg *config.StoredRequests, client *http.Client, db *sql.DB, router *httprouter.Router) (eventProducers []events.EventProducer) {
 	if cfg.CacheEvents.Enabled {
 		eventProducers = append(eventProducers, newEventsAPI(router, cfg.CacheEvents.Endpoint))
 	}
@@ -247,7 +206,7 @@ func newEventProducers(cfg *config.StoredRequestsSlim, client *http.Client, db *
 	return
 }
 
-func newPostgresPolling(cfg config.PostgresUpdatePollingSlim, db *sql.DB, startTime time.Time) events.EventProducer {
+func newPostgresPolling(cfg config.PostgresUpdatePolling, db *sql.DB, startTime time.Time) events.EventProducer {
 	timeout := time.Duration(cfg.Timeout) * time.Millisecond
 	ctxProducer := func() (ctx context.Context, canceller func()) {
 		return context.WithTimeout(context.Background(), timeout)
@@ -269,32 +228,37 @@ func newHttpEvents(client *http.Client, timeout time.Duration, refreshRate time.
 	return httpEvents.NewHTTPEvents(client, endpoint, ctxProducer, refreshRate)
 }
 
-func newFilesystem(configPath string) stored_requests.AllFetcher {
-	glog.Infof("Loading Stored Requests from filesystem at path %s", configPath)
+func newFilesystem(dataType config.DataType, configPath string) stored_requests.AllFetcher {
+	glog.Infof("Loading Stored %s data from filesystem at path %s", dataType, configPath)
 	fetcher, err := file_fetcher.NewFileFetcher(configPath)
 	if err != nil {
-		glog.Fatalf("Failed to create a FileFetcher: %v", err)
+		glog.Fatalf("Failed to create a %s FileFetcher: %v", dataType, err)
 	}
 	return fetcher
 }
 
-func newPostgresDB(cfg config.PostgresConnection) *sql.DB {
+func newPostgresDB(dataType config.DataType, cfg config.PostgresConnection) *sql.DB {
 	db, err := sql.Open("postgres", cfg.ConnString())
 	if err != nil {
-		glog.Fatalf("Failed to open postgres connection: %v", err)
+		glog.Fatalf("Failed to open %s postgres connection: %v", dataType, err)
 	}
 
 	if err := db.Ping(); err != nil {
-		glog.Fatalf("Failed to ping postgres: %v", err)
+		glog.Fatalf("Failed to ping %s postgres: %v", dataType, err)
 	}
 
 	return db
 }
 
 // consolidate returns a single Fetcher from an array of fetchers of any size.
-func consolidate(fetchers []stored_requests.AllFetcher) stored_requests.AllFetcher {
+func consolidate(dataType config.DataType, fetchers []stored_requests.AllFetcher) stored_requests.AllFetcher {
 	if len(fetchers) == 0 {
-		glog.Warning("No Stored Request support configured. request.imp[i].ext.prebid.storedrequest will be ignored. If you need this, check your app config")
+		switch dataType {
+		case config.RequestDataType:
+			glog.Warning("No Stored Request support configured. request.imp[i].ext.prebid.storedrequest will be ignored. If you need this, check your app config")
+		default:
+			glog.Warningf("No Stored %s support configured. If you need this, check your app config", dataType)
+		}
 		return empty_fetcher.EmptyFetcher{}
 	} else if len(fetchers) == 1 {
 		return fetchers[0]
diff --git a/stored_requests/fetcher.go b/stored_requests/fetcher.go
index 23fdb6b4..a31b9989 100644
--- a/stored_requests/fetcher.go
+++ b/stored_requests/fetcher.go
@@ -25,6 +25,11 @@ type Fetcher interface {
 	FetchRequests(ctx context.Context, requestIDs []string, impIDs []string) (requestData map[string]json.RawMessage, impData map[string]json.RawMessage, errs []error)
 }
 
+type AccountFetcher interface {
+	// FetchAccount fetches the host account configuration for a publisher
+	FetchAccount(ctx context.Context, accountID string) (json.RawMessage, []error)
+}
+
 type CategoryFetcher interface {
 	// FetchCategories fetches the ad-server/publisher specific category for the given IAB category
 	FetchCategories(ctx context.Context, primaryAdServer, publisherId, iabCategory string) (string, error)
@@ -33,6 +38,7 @@ type CategoryFetcher interface {
 // AllFetcher is an interface that encapsulates both the original Fetcher and the CategoryFetcher
 type AllFetcher interface {
 	FetchRequests(ctx context.Context, requestIDs []string, impIDs []string) (requestData map[string]json.RawMessage, impData map[string]json.RawMessage, errs []error)
+	FetchAccount(ctx context.Context, accountID string) (json.RawMessage, []error)
 	FetchCategories(ctx context.Context, primaryAdServer, publisherId, iabCategory string) (string, error)
 }
 
@@ -181,6 +187,10 @@ func (f *fetcherWithCache) FetchRequests(ctx context.Context, requestIDs []strin
 	return
 }
 
+func (f *fetcherWithCache) FetchAccount(ctx context.Context, accountID string) (json.RawMessage, []error) {
+	return f.fetcher.FetchAccount(ctx, accountID)
+}
+
 func (f *fetcherWithCache) FetchCategories(ctx context.Context, primaryAdServer, publisherId, iabCategory string) (string, error) {
 	return "", nil
 }
diff --git a/stored_requests/multifetcher.go b/stored_requests/multifetcher.go
index 24cf8484..2d08fd45 100644
--- a/stored_requests/multifetcher.go
+++ b/stored_requests/multifetcher.go
@@ -36,6 +36,21 @@ func (mf MultiFetcher) FetchRequests(ctx context.Context, requestIDs []string, i
 	return
 }
 
+func (mf MultiFetcher) FetchAccount(ctx context.Context, accountID string) (account json.RawMessage, errs []error) {
+	for _, f := range mf {
+		if af, ok := f.(AccountFetcher); ok {
+			if account, accErrs := af.FetchAccount(ctx, accountID); len(accErrs) == 0 {
+				return account, nil
+			} else {
+				accErrs = dropMissingIDs(accErrs)
+				errs = append(errs, accErrs...)
+			}
+		}
+	}
+	errs = append(errs, NotFoundError{accountID, "Account"})
+	return nil, errs
+}
+
 func (mf MultiFetcher) FetchCategories(ctx context.Context, primaryAdServer, publisherId, iabCategory string) (string, error) {
 	for _, f := range mf {
 		if cf, ok := f.(CategoryFetcher); ok {


Test patch:
diff --git a/adapters/brightroll/brightroll_test.go b/adapters/brightroll/brightroll_test.go
index 0a6c2c44..5f1c64fd 100644
--- a/adapters/brightroll/brightroll_test.go
+++ b/adapters/brightroll/brightroll_test.go
@@ -1,11 +1,37 @@
 package brightroll
 
 import (
+	"github.com/stretchr/testify/assert"
 	"testing"
 
 	"github.com/prebid/prebid-server/adapters/adapterstest"
 )
 
+func TestEmptyConfig(t *testing.T) {
+	output := NewBrightrollBidder("http://test-bid.ybp.yahoo.com/bid/appnexuspbs", "")
+	ex := ExtraInfo{
+		Accounts: []Account{},
+	}
+	expected := &BrightrollAdapter{
+		URI:       "http://test-bid.ybp.yahoo.com/bid/appnexuspbs",
+		extraInfo: ex,
+	}
+	assert.Equal(t, expected, output, "")
+}
+
+func TestNonEmptyConfig(t *testing.T) {
+	output := NewBrightrollBidder("http://test-bid.ybp.yahoo.com/bid/appnexuspbs", "{\"accounts\": [{\"id\": \"test\",\"bidfloor\":0.1}]}")
+	ex := ExtraInfo{
+		Accounts: []Account{{ID: "test", BidFloor: 0.1}},
+	}
+
+	expected := &BrightrollAdapter{
+		URI:       "http://test-bid.ybp.yahoo.com/bid/appnexuspbs",
+		extraInfo: ex,
+	}
+	assert.Equal(t, expected, output, "")
+}
+
 func TestJsonSamples(t *testing.T) {
-	adapterstest.RunJSONBidderTest(t, "brightrolltest", NewBrightrollBidder("http://test-bid.ybp.yahoo.com/bid/appnexuspbs"))
+	adapterstest.RunJSONBidderTest(t, "brightrolltest", NewBrightrollBidder("http://test-bid.ybp.yahoo.com/bid/appnexuspbs", "{\"accounts\": [{\"id\": \"adthrive\",\"badv\": [], \"bcat\": [\"IAB8-5\",\"IAB8-18\"],\"battr\": [1,2,3], \"bidfloor\":0.0}]}"))
 }
diff --git a/adapters/brightroll/brightrolltest/exemplary/banner-native-audio.json b/adapters/brightroll/brightrolltest/exemplary/banner-native-audio.json
index e8ef5b68..e67a1485 100644
--- a/adapters/brightroll/brightrolltest/exemplary/banner-native-audio.json
+++ b/adapters/brightroll/brightrolltest/exemplary/banner-native-audio.json
@@ -18,7 +18,7 @@
         },
         "ext": {
           "bidder": {
-            "publisher": "cafemom"
+            "publisher": "adthrive"
           }
         }
       },
@@ -30,7 +30,7 @@
         },
         "ext": {
           "bidder": {
-            "publisher": "cafemom"
+            "publisher": "adthrive"
           }
         }
       },
@@ -43,7 +43,7 @@
         },
         "ext": {
           "bidder": {
-            "publisher": "cafemom"
+            "publisher": "adthrive"
           }
         }
       }
@@ -52,14 +52,23 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://test-bid.ybp.yahoo.com/bid/appnexuspbs?publisher=cafemom",
+        "uri": "http://test-bid.ybp.yahoo.com/bid/appnexuspbs?publisher=adthrive",
         "body": {
           "id": "test-request-id",
           "at":1,
+          "bcat": [
+            "IAB8-5",
+            "IAB8-18"
+          ],
           "imp": [
             {
               "id": "test-imp-id",
               "banner": {
+                "battr": [
+                  1,
+                  2,
+                  3
+                ],
                 "format": [
                   {
                     "w": 300,
@@ -75,7 +84,7 @@
               },
               "ext": {
                 "bidder": {
-                  "publisher": "cafemom"
+                  "publisher": "adthrive"
                 }
               }
             },
@@ -87,7 +96,7 @@
               },
               "ext": {
                 "bidder": {
-                  "publisher": "cafemom"
+                  "publisher": "adthrive"
                 }
               }
             },
@@ -100,7 +109,7 @@
               },
               "ext": {
                 "bidder": {
-                  "publisher": "cafemom"
+                  "publisher": "adthrive"
                 }
               }
             }
diff --git a/adapters/brightroll/brightrolltest/exemplary/banner-video-native.json b/adapters/brightroll/brightrolltest/exemplary/banner-video-native.json
index 4df7ab5d..0fffbd2f 100644
--- a/adapters/brightroll/brightrolltest/exemplary/banner-video-native.json
+++ b/adapters/brightroll/brightrolltest/exemplary/banner-video-native.json
@@ -18,7 +18,7 @@
         },
         "ext": {
           "bidder": {
-            "publisher": "cafemom"
+            "publisher": "adthrive"
           }
         }
       },
@@ -30,7 +30,7 @@
         },
         "ext": {
           "bidder": {
-            "publisher": "cafemom"
+            "publisher": "adthrive"
           }
         }
       },
@@ -44,7 +44,7 @@
         },
         "ext": {
           "bidder": {
-            "publisher": "cafemom"
+            "publisher": "adthrive"
           }
         }
       }
@@ -53,14 +53,23 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://test-bid.ybp.yahoo.com/bid/appnexuspbs?publisher=cafemom",
+        "uri": "http://test-bid.ybp.yahoo.com/bid/appnexuspbs?publisher=adthrive",
         "body": {
           "id": "test-request-id",
           "at":1,
+          "bcat": [
+            "IAB8-5",
+            "IAB8-18"
+          ],
           "imp": [
             {
               "id": "test-imp-id",
               "banner": {
+                "battr": [
+                  1,
+                  2,
+                  3
+                ],
                 "format": [
                   {
                     "w": 300,
@@ -76,7 +85,7 @@
               },
               "ext": {
                 "bidder": {
-                  "publisher": "cafemom"
+                  "publisher": "adthrive"
                 }
               }
             },
@@ -88,13 +97,18 @@
               },
               "ext": {
                 "bidder": {
-                  "publisher": "cafemom"
+                  "publisher": "adthrive"
                 }
               }
             },
             {
               "id": "test-imp-video-id",
               "video": {
+                "battr": [
+                  1,
+                  2,
+                  3
+                ],
                 "mimes": ["video/mp4"],
                 "protocols": [2, 5],
                 "w": 1024,
@@ -102,7 +116,7 @@
               },
               "ext": {
                 "bidder": {
-                  "publisher": "cafemom"
+                  "publisher": "adthrive"
                 }
               }
             }
diff --git a/adapters/brightroll/brightrolltest/exemplary/banner-video.json b/adapters/brightroll/brightrolltest/exemplary/banner-video.json
index 50053102..f58dd9b1 100644
--- a/adapters/brightroll/brightrolltest/exemplary/banner-video.json
+++ b/adapters/brightroll/brightrolltest/exemplary/banner-video.json
@@ -18,7 +18,7 @@
         },
         "ext": {
           "bidder": {
-            "publisher": "cafemom"
+            "publisher": "adthrive"
           }
         }
       },
@@ -32,7 +32,7 @@
         },
         "ext": {
           "bidder": {
-            "publisher": "cafemom"
+            "publisher": "adthrive"
           }
         }
       }
@@ -41,14 +41,23 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://test-bid.ybp.yahoo.com/bid/appnexuspbs?publisher=cafemom",
+        "uri": "http://test-bid.ybp.yahoo.com/bid/appnexuspbs?publisher=adthrive",
         "body": {
           "id": "test-request-id",
           "at":1,
+          "bcat": [
+            "IAB8-5",
+            "IAB8-18"
+          ],
           "imp": [
             {
               "id": "test-imp-id",
               "banner": {
+                "battr": [
+                  1,
+                  2,
+                  3
+                ],
                 "format": [
                   {
                     "w": 300,
@@ -64,13 +73,18 @@
               },
               "ext": {
                 "bidder": {
-                  "publisher": "cafemom"
+                  "publisher": "adthrive"
                 }
               }
             },
             {
               "id": "test-imp-video-id",
               "video": {
+                "battr": [
+                  1,
+                  2,
+                  3
+                ],
                 "mimes": ["video/mp4"],
                 "protocols": [2, 5],
                 "w": 1024,
@@ -78,7 +92,7 @@
               },
               "ext": {
                 "bidder": {
-                  "publisher": "cafemom"
+                  "publisher": "adthrive"
                 }
               }
             }
diff --git a/adapters/brightroll/brightrolltest/exemplary/simple-banner.json b/adapters/brightroll/brightrolltest/exemplary/simple-banner.json
index 96fa0cbc..66bc06cf 100644
--- a/adapters/brightroll/brightrolltest/exemplary/simple-banner.json
+++ b/adapters/brightroll/brightrolltest/exemplary/simple-banner.json
@@ -18,7 +18,7 @@
         },
         "ext": {
           "bidder": {
-            "publisher": "cafemom"
+            "publisher": "adthrive"
           }
         }
       }
@@ -28,14 +28,23 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://test-bid.ybp.yahoo.com/bid/appnexuspbs?publisher=cafemom",
+        "uri": "http://test-bid.ybp.yahoo.com/bid/appnexuspbs?publisher=adthrive",
         "body": {
           "id": "test-request-id",
           "at":1,
+          "bcat": [
+            "IAB8-5",
+            "IAB8-18"
+          ],
           "imp": [
             {
               "id": "test-imp-id",
               "banner": {
+                "battr": [
+                  1,
+                  2,
+                  3
+                ],
                 "format": [
                   {
                     "w": 300,
@@ -51,7 +60,7 @@
               },
               "ext": {
                 "bidder": {
-                  "publisher": "cafemom"
+                  "publisher": "adthrive"
                 }
               }
             }
diff --git a/adapters/brightroll/brightrolltest/exemplary/simple-video.json b/adapters/brightroll/brightrolltest/exemplary/simple-video.json
index f2466b2f..3f9b8091 100644
--- a/adapters/brightroll/brightrolltest/exemplary/simple-video.json
+++ b/adapters/brightroll/brightrolltest/exemplary/simple-video.json
@@ -12,7 +12,7 @@
         },
         "ext":{
           "bidder":{
-            "publisher": "cafemom"
+            "publisher": "adthrive"
           }
         }
       }
@@ -22,14 +22,23 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://test-bid.ybp.yahoo.com/bid/appnexuspbs?publisher=cafemom",
+        "uri": "http://test-bid.ybp.yahoo.com/bid/appnexuspbs?publisher=adthrive",
         "body": {
           "id": "test-request-id",
           "at":1,
+          "bcat": [
+            "IAB8-5",
+            "IAB8-18"
+          ],
           "imp": [
             {
               "id": "test-imp-id",
               "video": {
+                "battr": [
+                  1,
+                  2,
+                  3
+                ],
                 "mimes": ["video/mp4"],
                 "protocols": [2, 5],
                 "w": 1024,
@@ -37,7 +46,7 @@
               },
               "ext": {
                 "bidder": {
-                  "publisher": "cafemom"
+                  "publisher": "adthrive"
                 }
               }
             }
diff --git a/adapters/brightroll/brightrolltest/exemplary/valid-extension.json b/adapters/brightroll/brightrolltest/exemplary/valid-extension.json
index 970b4ade..da38c62b 100644
--- a/adapters/brightroll/brightrolltest/exemplary/valid-extension.json
+++ b/adapters/brightroll/brightrolltest/exemplary/valid-extension.json
@@ -12,7 +12,7 @@
         },
         "ext":{
           "bidder":{
-            "publisher": "cafemom"
+            "publisher": "adthrive"
            }
         }
       }
@@ -22,14 +22,23 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://test-bid.ybp.yahoo.com/bid/appnexuspbs?publisher=cafemom",
+        "uri": "http://test-bid.ybp.yahoo.com/bid/appnexuspbs?publisher=adthrive",
         "body": {
           "id": "test-request-id",
           "at":1,
+          "bcat": [
+            "IAB8-5",
+            "IAB8-18"
+          ],
           "imp": [
             {
               "id": "test-imp-id",
               "video": {
+                "battr": [
+                  1,
+                  2,
+                  3
+                ],
                 "mimes": ["video/mp4"],
                 "protocols": [2, 5],
                 "w": 1024,
@@ -37,7 +46,7 @@
               },
               "ext": {
                 "bidder": {
-                  "publisher": "cafemom"
+                  "publisher": "adthrive"
                 }
               }
             }
diff --git a/adapters/brightroll/brightrolltest/exemplary/video-and-audio.json b/adapters/brightroll/brightrolltest/exemplary/video-and-audio.json
index 9f24a471..d3295e5b 100644
--- a/adapters/brightroll/brightrolltest/exemplary/video-and-audio.json
+++ b/adapters/brightroll/brightrolltest/exemplary/video-and-audio.json
@@ -12,7 +12,7 @@
         },
         "ext": {
           "bidder": {
-            "publisher": "cafemom"
+            "publisher": "adthrive"
           }
         }
       },
@@ -25,7 +25,7 @@
         },
         "ext": {
           "bidder": {
-            "publisher": "cafemom"
+            "publisher": "adthrive"
           }
         }
       }
@@ -34,14 +34,23 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://test-bid.ybp.yahoo.com/bid/appnexuspbs?publisher=cafemom",
+        "uri": "http://test-bid.ybp.yahoo.com/bid/appnexuspbs?publisher=adthrive",
         "body": {
           "id": "test-request-id",
           "at":1,
+          "bcat": [
+            "IAB8-5",
+            "IAB8-18"
+          ],
           "imp": [
             {
               "id": "test-imp-video-id",
               "video": {
+                "battr": [
+                  1,
+                  2,
+                  3
+                ],
                 "mimes": ["video/mp4"],
                 "protocols": [2, 5],
                 "w": 1024,
@@ -49,7 +58,7 @@
               },
               "ext": {
                 "bidder": {
-                  "publisher": "cafemom"
+                  "publisher": "adthrive"
                 }
               }
             },
@@ -62,7 +71,7 @@
               },
               "ext": {
                 "bidder": {
-                  "publisher": "cafemom"
+                  "publisher": "adthrive"
                 }
               }
             }
diff --git a/adapters/brightroll/brightrolltest/params/race/banner.json b/adapters/brightroll/brightrolltest/params/race/banner.json
index 91517e36..6eb4ec6a 100644
--- a/adapters/brightroll/brightrolltest/params/race/banner.json
+++ b/adapters/brightroll/brightrolltest/params/race/banner.json
@@ -1,3 +1,3 @@
 {
-  "publisher": "cafemom"
+  "publisher": "adthrive"
 }
diff --git a/adapters/brightroll/brightrolltest/params/race/video.json b/adapters/brightroll/brightrolltest/params/race/video.json
index 91517e36..6eb4ec6a 100644
--- a/adapters/brightroll/brightrolltest/params/race/video.json
+++ b/adapters/brightroll/brightrolltest/params/race/video.json
@@ -1,3 +1,3 @@
 {
-  "publisher": "cafemom"
+  "publisher": "adthrive"
 }
diff --git a/adapters/brightroll/brightrolltest/supplemental/invalid-imp.json b/adapters/brightroll/brightrolltest/supplemental/invalid-imp.json
index a6362c40..01beec71 100644
--- a/adapters/brightroll/brightrolltest/supplemental/invalid-imp.json
+++ b/adapters/brightroll/brightrolltest/supplemental/invalid-imp.json
@@ -3,7 +3,7 @@
     "id": "test-request-id",
     "ext": {
       "bidder": {
-        "publisher": "cafemom"
+        "publisher": "adthrive"
       }
     }
   },
diff --git a/adapters/brightroll/brightrolltest/supplemental/invalid-publisher.json b/adapters/brightroll/brightrolltest/supplemental/invalid-publisher.json
new file mode 100644
index 00000000..da48108a
--- /dev/null
+++ b/adapters/brightroll/brightrolltest/supplemental/invalid-publisher.json
@@ -0,0 +1,34 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-missing-req-param-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            },
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "publisher":"test"
+          }
+        }
+
+      }
+    ]
+  },
+  "expectedMakeRequestsErrors": [
+    {
+      "value": "Invalid publisher",
+      "comparison": "literal"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/rubicon/rubicon_test.go b/adapters/rubicon/rubicon_test.go
index 04897975..5ec78ccf 100644
--- a/adapters/rubicon/rubicon_test.go
+++ b/adapters/rubicon/rubicon_test.go
@@ -1219,6 +1219,15 @@ func TestOpenRTBRequestWithSpecificExtUserEids(t *testing.T) {
 				"ext": {
 					"segments": ["999","888"]
 				}
+			},
+			{
+				"source": "liveramp.com",
+				"uids": [{
+					"id": "LIVERAMPID"
+				}],
+				"ext": {
+					"segments": ["111","222"]
+				}
 			}
 			]}`),
 		},
@@ -1239,7 +1248,7 @@ func TestOpenRTBRequestWithSpecificExtUserEids(t *testing.T) {
 	}
 
 	assert.NotNil(t, userExt.Eids)
-	assert.Equal(t, 3, len(userExt.Eids), "Eids values are not as expected!")
+	assert.Equal(t, 4, len(userExt.Eids), "Eids values are not as expected!")
 
 	assert.NotNil(t, userExt.TpID)
 	assert.Equal(t, 2, len(userExt.TpID), "TpID values are not as expected!")
@@ -1250,6 +1259,9 @@ func TestOpenRTBRequestWithSpecificExtUserEids(t *testing.T) {
 	// liveintent.com
 	assert.Equal(t, "liveintent.com", userExt.TpID[1].Source, "TpID source value is not as expected!")
 
+	// liveramp.com
+	assert.Equal(t, "LIVERAMPID", userExt.LiverampIdl, "Liveramp_idl value is not as expected!")
+
 	userExtRPTarget := make(map[string]interface{})
 	if err := json.Unmarshal(userExt.RP.Target, &userExtRPTarget); err != nil {
 		t.Fatal("Error unmarshalling request.user.ext.rp.target object.")
diff --git a/adapters/smaato/smaatotest/exemplary/video.json b/adapters/smaato/smaatotest/exemplary/video.json
new file mode 100644
index 00000000..f2896d3d
--- /dev/null
+++ b/adapters/smaato/smaatotest/exemplary/video.json
@@ -0,0 +1,187 @@
+{
+    "mockBidRequest": {
+        "id": "447a0a1d-389d-4730-a418-3777e95de7bd",
+        "imp": [
+            {
+                "id": "postbid_iframe",
+                "video": {
+                    "mimes": [
+                        "video/mp4",
+                        "video/quicktime",
+                        "video/3gpp",
+                        "video/x-m4v"
+                    ],
+                    "minduration": 5,
+                    "maxduration": 30,
+                    "protocols": [
+                        7
+                    ],
+                    "w": 1024,
+                    "h": 768,
+                    "startdelay": 0,
+                    "linearity": 1,
+                    "skip": 1,
+                    "skipmin": 5,
+                    "api": [
+                        7
+                    ],
+                    "ext": {
+                        "rewarded": 0
+                    }
+                },
+                "ext": {
+                    "bidder": {
+                        "publisherId": "1100042525",
+                        "adspaceId": "130563103"
+                    }
+                }
+            }
+        ],
+        "site": {
+            "publisher": {
+                "id": "1100042525"
+            },
+            "ext": {
+                "data": {},
+                "amp": 0
+            }
+        },
+        "device": {
+            "ua": "test-user-agent"
+        },
+        "user": {
+            "ext": {
+                "data": {}
+            }
+        },
+        "ext": {
+            "prebid": {
+                "auctiontimestamp": 1598262728811,
+                "targeting": {
+                    "includewinners": true,
+                    "includebidderkeys": false
+                }
+            }
+        }
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "headers": {
+                    "Content-Type": [
+                        "application/json;charset=utf-8"
+                    ],
+                    "Accept": [
+                        "application/json"
+                    ]
+                },
+                "uri": "https://prebid/bidder",
+                "body": {
+                    "id": "447a0a1d-389d-4730-a418-3777e95de7bd",
+                    "imp": [
+                        {
+                            "id": "postbid_iframe",
+                            "tagid": "130563103",
+                            "video": {
+                                "w": 1024,
+                                "h": 768,
+                                "ext": {
+                                    "rewarded": 0
+                                },
+                                "mimes": [
+                                    "video/mp4",
+                                    "video/quicktime",
+                                    "video/3gpp",
+                                    "video/x-m4v"
+                                ],
+                                "minduration": 5,
+                                "startdelay": 0,
+                                "linearity": 1,
+                                "maxduration": 30,
+                                "skip": 1,
+                                "protocols": [
+                                    7
+                                ],
+                                "skipmin": 5,
+                                "api": [
+                                    7
+                                ]
+                            }
+                        }
+                    ],
+                    "user": {
+                        "ext": {
+                        }
+                    },
+                    "device": {
+                        "ua": "test-user-agent"
+                    },
+                    "site": {
+                        "publisher": {
+                            "id": "1100042525"
+                        }
+                    },
+                    "ext": {
+                        "client": "prebid_server_0.1"
+                    }
+                }
+            },
+            "mockResponse": {
+                "status": 200,
+                "body": {
+                    "id": "5ebea288-f13a-4754-be6d-4ade66c68877",
+                    "seatbid": [
+                        {
+                            "seat": "CM6523",
+                            "bid": [
+                                {
+                                    "adm": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?><VAST version=\"2.0\"></VAST>",
+                                    "adomain": [
+                                        "smaato.com"
+                                    ],
+                                    "bidderName": "smaato",
+                                    "cid": "CM6523",
+                                    "crid": "CR69381",
+                                    "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd",
+                                    "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+                                    "iurl": "https://iurl",
+                                    "nurl": "https://nurl",
+                                    "price": 0.01,
+                                    "w": 1024,
+                                    "h": 768
+                                }
+                            ]
+                        }
+                    ],
+                    "bidid": "04db8629-179d-4bcd-acce-e54722969006",
+                    "cur": "USD"
+                }
+            }
+        }
+    ],
+    "expectedBidResponses": [
+        {
+            "currency": "USD",
+            "bids": [
+                {
+                    "bid": {
+                        "adm": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?><VAST version=\"2.0\"></VAST>",
+                        "adomain": [
+                            "smaato.com"
+                        ],
+                        "cid": "CM6523",
+                        "crid": "CR69381",
+                        "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd",
+                        "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+                        "iurl": "https://iurl",
+                        "nurl": "https://nurl",
+                        "price": 0.01,
+                        "w": 1024,
+                        "h": 768
+                    },
+                    "type": "video"
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/adapters/smaato/smaatotest/supplemental/bad-adm-response.json b/adapters/smaato/smaatotest/supplemental/bad-adm-response.json
index 6d4990e9..1fce58f0 100644
--- a/adapters/smaato/smaatotest/supplemental/bad-adm-response.json
+++ b/adapters/smaato/smaatotest/supplemental/bad-adm-response.json
@@ -162,5 +162,11 @@
       }
     }
   ],
-  "expectedBidResponses": []
+  "expectedBidResponses": [],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "Invalid ad markup {\"badmedia\":{\"mediadata\":{\"content\":\"<div>hello</div>\", \"w\":350,\"h\":50},\"impressiontrackers\":[\"//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track/imp/2\"],\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\",\"//prebid-test.smaatolabs.net/track/click/2\"]}}",
+      "comparison": "literal"
+    }
+  ]
 }
\ No newline at end of file
diff --git a/analytics/pubstack/eventchannel/eventchannel_test.go b/analytics/pubstack/eventchannel/eventchannel_test.go
index 792e15e1..f450fb61 100644
--- a/analytics/pubstack/eventchannel/eventchannel_test.go
+++ b/analytics/pubstack/eventchannel/eventchannel_test.go
@@ -3,11 +3,12 @@ package eventchannel
 import (
 	"bytes"
 	"compress/gzip"
-	"github.com/stretchr/testify/assert"
 	"io/ioutil"
 	"sync"
 	"testing"
 	"time"
+
+	"github.com/stretchr/testify/assert"
 )
 
 var maxByteSize = int64(15)
@@ -160,16 +161,21 @@ func TestEventChannel_OutputFormat(t *testing.T) {
 	eventChannel := NewEventChannel(send, 15000, 10, 2*time.Minute)
 
 	eventChannel.Push([]byte("one"))
+	time.Sleep(1 * time.Millisecond)
+
 	eventChannel.flush()
+
 	eventChannel.Push([]byte("two"))
+	time.Sleep(1 * time.Millisecond)
+
 	eventChannel.Push([]byte("three"))
+	time.Sleep(1 * time.Millisecond)
 
 	eventChannel.Close()
 
-	time.Sleep(10 * time.Millisecond)
+	time.Sleep(1 * time.Millisecond)
 
 	expected := append(toGzip("one"), toGzip("twothree")...)
 
 	assert.Equal(t, expected, data)
-
 }
diff --git a/config/config_test.go b/config/config_test.go
index 40589ac7..d2e80c7e 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -2,7 +2,9 @@ package config
 
 import (
 	"bytes"
+	"errors"
 	"net"
+	"os"
 	"strings"
 	"testing"
 	"time"
@@ -135,6 +137,9 @@ func TestDefaults(t *testing.T) {
 	cmpBools(t, "account_adapter_details", cfg.Metrics.Disabled.AccountAdapterDetails, false)
 	cmpBools(t, "adapter_connections_metrics", cfg.Metrics.Disabled.AdapterConnectionMetrics, true)
 	cmpStrings(t, "certificates_file", cfg.PemCertsFile, "")
+	cmpBools(t, "stored_requests.filesystem.enabled", false, cfg.StoredRequests.Files.Enabled)
+	cmpStrings(t, "stored_requests.filesystem.directorypath", "./stored_requests/data/by_id", cfg.StoredRequests.Files.Path)
+	cmpBools(t, "auto_gen_source_tid", cfg.AutoGenSourceTID, true)
 }
 
 var fullConfig = []byte(`
@@ -221,6 +226,7 @@ adapters:
      usersync_url: https://tag.adkernel.com/syncr?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&r=
 blacklisted_apps: ["spamAppID","sketchy-app-id"]
 account_required: true
+auto_gen_source_tid: false
 certificates_file: /etc/ssl/cert.pem
 request_validation:
     ipv4_private_networks: ["1.1.1.0/24"]
@@ -287,6 +293,12 @@ adapters:
      usersync_url: http:\\tag.adkernel.com/syncr?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&r=
 `)
 
+var oldStoredRequestsConfig = []byte(`
+stored_requests:
+  filesystem: true
+  directorypath: "/somepath"
+`)
+
 func cmpStrings(t *testing.T, key string, a string, b string) {
 	t.Helper()
 	assert.Equal(t, a, b, "%s: %s != %s", key, a, b)
@@ -397,6 +409,7 @@ func TestFullConfig(t *testing.T) {
 	cmpStrings(t, "adapters.rhythmone.endpoint", cfg.Adapters[string(openrtb_ext.BidderRhythmone)].Endpoint, "http://tag.1rx.io/rmp")
 	cmpStrings(t, "adapters.rhythmone.usersync_url", cfg.Adapters[string(openrtb_ext.BidderRhythmone)].UserSyncURL, "https://sync.1rx.io/usersync2/rmphb?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir=http%3A%2F%2Fprebid-server.prebid.org%2F%2Fsetuid%3Fbidder%3Drhythmone%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5BRX_UUID%5D")
 	cmpBools(t, "account_required", cfg.AccountRequired, true)
+	cmpBools(t, "auto_gen_source_tid", cfg.AutoGenSourceTID, false)
 	cmpBools(t, "account_adapter_details", cfg.Metrics.Disabled.AccountAdapterDetails, true)
 	cmpBools(t, "adapter_connections_metrics", cfg.Metrics.Disabled.AdapterConnectionMetrics, true)
 	cmpStrings(t, "certificates_file", cfg.PemCertsFile, "/etc/ssl/cert.pem")
@@ -440,17 +453,57 @@ func TestUnmarshalAdapterExtraInfo(t *testing.T) {
 func TestValidConfig(t *testing.T) {
 	cfg := Configuration{
 		StoredRequests: StoredRequests{
-			Files: true,
+			Files: FileFetcherConfig{Enabled: true},
 			InMemoryCache: InMemoryCache{
 				Type: "none",
 			},
 		},
+		StoredVideo: StoredRequests{
+			Files: FileFetcherConfig{Enabled: true},
+			InMemoryCache: InMemoryCache{
+				Type: "none",
+			},
+		},
+		CategoryMapping: StoredRequests{
+			Files: FileFetcherConfig{Enabled: true},
+		},
+		Accounts: StoredRequests{
+			Files:         FileFetcherConfig{Enabled: true},
+			InMemoryCache: InMemoryCache{Type: "none"},
+		},
 	}
 
+	resolvedStoredRequestsConfig(&cfg)
 	err := cfg.validate()
 	assert.Nil(t, err, "OpenRTB filesystem config should work. %v", err)
 }
 
+func TestMigrateConfig(t *testing.T) {
+	v := viper.New()
+	SetupViper(v, "")
+	v.SetConfigType("yaml")
+	v.ReadConfig(bytes.NewBuffer(oldStoredRequestsConfig))
+	migrateConfig(v)
+	cfg, err := New(v)
+	assert.NoError(t, err, "Setting up config should work but it doesn't")
+	cmpBools(t, "stored_requests.filesystem.enabled", true, cfg.StoredRequests.Files.Enabled)
+	cmpStrings(t, "stored_requests.filesystem.path", "/somepath", cfg.StoredRequests.Files.Path)
+}
+
+func TestMigrateConfigFromEnv(t *testing.T) {
+	if oldval, ok := os.LookupEnv("PBS_STORED_REQUESTS_FILESYSTEM"); ok {
+		defer os.Setenv("PBS_STORED_REQUESTS_FILESYSTEM", oldval)
+	} else {
+		defer os.Unsetenv("PBS_STORED_REQUESTS_FILESYSTEM")
+	}
+	os.Setenv("PBS_STORED_REQUESTS_FILESYSTEM", "true")
+	v := viper.New()
+	SetupViper(v, "")
+	cfg, err := New(v)
+	assert.NoError(t, err, "Setting up config should work but it doesn't")
+	cmpBools(t, "stored_requests.filesystem.enabled", true, cfg.StoredRequests.Files.Enabled)
+}
+
 func TestInvalidAdapterEndpointConfig(t *testing.T) {
 	v := viper.New()
 	SetupViper(v, "")
@@ -592,6 +645,18 @@ func TestValidateDebug(t *testing.T) {
 	assert.NotNil(t, err, "cfg.debug.timeout_notification.sampling_rate should not be allowed to be greater than 1.0, but it was allowed")
 }
 
+func TestValidateAccountsConfigRestrictions(t *testing.T) {
+	cfg := newDefaultConfig(t)
+	cfg.Accounts.Files.Enabled = true
+	cfg.Accounts.HTTP.Endpoint = "http://localhost"
+	cfg.Accounts.Postgres.ConnectionInfo.Database = "accounts"
+
+	errs := cfg.validate()
+	assert.Len(t, errs, 2)
+	assert.Contains(t, errs, errors.New("accounts.http: retrieving accounts via http not available, use accounts.files"))
+	assert.Contains(t, errs, errors.New("accounts.postgres: retrieving accounts via postgres not available, use accounts.files"))
+}
+
 func newDefaultConfig(t *testing.T) *Configuration {
 	v := viper.New()
 	SetupViper(v, "")
diff --git a/config/stored_requests_test.go b/config/stored_requests_test.go
index 3e01dd3c..36a5e379 100644
--- a/config/stored_requests_test.go
+++ b/config/stored_requests_test.go
@@ -78,40 +78,40 @@ func TestPostgressConnString(t *testing.T) {
 func TestInMemoryCacheValidation(t *testing.T) {
 	assertNoErrs(t, (&InMemoryCache{
 		Type: "unbounded",
-	}).validate(nil))
+	}).validate("Test", nil))
 	assertNoErrs(t, (&InMemoryCache{
 		Type: "none",
-	}).validate(nil))
+	}).validate("Test", nil))
 	assertNoErrs(t, (&InMemoryCache{
 		Type:             "lru",
 		RequestCacheSize: 1000,
 		ImpCacheSize:     1000,
-	}).validate(nil))
+	}).validate("Test", nil))
 	assertErrsExist(t, (&InMemoryCache{
 		Type: "unrecognized",
-	}).validate(nil))
+	}).validate("Test", nil))
 	assertErrsExist(t, (&InMemoryCache{
 		Type:         "unbounded",
 		ImpCacheSize: 1000,
-	}).validate(nil))
+	}).validate("Test", nil))
 	assertErrsExist(t, (&InMemoryCache{
 		Type:             "unbounded",
 		RequestCacheSize: 1000,
-	}).validate(nil))
+	}).validate("Test", nil))
 	assertErrsExist(t, (&InMemoryCache{
 		Type: "unbounded",
 		TTL:  500,
-	}).validate(nil))
+	}).validate("Test", nil))
 	assertErrsExist(t, (&InMemoryCache{
 		Type:             "lru",
 		RequestCacheSize: 0,
 		ImpCacheSize:     1000,
-	}).validate(nil))
+	}).validate("Test", nil))
 	assertErrsExist(t, (&InMemoryCache{
 		Type:             "lru",
 		RequestCacheSize: 1000,
 		ImpCacheSize:     0,
-	}).validate(nil))
+	}).validate("Test", nil))
 }
 
 func assertErrsExist(t *testing.T, err configErrors) {
@@ -140,7 +140,7 @@ func assertHasValue(t *testing.T, m map[string]string, key string, val string) {
 }
 
 func buildQuery(template string, numReqs int, numImps int) string {
-	cfg := PostgresFetcherQueriesSlim{}
+	cfg := PostgresFetcherQueries{}
 	cfg.QueryTemplate = template
 
 	return cfg.MakeQuery(numReqs, numImps)
@@ -152,3 +152,67 @@ func assertStringsEqual(t *testing.T, actual string, expected string) {
 
 	}
 }
+
+func TestResolveConfig(t *testing.T) {
+	cfg := &Configuration{
+		StoredRequests: StoredRequests{
+			Files: FileFetcherConfig{
+				Enabled: true,
+				Path:    "/test-path"},
+			Postgres: PostgresConfig{
+				ConnectionInfo: PostgresConnection{
+					Database: "db",
+					Host:     "pghost",
+					Port:     5,
+					Username: "user",
+					Password: "pass",
+				},
+				FetcherQueries: PostgresFetcherQueries{
+					AmpQueryTemplate: "amp-fetcher-query",
+				},
+				CacheInitialization: PostgresCacheInitializer{
+					AmpQuery: "amp-cache-init-query",
+				},
+				PollUpdates: PostgresUpdatePolling{
+					AmpQuery: "amp-poll-query",
+				},
+			},
+			HTTP: HTTPFetcherConfig{
+				AmpEndpoint: "amp-http-fetcher-endpoint",
+			},
+			InMemoryCache: InMemoryCache{
+				Type:             "none",
+				TTL:              50,
+				RequestCacheSize: 1,
+				ImpCacheSize:     2,
+			},
+			CacheEvents: CacheEventsConfig{
+				Enabled: true,
+			},
+			HTTPEvents: HTTPEventsConfig{
+				AmpEndpoint: "amp-http-events-endpoint",
+			},
+		},
+	}
+
+	cfg.StoredRequests.Postgres.FetcherQueries.QueryTemplate = "auc-fetcher-query"
+	cfg.StoredRequests.Postgres.CacheInitialization.Query = "auc-cache-init-query"
+	cfg.StoredRequests.Postgres.PollUpdates.Query = "auc-poll-query"
+	cfg.StoredRequests.HTTP.Endpoint = "auc-http-fetcher-endpoint"
+	cfg.StoredRequests.HTTPEvents.Endpoint = "auc-http-events-endpoint"
+
+	resolvedStoredRequestsConfig(cfg)
+	auc := &cfg.StoredRequests
+	amp := &cfg.StoredRequestsAMP
+
+	// Auction should have the non-amp values in it
+	assertStringsEqual(t, auc.CacheEvents.Endpoint, "/storedrequests/openrtb2")
+
+	// Amp should have the amp values in it
+	assertStringsEqual(t, amp.Postgres.FetcherQueries.QueryTemplate, cfg.StoredRequests.Postgres.FetcherQueries.AmpQueryTemplate)
+	assertStringsEqual(t, amp.Postgres.CacheInitialization.Query, cfg.StoredRequests.Postgres.CacheInitialization.AmpQuery)
+	assertStringsEqual(t, amp.Postgres.PollUpdates.Query, cfg.StoredRequests.Postgres.PollUpdates.AmpQuery)
+	assertStringsEqual(t, amp.HTTP.Endpoint, cfg.StoredRequests.HTTP.AmpEndpoint)
+	assertStringsEqual(t, amp.HTTPEvents.Endpoint, cfg.StoredRequests.HTTPEvents.AmpEndpoint)
+	assertStringsEqual(t, amp.CacheEvents.Endpoint, "/storedrequests/amp")
+}
diff --git a/endpoints/openrtb2/amp_auction_test.go b/endpoints/openrtb2/amp_auction_test.go
index 692d3fb0..57e0a7b4 100644
--- a/endpoints/openrtb2/amp_auction_test.go
+++ b/endpoints/openrtb2/amp_auction_test.go
@@ -47,6 +47,7 @@ func TestGoodAmpRequests(t *testing.T) {
 		newParamsValidator(t),
 		&mockAmpStoredReqFetcher{goodRequests},
 		empty_fetcher.EmptyFetcher{},
+		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: maxSize},
 		theMetrics,
 		analyticsConf.NewPBSAnalytics(&config.Analytics{}),
@@ -100,6 +101,7 @@ func TestAMPPageInfo(t *testing.T) {
 		newParamsValidator(t),
 		&mockAmpStoredReqFetcher{stored},
 		empty_fetcher.EmptyFetcher{},
+		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: maxSize},
 		theMetrics,
 		analyticsConf.NewPBSAnalytics(&config.Analytics{}),
@@ -205,6 +207,7 @@ func TestGDPRConsent(t *testing.T) {
 			newParamsValidator(t),
 			&mockAmpStoredReqFetcher{stored},
 			empty_fetcher.EmptyFetcher{},
+			empty_fetcher.EmptyFetcher{},
 			&config.Configuration{MaxRequestSize: maxSize},
 			metrics,
 			analyticsConf.NewPBSAnalytics(&config.Analytics{}),
@@ -358,6 +361,7 @@ func TestCCPAConsent(t *testing.T) {
 			newParamsValidator(t),
 			&mockAmpStoredReqFetcher{stored},
 			empty_fetcher.EmptyFetcher{},
+			empty_fetcher.EmptyFetcher{},
 			&config.Configuration{MaxRequestSize: maxSize},
 			metrics,
 			analyticsConf.NewPBSAnalytics(&config.Analytics{}),
@@ -417,6 +421,7 @@ func TestNoConsent(t *testing.T) {
 		newParamsValidator(t),
 		&mockAmpStoredReqFetcher{stored},
 		empty_fetcher.EmptyFetcher{},
+		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: maxSize},
 		metrics,
 		analyticsConf.NewPBSAnalytics(&config.Analytics{}),
@@ -463,6 +468,7 @@ func TestInvalidConsent(t *testing.T) {
 		newParamsValidator(t),
 		&mockAmpStoredReqFetcher{stored},
 		empty_fetcher.EmptyFetcher{},
+		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: maxSize},
 		metrics,
 		analyticsConf.NewPBSAnalytics(&config.Analytics{}),
@@ -547,6 +553,7 @@ func TestNewAndLegacyConsentBothProvided(t *testing.T) {
 			newParamsValidator(t),
 			&mockAmpStoredReqFetcher{stored},
 			empty_fetcher.EmptyFetcher{},
+			empty_fetcher.EmptyFetcher{},
 			&config.Configuration{MaxRequestSize: maxSize},
 			metrics,
 			analyticsConf.NewPBSAnalytics(&config.Analytics{}),
@@ -599,6 +606,7 @@ func TestAMPSiteExt(t *testing.T) {
 		newParamsValidator(t),
 		&mockAmpStoredReqFetcher{stored},
 		empty_fetcher.EmptyFetcher{},
+		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: maxSize},
 		theMetrics,
 		analyticsConf.NewPBSAnalytics(&config.Analytics{}),
@@ -639,6 +647,7 @@ func TestAmpBadRequests(t *testing.T) {
 		newParamsValidator(t),
 		&mockAmpStoredReqFetcher{badRequests},
 		empty_fetcher.EmptyFetcher{},
+		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: maxSize},
 		theMetrics,
 		analyticsConf.NewPBSAnalytics(&config.Analytics{}),
@@ -670,6 +679,7 @@ func TestAmpDebug(t *testing.T) {
 		newParamsValidator(t),
 		&mockAmpStoredReqFetcher{requests},
 		empty_fetcher.EmptyFetcher{},
+		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: maxSize},
 		theMetrics,
 		analyticsConf.NewPBSAnalytics(&config.Analytics{}),
@@ -743,6 +753,7 @@ func TestQueryParamOverrides(t *testing.T) {
 		newParamsValidator(t),
 		&mockAmpStoredReqFetcher{requests},
 		empty_fetcher.EmptyFetcher{},
+		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: maxSize},
 		theMetrics,
 		analyticsConf.NewPBSAnalytics(&config.Analytics{}),
@@ -895,6 +906,7 @@ func (s formatOverrideSpec) execute(t *testing.T) {
 		newParamsValidator(t),
 		&mockAmpStoredReqFetcher{requests},
 		empty_fetcher.EmptyFetcher{},
+		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: maxSize},
 		theMetrics,
 		analyticsConf.NewPBSAnalytics(&config.Analytics{}),
@@ -952,7 +964,7 @@ var expectedErrorsFromHoldAuction map[openrtb_ext.BidderName][]openrtb_ext.ExtBi
 	},
 }
 
-func (m *mockAmpExchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, ids exchange.IdFetcher, labels pbsmetrics.Labels, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) {
+func (m *mockAmpExchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, ids exchange.IdFetcher, labels pbsmetrics.Labels, account *config.Account, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) {
 	m.lastRequest = bidRequest
 
 	response := &openrtb.BidResponse{
@@ -1042,3 +1054,82 @@ func getTestBidRequest(nilUser bool, userExt *openrtb_ext.ExtUser, nilRegs bool,
 
 	return json.Marshal(bidRequest)
 }
+
+func TestSetEffectiveAmpPubID(t *testing.T) {
+	testPubID := "test-pub"
+	testURLQueryParams := url.Values{}
+	testURLQueryParams.Add("account", testPubID)
+
+	testCases := []struct {
+		description    string
+		req            *openrtb.BidRequest
+		urlQueryParams url.Values
+		expectedPubID  string
+	}{
+		{
+			description: "No publisher ID provided",
+			req: &openrtb.BidRequest{
+				App: &openrtb.App{
+					Publisher: nil,
+				},
+			},
+			expectedPubID: "",
+		},
+		{
+			description: "Publisher ID present in req.App.Publisher.ID",
+			req: &openrtb.BidRequest{
+				App: &openrtb.App{
+					Publisher: &openrtb.Publisher{
+						ID: testPubID,
+					},
+				},
+			},
+			expectedPubID: testPubID,
+		},
+		{
+			description: "Publisher ID present in req.Site.Publisher.ID",
+			req: &openrtb.BidRequest{
+				Site: &openrtb.Site{
+					Publisher: &openrtb.Publisher{
+						ID: testPubID,
+					},
+				},
+			},
+			expectedPubID: testPubID,
+		},
+		{
+			description: "Publisher ID present in account query parameter",
+			req: &openrtb.BidRequest{
+				App: &openrtb.App{
+					Publisher: &openrtb.Publisher{
+						ID: "",
+					},
+				},
+			},
+			urlQueryParams: testURLQueryParams,
+			expectedPubID:  testPubID,
+		},
+		{
+			description: "req.Site.Publisher present but ID set to empty string",
+			req: &openrtb.BidRequest{
+				Site: &openrtb.Site{
+					Publisher: &openrtb.Publisher{
+						ID: "",
+					},
+				},
+			},
+			expectedPubID: "",
+		},
+	}
+
+	for _, test := range testCases {
+		setEffectiveAmpPubID(test.req, test.urlQueryParams)
+		if test.req.Site != nil {
+			assert.Equal(t, test.expectedPubID, test.req.Site.Publisher.ID,
+				"should return the expected Publisher ID for test case: %s", test.description)
+		} else {
+			assert.Equal(t, test.expectedPubID, test.req.App.Publisher.ID,
+				"should return the expected Publisher ID for test case: %s", test.description)
+		}
+	}
+}
diff --git a/endpoints/openrtb2/auction_benchmark_test.go b/endpoints/openrtb2/auction_benchmark_test.go
index fba0daec..09af23af 100644
--- a/endpoints/openrtb2/auction_benchmark_test.go
+++ b/endpoints/openrtb2/auction_benchmark_test.go
@@ -83,6 +83,7 @@ func BenchmarkOpenrtbEndpoint(b *testing.B) {
 		paramValidator,
 		empty_fetcher.EmptyFetcher{},
 		empty_fetcher.EmptyFetcher{},
+		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: maxSize},
 		theMetrics,
 		analyticsConf.NewPBSAnalytics(&config.Analytics{}),
diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go
index 957760c6..53fea2e0 100644
--- a/endpoints/openrtb2/auction_test.go
+++ b/endpoints/openrtb2/auction_test.go
@@ -38,16 +38,17 @@ const maxSize = 1024 * 256
 
 // Struct of data for the general purpose auction tester
 type getResponseFromDirectory struct {
-	dir             string
-	file            string
-	payloadGetter   func(*testing.T, []byte) []byte
-	messageGetter   func(*testing.T, []byte) []byte
-	expectedCode    int
-	aliased         bool
-	disabledBidders []string
-	adaptersConfig  map[string]config.Adapter
-	accountReq      bool
-	description     string
+	dir                    string
+	file                   string
+	payloadGetter          func(*testing.T, []byte) []byte
+	messageGetter          func(*testing.T, []byte) []byte
+	expectedCode           int
+	aliased                bool
+	disabledBidders        []string
+	adaptersConfig         map[string]config.Adapter
+	accountReq             bool
+	accountDefaultDisabled bool
+	description            string
 }
 
 // TestExplicitUserId makes sure that the cookie's ID doesn't override an explicit value sent in the request.
@@ -103,7 +104,7 @@ func TestExplicitUserId(t *testing.T) {
 	// NewMetrics() will create a new go_metrics MetricsEngine, bypassing the need for a crafted configuration set to support it.
 	// As a side effect this gives us some coverage of the go_metrics piece of the metrics engine.
 	theMetrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{})
-	endpoint, _ := NewEndpoint(ex, newParamsValidator(t), empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, cfg, theMetrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BidderMap)
+	endpoint, _ := NewEndpoint(ex, newParamsValidator(t), empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, cfg, theMetrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BidderMap)
 
 	endpoint(httptest.NewRecorder(), request, nil)
 
@@ -255,7 +256,7 @@ func TestRejectAccountRequired(t *testing.T) {
 			accountReq:    true,
 		},
 		{
-			// Account is required, was provided and is not in the blacklisted accounts map
+			// Account is required, was provided, not blacklisted, is not defined by host
 			dir:           "sample-requests/account-required",
 			file:          "with-acct.json",
 			payloadGetter: getRequestPayload,
@@ -264,6 +265,29 @@ func TestRejectAccountRequired(t *testing.T) {
 			aliased:       true,
 			accountReq:    true,
 		},
+		{
+			// Account is required, was provided, not blacklisted, is not defined by host
+			// but strict validation is in force because default account settings are disabled.
+			dir:                    "sample-requests/account-required",
+			file:                   "with-acct.json",
+			payloadGetter:          getRequestPayload,
+			messageGetter:          nilReturner,
+			expectedCode:           http.StatusBadRequest,
+			aliased:                true,
+			accountReq:             true,
+			accountDefaultDisabled: true,
+		},
+		{
+			// Account is required, was provided, not blacklisted and is a valid account
+			dir:                    "sample-requests/account-required",
+			file:                   "valid-acct.json",
+			payloadGetter:          getRequestPayload,
+			messageGetter:          nilReturner,
+			expectedCode:           http.StatusOK,
+			aliased:                true,
+			accountReq:             true,
+			accountDefaultDisabled: true,
+		},
 		{
 			// Account is required, was provided in request and is found in the  blacklisted accounts map
 			dir:           "sample-requests/blacklisted",
@@ -346,12 +370,23 @@ func (gr *getResponseFromDirectory) doRequest(t *testing.T, requestData []byte)
 	// NewMetrics() will create a new go_metrics MetricsEngine, bypassing the need for a crafted configuration set to support it.
 	// As a side effect this gives us some coverage of the go_metrics piece of the metrics engine.
 	theMetrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{})
+	cfg := config.Configuration{
+		MaxRequestSize:     maxSize,
+		BlacklistedApps:    []string{"spam_app"},
+		BlacklistedAppMap:  map[string]bool{"spam_app": true},
+		BlacklistedAccts:   []string{"bad_acct"},
+		BlacklistedAcctMap: map[string]bool{"bad_acct": true},
+		AccountRequired:    gr.accountReq,
+		AccountDefaults:    config.Account{Disabled: gr.accountDefaultDisabled},
+	}
+	assert.NoError(t, cfg.MarshalAccountDefaults())
 	endpoint, _ := NewEndpoint(
 		&nobidExchange{},
 		newParamsValidator(t),
 		&mockStoredReqFetcher{},
+		&mockAccountFetcher{},
 		empty_fetcher.EmptyFetcher{},
-		&config.Configuration{MaxRequestSize: maxSize, BlacklistedApps: []string{"spam_app"}, BlacklistedAppMap: map[string]bool{"spam_app": true}, BlacklistedAccts: []string{"bad_acct"}, BlacklistedAcctMap: map[string]bool{"bad_acct": true}, AccountRequired: gr.accountReq},
+		&cfg,
 		theMetrics,
 		analyticsConf.NewPBSAnalytics(&config.Analytics{}),
 		disabledBidders,
@@ -390,7 +425,7 @@ func doBadAliasRequest(t *testing.T, filename string, expectMsg string) {
 	// NewMetrics() will create a new go_metrics MetricsEngine, bypassing the need for a crafted configuration set to support it.
 	// As a side effect this gives us some coverage of the go_metrics piece of the metrics engine.
 	theMetrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{})
-	endpoint, _ := NewEndpoint(&nobidExchange{}, newParamsValidator(t), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, theMetrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), disabledBidders, aliasJSON, bidderMap)
+	endpoint, _ := NewEndpoint(&nobidExchange{}, newParamsValidator(t), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, theMetrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), disabledBidders, aliasJSON, bidderMap)
 
 	request := httptest.NewRequest("POST", "/openrtb2/auction", bytes.NewReader(requestData))
 	recorder := httptest.NewRecorder()
@@ -454,7 +489,7 @@ func TestNilExchange(t *testing.T) {
 	// NewMetrics() will create a new go_metrics MetricsEngine, bypassing the need for a crafted configuration set to support it.
 	// As a side effect this gives us some coverage of the go_metrics piece of the metrics engine.
 	theMetrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{})
-	_, err := NewEndpoint(nil, newParamsValidator(t), empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, theMetrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BidderMap)
+	_, err := NewEndpoint(nil, newParamsValidator(t), empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, theMetrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BidderMap)
 	if err == nil {
 		t.Errorf("NewEndpoint should return an error when given a nil Exchange.")
 	}
@@ -465,7 +500,7 @@ func TestNilValidator(t *testing.T) {
 	// NewMetrics() will create a new go_metrics MetricsEngine, bypassing the need for a crafted configuration set to support it.
 	// As a side effect this gives us some coverage of the go_metrics piece of the metrics engine.
 	theMetrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{})
-	_, err := NewEndpoint(&nobidExchange{}, nil, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, theMetrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BidderMap)
+	_, err := NewEndpoint(&nobidExchange{}, nil, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, theMetrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BidderMap)
 	if err == nil {
 		t.Errorf("NewEndpoint should return an error when given a nil BidderParamValidator.")
 	}
@@ -476,7 +511,7 @@ func TestExchangeError(t *testing.T) {
 	// NewMetrics() will create a new go_metrics MetricsEngine, bypassing the need for a crafted configuration set to support it.
 	// As a side effect this gives us some coverage of the go_metrics piece of the metrics engine.
 	theMetrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{})
-	endpoint, _ := NewEndpoint(&brokenExchange{}, newParamsValidator(t), empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, theMetrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BidderMap)
+	endpoint, _ := NewEndpoint(&brokenExchange{}, newParamsValidator(t), empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, theMetrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BidderMap)
 	request := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, "site.json")))
 	recorder := httptest.NewRecorder()
 	endpoint(recorder, request, nil)
@@ -588,7 +623,7 @@ func TestImplicitIPsEndToEnd(t *testing.T) {
 				IPv6PrivateNetworksParsed: test.privateNetworksIPv6,
 			},
 		}
-		endpoint, _ := NewEndpoint(exchange, newParamsValidator(t), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, cfg, metrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BidderMap)
+		endpoint, _ := NewEndpoint(exchange, newParamsValidator(t), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, cfg, metrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BidderMap)
 
 		httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, test.reqJSONFile)))
 		httpReq.Header.Set("X-Forwarded-For", test.xForwardedForHeader)
@@ -773,7 +808,7 @@ func TestImplicitDNTEndToEnd(t *testing.T) {
 	metrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{})
 	for _, test := range testCases {
 		exchange := &nobidExchange{}
-		endpoint, _ := NewEndpoint(exchange, newParamsValidator(t), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, metrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BidderMap)
+		endpoint, _ := NewEndpoint(exchange, newParamsValidator(t), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, metrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BidderMap)
 
 		httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, test.reqJSONFile)))
 		httpReq.Header.Set("DNT", test.dntHeader)
@@ -846,6 +881,7 @@ func TestStoredRequests(t *testing.T) {
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		empty_fetcher.EmptyFetcher{},
+		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: maxSize},
 		theMetrics,
 		analyticsConf.NewPBSAnalytics(&config.Analytics{}),
@@ -885,6 +921,7 @@ func TestOversizedRequest(t *testing.T) {
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		empty_fetcher.EmptyFetcher{},
+		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: int64(len(reqBody) - 1)},
 		pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}),
 		analyticsConf.NewPBSAnalytics(&config.Analytics{}),
@@ -920,6 +957,7 @@ func TestRequestSizeEdgeCase(t *testing.T) {
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		empty_fetcher.EmptyFetcher{},
+		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: int64(len(reqBody))},
 		pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}),
 		analyticsConf.NewPBSAnalytics(&config.Analytics{}),
@@ -953,6 +991,7 @@ func TestNoEncoding(t *testing.T) {
 		newParamsValidator(t),
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
+		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: maxSize},
 		pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}),
 		analyticsConf.NewPBSAnalytics(&config.Analytics{}),
@@ -1028,6 +1067,7 @@ func TestContentType(t *testing.T) {
 		newParamsValidator(t),
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
+		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: maxSize},
 		pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}),
 		analyticsConf.NewPBSAnalytics(&config.Analytics{}),
@@ -1058,6 +1098,7 @@ func TestDisabledBidder(t *testing.T) {
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		empty_fetcher.EmptyFetcher{},
+		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{
 			MaxRequestSize: int64(len(reqBody)),
 		},
@@ -1096,6 +1137,7 @@ func TestValidateImpExtDisabledBidder(t *testing.T) {
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		empty_fetcher.EmptyFetcher{},
+		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: int64(8096)},
 		pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}),
 		analyticsConf.NewPBSAnalytics(&config.Analytics{}),
@@ -1112,16 +1154,6 @@ func TestValidateImpExtDisabledBidder(t *testing.T) {
 	assert.Equal(t, []error{&errortypes.BidderTemporarilyDisabled{Message: "The bidder 'unknownbidder' has been disabled."}}, errs)
 }
 
-func TestEffectivePubID(t *testing.T) {
-	var pub openrtb.Publisher
-	assert.Equal(t, pbsmetrics.PublisherUnknown, effectivePubID(nil), "effectivePubID failed for nil Publisher.")
-	assert.Equal(t, pbsmetrics.PublisherUnknown, effectivePubID(&pub), "effectivePubID failed for empty Publisher.")
-	pub.ID = "123"
-	assert.Equal(t, "123", effectivePubID(&pub), "effectivePubID failed for standard Publisher.")
-	pub.Ext = json.RawMessage(`{"prebid": {"parentAccount": "abc"} }`)
-	assert.Equal(t, "abc", effectivePubID(&pub), "effectivePubID failed for parentAccount.")
-}
-
 func validRequest(t *testing.T, filename string) string {
 	requestData, err := ioutil.ReadFile("sample-requests/valid-whole/supplementary/" + filename)
 	if err != nil {
@@ -1137,6 +1169,7 @@ func TestCurrencyTrunc(t *testing.T) {
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		empty_fetcher.EmptyFetcher{},
+		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{},
 		pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}),
 		analyticsConf.NewPBSAnalytics(&config.Analytics{}),
@@ -1181,6 +1214,7 @@ func TestCCPAInvalid(t *testing.T) {
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		empty_fetcher.EmptyFetcher{},
+		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{},
 		pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}),
 		analyticsConf.NewPBSAnalytics(&config.Analytics{}),
@@ -1222,6 +1256,55 @@ func TestCCPAInvalid(t *testing.T) {
 	assert.Empty(t, req.Regs.Ext, "Invalid Consent Removed From Request")
 }
 
+func TestValidateSourceTID(t *testing.T) {
+	cfg := &config.Configuration{
+		AutoGenSourceTID: true,
+	}
+
+	deps := &endpointDeps{
+		&nobidExchange{},
+		newParamsValidator(t),
+		&mockStoredReqFetcher{},
+		empty_fetcher.EmptyFetcher{},
+		empty_fetcher.EmptyFetcher{},
+		empty_fetcher.EmptyFetcher{},
+		cfg,
+		pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}),
+		analyticsConf.NewPBSAnalytics(&config.Analytics{}),
+		map[string]string{},
+		false,
+		[]byte{},
+		openrtb_ext.BidderMap,
+		nil,
+		nil,
+		hardcodedResponseIPValidator{response: true},
+	}
+
+	ui := uint64(1)
+	req := openrtb.BidRequest{
+		ID: "someID",
+		Imp: []openrtb.Imp{
+			{
+				ID: "imp-ID",
+				Banner: &openrtb.Banner{
+					W: &ui,
+					H: &ui,
+				},
+				Ext: json.RawMessage(`{"appnexus": {"placementId": 5667}}`),
+			},
+		},
+		Site: &openrtb.Site{
+			ID: "myID",
+		},
+		Regs: &openrtb.Regs{
+			Ext: json.RawMessage(`{"us_privacy":"invalid by length"}`),
+		},
+	}
+
+	deps.validateRequest(&req)
+	assert.NotEmpty(t, req.Source.TID, "Expected req.Source.TID to be filled with a randomly generated UID")
+}
+
 func TestSChainInvalid(t *testing.T) {
 	deps := &endpointDeps{
 		&nobidExchange{},
@@ -1229,6 +1312,7 @@ func TestSChainInvalid(t *testing.T) {
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		empty_fetcher.EmptyFetcher{},
+		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{},
 		pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}),
 		analyticsConf.NewPBSAnalytics(&config.Analytics{}),
@@ -1269,6 +1353,63 @@ func TestSChainInvalid(t *testing.T) {
 	assert.ElementsMatch(t, errL, []error{expectedError})
 }
 
+func TestGetAccountID(t *testing.T) {
+	testPubID := "test-pub"
+	testParentAccount := "test-account"
+	testPubExt := openrtb_ext.ExtPublisher{
+		Prebid: &openrtb_ext.ExtPublisherPrebid{
+			ParentAccount: &testParentAccount,
+		},
+	}
+	testPubExtJSON, err := json.Marshal(testPubExt)
+	assert.NoError(t, err)
+
+	testCases := []struct {
+		description   string
+		pub           *openrtb.Publisher
+		expectedAccID string
+	}{
+		{
+			description: "Publisher.ID and Publisher.Ext.Prebid.ParentAccount both present",
+			pub: &openrtb.Publisher{
+				ID:  testPubID,
+				Ext: testPubExtJSON,
+			},
+			expectedAccID: testParentAccount,
+		},
+		{
+			description: "Only Publisher.Ext.Prebid.ParentAccount present",
+			pub: &openrtb.Publisher{
+				ID:  "",
+				Ext: testPubExtJSON,
+			},
+			expectedAccID: testParentAccount,
+		},
+		{
+			description: "Only Publisher.ID present",
+			pub: &openrtb.Publisher{
+				ID: testPubID,
+			},
+			expectedAccID: testPubID,
+		},
+		{
+			description:   "Neither Publisher.ID or Publisher.Ext.Prebid.ParentAccount present",
+			pub:           &openrtb.Publisher{},
+			expectedAccID: pbsmetrics.PublisherUnknown,
+		},
+		{
+			description:   "Publisher is nil",
+			pub:           nil,
+			expectedAccID: pbsmetrics.PublisherUnknown,
+		},
+	}
+
+	for _, test := range testCases {
+		acc := getAccountID(test.pub)
+		assert.Equal(t, test.expectedAccID, acc, "getAccountID should return expected account for test case: %s", test.description)
+	}
+}
+
 func TestSanitizeRequest(t *testing.T) {
 	testCases := []struct {
 		description  string
@@ -1344,12 +1485,55 @@ func TestSanitizeRequest(t *testing.T) {
 	}
 }
 
+func TestValidateAndFillSourceTID(t *testing.T) {
+	testTID := "some-tid"
+	testCases := []struct {
+		description   string
+		req           *openrtb.BidRequest
+		expectRandTID bool
+		expectedTID   string
+	}{
+		{
+			description:   "req.Source not present. Expecting a randomly generated TID value",
+			req:           &openrtb.BidRequest{},
+			expectRandTID: true,
+		},
+		{
+			description: "req.Source.TID not present. Expecting a randomly generated TID value",
+			req: &openrtb.BidRequest{
+				Source: &openrtb.Source{},
+			},
+			expectRandTID: true,
+		},
+		{
+			description: "req.Source.TID present. Expecting no change",
+			req: &openrtb.BidRequest{
+				Source: &openrtb.Source{
+					TID: testTID,
+				},
+			},
+			expectRandTID: false,
+			expectedTID:   testTID,
+		},
+	}
+
+	for _, test := range testCases {
+		_ = validateAndFillSourceTID(test.req)
+		if test.expectRandTID {
+			assert.NotEmpty(t, test.req.Source.TID, test.description)
+			assert.NotEqual(t, test.expectedTID, test.req.Source.TID, test.description)
+		} else {
+			assert.Equal(t, test.expectedTID, test.req.Source.TID, test.description)
+		}
+	}
+}
+
 // nobidExchange is a well-behaved exchange which always bids "no bid".
 type nobidExchange struct {
 	gotRequest *openrtb.BidRequest
 }
 
-func (e *nobidExchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, ids exchange.IdFetcher, labels pbsmetrics.Labels, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) {
+func (e *nobidExchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, ids exchange.IdFetcher, labels pbsmetrics.Labels, account *config.Account, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) {
 	e.gotRequest = bidRequest
 	return &openrtb.BidResponse{
 		ID:    bidRequest.ID,
@@ -1360,7 +1544,7 @@ func (e *nobidExchange) HoldAuction(ctx context.Context, bidRequest *openrtb.Bid
 
 type brokenExchange struct{}
 
-func (e *brokenExchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, ids exchange.IdFetcher, labels pbsmetrics.Labels, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) {
+func (e *brokenExchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, ids exchange.IdFetcher, labels pbsmetrics.Labels, account *config.Account, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) {
 	return nil, errors.New("Critical, unrecoverable error.")
 }
 
@@ -1716,11 +1900,27 @@ func (cf mockStoredReqFetcher) FetchRequests(ctx context.Context, requestIDs []s
 	return testStoredRequestData, testStoredImpData, nil
 }
 
+var mockAccountData = map[string]json.RawMessage{
+	"valid_acct":    json.RawMessage(`{"disabled":false}`),
+	"disabled_acct": json.RawMessage(`{"disabled":true}`),
+}
+
+type mockAccountFetcher struct {
+}
+
+func (af mockAccountFetcher) FetchAccount(ctx context.Context, accountID string) (json.RawMessage, []error) {
+	if account, ok := mockAccountData[accountID]; ok {
+		return account, nil
+	} else {
+		return nil, []error{stored_requests.NotFoundError{accountID, "Account"}}
+	}
+}
+
 type mockExchange struct {
 	lastRequest *openrtb.BidRequest
 }
 
-func (m *mockExchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, ids exchange.IdFetcher, labels pbsmetrics.Labels, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) {
+func (m *mockExchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, ids exchange.IdFetcher, labels pbsmetrics.Labels, account *config.Account, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) {
 	m.lastRequest = bidRequest
 	return &openrtb.BidResponse{
 		SeatBid: []openrtb.SeatBid{{
@@ -1775,3 +1975,70 @@ type hardcodedResponseIPValidator struct {
 func (v hardcodedResponseIPValidator) IsValid(net.IP, iputil.IPVersion) bool {
 	return v.response
 }
+
+func TestGetAccount(t *testing.T) {
+	unknown := pbsmetrics.PublisherUnknown
+	testCases := []struct {
+		accountID string
+		// account_required
+		required bool
+		// account_defaults.disabled
+		disabled bool
+		// expected error, or nil if account should be found
+		err error
+	}{
+		// Blacklisted account is always rejected even in permissive setup
+		{accountID: "bad_acct", required: false, disabled: false, err: &errortypes.BlacklistedAcct{}},
+
+		// empty pubID
+		{accountID: unknown, required: false, disabled: false, err: nil},
+		{accountID: unknown, required: true, disabled: false, err: &errortypes.AcctRequired{}},
+		{accountID: unknown, required: false, disabled: true, err: &errortypes.BlacklistedAcct{}},
+		{accountID: unknown, required: true, disabled: true, err: &errortypes.AcctRequired{}},
+
+		// pubID given but is not a valid host account (does not exist)
+		{accountID: "doesnt_exist_acct", required: false, disabled: false, err: nil},
+		{accountID: "doesnt_exist_acct", required: true, disabled: false, err: nil},
+		{accountID: "doesnt_exist_acct", required: false, disabled: true, err: &errortypes.BlacklistedAcct{}},
+		{accountID: "doesnt_exist_acct", required: true, disabled: true, err: &errortypes.AcctRequired{}},
+
+		// pubID given and matches a valid host account with Disabled: false
+		{accountID: "valid_acct", required: false, disabled: false, err: nil},
+		{accountID: "valid_acct", required: true, disabled: false, err: nil},
+		{accountID: "valid_acct", required: false, disabled: true, err: nil},
+		{accountID: "valid_acct", required: true, disabled: true, err: nil},
+
+		// pubID given and matches a host account explicitly disabled (Disabled: true on account json)
+		{accountID: "disabled_acct", required: false, disabled: false, err: &errortypes.BlacklistedAcct{}},
+		{accountID: "disabled_acct", required: true, disabled: false, err: &errortypes.BlacklistedAcct{}},
+		{accountID: "disabled_acct", required: false, disabled: true, err: &errortypes.BlacklistedAcct{}},
+		{accountID: "disabled_acct", required: true, disabled: true, err: &errortypes.BlacklistedAcct{}},
+	}
+
+	for _, test := range testCases {
+		description := fmt.Sprintf(`ID=%s/required=%t/disabled=%t`, test.accountID, test.required, test.disabled)
+		t.Run(description, func(t *testing.T) {
+			deps := &endpointDeps{
+				cfg: &config.Configuration{
+					BlacklistedAcctMap: map[string]bool{"bad_acct": true},
+					AccountRequired:    test.required,
+					AccountDefaults:    config.Account{Disabled: test.disabled},
+				},
+				accounts: &mockAccountFetcher{},
+			}
+			assert.NoError(t, deps.cfg.MarshalAccountDefaults())
+
+			account, errors := deps.getAccount(context.Background(), test.accountID)
+
+			if test.err == nil {
+				assert.Empty(t, errors)
+				assert.Equal(t, test.accountID, account.ID, "account.ID must match requested ID")
+				assert.Equal(t, false, account.Disabled, "returned account must not be disabled")
+			} else {
+				assert.NotEmpty(t, errors, "expected errors but got success")
+				assert.Nil(t, account, "return account must be nil on error")
+				assert.IsType(t, test.err, errors[0], "error is of unexpected type")
+			}
+		})
+	}
+}
diff --git a/endpoints/openrtb2/video_auction_test.go b/endpoints/openrtb2/video_auction_test.go
index 78715f5c..a4e9bfbb 100644
--- a/endpoints/openrtb2/video_auction_test.go
+++ b/endpoints/openrtb2/video_auction_test.go
@@ -1168,6 +1168,7 @@ func mockDepsWithMetrics(t *testing.T, ex *mockExchangeVideo) (*endpointDeps, *p
 		&mockVideoStoredReqFetcher{},
 		&mockVideoStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
+		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: maxSize},
 		theMetrics,
 		mockModule,
@@ -1210,6 +1211,7 @@ func mockDeps(t *testing.T, ex *mockExchangeVideo) *endpointDeps {
 		&mockVideoStoredReqFetcher{},
 		&mockVideoStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
+		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: maxSize},
 		theMetrics,
 		analyticsConf.NewPBSAnalytics(&config.Analytics{}),
@@ -1233,6 +1235,7 @@ func mockDepsNoBids(t *testing.T, ex *mockExchangeVideoNoBids) *endpointDeps {
 		&mockVideoStoredReqFetcher{},
 		&mockVideoStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
+		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: maxSize},
 		theMetrics,
 		analyticsConf.NewPBSAnalytics(&config.Analytics{}),
@@ -1275,7 +1278,7 @@ type mockExchangeVideo struct {
 	cache       *mockCacheClient
 }
 
-func (m *mockExchangeVideo) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, ids exchange.IdFetcher, labels pbsmetrics.Labels, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) {
+func (m *mockExchangeVideo) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, ids exchange.IdFetcher, labels pbsmetrics.Labels, account *config.Account, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) {
 	m.lastRequest = bidRequest
 	if debugLog != nil && debugLog.Enabled {
 		m.cache.called = true
@@ -1311,7 +1314,7 @@ type mockExchangeVideoNoBids struct {
 	cache       *mockCacheClient
 }
 
-func (m *mockExchangeVideoNoBids) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, ids exchange.IdFetcher, labels pbsmetrics.Labels, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) {
+func (m *mockExchangeVideoNoBids) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, ids exchange.IdFetcher, labels pbsmetrics.Labels, account *config.Account, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) {
 	m.lastRequest = bidRequest
 	return &openrtb.BidResponse{
 		SeatBid: []openrtb.SeatBid{{}},
diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go
index efabb845..21003b66 100644
--- a/exchange/exchange_test.go
+++ b/exchange/exchange_test.go
@@ -248,7 +248,7 @@ func TestDebugBehaviour(t *testing.T) {
 		}
 
 		// Run test
-		outBidResponse, err := e.HoldAuction(context.Background(), bidRequest, &emptyUsersync{}, pbsmetrics.Labels{}, &categoriesFetcher, nil)
+		outBidResponse, err := e.HoldAuction(context.Background(), bidRequest, &emptyUsersync{}, pbsmetrics.Labels{}, &config.Account{}, &categoriesFetcher, nil)
 
 		// Assert no HoldAuction error
 		assert.NoErrorf(t, err, "%s. ex.HoldAuction returned an error: %v \n", test.desc, err)
@@ -752,7 +752,7 @@ func TestRaceIntegration(t *testing.T) {
 	theMetrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{})
 	currencyConverter := currencies.NewRateConverter(&http.Client{}, "", time.Duration(0))
 	ex := NewExchange(server.Client(), &wellBehavedCache{}, cfg, theMetrics, adapters.ParseBidderInfos(cfg.Adapters, "../static/bidder-info", openrtb_ext.BidderList()), gdpr.AlwaysAllow{}, currencyConverter)
-	_, err := ex.HoldAuction(context.Background(), newRaceCheckingRequest(t), &emptyUsersync{}, pbsmetrics.Labels{}, &categoriesFetcher, nil)
+	_, err := ex.HoldAuction(context.Background(), newRaceCheckingRequest(t), &emptyUsersync{}, pbsmetrics.Labels{}, &config.Account{}, &categoriesFetcher, nil)
 	if err != nil {
 		t.Errorf("HoldAuction returned unexpected error: %v", err)
 	}
@@ -939,7 +939,7 @@ func TestPanicRecoveryHighLevel(t *testing.T) {
 	if error != nil {
 		t.Errorf("Failed to create a category Fetcher: %v", error)
 	}
-	_, err := e.HoldAuction(context.Background(), request, &emptyUsersync{}, pbsmetrics.Labels{}, &categoriesFetcher, nil)
+	_, err := e.HoldAuction(context.Background(), request, &emptyUsersync{}, pbsmetrics.Labels{}, &config.Account{}, &categoriesFetcher, nil)
 	if err != nil {
 		t.Errorf("HoldAuction returned unexpected error: %v", err)
 	}
@@ -1055,7 +1055,7 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) {
 		*debugLog = *spec.DebugLog
 		debugLog.Regexp = regexp.MustCompile(`[<>]`)
 	}
-	bid, err := ex.HoldAuction(context.Background(), &spec.IncomingRequest.OrtbRequest, mockIdFetcher(spec.IncomingRequest.Usersyncs), pbsmetrics.Labels{}, &categoriesFetcher, debugLog)
+	bid, err := ex.HoldAuction(context.Background(), &spec.IncomingRequest.OrtbRequest, mockIdFetcher(spec.IncomingRequest.Usersyncs), pbsmetrics.Labels{}, &config.Account{}, &categoriesFetcher, debugLog)
 	responseTimes := extractResponseTimes(t, filename, bid)
 	for _, bidderName := range biddersInAuction {
 		if _, ok := responseTimes[bidderName]; !ok {
diff --git a/exchange/targeting_test.go b/exchange/targeting_test.go
index e596e5aa..aaa75411 100644
--- a/exchange/targeting_test.go
+++ b/exchange/targeting_test.go
@@ -108,7 +108,7 @@ func runTargetingAuction(t *testing.T, mockBids map[openrtb_ext.BidderName][]*op
 	if error != nil {
 		t.Errorf("Failed to create a category Fetcher: %v", error)
 	}
-	bidResp, err := ex.HoldAuction(context.Background(), req, &mockFetcher{}, pbsmetrics.Labels{}, &categoriesFetcher, nil)
+	bidResp, err := ex.HoldAuction(context.Background(), req, &mockFetcher{}, pbsmetrics.Labels{}, &config.Account{}, &categoriesFetcher, nil)
 
 	if err != nil {
 		t.Fatalf("Unexpected errors running auction: %v", err)
diff --git a/stored_requests/backends/file_fetcher/fetcher_test.go b/stored_requests/backends/file_fetcher/fetcher_test.go
index 2429a77c..a145a3b4 100644
--- a/stored_requests/backends/file_fetcher/fetcher_test.go
+++ b/stored_requests/backends/file_fetcher/fetcher_test.go
@@ -24,6 +24,20 @@ func TestFileFetcher(t *testing.T) {
 	validateImp(t, storedImps)
 }
 
+func TestAccountFetcher(t *testing.T) {
+	fetcher, err := NewFileFetcher("./test")
+	assert.NoError(t, err, "Failed to create test fetcher")
+
+	account, errs := fetcher.FetchAccount(context.Background(), "valid")
+	assertErrorCount(t, 0, errs)
+	assert.JSONEq(t, `{"disabled":false, "id":"valid"}`, string(account))
+
+	account, errs = fetcher.FetchAccount(context.Background(), "nonexistent")
+	assertErrorCount(t, 1, errs)
+	assert.Error(t, errs[0])
+	assert.Equal(t, stored_requests.NotFoundError{"nonexistent", "Account"}, errs[0])
+}
+
 func TestInvalidDirectory(t *testing.T) {
 	_, err := NewFileFetcher("./nonexistant-directory")
 	if err == nil {
diff --git a/stored_requests/backends/file_fetcher/test/accounts/valid.json b/stored_requests/backends/file_fetcher/test/accounts/valid.json
new file mode 100644
index 00000000..2c8bd12a
--- /dev/null
+++ b/stored_requests/backends/file_fetcher/test/accounts/valid.json
@@ -0,0 +1,4 @@
+{
+    "id": "valid",
+    "disabled": false
+}
diff --git a/stored_requests/config/config_test.go b/stored_requests/config/config_test.go
index 11748a59..4c3943ea 100644
--- a/stored_requests/config/config_test.go
+++ b/stored_requests/config/config_test.go
@@ -19,8 +19,8 @@ import (
 )
 
 func TestNewEmptyFetcher(t *testing.T) {
-	fetcher := newFetcher(&config.StoredRequestsSlim{}, nil, nil)
-	ampFetcher := newFetcher(&config.StoredRequestsSlim{}, nil, nil)
+	fetcher := newFetcher(&config.StoredRequests{}, nil, nil)
+	ampFetcher := newFetcher(&config.StoredRequests{}, nil, nil)
 	if fetcher == nil || ampFetcher == nil {
 		t.Errorf("The fetchers should be non-nil, even with an empty config.")
 	}
@@ -33,13 +33,13 @@ func TestNewEmptyFetcher(t *testing.T) {
 }
 
 func TestNewHTTPFetcher(t *testing.T) {
-	fetcher := newFetcher(&config.StoredRequestsSlim{
-		HTTP: config.HTTPFetcherConfigSlim{
+	fetcher := newFetcher(&config.StoredRequests{
+		HTTP: config.HTTPFetcherConfig{
 			Endpoint: "stored-requests.prebid.com",
 		},
 	}, nil, nil)
-	ampFetcher := newFetcher(&config.StoredRequestsSlim{
-		HTTP: config.HTTPFetcherConfigSlim{
+	ampFetcher := newFetcher(&config.StoredRequests{
+		HTTP: config.HTTPFetcherConfig{
 			Endpoint: "stored-requests.prebid.com?type=amp",
 		},
 	}, nil, nil)
@@ -60,13 +60,13 @@ func TestNewHTTPFetcher(t *testing.T) {
 }
 
 func TestNewHTTPFetcherNoAmp(t *testing.T) {
-	fetcher := newFetcher(&config.StoredRequestsSlim{
-		HTTP: config.HTTPFetcherConfigSlim{
+	fetcher := newFetcher(&config.StoredRequests{
+		HTTP: config.HTTPFetcherConfig{
 			Endpoint: "stored-requests.prebid.com",
 		},
 	}, nil, nil)
-	ampFetcher := newFetcher(&config.StoredRequestsSlim{
-		HTTP: config.HTTPFetcherConfigSlim{
+	ampFetcher := newFetcher(&config.StoredRequests{
+		HTTP: config.HTTPFetcherConfig{
 			Endpoint: "",
 		},
 	}, nil, nil)
@@ -82,78 +82,14 @@ func TestNewHTTPFetcherNoAmp(t *testing.T) {
 	}
 }
 
-func TestResolveConfig(t *testing.T) {
-	cfg := &config.Configuration{
-		StoredRequests: config.StoredRequests{
-			Files: true,
-			Path:  "/test-path",
-			Postgres: config.PostgresConfig{
-				ConnectionInfo: config.PostgresConnection{
-					Database: "db",
-					Host:     "pghost",
-					Port:     5,
-					Username: "user",
-					Password: "pass",
-				},
-				FetcherQueries: config.PostgresFetcherQueries{
-					AmpQueryTemplate: "amp-fetcher-query",
-				},
-				CacheInitialization: config.PostgresCacheInitializer{
-					AmpQuery: "amp-cache-init-query",
-				},
-				PollUpdates: config.PostgresUpdatePolling{
-					AmpQuery: "amp-poll-query",
-				},
-			},
-			HTTP: config.HTTPFetcherConfig{
-				AmpEndpoint: "amp-http-fetcher-endpoint",
-			},
-			InMemoryCache: config.InMemoryCache{
-				Type:             "none",
-				TTL:              50,
-				RequestCacheSize: 1,
-				ImpCacheSize:     2,
-			},
-			CacheEventsAPI: true,
-			HTTPEvents: config.HTTPEventsConfig{
-				AmpEndpoint: "amp-http-events-endpoint",
-			},
-		},
-	}
-
-	cfg.StoredRequests.Postgres.FetcherQueries.QueryTemplate = "auc-fetcher-query"
-	cfg.StoredRequests.Postgres.CacheInitialization.Query = "auc-cache-init-query"
-	cfg.StoredRequests.Postgres.PollUpdates.Query = "auc-poll-query"
-	cfg.StoredRequests.HTTP.Endpoint = "auc-http-fetcher-endpoint"
-	cfg.StoredRequests.HTTPEvents.Endpoint = "auc-http-events-endpoint"
-
-	auc, amp := resolvedStoredRequestsConfig(cfg)
-
-	// Auction slim should have the non-amp values in it
-	assertStringsEqual(t, auc.Postgres.FetcherQueries.QueryTemplate, cfg.StoredRequests.Postgres.FetcherQueries.QueryTemplate)
-	assertStringsEqual(t, auc.Postgres.CacheInitialization.Query, cfg.StoredRequests.Postgres.CacheInitialization.Query)
-	assertStringsEqual(t, auc.Postgres.PollUpdates.Query, cfg.StoredRequests.Postgres.PollUpdates.Query)
-	assertStringsEqual(t, auc.HTTP.Endpoint, cfg.StoredRequests.HTTP.Endpoint)
-	assertStringsEqual(t, auc.HTTPEvents.Endpoint, cfg.StoredRequests.HTTPEvents.Endpoint)
-	assertStringsEqual(t, auc.CacheEvents.Endpoint, "/storedrequests/openrtb2")
-
-	// Amp slim should have the amp values in it
-	assertStringsEqual(t, amp.Postgres.FetcherQueries.QueryTemplate, cfg.StoredRequests.Postgres.FetcherQueries.AmpQueryTemplate)
-	assertStringsEqual(t, amp.Postgres.CacheInitialization.Query, cfg.StoredRequests.Postgres.CacheInitialization.AmpQuery)
-	assertStringsEqual(t, amp.Postgres.PollUpdates.Query, cfg.StoredRequests.Postgres.PollUpdates.AmpQuery)
-	assertStringsEqual(t, amp.HTTP.Endpoint, cfg.StoredRequests.HTTP.AmpEndpoint)
-	assertStringsEqual(t, amp.HTTPEvents.Endpoint, cfg.StoredRequests.HTTPEvents.AmpEndpoint)
-	assertStringsEqual(t, amp.CacheEvents.Endpoint, "/storedrequests/amp")
-}
-
 func TestNewHTTPEvents(t *testing.T) {
 	handler := func(w http.ResponseWriter, r *http.Request) {
 		w.WriteHeader(http.StatusInternalServerError)
 	}
 	server1 := httptest.NewServer(http.HandlerFunc(handler))
 
-	cfg := &config.StoredRequestsSlim{
-		HTTPEvents: config.HTTPEventsConfigSlim{
+	cfg := &config.StoredRequests{
+		HTTPEvents: config.HTTPEventsConfig{
 			Endpoint:    server1.URL,
 			RefreshRate: 100,
 			Timeout:     1000,
@@ -165,7 +101,7 @@ func TestNewHTTPEvents(t *testing.T) {
 }
 
 func TestNewEmptyCache(t *testing.T) {
-	cache := newCache(&config.StoredRequestsSlim{InMemoryCache: config.InMemoryCache{Type: "none"}})
+	cache := newCache(&config.StoredRequests{InMemoryCache: config.InMemoryCache{Type: "none"}})
 	cache.Save(context.Background(), map[string]json.RawMessage{"foo": json.RawMessage("true")}, nil)
 	reqs, _ := cache.Get(context.Background(), []string{"foo"}, nil)
 	if len(reqs) != 0 {
@@ -174,7 +110,7 @@ func TestNewEmptyCache(t *testing.T) {
 }
 
 func TestNewInMemoryCache(t *testing.T) {
-	cache := newCache(&config.StoredRequestsSlim{
+	cache := newCache(&config.StoredRequests{
 		InMemoryCache: config.InMemoryCache{
 			TTL:              60,
 			RequestCacheSize: 100,
@@ -189,26 +125,26 @@ func TestNewInMemoryCache(t *testing.T) {
 }
 
 func TestNewPostgresEventProducers(t *testing.T) {
-	cfg := &config.StoredRequestsSlim{
-		Postgres: config.PostgresConfigSlim{
-			CacheInitialization: config.PostgresCacheInitializerSlim{
+	cfg := &config.StoredRequests{
+		Postgres: config.PostgresConfig{
+			CacheInitialization: config.PostgresCacheInitializer{
 				Timeout: 50,
 				Query:   "SELECT id, requestData, type FROM stored_data",
 			},
-			PollUpdates: config.PostgresUpdatePollingSlim{
+			PollUpdates: config.PostgresUpdatePolling{
 				RefreshRate: 20,
 				Timeout:     50,
 				Query:       "SELECT id, requestData, type FROM stored_data WHERE last_updated > $1",
 			},
 		},
 	}
-	ampCfg := &config.StoredRequestsSlim{
-		Postgres: config.PostgresConfigSlim{
-			CacheInitialization: config.PostgresCacheInitializerSlim{
+	ampCfg := &config.StoredRequests{
+		Postgres: config.PostgresConfig{
+			CacheInitialization: config.PostgresCacheInitializer{
 				Timeout: 50,
 				Query:   "SELECT id, requestData, type FROM stored_amp_data",
 			},
-			PollUpdates: config.PostgresUpdatePollingSlim{
+			PollUpdates: config.PostgresUpdatePolling{
 				RefreshRate: 20,
 				Timeout:     50,
 				Query:       "SELECT id, requestData, type FROM stored_amp_data WHERE last_updated > $1",
diff --git a/stored_requests/data/by_id/accounts/test.json b/stored_requests/data/by_id/accounts/test.json
new file mode 100644
index 00000000..76bafff7
--- /dev/null
+++ b/stored_requests/data/by_id/accounts/test.json
@@ -0,0 +1,14 @@
+{
+    "id": "test",
+    "name": "test account",
+    "disabled": true,
+    "cache_ttl": {
+        "banner": 600,
+        "video": 3600,
+        "native": 3600,
+        "audio": 3600
+    },
+    "events": {
+        "enabled": true
+    }
+}
diff --git a/stored_requests/fetcher_test.go b/stored_requests/fetcher_test.go
index c1040acd..1928d116 100644
--- a/stored_requests/fetcher_test.go
+++ b/stored_requests/fetcher_test.go
@@ -215,6 +215,11 @@ func (f *mockFetcher) FetchRequests(ctx context.Context, requestIDs []string, im
 	return args.Get(0).(map[string]json.RawMessage), args.Get(1).(map[string]json.RawMessage), args.Get(2).([]error)
 }
 
+func (a *mockFetcher) FetchAccount(ctx context.Context, accountID string) (json.RawMessage, []error) {
+	args := a.Called(ctx, accountID)
+	return args.Get(0).(json.RawMessage), args.Get(1).([]error)
+}
+
 func (f *mockFetcher) FetchCategories(ctx context.Context, primaryAdServer, publisherId, iabCategory string) (string, error) {
 	return "", nil
 }
diff --git a/stored_requests/multifetcher_test.go b/stored_requests/multifetcher_test.go
index e703c2c9..5035cfba 100644
--- a/stored_requests/multifetcher_test.go
+++ b/stored_requests/multifetcher_test.go
@@ -125,3 +125,54 @@ func TestOtherError(t *testing.T) {
 	assert.JSONEq(t, `{"req_id": "def"}`, string(reqData["def"]), "MultiFetcher should return the right request data")
 	assert.JSONEq(t, `{"imp_id": "imp-1"}`, string(impData["imp-1"]), "MultiFetcher should return the right imp data")
 }
+
+func TestMultiFetcherAccountFoundInFirstFetcher(t *testing.T) {
+	f1 := &mockFetcher{}
+	f2 := &mockFetcher{}
+	fetcher := &MultiFetcher{f1, f2}
+	ctx := context.Background()
+
+	f1.On("FetchAccount", ctx, "ONE").Once().Return(json.RawMessage(`{"id": "ONE"}`), []error{})
+
+	account, errs := fetcher.FetchAccount(ctx, "ONE")
+
+	f1.AssertExpectations(t)
+	f2.AssertNotCalled(t, "FetchAccount")
+	assert.Empty(t, errs)
+	assert.JSONEq(t, `{"id": "ONE"}`, string(account))
+}
+
+func TestMultiFetcherAccountFoundInSecondFetcher(t *testing.T) {
+	f1 := &mockFetcher{}
+	f2 := &mockFetcher{}
+	fetcher := &MultiFetcher{f1, f2}
+	ctx := context.Background()
+
+	f1.On("FetchAccount", ctx, "TWO").Once().Return(json.RawMessage(``), []error{NotFoundError{"TWO", "Account"}})
+	f2.On("FetchAccount", ctx, "TWO").Once().Return(json.RawMessage(`{"id": "TWO"}`), []error{})
+
+	account, errs := fetcher.FetchAccount(ctx, "TWO")
+
+	f1.AssertExpectations(t)
+	f2.AssertExpectations(t)
+	assert.Empty(t, errs)
+	assert.JSONEq(t, `{"id": "TWO"}`, string(account))
+}
+
+func TestMultiFetcherAccountNotFound(t *testing.T) {
+	f1 := &mockFetcher{}
+	f2 := &mockFetcher{}
+	fetcher := &MultiFetcher{f1, f2}
+	ctx := context.Background()
+
+	f1.On("FetchAccount", ctx, "MISSING").Once().Return(json.RawMessage(``), []error{NotFoundError{"TWO", "Account"}})
+	f2.On("FetchAccount", ctx, "MISSING").Once().Return(json.RawMessage(``), []error{NotFoundError{"TWO", "Account"}})
+
+	account, errs := fetcher.FetchAccount(ctx, "MISSING")
+
+	f1.AssertExpectations(t)
+	f2.AssertExpectations(t)
+	assert.Len(t, errs, 1)
+	assert.Nil(t, account)
+	assert.EqualError(t, errs[0], NotFoundError{"MISSING", "Account"}.Error())
+}