Complete SEO Audit Tool - Website Crawler with 0-100 Scores avatar

Complete SEO Audit Tool - Website Crawler with 0-100 Scores

Pricing

from $30.00 / 1,000 pages

Go to Apify Store
Complete SEO Audit Tool - Website Crawler with 0-100 Scores

Complete SEO Audit Tool - Website Crawler with 0-100 Scores

Crawl a website and get a 0-100 SEO score for every page: meta tags, headings, Core Web Vitals, broken links, images and schema, each issue returned with its priority and a fix hint, as JSON.

Pricing

from $30.00 / 1,000 pages

Rating

5.0

(3)

Developer

My Smart Digital

My Smart Digital

Maintained by Community

Actor stats

27

Bookmarked

720

Total users

92

Monthly active users

8 days ago

Last modified

Share

Complete SEO Audit Tool

Crawl any website and get a 0-100 SEO score for every page, the exact issues behind that score, and a fix hint for each one — as structured JSON you can pipe into a report, a dashboard or an AI agent.

No account on the audited site, no browser extension, no manual checking. Give it a URL, get an audit.

Who it's for

  • SEO freelancers and agencies running audits for clients, who need the findings as data instead of a PDF.
  • Developers wiring an SEO check into CI, a monitoring job or an internal dashboard.
  • AI agents that need a factual, scored snapshot of a site before writing recommendations.

What you get

Two kinds of records in the dataset:

  • One page record per audited page — overall score, nine category scores, and the full detail behind each one.
  • One site-summary record per site (the site a start URL lands on, with the start URLs that led to it) — averages across its pages, top issues ranked by how many of its pages they hit, score distribution, crawl coverage, and the titles and descriptions its indexable pages share, with every page of each group. Several sites audited in one run each get their own summary, never a blend of them; a site that could not be audited at all still gets one, with 0 pages and no score.

Each issue comes twice: as a plain sentence (easy to display) and in a structured form with an ID, a category, a priority, an estimated impact and a fix hint (easy to sort and filter).

Input

{
"startUrls": ["https://example.com"],
"crawlPages": true,
"maxPages": 5,
"maxConcurrency": 5,
"respectRobotsTxt": true,
"excludeUrlPatterns": ["/tag/", "/author/"],
"includeSubdomains": false
}

startUrls also accepts objects: [{ "url": "https://example.com" }].

Parameters

ParameterDefaultWhat it does
startUrls (required)—URLs to audit. Every start URL that leads to an HTML page is audited; one that leads to a file (an image, a PDF, a feed) is skipped and not billed.
crawlPagestrueFollow internal links and audit several pages. Set to false to audit only the start URLs.
maxPages5Pages audited per site (the site a start URL lands on), start URLs included. 5 for a preview, 20 for a standard audit, 50+ for a deep one. Drives both duration and cost.
maxConcurrency5Pages audited in parallel. Lower it on slow or rate-limiting sites.
respectRobotsTxttrueSkip URLs disallowed by robots.txt.
excludeUrlPatterns[]Substrings to skip ("/tag/"), or regex wrapped in slashes ("/\\/page\\/\\d+/"). Useful to keep CMS archive pages out of the crawl. Links to files need no pattern: they are never followed.
includeSubdomainsfalseAlso crawl subdomains of the start URL.

Every audit module can be switched off individually (auditMetaTags, auditHeadings, auditContent, auditTechnical, auditPerformance, auditSchema, auditLinks, auditImages, auditAccessibility). All are on by default.

What gets checked

CategoryChecks
Meta tagsTitle and description with length in characters and rendered pixels, titles and descriptions repeated across the indexable pages of a site (flagged on each page that repeats one, every page of each group listed in its site-summary), Open Graph, Twitter Cards, canonical (including self-reference), robots meta
HeadingsFull H1-H6 tree with the text of each heading as a reader sees it: words split where a line break or a block splits them on the page ("Welcome to<br>Paris" reads "Welcome to Paris", not "Welcome toParis"), code a theme leaves inside a heading (a <style> block, the <noscript> copy of a logo) left out, spaces of the markup folded, original case kept. Multiple or missing H1, skipped levels
ContentWord and character count of the page's content: the main element when it holds the page's content, else the whole page (contentArea), always without navigation, the site's header and footer, sidebars, pop-ups, cookie banners, accessibility toolbars and screen-reader-only text. Paragraphs, sentences, average sentence length, a reading-ease score and level in the page's own language (told by its text: Flesch for English, Kandel-Moles for French, Amstad for German, Fernández-Huerta for Spanish, Franchina-Vacca for Italian, Douma for Dutch, Martins for Portuguese; other languages and pages under 30 words are not scored, score: null with the reason), keyword density with top keywords, the language's function words left out. Whether the H1 and the title tag share a keyword (h1TitleConsistency, with the sharedWords; case, accents and plurals aside, every H1 of the page compared): an H1 and title share no keyword warning only when both exist and share none, consistent: null when one is missing, which is reported on its own
TechnicalThe HTTP status the server actually returned for the page (a 404, 410 or 5xx page is reported as such and flagged not indexable), HTTPS and certificate validity, mobile viewport and responsiveness, robots.txt parsing, sitemap discovery (sitemaps declared in robots.txt first, then the usual locations; a sitemap only counts once the server actually returns one, never an HTML page served in its place, and a file is reported missing only when the server says so, with a 404, a 410 or an HTML page in its place), indexability from the robots meta tag, the X-Robots-Tag HTTP header, the page status and, when the canonical points to another URL (relative ones included, resolved as a browser does), that URL's status. A server that turns a check away (login, anti-bot wall, rate limit) leaves it unverified: the canonical is never reported as broken, and robots.txt or the sitemap is never reported missing nor counted against the technical score (verified: false, with the statusCode the server answered). Before that, the browser that loaded the page asks for robots.txt and the sitemap itself. Whether the page needs JavaScript to show its content, measured rather than guessed: the words a visitor reads once the page's scripts have run are looked up in the HTML the server sent (rendering.contentShareInHtml), which is all that search engines that do not run scripts, and most AI crawlers, ever read. Below half of them, on a page of 50 words or more, the page gets a Content requires JavaScript to render warning; the same goes for its internal links (internalLinksMissingWithoutJS). A page whose HTML the browser cannot hand back (over 10 MB) is left unverified (verified: false), with nothing reported
SchemaJSON-LD, microdata and RDFa detection with the types found
PerformanceTime to first byte, first contentful paint, speed index, total blocking time, cumulative layout shift, page load time
LinksInternal and external counts with unique totals and samples, split by navigation / footer / content, nofollow vs dofollow, redirects with their target, broken links verified by an actual request
ImagesAlt and title coverage, real file size in KB (inline data: images measured from their own payload), dimensions, format, oversized image detection, and the HTTP status of every image, so a picture that never loads is reported as such instead of being counted as a tiny file
AccessibilityARIA labels, lang attribute, skip links, basic colour contrast

Output

page record

{
"type": "page",
"pageUrl": "https://example.com/",
"title": "Example — Home",
"httpStatus": 200,
"audit": {
"overallScore": 89,
"categoryScores": {
"metaTags": 85, "headings": 100, "content": 70, "technical": 85,
"schema": 80, "performance": 100, "links": 100, "images": 100,
"accessibility": 95
},
"issues": {
"critical": [],
"warnings": ["Title tag length is 65 characters (optimal: 30-60)"],
"info": ["Sitemap not found", "4 redirects found"],
"structured": {
"critical": [],
"warnings": [
{
"id": "TITLE_TOO_LONG",
"message": "Title tag length is 65 characters (optimal: 30-60)",
"category": "meta",
"priority": 3,
"estimatedImpact": "medium",
"fixHint": "Adjust title length to 30-60 characters for better visibility in SERPs.",
"pagesAffected": ["https://example.com/"]
}
],
"info": []
}
},
"metaTags": {
"title": {
"present": true,
"value": "Example — Home",
"length": 65,
"lengthPixels": 423,
"optimal": false,
"unique": true,
"recommendation": "Title too long (65 characters, ~423px). Recommended: 30-60 characters (~195-390px)."
}
},
"headings": {
"h1": { "count": 1, "texts": ["Main heading"], "optimal": true },
"h2": { "count": 9, "texts": ["Section one", "Section two"] }
},
"content": {
"wordCount": 1808,
"paragraphCount": 108,
"sentenceCount": 123,
"averageWordsPerSentence": 14.7,
"contentArea": "main",
"readability": { "score": 42, "level": "Difficult", "language": "en", "formula": "Flesch", "recommendation": "Low readability (Difficult). Shorten sentences and prefer shorter words." },
"keywordDensity": { "topKeywords": [{ "keyword": "hotel", "density": 1.27, "count": 18 }] }
},
"performance": {
"timeToFirstByte": 468.1,
"firstContentfulPaint": 720,
"speedIndex": 1132.8,
"totalBlockingTime": 0,
"cumulativeLayoutShift": 0,
"coreWebVitals": { "cls": 0 }
},
"links": {
"internal": { "count": 13, "unique": 13, "inNavigation": 7, "inFooter": 10, "inContent": 4, "sample": ["https://example.com/pricing"] },
"external": { "count": 0, "nofollow": 0, "dofollow": 0, "sample": [] },
"broken": { "count": 0, "urls": [], "verified": [] },
"redirects": { "count": 4, "urls": [{ "url": "https://example.com/pricing", "statusCode": 301 }] }
},
"images": {
"total": 13, "withAlt": 13, "withoutAlt": 0, "withTitle": 0, "optimized": 13, "largeImages": 0,
"images": [
{ "url": "https://example.com/hero.webp", "alt": "Hotel reception", "width": 2000, "height": 1334,
"hasAlt": true, "sizeKB": 57.2, "format": "webp", "isLarge": false, "httpStatus": 200 },
{ "url": "https://example.com/team.jpg", "alt": "The team", "hasAlt": true,
"format": "jpeg", "isLarge": false, "httpStatus": 403 }
]
},
"accessibility": { "ariaLabels": 7, "langAttribute": true, "skipLinks": true, "colorContrast": { "passed": true, "issues": 0 } },
"crawlDepth": 0
}
}

Duplicate titles and descriptions. metaTags.title.unique and metaTags.description.unique are settled as each page is written, against the pages of its site written before it: false when one of them already uses the same text (whitespace aside), with that page in duplicateOf and a Duplicate title tag or Duplicate meta description warning. A page cannot know about the pages audited after it, so the first page of a group keeps true: the site-summary lists every page of each group (duplicateTitles, duplicateDescriptions) and counts them all in topIssues. As in SEO crawlers, only indexable pages are compared: the field is null on a page answered with an error, set to noindex or canonicalized to another URL, and when the tag is missing.

site-summary record

{
"type": "site-summary",
"site": "example.com",
"startUrls": ["https://www.example.com"],
"pagesCrawled": 3,
"averageScore": 85,
"categoryAverages": {
"metaTags": 92, "headings": 93, "content": 70, "technical": 78,
"schema": 57, "performance": 100, "links": 100, "images": 100, "accessibility": 88
},
"topIssues": [
{ "issue": "Duplicate title tag (same title as another page of the site)", "count": 2, "pages": ["https://example.com/contact", "https://example.com/pricing"] },
{ "issue": "Title tag length is 65 characters (optimal: 30-60)", "count": 1, "pages": ["https://example.com/"] },
{ "issue": "Skipped heading level: 1 → 3", "count": 1, "pages": ["https://example.com/pricing"] }
],
"distribution": { "excellent": 2, "good": 1, "needsImprovement": 0, "poor": 0 },
"crawlCoverage": {
"pagesCrawled": 3,
"indexable": 3,
"nonIndexable": { "noindex": 0, "canonicalized": 0, "errors4xx": 0, "errors5xx": 0, "redirects": 0 },
"maxDepth": 1
},
"duplicateTitles": [
{ "title": "Example — Plans, pricing and contact", "count": 2, "pages": ["https://example.com/pricing", "https://example.com/contact"] }
]
}

Scoring

Each category is scored independently against SEO best practice, then combined into overallScore:

ScoreReading
80-100Excellent
60-79Good
40-59Needs improvement
0-39Poor

A page loses points only for issues actually found on it, so the score moves when you fix things.

Cost

You are billed per page analyzed — see the Pricing section on this page for the current rate. maxPages is therefore your cost control: a 5-page preview costs a fraction of a 50-page deep audit, and a run never audits more than maxPages × the number of start URLs. Pages skipped by robots.txt or excludeUrlPatterns are not analyzed, and links to files (images, PDFs, documents, archives, media, feeds) are not followed at all. A link that fails to load, turns out to be a page already audited under another address, or leads to something other than an HTML page (an image or a feed served without a file extension) is not billed, and the next link of the site takes its place: each site gets its maxPages pages whenever it links to that many.

Limits

  • Static HTML analysis. Content injected client-side after load may not be seen.
  • Reading ease is an estimate. Syllables are counted from vowel groups, and a heading, a list item or a button label counts as a sentence of its own: a page made of short lines (a menu, a price list) scores as easy reading. Text that tabs, accordions or carousels hide with a stylesheet is counted, as search engines read it; text marked hidden in the markup (the hidden attribute, an inline display: none) is not, nor the labels of a form that is not displayed, nor a hidden mobile menu or cart drawer.
  • Performance metrics are lab measurements taken during the crawl, from one location and one connection. They indicate a trend, they are not field data from real users and will not match a Lighthouse or CrUX report exactly.
  • Some Core Web Vitals are only reported when the page exposes them. Expect cls, firstContentfulPaint, speedIndex and totalBlockingTime consistently; LCP is not always available.
  • Colour contrast is a basic check, not a full WCAG audit.
  • Image weight is the size the server declares (Content-Length), except for inline data: images, whose payload is measured directly. An image that answers 4xx or 5xx is reported with its httpStatus and no sizeKB: it never arrived, so it has no weight of its own.
  • Sites behind an anti-bot or a login are out of scope.
  • One crawl per site, limited to the site a start URL lands on unless includeSubdomains is on: a start URL that redirects (www.example.com to example.com, or to a country subdomain) is crawled on the site it redirects to. Start URLs that land on the same site share its maxPages budget, and each of them is audited even past it. Links are followed exactly as the site writes them, and a page reached through two URLs is audited and billed once. Only HTML pages are audited: the images, PDFs and other files a site links to are not.