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

FieldMeaning
score0–100. Starts at 100; each error costs 40, each warning 10. A missing file or an HTML page scores 0.
validtrue when there are no errors.
noFiletrue when the URL returned nothing usable (404, empty, or an HTML page instead of text).
title, summaryThe # 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.
fetchURL checks only: status, contentType, sizeBytes, fetchedAt.
llmsFullURL 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).

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)'
FieldMeaning
checked, skippedHow 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.

CodeLevelPointsMeaning
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.
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.
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.
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.txt containing 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 respect robots.txt for 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.

RequestReturns
GET /api/v1/sitesEvery 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/sitesAdds 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}/verifyChecks 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}/checkChecks 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 a reason), site.links_broken (paid plans, when links in the file stop working, with a broken list), site.listed, claim.closed (a site you were verifying was verified by another account; it leaves your pending list), and ping from "Send a test event" (never retried).
  • Verify the X-LLMsLedger-Signature header: sha256= followed by the hex HMAC-SHA256 of the raw body using your signing secret. The event name is also in X-LLMsLedger-Event.
  • sentAt is UTC in ISO 8601 (yyyy-MM-ddTHH:mm:ssZ), the time of the event. Retries keep the same body, id and 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 as id; use it to ignore repeats) and X-LLMsLedger-Attempt. Your dashboard shows recent deliveries. Emails are sent either way.