AdTech
Anatomy of an OpenRTB Bid Request and Response
An annotated walkthrough of the fields that matter, the mistakes that cause no-bids and a checklist for validating real traffic.
OpenRTB is the protocol that lets an exchange or SSP describe an ad opportunity to many buyers, and lets those buyers answer in a standard way. It is a set of JSON objects exchanged over HTTP. Everything in programmatic advertising that you can observe, including bids, timeouts, price floors, deals and supply-chain checks, appears in these payloads. Being able to read them fluently is the fastest route to debugging an integration. This guide uses OpenRTB 2.6 and a realistic example; the full specification is maintained by IAB Tech Lab.
A bid request, annotated
Below is a simplified but realistic request for one banner impression on a website. All values are illustrative.
{
"id": "5f2c9d1e-7a41-4b0c-9e2a-3d1b8a6c4f10",
"at": 1,
"tmax": 250,
"cur": ["USD"],
"imp": [{
"id": "1",
"tagid": "homepage-top",
"secure": 1,
"bidfloor": 0.85,
"bidfloorcur": "USD",
"banner": {
"format": [{ "w": 300, "h": 250 }, { "w": 300, "h": 600 }],
"pos": 1
},
"pmp": {
"private_auction": 0,
"deals": [{ "id": "DEAL-4821", "bidfloor": 2.50, "at": 1 }]
}
}],
"site": {
"id": "site-123",
"domain": "publisher.example",
"page": "https://publisher.example/news/story-slug",
"publisher": { "id": "pub-456", "domain": "publisher.example" }
},
"device": {
"ua": "Mozilla/5.0 (Linux; Android 14) ...",
"ip": "203.0.113.42",
"devicetype": 4,
"os": "Android",
"language": "en"
},
"user": {
"buyeruid": "a1b2c3d4",
"consent": "<TCF consent string>"
},
"regs": { "gdpr": 1, "gpp": "<GPP string>", "gpp_sid": [2] },
"source": {
"tid": "9d3b6e20-4f6a-43e0-8c2e-5b7f1f3d0a11",
"schain": {
"ver": "1.0",
"complete": 1,
"nodes": [{ "asi": "ssp.example", "sid": "1234", "hp": 1 }]
}
}
}
Read it from the top:
iduniquely identifies this auction. The response must echo it.atis the auction type. A value of 1 means first-price: the winner pays their bid. A value of 2 means second-price plus.tmaxis the time in milliseconds the exchange will wait for a response, including network latency. Late responses are discarded, silently from the buyer's point of view.curlists the currencies the exchange accepts for bids.
The impression object
Each element of imp is one slot being offered. Its id is local to this request. The banner object lists acceptable sizes in format; a bid whose creative is not one of these sizes may be rejected. A slot can instead carry a video, audio or native object. bidfloor and bidfloorcur set the minimum price (a CPM, meaning cost per thousand impressions). secure: 1 requires HTTPS creative assets, which is the norm. The pmp object lists private-marketplace deals, each with a deal id that the buyer must reference in a response to claim deal pricing and terms.
Where and who: site, device, user
The site object (or app for mobile applications) describes the publisher, domain and page. device includes the user agent, IP address and device type, and is a key input for targeting and fraud detection. The user object may carry a buyer-specific identifier (buyeruid, usually produced through cookie or ID synchronisation) and the consent string.
Privacy and regulations: regs
In OpenRTB 2.6 the regulatory flags are first-class fields: regs.gdpr indicates whether European data-protection rules apply, regs.gpp carries a Global Privacy Platform string, and gpp_sid lists the applicable sections of that string. Buyers use these to decide whether they may bid and what data they may use. A missing or malformed consent signal is one of the most common reasons buyers decline to bid on otherwise good inventory.
Provenance: source and supply chain
The source.schain object records the path that the request has taken from the publisher to the buyer, with one node per intermediary. Each node names the seller's system (asi), its account identifier (sid) and whether it handles payment (hp). Buyers cross-check these nodes with the publisher's ads.txt and each seller's sellers.json to verify authorisation. See our ads.txt and sellers.json guide for how that verification works.
A bid response, annotated
{
"id": "5f2c9d1e-7a41-4b0c-9e2a-3d1b8a6c4f10",
"cur": "USD",
"seatbid": [{
"seat": "buyer-seat-77",
"bid": [{
"id": "bid-9001",
"impid": "1",
"price": 3.10,
"dealid": "DEAL-4821",
"adm": "<div>...creative markup...</div>",
"adomain": ["advertiser.example"],
"crid": "creative-2231",
"w": 300,
"h": 250,
"mtype": 1,
"nurl": "https://dsp.example/win?p=${AUCTION_PRICE}",
"burl": "https://dsp.example/bill?p=${AUCTION_PRICE}"
}]
}]
}
idmust equal the requestid. A mismatch makes the exchange discard the response.seatbidgroups bids by buyer seat. Within each,bidobjects reference the request's impression throughimpid.priceis the CPM bid. Under first-price rules, this is what would be charged on a win, so buyers apply bid shading to avoid overpaying.dealidties the bid to a deal frompmp.deals.admholds the creative markup (or a VAST document for video). Alternatively, it can be fetched from a URL.adomainlists the advertiser domains, which exchanges use for brand-safety and blocking rules.crididentifies the creative for review and reporting.mtype(new in 2.6) states the media type of the markup: 1 for banner, 2 for video, 3 for audio and 4 for native.nurlandburlare the win and billing notice URLs. The macro${AUCTION_PRICE}is replaced by the exchange with the clearing price before the call is made.
Saying no: no-bid responses
Most requests do not receive a bid, and that is expected. A buyer can simply return an empty HTTP 204 response, or a body that includes a no-bid reason code (nbr):
HTTP/1.1 204 No Content
-- or, with a reason, a 200 with a body like: --
{ "id": "5f2c9d1e-7a41-4b0c-9e2a-3d1b8a6c4f10", "nbr": 2 }
The specification defines numbered reason codes. A few of the most useful to recognise are 1 (technical error), 2 (invalid request), 3 (known web spider), 4 (suspected non-human traffic), 5 (cloud, data-centre or proxy IP), 7 (blocked publisher or site) and 8 (unmatched user). Later revisions add codes for ads.txt authorisation and supply-chain problems. Reason codes are optional, so absence of a code does not mean there was no reason, only that the buyer did not say.
The most common reasons a bid is rejected
| Symptom | Likely cause | What to check |
|---|---|---|
| Response ignored entirely | id does not match the request, or invalid JSON | Echo the request id exactly; validate against the schema |
| Bid dropped | impid not present in the request | Use the imp id supplied, not an internal id |
| Bid under floor | Price lower than bidfloor, or deal floor, in the stated currency | Compare after currency conversion; apply deal floors where relevant |
| Size not accepted | Creative w and h not in banner.format | Return an allowed size, or set it explicitly in the bid |
| Late response | Latency above tmax, including network time | Measure from the exchange perspective; reduce hops and payload size |
| Creative blocked | Missing or disallowed adomain, category, or attributes | Always include advertiser domain; review exchange blocking rules |
| Deal not honoured | Missing dealid, or deal not eligible for this buyer seat | Confirm deal ID, seat and active dates match both sides |
| Markup mismatch | mtype or adm format does not match what was requested | Return VAST for video, HTML for banner, native JSON for native |
| Win notice never fires | Macro not replaced, URL malformed, or mixed content (HTTP in HTTPS) | Log the exact URL being called; ensure HTTPS and valid macros |
Practical validation checklist
If you operate an exchange, SSP or DSP, or test an integration with one, these checks catch most problems early:
- Schema validation of every request and response against a JSON schema for the version in use, with sampling in production.
- Required-field audit for each partner. Partners often require fields that the specification marks optional, so keep their documentation alongside your tests.
- Id consistency tests: response id to request id, impid to imp id, deal id to PMP deals.
- Price tests around the floor: one cent below, exactly at, and above. Add currency-conversion cases.
- Format tests for each size, and for every media type you support.
- Privacy-signal tests: with and without consent, with malformed strings, and in regions with different rules.
- Timeout tests: responses just inside and just outside
tmax, and slow DNS or TLS. - Notice tests: win and billing URLs are called once, with the macro replaced, and tolerate retries without double counting.
- Traffic replay: capture a sample of real requests and replay them against a staging environment to compare behaviour before and after a change.
Versions and extensions
OpenRTB evolves through numbered revisions. Many deployments still run earlier versions, such as 2.5, where some of the fields above live in ext objects instead (for example source.ext.schain and user.ext.consent). Each exchange also defines its own extensions for features outside the core specification. When something does not behave as you expect, check which version both sides implement and where each puts the field in question, before assuming the field is missing.
Key takeaways
- A bid request is an offer of one or more impressions with context; a bid response is a priced creative for a specific impression.
- The ids must line up: response id equals request id, and each bid impid equals an imp id from the request.
- Prices are CPM in the stated currency, and they are checked against floors, formats and deal terms before they can win.
- Privacy and supply-chain objects are first-class fields in 2.6 and often decide whether a buyer will bid at all.
- Debug with real payloads: compare what was sent against the spec and against what the partner documents.
Related guides
Further reading
About this guide. Written by the Kensaltensi engineering and QA team for educational purposes. Examples are illustrative, and standards evolve, so check the primary sources above before relying on a detail in production. If you spot an error or an out-of-date statement, email hello@kensaltensi.org and we will review and correct it.