Response Format
Complete specification of the OpenRTB 2.6 bid response format returned by the Affinity AI Bid API.
Response Types
The API returns one of three response types:
| Type | HTTP Status | Description |
|---|---|---|
| Bid Response | 200 OK | One or more bids available |
| No Bid | 204 No Content | No bids available (not an error) |
| Error Response | 4xx/5xx | Request validation or server error |
Bid Response Structure
{
"id": "unique-request-id",
"seatbid": [...],
"bidid": "bid-response-id",
"cur": "USD",
"customdata": "optional-custom-data",
"nbr": 0,
"ext": {...}
}
Top-Level Objects
BidResponse Object
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | ID from the bid request |
seatbid | object[] | Yes | Array of seatbid objects (at least one) |
bidid | string | No | Bidder-generated response ID |
cur | string | No | Bid currency (ISO-4217, default: "USD") |
customdata | string | No | Custom data for winning bidder |
nbr | integer | No | No-bid reason code (if no bids) |
ext | object | No | Extensions object |
Example Minimal Response
{
"id": "req-001",
"seatbid": [
{
"bid": [
{
"id": "bid-001",
"impid": "imp-1",
"price": 2.5,
"adm": "<a href='https://example.com'><img src='https://cdn.example.com/banner.jpg'/></a>"
}
]
}
]
}
SeatBid Object
Container for bids from a specific seat (bidder).
SeatBid Object Fields
| Field | Type | Required | Description |
|---|---|---|---|
bid | object[] | Yes | Array of bid objects (at least one) |
seat | string | No | Seat ID representing the bidder |
group | integer | No | Group bids flag (0=no, 1=yes) |
ext | object | No | Extensions object |
Example SeatBid
{
"seatbid": [
{
"seat": "affilizz-search",
"bid": [
{
"id": "bid-001",
"impid": "imp-1",
"price": 2.5,
"adm": "..."
}
]
}
]
}
Bid Object
Individual bid for a specific impression.
Bid Object Fields
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Unique bid ID |
impid | string | Yes | ID of impression from request |
adid | string | No | Ad ID for reporting |
nurl | string | No | Win notice URL |
burl | string | No | Billing notice URL |
lurl | string | No | Loss notice URL |
adm | string | No* | Ad markup (HTML, VAST, etc.) |
adomain | string[] | No | Advertiser domains for blocking |
bundle | string | No | App bundle for blocking |
iurl | string | No | Image URL for content review |
cid | string | No | Campaign ID |
crid | string | No | Creative ID |
tactic | string | No | Tactic ID |
cat | string[] | No | IAB content categories |
attr | integer[] | No | Creative attributes |
api | integer | No | API framework required |
qagmediarating | integer | No | QAG media rating |
language | string | No | Creative language (ISO-639-1) |
dealid | string | No | Deal ID if applicable |
w | integer | No | Width in pixels |
h | integer | No | Height in pixels |
wratio | integer | No | Width ratio for responsive |
hratio | integer | No | Height ratio for responsive |
exp | integer | No | Advisory expiration time (seconds) |
ext | object | No | Extensions object |
*Either adm or ext.aura.adcpFormat must be provided.
Example Bid
{
"id": "bid-001",
"impid": "imp-1",
"price": 2.5,
"adid": "ad-12345",
"nurl": "https://track.example.com/win?price=${AUCTION_PRICE}",
"adm": "<a href='https://example.com'><img src='https://cdn.example.com/banner.jpg'/></a>",
"adomain": ["example.com"],
"cid": "campaign-789",
"crid": "creative-456",
"cat": ["IAB19-30"],
"w": 300,
"h": 250
}
Creative Markup (adm)
The adm field contains the creative markup to be rendered.
Native Markup
JSON-encoded native response following the
IAB Native Ads 1.2
specification. The imptrackers field is optional and contains impression
tracking URLs that must be fired via HTTP GET when the ad is rendered.
{
"native": {
"ver": "1.2",
"assets": [
{
"id": 1,
"title": {
"text": "Example Product Title"
}
},
{
"id": 2,
"img": {
"url": "https://cdn.example.com/image.jpg",
"w": 1200,
"h": 627
}
}
],
"link": {
"url": "https://example.com/product"
},
"imptrackers": ["https://track.example.com/impression"]
}
}
imptrackers field:
| Field | Type | Required | Description |
|---|---|---|---|
imptrackers | string[] | No | Array of impression tracking URLs to fire when ad is rendered |
- Only present when impression tracking URLs are available
- Each URL must be fired via HTTP GET when the impression is rendered
- Use
navigator.sendBeacon()or an image pixel for reliable delivery
Tracking URLs
Win Notice URL (nurl)
Fired when the bid wins the auction:
https://track.example.com/win?price=${AUCTION_PRICE}&id=${AUCTION_ID}
Macros:
${AUCTION_PRICE}: Winning price (CPM)${AUCTION_ID}: Auction ID${AUCTION_BID_ID}: Bid ID${AUCTION_IMP_ID}: Impression ID${AUCTION_SEAT_ID}: Seat ID${AUCTION_AD_ID}: Ad ID${AUCTION_CURRENCY}: Currency code
Billing Notice URL (burl)
Fired when impression is billable:
https://track.example.com/billing?price=${AUCTION_PRICE}
Loss Notice URL (lurl)
Fired when the bid loses the auction:
https://track.example.com/loss?reason=${AUCTION_LOSS}&price=${AUCTION_PRICE}
Loss Reasons:
0: Bid was below auction floor1: Bid lost in auction2: Impression opportunity expired3: Invalid bid response100+: Exchange-specific reasons
Impression Trackers (imptrackers)
For native ad responses, the imptrackers field inside the native object
contains an array of impression tracking URLs. These follow the
OpenRTB Native Ads 1.2
standard and are separate from the bid-level nurl/burl fields.
| Field | Type | Location | Required | Description |
|---|---|---|---|---|
imptrackers | string[] | native object in adm | No | URLs to fire via HTTP GET when the ad is rendered |
When to fire: When the ad impression is rendered and visible to the user.
How to fire: HTTP GET to each URL. Use navigator.sendBeacon() or an image
pixel for reliable, non-blocking delivery.
function fireImpressionTrackers(nativeResponse) {
const trackers = nativeResponse.imptrackers
if (!trackers || trackers.length === 0) return
trackers.forEach(url => {
if (navigator.sendBeacon) {
navigator.sendBeacon(url)
} else {
new Image().src = url
}
})
}
AdCP Creative Manifests
When using AdCP extensions, bids include structured creative manifests instead of raw markup.
AdCP Response Structure
{
"id": "bid-001",
"impid": "imp-1",
"price": 2.5,
"nurl": "https://track.example.com/win?price=${AUCTION_PRICE}",
"ext": {
"aura": {
"adcpFormat": {
"formatId": {
"agent_url": "https://creative.adcontextprotocol.org",
"id": "display_300x250"
},
"creativeManifest": {
"promoted_offering": "Example Product",
"assets": {
"banner_image": {
"url": "https://cdn.example.com/banner.jpg",
"width": 300,
"height": 250
},
"headline": {
"content": "Example Product - Now 20% Off"
},
"clickthrough_url": {
"url": "https://example.com/product?campaign={MEDIA_BUY_ID}"
},
"impression_tracker": {
"url": "https://track.example.com/imp?buy={MEDIA_BUY_ID}&cb={CACHEBUSTER}"
}
}
}
}
}
}
}
View AdCP creative manifests →
No Bid Response
When no bids are available, return HTTP 204:
HTTP/1.1 204 No Content
Note: This is not an error. Publishers should handle 204 responses gracefully without displaying errors.
Optional No-Bid Reason
Include reason code in response body:
{
"id": "req-001",
"nbr": 2
}
No-Bid Reason Codes:
0: Unknown error1: Technical error2: Invalid request3: Known web spider4: Suspected non-human traffic5: Cloud/data center/proxy IP6: Unsupported device7: Blocked publisher8: Unmatched user9: Daily reader cap met10: Daily domain cap met
Response Validation
Required Fields
id: Must match request IDseatbid: Must contain at least one seatbidseatbid[].bid: Must contain at least one bidbid.id: Must be uniquebid.impid: Must match impression ID from request
Field Constraints
adomain: Required for advertiser blockingw,h: Should match requested dimensionsadmorext.aura.adcpFormat: At least one required
Response Examples
AdCP Format Bid
{
"id": "req-003",
"seatbid": [
{
"bid": [
{
"id": "bid-003",
"impid": "imp-1",
"price": 3.0,
"adomain": ["example.com"],
"crid": "adcp-789",
"nurl": "https://track.example.com/win?price=${AUCTION_PRICE}",
"ext": {
"aura": {
"adcpFormat": {
"formatId": {
"agent_url": "https://creative.adcontextprotocol.org",
"id": "display_300x250"
},
"creativeManifest": {
"promoted_offering": "Example Product",
"assets": {
"banner_image": {
"url": "https://cdn.example.com/banner.jpg"
},
"clickthrough_url": {
"url": "https://example.com/product"
}
}
}
}
}
}
}
]
}
],
"cur": "USD"
}
Multiple Bids
{
"id": "req-004",
"seatbid": [
{
"bid": [
{
"id": "bid-004a",
"impid": "imp-1",
"price": 2.5,
"adm": "...",
"w": 300,
"h": 250
},
{
"id": "bid-004b",
"impid": "imp-2",
"price": 3.0,
"adm": "...",
"w": 728,
"h": 90
}
]
}
],
"cur": "USD"
}
Handling Responses
Success Response (200 OK)
async function handleBidResponse(response) {
if (response.status === 200) {
const bidResponse = await response.json()
// Extract winning bid
const bid = bidResponse.seatbid[0].bid[0]
// Render creative
if (bid.adm) {
// Parse native markup if JSON-encoded
let creative = bid.adm
try {
const parsed = JSON.parse(bid.adm)
const native = parsed.native || parsed
// Fire impression trackers from native response (optional field)
if (native.imptrackers && native.imptrackers.length > 0) {
native.imptrackers.forEach(url => {
if (navigator.sendBeacon) {
navigator.sendBeacon(url)
} else {
new Image().src = url
}
})
}
renderNativeCreative(native)
} catch {
// Non-JSON markup (e.g. HTML banner)
renderCreative(creative)
}
} else if (bid.ext?.aura?.adcpFormat) {
// AdCP format
await renderAdcpCreative(bid.ext.aura.adcpFormat)
}
// Fire win notice
if (bid.nurl) {
const winUrl = replaceMacros(bid.nurl, {
AUCTION_PRICE: '0',
AUCTION_ID: bidResponse.id,
})
fetch(winUrl)
}
}
}
No Bid Response (204)
async function handleBidResponse(response) {
if (response.status === 204) {
// No bid available - not an error
console.log('No bid available for this request')
// Show fallback content or house ad
showFallbackAd()
}
}
Error Response (4xx/5xx)
async function handleBidResponse(response) {
if (response.status >= 400) {
const error = await response.json()
console.error('Bid request failed:', error)
// Log for debugging
logError({
requestId: error.request_id,
code: error.error.code,
message: error.error.message,
})
// Show fallback
showFallbackAd()
}
}
Best Practices
1. Always Handle 204 Responses
No bid responses are normal and should not be treated as errors.
2. Fire Win Notices Immediately
Fire the nurl as soon as the bid wins, before rendering the creative.
3. Replace Macros Correctly
Replace all macros in tracking URLs with actual values:
function replaceMacros(url, values) {
return url
.replace('${AUCTION_PRICE}', '0')
.replace('${AUCTION_ID}', values.id)
.replace('${AUCTION_BID_ID}', values.bidId)
}
4. Validate Response Structure
Always validate that required fields are present before using them.
5. Handle Multiple Bids
If multiple impressions were requested, handle all returned bids appropriately.
6. Respect Advisory Expiration
If exp is provided, don't cache the creative longer than specified.
7. Fire Impression Trackers for Native Ads
When a native ad response includes imptrackers, fire each URL via HTTP GET
when the impression is rendered. Use navigator.sendBeacon() for reliability:
if (native.imptrackers) {
native.imptrackers.forEach(url => {
navigator.sendBeacon ? navigator.sendBeacon(url) : (new Image().src = url)
})
}
Fire impression trackers after rendering the creative and before any user interaction tracking. Each URL should be fired only once per impression.
Next Steps
- Request Format - Bid request structure
- Error Handling - Error codes and retry logic
- AdCP Creative Manifests - Structured creative assembly