Description
TSJS injects a synthetic trustedServer bidder into every ad unit so providers configured with routing = "all_eligible", such as APS, can participate even when the publisher did not configure inline server-side bidders for that slot.
When such a slot has no plan-owned bidder parameters, TSJS sends this envelope:
The auction router currently treats missing, null, and empty bidderParams as intentional Prebid Server stored-request demand. It routes the slot to every configured prebid-server provider and builds:
{
"ext": {
"prebid": {
"storedrequest": {
"id": "<dynamic-slot-code>"
}
}
}
}
For a dynamic slot with no matching stored impression, PBS returns HTTP 400. That rejects the provider's complete OpenRTB request, including otherwise valid impressions with inline bidder parameters. Multiple PBS provider instances repeat the same failing stored-request lookup. Other all_eligible providers can still succeed independently.
An empty bidder map is ambiguous. It can mean either "invoke all-eligible providers with no PBS demand" or "load a PBS stored impression." The router should not infer both cases from the same shape.
This was observed while validating #1016, but it is unrelated to per-provider OpenRTB test: 1 behavior.
Suggested fix
Add explicit stored-request intent to the trustedServer envelope, for example:
{
"bidderParams": {},
"storedRequest": false
}
TSJS-generated envelopes used only to invoke all-eligible providers should set storedRequest: false. Routing should then skip every prebid-server provider that has neither routed inline parameters nor stored-request intent, while still admitting eligible non-PBS all_eligible providers such as APS. A PBS provider configured as all_eligible still has no useful request to make when both inline and stored demand are absent.
Intentional PBS stored demand should use storedRequest: true. Omitted values can preserve the legacy stored-request behavior during migration if compatibility requires it. Direct server-generated opportunities should continue to use trusted server-owned routing rather than allowing the browser to select a provider.
Wire compatibility and internal model
Keep storedRequest optional on the wire so the server can distinguish all three states:
| Wire value |
Meaning |
false |
No stored demand. Skip PBS providers without routed inline parameters. |
true |
Explicit stored-request intent. |
| omitted |
Legacy inference during migration. |
Represent this internally as an enum such as Disabled, Explicit, and Legacy, rather than collapsing omitted and false into a Rust boolean. Deploy server support before TSJS starts emitting storedRequest: false; an older server will otherwise continue treating the empty envelope as stored demand.
Stored request identity
The minimal boolean fix can continue deriving the stored impression ID from the slot code. A later design should consider an explicit shape such as storedRequest: { "id": "homepage-header" } or storedRequestId: "homepage-header", because dynamic display codes are poor stored-request identifiers. Multiple PBS provider instances also need server-owned provider selection for intentional stored demand instead of automatic fanout. Those changes do not need to block the minimal ambiguity fix.
Regression coverage should include:
- an APS all-eligible slot with no PBS bidder parameters, which reaches APS and skips every PBS provider without demand;
- explicit stored-request intent, which retains the existing PBS path;
- inline bidders split across multiple PBS provider instances;
- one no-PBS slot alongside valid inline PBS impressions, proving it cannot cause a provider-wide HTTP 400;
storedRequest: false with empty bidder parameter objects;
storedRequest: false with only unconfigured bidders after route filtering;
- a malformed non-boolean
storedRequest, which must fail validation atomically and never trigger legacy stored demand;
- an omitted value from a legacy direct
/auction caller, which preserves the existing stored-request path;
- TSJS serialization of
storedRequest: false for an empty all-eligible envelope; and
- a
prebid-server provider configured as all_eligible, which is still skipped when the slot has neither inline nor stored demand.
Description
TSJS injects a synthetic
trustedServerbidder into every ad unit so providers configured withrouting = "all_eligible", such as APS, can participate even when the publisher did not configure inline server-side bidders for that slot.When such a slot has no plan-owned bidder parameters, TSJS sends this envelope:
{ "bidderParams": {} }The auction router currently treats missing, null, and empty
bidderParamsas intentional Prebid Server stored-request demand. It routes the slot to every configuredprebid-serverprovider and builds:{ "ext": { "prebid": { "storedrequest": { "id": "<dynamic-slot-code>" } } } }For a dynamic slot with no matching stored impression, PBS returns HTTP 400. That rejects the provider's complete OpenRTB request, including otherwise valid impressions with inline bidder parameters. Multiple PBS provider instances repeat the same failing stored-request lookup. Other
all_eligibleproviders can still succeed independently.An empty bidder map is ambiguous. It can mean either "invoke all-eligible providers with no PBS demand" or "load a PBS stored impression." The router should not infer both cases from the same shape.
This was observed while validating #1016, but it is unrelated to per-provider OpenRTB
test: 1behavior.Suggested fix
Add explicit stored-request intent to the
trustedServerenvelope, for example:{ "bidderParams": {}, "storedRequest": false }TSJS-generated envelopes used only to invoke all-eligible providers should set
storedRequest: false. Routing should then skip everyprebid-serverprovider that has neither routed inline parameters nor stored-request intent, while still admitting eligible non-PBSall_eligibleproviders such as APS. A PBS provider configured asall_eligiblestill has no useful request to make when both inline and stored demand are absent.Intentional PBS stored demand should use
storedRequest: true. Omitted values can preserve the legacy stored-request behavior during migration if compatibility requires it. Direct server-generated opportunities should continue to use trusted server-owned routing rather than allowing the browser to select a provider.Wire compatibility and internal model
Keep
storedRequestoptional on the wire so the server can distinguish all three states:falsetrueRepresent this internally as an enum such as
Disabled,Explicit, andLegacy, rather than collapsing omitted andfalseinto a Rust boolean. Deploy server support before TSJS starts emittingstoredRequest: false; an older server will otherwise continue treating the empty envelope as stored demand.Stored request identity
The minimal boolean fix can continue deriving the stored impression ID from the slot code. A later design should consider an explicit shape such as
storedRequest: { "id": "homepage-header" }orstoredRequestId: "homepage-header", because dynamic display codes are poor stored-request identifiers. Multiple PBS provider instances also need server-owned provider selection for intentional stored demand instead of automatic fanout. Those changes do not need to block the minimal ambiguity fix.Regression coverage should include:
storedRequest: falsewith empty bidder parameter objects;storedRequest: falsewith only unconfigured bidders after route filtering;storedRequest, which must fail validation atomically and never trigger legacy stored demand;/auctioncaller, which preserves the existing stored-request path;storedRequest: falsefor an empty all-eligible envelope; andprebid-serverprovider configured asall_eligible, which is still skipped when the slot has neither inline nor stored demand.