SEO Audit Tool - SEO Checker, Site Audit & Core Web Vitals
Pricing
from $4.00 / 1,000 audited pages
SEO Audit Tool - SEO Checker, Site Audit & Core Web Vitals
SEO audit tool and site audit crawler for technical SEO, JavaScript sites too: 0-100 score and a fix hint per issue, site report (duplicate titles, broken pages, missing H1, meta, alt, sitemap, hreflang), optional Core Web Vitals. $5 per 1,000 pages; failed pages are free.
Pricing
from $4.00 / 1,000 audited pages
Rating
0.0
(0)
Developer
Yukai Lin
Maintained by CommunityActor stats
0
Bookmarked
9
Total users
7
Monthly active users
2 days ago
Last modified
Categories
Features
Share
SEO Audit Tool: SEO Checker, Site Audit and Core Web Vitals
What it does
This SEO crawler runs a technical SEO audit of a whole site, including JavaScript sites. Each audited page gets a 0-100 SEO score, category scores and a fix hint for every issue. Each run also writes a site report covering duplicate titles, broken internal pages, missing H1, meta descriptions, alt text, sitemap, canonical and hreflang.
Example. Input {"urls": ["https://www.python.org/"], "maxPages": 20}. Output is one row per page, for example https://www.python.org/ with score: 93 and 2 issues: h1-multiple ("Page has 5 H1 headings.") and canonical-missing, each with a fix hint. The site report for that 20-page run gave an average score of 94.4.
Price: $0.005 per audited page ($5 per 1,000) on Free and Bronze. Silver pays $0.0045 and Gold and up pay $0.004. Optional PageSpeed (Core Web Vitals) costs the same per page measured. Broken, blocked, failed and timed-out pages are free. Every row shows charged: true/false.
Max pages defaults to 100 total per run. If SUMMARY.pagesLimitReached is true, pagesDiscoveredNotAudited counts URLs this run already found but did not audit; more pages may exist beyond those discovered. Read SUMMARY.nextStep and raise the limit or limits it names (Max pages and, when reached, Max pages per site; each up to 10,000) for a larger one-run sample. To audit several sites in one run, put one start URL or domain per line in URLs or domains; the total Max pages is shared fairly, and optional Max pages per site caps each site. To split a large site into separate runs, supply distinct start URLs and choose Same folder or Only the start URLs to reduce overlap between batches. A run that stops at the spending limit instead uses SUMMARY.status: LIMIT_REACHED and SUMMARY.notProcessed.
With Respect robots.txt on, if at least 30% of processed URLs (including fetched non-HTML URLs) are skipped, SUMMARY.robotsSkipNotice shows the count and points to the Googlebot rules option when another ruleset is selected. Skipped URLs are free. When another ruleset is selected, the notice states how many skipped URLs Googlebot rules would allow; selecting Googlebot cannot override a site that blocks it too.
Use cases
Compare your site with two competitors. Put your site first: compareSites adds a side-by-side matrix to REPORT_HTML for the client. Numeric gaps against that first site are in SUMMARY.siteComparison.gaps and the Markdown REPORT. Use the same page cap per site for comparable samples.
{"urls": ["https://example.com/", "https://competitor-one.example/", "https://competitor-two.example/"],"maxPages": 90,"maxPagesPerSite": 30,"compareSites": true,"reportBrandName": "Agency SEO"}
Send only weekly regressions to your agency. Create a weekly Apify Schedule with this input. Keep the URLs, crawl scope and explicit monitorName the same each time; the first run sets the baseline. WEEKLY_CHANGES contains only new issues, score drops and pages no longer found after a complete crawl. The full audit still runs and is billed for successfully audited pages.
{"urls": ["https://example.com/"],"maxPages": 100,"compareWithPrevious": true,"weeklyChangesDigest": true,"monitorName": "example-com-weekly"}
Quickly triage a list of domains. Paste domains into urls and set crawlScope to page to audit just the start page of each site. Review the per-site results before running a deeper crawl.
{"urls": ["example.com", "example.org", "example.net"],"crawlScope": "page","maxPages": 100}
Why this one
- $5 per 1,000 pages, compute included, no start fee. The most used SEO audit Actor on the Store, smart-digital/complete-seo-audit-tool, charges $40 per 1,000 pages (comparison, checked 2 October 2026). scrapesignal_labs is cheaper per page ($0.50) but bills platform usage on top and has no browser fallback (all prices).
- Works on JavaScript sites and sites that block bots: a second network, then a real browser. The two most used SEO audit Actors read static HTML only.
- A fix hint for every issue, category scores, and site checks: sitemap cross-check, canonical and redirect targets, duplicate titles and content, hreflang return links, broken internal pages with the page that links to them, and whether pages that do not exist get a real 404 (soft 404).
- Broken, blocked, failed and timed-out pages are free. Every row says
charged: true/false. - Core Web Vitals without an API key (optional, $5 per 1,000 pages measured) and run-to-run comparison for weekly monitoring.
Input (copy and paste)
{"urls": ["https://www.python.org/"],"maxPages": 20}
Output (real result, shortened)
One page of a real 20-page python.org run. The whole run costs $0.10 on the Free plan (20 × $0.005), and its site report gave an average score of 94.4:
{"url": "https://www.python.org/","success": true,"score": 93,"issueCount": 2,"issues": [{ "id": "h1-multiple", "severity": "warning", "message": "Page has 5 H1 headings.", "category": "headings","fixHint": "Keep a single main H1 and turn the others into H2 headings.", "impact": "medium" },{ "id": "canonical-missing", "severity": "notice", "message": "Canonical link is missing.", "category": "indexing","fixHint": "Add <link rel=\"canonical\"> pointing to the preferred URL of this page (usually itself).", "impact": "low" }],"categoryScores": { "technical": 100, "indexing": 94, "meta": 100, "headings": 85, "images": 100, "social": 100, "...": 100 },"title": "Welcome to Python.org","wordCount": 1182}
All fields: Output: one item per page. The site report: Output: site report.
For mixed-content, no-https-redirect and temporary-redirect, an issue may include machine-readable evidence to check the finding. Other issues do not necessarily have this field.
How much does it cost?
| Event | Price |
|---|---|
| Audited page | $5.00 / 1,000 pages |
| PageSpeed measurement (optional) | $5.00 / 1,000 pages measured |
Example: auditing a 1,000-page site costs 1,000 × $0.005 = $5.00 on the Free plan; PageSpeed on 50 of those pages adds 50 × $0.005 = $0.25.
No start fee. Broken pages, failed pages and the site checks (sitemap, canonical targets, soft-404 check, image checks, structured data, run-to-run comparison) are free. PageSpeed measurements are only charged when a score comes back. Your maximum charge limit is always respected.
Features
SEO audit tool that crawls your website and checks discovered content pages for on-page and technical SEO problems, including sites built with JavaScript or protected against bots: when a site blocks plain requests, the page is fetched from a second network or audited in a real browser automatically, and a page that loads almost empty until JavaScript runs (a client-rendered app shell) is rendered in a real browser too. Each audited page gets a score from 0 to 100, category scores and a list of issues with a fix hint for every issue. The whole site gets a report: score distribution, most common problems, crawl coverage (indexable / noindex / canonicalized / 4xx / 5xx), sitemap checks, canonical and redirect problems, duplicate titles, H1s and content, and broken internal pages.
25+ checks per page, including:
- 🏷️ Title: missing, too short, too long (search results cut titles at about 60 characters)
- 📝 Meta description: missing, too short, too long
- 🔠 Headings: missing, empty or multiple H1s, skipped heading levels (H2 → H4), full heading outline
- 🔗 Canonical & indexing: missing or conflicting canonical,
noindexornonein meta robots or theX-Robots-Tagheader, pages that robots.txt blocks for Googlebot - 🖼️ Images: images without an alt attribute (
alt=""on decorative images is correctly accepted); optional check for broken and oversized images - 🌍 hreflang: invalid codes (
en-UK,jp), missingx-default, missing self-reference, duplicate codes, broken or redirecting targets, missing return links - 📱 Mobile & language: viewport tag,
langattribute - 🌐 Social & structured data: Open Graph title/image, Twitter card, and JSON-LD and Microdata validated against Google's rich result rules (errors, eligible rich results; RDFa types listed)
- ⚙️ Technical: HTTP status, HTTPS, an HTTP start URL that does not redirect to HTTPS, mixed content (HTTP images, scripts or stylesheets on an HTTPS page), redirect chains, temporary redirects (302/303/307 where a permanent 301/308 may be meant; redirects to a language/country version or a login page are not reported), slow server response, very large HTML, thin content (word count; Chinese and Japanese text, written without spaces, is estimated from its length)
- 💔 Broken pages: internal links that return 404/410/5xx, with the page that links to them. A page-discovered link whose path is exactly
/cdn-cgi/l/email-protectionis skipped, with or without a fragment, because it is Cloudflare's email helper rather than a content page. Other broken URLs and an explicitly entered helper start URL are handled normally.
Site-level checks (in the report):
- 🗺️ Sitemap: no sitemap found, sitemap URLs that return errors, redirect or are
noindex, crawled indexable pages missing from the sitemap - ↪️ Canonical tags that point to an error page, a redirect or a
noindexpage - 🔁 Internal links that point to a redirect instead of the final URL
- 👯 Duplicates: titles, meta descriptions, H1s and identical page text (duplicate content)
- 📊 Crawl coverage: indexable, noindex, canonicalized, redirected, 4xx, 5xx, blocked by robots.txt, deepest page
Optional:
- ⚡ Core Web Vitals with Google PageSpeed Insights, no API key needed: performance score, LCP, CLS, TBT, FCP and real-user INP for the pages you choose ($5 per 1,000 pages measured)
- 📈 Monitoring: compare with the previous run and see issues added or resolved per page and the score change
Why use it?
- Works on JavaScript sites and sites that block bots. Many SEO crawlers analyze static HTML only; this one falls back to a real browser automatically when a site blocks plain requests or a page has almost no text until JavaScript runs.
- A fix hint for every issue, plus category scores (technical, indexing, meta, headings, content, links, images, social, schema, performance, mobile, international).
- $5 per 1,000 pages, no extra compute charges. See the price comparison below.
- Pay only for audited pages: broken pages are reported for free; blocked pages, pages skipped by robots.txt and downloads are not charged.
- Built for recurring checks: schedule it weekly with Compare with the previous run to get issues added and resolved per page, score changes and new broken pages, or feed the JSON into your dashboard.
Cost control and price comparison
Control your cost
- Charged: one
Audited pageevent per HTML page audited (charged: trueon the row); PageSpeed only when a score comes back. - Free (
charged: false, theerrorends with "(not charged)"): invalid input lines (errorType: "invalid_input"), broken internal pages (a finding), blocked, failed and timed-out pages, downloads (PDF, images) and pages skipped by robots.txt. A start URL that is skipped or is not an HTML page still gets a row (errorType: "robots_txt"or"unsupported"); downloads found while crawling are skipped without a row. - Before it starts, the run logs its plan: Max pages × price (plus PageSpeed pages if on) is the most it can cost, compared with your maximum charge per run (run options). Small sites have fewer pages and cost less. The plan is saved as
costPlaninSUMMARY. - When the spending maximum is reached, the run stops: the status message says so,
SUMMARY.statusisLIMIT_REACHEDandSUMMARY.notProcessedhas the number of pages found but not audited and the first 100 of them. - If Apify restarts the run (server migration or Resurrect), pages audited before it are read again for the report but not charged again and not written to the dataset twice (
SUMMARY.resumed).
Price comparison (checked October 2026)
| Actor | Total price for 1,000 pages (Free plan) | Crawls the site | Real-browser fallback |
|---|---|---|---|
| SEO Audit Tool (this Actor) | $5.00, nothing else: no start fee, compute included | Yes | Yes |
| smart-digital/complete-seo-audit-tool | $40.00 | Yes | No (static HTML) |
| logiover/website-seo-audit-crawler | $10.00 + $0.00005 start fee | Yes | No (plain HTTP only) |
| autofacts/metadata-scraper | $5.00; the max charge per run must be set to at least $0.10 | Only with followLinks (off by default) | No (no JavaScript) |
| scrapesignal_labs/website-seo-audit | $0.50 + $0.00005 start fee + platform usage | Yes | No |
A 1,000-page audit with this Actor costs $5, which the Apify Free plan's $5 monthly credit covers.
We are not the cheapest: scrapesignal_labs costs less per page, but you also pay the Apify compute it uses, and it has no browser fallback. At $5 this Actor is the one with a real-browser fallback, a fix hint per issue and site-level checks (sitemap, canonical targets, duplicates, hreflang return links). For PageSpeed, lizaraco/site-audit charges $10 per 1,000 measurements; this Actor charges $5.
Switching from another SEO audit Actor
Already running an SEO audit Actor? You can keep most of your input: this Actor also accepts the field names other SEO audit Actors use and maps them to its own. The run log lists every mapping ("Input: ...").
| Their field | Read here as |
|---|---|
startUrls as [{ "url": ... }], or url / startUrl (one URL) | Start URLs |
maxRequestsPerCrawl, maxPagesToCrawl, maxCrawlPages, maxPagesPerCrawl | maxPages |
crawlScope: "same-domain" / "same-hostname" | domain / host |
followLinks: false or crawlPages: false | Only the start URLs |
includeSubdomains: true / false | Whole website / Only this host |
maxPages together with crawlPages or includeSubdomains (a per-site limit there) | maxPagesPerSite, so several sites get the same pages each as before |
excludeUrlPatterns: ["/tag/"] (a URL fragment) | the glob **/tag/** |
proxy | proxyConfiguration |
auditSchema: false | structured data validation off |
Per-check switches (auditImages, extractMetaTags, ...) and useProxy | ignored: every check always runs, at the same price |
maxDepth, maxConcurrency, respectRobotsTxt and excludeUrlPatterns have the same names here. One difference: if your startUrls is a list of plain strings (["https://example.com"]), rename it to urls, because Apify only accepts { "url": ... } objects in startUrls.
Compared with the two most-used SEO audit Actors (Apify Store, checked 2 October 2026)
| SEO Audit Tool (this Actor) | smart-digital/complete-seo-audit-tool | autofacts/metadata-scraper | |
|---|---|---|---|
| Price per 1,000 pages (Free plan) | $5.00 | $40.00 | $5.00 |
| With a paid Apify plan | $4.50 (Silver), $4.00 (Gold and up) | $35.00 (Silver), $30.00 (Gold and up) | $3.50 (Silver), $3.00 (Gold and up) |
| Start fee / platform usage | None / included | None / included | None / included; max charge per run at least $0.10 |
| Failed pages charged? | No: broken, blocked, failed and timed-out pages are free | Not stated in its README | No: failed URLs are not charged |
| Crawls the whole site by default | Yes | Yes (5 pages per start URL by default) | No: only with followLinks (off by default) |
| JavaScript sites / sites that block bots | Yes: second network, then a real browser | No: static HTML; anti-bot sites out of scope | No: no JavaScript |
| Fix hint per issue | Yes | Yes | Issues with severity and the recommended value |
| Site checks | Sitemap cross-check, canonical and redirect targets, duplicates, hreflang return links, broken pages, soft-404 check | Sitemap discovery, robots.txt, duplicate titles and descriptions, redirects, broken links | hreflang, local-business data |
| Core Web Vitals | Optional, Google PageSpeed Insights (lab data, plus real-user data when Google has it), $5 per 1,000 pages | Lab timings measured during the crawl, included | No |
| Compare with the previous run | Yes (issues added and resolved, score change) | Not mentioned in its README | Not mentioned in its README |
| Not in this Actor | Readability, keyword density, title width in pixels, basic accessibility checks | Full schema.org extraction (RDFa, hCard) |
Prices are the Store's pay-per-event prices on the date above; check each Actor's Pricing tab for the current rate.
Paste your existing input. A typical input from another SEO audit Actor runs here unchanged, except that a list of plain strings in startUrls goes in urls:
{"urls": ["https://example.com", "https://example.org"],"crawlPages": true,"maxPages": 20,"includeSubdomains": false,"respectRobotsTxt": true,"excludeUrlPatterns": ["/tag/", "/author/"]}
This audits up to 20 pages of each site on its own host, skips URLs containing /tag/ or /author/ and respects robots.txt: at most 40 pages, $0.20 on the Free plan. An input with startUrls: [{ "url": ... }], followLinks, maxDepth and maxRequestsPerCrawl, or with crawlScope: "same-domain" and maxPagesToCrawl, also runs as it is.
How to use it
- Enter your website URL in URLs or domains, e.g.
https://www.example.com/or justexample.com(one per line for several sites). A bad line never stops the run: it becomes a row witherrorType: "invalid_input"(not charged). - Set Max pages (100 is a good start).
- Optional: turn on Also audit pages from the sitemap, Respect robots.txt, Check images, Measure Core Web Vitals or Compare with the previous run, or limit the crawl with include/exclude URL patterns.
- Click Start. Open Site report in the Output tab for the summary, or the Pages, Issues and fixes and Category scores tables for details.
The prefilled input audits two sites in one run (python.org and books.toscrape.com, 20 pages shared between them): $0.10, usually done in about a minute, and the REPORT has a per-site table comparing them. Replace them with your own sites.
Input example
{"urls": ["https://www.python.org/"],"crawlScope": "domain","maxPages": 100,"useSitemap": true,"excludeUrlPatterns": ["**/blog/tag/**"]}
Crawl scope: Whole website includes subdomains (blog.example.com, shop.example.com); Only this host stays on the start URL's host (www. and the bare domain count as one); Same folder and Only the start URLs narrow it further. With Same folder, a start page that links to nothing inside its own folder (sites that give every page its own folder, such as /en/getting-started/) is audited with the folder one level up (/en/, never the whole site), listed in SUMMARY scopeWidened.
Use urls (a plain list of strings) in API calls. The older startUrls field ([{ "url": "https://…" }]) still works and can hold uploaded or linked URL list files, but Apify rejects the whole request with HTTP 400 if one of its entries is a bare domain or a blank line.
Several sites in one run (agencies). Put one site per line in URLs or domains. Max pages is shared fairly: each site gets its share (Max pages ÷ number of sites) while the others are still being crawled, and a small site leaves its unused share to the others. Set Max pages per site for a fixed cap. Every row has site (host without www.) and startUrl, the sitemap of every site is read (up to 20 sites), SUMMARY.sites has one entry per site (pagesAudited, averageScore, brokenPages, pagesFailed, sitemapFound, sitemapUrls) and the REPORT has a Per site table. Real run (September 2026, maxPages: 12): python.org, books.toscrape.com and webscraper.io got 4 pages each.
Turn on Compare sites side by side (compareSites: true) to put the first input site alongside competitors. SUMMARY.siteComparison, the Markdown REPORT and REPORT_HTML show each site's average score, audited pages, pages with issues, broken and failed pages, and category scores side by side. Numeric gaps (competitor minus first site) are in SUMMARY.siteComparison.gaps and the Markdown REPORT, not in REPORT_HTML; a score gap is unavailable when a site has no audited page. Negative score gaps mean the competitor scored lower. Compare similar page samples and use Max pages per site if needed. This option adds no charge; only successfully audited pages are billed.
{"urls": ["https://www.python.org/", "books.toscrape.com", "https://webscraper.io/"],"maxPages": 12}
Output: one item per page
For an HTTPS page with HTTP images, scripts or stylesheets, issues[] includes one mixed-content issue with evidence.pageUrl, up to three evidence.resourceUrls, and evidence.totalResources (the full detected count). For an HTTP start URL that remains HTTP, no-https-redirect includes evidence.startUrl and evidence.finalUrl. Evidence URLs omit query strings, fragments and URL credentials. For example, http://example.com/a.js?token=abc appears as http://example.com/a.js in resourceUrls. These fields use URLs already collected during the audit; they do not add requests. Other issues may have no evidence field. Scores and billing are unchanged.
A page whose URL reaches the final page through a temporary redirect (HTTP 302, 303 or 307) gets a temporary-redirect notice, for example URL redirects with HTTP 302 to https://example.com/new., with evidence.fromUrl, evidence.status and evidence.toUrl of the first such hop. Redirects that only add a language/country folder (/pricing → /en-sg/pricing) and redirects to a login or bot check page are not reported. To see each hop's status code, the Actor re-checks the redirect chain of redirected pages (one light HEAD request per hop), at most 20 pages per site when the page itself was read by our servers. Like other notices, it lowers the page score by 2; billing is unchanged.
Real output for https://www.python.org/ (shortened):
{"url": "https://www.python.org/","site": "python.org","startUrl": "https://www.python.org/","input": "https://www.python.org/","success": true,"mode": "fast","via": "direct","score": 93,"issueCount": 2,"issueCodes": "h1-multiple, canonical-missing","issues": [{ "id": "h1-multiple", "severity": "warning", "message": "Page has 5 H1 headings.", "category": "headings","fixHint": "Keep a single main H1 and turn the others into H2 headings.", "impact": "medium" },{ "id": "canonical-missing", "severity": "notice", "message": "Canonical link is missing.", "category": "indexing","fixHint": "Add <link rel=\"canonical\"> pointing to the preferred URL of this page (usually itself).", "impact": "low" }],"categoryScores": { "technical": 100, "indexing": 94, "meta": 100, "headings": 85, "images": 100, "social": 100, "...": 100 },"httpStatus": 200,"title": "Welcome to Python.org","titleLength": 21,"metaDescriptionLength": 52,"h1": ["Intuitive Interpretation", "Compound Data Types", "..."],"headings": [{ "level": 1, "text": "Intuitive Interpretation" }, "..."],"canonical": null,"xRobotsTag": null,"blockedByRobotsTxt": false,"viewport": true,"wordCount": 1182,"contentHash": "9f824c550ac6dbe88bd0688e","bytes": 53229,"imagesMissingAlt": 0,"nofollowLinks": 0,"hreflangLinks": [],"twitterCard": null,"structuredDataTypes": ["WebSite"]}
via tells you where the page was read from: backend (our servers), direct (Apify's network, used when a site blocks our servers), vm (our second server, Oracle, different IP) or browser. Failed pages carry an errorType (blocked, not_found, timeout, network, unsupported, robots_txt…). input (on start pages) is your original line; issueCodes is the issue IDs as one text column for spreadsheets; charged says whether the row was charged.
Output: site report
The key-value store contains REPORT_HTML (printable client report), REPORT (Markdown) and SUMMARY (JSON). From the same python.org run (20 pages):
{"status": "SUCCESS","pagesAudited": 20,"averageScore": 94.4,"scoreDistribution": { "80-100": 20, "60-79": 0, "40-59": 0, "0-39": 0 },"categoryAverages": { "technical": 100, "indexing": 94, "meta": 99.7, "headings": 89.5, "...": 100 },"site": {"coverage": { "crawled": 20, "indexable": 20, "noindex": 0, "canonicalized": 0, "redirected": 3, "clientErrors4xx": 0, "serverErrors5xx": 0, "blockedByRobotsTxt": 0, "maxDepth": 1 },"sitemap": { "found": false, "urls": 0 },"internalLinksToRedirects": [{ "url": "https://www.python.org/psf/", "finalUrl": "https://www.python.org/psf-landing/", "linkedFrom": "https://www.python.org/" },{ "url": "https://www.python.org/doc/av", "finalUrl": "https://www.python.org/doc/av/", "linkedFrom": "https://www.python.org/" }],"canonicalProblems": [],"duplicateH1": [],"duplicateContent": [],"hreflangProblems": []}}
status is SUCCESS, PARTIAL_RESULTS (some pages failed), FAILED, NO_RESULTS or LIMIT_REACHED (stopped at your spending limit). The summary also lists the most common issues (with fix hints), the lowest-scoring pages, duplicate titles and descriptions, and broken internal pages with the page that links to them. It also has sites (one entry per site), reportHtmlUrl (direct link to REPORT_HTML), invalidInputs and costPlan. A spending-limit stop adds notProcessed. A page-cap stop with discovered URLs left adds pagesLimitReached: true, pagesDiscoveredNotAudited and nextStep; this count covers discovered URLs only. When both Max pages and Max pages per site are reached, nextStep asks you to raise both. With Respect robots.txt on, a skip rate of at least 30% among processed URLs adds robotsSkipNotice.
SUMMARY.site.soft404 shows whether each site answers with a real 404 for a page that does not exist. After the crawl, the Actor requests one made-up URL per site (for example /tidytools-404-check-1f3a9c...). sites[].result is ok (HTTP 404 or 410), soft_404 (HTTP 200, also after a redirect to another page), redirects_to_home (a redirect that ends on the home page with HTTP 200) or unknown (no clear answer: blocked, rate limited, server error, timeout, connection error, or skipped because Respect robots.txt is on and robots.txt disallows the path). Each entry also has site, probeUrl, status, finalUrl and redirects; soft_404 and redirects_to_home add problem and fixHint; unknown adds reason. sitesProbed and problemSites are the totals, and unknown is not counted as a problem. The check is free, adds no Dataset row and does not change page scores. It is a direct request from the Actor, without your proxy settings. REPORT_HTML and the Markdown REPORT list the results.
Each SUMMARY.sites[] entry also has aiReadiness: score, robots.bots (GPTBot, ClaudeBot, PerplexityBot, Google-Extended and CCBot status), robots.searchAccess (OAI-SearchBot, Claude-SearchBot, PerplexityBot and Googlebot), llmsTxt.status, structuredData (audited-page JSON-LD coverage and type counts), answerContent (FAQ/HowTo pages and question-heading ratio), and up to five recommendations. compareSites includes aiReadinessScore in the gaps; compareWithPrevious adds per-site scores and changes under SUMMARY.changes.aiReadiness. GPTBot, ClaudeBot, Google-Extended and CCBot are training controls, so blocking them alone does not mean AI search is blocked. These checks add no billed events and do not refetch audited pages.
How to get the client report
After the run finishes, open its Storage → Key-value store → REPORT_HTML and download the HTML file. You can also open SUMMARY.reportHtmlUrl; the run status message points to the same record. Open the downloaded file in a browser and choose Print → Save as PDF to share it with a client. The CSS is embedded. If you set branding.logoUrl, the report loads that HTTPS image when opened or printed; keep the image online for your client.
The report starts with sites, audit date, audited page count and average score. Its top 10 fixes are ranked by affected pages × severity and show a sample page and the audit's fix hint. Category scores follow. Turn on Compare sites side by side to add a site comparison table; turn on Compare with the previous run for new issues, fixed issues and score regressions after the first baseline run. Sections with no corresponding data are omitted. The comparison uses audited pages only. The AI search readiness section gives a score and the underlying signals for each site. Its score also appears in the side-by-side matrix and previous-run comparison.
White-label reports
Set the optional branding object to put your agency name, logo, color and footer on REPORT_HTML:
{"urls": ["https://www.python.org/"],"branding": {"agencyName": "Example Agency","logoUrl": "https://example.com/logo.png","primaryColor": "#146A91","footerText": "Prepared for our client","hideTidyToolsCredit": true}}
agencyName replaces the older reportBrandName in the HTML title when both are set. The older field still works. logoUrl must be a public HTTPS URL; the HTML references the image, so the client needs network access when viewing or printing it. Invalid logo URLs are omitted. Only #RRGGBB colors are applied; invalid colors use the standard palette. All text is HTML-escaped. hideTidyToolsCredit removes TidyTools attribution; the default for a branded report is to show it. Leave out branding to keep the existing report unchanged.
reportLanguage is en by default; set zh-TW for Traditional Chinese report labels. Issue IDs and fix hints remain in the audit's original language. Branding changes only REPORT_HTML: Dataset rows, SUMMARY, the Markdown REPORT, and billed events stay the same. The HTML file adds no charge.
Optional: PageSpeed Insights (Core Web Vitals)
Turn on Measure Core Web Vitals (PageSpeed Insights) to run Google's PageSpeed Insights on the start URL(s) and the next pages audited, up to Max pages to measure (default 5). No setup is needed: without your own key, our servers run Google PageSpeed Insights with our key, and when Google's quota is busy they measure with our own Lighthouse server instead. With your own key, the Actor calls Google directly.
Where to measure from (pagespeedSource): lab results depend on where Lighthouse runs, so you can pick the location. auto (default) uses Google, then our Lighthouse servers; google uses Google only; lighthouse uses our servers only (Taiwan first, then Phoenix, US); lighthouse-tw and lighthouse-us measure from that one location and are never swapped for another (if it is unavailable, the page is reported as not measured and is not charged). Choose Taiwan when your visitors are in Asia. Our servers give lab data only; real-user field data comes from Google. The price is the same for every option.
Each page gets a pagespeed object:
| Field | Meaning |
|---|---|
performanceScore | Lighthouse performance score, 0-100 |
lcpMs, cls, tbtMs, fcpMs, speedIndexMs | Lab measurements: Largest Contentful Paint, Cumulative Layout Shift, Total Blocking Time, First Contentful Paint, Speed Index |
inpMs | Interaction to Next Paint from real-user data (lab tests cannot measure INP), when Google has enough traffic for the page or its site |
field | Real-user (Chrome UX Report) 75th percentiles: lcpMs, inpMs, cls, fcpMs, ttfbMs, category, and source (url or origin) |
source | Who measured: google (Google with our key), vm-lighthouse (our Lighthouse servers: lab data only, no field/inpMs) or google (your key) |
node, nodeLocation | With vm-lighthouse: which of our servers measured, tw (Taiwan) or us (Phoenix, US) |
ttiMs, opportunities | Time to interactive and the top savings suggestions (id, title, estimated ms/bytes saved); measured through our servers |
error, errorKind | Why a page was not measured (quota, key, page, api, skipped); such pages are not charged |
Price: $5 per 1,000 pages measured (pagespeed-audit event), charged only when a score comes back.
Real result (September 2026, mobile, 3 of 5 audited python.org pages measured with pagespeedMaxPages: 3; charged 5 × page audit + 3 × PageSpeed):
{"url": "https://www.python.org/","score": 93,"pagespeed": {"strategy": "mobile","performanceScore": 78,"lcpMs": 4802,"cls": 0.003,"tbtMs": 0,"fcpMs": 2718,"speedIndexMs": 2718,"inpMs": 88,"field": { "source": "url", "category": "FAST", "lcpMs": 1225, "inpMs": 88, "cls": 0, "fcpMs": 1158, "ttfbMs": 526 },"lighthouseVersion": "13.5.0","measuredUrl": "https://www.python.org/"}}
The lab LCP (4.8 s) is much slower than what real visitors see (field.lcpMs 1.2 s): Lighthouse simulates a slow phone on a throttled mobile network. Use the lab numbers to compare pages and track changes, and the field numbers for what your visitors actually experience. Lab scores also vary a few points between runs.
Optional: use your own Google API key (about 5 minutes)
You do not need a key. Add one only if you want the calls to count against your own Google quota (for example very large runs): put it in Your own Google API key. If our daily capacity is ever used up, the remaining pages are marked errorKind: "quota" and not charged, and adding your key lets you measure them.
- Open Google Cloud Console and create a project (no billing account needed).
- Enable the PageSpeed Insights API.
- Go to APIs & Services → Credentials → Create credentials → API key.
- Recommended: edit the key, keep Application restrictions: None (Apify servers have changing IPs) and set API restrictions to PageSpeed Insights API only.
- Paste the key into the Actor input. It is stored encrypted.
The API is free; your daily quota is shown in Google Cloud Console under the PageSpeed Insights API's Quotas page.
After a quota or key error the Actor stops measuring for the rest of the run (those pages are not charged). SUMMARY.pagespeed has the average performance score and the pages sorted from slowest, and the REPORT gets a PageSpeed table.
Structured data (JSON-LD and Microdata)
Every page gets a structuredData object checked with the same rules as Schema Markup Validator (by TidyTools): status, formats (json-ld, microdata), types, eligible richResults, errorCount, warningCount, the first errors with code and path, and rdfaTypes (RDFa is detected, not validated). It does not change the page score. Real result from webscraper.io (September 2026), a page marked up with Microdata only:
"structuredData": {"status": "errors","formats": ["microdata"],"types": ["OfferCatalog", "VideoObject"],"richResults": [],"errorCount": 2,"warningCount": 2,"errors": [{ "code": "REQUIRED_MISSING", "type": "VideoObject", "property": "thumbnailUrl", "path": "microdata[1]", "format": "microdata" },{ "code": "REQUIRED_MISSING", "type": "VideoObject", "property": "uploadDate", "path": "microdata[1]", "format": "microdata" }],"rdfaTypes": []}
Turn off Validate structured data to skip it.
Monitoring: compare with the previous run
Turn on Compare with the previous run and schedule the Actor (for example weekly). Each page gets change (new, changed, unchanged), previousScore, scoreDelta, issuesAdded and issuesResolved. SUMMARY.changes and a Since the last run section in the REPORT show the average score change, issues added (including issues on new pages) and resolved (by issue ID), the biggest score drops and gains, new broken pages, fixed broken pages and pages no longer found. Runs with the same Monitor name are compared (default: start URL + crawl scope). The first run is the baseline. If a run stops before the whole site is crawled (Max pages or spending limit), pages it did not reach are kept and not reported as removed.
The previous snapshot lives in the named Apify KV store for Monitor name; no dataset ID is needed. Keep the URL list, crawl scope and monitor name stable between weekly runs. SUMMARY.changes.issueChanges lists up to 500 page/issue entries marked new or fixed (including issues on newly discovered pages), with issueChangesTruncated for any remainder. regressions lists up to 100 pages whose score fell. The REPORT includes the page/issue list. The dataset's Changes since last run view includes site, charged, and each page's added/resolved issue IDs. The first run is a baseline, so issue changes start with the second run.
Optional weekly regressions digest: set weeklyChangesDigest: true together with compareWithPrevious: true and an explicit, stable monitorName. Each run then writes WEEKLY_CHANGES (JSON) and WEEKLY_CHANGES_MD (Markdown) to its default Key-value store. SUMMARY.weeklyChanges gives count, counts (issuesAdded, scoreDrops, removedPages) and public jsonUrl / markdownUrl record links. The JSON also gives monitorName, firstRun, partialRun and sites; each affected site has its own count, issuesAdded (url, issue), scoreDrops (url, previousScore, score, scoreDelta) and removedPages (URLs). count counts individual new issues, score drops and removed pages; it is 0 on the baseline and when there are only fixes, improvements or no changes. A partial crawl never claims a page was removed. The regular dataset, SUMMARY.changes, REPORT and REPORT_HTML remain complete; the digest adds no charge.
To notify a client, create an Apify Schedule for the saved input and attach a run succeeded webhook to your own automation. The webhook fires after every successful run, including the baseline and runs with count: 0; it does not filter changes for you. In the downstream automation, use the run's default Key-value store to read its WEEKLY_CHANGES record (or SUMMARY.weeklyChanges.jsonUrl), check count > 0, then send the digest to the client. If count is 0, send nothing. Keep the Schedule's input and monitor name stable; use a different monitor name for a different URL set. The existing reportBrandName can brand the full HTML report; it does not change digest contents.
Weekly schedule setup
- Enter the URLs, crawl scope and Max pages, turn on Compare with the previous run and Weekly regressions digest, and enter an explicit Monitor name such as
example-com-weekly. Without a Monitor name, the digest option stops the run with an error before any page is audited. - Save this input as an Apify Task and run the Task once. That run is the baseline:
WEEKLY_CHANGEShascount: 0, and its audited pages are charged as usual. - In Apify Console, open Schedules, create a new schedule with a weekly cron expression such as
0 8 * * 1(Mondays at 08:00) and your timezone, add the Task, save, and switch the schedule to Enabled (new schedules start disabled). - Keep the URLs, crawl scope and monitor name the same in later runs. A different monitor name has no previous snapshot, so its first run becomes a new baseline.
Real second run on webscraper.io (September 2026; for the test, the previous snapshot of two pages was edited):
[{ "url": "https://webscraper.io/", "score": 96, "change": "changed", "previousScore": 90, "scoreDelta": 6,"issuesAdded": ["structured-data-missing"], "issuesResolved": ["title-missing"] },{ "url": "https://webscraper.io/pricing", "score": 94, "change": "changed", "previousScore": 100, "scoreDelta": -6,"issuesAdded": ["canonical-other", "heading-skip", "structured-data-missing"], "issuesResolved": [] }]
REPORT excerpt from the same run:
## Since the last run- Average score: **97.3** (+2.3 from 95)- Issues added: **4**, resolved: **1** (added count includes issues on new pages; compared 6 page(s) seen in both runs and 0 new page(s))- New pages: **0**, pages no longer found: **0**, new broken pages: **0**, broken pages fixed: **1**
Pages are audited and charged the same way with or without monitoring.
Use with AI agents (MCP)
Connect Apify's MCP server (https://mcp.apify.com?tools=tidytools/seo-audit-crawler) to Claude, Cursor or any MCP client, then ask e.g. "Audit the first 50 pages of example.com and list the three fixes that would raise the score most."
{ "urls": ["https://example.com/"], "maxPages": 50 }
Failed pages are not charged and carry an errorType, so the agent can tell a blocked site from a broken page.
Use it from code and integrations
Run it from your own code with the Apify API. This call waits for the run and returns the results as JSON (replace YOUR_TOKEN with your Apify API token):
curl -X POST "https://api.apify.com/v2/acts/tidytools~seo-audit-crawler/run-sync-get-dataset-items?token=YOUR_TOKEN" \-H "Content-Type: application/json" \-d '{"urls":["https://www.python.org/"],"maxPages":20}'
The synchronous endpoint waits up to 5 minutes. For bigger runs, start the run with POST https://api.apify.com/v2/acts/tidytools~seo-audit-crawler/runs and read the dataset when it finishes, or use the apify-client package for JavaScript or Python.
Schedules and integrations: run it daily or weekly with Apify Schedules, get a webhook when a run finishes, or send the results to Zapier, Make, n8n, Google Sheets, Slack and other apps with Apify integrations. Results can be exported as JSON, CSV, Excel or XML.
Scoring
Each page starts at 100. Critical issues (e.g. missing title, missing H1, noindex, HTTP errors, no HTTPS) cost 15 points, warnings (e.g. missing meta description, images without alt text, invalid hreflang) cost 5, and notices (e.g. missing canonical, Open Graph or structured data) cost 2. Each category score starts at 100 and loses three times those points for issues in that category.
Redirects that only add a language or country folder (/pricing → /en-sg/pricing) depend on where the request comes from, so they are counted separately and not reported as problems.
Advanced settings
- Plain HTTP requests from: Auto (recommended) reads pages from our servers and retries from Apify's network when a site blocks data-center requests, then from our second server (Oracle, different IP), before falling back to a browser. A Cloudflare 520-530 error answered to our servers (common for sites that sit behind Cloudflare) counts as a refusal and is retried the same way. You can force any one of them.
- Time limit per page: default 120 seconds for reading a page, all retries and the browser fallback included. A slower page gets
errorType: "timeout"and is not charged. 0 = no limit. - Image checks (Check images) count toward the same page time limit. When it runs out, the page is still audited, and
imageCheckhasincomplete: trueand the number of images not checked. - Proxy: optional Apify Proxy for requests sent from Apify's network (billed to your Apify account).
Limitations
- On-page checks: no backlinks or keyword rankings. Core Web Vitals only with the optional PageSpeed Insights measurement (no key needed).
- JSON-LD and Microdata are validated; RDFa types are only listed.
- Only public
http/httpspages; no logins. - Download files (PDF, ZIP, installers) found while crawling are skipped; a start URL that is a download gets a free row with
errorType: "unsupported". - Sitemap URLs outside the crawl are checked as a sample (up to 100 per run).
FAQ
Can this tell whether ChatGPT, Perplexity or Google AI can cite my site? It checks robots.txt access for five named AI crawlers, validates /llms.txt, and measures JSON-LD and answer-style content on pages this run already audited. It cannot verify a live AI engine's index, firewall access or citation decisions. In particular, GPTBot and Google-Extended control training use; Google AI search also depends on Googlebot and other factors. The checks are included in the existing audited-page price.
Does the Actor collect usage data? The Actor sends one anonymous usage counter per run (input type, item count range, outcome) to improve the tool; no URLs, content or account data.
Does it measure Core Web Vitals? Yes, optionally. Turn on PageSpeed Insights for the pages you choose to get the performance score, LCP, CLS, TBT, FCP and real-user INP. No API key is needed; it costs $5 per 1,000 pages measured.
Can it audit JavaScript websites? Yes. In Auto mode a page that comes back almost empty (under about 200 characters of visible text, typical for client-rendered React or Vue apps) is audited again in a real browser, and a site that blocks plain requests is fetched from a second network or a browser (mode: "browser" on the row). Text or tags that JavaScript adds to a page that already has content are only seen with Page fetching (HTTP or browser) set to Browser.
Does it check backlinks or keyword rankings? No. It is a technical and on-page SEO checker: titles, meta, headings, canonical, hreflang, structured data, broken pages, sitemap and duplicates.
Can I track SEO issues over time? Yes. With Compare with the previous run on, each run is compared with the previous one and shows the issues added or resolved per page and the score change.
How many websites can I audit in one run? Up to 100 start URLs across urls and startUrls, so you can enter up to 100 different sites with one start URL each. Max pages applies to all sites together; use Max pages per site for a cap on each. Sitemap checks cover the first 20 sites.
How do I set up a weekly comparison? Save an input with compareWithPrevious: true and a stable monitorName, then create a weekly Apify Schedule for this Actor using that input. Keep the URLs and crawl scope the same. The first run is the baseline; later runs show added or resolved issues and score changes in SUMMARY.changes and the reports.
How long does a 100-page run take? There is no reliable fixed duration: site response times, browser fallback and optional PageSpeed measurements vary. Check the duration of a completed run on your target site before planning a client delivery time.
What are the limits? Up to 10,000 pages per run (Max pages), 120 seconds per page by default (Time limit per page), PageSpeed on up to 500 pages per run, and sitemap URLs outside the crawl are checked as a sample of up to 100. Before it starts, the run logs the most it can cost (Max pages × price); it never charges more than your maximum charge per run and stops there, keeping the pages audited so far.
How fast is it? The prefilled input (2 sites, 20 pages) is usually done in about a minute. Pages are audited in parallel (Parallel pages: 10 by default, up to 30); pages that need the browser fallback and PageSpeed measurements take longer.
Can I audit any website? Does it respect robots.txt? It reads only public pages (no logins) and reports for every page whether robots.txt blocks Googlebot. Respect robots.txt is off by default because the Actor is meant for sites you own or are allowed to audit: an audit has to see pages that robots.txt may hide from crawlers. Use it only on sites you own or may audit; turn Respect robots.txt on to follow robots.txt and skip the pages it disallows for crawlers (skipped pages are not charged; a skipped start URL gets a row with errorType: "robots_txt"). By default it follows the rules for TidyTools, which on most sites are those for all crawlers (User-agent: *); set Follow robots.txt rules for (robotsUserAgent) to Googlebot or Bingbot to follow the rules the site gives that search engine instead, so you audit the pages Google or Bing may crawl (pages are still requested with our own User-Agent). A skipped start URL that Googlebot may crawl says so in its error, and the SUMMARY counts them (skippedButAllowedForGooglebot). Auditing public pages is generally allowed, but follow each site's terms of service; this is not legal advice.
Does it work with the API, schedules, webhooks, Make, n8n or Zapier? Yes. Start runs from the Apify API (examples), an Apify schedule or a webhook, use the Apify apps for Make, n8n and Zapier, or call it from AI agents through MCP.
Support
Open an issue in the Issues tab with your start URL and input. Issues are checked regularly.
Found this useful? A short review on the Store helps other users find it.
Suspected infinite URLs
Discovered URLs longer than 2,000 characters or with the same path segments repeated three times are skipped. After five consecutive non-2xx responses from URLs with the same parent path or query parameter names, new URLs in that group are skipped until a 2xx response resets the count. Skipped URLs are not requested or charged as crawl pages, and they do not use Max pages. SUMMARY.skippedTrapUrls reports the number skipped (0 when none). Explicit start URLs are still attempted.