Use Case
Most SEO audit tools skip structured data entirely. SchemaCheck gives you a REST API to validate every Schema.org block across your entire site — catch errors before Google does.
Google's Rich Results Test is the industry standard — but it's a web UI with no API, no bulk mode, and no CI integration. Auditing 50,000 pages means 50,000 manual checks. It's impossible to scale.
0
APIs offered by Google's Rich Results Test
~40%
of pages with schema have at least one error
−10pts
typical score penalty per missing recommended property
Missing required properties
datePublished, author, and image on Article; name and offers on Product. Each missing property is a hard blocker for rich results.
Deprecated schema types
HowTo was retired by Google in August 2024. FAQPage is now restricted to government and health sites. SchemaCheck flags both automatically.
Rich result ineligibility
A schema can be technically valid JSON but still not qualify for rich results. SchemaCheck checks the full Google eligibility criteria, not just JSON syntax.
@graph arrays with broken children
JSON-LD @graph blocks often contain multiple schemas. SchemaCheck flattens and validates each child individually.
Empty and null property values
Tools like Yoast generate schemas with empty strings or null values for optional properties, which can confuse Google's parsers.
Pull every URL from your sitemap, validate each one, and sort by worst score. Run this nightly and pipe results into your reporting dashboard.
import requests
import xml.etree.ElementTree as ET
API_KEY = "YOUR_KEY"
# Step 1: Pull all URLs from a sitemap
def get_sitemap_urls(sitemap_url: str) -> list[str]:
r = requests.get(sitemap_url, timeout=10)
root = ET.fromstring(r.text)
ns = {"sm": "http://www.sitemaps.org/schemas/sitemap/0.9"}
return [loc.text for loc in root.findall("sm:url/sm:loc", ns)]
# Step 2: Validate each URL and collect issues
urls = get_sitemap_urls("https://example.com/sitemap.xml")
report = []
for url in urls:
resp = requests.get(
"https://schemacheck.dev/api/v1/validate",
params={"url": url, "access_key": API_KEY},
timeout=30,
)
data = resp.json()
if not data.get("success"):
continue
summary = data["summary"]
if summary["total_errors"] > 0 or summary["total_warnings"] > 0:
report.append({
"url": url,
"score": summary["score"],
"errors": summary["total_errors"],
"warnings": summary["total_warnings"],
"rich_result_eligible": summary["rich_result_eligible"],
})
# Step 3: Sort by worst score first
report.sort(key=lambda x: x["score"])
for item in report[:10]:
print(f"{item['score']:3}/100 {item['errors']}err {item['warnings']}warn {item['url']}")Add a GitHub Actions step that validates key pages on every deploy. Merge is blocked if any required properties are missing.
# .github/workflows/schema-audit.yml
name: Schema Audit
on:
push:
branches: [main]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Validate schemas
run: |
# Check key pages — fail CI if any have errors
PAGES=(
"https://example.com/"
"https://example.com/blog/latest-post"
"https://example.com/products/best-seller"
)
for url in "${PAGES[@]}"; do
RESULT=$(curl -s "https://schemacheck.dev/api/v1/validate?url=$url&access_key=${{ secrets.SCHEMACHECK_API_KEY }}")
ERRORS=$(echo $RESULT | jq '.summary.total_errors')
SCORE=$(echo $RESULT | jq '.summary.score')
echo "$url — score: $SCORE/100, errors: $ERRORS"
if [ "$ERRORS" -gt 0 ]; then
echo "❌ Schema errors found on $url"
echo $RESULT | jq '.schemas[].errors[]'
exit 1
fi
done
echo "✓ All schema checks passed"Export URLs from Screaming Frog, validate in bulk, and import schema scores back as a custom column. Your entire site in a single spreadsheet.
// Export Screaming Frog URLs → validate → import back as custom column
const fs = require("fs");
const urls = fs.readFileSync("crawl-export.txt", "utf8").trim().split("\n");
async function auditUrls(urls, concurrency = 5) {
const results = [];
for (let i = 0; i < urls.length; i += concurrency) {
const batch = urls.slice(i, i + concurrency);
const settled = await Promise.allSettled(
batch.map(async (url) => {
const res = await fetch(
`https://schemacheck.dev/api/v1/validate?url=${encodeURIComponent(url)}&access_key=${process.env.SCHEMACHECK_API_KEY}`
);
return res.json();
})
);
for (let j = 0; j < batch.length; j++) {
const s = settled[j];
if (s.status === "fulfilled" && s.value.success) {
const d = s.value;
results.push({
url: batch[j],
score: d.summary.score,
errors: d.summary.total_errors,
warnings: d.summary.total_warnings,
schemas: d.schemas.map((s) => s.type).join(", "),
rich_eligible: d.summary.rich_result_eligible,
cached: d.meta.cached,
});
}
}
process.stdout.write(`${Math.min(i + concurrency, urls.length)}/${urls.length} validated\r`);
}
return results;
}
const results = await auditUrls(urls);
const csv = [
"URL,Score,Errors,Warnings,Schema Types,Rich Results Eligible,Cached",
...results.map((r) =>
[`"${r.url}"`, r.score, r.errors, r.warnings, `"${r.schemas}"`, r.rich_eligible, r.cached].join(",")
),
].join("\n");
fs.writeFileSync("schema-audit-results.csv", csv);
console.log(`\nDone. Results saved to schema-audit-results.csv`);“We run SchemaCheck against our sitemap every night. It caught a Yoast update that silently removed datePublished from 3,000 article pages before Google noticed.”
Free plan includes 100 validations per month. No credit card required.