Blog · Supply chain
ads.txt vs sellers.json vs schain is three files, and a complete chain can still fail the join.
ads.txt vs sellers.json vs schain is three artifacts that only work as a loop. A well-formed source.schain does not mean the seller is authorized. A populated ads.txt does not mean this request walked that path.
Each file is written by a different party
ads.txt is a text file the publisher hosts at the root of its domain, example.com/ads.txt. It lists advertising systems allowed to sell that inventory and the publisher account id in each. The app file is app-ads.txt, a different URL for a different inventory type. If a request claims to sell example.com through an exchange that file does not list, a buyer has grounds to treat the sale as unauthorized. The file is the publisher allowlist. It is not the path of this auction.
sellers.json is hosted by the advertising system, the SSP or exchange, at its own domain. It maps each seller account id that can appear in that system to a name, a domain, and whether the account is a publisher selling its own inventory or an intermediary reselling someone else's. ads.txt is the publisher's list of sellers. sellers.json is the seller's disclosure of accounts. They are designed to be cross-referenced. One cannot stand in for the other.
schain is the only one of the three that travels on the request. In OpenRTB 2.6 it is source.schain. In 2.5 it traveled as source.ext.schain. Each node has asi, the advertising system domain, which should match a sellers.json host; sid, the seller id in that system, which should match an entry in that file; and hp, whether this node is paid directly for the impression. complete of 1 asserts every hop from the publisher is represented. complete of 0 admits the chain is partial. A complete flag on a chain with a gap is worse than an honest 0.
The check is a loop, and it stops at the first break
For each node, ask whether asi has a sellers.json, whether sid is listed there, and whether the publisher ads.txt or app-ads.txt authorizes that system plus that account. If every hop resolves, the path is accountable. A break is a reason to block or to ask, not an automatic fraud verdict. The trust-stack guide is the field list. This is the request that looks complete and still fails the join.
A blank asi or sid, or two identical adjacent nodes, is a malformed chain before any file is fetched. An asi with no sellers.json, or a sid missing from the file that does exist, defeats verification even when complete is 1. Buyers who only test that the object parses will bid the dishonest chain and refuse an honest partial one, because 1 looks healthier than 0 in a spreadsheet.
The chain on the request can also be in the wrong place. A 2.6 reader looks at source.schain. A chain that exists only at source.ext.schain is invisible to that reader, which is openrtb.field.moved when you validate against a 2.6 snapshot. Writing both paths with different nodes means each reader trusts a different path. Copy one object. Do not maintain two.
The CLI will not fetch the files for you
rtblint validate can check that the SupplyChain object matches the snapshot you pinned. --resolve --cache checks hops against files you already stored: sellers/<asi>/sellers.json, ads/<domain>/ads.txt, and app-ads/<bundle>/app-ads.txt. Nothing is fetched. A green schain with resolve omitted is a shape check. It is not an authorization check. Populate the cache from the hosts you intend to trust, then turn the flag on.
app-ads.txt is not a rename of ads.txt. Web inventory reads the domain file. App inventory reads the bundle file. A schain node that you join to example.com/ads.txt while the request is an app with a bundle id is the wrong allowlist. Store both in the cache when you sell both. Do not point the app bundle at the website file because the publisher brand looks the same.
adagents.json, at /.well-known/adagents.json, is a later file: which sales agents may represent the publisher, with scope ads.txt does not express. It does not replace schain, and a blog that treats it as a fourth copy of sellers.json will authorize an agent the request never listed. The chain is still the path. The well-known file is who may broker. See the AdCP explainer for the agent side, and keep it off this join until the node list is honest.
A direct line and a reseller line are not the same account
sellers.json says whether the account is a publisher selling its own inventory or an intermediary reselling someone else's. ads.txt authorizes a system plus an account id. A schain sid that resolves to a different account than the one ads.txt lists for that system is a join on the wrong line. Match the account id, not the brand name in the file.
hp says whether that node is paid directly for the impression. It does not say the node is authorized. A paid hop can still be missing from ads.txt. An authorized hop can still be unpaid. Read hp after the asi, sid, and ads.txt checks, when you are deciding which hop in a clean path actually got paid. Supply path optimization is that second decision. It is not a substitute for the first.
The request's site.domain or app.bundle picks the file. A domain that does not match the page the user is on, or a bundle that does not match the app, makes a perfect schain describe the wrong publisher. Validate the schain shape, then resolve it against the inventory ids on the same request. A sample schain checked against a fixture ads.txt from a different domain will pass in CI and fail on the live publisher.
What a buyer can decide from one request
- Read
source.schainon 2.6. If the only chain is underext, the current path is empty. - Treat
complete0 as partial. Treatcomplete1 with a missing hop as the case to block. - Resolve
asito that system's sellers.json andsidto a listed account. A parse of the object is not that lookup. - Authorize the account in the publisher ads.txt or the app app-ads.txt, matching the inventory type on the request.
- Drop empty nodes and duplicate adjacent nodes before you debate payment.
- Run
--resolve --cacheonly after the cache holds the files. The validator does not download them.
RTBlint is not an IAB validator. A pass on the object means the snapshot accepts the shape. Authorization is the cache, or a buyer system that fetched the same files. Supply path optimization starts after the path is legible. It does not start at a version string.