Implementing bid shading

Integration Partners
Last Updated: August 13, 2026

Before you begin: Your container should already handle the DSPClosed Demand-Side Platform (DSP). A software platform that automates bidding decisions in real-time and efficiently connects buyers and audiences through an ad exchange or SSP. Also known as a buy-side platform. Bid ResponseClosed An OpenRTB response that is sent by the DSP in response to the SSP's or ad exchange's bid request. It is an event directed back to the seller expressing a valuation for the request and conditions of sale. extension point in general. See Build a gRPC RTD service.

Bid shading lets your container recommend a lower transaction price for a DSP bid before Index Exchange (Index) selects a winner, reducing buyer cost without reducing win rates. This topic is the single home for everything specific to bid shading: the response structure, the business rules, and how to build and test your handler. If you already have a container set up for the DSP Bid Response extension point in general, this topic is all you need to add bid shading to it.

Where bid shading is supported

Bid shading is available under the following conditions:

DimensionSupport

Protocol

gRPC only. Not supported on the HTTP integration.

Extension point

DSP Bid Response extension point only (LIFECYCLE_DSP_BID_RESPONSE). Not available at the PublisherClosed The owner of a website or app where advertisements are served. Request extension point.

Hosting model

Partner-hosted and Index Cloud.

Mutation

BID_SHADE, path /seatbid/<seatID>/bid/<bidID>. See Mutation schema below.

Auction typeClosed The RTB auction type and can be either a first price auction (an auction where the highest bid wins, and the winner pays the highest bid amount in full) or a fixed price auction (an auction that has priority over all other auction bid types and the bid must meet or exceed the pre-determined fixed price).

First-price only. No effect in second-price auctions.

DealClosed A private auction that allows media owners to offer specific inventory directly to selected buyers identified by a deal ID. Terms are negotiated and are agreed upon before the auction occurs. ownership

Deals you own only. There is no path today to request permissioning on a deal owned by another partner.

How it fits into the auction

Bid shading operates at the DSP Bid Response extension point, after DSPs have returned their bids and before Index picks a winner. Your container evaluates each eligible bid and returns a BID_SHADE mutation recommending a lower price. If bid shading is applied, the buyer pays the shaded price instead of their original bid. To learn how to set up a container for the extension point in general, see Build a gRPC RTD service, and Building your container.

Response structure

When lifecycle is LIFECYCLE_DSP_BID_RESPONSE, the bid_response field on the RTBRequest is populated. The following fields are what you need for shading decisions:

FieldTypeDescription

price

float

The DSP's submitted gross bid priceClosed The bid price before Index fee or, if applicable, Marketplace fees. in CPMClosed Cost Per Thousand (CPM). A pricing structure for buying impressions and is the cost of serving an advertisement 1,000 times. Also known as Cost Per Mille (where M represents 1,000 in Roman numerals) or Cents Per Mille. (USD). This is the value you are shading.

dealid

string

The deal ID this bid is associated with. Bid shading operates on deal bids only.

impid

string

Links the bid back to the imp object in the bid requestClosed An OpenRTB request that is sent from a supply-side platform (SSP) or ad exchange to the DSP requesting a bid response for potential impressions. A bid request contains information about the impression that allows the DSP to decide whether to bid on the impression. for floorClosed A pricing control used by media owners and exchanges to set a minimum sale price on inventory. and context lookups.

seat

string

DSP seat identifier. Useful for partner-level or seat-level shading model segmentation.

Note: bid_request is still present and required at this extension point, even though you are acting on bid_response. For the full field list on both objects, including privacy redaction rules, see the DSP bid response payload reference.

Mutation schema

The BID_SHADE mutation uses the following schema:

FieldValue

intent

BID_SHADE

op

OPERATION_REPLACE

path

/seatbid/<seatID>/bid/<bidID>

value

AdjustBidPayload. Contains adjustBid.price (float, CPM USD).

{
   "intent": "BID_SHADE",
   "op": "OPERATION_REPLACE",
   "path": "/seatbid/123/bid/456",
   "adjustBid": { "price": 1.85 }
}

Business rules

The following rules govern how Index applies your bid shading mutations:

  • Strictly less than the original bid. The adjusted price must be greater than zero and less than the original DSP bid price. Mutations violating this constraint are silently dropped.

  • The floor still applies. You can shade below the deal or impression floor, but the bid is then blocked by the floor check rather than transacting at the shaded price. Only shade below the floor if you explicitly intend to suppress the bid; it is not recommended otherwise.

  • Deals you own only. Bid shading mutations on a deal you own are accepted by default. There is currently no path to request permissioning on a deal owned by another partner. Attempting to shade a bid on a deal you do not own results in that mutation being silently dropped.

  • One mutation per bid. You can shade multiple bids in a single response; return one mutation per bid.

  • Omit to skip. If you do not want to shade a bid, do not return a mutation for it. Omitting a bid leaves it at its original price.

  • Mutations are atomic. If one mutation fails validation, it is dropped. Other valid mutations in the response are still applied.

Handler implementation

Route on lifecycle to separate Publisher Request logic from Bid Response logic. For bid shading, only act on LIFECYCLE_DSP_BID_RESPONSE:

func (a *Agent) handleBidResponse(ctx context.Context, req *pb.RTBRequest) (*pb.RTBResponse, error) {
	var mutations []*pb.Mutation
	for _, seatBid := range req.GetBidResponse().GetSeatbid() {
		seat := seatBid.GetSeat()
		for _, bid := range seatBid.GetBid() {
			// Skip non-deal bids
			if bid.GetDealid() == "" {
				continue
			}
			// Skip if BID_SHADE isn't applicable for this request
			if !isIntentApplicable(req.GetApplicableIntents(), pb.Intent_BID_SHADE) {
				continue
			}
			price := bid.GetPrice()
			shadedPrice := a.model.Shade(bid, req.GetBidRequest())
			if shadedPrice >= price || shadedPrice <= 0 {
				continue // no shade: omit the mutation
			}
			// Path is /seatbid/<seatID>/bid/<bidID>: seat + bid ID, not imp ID.
			mutations = append(mutations, buildAdjustBidMutation(seat, bid.GetId(), shadedPrice))
		}
	}
	return &pb.RTBResponse{Id: req.Id, Mutations: mutations}, nil
}

Note: If a mutation type is returned that was not in applicable_intents, Index silently discards it. Always filter mutations against applicable_intents before building your response.

Performance considerations

The 5ms SLA applies to your entire handler. Bid shading containers typically process multiple bids per call, so per-bid latency must be kept very low.

Testing validation checklist

Note: Validate the following before requesting deployment. Required items are deployment blockers. To send test traffic to your container, see Using the Index testing tool; use the -lifecycle LIFECYCLE_DSP_BID_RESPONSE flag to generate bid shading test requests.

  • Required: Adjusted prices are always less than the original bid price and greater than zero.

  • Required: The container returns a valid RTBResponse.id echoing the request ID.

  • Required: Handler latency is consistently under 5ms at p95 on your test hardware.

  • Required: The container returns an empty mutations list, not an error, for bids it chooses not to shade.

The following example shows a request and its expected response:

// request (numeric or string enum both work with freshly generated bindings)
{ "lifecycle": "LIFECYCLE_DSP_BID_RESPONSE", "id": "req-1", "tmax": 5,
  "bid_request": { "id": "brq-1", "imp": [ { "id": "imp-1", "bidfloor": 2.00 } ] },
  "bid_response": { "seatbid": [ { "seat": "dsp-42",
     "bid": [ { "id": "bid-1", "impid": "imp-1", "dealid": "deal-x", "price": 4.00 } ] } ] } }

// expected response (shade factor 0.85 => 3.40)
{ "id": "req-1", "mutations": [ { "intent": "BID_SHADE", "op": "OPERATION_REPLACE",
     "path": "/seatbid/dsp-42/bid/bid-1", "adjustBid": { "price": 3.40 } } ] }

Reporting

Note: Bid shading performance reporting (Grafana metrics and audit log fields for training your model) is not yet available for partners. This section will be updated once that capability ships.

What's covered elsewhere

The following are not specific to bid shading and are covered where they apply to any container implementation:

TopicWhat it covers

DSP bid response payload reference

The full field list for both bid_request and bid_response at this extension point, including privacy redaction rules. This topic only lists the four fields most relevant to shading logic.

Build a gRPC RTD service

General container setup, the classification endpointClosed A URL which is configured to interact with a server in a specific way., circuit breaker behavior, and the full list of gRPC-supported mutations.

Using the Index testing tool

The testing tool for sending test traffic to your container. Use the -lifecycle LIFECYCLE_DSP_BID_RESPONSE flag to generate bid shading test requests.

Building your container and Validating and deploying your container

Packaging, deploying, and onboarding your container in Index Cloud, including what bid shading partners should confirm during scoping.

Next: If your container already handles the DSP Bid Response extension point, start with Response structure and Business rules above, then implement the handler pattern and work through the testing checklist before requesting deployment.