Audit¶
sluicer audit holds what a page declares to what its documentation asks of
it. For every record JSON-LD, microdata and RDFa declare, it says which of
Google’s rich-result features the record’s type is documented for, which
required and recommended properties it lacks, and which of its values are in a
form the documentation or schema.org refuses. Then what the page as a whole
lacks, where two vocabularies contradict each other, which AI agents the site’s
robots.txt admits, and whether its llms.txt keeps to llmstxt.org’s format.
In a workflow, the GitHub Action runs it on the pages you
name and fails the check when one breaks a rule.
No model reads the page. The rules are data, transcribed from the pages that state them, and every finding names the page its rule comes from, so the same page always gets the same audit and any finding can be checked by opening one link. SEO tooling for coding agents usually does this by handing a model the HTML; the difference is the one Sluicer makes everywhere else.
sluicer audit https://example.com/product # a report for a person
sluicer audit https://example.com/product --json # the same audit as JSON
sluicer audit saved.html --url https://example.com/p # a file: no site is read
sluicer audit https://example.com/p --no-site # the page only
from sluicer.audit import audit
from sluicer.fetch import fetch
from sluicer.fetch.site import read_site
page = fetch("https://example.com/product")
result = audit(page.html, url=page.url, site=read_site(page.url))
for record in result.records:
for feature in record.features:
print(record.source, record.types, feature.name, feature.requirements_met)
audit itself fetches nothing: read_site reads robots.txt, llms.txt and
llms-full.txt, over plain HTTP, under Sluicer’s own name and the site’s
robots.txt, and audit is handed what it read. It also takes an Extraction
already made; its records are then audited as merged, and what needs the page
itself is listed as not checked. For an agent, the MCP tool is audit_page.
What is checked, and against what¶
Records, one vocabulary at a time¶
A record is one item a reader returned – a JSON-LD node, a top-level microdata
item, an RDFa subject – before any folding. Google reads each syntax on its
own, and a finding has to name the vocabulary that declared the value, so a
product described in JSON-LD and in microdata is audited twice, once as each.
A JSON-LD graph that names its nodes by @id is followed through them, and a
node that is a record of its own is audited once, where it is, not again inside
every record that points at it.
Each finding carries its severity, a code, the record’s source and
index (its position among that reader’s records), the property path
(offers[1].price), the feature it serves if any, the offending value, and
the rule: the address of the page the rule is written on.
Features¶
Read from each feature’s “Structured data type definitions” on Google Search
Central, every page read on 2026-09-23. A property is required or recommended
because the page’s table says so. Where the prose adds a condition the table
does not carry – a breadcrumb’s last item needs no item, a recipe that
states calories needs a yield, a remote job needs no jobLocation when it
names applicantLocationRequirements – the condition is written on the
property. A subtype counts where the page says it does: a Restaurant is a
local business, and a Car is not a product, because the product pages say
“Car isn’t supported automatically as a subtype of Product”.
| Feature | Types | Required on the record | Status | Page, last updated |
|---|---|---|---|---|
| Product snippet | Product and subtypes (not Car, ProductGroup) | name, review or aggregateRating or offers |
supported | product-snippet, 2026-09-08 |
| Merchant listing | Product and subtypes (not Car, ProductGroup) | name, image, offers |
supported | merchant-listing, 2026-09-08 |
| Review snippet | Review | author, itemReviewed, itemReviewed.name, reviewRating, reviewRating.ratingValue |
supported | review-snippet, 2026-09-08 |
| Review snippet | AggregateRating | itemReviewed, itemReviewed.name, ratingCount or reviewCount, ratingValue |
supported | the same |
| Review snippet | the types the page lists a review may be about, with subtypes, when they carry review or aggregateRating |
name, review or aggregateRating |
supported | the same |
| Article | Article, NewsArticle, BlogPosting and subtypes (not the two posting types the forum page claims) | none | supported | article, 2026-09-08 |
| Breadcrumb | BreadcrumbList | itemListElement |
supported | breadcrumb, 2026-09-08 |
| Organization | Organization and subtypes | none | supported | organization, 2026-09-08 |
| Local business | LocalBusiness and subtypes | address, name |
supported | local-business, 2026-09-08 |
| Recipe | Recipe | image, name, recipeYield if nutrition.calories |
supported | recipe, 2026-09-08 |
| Event | Event and subtypes | location, location.address, name, startDate |
supported | event, 2026-09-08 |
| Job posting | JobPosting | datePosted, description, hiringOrganization, jobLocation or applicantLocationRequirements, jobLocation.address.addressCountry, title |
supported | job-posting, 2026-09-08 |
| Video | VideoObject and subtypes | name, thumbnailUrl, uploadDate |
supported | video, 2026-09-08 |
| Software app | SoftwareApplication and subtypes (not VideoGame alone) | name, offers.price, aggregateRating or review |
supported | software-app, 2026-09-08 |
| Course list | Course | description, name |
limited | course, 2026-09-08 |
| Dataset | Dataset | description, name |
limited | dataset, 2026-09-08 |
| Book actions | Book and subtypes | @id, author, name, url, workExample |
limited | book, 2025-12-10 |
| Q&A | QAPage | mainEntity |
supported | qapage, 2026-09-08 |
| Discussion forum | DiscussionForumPosting, SocialMediaPosting | author, author.name, datePublished, text or image or video |
supported | discussion-forum, 2026-09-08 |
| Profile page | ProfilePage | mainEntity |
supported | profile-page, 2026-09-08 |
| Site name | WebSite | name, url |
supported | site-names, 2025-12-10 |
The nested types each page defines are rules of their own, applied to the
values of the property that holds them: an Offer in offers, an
AggregateOffer beside it, a UnitPriceSpecification, OfferShippingDetails,
MerchantReturnPolicy, a nested Review and AggregateRating, HowToStep and
HowToSection, BroadcastEvent, Clip, Question, Answer, Comment,
ReadAction. The full transcription, recommended properties included, is
sluicer/audit/google.py.
Limited means Google names a condition the markup cannot show: course lists need three courses and a carousel; Dataset markup has been used by Dataset Search only, not Google Search, since November 2025; Book actions are read from a data feed Google accepts from providers it has admitted, so what a page’s Book markup lacks for them is reported as a note, not an error. A merchant listing and a product snippet are both reported for a product, because which one applies depends on whether the page sells it, which markup does not say.
Parts. A nested value the feature needs – a merchant listing’s offers,
a Q&A page’s mainEntity, a product’s only reviews – is part of the feature:
what it lacks, the feature lacks. One the feature can do without – an offer’s
shippingDetails, a product’s reviews beside its offers – is a part of its
own. Google writes of those “you must add the following properties if you
want” them used; what they lack makes the part unusable and not the feature,
and is reported as incomplete-part, a warning.
Properties a table recommends that its own prose confines to some pages – a
live video’s publication, a segmented video’s hasPart, a seasonal
closure’s validFrom – are never reported missing, nor are the job posting
page’s beta education properties.
Retired and restricted features¶
A record of a type whose feature Google no longer shows says so, as a note:
| Feature | Types | What happened | Source |
|---|---|---|---|
| FAQ | FAQPage | Restricted to well-known government and health sites from 2023-08-08; not shown in Google Search since 2026-05-07; its documentation was removed in June 2026. | changelog |
| How-to | HowTo | Not shown on mobile from August 2023, nor on desktop since 2023-09-13. | blog |
| Sitelinks search box | WebSite with potentialAction |
Not shown since 2024-11-21; WebSite markup still names the site. | blog |
| Fact check | ClaimReview | Being phased out of Google Search since 2025-06-12; still read by the Fact Check Explorer. | page |
| Course info, Estimated salary, Learning video, Special announcement, Vehicle listing | Course with hasCourseInstance, Occupation, LearningResource, SpecialAnnouncement, Car and Vehicle with offers |
Phased out from 2025-06-12; removed from Search Console and the Rich Results Test on 2025-09-09. | blog |
The practice problem feature, removed in January 2026, shares its Quiz type
with Education Q&A, which is still shown, so it is not matched. Features Google
documents that this audit does not check – movie carousels, vacation rentals,
math solvers, education Q&A, employer ratings, image metadata, product
variants, loyalty programs, return and shipping policies, carousels – are
named on the record as not checked, so that no feature is never mistaken for
none.
Values¶
Each is checked wherever its property appears, at any depth of a record:
| Property | Checked against | Source |
|---|---|---|
price, lowPrice, highPrice |
digits 0-9 and a full stop; a comma or a currency symbol is named as such | schema.org/price |
priceCurrency, currency |
ISO 4217 list one, the edition published 2026-09-17 | product snippet, the maintenance agency’s list |
dates (datePublished, startDate, validThrough, uploadDate …) |
an ISO 8601 calendar date, optionally T, a time and a zone; a space for the T is named |
article |
| two dates of one object | dateModified not before datePublished, endDate not before startDate, validThrough not before validFrom |
merchant listing |
durations (cookTime, duration …) |
an ISO 8601 duration | recipe |
URLs (url, image, logo, sameAs, contentUrl …) |
absolute http or https; a warning, since Google accepts relative URLs in places | local business |
availability, itemCondition, eventStatus, dayOfWeek, bookFormat, return-policy terms |
the terms of the schema.org enumeration, with or without its address | schema.org 30.1 |
ratingValue |
a number on the scale worstRating to bestRating, 1 to 5 when unstated; a fraction or a percentage |
review snippet |
counts, position, coordinates |
whole numbers, from 1 for a position; latitude within 90, longitude within 180 | the pages that define them |
gtin, gtin8 … gtin14, isbn |
length and check digit | GS1, ISBN Users’ Manual |
opens, closes, booleans, employmentType, jobLocationType, applicationCategory, priceRange |
the forms and lists the feature’s page gives | the feature’s page |
Checks a feature adds for itself: a merchant listing’s price above zero and its
offer an Offer, not an AggregateOffer; a reviewer’s name under 100
characters; a review’s itemReviewed a type Google shows reviews for; a
dataset’s description 50 to 5,000 characters; a salary’s unitText one of the
five units.
The page, and its vocabularies side by side¶
A <title>
(Google), a
meta description
(Google), one
absolute canonical
(Google),
and OpenGraph’s four required properties, og:title, og:type, og:image and
og:url (ogp.me). Each missing one is a warning.
The canonical is read as extract reads it for links: from the <head>
only, since Google accepts a rel=canonical nowhere else, and a canonical in
the body – which a page’s own content, a comment, can put there – names
nothing. Two canonicals that resolve to one address, /pads and
https://example.com/pads on that site, are one; the relative one is still
named.
Two vocabularies that each declare one record of a type describe one thing,
and the facts they state about it must agree: identifiers, prices, the
currency, availability, the rating and its counts, the dates. Each is compared
as what it is – 41.9 is 41.90, eur is EUR, two moments in two zones are
one moment – and a disagreement is a warning citing Google’s rule that
structured data “must be a true representation of the page content”. With two
products in one vocabulary and one in another, which is which would be a
guess, and nothing is compared.
AI agents¶
Every agent is named on its vendor’s own page about its crawlers, read on
2026-09-23; nothing comes from a third-party list. For each, the site’s
robots.txt is read by protego, the parser Sluicer obeys itself, and the report
names the User-agent line that decided.
| Agent | Vendor | For | Honours robots.txt, per the vendor | Page |
|---|---|---|---|---|
GPTBot |
OpenAI | training | yes | OpenAI |
OAI-SearchBot |
OpenAI | search | yes | the same |
ChatGPT-User |
OpenAI | user | may not | the same |
ClaudeBot |
Anthropic | training | yes | Anthropic |
Claude-User |
Anthropic | user | yes | the same |
Claude-SearchBot |
Anthropic | search | yes | the same |
Google-Extended |
training, and grounding Gemini | yes; a control token, no crawler | Google, common crawlers | |
Google-CloudVertexBot |
agents a site owner builds | yes | the same | |
Google-Agent |
user | may not | Google, user-triggered fetchers | |
Google-GeminiNotebook |
user | may not | the same | |
PerplexityBot |
Perplexity | search | yes | Perplexity |
Perplexity-User |
Perplexity | user | may not | the same |
CCBot |
Common Crawl | an open crawl others train on | yes | Common Crawl |
Applebot |
Apple | search; may train unless Applebot-Extended is disallowed |
yes | Apple |
Applebot-Extended |
Apple | training; crawls nothing | yes | the same |
Amazonbot |
Amazon | training | yes | Amazon |
Amzn-SearchBot |
Amazon | search | yes | the same |
Amzn-User |
Amazon | user | may not | the same |
Meta-ExternalAgent |
Meta | training | yes | Meta |
Meta-WebIndexer |
Meta | search | yes | the same |
Meta-ExternalFetcher |
Meta | user | may not | the same |
MistralAI-Training |
Mistral | training | yes | Mistral |
MistralAI-Index |
Mistral | search | yes | the same |
MistralAI-User |
Mistral | user | yes | the same |
DuckAssistBot |
DuckDuckGo | search | yes | DuckDuckGo |
Left out, deliberately: anthropic-ai, which Anthropic’s page does not list;
cohere-ai, since Cohere’s page
lists no crawler at all; Bytespider, since ByteDance publishes no page about
it; and ad crawlers and link-preview fetchers, which are on the same pages and
are not what a page is being read for. Every User-agent a robots.txt names
that no agent here answers to is listed as other_agents: a site that names
anthropic-ai and not ClaudeBot has turned nobody away.
What the deciding group says about use is read too, from the lines protego
passes over. Content-Usage is the IETF aipref working group’s rule
(draft-ietf-aipref-attach-05, its vocabulary draft-ietf-aipref-vocab-08):
train-ai, ai-use and search, each y or n, optionally for a path.
Content-Signal is Cloudflare’s (search, ai-input, ai-train, each yes
or no), which its managed robots.txt writes – GitHub’s code search counted
3,448 robots.txt files with a Content-Signal line on 2026-09-23 – and whose
draft, draft-romm-aipref-contentsignals-00, expired on 2026-04-04. The rule whose path matches the page longest applies, rules on the
same path combine with the most restrictive winning, and a page the agent may
not fetch has no preferences, as the aipref drafts say. None of them is an RFC,
and each agent’s line says only what the site stated: allow, disallow, or
nothing when it stated nothing. The report says them once per group.
A robots.txt that answered 4xx has no rules, which allows everything; one that could not be read decides nothing, and every verdict is unknown.
Text and data mining¶
The page’s TDMRep reservation is reported as tdm – reserved or not, the
policy it names, and which declaration last said so: the site’s
/.well-known/tdmrep.json, read with the other files, or the page’s
<meta name="tdm-reservation">, which supersedes it. It is a statement
recorded, never a finding: a page may reserve its rights, and one that does
not has broken no rule.
llms.txt¶
Read against the proposal at llmstxt.org (v2, August
2026): an H1 with the site’s name, “the only required section”, is an error
when missing; a blockquote summary, no headings besides the H1 and the H2
sections, and H2 sections that are lists of [name](url) links, optionally
followed by : and notes, are warnings when departed from. A site that
answers /llms.txt with an HTML page – a single-page app’s fallback, a 404 page
served with 200 – is an error. llms-full.txt is not part of the proposal and
has no format to check; whether the site serves one, and how long it is, is
reported. Google said in June 2026 that these files are not used by Google
Search and neither help nor harm a page there
(changelog); they are for the
agents and tools that read them.
Severity and exit codes¶
error: the page breaks a rule its documentation states – a required
property missing, a value in a form the documentation refuses, an llms.txt
with no name. warning: it could do what the documentation recommends and
does not, an optional part is unusable, two vocabularies disagree, a URL is
relative. info: worth knowing and nothing to fix, such as a retired feature.
The command exits 3 when there is any error, whatever else is true – as run
does for a page that broke its extractor, 3 means the page was read and breaks
a rule it is held to. Otherwise 1 when no record was declared, since there was
nothing to audit, and 0. 2, as everywhere, when the page could not be read. A
page the site answered with 403 or 404 is audited as the answer it is, and the
audit says so first.
An example¶
The repository’s example product page, which declares its product in JSON-LD and again in microdata, and leaves out what a merchant listing needs:
sluicer audit examples/brake-pads.html --url https://example.com/p/bp-2210
page https://example.com/p/bp-2210
records 2 audited (jsonld 1, microdata 1)
Product (jsonld record 0)
Product snippet requirements met
recommended, missing: aggregateRating, review, offers.priceValidUntil
Merchant listing 1 required missing: image
recommended, missing: aggregateRating, audience, category, color, description, gtin|gtin8|gtin12|gtin13|gtin14|isbn, hasAdultConsideration, hasCertification, inProductGroupWithID, isVariantOf, material, mpn, pattern, review, size, sku, subjectOf, offers.hasMerchantReturnPolicy, offers.itemCondition, offers.priceValidUntil, offers.shippingDetails, offers.url, offers.validFrom, offers.validThrough
Product (microdata record 0)
Product snippet 1 required missing: review|aggregateRating|offers
recommended, missing: aggregateRating, offers, review
Merchant listing 2 required missing: image, offers
recommended, missing: aggregateRating, audience, brand.name, category, color, description, hasAdultConsideration, hasCertification, inProductGroupWithID, isVariantOf, material, pattern, review, size, sku, subjectOf
page 1 finding
warning og:url is absent; OpenGraph requires it on every page [https://ogp.me/]
not checked
The AI agents robots.txt admits, and llms.txt: the site is read only for a URL.
summary 4 errors, 47 warnings, 0 notes
It exits 3: the page breaks rules its documentation states. Each record is held to every feature its type is documented for, so the JSON-LD product meets the product snippet’s requirements and lacks the image a merchant listing requires, and the microdata product, which declares no offer, lacks more. Run on a page at a URL, the audit also reads the site’s robots.txt and llms.txt.
What it does not do¶
It checks what Google’s documentation says, not what Google’s own validator decides, which is not published; a page that meets every documented requirement may still not be shown. Nothing is checked against today’s date – a price valid until last year, an event that is over – because the answer would change with the clock. It reads the HTML the server sent, as the rest of Sluicer does, so markup a page’s script adds later is only seen when the fetch climbed to a browser. The rest is in known limits.