本文属于 Bidding 竞价引擎链路 系列。
总览篇把 Bidder 拆成「解析 → 定向 → 召回 → 打分 → 出价 → 排序 → 填充」七级漏斗,第一级解析(Parse)要把「原始竞价请求」变成内部可算对象——而那份「原始竞价请求」的格式,正是本篇的主角 OpenRTB。本篇不碰引擎实现,专讲 Bidder 每天要吞吐的这份协议本身:它长什么样、字段是什么语义、赢了怎么结算与回传。读完再回到解析层,你就知道它在归一化的到底是什么。
OpenRTB 不只是竞价引擎的「母语」,也是整个程序化生态的通用语言——生态全景里画的角色、交易模式分出的 RTB / PMP / PD / PG、last look 里的拍卖博弈、供应链 schain 的核验,全都踩在这份协议之上。只是它太过基础,反而常被当成「背景板」一带而过。而它约定的核心其实只有一句话:DSP 与 SSP / ADX 之间,靠这份 JSON 协议对话。
这篇文章就把这根「线」本身拆开——不讲角色、不讲商业模式,只讲协议:一次曝光机会被编码成什么样的 bid request、DSP 回一个什么样的 bid response、交易所拿到出价后怎么结算、赢家 / 输家分别收到什么通知(notice)、成交价怎么通过宏回填、以及这套 JSON 对象模型这些年是怎么演进的。读完之后,别处一带而过的 imp、pmp.deals[]、source.schain、nurl、${AUCTION_PRICE},你都能指着字段说出它在协议里的确切位置和语义。
范围划界:本文讲协议本身(对象模型 + 结算语义)。竞价引擎怎么解析和归一化这份请求,是下一篇解析层(Parse)的事;库存的几种卖法(RTB/PMP/PD/PG)见交易模式;
source.schain的供应链核验见 schain 专文。这里只在字段层面点到它们,不重复展开。
一句话读图:请求描述机会,响应描述出价。左边 BidRequest 的每个子对象都在回答「关于这次曝光你需要知道的一件事」(在哪个位、什么设备、哪个人、经过谁、受什么法规约束);右边 BidResponse 则把「出多少、投什么、赢了怎么通知」打包进
seatbid[].bid[]。后面各节就沿着这棵树逐个拆。
TL;DR
- OpenRTB 是 DSP ↔ SSP/ADX 之间的实时竞价标准协议(IAB Tech Lab 维护),把「一次曝光机会」和「一次出价」编码成两份 JSON:
bid request与bid response。它解决的是互操作——让成千上万买卖方不必两两约定私有格式。 - BidRequest 的骨架是
imp[]:一次请求携带一个或多个imp(impression,广告位),每个imp里 banner / video / audio / native 四选一描述广告形态,外加bidfloor(底价)与pmp(私有交易)。其余顶层对象都在给出价补充上下文:site/app(媒体环境,二选一)、device、user、source(链路来源,schain 挂这)、regs(隐私法规)。 - BidResponse 的骨架是
seatbid[].bid[]:按买方席位(seat) 分组的出价数组。单个bid的核心是price(出价)、impid(对哪个广告位)、adm(创意标记)或nurl、以及一堆合规字段(adomain落地域名、crid创意 ID、cat品类)。不出价则回nbr(no-bid reason)。 at决定怎么结算:at=1一价(industry 2019 后的默认)、at=2二价+(legacy)。交易所才是拍卖的主持人——DSP 只管报价,赢家和成交价由交易所定。- 创意有两种交付方式:①
adm内联在响应里(现代主流,省一次 RTT);② 省略adm、由交易所在你赢了之后回调nurl拉取标记(legacy,多一跳延迟,已不推荐)。 - 三条 notice URL 各司其职:
nurl=赢价通知(赢了就打,常用来回传成交价)、burl=计费通知(曝光真正可计费 / 被渲染时才打,把「赢」和「计费」解耦)、lurl=输价通知(输了打,带${AUCTION_LOSS}告诉你为什么输)。 - 成交价靠宏回填:交易所把胜者 URL 里的
${AUCTION_PRICE}替换成实际结算价(常加密)再回调——这样 DSP 不必自己猜清算价。配套还有${AUCTION_ID}、${AUCTION_IMP_ID}、${AUCTION_LOSS}等一组宏。 ext是官方留的扩展逃生口:任何对象都能挂ext,各交易所的私有字段(以及历史上 schain、gdpr consent)都从这里长出来——它既是协议能活这么多年的原因,也是「方言」和归一化成本的来源。- 版本演进有主线:2.5 打稳地基、2.6 把
schain/gpp等收编进主规范;3.0 是一次大重构——用分层架构 + AdCOM(Advertising Common Object Model) 把「传输」和「广告对象」分开。但今天线上的绝对主力仍是 2.5 / 2.6。
Table of contents
Open Table of contents
1. 为什么需要一份协议:从「两两私约」到 OpenRTB
回到 last look 那篇提过的年代——RTB 刚起步时,每家 DSP 要接每家交易所,都得私下约定请求长什么样、字段叫什么、价格单位是什么。买卖双方数量一多,这就是个 (N \times M) 的对接地狱:接一个新交易所等于重写一遍解析。
OpenRTB 做的事,本质和 HTTP、SQL 一样——立一份大家都认的标准接口,把 (N \times M) 压成 (N + M):DSP 只实现一次 OpenRTB,就能接所有遵循它的交易所;交易所同理。IAB(后来是 IAB Tech Lab)从 2010 年起维护这份规范,RTB 生态能长到今天这个规模,这份协议是地基。
它约定的其实只有两样东西:
- 一次曝光机会长什么样 →
bid request(交易所 → DSP,广播)。 - 一次出价长什么样 →
bid response(DSP → 交易所,作答)。
外加一套结算与通知的语义(谁赢、成交多少、怎么回传),以及一个扩展机制(ext)留给各家塞私货。就这么点东西,撑起了每天数以万亿计的竞价。
一个必须先建立的直觉:OpenRTB 是「交易所主持、DSP 应答」的模型。交易所把同一个
bid request广播给多个 DSP,收齐bid response后自己跑拍卖、定胜负与成交价。DSP 永远只看得到自己的那一份请求和自己的出价——这也是 last look 那类信息不对称之所以可能存在的协议前提。
2. BidRequest:把一次曝光机会编码成 JSON
先看一个精简但字段齐全的真实形态(省略号处为省略字段):
{
"id": "1a2b3c-req-0001",
"at": 1,
"tmax": 120,
"cur": ["USD"],
"imp": [
{
"id": "1",
"tagid": "homepage-top",
"bidfloor": 0.85,
"bidfloorcur": "USD",
"secure": 1,
"banner": { "w": 300, "h": 250, "pos": 1 },
"pmp": {
"private_auction": 0,
"deals": [
{ "id": "DEAL-42", "bidfloor": 2.5, "at": 1, "wseat": ["agencyX"] }
]
}
}
],
"app": {
"bundle": "com.example.news",
"publisher": { "id": "pub-9" },
"cat": ["IAB12"]
},
"device": {
"ua": "Mozilla/5.0 ...",
"ip": "203.0.113.7",
"ifa": "6D92078A-8246-...",
"lmt": 0,
"os": "iOS",
"devicetype": 4,
"geo": { "country": "USA", "type": 2 }
},
"user": { "id": "ssp-side-user-77", "buyeruid": "dsp-side-user-abc" },
"source": { "fd": 1, "tid": "txn-555", "schain": { "...": "见 schain 专文" } },
"regs": { "coppa": 0, "ext": { "gdpr": 0, "gpp": "DBABzw~..." } }
}
这份 JSON 的顶层字段和子对象,可以按「它在回答关于这次曝光的什么问题」归类:
| 对象 / 字段 | 回答的问题 | 关键子字段(挑重点) |
|---|---|---|
id | 这是哪次请求 | 交易所生成的唯一 ID,响应里要原样带回 |
imp[] | 要卖的是哪些广告位 | 见 §2.1,请求的绝对核心 |
at | 用哪种拍卖结算 | 1 一价 / 2 二价+;见 §4 |
tmax | 最多给你多少毫秒作答 | 交易所给 DSP 的时间预算,超时的响应直接丢 |
cur[] | 用什么货币出价 | 允许的货币列表,默认 USD |
site / app | 曝光发生在哪 | 二选一:网页环境用 site、App 用 app;含 domain/bundle、publisher、cat 品类 |
device | 什么设备 / 网络 / 位置 | ua、ip/ipv6、ifa 广告标识、lmt/dnt 限跟踪、geo、connectiontype |
user | 面对的是哪个人 | id(SSP 侧)、buyeruid(DSP 侧,见 §2.3)、data[] 人群标签 |
source | 这条请求经过了谁 | fd 谁做最终决策、tid 事务 ID、schain 供应链(见 schain 专文) |
regs | 受什么隐私法规约束 | coppa、以及 ext 里的 gdpr/consent、gpp/gpp_sid |
test | 是不是测试流量 | 1 表示测试,别真扣钱 |
bcat/badv/bapp | 媒体不接受什么 | 屏蔽的品类 / 广告主域名 / 竞品 App |
wseat/bseat | 谁能 / 不能参与 | 白 / 黑名单席位 |
ext | 协议没覆盖的 | 各交易所私有字段的容器(见 §7) |
下面拆三个最该讲清楚的:imp、site vs app、以及 user 里那对容易搞混的 ID。
2.1 imp:请求的核心,一个广告位一个
bid request 的一切都是为 imp 服务的——它是真正要卖的东西。一次请求可以带多个 imp(一个页面上有多个广告位),但更常见是一个。一个 imp 里最关键的是广告形态四选一:
banner:展示 / 图片广告,带w/h或format[](多尺寸候选)、pos(首屏位置)。video: 视频广告,带mimes、minduration/maxduration、protocols(VAST 版本)、linearity、placement。audio: 音频广告(播客 / 流媒体电台)。native:原生广告,native.request里再嵌一份原生资产规格(标题、图、CTA 各要多大)。
外加两个几乎每次都在看的字段:
bidfloor+bidfloorcur:底价,出价低于它基本没戏(见 §4)。pmp:私有交易容器。里面deals[]的每个 deal 有自己的id(就是交易模式里反复出现的 Deal ID)、bidfloor、at、wseat。private_auction=1表示这个位只对 deal 开放、屏蔽公开竞价。DSP 认出自己签过的 Deal ID,就按约定出价——这正是把线下谈判「激活」进实时竞价的那一步。
别把
imp.id(本次请求内广告位的局部编号,响应里bid.impid要对上)和bid request.id(整条请求的全局 ID)搞混——这是新手解析时最常见的一类串号 bug。
2.2 site vs app:二选一,别同时出现
同一次曝光只可能发生在一个环境里,所以 site(网页)和 app(移动 / CTV 应用)互斥,规范要求只出现其一。它们结构相似,都挂着 publisher、content、cat(IAB 品类)、keywords,区别在身份字段:
site.domain/site.page:网页的域名与 URL——域名伪造就发生在这个字段上(见供应链透明度)。app.bundle/app.storeurl:App 的包名与商店链接——app-ads.txt靠bundle反查开发者官网来核验。
对买方,site vs app 直接决定用哪套定向与归因逻辑(Web 靠 cookie / 广告主 Ad Server,App 靠设备 ID / MMP),所以解析时第一件事往往就是分流这两种环境。
2.3 user.id vs user.buyeruid:cookie matching 的产物
user 对象里有两个 ID,是新人最容易糊的一对:
user.id:卖方(SSP / 交易所) 视角下这个用户的 ID。user.buyeruid:买方(DSP) 视角下同一个用户的 ID。
为什么要两个?因为买卖双方各自维护自己的用户体系,同一个人在 SSP 眼里是 A、在 DSP 眼里是 B。Cookie Matching(cookie 同步) 就是双方事先通过 pixel 互换、建立起 A↔B 映射表;交易所发请求时,顺手把该 DSP 认得的 buyeruid 填进去,DSP 拿到就能查自己的画像 / 频控 / 归因。
第三方 cookie 退场后,这套
buyeruid匹配率坍塌,正是 RTA(不共享 ID、只回传决策)和以第一方数据为核心的方案兴起的直接背景——协议字段还在,但它承载的确定性在消失。
3. BidResponse:把一次出价编码成 JSON
DSP 在 tmax 内决策完,回一份 bid response:
{
"id": "1a2b3c-req-0001",
"cur": "USD",
"seatbid": [
{
"seat": "agencyX",
"bid": [
{
"id": "bid-001",
"impid": "1",
"price": 3.20,
"adm": "<div>...creative markup...</div>",
"adomain": ["example-brand.com"],
"crid": "creative-88",
"cid": "campaign-12",
"cat": ["IAB19"],
"w": 300,
"h": 250,
"dealid": "DEAL-42",
"nurl": "https://dsp.example/win?imp=${AUCTION_IMP_ID}&price=${AUCTION_PRICE}",
"burl": "https://dsp.example/bill?imp=${AUCTION_IMP_ID}&price=${AUCTION_PRICE}",
"lurl": "https://dsp.example/loss?imp=${AUCTION_IMP_ID}&reason=${AUCTION_LOSS}"
}
]
}
]
}
顶层 id 必须回填请求的 id(对上号)。真正的内容在 seatbid[]:
seatbid:按买方席位(seat) 分组。一个 DSP 可以代表多个买方席位(不同代理商 / 广告主),所以出价先按 seat 分组,再在组里放bid[]。group字段还能表达「这一组要么全赢要么全输」的打包语义。bid:一个出价,对应到impid指向的那个广告位。核心字段:
| 字段 | 含义 | 备注 |
|---|---|---|
impid | 出价对应哪个 imp | 必须等于请求里某个 imp.id |
price | 出价(CPM) | 单位是 cur 指定的货币,每千次曝光价 |
adm | 创意标记(markup) | HTML / VAST / 原生 JSON;内联交付见 §5.1 |
nurl | 赢价通知 URL | 见 §6 |
burl | 计费通知 URL | 2.5+,见 §6 |
lurl | 输价通知 URL | 见 §6 |
adomain[] | 落地页 / 广告主域名 | 品牌安全与竞品屏蔽的核心依据,几乎必填 |
crid | 创意 ID | 交易所据此做创意审核 / 缓存 |
cid | 活动 ID | |
cat[] | 创意品类 | 要落在媒体 bcat 屏蔽之外 |
w/h | 创意尺寸 | 要匹配 imp 允许的尺寸 |
dealid | 命中哪个 Deal | 与 imp.pmp.deals[].id 对应;PMP/PD/PG 靠它结算 |
adid | 已审核广告的引用 ID | |
attr[]/api | 创意属性 / 支持的 API 框架 | 如是否可展开、是否 MRAID |
如果不出价,规范推荐直接回 HTTP 204 No Content(最省),或回一个带 nbr(no-bid reason)的空响应说明为什么不出——nbr 常见码:0 未知、1 技术错误、2 请求无效、3 已知爬虫、4 疑似非人类流量、8 未匹配到用户等。给出 nbr 对交易所侧的流量诊断很有价值,但也多花一点带宽,是否回是双方约定。
4. 交易所怎么结算:at 拍卖类型与成交价
这是最容易被工程新人跳过、却是整个协议语义核心的一节:DSP 只负责报 price,谁赢、成交多少,是交易所说了算。 请求里的 at(auction type)声明用哪种规则结算:
at | 规则 | 成交价 | 现状 |
|---|---|---|---|
1 | First Price | 赢家照自己出的价成交 | 2019 年后的行业默认(见 last look) |
2 | Second Price Plus | 赢家付第二名 + 一个增量 | legacy,仍有存量 |
3 | (历史)固定价 deal | bidfloor 即成交价 | 已废弃,deal 价改由 pmp 表达 |
一价与二价的博弈、以及为什么行业从二价集体倒向一价,last look 那篇已经讲透,这里只强调协议层的两个落点:
- 成交价 ≠ 出价(在二价下)。DSP 出 $3.20 不代表付 $3.20——所以它需要交易所把真实成交价告诉自己。这就是下一节
${AUCTION_PRICE}宏存在的根本原因。 - 底价是一道硬门槛。出价必须 ≥
imp.bidfloor(公开竞价)或deal.bidfloor(私有交易),否则连入围都算不上——对应输价原因码里的100 Bid Below Auction Floor/101 Bid Below Deal Floor。
把 §3–§6 串成一条时间线:广播 → 应答 → 交易所结算 → 通知赢家(nurl)→ 渲染 → 计费(burl)→ 通知输家(lurl)。注意
tmax 是一道无情的闸门——晚到即作废;也注意拍卖发生在交易所侧,DSP 从头到尾只看得见自己那一份。
5. 创意怎么送达:adm 内联 vs nurl 拉取
赢了之后,那段真正要渲染的创意标记(markup)怎么到达页面?OpenRTB 允许两种,理解它们的取舍很重要:
5.1 adm 内联(现代主流)
DSP 在 bid response 里直接把 adm 填上。赢了,交易所手上已经有创意,直接下发渲染。nurl 此时只作赢价通知用(打点 + 回传成交价)。
- 优点:不需要为了拿创意再多一次网络往返,延迟最优;也不给「赢了但拿不到创意」留失败面。
- 代价:无论输赢,每个
bid都得把完整创意塞进响应,响应体更大——但在今天的带宽下这几乎总是划算的。
5.2 nurl 拉取(legacy,已不推荐)
DSP 省略 adm,只给 nurl。交易所判定它赢了之后,去回调 nurl,DSP 在这个回调的响应里才返回创意标记。
- 问题:把「拿创意」放到了曝光的关键路径上——多一次 RTT、多一个可能超时 / 失败的点,赢了却渲染不出来(
nurl超时)就是纯粹的收入损失。 - 现状:早期为省带宽这么干,如今除非有特殊缓存架构,基本都改用 §5.1 的内联。
一句话取舍:能内联就内联。
nurl的价值早已从「送创意」退化为「送通知 + 回传价格」,别再让它扛创意交付的活。
6. 三条 notice URL:win / billing / loss 各打各的点
这是 OpenRTB 里最能体现「协议设计者踩过坑」的一组字段。为什么需要三条回调 URL?因为「赢了」「被计费」「输了」是三个不同时刻、不同用途的事件:
| URL | 何时触发 | 典型用途 | 关键宏 |
|---|---|---|---|
nurl | 赢得拍卖时 | 回传成交价、赢价打点 | ${AUCTION_PRICE} |
burl | 曝光真正可计费(渲染/度量) | 计费与预算扣减的权威时点 | ${AUCTION_PRICE} |
lurl | 输掉拍卖时 | 拿到输价原因、优化出价模型 | ${AUCTION_LOSS} |
6.1 为什么把 nurl(赢)和 burl(计费)拆开
这是 2.5 引入 burl 的核心动机:「赢得拍卖」不等于「产生了一次真实曝光」。 赢了之后,广告可能因为用户划走太快、渲染失败、视口外没被看见(viewability)等原因根本没被计费。
- 早期只有
nurl,很多人拿它当计费点——于是「赢价」和「实际曝光」对不上账。 - 2.5 把二者解耦:
nurl一赢就打(适合回传价格、做实时预算的乐观预扣),burl只有当交易所认定这次曝光可计费时才打(适合做权威的花费记账)。
对 DSP 工程的落点:预算控制要以 burl 为准,nurl 只能作参考——否则你会把「赢了但没曝光」的量也算进花费,导致投放数据虚高。
6.2 成交价靠 ${AUCTION_PRICE} 宏回填
回到 §4 的问题:二价下 DSP 不知道自己实际付多少。解法是宏替换——DSP 在 nurl/burl 里写占位符 ${AUCTION_PRICE},交易所在回调前把它替换成真实结算价:
DSP 提交: https://dsp.example/win?imp=${AUCTION_IMP_ID}&price=${AUCTION_PRICE}
交易所回调:https://dsp.example/win?imp=1&price=3.05 ← 宏被替换成真实成交价
${AUCTION_PRICE} 常被交易所加密(防止买方反推别人的出价、以及防篡改),DSP 用事先约定的密钥解密。除它之外还有一组配套宏:
${AUCTION_ID}、${AUCTION_BID_ID}、${AUCTION_IMP_ID}:对上是哪次拍卖 / 哪个出价 / 哪个广告位。${AUCTION_SEAT_ID}、${AUCTION_AD_ID}:哪个席位 / 哪个创意赢了。${AUCTION_CURRENCY}:结算货币。${AUCTION_MBR}:Minimum Bid to win,恰好赢下所需的最低价(用于分析)。${AUCTION_LOSS}:输价原因码,只在lurl里有意义。
6.3 lurl:输了也要知道为什么
lurl(loss notice,2.5 引入)是给 DSP 的负反馈:输了,交易所回调它并带上 ${AUCTION_LOSS} 原因码。常见码:
| 码 | 含义 |
|---|---|
0 | 其实赢了(Bid Won) |
1 | 交易所内部错误 |
2 | 曝光机会已过期 |
100 | 出价低于公开底价 |
101 | 出价低于 deal 底价 |
102 | 输给了更高的出价 |
103 | 输给了一个 PMP deal |
2xx | 创意被过滤(品类/尺寸/域名等各种审核不过) |
对出价模型,lurl 是极有价值的训练信号:「输给更高价(102)」意味着可以适当加价,「创意被过滤(2xx)」意味着素材本身有问题,两者的优化动作完全相反。 没有 lurl,DSP 只能靠赢单反推,盲区很大。
7. ext:协议的扩展逃生口,与「方言」的由来
OpenRTB 规定:几乎每个对象都可以带一个 ext 字段,用来放规范没有覆盖的私有数据。这是这套协议能存活十几年的关键设计——它不需要每加一个新概念就发一个大版本,交易所可以先在 ext 里试验,成熟后 IAB 再考虑收编进主规范。
历史上很多今天的「正式字段」都是从 ext 长出来的:
- GDPR consent:早期放在
user.ext.consent/regs.ext.gdpr。 - SupplyChain(schain):先在
source.ext.schain,2.6 才转正到source.schain(见 schain 专文)。 - GPP(Global Privacy Platform):
regs.ext.gpp逐步成为跨法域隐私串的载体。
但 ext 也是**「方言」的根源**:同一个概念,交易所 A 放在 imp.ext.viewability、交易所 B 放在 imp.ext.pm.viewability,字段名和结构各不相同。这正是 Bidder 解析层必须做方言适配 / 归一化的原因——把各家 ext 里的私货,映射成引擎内部统一的一套模型,下游才不必关心它来自哪个交易所。
一句话:
ext是协议的优点也是它的税。它让 OpenRTB 灵活到能容纳整个行业的演进,也让每个买方都得为「对接第 N 个交易所」付一份归一化成本。
8. 版本演进:2.5 → 2.6 →(3.0 / AdCOM)
工程上你几乎只会碰到 2.x,但知道演进主线能帮你判断字段该去哪找:
- 2.5(2016):把地基打牢的一版——引入
burl/lurl(§6)、metric、source对象、以及一套标准的 no-bid / loss 原因码。今天大量交易所仍以 2.5 为基线。 - 2.6(2022 起持续小版本):把这些年在
ext里跑成熟的东西收编进主规范——source.schain、regs.gpp/gpp_sid、以及一批面向 CTV / 视频的字段增强(imp.video里的 podding 等)。方向很清晰:把隐私与供应链透明度变成一等公民。 - 3.0(重构,落地缓慢):一次架构级重写。核心思想是分层——把「怎么传」(Layer-4 传输,可选 Protobuf)和「传什么广告对象」分开,后者独立成 AdCOM(Advertising Common Object Model):
imp、site/app、创意等被抽成跨协议复用的通用对象模型。理念更干净,但因为迁移成本巨大,线上普及远慢于 2.x。
给工程师的实用结论:按 2.6 的字段位置去理解和归一化,把 3.0/AdCOM 当作「未来的对象模型词汇表」来读——当你看到某个交易所在 ext 里用 AdCOM 风格的对象时,就不会陌生。
9. 常见误解 ↔ 正解
| 常见误解 | 正解 |
|---|---|
| OpenRTB 里 DSP 决定谁赢 | 交易所主持拍卖、定胜负与成交价;DSP 只报 price |
bid.price 就是最终花的钱 | 二价下不是;真实成交价靠 ${AUCTION_PRICE} 宏回传(见 §4/§6.2) |
nurl 就是用来计费的 | nurl=赢价通知,计费应以 burl 为准(赢 ≠ 曝光,见 §6.1) |
创意必须靠 nurl 回调去拿 | 现代主流是 adm 内联;nurl 拉取是 legacy、多一跳(见 §5) |
imp.id 和请求的 id 是一回事 | 前者是广告位在本请求内的局部编号(bid.impid 对它),后者是请求全局 ID |
user.id = buyeruid | 一个是卖方视角 ID、一个是买方视角 ID,靠 cookie matching 映射 |
site 和 app 可以同时出现 | 互斥,只出现其一——一次曝光只在一个环境里 |
| schain 是 2.6 才有的东西 | 概念更早,先活在 source.ext.schain,2.6 才转正到 source.schain |
| 输了就没有反馈 | lurl + ${AUCTION_LOSS} 会告诉你为什么输,是出价模型的关键训练信号 |
| 现在大家都用 OpenRTB 3.0 | 线上主力仍是 2.5 / 2.6;3.0/AdCOM 理念先进但落地缓慢 |
10. 一张速查表
把全篇压成一张随手可查的表:
| 你关心的问题 | 看哪个字段 / 对象 | 出现在 |
|---|---|---|
| 要卖的广告位 | imp[](+ banner/video/audio/native) | request |
| 底价 | imp.bidfloor / imp.pmp.deals[].bidfloor | request |
| 私有交易 / Deal ID | imp.pmp.deals[](id 即 Deal ID) | request |
| 曝光环境 | site(网页)/ app(应用),二选一 | request |
| 设备 / 广告标识 / 限跟踪 | device(ifa/lmt/dnt/geo) | request |
| 用户身份(买卖两侧) | user.id / user.buyeruid | request |
| 供应链路径 | source.schain(2.6) | request |
| 隐私法规 | regs(coppa / ext.gdpr / gpp) | request |
| 时间预算 | tmax | request |
| 拍卖类型 | at(1 一价 / 2 二价+) | request |
| 出价 | seatbid[].bid[].price | response |
| 出价对应哪个位 | bid.impid(对上 imp.id) | response |
| 创意标记 | bid.adm(内联) | response |
| 品牌安全 / 竞品 | bid.adomain[] / bid.cat[] | response |
| 命中的 Deal | bid.dealid | response |
| 不出价原因 | nbr(+ HTTP 204) | response |
| 赢价通知 / 回传成交价 | bid.nurl + ${AUCTION_PRICE} | notice |
| 计费(权威花费) | bid.burl | notice |
| 输价原因 | bid.lurl + ${AUCTION_LOSS} | notice |
| 私有 / 未覆盖字段 | 任意对象的 ext | 两端 |
记住三条主线,这份协议就不会乱:
- 请求描述机会、响应描述出价——骨架分别是
imp[]和seatbid[].bid[]。 - 交易所主持、DSP 应答——报价的是 DSP,定胜负与成交价的是交易所(这也是 last look 的协议土壤)。
- 赢 / 计费 / 输是三件事——
nurl/burl/lurl各打各的点,成交价靠${AUCTION_PRICE}宏回传。
下次再在别的文章里看到 pmp.deals[]、source.schain、${AUCTION_PRICE},你就知道它们在这棵对象树上的确切位置了。
延伸阅读
同系列(Bidding 竞价引擎链路)——协议进了引擎之后:
- Bidder 竞价服务架构:把 Bidder 拆成七级漏斗的总览,本文正是它的输入格式。
- Bidder 解析层(Parse):竞价引擎怎么解析和归一化这份请求(含各交易所
ext方言适配)。
程序化生态系列——都踩在这份协议上:
- 程序化广告生态全景:谁在收发这份 OpenRTB——DSP / SSP / ADX 的角色与链路。
- 程序化交易模式:RTB / PMP / PD / PG:
imp.pmp.deals[]与at在不同卖法下怎么用。 - Last Look 深挖:
at从二价倒向一价的来龙去脉,以及「交易所主持」为何留出偷看空间。 - 供应链透明度(下):OpenRTB schain:
source.schain对象的逐跳核验,本文只在字段层点到。 - RTA 精讲:在 OpenRTB 出价之前那道私有过滤,与
buyeruid匹配率坍塌的背景。
规范与一手资料:
- IAB Tech Lab. OpenRTB 2.6 Specification:bid request / response 对象模型、
at、notice URL 与宏的权威定义。 - IAB Tech Lab. OpenRTB 3.0 & AdCOM:分层架构与广告通用对象模型(AdCOM)。
- IAB Tech Lab. OpenRTB Native Ads Specification:
imp.native里那份原生资产规格。
附录:术语表
- OpenRTB:IAB Tech Lab 维护的实时竞价标准协议,规定 bid request / response 的 JSON 对象模型与结算语义。
- bid request / bid response:交易所广播的「曝光机会」/ DSP 应答的「出价」,OpenRTB 的核心一来一回。
imp(impression):一个广告位,请求的核心;banner/video/audio/native四选一描述形态。seatbid/seat(席位):按买方席位分组的出价数组;一个 DSP 可代表多个 seat。at(auction type):拍卖类型,1一价 /2二价+。bidfloor(底价):出价的硬门槛,低于它不入围。tmax:交易所给 DSP 的作答时间预算(毫秒),超时响应作废。adm(ad markup):创意标记(HTML / VAST / 原生 JSON),现代主流内联在响应里。nurl(win notice):赢价通知 URL,常用来回传成交价。burl(billing notice):计费通知 URL,曝光真正可计费时才打——权威花费记账应以它为准。lurl(loss notice):输价通知 URL,带${AUCTION_LOSS}告知输因。${AUCTION_PRICE}:成交价宏,交易所回调前替换成真实结算价(常加密)。nbr(no-bid reason):不出价原因码。user.id/buyeruid:同一用户在卖方 / 买方两侧的 ID,靠 cookie matching 映射。ifa/lmt/dnt:设备广告标识 / 限制广告跟踪 / 请勿跟踪。pmp/dealid:私有市场容器 / 命中的交易 ID,把线下 deal 激活进实时竞价。source.schain:供应链对象,逐跳记录库存经过的中介(详见 schain 专文)。ext:任意对象上的扩展字段容器,私有字段与新概念的试验田,也是「方言」之源。- AdCOM(Advertising Common Object Model):OpenRTB 3.0 中抽出的跨协议通用广告对象模型。