OpenRTB protocol

OpenRTB bid response: seatbid, bid, and win notices

A bid response is three nested layers: BidResponse echoes the request id, each SeatBid groups bids under a buyer seat, and each Bid offers a CPM price for one impid. Around that sit the notice URLs (nurl, burl, lurl) and the HTTP 204 convention for not bidding.

Top-level BidResponse

id is required and must carry the ID of the bid request this answers; it exists for logging and correlation. If the bidder wants its own tracking ID, that goes in the optional bidid, which the exchange can echo back as ${AUCTION_BID_ID} in the win notice.

  • seatbid (object array): 1+ entries required if a bid is to be made.
  • cur (string, default "USD"): bid currency as an ISO-4217 alpha code. It must be one the request permitted in cur.
  • customdata (string): base85 cookie-safe data the bidder can ask the exchange to store.
  • nbr (integer): reason for not bidding, from the No-Bid Reason Codes list kept in OpenRTB 3.0. Decoded values: OpenRTB no-bid reason codes.

SeatBid

A response can contain multiple SeatBid objects, each bidding on behalf of a different buyer seat. seat is the seat ID (advertiser or agency) coordinated with the exchange in advance, and bid is a required array of 1+ Bid objects; multiple bids may target the same impression. group (default 0) matters for multi-impression requests: 1 means the seat only wants the impressions if it can win them all as a group.

The Bid object

Three fields are required: id (bidder-generated, for logging), impid (must match an imp.id from the request), and price (a float expressed as CPM; the spec recommends integer math such as BigDecimal when handling currency). The rest describe the creative and how to serve and account for it:

  • adm: the ad markup itself (HTML, a VAST document, or a native payload), served if the bid wins.
  • nurl / burl / lurl: win, billing, and loss notice URLs, covered below.
  • adomain: advertiser domains for block-list checks, e.g. ["ford.com"]. Exchanges can mandate a single entry.
  • cid and crid: campaign and creative IDs for ad quality review.
  • w / h: creative size in device-independent pixels.
  • mtype: markup type added in 2.6 (1 banner, 2 video, 3 audio, 4 native) so the exchange can tie the bid to the right Imp sub-object.
  • dealid: references deal.id from the request for private marketplace bids.
  • exp: advisory seconds the bidder will wait between auction and impression.
  • apis: supported API frameworks; the singular api field is deprecated in 2.6 in its favor.

Markup: adm vs the win notice

OpenRTB allows two delivery paths. Markup served in the bid puts the creative in adm, which reduces the risk of forfeiting a won impression to an HTTP failure. Markup served on the win notice omits adm; the exchange calls nurl and the response body must contain the markup and only the markup. If both are present, adm takes precedence. Either way, the exchange substitutes macros in the markup before delivery.

Win, billing, and loss notices

FieldFiredNotes
nurlWhen the bid wins the auctionNot necessarily a delivered, viewed, or billable ad. Its response body can carry the ad markup.
burlWhen the win becomes billablePer exchange policy, typically on delivery or view. Best practice is firing it server-side, close to where revenue is booked.
lurlWhen the bid is known to have lostCarries ${AUCTION_LOSS} with a loss reason code. Exchanges may withhold clearing prices by blanking ${AUCTION_PRICE}.

Before calling any notice URL, the exchange replaces substitution macros: ${AUCTION_ID}, ${AUCTION_IMP_ID}, ${AUCTION_PRICE} (the clearing price), ${AUCTION_MBR}, ${AUCTION_LOSS}, and ${AUCTION_MIN_TO_WIN} among others. Unknown optional values are replaced with an empty string, and "AUDIT" is the best-practice stand-in when rendering for test purposes. Full list and encoding rules: OpenRTB substitution macros.

No bid: HTTP 204 and nbr

To pass on an auction, return an empty body with HTTP 204. To tell the exchange why, return a minimal BidResponse with just id and an nbr code instead. Exchanges monitor no-bid rates either way, so a malformed request that silently 204s everywhere is lost revenue; that is the failure mode common OpenRTB mistakes catalogs.

A short valid response

{
  "id": "80ce30c53c16e6ede735f123ef6e3236",
  "bidid": "resp-8f2a01",
  "cur": "USD",
  "seatbid": [{
    "seat": "512",
    "bid": [{
      "id": "bid-0001",
      "impid": "1",
      "price": 9.43,
      "adm": "<div>...markup...</div>",
      "adomain": ["advertiser.com"],
      "cid": "campaign-111",
      "crid": "creative-112",
      "mtype": 1,
      "w": 300,
      "h": 250,
      "burl": "https://bidder.example/bill?p=${AUCTION_PRICE}"
    }]
  }]
}

Note impid matching the request's imp[0].id, the CPM price, and the billing macro left literal for the exchange to substitute.

FAQ

How does a bidder signal no bid in OpenRTB?

The standard option is an empty response body with HTTP 204 No Content. If the bidder wants to tell the exchange why, it returns a bare BidResponse object with a reason code in the nbr field, using the No-Bid Reason Codes list maintained in OpenRTB 3.0.

If a bid contains both adm and win notice markup, which is served?

The adm contents take precedence. Per the 2.6 spec, if both the adm attribute and the win notice return contain markup, adm wins. When serving markup on the win notice instead, the nurl response body must contain the ad markup and nothing else, and adm must be omitted.

Does BidResponse.id have to match the request?

Yes. BidResponse.id is required and is defined as the ID of the bid request to which this is a response. The bidder-generated tracking ID goes in bidid instead.

Is bid.price gross or net, and per what unit?

price is expressed as CPM, the cost per thousand impressions, even though the transaction is for a single impression. The currency is BidResponse.cur, which defaults to USD and must be one the request allowed via cur.

Validate it

rtblint validates OpenRTB bid requests today; bid response validation is on the roadmap. Check the request side of your integration in the bid request tester or with the CLI (rtblint validate request.json), and see what rtblint checks for the rule families involved.