A buyer agent finishes an AdCP media buy, fetches the publisher's /.well-known/adagents.json, and sees an exclusive authorization with delegation_type: "direct". It fetches the seller's brand.json for the same property and reads relationship: "delegated". Both requests return HTTP 200. Both pass JSON Schema. Nobody in the pipeline throws.
The buyer thinks it bought a direct publisher path. The operator's public identity file says it is selling on delegated terms. That disagreement is not a parse error. It is the failure mode AdCP's Property Governance was written to prevent, and most tooling still treats the two files as independent artifacts.
Two names for one commercial arrangement
In the programmatic stack, trust was split across publisher and seller files long before agents showed up. ads.txt lives on the publisher. sellers.json lives on the exchange. A buyer reconciled them when both existed.
AdCP replaces the flat seller side with brand.json and replaces the flat publisher side with adagents.json at /.well-known/adagents.json. The spec is explicit about the pairing:
| Programmatic | AdCP equivalent | Who hosts it |
|---|---|---|
| ads.txt | adagents.json with delegation_type
|
Publisher |
| sellers.json | brand.json properties with relationship
|
Operator |
On the publisher file, delegation_type describes how the agent is authorized: direct, delegated, or ad_network. On the operator file, relationship describes how the operator relates to that property. For delegated and network paths, the values are meant to match. First-party inventory uses relationship: "owned" on the operator side, which has no mirror value in adagents.json.
The field names differ on purpose. The publisher delegates authority; the operator declares its relationship. Same contract, two perspectives. When they disagree, you do not get malformed JSON. You get two true statements about different commercial realities.
What "direct" and "delegated" actually commit to
direct in adagents.json means the publisher treats this endpoint as a direct way to buy from them, even if a third party operates the software behind it. delegated means the agent is authorized to sell on the publisher's behalf. ad_network means network-mediated inventory with its own governance rules.
relationship: "direct" in brand.json is the operator's mirror claim for the same slice of inventory. relationship: "delegated" is the operator saying it resells under publisher authorization. owned is only on the operator side: inline first-party inventory with no matching delegation_type on the publisher file.
Scoped fields make the mismatch worse, not better. An authorized_agents entry can narrow authorization with placement_ids, placement_tags, countries, or effective_from / effective_until. brand.json carries the same property identity with its own relationship label. Validating shape on either file ignores whether the scoped slice and the relationship label describe the same path.
Exclusive flags compound it. A publisher can mark an agent exclusive: true with delegation_type: "direct" while the operator's brand.json lists relationship: "delegated" for the same domain. Schema validation on either file is silent. A buyer reading only one side can overpay for a path that is not exclusive, or under-audit a resale path that looks direct in adagents.json alone.
How I catch it before the bid request ships
AdCP negotiation mostly lives above the bid stream. What executes downstream is still OpenRTB: a deal in imp.pmp.deals, a PG line, or an ordinary tag. The trust argument happens in the files; the money moves in the request.
My first step is boring and manual: fetch both files for the same property identifier, match the agent URL or operator domain, and compare delegation_type to relationship on the overlapping property row. The Ad Context Protocol explainer on RTBlint walks the stack and names adagents.json as the trust artifact beside ads.txt, sellers.json, and schain. That explainer is the map; the comparison is still yours.
Second step: validate the bid request the deal resolves to. Paste the JSON into the RTBlint OpenRTB tester or run the CLI against the dated snapshot your exchange runs. A clean bid request does not prove the agent was authorized. It proves the auction payload is syntactically valid for OpenRTB. The gap between "valid request" and "authorized path" is where agentic buys fail quietly.
Third step: trace the mapping from the negotiated buy to the fields on the wire. The AdCP to OpenRTB mapping guide shows how package and format claims land in imp.video.plcmt, deal objects, and related fields. A mismatch in delegation rarely appears as its own OpenRTB field. It shows up as a deal ID on inventory the operator was never authorized to sell as direct.
I maintain RTBlint for OpenRTB validation. It catches malformed bid requests and version drift. It does not today cross-fetch adagents.json and brand.json in one pass. That bilateral check is still operational hygiene, not a linter rule id you can grep for in a report.
Where to go next
If you are wiring an AdCP buyer agent, treat trust reconciliation as part of deal confirmation, not as a post-mortem after fill drops. Start with the bid request object reference when you need to see where agent-sourced demand actually lands: site/app, imp, source, and regs are the fields exchanges still parse.
When format claims disagree with delivery, the bug is often upstream of trust files but shows up in the same QA pass. The placement vs plcmt guide explains why OpenRTB 2.6 moved enumerated video semantics into AdCOM and why echoing a legacy integer mislabels inventory. Agentic buys that map video placement types into imp.video.plcmt can validate while misdescribing the slot; that is a different failure, but the same lesson applies: schema-clean is not contract-clean.
For the schain angle on the same trust stack, see adagents.json does not audit schain. OpenRTB carries schain on the request. adagents.json lives on the publisher origin. Nothing in the bid stream cross-checks authorization in adagents.json against nodes in source.schain. delegation_type vs relationship is the bilateral file pair AdCP defined; schain is the hop-level artifact IAB already had. A path can fail both checks independently.
Agentic authorization is accumulating in public JSON at /.well-known/adagents.json. Operators answer with brand.json. Until something compares the two on every buy, returning 200 on both files is the easy part. Agreeing on what "direct" means is the work.
Top comments (0)