DEV Community

Aleksander Sekowski
Aleksander Sekowski

Posted on

AdCP Scopes the Buy in adagents.json. OpenRTB imp Does Not Carry That Scope Back.

A buyer agent finishes an AdCP media buy against a publisher's Prebid Sales Agent. Negotiation narrowed the deal to one placement_id, US only, and a window that ends next quarter. The agent fetched /.well-known/adagents.json, found an authorized_agents row whose property_ids and optional placement_ids matched, and stored the authorization snapshot.

The sell side then emits an OpenRTB bid request. It validates. The exchange accepts it. Nobody compares the live imp object to the scoped slice that was authorized five minutes earlier in JSON on the publisher origin.

That gap is not a typo in one field. It is two layers answering different questions with different grammars.

What adagents.json actually scopes

In AdCP, publisher authorization is explicit. An authorized_agents entry names the sales agent URL, an authorization_type discriminator, and the selector that carries inventory scope: property_ids, property_tags, inline properties on the entry, or federated publisher_properties selectors. Optional qualifiers narrow further: placement_ids, placement_tags, countries, collections, effective_from, effective_until, and exclusive.

Those fields are machine-readable promises about which agent may represent which slice. They do not ride inside OpenRTB by default. The spec even calls out that inline_properties is the one authorization pattern whose companion array is named properties, not inline_properties, because validators reject the obvious-but-wrong shape. A model generating adagents.json from examples often emits the wrong array name, passes casual JSON lint, and still fails a strict AdCP validator. That is a file-level bug.

The failure this post is about happens after both files look fine: the buy was scoped in adagents.json, but the auction payload never had to repeat the scope.

What OpenRTB actually enforces

OpenRTB enforces what is on the wire at impression time: imp structure, video or banner constraints, deal objects under imp.pmp, privacy under regs and user, and supply path under source.schain. Sellers reconcile schain nodes against ads.txt and sellers.json. None of those objects require a copy of the publisher's placement_ids array or the effective_until timestamp from adagents.json.

You might map placement identity to imp.tagid or an extension. That mapping is integration policy, not a cross-file guarantee. If the agent stack omits it, constraint loss is silent: everything agreed in negotiation is not expressed in the impression, and anything not expressed is not enforced downstream no matter how precise the AdCP conversation was.

The same split shows up on the trust side. adagents.json answers whether this agent was allowed to sell this property slice. schain answers which seller domains handled this impression. A stack that validates schain syntax but never re-checks agent authorization has an auditable path and an unauditable agreement attached to it.

How I catch it

I treat the emitted bid request as the only observable output of the handoff. Paste the JSON into the OpenRTB tester or run rtblint validate on the bytes after serialization, not on the object you think you built.

That pass is structural. It catches version drift, type mistakes, and schain defects such as an empty node identifier (openrtb.schain.node.identifier_empty) or an unexpected ver value (openrtb.schain.ver_unexpected). It does not fetch adagents.json and prove the live imp still sits inside the authorized placement and country slice.

Agent workflows that let a model assemble AdCP output and OpenRTB in one pipeline still need that deterministic check on the RTB object. RTBlint (cargo install rtblint, npm install rtblint-core, MCP rtblint-mcp) is built for the OpenRTB payload, ARTF envelopes, and AdCP discovery via get_adcp_capabilities. It is independent of IAB Tech Lab and of AgenticAdvertising.org. It is not a substitute for comparing authorization time to execution time; it is the part that keeps the auction JSON from shipping with plausible-but-invalid shape while you write the policy layer that re-applies adagents scope.

My manual step stays boring: for each fixture product type, assert the fields you care about (tag id, deal id, geo, duration caps) are present on imp after mapping, then validate. If the mapping guide says video placement types become video.plcmt, confirm plcmt is set and that deprecated placement is not standing in for it.

Where to go next

The AdCP to OpenRTB mapping guide walks field-level handoffs and lists where the mapping silently breaks: version drift, names that look mapped but are not, constraint loss, and privacy signals that never leave the seller ad stack. Read that alongside the Ad Context Protocol explainer for where adagents.json sits relative to MCP tools and the Prebid Sales Agent.

For the execution-time trust stack that still runs in parallel, sellers.json, ads.txt, and schain explains how hop-level checks complement publisher agent authorization. IAB agentic advertising standards maps AAMP and ARTF next to AdCP without collapsing them into one file.

Until your emit boundary re-expresses every adagents qualifier on the impression, a scoped AdCP buy and a permissive OpenRTB request can both return 200. The authorization was real. The auction just was not required to remember it.

Top comments (0)