SEO Audit & Broken Link Checker avatar

SEO Audit & Broken Link Checker

Pricing

$4.00 / 1,000 page auditeds

Go to Apify Store
SEO Audit & Broken Link Checker

SEO Audit & Broken Link Checker

HTTP-only technical SEO audit and broken link checker: titles, meta, headings, canonicals, robots, hreflang, images, structured data, duplicates, and XML sitemaps. JSON/Markdown reports for CI and AI agents. No JS rendering.

Pricing

$4.00 / 1,000 page auditeds

Rating

0.0

(0)

Developer

Lintlab

Lintlab

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

2 hours ago

Last modified

Share

Technical SEO Site Audit

Crawl public pages or XML sitemaps and get deterministic, actionable technical SEO findings for every page, plus concise JSON and Markdown site summaries.

What it checks

  • Titles, meta descriptions, headings, canonicals, robots directives, hreflang, images, and structured data
  • Duplicate titles and descriptions across the audited site
  • Internal links with cached HEAD checks and a GET fallback
  • Ordinary, gzip-compressed, and nested sitemap indexes
  • $0.004 per successfully fetched and saved page

This Actor is HTTP-only and does not render JavaScript.

Quick start

{"startUrls":[{"url":"https://example.com"}],"discoverSitemap":true,"maxPages":100,"checkLinks":true}

Use with AI agents / MCP

Call lintlab/seo-site-qa through the Apify API or Apify MCP server. Use the per-page findings for precise fixes and SUMMARY or SUMMARY.md for a site-level plan.

Overview

SEO Site QA is an HTTP-only technical SEO audit for sites you own or are authorized to audit. It accepts pages, an XML sitemap, or a sitemap index; checks each allowed page; and writes compact, deterministic JSON plus a site summary. It is intended for developers, agencies, CI workflows, and AI agents that need findings they can act on without running a browser.

What it does

  • Reads ordinary and gzip-compressed sitemaps, including nested sitemap indexes.
  • Optionally discovers a sitemap from robots.txt and then /sitemap.xml when given only a homepage.
  • Uses Crawlee's CheerioCrawler with guarded HTTP requests. It does not launch a browser.
  • Audits up to 5,000 pages and can optionally discover more pages from internal links.
  • Checks internal links with cached HEAD requests and a GET fallback, within a configurable cap.
  • Produces one dataset item per attempted page, SUMMARY JSON, and SUMMARY.md.
  • Charges only after an allowed, successfully fetched page has been saved to the dataset.

Input

{
"startUrls": [{ "url": "https://crawlee.dev" }],
"discoverSitemap": true,
"maxPages": 100,
"sameHostOnly": true,
"followLinks": false,
"checkLinks": true,
"maxLinkChecks": 500,
"timeoutSecs": 20,
"concurrency": 5
}

You may use sitemapUrl instead of, or together with, startUrls. startUrls also accepts plain URL strings through the programmatic input path. Global concurrency defaults to 5; requests to one origin are limited to 2 concurrently.

Page output

Each dataset item describes one attempted page. Blocked and failed pages include an error and are not charged.

{
"url": "https://example.com/about",
"finalUrl": "https://example.com/about",
"status": 200,
"redirectChain": [],
"title": "About Example Company",
"metaDescription": "How our team builds useful products.",
"h1": ["About us"],
"canonical": "https://example.com/about",
"indexable": true,
"wordCount": 438,
"jsonLdTypes": ["AboutPage"],
"findings": [
{
"checkId": "opengraph.image_missing",
"severity": "notice",
"message": "Open Graph image is missing.",
"fix": "Add a og:image meta tag for richer social previews."
}
],
"counts": { "error": 0, "warning": 0, "notice": 1 },
"checkedAt": "2026-01-01T00:00:00.000Z"
}

redirectChain contains {url, status, location} objects. A finding always contains checkId, severity, message, and fix; value is included when useful.

Checks

Check IDs are stable API values. New checks may be added in later versions.

Check IDSeverityCondition
title.missingerrorNo <title> element
title.emptyerrorTitle element has no text
title.too_longwarningTitle is over 60 characters
title.too_shortwarningTitle is under 10 characters
title.duplicatewarningSame non-empty title appears on multiple audited pages
description.missingwarningMeta description is absent or empty
description.too_longwarningMeta description is over 160 characters
description.duplicatewarningSame non-empty description appears on multiple audited pages
h1.missingerrorNo H1 heading
h1.multiplewarningMore than one H1
headings.level_skipwarningHeading order jumps by more than one level
canonical.missingwarningCanonical link is absent or empty
canonical.relativewarningCanonical is not written as an absolute URL
canonical.elsewherewarningCanonical resolves to a different URL
canonical.malformederrorCanonical cannot be parsed as a URL
canonical.non_200errorCanonical target does not return HTTP 200 or cannot be fetched
robots.meta_noindexwarningMeta robots contains noindex
robots.meta_nofollowwarningMeta robots contains nofollow
robots.x_noindexwarningX-Robots-Tag contains noindex
robots.x_nofollowwarningX-Robots-Tag contains nofollow
robots.blocked_sitemaperrorURL is in a sitemap but disallowed by robots.txt
robots.disallowederrorNon-sitemap start/discovered URL is disallowed
http.non_200errorFinal page response is outside 2xx
sitemap.redirectedwarningA URL taken from the sitemap redirects
html.lang_missingwarning<html> has no lang
html.lang_malformedwarninglang is not a valid BCP 47 tag
viewport.missingwarningViewport meta tag is absent
images.alt_missingwarningOne or more images lack an alt attribute; value has count and first five sources
opengraph.title_missingnoticeog:title is absent or empty
opengraph.description_missingnoticeog:description is absent or empty
opengraph.image_missingnoticeog:image is absent or empty
jsonld.invaliderrorA JSON-LD block does not parse as JSON
jsonld.typesnoticeValid JSON-LD types were found; types are reported in value and jsonLdTypes
resources.mixed_contenterrorAn HTTPS page references an http:// resource
html.too_largewarningUncompressed HTML is over 1.5 MB
hreflang.malformedwarningHreflang language or absolute HTTP(S) target is malformed
links.broken_internalerrorInternal link returns 4xx/5xx or a network error
links.redirect_chainwarningInternal link has more than one redirect hop
fetch.failederrorPage fetch fails before an HTTP response is available

An empty JSON-LD set is not an error. If valid JSON-LD exists, its @type values are reported. Link findings are attached to each source page; the summary also lists broken source-to-target relationships.

Summary and score

The default key-value store receives:

  • SUMMARY: JSON with pages audited/attempted, aggregate counts, top issues, duplicate title and description groups, broken links, sitemap errors, and the score.
  • SUMMARY.md: the same core results in a readable Markdown report.

The score is:

100 - min(100, (errors × 5 + warnings × 2 + notices × 0.25) / successful pages)

It is clamped to 0–100 and rounded to one decimal. The formula is deliberately simple and comparative; a business-critical issue can matter more than its numeric weight.

Pricing

The page-audited event costs $0.004 per successfully audited page, or $4 per 1,000 pages. Internal link checks are free and bounded by maxLinkChecks. Disallowed pages, network failures, and non-2xx final page responses are written for diagnosis but are not charged. The Actor stops writing further page results when the platform reports that the event charge limit has been reached.

Limits and behavior

  • Maximum 5,000 scheduled pages per run and 10,000 unique internal link checks.
  • Maximum 10 redirects per HTTP operation.
  • HTML responses are capped at 10 MB; sitemap responses at 20 MB.
  • Sitemap indexes are capped at 100 fetched sitemap documents per run.
  • Link checks use HEAD, falling back to GET for HTTP 405 or 501. Canonical targets are checked separately and cached.
  • sameHostOnly compares hostnames. URL fragments are removed during normalization.
  • The crawler evaluates server-delivered HTML only. It does not execute JavaScript, measure rendered layout, or run Core Web Vitals.
  • Hreflang validation checks language syntax and absolute HTTP(S) targets; it does not verify reciprocal annotations.
  • Duplicate matching is case-insensitive after trimming, not semantic.
  • robots.txt rules support user-agent groups, Allow/Disallow, *, and terminal $; unusual nonstandard directives are ignored.

Safety and compliance

Use this Actor only for sites you own or have permission to audit.

  • User agent: lintlab-seo-qa/1.0 (+https://github.com/lintlab); robots product token: lintlab-seo-qa.
  • robots.txt is cached per origin. Fetch/network errors and 5xx responses fail closed (all URLs on that origin are disallowed); 4xx responses allow crawling under RFC 9309 behavior.
  • Every page, sitemap, canonical, link check, and redirect hop is restricted to HTTP(S), DNS-resolved before connecting, and blocked if any result is private, loopback, link-local, CGNAT, multicast, reserved, unspecified, or IPv6 ULA/IPv4-mapped private space.
  • Redirects are followed manually, at most 10 hops, so every destination receives the same SSRF validation.
  • No proxy, login/cookie input, CAPTCHA handling, browser, or stealth behavior is used.
  • The output does not extract emails, phone numbers, or personal names.

Local development

Requires Node.js 20 or newer.

npm ci
npm test
APIFY_LOCAL_STORAGE_DIR=./storage CRAWLEE_STORAGE_DIR=./storage node src/main.js

Before the final command, write the desired Actor input to storage/key_value_stores/default/INPUT.json (or use apify run --purge with the Apify CLI).


Built by lintlab — small, reliable data tools. AI-assisted (Claude/Codex), reviewed before release. Support: lintlab.dev@gmail.com · https://github.com/lintlab