llms.txt validator API for developers
Everything the website does, without the hand-holding: a JSON API for the validator, the exact rules, and how verification and crawling work.
Validator API
Free, no key needed. Rate limited to about 12 checks a minute per IP (HTTP 429 with Retry-After when exceeded). CORS is open (including preflight), so you can call it from a browser. Private and local addresses are never fetched.
Check a live site
curl -s "https://llmsledger.com/validator.json?url=example.com" | jq '.score, .issues'
We fetch /llms.txt and /llms-full.txt, trying https and www/non-www variants (then http) if the address as given can't be reached.
Check a file before you publish it
curl -s -X POST "https://llmsledger.com/validator.json" \ -H "Content-Type: text/plain" --data-binary @llms.txt
From JavaScript or anything that prefers JSON, send {"content": "…"} with Content-Type: application/json.
Fail a CI build when the file has problems
curl -s -X POST "https://llmsledger.com/validator.json" -H "Content-Type: text/plain" --data-binary @public/llms.txt \ | jq -e '[.issues[] | select(.severity == "error")] | length == 0'
Response
| Field | Meaning |
|---|---|
score | 0–100. Starts at 100; each error costs 40, each warning 10. A missing file or an HTML page scores 0. |
valid | true when there are no errors. |
noFile | true when the URL returned nothing usable (404, empty, or an HTML page instead of text). |
title, summary | The # title and the > summary line. |
sections[] | name, links (count), optional (the ## Optional section). |
issues[] | severity (error, warning, info), code (see rules), line (1-based, may be null), message, fix. |
fetch | URL checks only: status, contentType, sizeBytes, fetchedAt. |
llmsFull | URL checks only: whether /llms-full.txt exists, its size, title and link count. |
Status codes: 200 (checked, including "no file" and sites that couldn't be reached: then reachable is false and error says why), 400 (bad input), 429 (rate limited).
Link checking
Opens each page a live file links to (up to 40) and reports the ones that don't work. Same rate limit as the validator.
curl -s "https://llmsledger.com/validator/links.json?url=example.com" | jq '.links[] | select(.ok | not)'
| Field | Meaning |
|---|---|
checked, skipped | How many links were opened, and how many were left out over the limit. |
links[] | title, url, ok, status (HTTP status, null if there was no answer) and problem (a short reason when ok is false). |
Rules
Based on the llms.txt proposal. Codes are stable; use them in scripts.
| Code | Level | Points | Meaning |
|---|---|---|---|
empty |
error | −100 | File is empty. The address returned nothing. Publish your llms.txt content there. |
html |
error | −100 | Web page instead of a file. The address returned a web page (HTML). Your site doesn't have an llms.txt yet, or it's in the wrong place. |
no-h1 |
error | −40 | Missing title. The file must start with your site or project name as a heading: # Your name. |
h1-not-first |
error | −40 | Title isn't first. Nothing may come before the # title line. |
preamble-before-title |
warning | −10 | Note above the title. A plain-text note (often added by SEO plugins like Yoast) sits above the # title. AI tools usually skip it, but the title should come first. |
multiple-h1 |
warning | −10 | More than one title. Use one # title. Use ## for section headings. |
content-type |
warning | −10 | Served with the wrong type. Servers should send llms.txt as text/plain or text/markdown. |
large |
warning | −10 | File is very large. Keep llms.txt short (under 1 MB). Put full content in llms-full.txt. |
section-no-links |
warning | −10 | Empty section. Each ## section should list pages as: - [Page name](https://url): short note. |
text-section |
info | 0 | Section with text only. A ## section that holds text but no links. That's fine for context; AI tools just won't find pages there. |
no-sections |
warning | −10 | No link sections. Add at least one ## section listing your most important pages. |
few-links |
warning | −10 | Very few pages listed. One or two links don't tell AI tools much. List your 3 to 10 most important pages. |
bad-url |
warning | −10 | Link isn't a web address. Links should be full web addresses (https://…) or paths on your site (/page). |
missing-scheme |
warning | −10 | Link is missing https://. A link like example.com/page is read as a page on your own site. Write https://example.com/page. |
repeated-domain |
warning | −10 | Address repeats the domain. A link like https://site.com/site.com/page is broken. Remove the repeated domain. |
malformed-link |
warning | −10 | Broken link format. A list item that looks like a link but won't parse, e.g. brackets in the name or spaces in the address. |
no-summary |
info | 0 | No summary line. Optional but recommended: a one-line description after the title, starting with >. |
relative-url |
info | 0 | Relative link. Links like /pricing work, but full addresses (https://…) are safer for AI tools. |
http-url |
info | 0 | Insecure link. Links that start with http:// work, but https:// is preferred where the page supports it. |
plain-list-items |
info | 0 | List items without links. List items inside sections are most useful as links. |
optional-section |
info | 0 | Has an Optional section. AI tools may skip the ## Optional section when they're short on space. That's by design. |
Verifying sites
Each site gets its own code. Any one of these proves ownership; we check all three each time you press verify:
- Meta tag in the home page
<head>:<meta name="llmsledger-verification" content="<your code>"> - File at
/.well-known/llmsledger-verification.txtcontaining the code (plain text). - DNS TXT record on the domain:
llmsledger-verification=<your code>
You can remove the tag, file or record once the site is verified. Free listings also need the badge link on the page you choose; paid plans don't.
Many sites
Add several sites takes a pasted list: one site per line, or rows copied from a spreadsheet or CSV (any column with a website in it counts; names and notes are ignored). It creates a code per site, which you can copy or download as a CSV (domain,code,meta_tag,dns_txt_value,well_known_url) to hand to whoever deploys. GET /api/v1/sites lists the same codes under pendingVerification. Then "Verify all". Agency covers 10 sites per pack ($79/month per pack) and costs less than Nightly from 5 sites.
Our crawler
- User agent:
LLMsLedgerBot/1.0 (+https://llmsledger.com/bot). We respectrobots.txtfor it. More about the crawler. - Schedule: Free every 3 months, Monthly every month, Nightly and Agency every night. "Check now" in the dashboard runs it on demand.
- Conditional requests (
ETag,If-Modified-Since), a size cap, and no requests to private addresses.
Monitoring
Every fetched version is stored with a line-by-line diff in the dashboard (paid plans). Nightly and Agency email you each change (what was added and removed, with a link to the full diff), and every paid plan emails you if the file breaks or disappears, or if pages it links to stop working (we open every link each time we check the file).
Your sites API
Create a personal key in your dashboard under API and webhooks and send it as a bearer token. Up to 60 requests a minute from each IP address.
| Request | Returns |
|---|---|
GET /api/v1/sites | Every site on the account (plan, status, listed, score, AI readiness, broken links, last/next check, badge state, pending plan) and sites waiting for verification with their codes, meta tag, file URL and DNS value. |
GET /api/v1/sites/{domain} | One site, its current llms.txt issues (same fields as the validator; empty while the file is missing or the site can't be reached) and its versions (the last 20 on paid plans, the current one on Free). |
POST /api/v1/sites | Adds a site. JSON body: {"domain": "example.com", "plan": "Agency"} (plan is optional: Agency if you have Agency packs, otherwise Free). Returns the verification code, meta tag, file URL and DNS value. |
POST /api/v1/sites/{domain}/verify | Checks the code on the site and applies the plan, like the green button on the verify page. Returns step (NotVerified, NeedsBadge, Published, NeedsPayment, AgencyFull, Conflict), a message, and an action.url when payment is needed in the browser. |
POST /api/v1/sites/{domain}/check | Checks the site now, like "Check now" in the dashboard, and returns outcome (Changed, Unchanged, NotModified, NotFound, Error, Blocked), the site and its issues. Once a minute per site (429 otherwise). |
curl -s -H "Authorization: Bearer $LLMS_LEDGER_KEY" "https://llmsledger.com/api/v1/sites" | jq '.sites[] | {domain, score, status}'Every error is JSON: {"error": "…"}. 400 (bad domain, plan or body), 401 (missing or revoked key), 404 (not a site on your account, or no such endpoint), 405 (wrong method), 409 (site belongs to another account), 415 (body isn't JSON), 429 (rate limited; Retry-After says how many seconds). Adding a site you've already added returns 200 with the same code. Verifying also checks the file and returns its issues, so you don't need /check straight after.
Webhooks
Add an https URL under API and webhooks. We POST JSON when one of your sites changes, breaks or goes live:
{
"id": "3f9c0d2a7b8e4f5a9c1d2e3f4a5b6c7d",
"event": "site.changed",
"sentAt": "2026-10-04T03:12:09Z",
"data": {
"site": { "domain": "example.com", "plan": "Nightly", "status": "Live", "score": 90, ... },
"version": { "id": 812, "sha256": "…", "added": 4, "removed": 1, "score": 90, "diff": "https://llmsledger.com/dashboard/sites/12/versions/812" }
}
}
- Events:
site.changed(Nightly and Agency),site.broken(paid plans, with areason),site.links_broken(paid plans, when links in the file stop working, with abrokenlist),site.listed,claim.closed(a site you were verifying was verified by another account; it leaves your pending list), andpingfrom "Send a test event" (never retried). - Verify the
X-LLMsLedger-Signatureheader:sha256=followed by the hex HMAC-SHA256 of the raw body using your signing secret. The event name is also inX-LLMsLedger-Event. sentAtis UTC in ISO 8601 (yyyy-MM-ddTHH:mm:ssZ), the time of the event. Retries keep the same body,idand signature.- Respond with any 2xx within 10 seconds. Failed calls are retried after 1, 5 and 30 minutes, then 2 and 8 hours. Headers:
X-LLMsLedger-Delivery(same asid; use it to ignore repeats) andX-LLMsLedger-Attempt. Your dashboard shows recent deliveries. Emails are sent either way.