Ads.txt & App-ads.txt Validator + sellers.json Check avatar

Ads.txt & App-ads.txt Validator + sellers.json Check

Pricing

from $2.00 / 1,000 domain auditeds

Go to Apify Store
Ads.txt & App-ads.txt Validator + sellers.json Check

Ads.txt & App-ads.txt Validator + sellers.json Check

Validate ads.txt or app-ads.txt for any list of domains against the IAB Tech Lab 1.1 spec: syntax errors, duplicates, DIRECT/RESELLER conflicts, redirect and content-type problems, plus seller ID checks against exchanges' public sellers.json. Respects robots.txt; no login or proxies.

Pricing

from $2.00 / 1,000 domain auditeds

Rating

0.0

(0)

Developer

Doyle Pounders

Doyle Pounders

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

11 hours ago

Last modified

Categories

Share

Ads.txt & App-ads.txt Auditor with sellers.json Cross-check

Check any list of publisher or app-developer domains for ads.txt / app-ads.txt problems that cost ad revenue: syntax errors, invalid DIRECT/RESELLER values, duplicates, conflicting relationships, wrong Google IDs, bad redirects, wrong content type. It also checks each seller account ID against the ad system's public sellers.json: is the ID listed at all, and does DIRECT/RESELLER match the seller type?

  • Follows the IAB Tech Lab ads.txt 1.1 spec (spec page, PDF) and sellers.json 1.0 (spec page).
  • Respects robots.txt (RFC 9309) on every host it contacts, including redirect targets and sellers.json hosts. A disallowed file is reported as blocked_by_robots and is never fetched or charged.
  • One row per domain with a heuristic health score, counts, findings with line numbers, and a sellers.json summary. Every finding for every domain also goes to a FINDINGS.csv file.
  • No login, no proxies, no personal data. Seller names and contact emails from sellers.json are never stored or output.

Independent tool. Not affiliated with or endorsed by IAB Tech Lab, Google, or any ad exchange. "ads.txt" and "sellers.json" are IAB Tech Lab specifications; product and company names belong to their owners.

Current as of 2026-10-26. Behaviour, limits and data-source rules were checked against the sources linked below on that date.

Who it's for

  • Publishers and app developers: catch broken lines before they block demand. Google's Ad Manager help says an ads.txt "will be ignored by crawlers that respect the robots.txt standard" when robots.txt disallows it. This Actor sees it the same way.
  • Ad ops and monetisation agencies: audit every client domain in one run, then re-run on a schedule.
  • SSPs, exchanges and supply-path analysts: see which DIRECT lines point at accounts that sellers.json lists as INTERMEDIARY, or at IDs that aren't listed at all.

Real run (2026-10-27, 8 inputs, maxSellersJsonMb: 150)

Input (demo/INPUT.json):

{
"domains": [
"nytimes.com",
"cnn.com",
"washingtonpost.com",
"forbes.com",
"bbc.com",
"wikipedia.org",
"example.com",
"localhost"
],
"checkSellersJson": true,
"maxSellersJsonMb": 150
}
DomainFile statusHealth (heuristic)RecordsErrorsWarningssellers.json match %
nytimes.comvalid93300790
cnn.comvalid70855011495.7
washingtonpost.comhas_errors7720311895.8
forbes.comvalid808802083.7
bbc.comvalid95540596.3
wikipedia.orgnot_found–––––
example.comnot_found–––––
localhostinvalid_input–––––

The run took 24.5 s and made 116 HTTP requests (about 15 s of that was waiting on aps.amazon.com's HTTP 503 retries). It streamed 12 sellers.json files, including Google's (101.73 MB, 954,129 sellers). indexexchange.com's sellers.json is disallowed by its robots.txt (blocked_by_robots), and aps.amazon.com's robots.txt returned HTTP 503 (robots_unreachable). Both were skipped, as RFC 9309 requires. yieldmo.com answered HTTP 202 with a body that isn't JSON (invalid_json).

Example findings from the same run (FINDINGS.csv, washingtonpost.com):

LineSeverityCodeMessageLine content
68errorINVALID_RELATIONSHIPField 3 must be DIRECT or RESELLER (got "DIRECT/RESELLER").conversantmedia.com, 101272, DIRECT/RESELLER, 03113cd04947736d
100warningGOOGLE_CERT_IDGoogle's certification authority ID is f08c47fec0942fa0.google.com, pub-1493799638656017, RESELLER, f08c47fec0942fa
100warningCONFLICTING_RELATIONSHIPThe same ad system + account ID is listed as both DIRECT and RESELLER.google.com, pub-1493799638656017, RESELLER, f08c47fec0942fa

Output

One dataset row per input. This is the full bbc.com row from the run above, unedited:

{
"domain": "bbc.com",
"input": "bbc.com",
"fileType": "ads.txt",
"status": "ok",
"fileStatus": "valid",
"healthScore": 95,
"errors": 0,
"warnings": 5,
"infos": 0,
"totalLines": 82,
"blankLines": 23,
"commentLines": 3,
"recordCount": 54,
"directCount": 39,
"resellerCount": 15,
"duplicateCount": 0,
"uniqueAdSystems": 25,
"placeholder": false,
"findingCodes": {
"SELLER_TYPE_MISMATCH": 4,
"SELLER_ID_NOT_IN_SELLERS_JSON": 1
},
"variables": {
"contact": true,
"ownerDomain": "bbc.com",
"managerDomains": [],
"subdomains": [],
"inventoryPartnerDomains": []
},
"topAdSystems": [
{
"domain": "openx.com",
"records": 7
},
{
"domain": "pubmatic.com",
"records": 5
},
{
"domain": "triplelift.com",
"records": 5
},
{
"domain": "indexexchange.com",
"records": 4
},
{
"domain": "tritondigital.com",
"records": 4
},
{
"domain": "rubiconproject.com",
"records": 3
},
{
"domain": "appnexus.com",
"records": 2
},
{
"domain": "aps.amazon.com",
"records": 2
},
{
"domain": "liveintent.com",
"records": 2
},
{
"domain": "taboola.com",
"records": 2
}
],
"sellersJson": {
"recordsChecked": 27,
"found": 26,
"notFound": 1,
"typeMismatch": 4,
"confidential": 0,
"domainDiffers": 0,
"adSystemsChecked": [
"appnexus.com",
"google.com",
"kargo.com",
"media.net",
"openx.com",
"pubmatic.com",
"rubiconproject.com",
"teads.tv",
"triplelift.com",
"xandr.com"
],
"adSystemsUnavailable": [
{
"adSystem": "aps.amazon.com",
"status": "robots_unreachable"
},
{
"adSystem": "indexexchange.com",
"status": "blocked_by_robots"
},
{
"adSystem": "yieldmo.com",
"status": "invalid_json"
}
],
"adSystemsSkipped": 12,
"matchRatePct": 96.3
},
"findings": [
{
"line": 27,
"severity": "warning",
"code": "SELLER_TYPE_MISMATCH",
"message": "Listed as DIRECT, but sellers.json says INTERMEDIARY.",
"content": "openx.com, 540222416, DIRECT",
"source": "sellers.json"
},
{
"line": 32,
"severity": "warning",
"code": "SELLER_TYPE_MISMATCH",
"message": "Listed as DIRECT, but sellers.json says INTERMEDIARY.",
"content": "pubmatic.com, 157339, DIRECT",
"source": "sellers.json"
},
{
"line": 36,
"severity": "warning",
"code": "SELLER_TYPE_MISMATCH",
"message": "Listed as DIRECT, but sellers.json says INTERMEDIARY.",
"content": "rubiconproject.com, 17264, DIRECT",
"source": "sellers.json"
},
{
"line": 50,
"severity": "warning",
"code": "SELLER_TYPE_MISMATCH",
"message": "Listed as DIRECT, but sellers.json says INTERMEDIARY.",
"content": "triplelift.com, 918-EB, DIRECT",
"source": "sellers.json"
},
{
"line": 52,
"severity": "warning",
"code": "SELLER_ID_NOT_IN_SELLERS_JSON",
"message": "Account ID not found in triplelift.com's sellers.json.",
"content": "triplelift.com, 14807-EB, DIRECT",
"source": "sellers.json"
}
],
"findingsTruncated": false,
"note": null,
"http": {
"requestedUrl": "https://bbc.com/ads.txt",
"finalUrl": "https://www.bbc.com/ads.txt",
"redirectChain": [
"https://bbc.com/ads.txt",
"https://www.bbc.com/ads.txt"
],
"statusCode": 200,
"contentType": "text/plain",
"bytes": 3634
},
"checkedAt": "2026-10-27T03:17:43.159Z",
"elapsedMs": 350
}

A domain without the file gives a free row:

{
"domain": "wikipedia.org",
"input": "wikipedia.org",
"fileType": "ads.txt",
"status": "not_found",
"fileStatus": "not_found",
"note": "No ads.txt published (HTTP 404).",
"http": {
"requestedUrl": "https://wikipedia.org/ads.txt",
"finalUrl": "https://en.wikipedia.org/ads.txt",
"redirectChain": [
"https://wikipedia.org/ads.txt",
"https://www.wikipedia.org/ads.txt",
"https://en.wikipedia.org/ads.txt"
],
"statusCode": 404,
"contentType": "text/html; charset=utf-8"
},
"checkedAt": "2026-10-27T02:48:07.532Z",
"elapsedMs": 139
}

status is ok when a text file was retrieved and parsed (charged). Other values are free: not_found (404/410), restricted (401/403, often bot protection, which is never bypassed), blocked_by_robots, robots_unreachable (robots.txt returned 5xx, so RFC 9309 says skip), redirect_violation, html_instead_of_text, unreachable, http_error, too_large, invalid_input and skipped (not processed because your Max cost per run was reached). fileStatus is valid (no errors), has_errors or invalid_content_type for charged rows.

sellersJson.adSystemsUnavailable lists each ad system whose sellers.json could not be used, with its status: not_found, http_error, blocked_by_robots, robots_unreachable, redirect_refused (it redirected to an invalid or non-public URL, which is never followed), too_large, invalid_json or error.

Key-value store:

  • FINDINGS.csv: every finding, one line each: domain, line, severity, code, message, line content.
  • SUMMARY: status counts, per-domain scores, top finding codes and each sellers.json file's status and size.

What is checked

Errors: HTML instead of text, wrong Content-Type (must be text/plain), fewer than 3 fields, URL or invalid domain in field 1, missing account ID or one with whitespace, relationship that isn't DIRECT/RESELLER, SUBDOMAIN not under the root domain, empty variables, a file with no records (ads.txt 1.1 asks for the placeholder.example.com, placeholder, DIRECT, placeholder line instead).

Warnings: duplicates, the same account as both DIRECT and RESELLER, more than 4 fields, Google lines that use googlesyndication.com/doubleclick.net instead of google.com, Google IDs not in pub- + 16-digit form, Google certification ID not f08c47fec0942fa0 (Google AdSense help), unknown variables, several OWNERDOMAINs or global MANAGERDOMAINs, invalid UTF-8.

sellers.json cross-check: SELLER_ID_NOT_IN_SELLERS_JSON and SELLER_TYPE_MISMATCH (warnings: DIRECT vs INTERMEDIARY, RESELLER vs PUBLISHER). Info: SELLER_DOMAIN_DIFFERS (DIRECT seller's domain ≠ OWNERDOMAIN/root, normal for multi-site groups) and SELLER_CONFIDENTIAL.

Info: placeholder record, no OWNERDOMAIN, UTF-8 BOM, lower-case variable names.

Redirects: any number of redirects are allowed inside the root domain, plus at most one hop to another domain. A second hop after leaving the root domain gives redirect_violation, as the IAB spec requires. Google's own crawler is stricter: it ignores the file after a second redirect even within the same domain (Ad Manager help). Check http.redirectChain in each row if you need Google's stricter rule.

Safety: the Actor only fetches public http(s) URLs on ports 80/443. Redirects to other schemes (file:, data:, javascript:), malformed URLs, IP addresses, localhost, and names that resolve to private, loopback or link-local addresses are refused, and the row gets a free status instead of failing the run. The DNS check runs before each fetch, so it does not cover DNS rebinding between the check and the request.

Health score (our heuristic, not an IAB metric): 100 − min(60, 5 × errors) − min(30, warnings), and −20 for the wrong content type.

Input

FieldDefaultNotes
domains["nytimes.com"]Domains or URLs. Each is reduced to its root domain (www.bbc.co.uk/news → bbc.co.uk). IPs, localhost and unknown TLDs are rejected.
fileTypeads.txtOr app-ads.txt for app developers' sites.
checkSellersJsontrueCross-check against sellers.json.
sellersJsonMaxAdSystems15The most-listed ad systems are checked first.
maxSellersJsonMb25Google's file is ~100 MB and is skipped by default. Set 150 to include it (adds ~10–15 s, est. from local tests).
includeRecordsfalseAdd every parsed record to each row.
maxFindingsPerDomain200The CSV always has all of them.
requestTimeoutSecs / maxConcurrency20 / 5Each domain is a different site, so concurrency doesn't increase load on any single site.
contactInfo–Optional. Appended to the User-Agent as run-by:.

Empty input {} runs the default domain instead of failing.

How it behaves

  • User-Agent: AdsTxtAuditorBot/0.1.0 (+https://apify.com/capable_santolina/ads-txt-auditor; pounderswork@mail.grokbot.com) apify-sdk/3 node/<version>. The robots.txt product token is AdsTxtAuditorBot.
  • One request per file, plus one robots.txt per host (cached). HTTPS is tried first, then HTTP. 429/503 responses are retried twice, honouring Retry-After up to 120 s.
  • sellers.json files are streamed: only the IDs that appear in the audited files are kept. Peak memory with Google's 100 MB file was 139 MB RSS in a local test.
  • Results show what each site served at that moment (checkedAt is UTC). CDN caches can lag behind recent edits.

Pricing (pay per event)

domain-audited once per status: ok row and sellers-json-check once per domain where at least one record was cross-checked. All other rows are free. Run cost is capped by your Max cost per run: the Actor stops starting new domains when the next one wouldn't fit, and returns those domains as free skipped rows. Nothing is dropped. Prices are set in Console; see PRICING.md.

  • ads.txt and sellers.json are public files meant for automated crawling. IAB Tech Lab describes sellers.json as something that "enables buyers to verify the entities who are direct sellers, or intermediaries" and links an open-source ads.txt crawler for engineers (sellers.json, ads.txt).
  • robots.txt is honoured for every host. Bot walls (401/403/challenge pages) are reported, never bypassed. No proxies, no login.
  • sellers.json name fields can identify individuals, so they are never stored. The contact_email field is never stored either. Only seller_type, is_confidential and domain are used, in memory only.
  • Audit domains you own, manage or have a business reason to check. Some sites' own terms restrict commercial crawling even where robots.txt allows it. Respect those.
  • Questions or takedown requests: pounderswork@mail.grokbot.com.
  • The output is QA data, not proof of fraud or of contractual authorisation. Verify findings with the parties involved.