Chain of Sale — ad inventory chain of custody

Ad inventory has a chain of custody. Nobody walks it end to end.

Chain of Sale follows ads.txt → sellers.json → adagents.json for every domain you care about and reports where the chain breaks — sorted by who can actually fix each break.

$ cos check domains.txt --out out/

Runs on your machine. Text, CSV and JSON out. No account, no upload, nothing to deploy.

The gap

Four files decide who may sell and who may represent inventory. Two of them are new.

The IAB stack2017–2019
ads.txt
Hosted by the publisher. Which ad systems may sell this inventory.
sellers.json
Hosted by each ad system. What those seller IDs actually mean.
The AdCP stack2025–2026
adagents.json
Hosted by the publisher or its manager. Which AI sales agents may represent it.
brand.json
Hosted by the brand owner. The buy-side counterpart.
The two stacks do not cross-check each other — not in the spec, not in any existing tool. Every validator on the market checks one layer in isolation. A publisher can pass all of them and still be unreachable to a buyer's agent walking the chain.

Evidence

A 100-domain sample, measured before any of this was designed.

12.7%
of records break
1,201 ads.txt records cross-checked → 153 findings
50%
of domains affected
42 of the 84 domains with a reachable ads.txt had at least one finding
58%
from 10 ad systems
The top 5 alone produce 39%. The distribution has a very short head.
36 of 41
manifests via two companies
Of 41 domains with an adagents.json, only 5 serve their own — and one of those five serves an empty file.

Every finding is sorted by who can fix it

68% of relationship mismatches come from five SSPs declaring a generic INTERMEDIARY where the publisher declared DIRECT. The publisher cannot fix those. A report that presents them as the publisher's errors teaches the reader to ignore the whole report.

103 you can fix — your ads.txt, your manifest, your crawler access 50 partner must fix — an SSP's sellers.json, a manager's manifest

The split across the 153 findings in the sample, by the same rule the tool applies to every run.

What a chain break looks like

The authorization exists. The chain cannot be walked.

britannica.com you can fix agentic layer

A buyer's agent needs to know who may represent this inventory. Here is what it does, step by step:

  • Fetches britannica.com/.well-known/adagents.json — no manifest published.
  • Falls back to ads.txt, reads MANAGERDOMAIN=britannica.com — itself.
  • Stops. There is nowhere left to look.
  • Meanwhile cafemedia.com/.well-known/adagents.json does list britannica.com as a property — 2.6 MB and ~6,800 domains that the agent will never open.
Fix: one line. MANAGERDOMAIN=cafemedia.com

No existing tool reports this, because reporting it means holding both stacks in view at once. Both files are individually valid.

The report

Plain text first. If it isn't useful in a terminal, no dashboard would save it.

CHAIN OF SALE — 2026-09-05 12:53 UTC

9 of 13 domains served ads.txt   (3 of the 4 misses are ad systems — expected; 1 bot-challenged)
135 records checked → 35 findings (26%)

    16  you can fix       chain break, crawlers blocked, seller ID not in sellers.json
    19  partner must fix  DIRECT vs INTERMEDIARY, manager publishes no manifest

  by severity   high 2   medium 18   info 15

AGENTIC LAYER
     4  own manifest
     1  via manager       cafemedia.com 1
     7  no manifest published
     1  chain broken

CHAIN BREAKS (high severity)
  britannica.com    britannica.com is listed in https://cafemedia.com/.well-known/adagents.json,
                    but its ads.txt declares MANAGERDOMAIN=britannica.com (itself). A buyer's
                    agent looks at britannica.com, finds no manifest, and stops — it never
                    reaches cafemedia.com. Fix: MANAGERDOMAIN=cafemedia.com.
  craftgossip.com   ads.txt declares MANAGERDOMAIN=mediavine.com, and mediavine.com publishes a
                    manifest — but that manifest does not list craftgossip.com.

WHERE THE FINDINGS COME FROM
  indexexchange.com    5  18% cumulative    5 DIRECT vs INTERMEDIARY
  appnexus.com         3  29% cumulative    3 seller ID not in sellers.json
  freewheel.tv         3  39% cumulative    2 DIRECT vs INTERMEDIARY, 1 seller ID not found
  …

A bot-challenged fetch stays bot challenged all the way into the report — it is never reported as a missing file, because the remediation is "ask the publisher to allowlist crawlers", which has nothing to do with fixing a file.

The interface

One command, at two scales.

A portfolio

Every domain you represent or buy from, one per line.

$ cos check domains.txt --out out/

One domain, in detail

The full chain walk, every record, every lookup.

$ cos check britannica.com \ --domain britannica.com

Output is text, CSV and JSON. The 100-domain sample that produced the numbers above is kept as a fixture, so every figure on this page can be reproduced offline.

Reality check

The agentic layer currently lives almost entirely inside two managed networks. Independent publishers have nothing to migrate yet. That makes the network-and-agency case the plausible one today, and the publisher self-serve case speculative.

One question is genuinely open: nobody has confirmed that a DIRECT/INTERMEDIARY mismatch actually costs a publisher bids. If it doesn't, a third of the findings are noise and their severity should drop. If you work in ad ops and know the answer, that is the most useful thing you could tell us.