Skip to main content

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:

TypeHTTP StatusDescription
Bid Response200 OKOne or more bids available
No Bid204 No ContentNo bids available (not an error)
Error Response4xx/5xxRequest 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

FieldTypeRequiredDescription
idstringYesID from the bid request
seatbidobject[]YesArray of seatbid objects (at least one)
bididstringNoBidder-generated response ID
curstringNoBid currency (ISO-4217, default: "USD")
customdatastringNoCustom data for winning bidder
nbrintegerNoNo-bid reason code (if no bids)
extobjectNoExtensions 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

FieldTypeRequiredDescription
bidobject[]YesArray of bid objects (at least one)
seatstringNoSeat ID representing the bidder
groupintegerNoGroup bids flag (0=no, 1=yes)
extobjectNoExtensions 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

FieldTypeRequiredDescription
idstringYesUnique bid ID
impidstringYesID of impression from request
adidstringNoAd ID for reporting
nurlstringNoWin notice URL
burlstringNoBilling notice URL
lurlstringNoLoss notice URL
admstringNo*Ad markup (HTML, VAST, etc.)
adomainstring[]NoAdvertiser domains for blocking
bundlestringNoApp bundle for blocking
iurlstringNoImage URL for content review
cidstringNoCampaign ID
cridstringNoCreative ID
tacticstringNoTactic ID
catstring[]NoIAB content categories
attrinteger[]NoCreative attributes
apiintegerNoAPI framework required
qagmediaratingintegerNoQAG media rating
languagestringNoCreative language (ISO-639-1)
dealidstringNoDeal ID if applicable
wintegerNoWidth in pixels
hintegerNoHeight in pixels
wratiointegerNoWidth ratio for responsive
hratiointegerNoHeight ratio for responsive
expintegerNoAdvisory expiration time (seconds)
extobjectNoExtensions 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:

FieldTypeRequiredDescription
imptrackersstring[]NoArray 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 floor
  • 1: Bid lost in auction
  • 2: Impression opportunity expired
  • 3: Invalid bid response
  • 100+: 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.

FieldTypeLocationRequiredDescription
imptrackersstring[]native object in admNoURLs 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 error
  • 1: Technical error
  • 2: Invalid request
  • 3: Known web spider
  • 4: Suspected non-human traffic
  • 5: Cloud/data center/proxy IP
  • 6: Unsupported device
  • 7: Blocked publisher
  • 8: Unmatched user
  • 9: Daily reader cap met
  • 10: Daily domain cap met

Response Validation

Required Fields

  • id: Must match request ID
  • seatbid: Must contain at least one seatbid
  • seatbid[].bid: Must contain at least one bid
  • bid.id: Must be unique
  • bid.impid: Must match impression ID from request

Field Constraints

  • adomain: Required for advertiser blocking
  • w, h: Should match requested dimensions
  • adm or ext.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