Blog · Supply chain
ads.txt vs app-ads.txt is the domain file or the bundle file, and the request picks which.
ads.txt vs app-ads.txt is not a rename. Web inventory is authorized in example.com/ads.txt. App inventory is authorized in the developer's app-ads.txt, keyed by app.bundle. A request that carries both files in your head and only one on the wire checks the wrong allowlist.
The request says which file exists
A site request has site.domain. The allowlist is that domain's ads.txt. An app request has app.bundle. The allowlist is app-ads.txt for that bundle. OpenRTB still forbids sending more than one of site, app, and dooh. If both site and app are present, you do not have a puzzle about which file to read. You have an invalid request, and bidders drop it before the file matters.
The line shape is the same idea in both files: an advertising system domain, a seller account id, and DIRECT or RESELLER. DIRECT means that account sells this inventory. RESELLER means the account may resell it. The first payment hop on the schain is the node whose hp is not 0. That hop's asi and sid have to appear as DIRECT or RESELLER in the file the inventory type names. A match against the website file while the request is an app is a pass on the wrong publisher.
sellers.json still sits on the seller's domain and answers who the account is. ads.txt and app-ads.txt answer whether that account may sell this site or this app. The three-file loop is the schain post. This page is only the choice of allowlist.
The cache paths are different on purpose
rtblint validate --resolve --cache does not download either file. Web lookups read ads/<site.domain>/ads.txt. App lookups read app-ads/<app.bundle>/app-ads.txt. Missing app-ads.txt for a bundle that has a payment hop is openrtb.resolve.app_ads_txt_unavailable. A file that does not list the first payment node is openrtb.resolve.app_ads_txt_unauthorized. The website equivalents are the ads.txt findings. Populate the directory that matches the object on the request.
A publisher brand that owns both a site and an app often has both files, with different account ids. Copying example.com/ads.txt into the app-ads cache slot makes CI green for a bundle the developer never authorized. Copying the app file onto the domain misses the website's sellers. Store them under the keys the CLI looks up, and refresh them when the publisher edits the live file. The validator will not notice a stale cache.
hp 0 is not the payment hop those findings check. A chain can list several nodes and only one of them is paid directly. Authorizing a non-payment node and missing the paid one is the miss. Read hp before you pick the line.
A developer domain is not the bundle
App-ads.txt is published on a developer domain, and the cache key RTBlint uses is the bundle string, not that domain. You fetch the file from the domain the store listing names, then you save it as app-ads/com.example.app/app-ads.txt. Looking up example.com/ads.txt because the company brand matches does not authorize com.example.app. The finding for a missing file is a warning that the hop could not be checked. The finding for a file that omits the paid node is an error.
DIRECT and RESELLER are not interchangeable. A RESELLER line allows that account to resell. A DIRECT line is the account the developer named as selling its own inventory through that system. Matching the right asi and the wrong relationship, or the right relationship and the wrong sid, is still unauthorized. The check is the pair on the first node whose hp is not 0.
CTV apps and mobile apps both use the bundle path when the request is an app object. A site.domain on a web wrapper around an app is a site request and reads ads.txt. Do not keep a single spreadsheet of authorized sellers and point every request at it. Split the cache the way the objects split.
What to check on the next app request
- If
app.bundleis set, resolve app-ads.txt, not the website ads.txt. - If
site.domainis set, resolve ads.txt for that domain. - Do not send site and app together and then pick a file.
- Match the first node with
hpnot 0 to a DIRECT or RESELLER line. - Keep the cache path
app-ads/<bundle>/app-ads.txtidentical to the bundle string, including case. - A green schain without
--resolvehas not opened either file.
Do not authorize the bundle with the website file
A company can own example.com and com.example.app and still list different sellers in each file. The website DIRECT line for an SSP does not authorize that SSP to sell the app. The app RESELLER line does not authorize the website. When a bid request is an app, only the bundle file is the allowlist. When a bid request is a site, only the domain file is. Checking both and passing if either matches will approve a seller the inventory owner did not name.
Put the live file in the cache before CI, under the exact domain or the exact bundle. A renamed bundle, a debug suffix, or a different store id is a different key and a missing file. Re-run with --resolve after the publisher edits the file. A cached copy from last quarter will keep blessing a seller who was removed, or keep failing a seller who was added.