What it does
Spot-checking a few pages misses the problems that only show up across a site: duplicate titles, pages nothing links to, internal links that hit 404s or redirect chains. The SEO Audit Crawler follows your links and your sitemap, respects robots.txt and runs more than 40 technical and on-page checks on every HTML page it fetches.
Each page gets HTTP status and redirect chain, indexability (meta robots, X-Robots-Tag, canonical), title and meta description checks including duplicates, H1 and heading outline, word count and thin content, images missing alt text, link counts, broken internal links, inlinks, hreflang, Open Graph and Twitter cards, schema.org JSON-LD, mixed content and URL hygiene. Issues are graded error, warning or notice, and the page score starts at 100 and drops 10, 5 or 1 point per distinct issue.
A site summary adds average score, top issues by pages affected, duplicate title and description groups, the most-linked broken URLs and orphan pages that are in your sitemap but linked from nowhere. It costs $6 per 1,000 pages audited ($0.60 for a 100-page site), and pages that answer an error are reported free. It suits SEOs, agencies and developers who want a scheduled audit without installing desktop software.
How it works
- You give one or more
startUrls; the crawl stays on that site and under each URL's path unless you setincludeGlobs. - It follows links up to
maxDepthclicks and, withuseSitemapon, reads sitemap.xml and sitemaps listed in robots.txt, then audits sitemap URLs no page links to as orphan pages. - Pages are fetched as static HTML, at most 3 requests at a time, pausing on HTTP 429 for the Retry-After period.
- Once the crawl finishes it computes the cross-page checks (duplicates, broken internal links, inlinks, hreflang return links) and writes one row per page.
- The site-level summary is saved to the run's key-value store as the
OUTPUTrecord.
Input example
A typical run input (JSON). Every field is also available as a form in the Apify console.
{
"startUrls": [
"https://crawlee.dev/js/docs/guides"
],
"maxPages": 500,
"maxDepth": 20,
"includeSubdomains": false,
"excludeGlobs": [
"**/tag/**",
"**/page/*"
],
"useSitemap": true,
"respectRobotsTxt": true
}All input fields
| Field | Type | Required | Default | What it does |
|---|---|---|---|---|
startUrls | array | yes | Where to start the audit. The crawl follows links on the same site and stays under each URL's path: https://example.com audits the whole site, https://example.com/blog only the blog. | |
maxPages | integer | no | 200 | Stop after auditing this many HTML pages. Each audited page is one 'page-audited' event; pages that answer an error are reported for free and don't count. |
maxDepth | integer | no | 20 | How many clicks away from a start URL to crawl. 0 = audit only the start pages. |
includeSubdomains | boolean | no | false | Also audit subdomains of the start site (e.g. blog.example.com when starting at example.com). |
includeGlobs | array | no | [] | Only audit pages matching one of these, e.g. https://example.com/docs/ ( = anything, * = anything except /). Replaces the default 'stay under the start path' rule. |
excludeGlobs | array | no | [] | Never audit pages matching these, e.g. /tag/ or /page/* (archive and pagination pages). |
useSitemap | boolean | no | true | Read sitemap.xml (and sitemaps listed in robots.txt). After the pages reachable by links, sitemap URLs that no page links to are audited too and reported as orphan pages. |
respectRobotsTxt | boolean | no | true | Don't audit pages that robots.txt disallows. Turn off to audit them anyway (they are then flagged 'blocked-by-robots-txt', since search engines can't crawl them). |
Output example
Each audited page is one row with its score, status, indexability, key lengths, inlinks and issue codes, as in the sample below.
| url | score | statusCode | indexable | titleLength | metaDescriptionLength | h1Count | wordCount | inlinks | issues |
|---|---|---|---|---|---|---|---|---|---|
| https://crawlee.dev/js/docs/guides/motivation | 83 | 200 | true | 68 | 146 | 1 | 8 | 0 | meta-description-duplicate, thin-content, title-too-long, orphan-page, structured-data-missing |
| https://crawlee.dev/js/docs/guides/cheerio-crawler-guide | 89 | 200 | true | 78 | 56 | 1 | 622 | 19 | meta-description-duplicate, title-too-long, meta-description-too-short |
| https://crawlee.dev/js/docs/guides/custom-http-client | 94 | 200 | true | 99 | 66 | 1 | 474 | 19 | title-too-long, meta-description-too-short |
| https://crawlee.dev/js/docs/quick-start | 95 | 200 | true | 69 | 118 | 1 | 1207 | 21 | title-too-long |
Pricing
Pay per event: you are charged only for the results you receive, with no subscription. Set a maximum total charge on any run and it stops cleanly at the cap.
| Charged for | When | Price | Per 1,000 | Worked example |
|---|---|---|---|---|
| Page audited | Charged per HTML page of your site fetched and audited (one dataset row) | $0.006 | $6 | 100 pages = $0.60; a 1,000-page site = $6.00. Pages that answer 4xx/5xx or time out are reported free |
| Actor Start (platform start fee) | Charged when the Actor starts running. Number of events charged depends on Actor memory (one event per GB, minimum one event). | $0.00005 | — | One run at 1 GB = $0.00005 |
Prices are read from the live Apify Store listing when this page is built. The listing is always authoritative.
Try SEO Audit Crawler on Apify. Pay only for the results you get.
Run on Apify →Use cases
Pre-launch or post-migration check
Audit a staging or freshly migrated site to catch missing titles, noindex tags left on by mistake, canonicals pointing elsewhere and redirect chains before search engines find them.
Find orphan pages in your sitemap
With useSitemap on, sitemap URLs that no crawled page links to are audited and flagged orphan-page. Raise maxPages to cover the whole site so the result is conclusive. The sitemap audit guide walks through it.
Weekly score tracking
Schedule a weekly run and compare the average score and top issues in the summary. A sudden jump in errors usually points to a template change or a deploy that broke something.
Audit one section only
Start at https://example.com/blog to audit just the blog, or use includeGlobs and excludeGlobs to target docs and skip tag and pagination archives.
How it compares
| ActorForge SEO Audit | Desktop SEO crawler | SaaS site audit platform | |
|---|---|---|---|
| Install required | No, runs in the cloud | Yes | No |
| Pricing model | $6 per 1,000 pages audited | Licence, often with free tier | Monthly subscription |
| JavaScript rendering | No, static HTML only | Often optional | Often optional |
| Core Web Vitals | No | Often via integrations | Often yes |
| Orphan sitemap pages | Yes | Usually yes | Usually yes |
| External link checking | No, internal links only | Usually yes | Usually yes |
| Output | JSON, CSV or Excel via the dataset, plus API | Exports from the app | Dashboards and exports |
If your site renders content client-side or you need Core Web Vitals and external link checks, a desktop crawler with JavaScript rendering or a full SaaS platform covers more. This actor is for fast, cheap static-HTML audits you can schedule and pull through an API.
Limits and honest notes
- Pages are fetched as static HTML without a browser: fast and cheap, and it covers almost all websites. Content, links and tags that exist only after JavaScript runs (single-page apps that render client-side) are not seen, which is also how many search engine and AI crawlers see your site. Core Web Vitals and other browser performance metrics are not measured.
- Thresholds: titles 30-60 characters, meta descriptions 70-160 characters, thin content under 200 words (counted in the page body without navigation, header, footer, sidebars and scripts), URLs over 115 characters, responses slower than 2 seconds.
- The score starts at 100 and loses 10 points per distinct error, 5 per warning and 1 per notice. Duplicate titles and descriptions are compared across indexable pages only (noindex and canonicalised copies are expected to repeat), case and spacing aside.
- Broken internal links and links to redirects are found from the pages the crawl fetched; internal links beyond your page limit, and external links, are not checked (use a broken link checker for that). Orphan pages are sitemap URLs no audited page links to, so they are only conclusive when the crawl covered the whole site (orphanCheckComplete in the summary).
- Crawling respects robots.txt and stays on your site and under the start URL's path, unless you set include globs. Pages behind a login are not supported. Polite by design: at most 3 requests at a time to your site, and a 429 'Too Many Requests' pauses it for its Retry-After. Pages refused by bot protection are reported as 'fetch-blocked' rows, free.
- Rows are delivered when the crawl finishes, since duplicates, broken links, hreflang return links and inlinks need every page; runs resume after a platform migration without re-charging work already done. Pages larger than 5 MB and non-HTML files are skipped.
- Links to internal network addresses (localhost, private IPs, cloud metadata) are never requested and never charged.
- Respects your run budget: set a maximum total charge and the run stops cleanly at the cap and delivers every page audited so far.
FAQ
How do I audit every page of my website for SEO?
Put your homepage in startUrls and set maxPages above your site's page count (it defaults to 200). Leave useSitemap on so pages only reachable via the sitemap are included too. Each page becomes one row with its issues and score.
What counts as a title that is too long or a thin page?
Titles are checked against 30-60 characters and meta descriptions against 70-160. Thin content means under 200 words in the page body, excluding navigation, header, footer, sidebars and scripts. URLs over 115 characters and responses slower than 2 seconds are also flagged.
Does it render JavaScript?
No. Pages are fetched as static HTML, which is fast and cheap but means content that only appears after JavaScript runs is not seen. That is also how many search engine and AI crawlers first see a site.
Are pages that return errors charged?
No. Pages that answer 4xx/5xx, time out or are refused by bot protection are reported as rows free of charge. Only HTML pages fetched and audited are charged, at $0.006 each.
Where is the site-wide summary?
It is saved in the run's default key-value store as the OUTPUT record. It lists pages audited, average score, top issues, duplicate groups, broken internal links, orphan sitemap pages and whether robots.txt and a sitemap exist.
Use it from code
Run the actor through the Apify API with the input above.
cURL
curl -X POST "https://api.apify.com/v2/acts/actor-forge~seo-audit/run-sync-get-dataset-items" \
-H "Authorization: Bearer $APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"startUrls": ["https://crawlee.dev/js/docs/guides"], "maxPages": 500, "maxDepth": 20, "includeSubdomains": false, "excludeGlobs": ["**/tag/**", "**/page/*"], "useSitemap": true, "respectRobotsTxt": true}'JavaScript (Node.js)
// Node.js 18+ (built-in fetch). Set APIFY_TOKEN in your environment.
const input = {
"startUrls": [
"https://crawlee.dev/js/docs/guides"
],
"maxPages": 500,
"maxDepth": 20,
"includeSubdomains": false,
"excludeGlobs": [
"**/tag/**",
"**/page/*"
],
"useSitemap": true,
"respectRobotsTxt": true
};
const res = await fetch(
"https://api.apify.com/v2/acts/actor-forge~seo-audit/run-sync-get-dataset-items",
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.APIFY_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify(input),
}
);
const items = await res.json();
console.log(items.length, items[0]);Python
# pip install apify-client (set APIFY_TOKEN in your environment)
import os
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
run_input = {
"startUrls": [
"https://crawlee.dev/js/docs/guides"
],
"maxPages": 500,
"maxDepth": 20,
"includeSubdomains": False,
"excludeGlobs": [
"**/tag/**",
"**/page/*"
],
"useSitemap": True,
"respectRobotsTxt": True
}
run = client.actor("actor-forge/seo-audit").call(run_input=run_input)
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
print(item)The run-sync-get-dataset-items endpoint waits for the run (up to 5 minutes) and returns the dataset rows directly. For longer runs, start the run asynchronously and read the dataset when it finishes, as the Python example does. Replace APIFY_TOKEN with your own token from the Apify console.
Step-by-step guides
How to Audit Every URL in Your Sitemap and Find Orphan Pages
Run the SEO Audit Crawler with useSitemap on and maxPages above your site's size; it audits every page it can reach plus every sitemap URL, and…
Related actors
Keyword Suggestions — Google, YouTube, Bing Autocomplete
Bulk keyword suggestions from Google, YouTube, Bing and Baidu autocomplete. Expand each seed with a-z, question words or prepositions for long-tail…
$0.30 per 1,000 · seed expandedWayback Machine Scraper — Archive.org Snapshots & URL History
Every Wayback Machine (archive.org) capture of a URL, folder, host or domain: timestamp, status, MIME type, digest and archive links. Unique…
$2 per 1,000 · snapshotGoogle Trends Scraper — Trending Now & Interest Over Time
Google Trends scraper and API: Trending Now searches for 125 countries from the official feed (traffic, start time, picture, news articles), plus…
$0.50 per 1,000 · trend resultTry SEO Audit Crawler on Apify. Pay only for the results you get.
Run on Apify →