Technical SEO Audit Tool
Pricing
from $1.79 / 1,000 results
Technical SEO Audit Tool
Review technical SEO signals across a website or a supplied set of URLs. Each HTML page record includes titles, headings, canonicals, indexability directives, redirect details, links, image alt gaps, schema types and rule-based issue codes. Uses plain HTTP without a browser or API key.
Pricing
from $1.79 / 1,000 results
Rating
0.0
(0)
Developer
Axiom Works
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
3 days ago
Last modified
Share
Returns: one row per web page with finalUrl, statusCode, title, metaDescription, h1, canonical, noindex, wordCount and a list of issues.
Input: {"startUrls": [{"url": "https://www.example.com/"}], "maxDepth": 1, "maxPages": 50}
Best for: technical SEO audits for clients, pre-launch and post-migration checks, monitoring titles, canonicals and indexability across a site.
{"finalUrl": "https://www.python.org/","statusCode": 200,"title": "Welcome to Python.org","titleLength": 21,"h1Count": 5,"canonical": null,"isIndexable": true,"issues": ["title_too_short", "meta_description_too_short", "multiple_h1", "missing_canonical", "redirected"]}
What does Website SEO Audit Tool do?
Website SEO Audit Tool is an on-page SEO checker and site crawler. Give it a website URL and it follows the internal links breadth-first, fetches each page over plain HTTP and returns one record per HTML page with the on-page SEO data an audit needs: the title and meta description with their lengths, H1 and H2 headings, the canonical URL and whether it points to the page itself, robots meta and X-Robots-Tag directives, whether robots.txt disallows the path, hreflang alternates, the html lang attribute, Open Graph and Twitter card tags, viewport and charset, the HTTP status with the full redirect chain and response time, image counts and missing alt text, a boilerplate-free word count, schema.org types from JSON-LD and microdata, and internal, external and nofollow link counts.
Every record also carries a list of plain issue codes such as missing_meta_description, multiple_h1, canonical_mismatch, noindex or http_404, so you can sort and filter a whole site in a spreadsheet without reading HTML. There is no made-up score: each code comes from a fixed, documented rule (see the FAQ).
It works on any public website. You can crawl a site from its homepage, or set maxDepth to 0 and paste a list of URLs to audit just those pages. No login, no API key and no browser are needed. Use it as a website SEO audit API, a scheduled regression check, or a tool for AI agents.
What data can you get from a website SEO audit?
| Field | Description | Example |
|---|---|---|
id | Stable ID: the first 16 hex characters of the SHA-1 of finalUrl | 1ab11d99cba1c395 |
url | URL as requested (normalised start URL or the internal link followed) | https://python.org/ |
finalUrl | URL after redirects; same as sourceUrl | https://www.python.org/ |
statusCode | HTTP status of the final response | 200 |
redirectCount | Number of redirect hops | 1 |
redirectChain | {url, statusCode} for each 3xx hop; empty when no redirect | [{"url": "https://python.org/", "statusCode": 301}] |
contentType | MIME type of the response | text/html |
responseTimeMs | Time to fetch the page, redirects included | 60 |
htmlSizeBytes | Size of the decoded HTML | 52282 |
depth | Crawl depth, 0 for start URLs | 0 |
title | Text of <title>; null when missing | Welcome to Python.org |
titleLength | Characters in the title (0 when missing) | 21 |
metaDescription | Content of the meta description; null when missing | The official home of the Python Programming Language |
metaDescriptionLength | Characters in the meta description | 52 |
metaKeywords | Content of meta keywords; null when missing | Python programming language ... |
h1, h2 | Text of every H1 and H2 on the page | ["Functions Defined", ...] |
h1Count, h2Count, h3Count | Number of H1, H2 and H3 elements | 5, 9, 0 |
canonical | Absolute URL from <link rel=canonical>; null when missing | https://www.mozilla.org/en-US/ |
canonicalMatches | true when the canonical equals finalUrl; null without a canonical | true |
robotsMeta | Content of <meta name=robots> | noindex |
xRobotsTag | X-Robots-Tag response header | noindex |
robotsTxtDisallowed | true when robots.txt disallows the path for User-agent: * | false |
noindex, nofollow | Directives found in robots meta or X-Robots-Tag | false |
isIndexable | Status 200, not noindex, canonical absent or self-referencing | true |
lang | Value of <html lang> | en |
hreflang, hreflangCount | {lang, href} alternates and their number | 85 |
ogTitle, ogDescription, ogImage | Open Graph tags | https://www.python.org/static/opengraph-icon-200x200.png |
twitterCard | Content of twitter:card | summary |
viewport, charset | Viewport meta and declared charset | width=device-width, initial-scale=1.0, utf-8 |
imagesTotal, imagesMissingAlt | Number of <img> and of those without a non-empty alt | 22, 17 |
imagesMissingAltSrc | Up to 10 absolute src URLs of images missing alt | ["https://www.mozilla.org/media/..."] |
wordCount | Words in the body text with navigation, header, footer, aside, scripts and styles removed | 409 |
schemaTypes | schema.org types from JSON-LD and microdata | ["WebSite", "SearchAction"] |
internalLinksCount, externalLinksCount, nofollowLinksCount | Unique links to the same site, to other hosts, and with rel=nofollow | 64, 63, 0 |
internalLinks, externalLinks | Up to 20 unique link URLs of each kind | ["https://www.python.org/jobs/", ...] |
issues, issueCount | Detected issue codes and how many | ["multiple_h1", "missing_canonical"], 2 |
scrapedAt | When the page was audited (ISO 8601) | 2026-10-20T00:20:35.701Z |
sourceUrl | The audited page | https://www.python.org/ |
At the end of each run the key-value store also gets a SUMMARY record: the start URLs, pages crawled, pages with issues, a count per issue code, the URLs skipped because they were not HTML (PDF, XML, images) and the URLs that gave no response, with the reason.
How to use Website SEO Audit Tool
- Open the Actor in Apify Console and go to the Input tab.
- In Start URLs, enter the site to audit, for example
[{"url": "https://www.example.com/"}]. A bare host such asexample.comworks too. - Set Crawl depth: 0 audits only the URLs you gave, 1 also audits the pages they link to, 2 goes one level further.
- Set Maximum pages to fetch and Maximum results to the size of the audit you want. Both are shared by all start URLs.
- Click Start. Records appear in the Output tab as pages are audited.
- Use the SEO audit view for the key columns, or export everything as JSON, CSV, Excel, XML or HTML.
Tips:
- To audit a list of landing pages, paste them all into Start URLs and set Crawl depth to 0. Each URL becomes one record, including pages that answer 404 or 500.
- Exclude sections you do not care about with Exclude URL patterns, for example
/tag/or\?page=. - Leave the proxy off for most sites. If a start page answers 403, 429 or 503, the Actor retries it once through Apify Proxy datacenter and then residential IPs on its own.
- Turn on Respect robots.txt if you want the crawl to stay out of disallowed sections. The
robotsTxtDisallowedfield is reported either way.
Input
| Field | Type | Required | Description |
|---|---|---|---|
startUrls | array | Yes (or url / urls / startUrl) | Pages to start from, as {"url": ...} objects or plain strings. Full URLs or bare hosts. |
maxDepth | integer | No | Link levels to follow, 0-10, default 1. 0 = URL-list mode. |
maxPages | integer | No | Pages fetched across all start URLs, 1-10000, default 50. |
maxItems | integer | No | Records returned across all start URLs, 1-100000, default 50. |
includeSubdomains | boolean | No | Treat subdomains as internal, default false. |
respectRobotsTxt | boolean | No | Skip links disallowed by robots.txt, default false. |
excludeUrlPatterns | array of strings | No | Regular expressions; matching internal links are not crawled. |
maxRedirects | integer | No | Redirect hops to follow per page, 0-30, default 10. |
requestTimeoutSecs | integer | No | Seconds to wait for each response, 3-120, default 15. |
maxConcurrency | integer | No | Pages fetched in parallel, 1-20, default 3 (at most 2 per host). |
userAgent | string | No | Custom User-Agent; default is desktop Chrome. |
proxyConfiguration | object | No | Proxy for all requests. Default: none, with an automatic Apify Proxy retry for blocked start pages. |
{"startUrls": [{ "url": "https://www.mozilla.org/en-US/" }],"maxDepth": 1,"maxPages": 25,"maxItems": 25,"includeSubdomains": false,"respectRobotsTxt": false,"excludeUrlPatterns": ["/wp-admin/"],"proxyConfiguration": { "useApifyProxy": false }}
Output
Real records from a local run: the python.org homepage reached through a redirect, and a test page that answers 404 with noindex. Some fields are left out here for space.
[{"id": "1ab11d99cba1c395","url": "https://python.org/","finalUrl": "https://www.python.org/","statusCode": 200,"redirectCount": 1,"redirectChain": [{ "url": "https://python.org/", "statusCode": 301 }],"contentType": "text/html","responseTimeMs": 60,"htmlSizeBytes": 52282,"depth": 0,"title": "Welcome to Python.org","titleLength": 21,"metaDescription": "The official home of the Python Programming Language","metaDescriptionLength": 52,"h1": ["Functions Defined", "All the Flow You’d Expect", "Compound Data Types", "Intuitive Interpretation", "Quick & Easy to Learn"],"h1Count": 5,"h2Count": 9,"canonical": null,"canonicalMatches": null,"robotsTxtDisallowed": false,"noindex": false,"isIndexable": true,"lang": "en","hreflangCount": 0,"ogTitle": "Welcome to Python.org","ogImage": "https://www.python.org/static/opengraph-icon-200x200.png","viewport": "width=device-width, initial-scale=1.0","charset": "utf-8","imagesTotal": 1,"imagesMissingAlt": 0,"wordCount": 409,"schemaTypes": ["WebSite", "SearchAction"],"internalLinksCount": 64,"externalLinksCount": 63,"issues": ["title_too_short", "meta_description_too_short", "multiple_h1", "missing_canonical", "redirected"],"issueCount": 5,"scrapedAt": "2026-10-20T00:20:35.701Z","sourceUrl": "https://www.python.org/"},{"id": "9c72b15b852b8684","url": "https://crawler-test.com/robots_protocol/non_200_with_noindex","finalUrl": "https://crawler-test.com/robots_protocol/non_200_with_noindex","statusCode": 404,"redirectCount": 0,"title": "Non-200 with Noindex","titleLength": 20,"h1Count": 0,"robotsMeta": "noindex","noindex": true,"isIndexable": false,"wordCount": 4,"issues": ["title_too_short", "missing_meta_description", "missing_h1", "missing_canonical", "noindex", "thin_content", "missing_viewport", "missing_html_lang", "http_404"],"issueCount": 9,"sourceUrl": "https://crawler-test.com/robots_protocol/non_200_with_noindex"}]
How much does it cost to run a website SEO audit?
Website SEO Audit Tool is charged per result: one result is one audited page. You pay only for the records returned, and maxItems caps how many there can be, so a run never costs more than you planned. Pages that give no response and non-HTML files are not records and are not charged. See the Pricing tab for the current price.
Use with AI agents (MCP)
Connect Website SEO Audit Tool to Claude, Cursor or any MCP client through the Apify MCP server:
{"mcpServers": {"apify": {"url": "https://mcp.apify.com/?tools=axiomworks/website-seo-audit-tool"}}}
Example prompts:
- "Audit https://www.example.com/ two levels deep and list every page with a missing meta description or more than one H1."
- "Check these 10 landing pages with crawl depth 0 and tell me which ones are not indexable and why."
- "Crawl our blog, find pages under 300 words, and suggest which ones to merge."
Integrations and API
Run Website SEO Audit Tool from your own code with the Apify API. Set APIFY_TOKEN to your Apify API token (send it in the Authorization header, never in the URL).
Python
from apify_client import ApifyClientclient = ApifyClient("<YOUR_API_TOKEN>")run = client.actor("axiomworks/website-seo-audit-tool").call(run_input={"startUrls": [{"url": "https://www.example.com/"}],"maxDepth": 1,"maxPages": 50,})for item in client.dataset(run["defaultDatasetId"]).iterate_items():print(item["finalUrl"], item["statusCode"], item["issues"])
JavaScript
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: '<YOUR_API_TOKEN>' });const run = await client.actor('axiomworks/website-seo-audit-tool').call({startUrls: [{ url: 'https://www.example.com/' }],maxDepth: 1,maxPages: 50,});const { items } = await client.dataset(run.defaultDatasetId).listItems();
cURL
curl -X POST "https://api.apify.com/v2/acts/axiomworks~website-seo-audit-tool/run-sync-get-dataset-items" \-H "Authorization: Bearer $APIFY_TOKEN" \-H "Content-Type: application/json" \-d '{"startUrls": [{"url": "https://www.example.com/"}], "maxDepth": 0}'
The Integrations tab connects the Actor to Make, Zapier, n8n, Google Sheets, Slack and webhooks, so a scheduled audit can drop its results into a sheet or alert you when new issues appear.
FAQ
Which rules produce the issue codes?
missing_title; title_too_long (over 60 characters) and title_too_short (under 30); missing_meta_description; meta_description_too_long (over 160) and meta_description_too_short (under 70); missing_h1 and multiple_h1; missing_canonical and canonical_mismatch (canonical is not the page's own final URL); noindex; robots_txt_disallowed; images_missing_alt; thin_content (under 300 words); redirected; not_https; missing_viewport; missing_html_lang; and http_<status> for any status other than 200. They are fixed thresholds, not a judgement about rankings, so treat them as a checklist.
How is the word count calculated?
The body text is taken after removing scripts, styles, noscript, template, svg, nav, header, footer and aside elements, then split on whitespace. It is a heuristic: a site that puts its main content inside a <header> or <nav> gets a lower count.
Does it render JavaScript? No. It reads the HTML the server sends, which is what most search engine crawlers see first. Content that only appears after JavaScript runs is not counted.
Which links count as internal?
Links whose host, ignoring a leading www., equals the host the start URL lands on. A start URL python.org that redirects to www.python.org makes both internal. Subdomains such as docs.python.org count as external unless you turn on includeSubdomains. A link that redirects to another site is audited (status and redirect chain) but its own links are not followed.
Why did my run return 0 results?
The start URL gave no HTTP response (the host does not exist, refused the connection, timed out, or redirected too many times), or it is not an HTML page (a PDF or XML file). The status message names the URL and the reason, and the SUMMARY record lists every skipped or failed URL. A page that answers 404 or 500 still gets a record with its status.
Is robots.txt obeyed?
By default the crawl reports robots.txt (robotsTxtDisallowed) without obeying it, so you can see blocked pages that are still linked. Set respectRobotsTxt to true to keep the crawl out of disallowed paths.
Can I schedule it?
Yes. Save your input as a task and add a weekly schedule, then compare the SUMMARY issue counts between runs.
Is it legal to audit websites with a crawler?
Website SEO Audit Tool reads only publicly available pages, the same HTML any visitor's browser receives, and does not log in or bypass access controls. It crawls politely and can respect robots.txt on request. You are responsible for how you use the results and for having the right to audit the sites you crawl; when pages contain personal data, data protection laws such as the GDPR may apply. This is not legal advice.
Website SEO Audit Tool is an independent tool. It is not affiliated with, endorsed by or sponsored by any of the websites it audits, including the sites used in the examples above.
Related Actors
| Actor | What it does |
|---|---|
| Sitemap URL Extractor | Lists every URL in a site's sitemaps, a good start list for a URL-list audit |
| Link Status Checker | Checks links for broken or redirecting targets |
| PageSpeed Insights Checker | Gets Core Web Vitals and Lighthouse scores for pages |
Feedback
Found a bug or need a field that isn't here? Open an issue on the Issues tab and we'll look at it.