On-Page SEO Audit Tool: SEO Checker & Crawler
Pricing
from $7.00 / 1,000 pages
On-Page SEO Audit Tool: SEO Checker & Crawler
SEO audit tool: crawls a website, or checks a list of pages, and audits the on-page SEO of each page: title, meta description, H1 and headings, canonical, noindex, image alt text, structured data, status codes and redirects. A 0-100 SEO score per page and a site report of duplicate titles.
Pricing
from $7.00 / 1,000 pages
Rating
0.0
(0)
Developer
Sanira Botheju
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
a day ago
Last modified
Categories
Share
What does On-Page SEO Audit do?
On-Page SEO Audit checks the SEO basics of every page of a website and gives each page a score from 0 to 100. You get one result per page in a table you can download as JSON, CSV or Excel or read through the API.
Enter a website. The tool starts at its address, follows the links to the other pages of the same site, and for each page reports the title, meta description, headings, canonical address, noindex, images without alt text, structured data, links and more. Every problem it finds is listed on the page with a code and a sentence that says what is wrong.
- Crawls the site for you: follows links on the same site, up to the number of pages you set. Or turn crawling off and give it a list of pages.
- One result per page: about 50 facts, a list of issues and a score. Sort by score to see the worst pages first.
- A report for the whole site: duplicate titles, duplicate descriptions, pages with noindex, pages that returned errors, redirects, and pages the site's
robots.txtdoes not allow. - Follows the rules of the site: reads
robots.txtand does not fetch what it forbids, waits for aCrawl-delay, and sends no more than three requests at a time to one host. - Reads the HTML the server sends. It does not run JavaScript, so a page that builds its content in the browser shows little text here. See the FAQ.
What data do you get?
| Field | Example | Meaning |
|---|---|---|
url, finalUrl | https://crawlee.dev/js | The address checked, and where it ended up after redirects |
httpStatus | 200 | HTTP status of the final address |
redirectCount, redirectChain | 1 | How many redirects were followed, and the addresses that redirected |
responseTimeMs, sizeBytes | 51, 62516 | Time to download the page (from the Apify server) and the size of its HTML |
title, titleLength | Crawlee for JavaScript | The page title and its length in characters |
metaDescription, metaDescriptionLength | Crawlee helps you... | The meta description and its length |
h1Count, h1Texts, h2Count to h6Count | 1 | Headings that have text |
canonicalUrl, canonicalStatus | self | missing, self, elsewhere or invalid |
metaRobots, xRobotsTag, noindex, nofollow | false | What the robots meta tag and the X-Robots-Tag header say |
indexable | true | Loaded, no noindex, canonical not pointing to another page |
lang, hreflangCount, hasViewport, charset | en, 2, true, utf-8 | Language and device settings |
hasOgTitle, hasOgDescription, hasOgImage, hasTwitterCard | true | Social sharing tags |
structuredDataTypes | ["Organization"] | The @type values of the JSON-LD on the page |
wordCount | 554 | Words of visible text in the main content |
imageCount, imagesMissingAlt | 30, 0 | Images, and images with no alt attribute |
internalLinkCount, externalLinkCount, nofollowLinkCount | 28, 11, 0 | Links by kind |
mixedContentCount | 0 | Files loaded over http on an https page |
score, errorCount, warningCount, noticeCount | 100, 0, 0, 0 | The score and the number of issues of each severity |
issues | [{ "code": "TITLE_TOO_LONG", ... }] | Each issue: code, severity (error, warning or notice) and message |
depth, foundOn | 1, https://crawlee.dev/ | How many clicks from your start address, and the page that linked here |
error | null | Empty when the page was fetched, otherwise a code and a message |
How to audit a website
- Open the tool and enter the website under Websites or pages, for example
https://crawlee.dev. - Set Maximum pages per site. The default is 100. The run stops there, so this is also the most you can be charged for that site.
- Press Start.
- Open the Output tab. The Pages view shows the score and the main facts of each page, the Issues view shows what was found. The site report is in the key-value store of the run, under
SITE_ISSUES.
To check only certain pages, turn off Crawl the site and list the pages. To run an audit every week, use the Schedule option in Apify Console.
How much does it cost to audit a website?
You pay for results, not for time: $10 per 1,000 pages checked ($0.01 a page). Apify subscribers on the Silver plan pay $8.50 and on the Gold plan $7.00 per 1,000 pages. Each run also has a start fee of $0.00005.
| Pages checked | Cost |
|---|---|
| 10 | $0.10 |
| 100 | $1.00 |
| 1,000 | $10.00 |
| 10,000 | $100.00 |
Apify's free plan includes $5 of credit each month, which covers about 500 pages.
Every result in the table is charged, also a page that answered 404 or could not be fetched, because finding those is part of the audit. So is the result for an entry that is not a web address, and for an address you entered that robots.txt forbids. A page that is reached under two addresses (a redirect and its target) is one result. Links to files that are not web pages, and links the site's robots.txt forbids, are not saved and cost nothing. Set Maximum pages per site, or a maximum cost for the run in Apify Console, and the tool stops there and keeps what it has.
How the score works
Each page starts at 100 points. For every issue found, points come off: 20 for an error, 8 for a warning, 3 for a notice. The score does not go below 0. A page that could not be loaded (a fetch failure, or a final HTTP status that is not 2xx) scores 0. No score (null) is given where no page was read: an entry that is not a web address, a file that is not HTML, or an address robots.txt forbids.
| Issue | Severity | When |
|---|---|---|
HTTP_ERROR | error | The final HTTP status is not 2xx |
FETCH_FAILED | error | No answer: DNS error, refused connection, timeout, certificate error, more than 10 redirects |
TITLE_MISSING | error | No title |
TITLE_TOO_LONG / TITLE_TOO_SHORT | warning / notice | More than 60 / fewer than 30 characters |
DESCRIPTION_MISSING | warning | No meta description |
DESCRIPTION_TOO_LONG / DESCRIPTION_TOO_SHORT | warning / notice | More than 160 / fewer than 70 characters |
H1_MISSING / H1_MULTIPLE | warning | No H1 with text / more than one |
NOINDEX | warning | Meta robots or X-Robots-Tag says noindex |
NOFOLLOW | notice | Meta robots or X-Robots-Tag says nofollow |
CANONICAL_MISSING | notice | No canonical address |
CANONICAL_ELSEWHERE / CANONICAL_INVALID | warning | The canonical names another page / cannot be used as an address |
LANG_MISSING | notice | The html tag has no lang |
NO_VIEWPORT | warning | No viewport meta tag |
IMAGES_MISSING_ALT | warning | An img has no alt attribute (an empty alt is fine) |
THIN_CONTENT | warning | Fewer than 200 words in the main content |
MIXED_CONTENT | warning | An https page loads images, scripts, styles, media or frames over http |
JSONLD_INVALID | warning | A JSON-LD block is not valid JSON |
OG_MISSING / OG_INCOMPLETE | notice | No Open Graph tags at all / some of title, description and image missing |
REDIRECTED | notice | The address redirects |
REDIRECT_CHAIN | warning | Two or more redirects in a row |
REDIRECTED_OFFSITE | warning | The address redirects to a different site (only that page is checked) |
SLOW_RESPONSE | notice | The download took more than 3,000 ms |
PAGE_TRUNCATED | notice | The page is over 3 MB; only the start was checked |
NOT_HTML | warning | An address you entered returns something that is not an HTML page |
ROBOTS_BLOCKED / ROBOTS_UNREACHABLE | warning | robots.txt forbids the page / could not be read, so the page was not fetched |
The limits (60, 160, 200 words and so on) are common rules of thumb, not rules of any search engine. Use the score to sort pages, not as a verdict.
Input
The only required field is Websites or pages.
{"startUrls": [{ "url": "https://crawlee.dev" }],"crawlSite": true,"maxPagesPerSite": 100,"includePatterns": ["/docs/"],"excludePatterns": ["*/api/*"],"requestTimeoutSecs": 30}
| Field | What it does |
|---|---|
startUrls | The websites to crawl, or the pages to check when crawlSite is off. Several sites in one run are fine. |
crawlSite | On (default): follow links to other pages of the same site. Off: check only the addresses you listed. |
maxPagesPerSite | The most pages checked on each site (default 100, up to 100,000). Your start addresses count. |
includePatterns | Check a page found by the crawl only if its address matches one of these. |
excludePatterns | Skip a page if its address matches one of these. |
requestTimeoutSecs | How long to wait for one page (default 30). |
userAgent | The name sent with each request. Leave empty to use the tool's own name. |
Patterns are plain text that may appear anywhere in the address, with * for any characters. For a regular expression, wrap it in slashes, for example /\/blog\/\d{4}\//. Patterns filter the pages the crawl finds. The addresses you enter yourself are always checked.
What counts as the same site: the same host, with or without a leading www., over http or https. Links to other hosts, such as a blog. subdomain, are counted as external links and not followed. The crawl does not follow links marked rel="nofollow", or the links on a page whose robots meta tag or header says nofollow, and it skips addresses that end in a file type such as .pdf, .jpg or .zip.
Output example
One page of crawlee.dev, checked with Crawl the site off:
{"url": "https://crawlee.dev/blog","finalUrl": "https://crawlee.dev/blog","site": "https://crawlee.dev","depth": 0,"foundOn": null,"httpStatus": 200,"redirectCount": 0,"redirectChain": [],"responseTimeMs": 957,"sizeBytes": 66715,"contentType": "text/html","title": "Crawlee Blog - learn how to build better scrapers | Crawlee for JavaScript · Build reliable crawlers. Fast.","titleLength": 107,"metaDescription": "Guides and tutorials on using Crawlee, the most reliable open-source web scraping and browser automation library for JavaScript and Node.js developers.","metaDescriptionLength": 151,"h1Count": 0,"h1Texts": [],"h2Count": 12,"h3Count": 3,"h4Count": 0,"h5Count": 0,"h6Count": 0,"canonicalUrl": "https://crawlee.dev/blog","canonicalStatus": "self","metaRobots": null,"xRobotsTag": null,"noindex": false,"nofollow": false,"indexable": true,"lang": "en","hreflangCount": 2,"hasViewport": true,"charset": "utf-8","hasOgTitle": true,"hasOgDescription": true,"hasOgImage": true,"hasTwitterCard": true,"structuredDataTypes": ["Blog"],"structuredDataErrors": 0,"wordCount": 1340,"imageCount": 28,"imagesMissingAlt": 0,"internalLinkCount": 88,"externalLinkCount": 49,"nofollowLinkCount": 0,"mixedContentCount": 0,"score": 84,"errorCount": 0,"warningCount": 2,"noticeCount": 0,"issues": [{"code": "TITLE_TOO_LONG","severity": "warning","message": "The title is 107 characters long. Search results usually cut titles after about 60."},{"code": "H1_MISSING","severity": "warning","message": "The page has no H1 heading."}],"error": null}
The site report
Findings that need all pages are saved in the key-value store of the run under SITE_ISSUES, as JSON. For each site it holds:
duplicateTitlesandduplicateDescriptions: groups of pages with the same text (ignoring case), with the count and up to 25 addressesnoindexPages: the pages that say noindex, and whether the meta tag or the header says iterrorPages: pages that answered with an error status or could not be fetched, and the page that linked to themredirects: addresses that redirect, with the address they lead toblockedByRobots: addresses the site'srobots.txtdoes not allow, which the tool did not fetchlowestScoringPages,issueCounts,averageScoreandcountswith the true totals
Each list holds at most 1,000 entries. The SUMMARY record of the run has the average score, the number of pages and how often each issue was found.
What can you use it for?
- SEO audits for clients or your own site: a first pass over every page in minutes, sorted by score.
- Before and after a redesign or migration: run it on the old and the new site and compare titles, canonicals, noindex and redirects.
- Finding the pages that need work: missing titles, duplicate descriptions, thin pages, images without alt text.
- Regular checks: put the tool on a schedule and watch for a noindex that slipped into production or a page that started to return errors.
- Feeding a report or an AI pipeline: the results are flat JSON with stable field names.
Use it from the API
curl -X POST "https://api.apify.com/v2/acts/cyber1nj3ct0r~on-page-seo-audit/run-sync-get-dataset-items?token=YOUR_TOKEN" \-H "Content-Type: application/json" \-d '{ "startUrls": [{ "url": "https://crawlee.dev" }], "maxPagesPerSite": 50 }'
To read the site report, start the run with the normal run endpoint, then fetch https://api.apify.com/v2/key-value-stores/<defaultKeyValueStoreId>/records/SITE_ISSUES with the store id from the run. The tool also works as a tool for AI agents through the Apify MCP server.
FAQ
Does it run JavaScript?
No. It reads the HTML the server sends, as a search engine's first fetch does. A page that builds its title, headings or text in the browser with JavaScript shows little or nothing here, and a site made only of such pages will give you a thin result. Links that appear only after JavaScript runs are not found either, so use the list mode for those pages.
How fast is it, and how many pages can it check?
About two pages a second on one site, because the tool spaces out its requests (three at a time at most, 300 ms between starts). A site of 1,000 pages takes five to ten minutes, more if the site answers slowly. Several sites in one run are checked at the same time. If the site's robots.txt asks for a Crawl-delay, the tool waits for it, up to 30 seconds between requests. The run has a time limit of one hour by default; when it is reached, the tool stops and keeps what it has.
Why do I not see duplicate titles in the page results?
Duplicates can only be found once all pages are known, so they are in the site report (SITE_ISSUES), not in the single results.
A page appears once although I entered two addresses for it
A page that is reached under two addresses, for example /old that redirects to /new and /new itself, is checked and charged once. The redirect is listed in the redirects of the site report.
A site blocks the tool. What can I do?
The tool identifies itself honestly in its User-Agent and does not try to get round blocks, so it will not pass a firewall, a captcha or a login. If it is your own site, allow the User-Agent in your firewall, or set your own under More options. If the site's robots.txt forbids the tool, those pages are not fetched; they are listed under blockedByRobots. When ten pages in a row fail or are refused (HTTP 403, 429, a server error, or no answer), the tool gives that site up, keeps the results so far and says why under stopped in the summary and the site report. You are charged only for the results that were saved.
Is it allowed?
The tool fetches public pages the way a search engine's crawler does, obeys robots.txt, limits its speed and never logs in. You are responsible for how you use the results, and for having the right to audit a site at the speed you choose. Use it on your own sites and on sites you have permission to test.
Something does not work
Open an issue on the Issues tab with the website you tried and what you expected. Every report is read and answered.