For AI Agents
Give your assistant a way to check ads.txt
Built for a caller, not a browser
Every operation is one HTTP request with a key and a secret in the headers, and every answer is JSON. There is no session to hold, no page to parse and no rate of change to keep up with, so an agent can call us the same way in a year as it does today. The same endpoints handle app-ads.txt.
There are three tools, and they are below. Validate tells you whether an ads.txt file is correct and exactly where it is not. sellers.json checks the file an ad system publishes about its sellers, the way buyers' crawlers read it. Compare takes a list of domains and tells you which of them carry the records you care about.
An agent cannot run up a bill
Calls are paid for with tokens you buy in advance, not billed after the fact. Your spend is capped at what you have already bought, which matters more when the thing making the calls decides for itself how many to make.
Every response tells the caller where it stands: X-Tokens-Cost for the call it just made and X-Tokens-Balance for what is left, so an agent can pace itself instead of waiting to be refused. A call your balance will not cover is answered 402 with the cost, your balance and the shortfall, and nothing is charged and nothing is run.
Validate: every problem, against the line it is on
Hand us a file, or a URL to read it from, and get back every record parsed plus every error, warning and notice attached to the line that caused it. It is the call to make before you publish a change, and the call to make when a partner says your file is wrong and you want to know precisely where. Pasted text costs 1 token; asking us to fetch the file costs 2.
# Validate text you already have
curl https://adstxt.guru/api/1.0/validate/adstxt/ \
-H "X-API-Key: KEY" \
-H "X-API-Secret: SECRET" \
--data-urlencode 'data=CONTACT=adops@example.com
greenadexchange.example, 12345, DIRECT, a1b2c3d4e5f6a7b8
blueadnetwork.example, 998877, reseller
greenadexchange.example 12345 DIRECT'
{
"success": true,
"error_count": 1,
"warning_count": 0,
"notice_count": 1,
"valid_count": 2,
"adstxt": {
"1": { "line": 1, "type": "variable", "variable": "contact", ..., "valid": "1" },
"2": { "line": 2, "type": "data", ..., "valid": "1" },
"3": {
"line": 3,
"type": "data",
"data": "blueadnetwork.example, 998877, reseller",
"error": [],
"warning": [],
"notice": [ "data-relationship-case" ],
"valid": "0"
},
"4": {
"line": 4,
"type": "unknown",
"data": "greenadexchange.example 12345 DIRECT",
"error": [ "line-invalid" ],
"warning": [],
"notice": [],
"valid": "0"
}
}
}
# Or have us read the live file instead
curl https://adstxt.guru/api/1.0/validate/adstxt/ \
-H "X-API-Key: KEY" \
-H "X-API-Secret: SECRET" \
-d url=https://example.com/ads.txt
Check a sellers.json: the other side of every line
Every ads.txt line names an advertising system, and that system publishes a sellers.json saying who each seller_id belongs to. Hand us the system's domain, or the file, and get back every problem in it as one finding each, with the sellers it applies to and the IAB Tech Lab error code a buyer's crawler would report. Build on each finding's key, level and iab; the sentences beside them are for people. Pasted text costs 1 token; asking us to fetch the file costs 2.
# Check a sellers.json you already have
curl https://adstxt.guru/api/1.0/validate/sellersjson/ \
-H "X-API-Key: KEY" \
-H "X-API-Secret: SECRET" \
--data-urlencode 'data={"version": "1.0", "sellers": [
{"seller_id": "pub-1", "seller_type": "PUBLISHER", "name": "Publisher One", "domain": "https://www.example.com/"},
{"seller_id": "pub-1", "seller_type": "PUBLISHER", "name": "Publisher Two", "domain": "example.org"}
]}'
{
"success": true,
"outcome": "error",
"error_count": 2,
"warning_count": 0,
"notice_count": 1,
"parsed": true,
"summary": { "sellers": 2, "domains": 2, ... },
"findings": [
{
"key": "domain-url",
"level": "error",
"iab": "21010",
"title": "A \"domain\" is a URL, not a domain",
"detail": "The spec asks for the root domain alone, ...",
"count": 1,
"examples": [
{ "position": 1, "seller_id": "pub-1", "value": "\"https://www.example.com/\"", "suggestion": "example.com" }
]
},
{ "key": "seller-id-duplicate", "level": "error", "iab": "12060 / 13060", "count": 1, ... },
{ "key": "contact-email", "level": "notice", "iab": "", "count": 1, ... }
],
"syntax": false
}
# Or have us read it from the ad system's domain
curl https://adstxt.guru/api/1.0/validate/sellersjson/ \
-H "X-API-Key: KEY" \
-H "X-API-Secret: SECRET" \
-d url=ssp.example.com
Compare: your whole publisher list, in one call
Give us a set of records and a list of domains, up to 100 in a call, and we read every one of those domains' live files and tell you which carry all of your records, which carry only some, and which could not be read at all. This is the question an ad network asks about its entire publisher list, and the one that does not scale by hand.
Each domain can carry an id of your own after a comma, which is echoed back untouched, so the answer drops straight into whatever you keep your publishers in. A domain that falls short comes back with the exact records it is missing, which is the line you can send the publisher.
Every domain we read tells you when we read it. fetched_at is a unix timestamp and age is how many seconds before this response the file was actually fetched, so 0 means we read it for this call. We hold a publisher's file for a short time and honour that publisher's own Cache-Control where they set one, which means a second call about the same domain can be answered without troubling their server again. The response never hides that: check age if you need to know. Both fields are absent for a domain we never managed to read.
# Check 3 publishers for 2 records
curl https://adstxt.guru/api/1.0/compare/adstxt/ \
-H "X-API-Key: KEY" \
-H "X-API-Secret: SECRET" \
--data-urlencode 'adstxt=greenadexchange.example, 12345, DIRECT, a1b2c3d4e5f6a7b8
blueadnetwork.example, 998877, RESELLER, 0e1f2a3b4c5d6e7f' \
--data-urlencode 'domains=publisher-one.example,pub-001
publisher-two.example,pub-002
gone.example,pub-003'
X-Tokens-Cost: 3
X-Tokens-Balance: 1497
{
"success": true,
"matched_count": 1,
"partial_count": 1,
"failed_count": 1,
"results": [
{
"domain": "publisher-one.example",
"id": "pub-001",
"url": "https://publisher-one.example/ads.txt",
"status": "success",
"missing": [],
"error": "",
"reason": "",
"fetched_at": 1789856084,
"age": 0
},
{
"domain": "publisher-two.example",
"id": "pub-002",
"url": "https://publisher-two.example/ads.txt",
"status": "missing",
"missing": [
"blueadnetwork.example, 998877, RESELLER, 0e1f2a3b4c5d6e7f"
],
"error": "",
"reason": "",
"fetched_at": 1789856084,
"age": 0
},
{
"domain": "gone.example",
"id": "pub-003",
"url": "https://gone.example/ads.txt",
"status": "failed",
"missing": [],
"error": "Connection error",
"reason": "This domain name does not resolve."
}
],
"tokens_charged": 3,
"tokens_not_charged": 0
}
Know the price before you send it
A validation, of an ads.txt or a sellers.json, costs 1 token, or 2 if we fetch the file for you rather than being handed it. A comparison costs 1 token for each domain you ask about, because each one is a separate request we make to that domain's web server.
A call is charged when it runs, not when it succeeds: a domain that does not resolve still costs us the request. What you are never charged for is work we did not do, so a domain we decline to fetch, or run out of time to reach, is credited straight back.
A comparison answers with tokens_charged and tokens_not_charged, beside the per-domain reason saying which were skipped and why, so the reconciliation happens at the moment you can act on it rather than on a statement days later.
That is the whole point of a published weight. You can price a job before you send it, which is not something a per-second or per-second-of-compute meter lets you do.
Start on us
Verify your email address and we credit 1,000 tokens, once. That is enough to build and test an integration end to end before deciding whether to buy anything.
We also publish llms.txt and llms-full.txt, so an assistant can learn what we do and how the API is shaped without crawling the site, and an OpenAPI document generated from the API's own observed behaviour rather than written by hand.
What a token balance buys
Tokens pay for the three tools above, and only those three: they are the ones that go out and read the public web, which is the work worth metering. The rest of the API manages the domains and groups inside your own account, does no crawling, and comes with a plan instead. Ask it for one on tokens alone and it answers 403 and says so rather than charging you.
An MCP server is in development
The API above works today with any agent that can make an HTTP request. A hosted Model Context Protocol server, so assistants that speak MCP can use these tools without any integration work at all, is something we are building and have not shipped.
If that is what you are waiting for, tell us and we will let you know when it lands.
Tokens, bought in advance
Valid for 12 months. No monthly commitment.
Starter
$10
Checks 10,000 domains
10,000 tokens
Standard
$45
Checks 60,000 domains
60,000 tokens
Growth
$150
Checks 250,000 domains
250,000 tokens
A validation, of an ads.txt or a sellers.json, costs 1 token, or 2 if we fetch the file for you. A comparison costs 1 token for each domain you ask about. Sold to businesses only, so we ask for your company name, registration number and country before the first purchase. See the API Token Terms.
Read the API documentation, or contact us about a volume you do not see here.