Website Health Audit: Broken Links & SEO Checker avatar

Website Health Audit: Broken Links & SEO Checker

Pricing

from $5.00 / 1,000 page auditeds

Go to Apify Store
Website Health Audit: Broken Links & SEO Checker

Website Health Audit: Broken Links & SEO Checker

Crawl a website and find broken links, redirect chains, missing or duplicate titles and meta descriptions, missing H1, slow pages and sitemap gaps. A 0-100 score and a fix for every issue. $5 per 1,000 pages.

Pricing

from $5.00 / 1,000 page auditeds

Rating

0.0

(0)

Developer

Helvetic Labs

Helvetic Labs

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

17 days ago

Last modified

Features

Limited permissions

Share

Crawl a website and get a page-by-page report of what is wrong with it: broken links, redirect chains, slow pages, missing or duplicate titles and meta descriptions, missing H1 headings, images without alt text and pages that are missing from the sitemap. Every page gets a score from 0 to 100 and a short fix for every issue. For SEO specialists, agencies, web developers and site owners who need a repeatable website SEO audit and broken link checker.

What is Website Health Audit?

It is a site crawler, broken link checker and on-page SEO audit in one Actor. You give it the address of a site. It starts there, follows the links that stay on the same host, breadth first, and audits each page it reaches, up to a page limit you set. For every page it records the HTTP status, the redirect chain, the response time and the on-page facts that search engines care about. It also checks the links on the page, including links to other sites and to linked files such as PDFs, and compares the crawl with the site's sitemaps.

The result is one dataset item per page, with issues, fixes and a score, plus a site summary per site in the key-value store. Sort by score to see the worst pages first, or open the Broken links view for the list of dead links with the page they sit on.

The audit works on the HTML the server sends. It parses each page in memory and drops it right away: the output has lengths, counts, flags and URLs, never page text.

What does it check?

Per page:

  • Status and speed: HTTP status, number of redirects and the full redirect chain, response time in milliseconds, content type, size in bytes and whether the final URL uses HTTPS.
  • Title and meta description: present or missing, length in characters, too short or too long, and whether another crawled page has the same one (compared by a hash, the text is never returned).
  • Headings: number of <h1> elements (none, one or several).
  • Mobile and language basics: <meta name="viewport"> and the lang attribute of <html>.
  • Canonical: the canonical URL and whether it points to the page itself.
  • Robots directives: noindex and nofollow from the robots meta tag and the X-Robots-Tag header.
  • Images: how many <img> elements the page has and how many have no alt attribute.
  • Links: how many internal and external links the page has, and which of them are broken.
  • Sitemap: whether the page is listed in the site's sitemap.

Per site (SUMMARY record): pages audited and average score, pages by status class, broken internal and external links, how many external links and files were checked, issue counts by type, average response time, robots.txt status, whether the crawl was complete, and how the crawl compares with the sitemap.

Issues and score

Each page starts at 100. Each issue takes points off, and the score never goes below 0. The issue list on every page contains the code, the severity and a one-sentence fix.

Issue codeSeverityPointsMeaning
http_errorerror100The page answers with a 4xx or 5xx status.
unreachableerror100No answer: timeout, TLS error, connection error or too many redirects.
broken_linkserror10At least one link on the page is broken.
missing_titleerror15No <title>.
title_too_longwarning5More than 60 characters.
title_too_shortnotice2Fewer than 15 characters.
duplicate_titlewarning5Another crawled page has the same title.
missing_descriptionwarning8No meta description.
description_too_longnotice2More than 160 characters.
description_too_shortnotice2Fewer than 50 characters.
duplicate_descriptionnotice3Another crawled page has the same description.
missing_h1warning5No <h1>.
multiple_h1notice2More than one <h1>.
redirect_chainwarning5More than one redirect before the final page.
slowwarning5The answer took more than 2,000 ms.
not_httpswarning10The final URL is not HTTPS.
no_viewportwarning5No viewport meta tag.
missing_langnotice2No lang attribute on <html>.
missing_canonicalnotice2No canonical link.
images_missing_altnotice3At least one image has no alt attribute.
not_in_sitemapnotice2Indexable page that the sitemap does not list.
noindexnotice0The page asks search engines not to index it. Reported, not penalised.
page_too_largenotice2The HTML is larger than 2 MB.

A page with an error status or no answer scores 0 on its own. The score is a quick ranking aid, not a prediction of how a search engine ranks the page.

Use cases

  • Technical SEO audit: run a full site audit before a pitch or a project and hand over a sorted list of problems with fixes.
  • Broken link checking: find dead internal links and dead links to other sites, with the page they appear on, before visitors and search engines do.
  • Site migrations and relaunches: crawl the old and the new site and compare redirects, status codes, titles and canonicals; run again after the switch to catch leftovers.
  • Content and metadata clean-up: find missing, too long and duplicate titles and meta descriptions across hundreds of pages in one pass.
  • Sitemap hygiene: find indexable pages the sitemap forgot, and sitemap URLs that no page links to.
  • Monitoring: schedule a weekly audit and watch the average score and the number of broken links over time.
  • Agency reporting: one run per client site, exported to a spreadsheet or pushed to a dashboard through the API.
  • Pre-launch check: audit a staging site that is publicly reachable to catch missing titles, noindex tags and mixed redirects before go-live.

How to use Website Health Audit

  1. Open the Actor in Apify Console and click Try for free or Start.
  2. Enter the site's address in Sites (https://www.example.com/). You can add several sites; each is audited on its own.
  3. Set Max pages per site. The default is 100. You pay per page audited, so start small on an unknown site.
  4. Decide whether to check external links and whether to compare with the sitemap. Both are on by default.
  5. Set a maximum cost per run in the run options if you want a hard spending limit.
  6. Click Start. The status message shows the page count and the link checking progress.
  7. Open the Output tab. The Overview view lists pages with score, status, broken links and key SEO numbers; Broken links and Issues are two more views. Download everything as JSON, CSV, Excel, XML or HTML. The site summary is the SUMMARY record of the run's key-value store (linked in the output tab).
  8. To monitor a site, create a Schedule with the same input, for example weekly.

Input

FieldTypeDefaultWhat it does
startUrlsarray of stringsrequiredWhere the crawl starts. Up to 100 different sites per run; exact duplicates are removed. The crawl follows links on the same host only. Objects like {"url": "https://example.com"} also work.
maxPagesPerSiteinteger100Stop after this many pages for one site, from 1 to 10,000.
checkExternalLinksbooleantrueAlso check links to other sites, once each, with a HEAD request first. Included in the page price.
maxExternalLinksPerSiteinteger300Check at most this many different external links per site, from 0 to 2,000.
compareWithSitemapbooleantrueRead the site's sitemaps and flag pages that are missing from them.
timeoutSecsinteger20How long to wait for each response, from 5 to 120 seconds.

Tips:

  • Start with a small limit (25 to 100 pages) to see what the audit finds and how big the site is. Raise the limit for the real run.
  • The crawl stays on one host. www.example.com and example.com, or a blog. subdomain, count as different hosts: links to them are treated as external links. If the start URL redirects to another host (for example from example.com to www.example.com), the crawl follows it and audits that host.
  • Query strings count. Two URLs that differ in a query string are two pages. A site with endless filter or calendar URLs can use up your page limit, so keep the limit sensible.
  • Fragments are ignored and mailto:, tel: and javascript: links are not followed or checked.
  • Raise timeoutSecs for slow servers; lower it to fail fast.
  • Set maxExternalLinksPerSite to 0 or turn off checkExternalLinks if you only care about your own pages. The links to other sites are then not requested and show up as unchecked_links.
{
"startUrls": ["https://www.example.com/"],
"maxPagesPerSite": 500,
"checkExternalLinks": true,
"maxExternalLinksPerSite": 300,
"compareWithSitemap": true,
"timeoutSecs": 20
}

Output

One dataset item per audited page, in crawl order. Example (shortened where noted):

{
"site": "https://www.example.com/",
"url": "https://www.example.com/pricing",
"final_url": "https://www.example.com/pricing",
"final_url_audited_elsewhere": false,
"depth": 1,
"status": 200,
"error": "",
"redirects": 0,
"redirect_chain": [],
"response_ms": 412,
"content_type": "text/html",
"bytes": 48213,
"https": true,
"title_length": 71,
"description_length": null,
"duplicate_title": false,
"duplicate_description": false,
"h1_count": 1,
"lang": "en",
"viewport": true,
"images": 12,
"images_missing_alt": 3,
"canonical": "https://www.example.com/pricing",
"canonical_is_self": true,
"noindex": false,
"nofollow": false,
"internal_links": 34,
"external_links": 6,
"unchecked_links": 0,
"broken_link_count": 1,
"broken_links": [
{"url": "https://old-partner.example.net/logo", "status": 404, "error": "", "internal": false}
],
"in_sitemap": true,
"issues": [
{"code": "title_too_long", "severity": "warning", "fix": "Shorten the title to at most 60 characters; search engines cut it."},
{"code": "missing_description", "severity": "warning", "fix": "Add a meta description (50 to 160 characters)."},
{"code": "images_missing_alt", "severity": "notice", "fix": "Give images an alt attribute (empty for decoration)."},
{"code": "broken_links", "severity": "error", "fix": "Links on this page lead to missing pages or dead hosts: fix or remove them."}
],
"score": 74,
"checked_at": "2026-10-20T08:21:07Z"
}

The score is 100 minus 5 (title), 8 (description), 3 (alt text) and 10 (broken links).

Output fields

FieldTypeMeaning
sitestringThe site (origin) the page belongs to.
urlstringThe page URL as the crawler requested it. E-mail addresses inside URLs are replaced by [email].
final_urlstring or nullThe URL after redirects; null when there was no answer.
final_url_audited_elsewherebooleantrue when this URL redirects to a page that was audited under another URL. Such items are listed but free and have no on-page facts.
depthintegerClicks from the start page; the start page is 0.
statusinteger or nullHTTP status of the final response; null when there was no answer.
errorstringEmpty, or why there was no answer: timeout, connection_error, tls_error, protocol_error, http_error (any other HTTP-level failure), dns_error or too_many_redirects.
redirectsintegerNumber of redirects followed (at most 10).
redirect_chainarrayEach hop with its url and status.
response_msinteger or nullTime in milliseconds for the request, redirects included.
content_typestring or nullMedia type without parameters, for example text/html.
bytesinteger or nullSize of the response body that was read (at most 2 MB).
httpsbooleanThe final URL is HTTPS.
title_lengthinteger or nullCharacters in the <title>; null if missing or the page was not parsed.
description_lengthinteger or nullCharacters in the meta description; null if missing or not parsed.
duplicate_titlebooleanAnother crawled page has the same title.
duplicate_descriptionbooleanAnother crawled page has the same description.
h1_countinteger or nullNumber of <h1> elements; null if not parsed.
langstring or nullThe lang attribute of <html> (first 20 characters).
viewportboolean or nullWhether a viewport meta tag exists.
imagesinteger or nullNumber of <img> elements.
images_missing_altinteger or null<img> elements without an alt attribute.
canonicalstring or nullThe canonical URL, resolved to an absolute URL.
canonical_is_selfboolean or nullWhether the canonical equals the final URL; null without a canonical.
noindexbooleannoindex in the robots meta tag or X-Robots-Tag.
nofollowbooleannofollow in the robots meta tag or X-Robots-Tag. Links on such a page are counted but never requested.
internal_linksintegerDifferent links on the page to the same host.
external_linksintegerDifferent links to other hosts.
unchecked_linksintegerLinks whose status is unknown: beyond the external link limit, not crawled because of the page limit or the time limit, or on a nofollow page.
broken_link_countintegerNumber of broken links on the page.
broken_linksarrayUp to 50 broken links with url, status, error and internal.
in_sitemapboolean or nullWhether the page is in the sitemap; null when no sitemap URLs were found or the comparison is off.
issuesarrayEach issue with code, severity and fix.
scoreinteger100 minus the issue points, at least 0.
checked_atstringUTC time of the check.

Pages that are not HTML (a PDF reached through a link without a file extension, for example) or that did not answer with status 200 have null on-page facts, because there is no HTML to measure.

  • Links to your own site: any error status from 400 up, plus requests that failed (DNS error, connection error, TLS error, timeout, protocol error, too many redirects).
  • Links to other sites: only 404, 410 and 5xx statuses and failed requests. Other sites often answer a bot with 400 or a special code, and that is not a sign of a dead link.
  • Never broken: 401, 403 and 429 ("not for you", not "gone"), and links the target's robots.txt does not allow this Actor to request.

The SUMMARY record

The key-value store record SUMMARY has one entry per site, keyed by the site's address. Example:

{
"https://www.example.com/": {
"site": "https://www.example.com/",
"pages_audited": 100,
"redirects_to_audited_pages": 4,
"crawl_complete": false,
"stopped_by": "page_limit",
"average_score": 86,
"pages_by_status": {"2xx": 101, "4xx": 3},
"skipped": {"blocked_by_robots": 2},
"robots_txt": "parsed",
"redirects_off_site": 1,
"broken_internal_links": 3,
"broken_external_links": 5,
"external_links_checked": 212,
"external_links_unchecked": 0,
"files_checked": 18,
"files_unchecked": 0,
"average_response_ms": 388,
"issues": {"missing_description": 31, "missing_canonical": 30, "broken_links": 6},
"sitemap": {
"found_from": "robots.txt",
"urls": 140,
"crawled_in_sitemap": 97,
"crawled_not_in_sitemap": 3,
"in_sitemap_not_linked": null
},
"checked_at": "2026-10-20T08:24:51Z"
}
}
FieldMeaning
pages_auditedPages audited and charged.
redirects_to_audited_pagesURLs listed free because they redirect to a page audited under another URL.
crawl_completefalse when the crawl stopped early.
stopped_bypage_limit or time_limit when it stopped early, otherwise null.
average_scoreAverage page score over the audited pages; null without pages.
pages_by_statusItems by status class (2xx, 3xx, 4xx, 5xx, or error).
skippedPages that were not requested, by reason (for example blocked_by_robots).
robots_txtparsed, missing or unreachable.
redirects_off_siteOwn URLs that redirect to another host.
broken_internal_links, broken_external_linksDifferent broken link targets found.
external_links_checked, external_links_uncheckedExternal links checked, and left unchecked by the limit or the time.
files_checked, files_uncheckedLinked files (PDFs, images, archives, scripts and similar) checked with a HEAD request, up to 500 per site.
average_response_msAverage response time.
issuesNumber of pages per issue code.
sitemapWhere the sitemap was found, how many URLs it lists, how many crawled pages are in it, how many crawled indexable pages are not, and how many sitemap URLs the crawl did not reach (only when the crawl was complete).

A site that could not be audited has an entry like {"error": "invalid_url"} or {"skipped": "this site was audited already in this run"} instead.

How much does it cost?

This Actor uses pay-per-event pricing: you pay USD 0.005 per page audited (USD 5.00 per 1,000 pages), plus Apify's standard actor start event of USD 0.00005 per run. With pay-per-event pricing the platform usage of the run is included in these prices.

Pages auditedPage eventsActor startTotal per run
100USD 0.50USD 0.00005USD 0.50005
1,000USD 5.00USD 0.00005USD 5.00005
10,000USD 50.00USD 0.00005USD 50.00005
100,000USD 500.00USD 0.00005 per runUSD 500.00005 for one run, more if split into several

What is charged and what is free:

  • Charged: each page that was requested and audited, including pages that answer with an error status or not at all.
  • Included in the page price: the checks of links to other sites and of linked files, the comparison with the sitemap and the SUMMARY record.
  • Free: pages that robots.txt does not allow or that are not public, URLs that redirect to a page already audited (listed in the dataset with final_url_audited_elsewhere: true), and inputs that are invalid or not public sites.

Set a maximum cost per run and the Actor stops when the limit is reached; the crawl never audits more pages than the limit pays for. Max pages per site caps each site on top of that. Apify's free plan includes monthly platform credit, which you can use to try the Actor.

Tips and best practices

  • Work from the worst page up. Sort the dataset by score, or open the Issues view.
  • Fix in bulk. The SUMMARY.issues counts tell you which problem is most common, such as 31 pages without a meta description, and often one template change fixes all of them.
  • Watch unchecked_links. A high value means the page limit, the external link limit or the time limit cut the checking short.
  • Compare with the sitemap. in_sitemap_not_linked shows sitemap URLs that no crawled page links to; it needs a complete crawl, so raise maxPagesPerSite until crawl_complete is true.
  • Pair it with the other tools. Check long URL lists with the URL Status & Redirect Checker, and get the full sitemap list with the Sitemap URL Extractor.
  • Schedule it and compare average_score and broken_internal_links between runs.

Limitations

  • HTML as served. The audit reads the HTML the server returns and runs no JavaScript. On a single-page app that builds its content in the browser, titles, headings and links may not be visible to the audit.
  • robots.txt is obeyed. Pages it disallows are skipped, are not charged and are counted in skipped. If robots.txt cannot be read, nothing on that host is requested. A page with nofollow (meta tag or X-Robots-Tag) has its links counted but not requested.
  • Links only from <a> and <area>. Images, scripts and style sheets are not checked for being broken. Images are counted only for missing alt attributes. Files linked with <a> are checked with a HEAD request.
  • Page size. At most the first 2 MB of a page's HTML is read; larger pages get page_too_large.
  • Caps. Up to 100 sites per run, 10,000 pages per site, 2,000 external links per site and 500 linked files per site. If a site has more, the rest is reported as unchecked.
  • Polite pace. Pages are requested one after another, with at least half a second between two requests to the same host, and longer if robots.txt asks for a crawl delay (up to 30 seconds). A site of 1,000 pages therefore needs at least about eight minutes for the crawl alone.
  • Time limit. The default run timeout is four hours. The Actor stops the crawl in time to check the remaining links and save the results, and reports stopped_by: time_limit. Audit very large sites in several runs.
  • Public sites only. Hosts on private or non-public addresses are refused, and URLs with a user name or password are not requested. Pages behind a login are not audited.
  • Automated checks. Results can be incomplete and are no guarantee for rankings.

FAQ

Is it legal to use? The Actor requests public pages like a search engine bot would, respects robots.txt, identifies itself and keeps a polite pace. It does not store page text and does not return e-mail addresses. Audit only sites you own or may check, and see the fair use rules at the end of this page.

Does it respect robots.txt? Yes, for every request, including the links to other sites. The user agent is HelveticLabs-WebCheck, with a link to the provider's imprint.

Does it store personal data? No. The output has URLs, status codes, lengths, counts and flags. E-mail addresses inside URLs are replaced by [email], and mailto: links are ignored.

How fast is it? Pages are fetched one at a time per host with a pause of at least half a second, so the speed depends on the site and on your page limit. A small site is done within minutes; check the status message for progress.

Can it audit a site that needs JavaScript? It audits the HTML as served, without running JavaScript. For many sites that is what search engines see first, but content added in the browser is not seen.

Why was a page not charged? Free are pages blocked by robots.txt, URLs that redirect to a page that was audited under another URL, and invalid inputs. Everything that was requested and audited is charged, including 404 pages.

Why does title_length say null? The page has no <title>, or it was not an HTML page with status 200 and so was not parsed. Check status and content_type.

Why are external links reported as unchecked? Because of maxExternalLinksPerSite, because checkExternalLinks is off, because of the time limit, or because the page has nofollow.

Can I schedule it? Yes, with a schedule in Apify Console, daily or weekly. A webhook can start the next step when a run finishes.

Can I use it from Python, JavaScript or the API? Yes, see Integrations below.

Can AI agents use it? Yes, through Apify's MCP server. An agent can run an audit and read the worst pages and the summary.

What happens on errors and timeouts? A page that fails is audited with its error and the crawl goes on. A site that cannot be audited gets an error entry in SUMMARY. Before the run's timeout, the Actor stops crawling by itself and saves what it has; a site that is not reached in time is listed in SUMMARY as skipped. Each site's pages are pushed once its crawl is done, so give large sites enough run time.

Integrations

The actor ID is HelveticLabs/website-health-audit.

Python (pip install apify-client):

from decimal import Decimal
from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("HelveticLabs/website-health-audit").call(
run_input={
"startUrls": ["https://www.example.com/"],
"maxPagesPerSite": 200,
"checkExternalLinks": True,
},
max_total_charge_usd=Decimal("2"), # optional spending limit for this run
)
# apify-client 3.x; with older versions use run["defaultDatasetId"] and run["defaultKeyValueStoreId"]
pages = list(client.dataset(run.default_dataset_id).iterate_items())
summary = client.key_value_store(run.default_key_value_store_id).get_record("SUMMARY")
worst = sorted(pages, key=lambda p: p["score"])[:10]
print([(p["url"], p["score"]) for p in worst])

JavaScript (npm install apify-client):

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('HelveticLabs/website-health-audit').call(
{ startUrls: ['https://www.example.com/'], maxPagesPerSite: 200, checkExternalLinks: true },
{ maxTotalChargeUsd: 2 },
);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
const summary = await client.keyValueStore(run.defaultKeyValueStoreId).getRecord('SUMMARY');
console.log(items.filter((p) => p.broken_link_count > 0).length, summary?.value);

HTTP API: POST https://api.apify.com/v2/acts/HelveticLabs~website-health-audit/runs with the input as the JSON body; add maxTotalChargeUsd as a query parameter to cap the cost.

No-code and automation: connect the Actor to Make, Zapier or n8n, or add a webhook that starts the next step when a run finishes, for example a message to your team when broken_internal_links is above zero.

AI agents (MCP): Apify's MCP server lets AI agents and MCP clients run this Actor as a tool; pass maxTotalChargeUsd in the call options to cap the spend.

ActorWhat it does
URL Status & Redirect CheckerStatus code, redirect chain, final URL and response time for up to 100,000 URLs.
Sitemap URL Extractor & ValidatorEvery URL a site lists in its sitemaps, with validation.
Website Security Headers & TLS AuditA+ to F security grade: HTTPS, headers, SSL certificate, SPF and DMARC.
AI Crawler Access CheckerWhich AI bots a site's robots.txt allows, plus llms.txt.

Support and feedback

A page that was judged wrongly, a check you miss, or a site the audit cannot read? Open an issue in the Issues tab on this Actor's page, or write to contact@helveticlabs.org. Include the run ID and the site if you can.

Provider, privacy and fair use

Provider: HelveticLabs, sole proprietorship of Marvin Krieg, c/o SwissMailBox 170, 12 Rue Le Corbusier, 1208 Genève, Switzerland. E-mail: contact@helveticlabs.org. Imprint: https://www.helveticlabs.org/imprint

Privacy: This Actor runs on the Apify platform, in your Apify account. It processes only the URLs or domains you enter and the public technical data the checked servers return (status codes, headers, certificates, DNS records, sitemap URLs, links). It does not collect personal data: it never stores or returns page text (where a page's HTML is read, it is read in memory only, to find links and measure tags), and it leaves out e-mail addresses, cookie names and values and similar values. Your input and results stay in your Apify account and are stored by Apify under its own terms (https://apify.com/privacy-policy). The provider sees only the usage statistics Apify shows to Actor authors (such as the number of users and runs), not your input or results, unless you share a run for support. Questions about privacy: contact@helveticlabs.org.

Fair use: Check only sites you own or may check. Do not use the results to attack systems or people. The Actor respects robots.txt, identifies itself, and keeps a polite pace per site.

No warranty: The results are automated checks and can be incomplete. They are not a penetration test, legal advice or a compliance certificate.