Skip to content

Auction Orchestration

Learn how Trusted Server coordinates multiple demand sources in parallel to maximize revenue and minimize latency.

Overview

The auction orchestrator is the core system that manages server-side ad auctions. It launches bid requests to multiple demand providers simultaneously, collects responses, and selects winners.

Key capabilities:

  • Parallel execution — Bid requests to all providers launch concurrently using Fastly's select() API
  • Strategy-based winner selection — Automatic strategy detection based on configuration
  • Mediator support — Optional external mediator for final winner selection and unified floor pricing
  • Provider abstraction — Pluggable provider interface for adding new demand sources
  • Creative processing — Winning creatives are rewritten to first-party proxy URLs by default, with opt-in sanitization

System Flow (Prebid + APS)

The following diagram shows the full auction flow when both Prebid and APS providers are configured with a mediator:

Architecture

Request Flow

The auction system processes requests through a pipeline of transformations:

POST /auction (AdRequest in Prebid.js format)

  ├─ Parse body → AdRequest { adUnits[] }
  ├─ Generate EC + fresh user IDs
  ├─ Convert adUnits → AdSlots with formats and bidder params
  ├─ Extract device info (User-Agent, geo)


AuctionOrchestrator.run_auction()

  ├─ Detect strategy (parallel_only or parallel_mediation)
  ├─ Launch all providers in parallel via select()
  ├─ Collect responses as they complete

  ├─[parallel_only]─── Select highest decoded CPM per slot
  └─[parallel_mediation]─── Forward decoded-price bids to mediator for final selection


Convert OrchestrationResult → OpenRTB 2.x Response

  ├─[sanitize_creatives=true] Strip executable markup
  ├─[rewrite_creatives=true] Rewrite URLs and inject creative TSJS
  ├─ Add ext.orchestrator metadata
  └─ Set consent and optional EID response headers

Key Components

The orchestrator is composed of several modules:

ModulePathPurpose
orchestrator.rscrates/trusted-server-core/src/auction/Core parallel execution and bid selection
provider.rscrates/trusted-server-core/src/auction/AuctionProvider trait definition
types.rscrates/trusted-server-core/src/auction/Data structures (AuctionRequest, Bid, etc.)
formats.rscrates/trusted-server-core/src/auction/Format conversions (TSJS ↔ OpenRTB)
endpoints.rscrates/trusted-server-core/src/auction/HTTP handler for POST /auction
config.rscrates/trusted-server-core/src/auction/Auction configuration types

Provider Auto-Discovery

Providers register themselves at startup via builder functions. The build_orchestrator() function in auction/mod.rs iterates all registered builders, passes the application settings, and each builder returns zero or more providers depending on whether its config section is present and enabled:

rust
// Each integration registers its own builder
fn provider_builders() -> &'static [ProviderBuilder] {
    &[
        prebid::register_auction_provider,
        aps::register_providers,
        adserver_mock::register_providers,
    ]
}

This means you only need to add a config section to trusted-server.toml for a provider to be automatically discovered and registered.

Auction Strategies

The orchestrator automatically selects a strategy based on whether a mediator is configured.

Parallel Only

When no mediator is set, the orchestrator runs all providers in parallel and selects winners by comparing decoded prices directly. This is the simplest strategy.

toml
[auction]
enabled = true
providers = ["prebid", "aps"]
# No mediator — direct price comparison
timeout_ms = 2000

How winner selection works:

  1. Collect bids from all providers.
  2. Group bids by slot ID.
  3. Skip bids without a decoded numeric price.
  4. Select the highest CPM for each slot.
  5. Apply floor prices and drop winners below the slot's floor.

APS OpenRTB supplies decoded prices, so eligible APS bids participate directly without requiring a mediator.

Parallel Mediation

When a mediator is configured, provider responses are forwarded to the mediator service for final winner selection and unified floor pricing.

toml
[auction]
enabled = true
providers = ["prebid", "aps"]
mediator = "adserver_mock"  # Enables mediation
timeout_ms = 2000

How mediation works:

  1. Run all providers in parallel (same as parallel_only).
  2. Collect all responses.
  3. Forward bids with decoded numeric prices to the mediator.
  4. Let the mediator apply policy and choose a winner.
  5. Restore render/accounting state from the selected source bid.
  6. Filter any mediator winner without a decoded price.

Mediation is optional for APS. APS reduces to one candidate per impression before mediation so the selected renderer can be restored without same-slot ambiguity.

Providers

Provider Interface

All demand sources implement the AuctionProvider trait:

rust
pub trait AuctionProvider: Send + Sync {
    fn provider_name(&self) -> &'static str;

    fn request_bids(
        &self,
        request: &AuctionRequest,
        context: &AuctionContext<'_>,
    ) -> Result<PendingRequest, Report<TrustedServerError>>;

    fn parse_response(
        &self,
        response: fastly::Response,
        response_time_ms: u64,
    ) -> Result<AuctionResponse, Report<TrustedServerError>>;

    fn supports_media_type(&self, media_type: &MediaType) -> bool;
    fn timeout_ms(&self) -> u32;
    fn is_enabled(&self) -> bool;
    fn backend_name(&self) -> Option<String>;
}

The trait uses a two-phase design:

  1. request_bids() — Builds and sends the HTTP request, returning a PendingRequest (Fastly's async handle)
  2. parse_response() — Called once the response arrives, parses the provider-specific format into a unified AuctionResponse

This split enables true parallel execution: all requests launch first, then the orchestrator uses select() to process responses as they arrive.

Prebid Provider

Transforms auction requests into OpenRTB 2.x format and sends them to a Prebid Server instance.

Request transformation:

  • AdSlotImp with Banner { format: [Format { w, h }] }
  • Bidder params from slot config → ext.prebid.bidder map
  • EC and fresh user IDs injected into User object
  • Device info, geo data, and GPC signals included
  • Optional Ed25519 request signing (see Request Signing)

Response parsing:

  • Bids include decoded price (clear decimal CPM)
  • Creative HTML provided in adm field
  • Winning creative URLs rewritten to first-party proxy format by default when the /auction response is assembled
  • Per-bidder timing (responsetimemillis), errors, and warnings always attached as response metadata
  • When debug is enabled, PBS debug payload and per-bid status (bidstatus) also included
toml
[integrations.prebid]
enabled = true
server_url = "https://prebid-server.example.com"
timeout_ms = 1000
bidders = ["appnexus", "rubicon"]

APS Provider

Builds an independent banner OpenRTB request for Amazon Publisher Services.

Request transformation:

  • banner AdSlot formats become secure OpenRTB impressions;
  • ext.account uses canonical account_id;
  • ext.sdk identifies the compatible Prebid contract; and
  • existing page, device, consent, identity, and geo privacy gates are preserved.

Response parsing:

  • decoded USD prices compete directly with other providers;
  • positive compatible dimensions and an HTTPS creativeurl are required;
  • script creatives are rejected before winner selection unless explicitly enabled;
  • one candidate per impression is retained deterministically; and
  • a minimized typed renderer is preserved instead of creative markup or APS notifications.
toml
[integrations.aps]
enabled = true
account_id = "example-account"
timeout_ms = 800
debug = false
allow_script_creatives = false

See APS OpenRTB Integration for rollout and rendering requirements.

AdServer Mock Mediator

An external mediation service that receives decoded-price bidder responses and performs final winner selection. APS prices are already decoded at the provider boundary.

Mediation request format:

json
{
  "id": "auction-123",
  "imp": [
    { "id": "header-banner", "banner": { "format": [{ "w": 728, "h": 90 }] } }
  ],
  "ext": {
    "bidder_responses": [
      {
        "bidder": "aps",
        "bids": [{ "imp_id": "header-banner", "price": 2.5, "adm": null }]
      },
      {
        "bidder": "prebid",
        "bids": [
          { "imp_id": "header-banner", "price": 2.0, "adm": "<html>..." }
        ]
      }
    ],
    "config": { "price_floor": 0.5 }
  }
}

Mediation response: Standard OpenRTB with decoded prices and selected winners.

toml
[integrations.adserver_mock]
enabled = true
endpoint = "https://your-mediator.example.com/adserver/mediate"
timeout_ms = 500
price_floor = 0.50

Data Structures

AuctionRequest

The internal representation of an auction, converted from the incoming AdRequest:

rust
pub struct AuctionRequest {
    pub id: String,                                    // UUID
    pub slots: Vec<AdSlot>,                            // Ad placements
    pub publisher: PublisherInfo,                       // Domain, page URL
    pub user: UserInfo,                                // EC ID, fresh ID, consent
    pub device: Option<DeviceInfo>,                    // UA, IP, geo
    pub site: Option<SiteInfo>,                        // Domain, page
    pub context: HashMap<String, serde_json::Value>,   // Additional metadata
}

AdSlot

Represents a single ad placement on the page:

rust
pub struct AdSlot {
    pub id: String,
    pub formats: Vec<AdFormat>,                         // Supported sizes
    pub floor_price: Option<f64>,                       // Minimum CPM
    pub targeting: HashMap<String, serde_json::Value>,  // Key-value targeting
    pub bidders: HashMap<String, serde_json::Value>,    // Per-bidder params
}

Bid

The unified bid format used across all providers:

rust
pub struct Bid {
    pub slot_id: String,
    pub price: Option<f64>,           // Missing prices fail closed
    pub currency: String,
    pub creative: Option<String>,     // APS uses renderer instead of markup
    pub adomain: Option<Vec<String>>,
    pub bidder: String,
    pub width: u32,
    pub height: u32,
    pub nurl: Option<String>,         // Win notification URL
    pub burl: Option<String>,         // Billing URL
    pub renderer: Option<BidRenderer>,
    pub metadata: HashMap<String, serde_json::Value>,
}

The price field remains optional so missing-price bids fail closed. APS supplies a decoded price and a typed renderer instead of creative HTML; the renderer is retained through direct winner selection and mediation.

OrchestrationResult

The complete result of an auction:

rust
pub struct OrchestrationResult {
    pub provider_responses: Vec<AuctionResponse>,       // All provider results
    pub mediator_response: Option<AuctionResponse>,     // Mediator result (if used)
    pub winning_bids: HashMap<String, Bid>,             // Slot ID → winning bid
    pub total_time_ms: u64,
    pub metadata: HashMap<String, serde_json::Value>,
}

Input and Output Formats

Request Format (TSJS / Prebid.js)

The POST /auction endpoint accepts a Prebid.js-compatible AdRequest:

json
{
  "adUnits": [
    {
      "code": "header-banner",
      "mediaTypes": {
        "banner": {
          "sizes": [
            [728, 90],
            [970, 250]
          ]
        }
      },
      "bids": [
        {
          "bidder": "appnexus",
          "params": { "placementId": 12345 }
        }
      ]
    }
  ]
}

Response Format (OpenRTB 2.x)

Auction results are returned in standard OpenRTB format with an ext.orchestrator metadata block:

json
{
  "id": "auction-abc123",
  "seatbid": [
    {
      "seat": "prebid",
      "bid": [
        {
          "id": "bid-1",
          "impid": "header-banner",
          "price": 2.5,
          "adm": "<iframe src=\"/first-party/proxy?tsurl=...&tstoken=sig\">...</iframe>",
          "w": 728,
          "h": 90
        }
      ]
    }
  ],
  "ext": {
    "orchestrator": {
      "strategy": "parallel_mediation",
      "providers": 2,
      "total_bids": 3,
      "time_ms": 145
    }
  }
}

APS renderer winners use the same OpenRTB response with a typed renderer extension instead of adm:

json
{
  "id": "auction-abc123",
  "seatbid": [
    {
      "seat": "aps",
      "bid": [
        {
          "id": "upstream-aps-bid-id",
          "impid": "header-banner",
          "price": 2.5,
          "w": 728,
          "h": 90,
          "ext": {
            "trusted_server": {
              "renderer": {
                "type": "aps",
                "version": 1,
                "accountId": "example-account",
                "bidId": "upstream-aps-bid-id",
                "tagType": "iframe",
                "creativeUrl": "https://creative.example/render",
                "aaxResponse": "fictional-base64-envelope",
                "width": 728,
                "height": 90
              }
            }
          }
        }
      ]
    }
  ]
}

For these bids, id preserves APS's upstream bid ID, crid is present only when APS supplies one, and adm is absent. TSJS understands this contract; other /auction consumers must render ext.trusted_server.renderer explicitly.

EC identity is maintained with the ts-ec cookie; auction responses do not emit EC ID headers.

Creative Processing

Winning creatives returned by POST /auction pass through two independent transforms. sanitize_creatives (opt-in, default false) strips executable markup with its inner content. rewrite_creatives (default true) runs an HTML rewriter (lol_html) that converts eligible external resource and click URLs to signed first-party paths, adds data-tsclick, rewrites inline CSS url(...) values, removes bidder-supplied <base> elements, and injects the unified creative TSJS runtime exactly once, whether or not the bidder supplied a <body> element. In every mode, a creative larger than the 1 MiB per-creative cap is rejected and its adm is dropped.

toml
[auction]
sanitize_creatives = false
rewrite_creatives = true
sanitize_creativesrewrite_creativesWinning-bid adm behavior
false (default)falseDeliver the creative exactly as the bidder returned it (subject to the size cap).
truefalseStrip executable markup, then deliver without rewriting. Accepted asset and click URLs remain direct.
falsetrue (default)Rewrite eligible URLs, add click-guard attributes, and inject creative TSJS into the raw bidder markup. Executable markup is preserved.
truetrueSanitize first, then rewrite eligible URLs, add click-guard attributes, and inject creative TSJS.

When sanitization is enabled, scripts, stylesheets, style blocks, forms, event handlers, dangerous URL schemes, and other rejected content are removed together with their inner content — which blanks script-based creatives. Disabling rewriting removes the injected creative runtime and first-party proxy/click mediation from the resulting adm, so the browser may contact third-party hosts without mediation. Sanitizer-accepted hosts are not allowlisted or trusted merely because their URLs remain in the output.

Both settings apply to winning-bid adm in both the shared POST /auction response converter and the production publisher SSAT/page-bids path. The former emits root-relative first-party URLs and injects creative TSJS; the latter emits absolute first-party URLs for its foreign-origin renderer and does not inject that bundle. HTML/CSS returned by /first-party/proxy continues to be rewritten independently. [debug].inject_adm_for_testing adds the diagnostic debug_bid blob and enables a testing-only direct GAM replacement; it does not control whether processed adm is delivered.

Elements handled by the rewrite pass:

ElementAttributesTarget
<img>src, data-src, srcset/first-party/proxy?tsurl=...
<script>src/first-party/proxy?tsurl=...
<link>href, imagesrcset/first-party/proxy?tsurl=...
<iframe>src/first-party/proxy?tsurl=...
<video>, <audio>, <source>src/first-party/proxy?tsurl=...
<a>, <area>href/first-party/click?tsurl=...
<style>, [style]url() references/first-party/proxy?tsurl=...
SVG <image>, <use>href, xlink:href/first-party/proxy?tsurl=...

The rewrite pass leaves relative URLs and non-network schemes unchanged. When sanitize_creatives is also enabled, sanitization runs first and strips dangerous schemes, so only sanitizer-accepted values reach this pass; with sanitization disabled, the rewriter operates on the raw bidder markup. Domains in the rewrite.exclude_domains config list (supports wildcards like *.cdn.example.com) are also skipped.

Each proxied URL includes a tstoken HMAC signature for tamper protection. See Proxy Signing for details.

Configuration

Full Example

toml
[auction]
enabled = true
sanitize_creatives = false     # Opt-in; blanks script-based creatives when enabled
rewrite_creatives = true
providers = ["prebid", "aps"]
mediator = "adserver_mock"    # Remove for parallel_only strategy
timeout_ms = 2000

[integrations.prebid]
enabled = true
server_url = "https://prebid-server.example.com"
timeout_ms = 1000
bidders = ["appnexus", "rubicon"]
auto_configure = true
debug = false

[integrations.aps]
enabled = true
account_id = "example-account"
timeout_ms = 800
debug = false
allow_script_creatives = false

[integrations.adserver_mock]
enabled = true
endpoint = "https://your-mediator.example.com/adserver/mediate"
timeout_ms = 500
price_floor = 0.50

Configuration Reference

[auction]

FieldTypeDefaultDescription
enabledboolfalseEnable the auction system
sanitize_creativesboolfalseStrip executable markup from winning-bid adm before delivery
rewrite_creativesbooltrueRewrite winning-bid adm through first-party endpoints
providersstring[][]Ordered list of provider names to call
mediatorstring?nullProvider name to use as mediator (enables parallel_mediation)
timeout_msu322000Overall auction timeout in milliseconds

Both creative-processing fields must be present in the TOML for their environment overrides to apply; see Environment Variable Overrides.

[integrations.prebid]

FieldTypeDefaultDescription
enabledbooltrueEnable Prebid provider
server_urlstringPrebid Server URL (required)
timeout_msu321000Request timeout
biddersstring[]["mocktioneer"]Default bidders when not specified per-slot
auto_configurebooltrueAuto-remove client-side prebid.js scripts
debugboolfalseEnable Prebid debug mode (sets ext.prebid.debug and ext.prebid.returnallbidstatus)
test_modeboolfalseSet OpenRTB test: 1 for non-billable test traffic

[integrations.aps]

FieldTypeDefaultDescription
enabledboolfalseEnable APS provider
account_idstringAPS account ID (required; pub_id is an alias)
endpointstringBuilt-in APS OpenRTB endpointOptional APS OpenRTB endpoint override
timeout_msu32800Request timeout
debugboolfalseInclude the raw APS HTTP exchange in /auction provider metadata
inventory_domainstringOverride site.domain for APS-authorized inventory
inventory_page_originstringHTTPS origin paired with inventory_domain for site.page
allow_script_creativesboolfalseAdmit script bids before APS candidate reduction

[integrations.adserver_mock]

FieldTypeDefaultDescription
enabledboolfalseEnable mediator
endpointstringhttp://localhost:6767/adserver/mediateMediator service endpoint
timeout_msu32500Request timeout
price_floorf64?nullGlobal price floor CPM

Timeout Tuning

The orchestrator timeout should exceed the sum of provider timeouts to allow all providers to respond. Providers that exceed their individual timeouts are collected as they finish — the orchestrator doesn't wait indefinitely.

toml
[auction]
timeout_ms = 2000              # Overall ceiling

[integrations.prebid]
timeout_ms = 1000              # Prebid Server budget

[integrations.aps]
timeout_ms = 800               # APS budget

[integrations.adserver_mock]
timeout_ms = 500               # Mediator budget (called after providers)

Environment Variable Overrides

The typed ts config validate, ts config diff, and ts config push flows can override auction values that already exist in the TOML. EdgeZero v0.0.4 does not create missing leaves, so existing configs must add bothrewrite_creatives = true and sanitize_creatives = false under [auction] before relying on the corresponding environment overrides — an override for a missing leaf is silently ignored.

bash
TRUSTED_SERVER__AUCTION__ENABLED=true
TRUSTED_SERVER__AUCTION__REWRITE_CREATIVES=true
TRUSTED_SERVER__AUCTION__SANITIZE_CREATIVES=false
TRUSTED_SERVER__AUCTION__PROVIDERS=prebid,aps
TRUSTED_SERVER__AUCTION__MEDIATOR=adserver_mock
TRUSTED_SERVER__AUCTION__TIMEOUT_MS=2000
TRUSTED_SERVER__INTEGRATIONS__PREBID__SERVER_URL=https://pbs.example.com
TRUSTED_SERVER__INTEGRATIONS__APS__ACCOUNT_ID=example-account
TRUSTED_SERVER__INTEGRATIONS__APS__DEBUG=false

Before rolling back to a binary that does not know a creative-processing field, remove that field's non-default value (rewrite_creatives = false or sanitize_creatives = true), push the default-compatible blob, and then roll back. See Configuration for the complete migration, upgrade-sequencing, and rollback guidance.

Floor Prices

Floor prices can be set per-slot in the auction request. The orchestrator enforces floors after winner selection:

  • In parallel_only mode: bids below the floor are dropped after selection
  • In parallel_mediation mode: the floor is sent to the mediator in ext.config.price_floor, and also enforced locally as a safety net
  • Bids without a decoded numeric price are dropped before delivery in both strategies

Error Handling

The orchestrator is designed to be resilient:

  • Provider launch failure — If a provider fails to launch its request (e.g., missing backend), it is skipped with a warning. Other providers continue.
  • Provider parse failure — If a response can't be parsed, an AuctionResponse::error() is recorded. Other results are unaffected.
  • No providers configured — Returns an error: "No providers configured"
  • All providers fail — Returns an empty OrchestrationResult with zero winning bids
  • Mediator returns bids without decoded prices — Those bids are filtered out with a warning

Observability

Logging

The auction system logs at multiple levels throughout execution:

LevelExamples
infoAuction request received, provider launch, bid counts, winner selection, total timing
debugBid-drop reasons, mediation restoration notes, creative processing mode and byte counts
warnProvider launch failures, parse failures, mediator bids without decoded prices

Response Metadata

Every auction response includes structured metadata in ext.orchestrator:

json
{
  "strategy": "parallel_mediation",
  "providers": 2,
  "total_bids": 3,
  "time_ms": 145
}

Released under the Apache License 2.0.