Technical SEO audit of a website, up to 5,000 pages: broken links, redirects, duplicate titles and descriptions, sitemap and indexing problems. A health score and a fix for every issue. Several sites per run. Schedule it for website monitoring: each site audit shows what changed since the last run.
The Actor is now called Website SEO Audit, Broken Links & Change Monitor, and its
description names technical SEO, several sites per run and website monitoring.
The speed test no longer needs a key of your own. With this Actor's key it tests 5 pages a run
by default, at most 10, and only the start page on a free run. Your own free Google API key
still tests up to 50 pages.
A run of several sites tests each site's speed with this Actor's key, up to 10 pages a site.
30 September 2026 (0.2.41)
Sites of 10 to 50 pages now cost $0.50, or $0.38 to $0.45 on paid Apify plans.
Several sites in one run. An address in More URLs on another site than Website URL, which was
refused, is now checked as a site of its own: each site in a run of its own, one after
another, with its own report, change history and charge, by its own pages. Max pages is per
site, and the run's maximum cost covers all the sites together; each site's run is given a
maximum cost of at least $2.00, the least Apify starts a run with. The sites share the run's
timeout, at least 5 minutes each, and a run checks up to 25 sites. The run's Report lists
every site with a link to its own report, its Pages, Issues and Changes hold every site's
rows, and summary.json has sites. Aborting the run stops the site being checked, free: at
once when you abort gracefully, within about 3 minutes when you abort at once.
The report's change table says "this site → another site" for a canonical that moved to
another domain, and "indexable → not indexable", instead of "false → true". The Changes
view keeps the stored values.
Score over time shows the time next to the date when two runs were on the same day.
29 September 2026 (0.2.32)
The form starts with Max pages at 9: a free preview of your own site. A preview that stops at
that limit is not kept as the site's baseline, so the first full audit is not compared with
nine pages. The API's default is still 250.
Images: up to 1,000 are checked within five minutes. The check used to stop after as long as
the crawl took, which on a 20-page run of books.toscrape.com was 59 of its 288 images. Each
check asks for the start of the file over a kept connection, instead of a HEAD that opened a
new connection each time. Your site's images go at up to 8 a second, 2 at a
time, for all its hosts together, when Requests per minute is 120 or more; other sites stay
at 2 a second. A host that answers 429 or 503 is slowed down and left alone if it keeps
doing so (image-check-rate-limited), and one that stops answering is left too
(image-check-host-unresponsive). An image whose turn never came now says
image-check-out-of-time; image-check-timeout means only a request with no answer in 10
seconds. SVG files are not counted as over 100 KB. The Pages schema said 100 image files a
row where the code keeps 50.
Images on five large image CDNs that answer a range request (Wix, Squarespace,
Webflow, Cloudinary and Sanity) are checked at up to 8 a second too, each CDN on its own, so
a site built on one of them no longer waits at 2 a second for its images.
Each image's width and height in pixels, as a browser shows it, are on its Pages row (width,
height), read from the first 4 KB of the file that each check now asks for. An Image without
width and height row names the numbers to add, such as add width="125" height="155", for the
first such image whose size was read (missingDimensions marks each one).
Audit a list of URLs. More URLs takes more pages of the same site, one per line. With the
new Follow links switch off, the run checks exactly those URLs and Website URL, and follows
no link on them; links on them to the site's other pages are still checked for broken pages,
with one request each. With Follow links on, More URLs are more places for the crawl to start
from. An address on another site is refused before anything is fetched: several sites per run
are coming.
Every Pages, Issues and Changes row ends with a site column, the host of Website URL as typed,
and summary.json has followLinks and urlsListed.
Speed, from Google, with your own free Google API key. Google PageSpeed Insights tests 5 pages
a run by default and up to 50, as a phone (the start page, then one page of each kind), and the
Chrome UX Report adds real visitors' Core Web Vitals where Google has them. Four new checks:
Slow main content, Page moves while loading and Fails Core Web Vitals (warnings), Busy main
thread (a notice). The health score does not change, and speed is not compared between runs.
The pages tested have a speed field with a link to test again, summary.json has a speed
block, and the Report a Speed section. New inputs under Speed: Pages to test for speed (0 turns
it off and sends nothing to Google) and your own Google API key, which the test needs.
Eight accessibility checks, read from the HTML. Warnings: Link without text, Button without a
name, Form field without a label and Frame without a title. Notices: Zoom turned off, Tab
order forced, Label points at a repeated id and Empty heading. None is an error, so the
health score does not change. Each Pages row has an accessibility object with the counts,
up to three examples of each, and how many controls were left to the page's scripts.
Warnings now cover problems that stop some people using a page, not only ranking and how a
page looks in search results. The Report and the README say so.
Image without alt text: alt="" is right for a decorative image, but not for the only image
in a link, which leaves the link without text. Link without text reports that case.
Title too long is decided by width: wider than 600 pixels in 20px Arial, where it was over
60 characters, since search results cut titles by width. Both figures are estimates; Google
publishes neither. On 157 real pages the switch stopped flagging 4 titles of narrow letters
and flagged none that were not flagged before. Each Pages row has titleWidthPx and
metaDescriptionWidthPx. The first run after the update does not compare Title too long
with the last run, since the last run counted characters.
Each Pages row of an English page has readingEase, the Flesch reading ease of its
paragraphs outside menus, headers, footers and forms, from 0 to 100, and proseWordCount. A
form that wraps the whole page, as on ASP.NET and DNN sites, is read as the page. It needs 100
words of prose, and it is not an issue.
A new notice, Page seems to need JavaScript, for a page that runs scripts and has almost no
text in its HTML: under 20 words, an empty app container such as #root, or its text only
inside <noscript>. When the start page has it, the Report and the run's status say so. Each
Pages row has scriptCount, emptyAppContainer and noscriptWordCount.
Each Pages row has pageScore, 0 to 100, and the Pages table shows it: 50 or more when the
page has no error, under 50 when it has one, so the health score is the share of pages
scoring 50 or more. A page on a host with an invalid certificate scores under 50, as the
health score counts it.
The Report shares its examples out: 20 for each issue while a run finds 50 kinds of issue or
fewer, and fewer of each above that, so it stays under the 9 MB a record may hold.
Fixed: a page whose link or heading held markup nested about 3,000 elements deep stopped the
check of that page, which was then reported as a page that stopped loading. It is now checked
like any other page.
26 September 2026 (0.2.27)
Fixed: naming the other pages in duplicate rows (0.2.25) kept a copy of the whole group for
every page in it, so a 5,000-page site with the same title on every page could run out of
memory after the crawl and save nothing. Each group is now kept once: three 5,000-page groups
take 7 MB.
26 September 2026 (0.2.25)
Each Pages row lists the page's headings, h1 to h6, in order (the first 50, in headings),
and a new notice, Skipped heading level, names the first heading that is more than one
level below the one before it, such as an h4 right after an h2 (headingSkip).
Duplicate title, description and content rows name the other pages, up to three, instead of
only counting them.
26 September 2026 (0.2.23)
The Report says how much of a partly made check was made. A 20-page run of a site with 351
pages linked listed Broken link as passed; it now says "19 of the 351 pages linked within the
site were checked", and image checks give their counts the same way.
Under a score below 100, the Report names the one error whose fixing on every page would
raise the score most, and the score that would give. A score of 0 reads "All 20 pages have at
least one error."
The Report has a link at the top that opens it in a new tab, since the Console shows it in a
window that does not scroll. Its notes take one paragraph, its closing note makes sense in a
file sent to a client, it prints in light colours, and its muted text has more contrast.
Each Pages row has errorCount, warningCount, noticeCount and issueNames, and the
Pages table shows them in place of the worst severity and the issue count.
The Issues rows' howToFix is plain text, without the backticks that showed in spreadsheets.
Form help: Max pages says which maximum cost keeps more than 250 pages, and Website URL and
Include subdomains say that the whole domain is checked.
26 September 2026 (0.2.21)
Canonical checks. A canonical tag pointing at a page that answers with a 4xx or 5xx error,
and canonical tags on one page that name different URLs, are errors. A canonical pointing at
a redirect or at a noindex page is a warning. Each Pages row says what its canonical URL
answered (canonicalTarget) and lists every canonical URL on the page (canonicalUrls).
Sitemap checks, each a warning: a listed URL that redirects, answers with a 4xx or 5xx
error, is set to noindex or has a canonical pointing elsewhere, and an http URL in the
sitemap of an https site.
Each step of a redirect chain now has its status code, such as 301 or 302, as the crawl
received it. A redirect with 302, 303 or 307 is a new warning, Temporary redirect.
A new notice, Links to redirects, names the links on a page to URLs of the site that
redirect. The Pages row's redirectingLinks lists them with their status codes.
These checks use what the crawl fetched and make no extra requests. A canonical or sitemap URL
the crawl did not fetch is not checked, and the Report says so.
HTTPS checks. Errors: a password field on a page that loads over http (nonSecurePage), and
an https page whose HTML loads a script, style sheet, frame, image or media file over http
(mixedContent; a page that sends upgrade-insecure-requests is not counted). Warnings: a
page that loads over http on a site that uses https (httpPage), and an https page that
links to the site's http addresses (linksToHttp). Each page row has passwordField,
mixedContentCount, mixedContentUrls and httpLinkCount.
Certificate checks. After the crawl, one secure connection to each host that served https
pages, up to 20, reads its certificate. An expired certificate, or one that does not name
the host, is an error (certificateInvalid); one that expires in less than 14 days is a
warning (certificateExpiringSoon). Each is one row for the host, not one per page. Every
page on a host with an invalid certificate counts as a page with an error in the health
score, since visitors see a browser warning first. Other certificate problems, such as a missing
intermediate certificate that browsers often work around, are named in the Report as not
checked, never as errors. summary.json lists each host's certificate in certificates,
with its expiry date and days left.
Ten more checks on each page: a missing viewport tag and a meta refresh that redirects
(errors); a viewport without a width, a missing lang and internal links with nofollow
(warnings); more than one title or meta description tag, a page set to nofollow, a low
text-to-HTML ratio and a response over 2 seconds (notices).
Each Pages row also has the viewport, language, meta refresh, robots meta tag and
X-Robots-Tag values as written, how many title and description tags the page has, the HTML's
size and share of text, and the time to first byte. Each link says whether it is nofollow.
Times are measured from Apify's servers while the crawl is loading your site.
Each Pages row lists the structured data types in the page's HTML: jsonLdTypes (including
the items in @graph), microdataTypes and rdfaTypes. A JSON-LD block that is not valid
JSON is a new error, Invalid JSON-LD, with the block and the place the parser stopped, so
a page with one now counts against the health score. A page without structured data is not
reported: markup a script adds, such as through a tag manager, is not seen.
Each Pages row has the page's Open Graph and Twitter card tags: ogTitle, ogDescription,
ogImage, ogUrl, twitterCard, twitterTitle and twitterImage. A new notice, Missing
social title and image, names pages that have neither a title nor an image for a shared
link.
Images are checked. Each image file the pages use, from src, data-src, srcset,
data-srcset and <picture>, is checked once with the headers a browser sends for an image:
up to 1,000 a run, those on your own site and those on the most pages first, for no longer
than the crawl itself took (at least 30 seconds, at most five minutes), and at most 2
requests a second to any one host. Only the headers are read. Images on your site that robots.txt
disallows are not checked, and hidden or 1-pixel images, such as tracking pixels, are not
requested.
Four new checks: Broken internal image (error) and Broken external image (warning),
for an image that returns 404 or 410 or whose domain no longer exists; Image over 100 KB
(notice); and Image without width and height (notice). A 403, 429 or 999 is a server
refusing the check and is never counted as broken. A broken or large image is one row in the
Issues view, naming the pages that use it.
Each Pages row lists the images the page uses, with each one's status, size and type, and
counts its images without a width and height.
Check links to other sites now also covers images hosted on other sites. Images on your
own site are always checked.
Keeping each page's image files takes memory. A site of 5,000 pages that each use about a
hundred image files can stop a little short of 5,000 at the default 1 GB; the run log says so,
and 2 GB in the run options gives it more room.
This update adds 33 checks, so the first run after it can score lower with nothing changed on
your site. That run says so next to the score, with the score counting only the checks the last
run had, and summary.json lists the new checks in newChecks and gives that score in
healthScoreWithoutNewChecks. The new checks show no Since last run figure and add no rows
to Changes on that run; the run after compares them like the others.
26 September 2026 (0.2.18)
Max pages can be set from 1. A run of 1 to 9 pages is a free preview of your own site.
A URL pattern without *, [ or { now matches every URL that contains it, in any
case, so blog, /tag/ or ?page= does what you expect. Patterns with those characters
match as before. If you use a pattern without them, the first run after this update reports no
pages as new or removed, because what the crawl can reach has changed.
A URL pattern that matched no URL the crawl found is named in the run log and the Report.
An Include subdomains switch, on by default, which is how every run worked before. Off,
only the host in Website URL is checked, and links to the domain's other hosts are treated
as links to other sites. Each setting compares with the last run that used it, and the first
run with a setting reports no pages as new or removed.
Each Pages row lists the problems on that page, worded as in the Issues view: the issue,
how serious it is, the value that is wrong and how to fix it. Apify's usual API routes and
the run's Export button read only the Pages rows, so these now say what to fix.
The report counts problems by area, such as Links or Titles and descriptions, and lists up to
ten pages to fix first: the most errors, then the most warnings. summary.json has the same
two lists, issuesByCategory and pagesToFixFirst.
Each run's health score is kept with the site's history, and the report shows the scores of
earlier runs with the same settings. Earlier runs did not keep one, so the first run after this
update says the trend starts there. A run that stopped before checking every page, such as a
free preview, is compared only with a run that checked as many pages.
Printed from Chrome, or saved there as PDF, the report shows every issue open, with its fix
and examples. They used to print closed.
25 September 2026
Every output field now has a title and a description in the dataset schemas, and the run's
report and summary are described too, so the Output tab, the API and AI agents can say what
each field means.
The first report of a site says to run it again after you change something, or to add a
weekly schedule, to see what changed.
The form's help says when to change Max pages, Max link depth and Requests per minute.
A meta description of only spaces or line breaks now counts as missing. Titles and
descriptions are measured as search results show them, with each run of spaces and line
breaks read as one space.
A title or description whose spacing alone differs from the last run is not reported as
changed. The first run after this update shows no Since last run figure for the checks
the new measuring affects: title length, missing and long descriptions, and duplicate
titles and descriptions. The run after that compares them again.
The form's pattern examples, **/blog/** and **/tag/**, show their asterisks. They were
shown as a bold "/blog/" and "/tag/", patterns that match nothing.
The form refuses a Website URL that is an IP address, localhost, a host without a dot, or
an address with a username or password, with an example of an address it accepts. Such a
run used to start and then fail.
Right under the score, the report names the top three issues and how many pages each is
on, the number of changes since the last run, and how many pages have warnings. The
Console shows only the top of the report, so this is what it shows first. The counts by severity are labelled
as problems, since one page can have several.
The README gives a larger site's price as $5.00 for up to 1,000 pages and $15.00 for up to
5,000, says which maximum cost covers each where you set Max pages, tells Make, n8n and AI
agents where the Issues and Changes rows are, and how to export Issues from the Console.
0.2 — September 2026
First public release.
A readable Report is the first view of every run: health score, errors, warnings and
notices, what changed since the last run, and every issue with where it is and how to
fix it.
An Issues view with one row per problem, errors first, each with the exact value
that is wrong and how to fix it.
A Pages view with one row per page, and a Changes view with one row per change
since the last run on the same site.
28 checks, including broken links, redirect loops, duplicate titles and content,
hreflang return links, orphan pages and sitemap URLs blocked by robots.txt.
One price per audit, set by how many pages were checked. Runs under 10 pages are free,
and a run that cannot reach your site is free.
Corrected before release
A pre-publish audit went through the whole product. Nothing below ever reached a buyer; the
list is here so the record of what changed is complete.
What you are charged.
A run's maximum cost now limits how many pages it checks: if the cap is below the price
for Max pages, the run checks up to 1,000 or 250 pages (or 9, free) and says so. An audit
fee the cap does not cover is never sent or reported as charged.
Paid runs no longer warn that billing stopped.
The cost estimate no longer quotes a price for pages 1-9.
A restarted run that was already charged is never described as free or uncharged.
What the run tells you.
Every successful run ends with a one-line status: pages, health score, changes, early stop
and charge.
A run stopped by its time limit before any page says so, instead of blaming the server.
Stop warnings no longer give a page count that differs from the final one.
Memory advice no longer asks a 2 GB run for more memory.
The advice for a site that refuses the crawl no longer asks to allow the Actor.
A run with invalid input still writes its report and summary.
What gets crawled.
A crawl that starts at a hosted address, such as name.wordpress.com, stays on that one
address instead of spreading over the hosting platform.
A restarted run checks its full page limit again; Crawlee's saved request count no longer
cuts it short.
Resurrecting a run that already finished no longer writes every row twice; the run stops
and says where its results are.
A short run timeout no longer stops the crawl before the first page; the time kept for
later steps shrinks with the timeout, and under 4 minutes links to other sites are not
checked.
Links to other sites keep to 2 requests a second per host, redirects included.
What gets reported.
Duplicate title, content and meta description are no longer reported on pages set to
noindex or canonicalised away, or on their originals.
"Not indexable" reads the robots directives exactly, so max-image-preview:none is no
longer read as noindex.
"Page missing from sitemap" is only reported on hosts whose sitemap was read.
The orphan page check stays on when the only skipped links were add-to-cart and similar
addresses.
The Report says "some pages found were left out" when your patterns, the link depth limit,
the crawl-trap filter or robots.txt kept a page out, instead of claiming every page found
was checked. The note about skipped pages names the crawl-trap filter, and now appears on
sites with no sitemap too.
The Report says when robots.txt disallowed pages, and when it could not be read at all.
The Report's change table shows the changes that matter first, so a page set to noindex is
not pushed out by URLs that moved, and every kind of change gets at least one row.
A very long address is shown as text, cut in the middle so the end still tells two of them
apart, and one page cannot make the Report too large to save.
The share bar is left empty for sitemap URLs blocked by robots.txt, which are not pages
that were checked.
All four issue-table headings are set in the same font.
Change history.
Runs with different URL patterns, link depth or sitemap setting on the same site each
compare against their own last run.
A page no longer found is reported once, even when the crawl boundary moved.
A site typed with and without www. no longer reports every page as removed and re-added:
the two spellings share one history store and each compares against its own last crawl.
Change rows are written before the snapshot is saved.
The run log counts "today" and "yesterday" on UTC dates.
Outputs and the form.
summary.json lists the run's dataset ids (datasets) and, when it applies, the
maximum-cost limit (maxCostCap).
The Pages output link no longer promises fields its view leaves out, and the Issues and
Changes links say they need an API token outside the Console.
The input form opens with what a run does and what it costs, and the crawl settings are in
a section of their own.
The Report's monospace font is IBM's own subset of IBM Plex Mono, shipped with IBM's
licence text. The report looks the same.
Corrected in review round 2, before release
Seven readers went over the pre-publish audit's own changes, and independent skeptics tried
to refute every serious finding. Nothing below reached a buyer either.
What you are charged.
Pages your site refuses are not charged for. A page that answers 401, 403 or 429 was not
read, so a run behind bot protection is free unless it still read 10 pages or more.
A free run gives one page count that matches the other: "Only 8 of the 12 pages crawled
could be read, so this run is free."
A price band the run's own pricing does not list is no longer reported as charged.
A run whose charge receipt was saved but not listed is still charged, and a run killed
while its report was being written charges normally when it is started again.
What gets reported.
Duplicate title, meta description and content are listed as not checked, not as passed,
when there were not two indexable pages to compare — and a later run no longer says they
were "fixed".
The orphan page check is skipped when your robots.txt kept pages out of the crawl, because
a page this crawler may not read can be one search engines reach.
A noindex written as noindex;nofollow, or after a directive the Actor does not
recognise, is now read as not indexable.
A run that could not reach its start URL names what actually happened: a 404 homepage is no
longer reported as a redirect loop.
The Report's first note about a maximum-cost limit says the same three things the run log
does: how many pages it will check, that the run is free when it is, and what to raise the
maximum cost to.
The change table keeps a row for every kind of change, so a site-wide title rewrite no
longer hides the pages that appeared or disappeared.
Change history and scope.
A start address written with a trailing dot (https://example.com./) is crawled instead of
putting the whole site out of scope.
An unreadable snapshot for one set of settings falls back to the site's last crawl, so that
run's changes are still reported.
A history store that refuses to delete an old record no longer reports the snapshot it just
saved as lost.
Runs, time and memory.
A run's per-request timeouts come out of the time it actually has, so a slow page is less
likely to end a short run before its results are saved — and under about 40 seconds the run
log says the risk is there.
A run started by an older client reads its real memory limit, so the memory guard stops it
at the right point.
Resurrecting a finished run says what that audit found before saying it already finished.
The run's one-line status says when some pages found were left out.
The form and the documentation.
The Website URL help text says an IP address is not accepted, and the FAQ says which
addresses are refused before the crawl starts.
The FAQ names every browser whose headers the crawl can send, not only two of them.
The Changes view is described as it is ordered: the URLs no longer found first, then
everything else in URL order.