SEO Audit Tool: technical SEO issues per page, in bulk
Pricing
from $19.50 / 1,000 page auditeds
SEO Audit Tool: technical SEO issues per page, in bulk
SEO audit tool for up to 500 pages per run: title and description lengths, canonical, robots meta, hreflang, headings, word count, images without alt text, structured data, Open Graph, viewport and redirects, with a ranked list of issues per page. Respects robots.txt.
Pricing
from $19.50 / 1,000 page auditeds
Rating
0.0
(0)
Developer
Steadydata Team
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
2 days ago
Last modified
Categories
Share
An SEO audit tool for up to 500 pages per run. Audit the pages on the technical basics and get, per page, a ranked list of what is actually wrong: a forgotten noindex, a missing or overlong title, no canonical, no H1 or six of them, images without alt text, thin content, no viewport, no structured data. Plus the measured fields behind every verdict. Pages that their own robots.txt forbids are skipped and never charged.
Why this scraper
- It tells you what to fix, not just what it found. Thirty columns of raw fields are
a dataset;
issuesis a to-do list. Every failed check comes back with a code, a severity (critical, warning, info), a sentence and the measured value, sorted worst first. Filter onseverity = criticaland you have your morning. - The noindex check alone pays for a run. A page nobody can find because a staging
noindexshipped to production is the most expensive SEO mistake there is, and it is invisible in a browser. - It respects robots.txt. The target's own robots.txt is read first, with the rules
search engines actually use, and a page it forbids comes back as a free
ROBOTS_DISALLOWEDrecord. An audit that reports on robots meta tags while ignoring robots.txt would not be worth much. - Your limits, not ours. Title and description length limits are inputs, so the audit matches your own house style instead of a number we picked.
- Sitemaps found for free. Every row lists the sitemaps declared in the site's robots.txt, which is usually the fastest way to find the rest of the pages.
- Only delivered pages are charged. Robots refusals, dead URLs, timeouts and bot walls are free error records. No start fee.
Who this is for
SEO teams and agencies auditing a site or a portfolio of client sites on a schedule. Developers checking a release for the mistakes that only show up in the HTML. Anyone who needs the technical basics of hundreds of pages in one table.
Who this is not for
Read this before you buy. This actor fetches the HTML of each URL you give it and
does not run JavaScript, so a page that renders its title and content in the browser
shows up emptier here than it looks to a visitor. It also audits only the URLs you
supply: it does not crawl a site to discover pages (the sitemapsInRobots field points
you at the sitemap so you can feed those URLs in). Page speed, Core Web Vitals and
Lighthouse scores are not included; those need a browser and Google's own API.
Input fields
| Field | Type | Required or default | What it does |
|---|---|---|---|
urls | list of text | required | Page URLs or domains, one per row, up to 500. A domain is audited on its home page. A URL that its own robots.txt forbids is skipped and not charged. |
language | text | en-US,en;q=0.9 | Language header sent with the request. |
titleMaxLength | number | 60 | A title longer than this is reported as an issue. Google truncates around 60 characters. |
descriptionMaxLength | number | 160 | A meta description longer than this is reported as an issue. Google truncates around 160. |
Input example
{"urls": ["https://apify.com", "wordpress.org", "https://example.com/product/x"],"language": "en-US,en;q=0.9","titleMaxLength": 60,"descriptionMaxLength": 160}
A domain is audited on its home page; a full URL is audited as given.
Output example
| Field | Type | What it holds | Example |
|---|---|---|---|
url | text | The page that was audited. | https://wordpress.org |
finalUrl | text | The page that was really read, after every redirect. | https://wordpress.org/ |
statusCode | number | The HTTP status the page answered with; anything but 200 means a visitor sees something else. | 200 |
issueCount | number | How many findings this row holds; zero means nothing went wrong. | 1 |
issues | list | The findings themselves, each with what is wrong and how serious it is. | [{"code": "images-without-alt", "severity": "warning", "m... |
title | text | The page title as it stands in the HTML, which is what a search result shows. | Blog Tool, Publishing Platform, and CMS – WordPress.org |
titleLength | number | Counted from title; empty when the page has no title. | 55 |
metaDescription | text | Filled when the page has a meta description; missing is reported as an issue. | Open source software which you can use to easily create a... |
metaDescriptionLength | number | Counted from metaDescription; empty when the page has none. | 90 |
canonical | text | Filled when the page declares a canonical link. | https://wordpress.org/ |
robotsMeta | text | Filled when the page has a robots meta tag; without one the page is indexable by default. | max-image-preview:large |
isIndexable | true/false | True when nothing stops a search engine from indexing the page; false means it will never rank. | True |
h1 | list | The first H1 on the page; empty for a page without one, which is reported as an issue. | ["Meet WordPress"] |
h1Count | number | How many h1 headings the page has; none and more than one are both a finding. | 1 |
h2Count | number | How many h2 headings the page has, a rough measure of its structure. | 10 |
wordCount | number | How many words of text the page holds; a thin page rarely ranks. | 524 |
imageCount | number | How many images the page carries. | 18 |
imagesWithoutAlt | number | How many of those carry no alt text, which costs both accessibility and image search. | 9 |
internalLinks | number | How many links point to the site's own pages. | 61 |
externalLinks | number | How many point elsewhere. | 18 |
hreflang | list | Filled when the page declares language alternatives. | ["af-za", "am-et"] |
structuredDataTypes | list | The schema.org types in the JSON-LD on the page; empty when the page has none. | ["ImageObject", "Organization"] |
openGraph | list | The Open Graph tags that decide how the page looks when shared; empty when the page has none. | ["description", "image"] |
hasTwitterCard | true/false | Whether the page declares Twitter Card tags. | True |
hasViewport | true/false | Whether the page declares a viewport, the tag that makes it usable on a phone. | True |
htmlLang | text | Filled when the html tag declares a language. | en-US |
charset | text | Filled when the page declares a character set. | utf-8 |
isHttps | true/false | True when the page is served over https. | True |
redirected | true/false | True when the final URL differs from the one submitted. | False |
contentEncoding | text | Filled when the server compressed the response, for example gzip or br. | br |
pageSizeBytes | number | The size of the page in bytes; a heavy page is slow on a phone. | 165017 |
responseTimeMs | number | How long the server took to answer, in milliseconds. | 37 |
robotsRule | text | The robots.txt rule that decided whether this URL could be fetched, so the choice can be checked. | geen regel |
sitemapsInRobots | list | The sitemaps named in robots.txt; empty when robots.txt names none or does not exist. | ["https://wordpress.org/sitemap.xml", "https://wordpress.... |
{"url": "https://example.com/pagina","finalUrl": "https://example.com/pagina","statusCode": 200,"issueCount": 3,"issues": [{"code": "noindex", "severity": "critical", "message": "The page tells search engines not to index it", "value": "noindex, nofollow"},{"code": "canonical-missing", "severity": "warning", "message": "No canonical link", "value": null},{"code": "images-without-alt", "severity": "warning", "message": "4 of 9 images have no alt text", "value": 4}],"title": "Een nette titel voor deze pagina","titleLength": 32,"metaDescription": "Een nette beschrijving van deze pagina.","metaDescriptionLength": 39,"canonical": null,"robotsMeta": "noindex, nofollow","isIndexable": false,"h1": ["Kop een"],"h1Count": 1,"h2Count": 2,"wordCount": 251,"imageCount": 9,"imagesWithoutAlt": 4,"internalLinks": 24,"externalLinks": 3,"hreflang": ["en", "nl"],"structuredDataTypes": ["Organization", "PostalAddress"],"openGraph": ["image", "title"],"hasTwitterCard": true,"hasViewport": true,"htmlLang": "nl","charset": "utf-8","isHttps": true,"redirected": false,"contentEncoding": "br","pageSizeBytes": 81234,"responseTimeMs": 412,"robotsRule": "allow /","sitemapsInRobots": ["https://example.com/sitemap.xml"],"status": "ok"}
A page its own robots.txt forbids produces an error record instead, and is not charged:
{"input": "https://example.com/private/page","status": "error","errorCode": "ROBOTS_DISALLOWED","error": "https://example.com/private/page is forbidden by its own robots.txt (disallow /private); skipped and not charged"}
Error codes: INVALID_URL, ROBOTS_DISALLOWED, FETCH_FAILED, BLOCKED. INPUT_TRUNCATED appears once when your input is longer than this actor accepts.
Related actors from steadydata
- website-tech-stack: what the site is built with
- domain-dns-ssl-report: domain and certificate health
- rss-feed-reader: the site's own feed, if it publishes one
- Used together with company-career-site-jobs and website-tech-stack in a recipe: Find companies that are hiring, and see what they run.
Pricing
Pay per event: one page-audited event per delivered page. No charge for robots
refusals, dead URLs, timeouts or bot walls, and no start fee.
Free Apify plan: this actor delivers up to 25 rows per run for accounts on the Apify free plan, and then stops with a message. That limit is set by us, not by Apify. It exists so the actor keeps paying for itself for the people who do pay. Any paid Apify plan runs it at full size, billed per delivered row, with failed rows never charged.
Reviews: if this actor saves you time, a short review on this page is the one thing that helps most. Ratings are what other buyers look at first, and we have no other way to ask.
FAQ
Which issues can it report?
noindex, http-error, title-missing, title-too-long, title-too-short,
description-missing, description-too-long, canonical-missing, h1-missing,
h1-multiple, images-without-alt, thin-content, viewport-missing, lang-missing,
structured-data-missing, open-graph-missing and no-https.
Why does imagesWithoutAlt count images with alt=""?
Because an empty alt is only correct for decorative images, and at scale it is far more
often an oversight. The count and the total are both in the row, so you can judge.
Why does one page report 117 images without alt text? Because that is what the HTML as served contains. Sites that lazy-load images often set the alt attribute later in JavaScript, which this actor does not run, so treat a very high count on an image-heavy page as "check this by hand" rather than as 117 separate mistakes. The total count is in the row so you can see the ratio.
Are 251 words really thin content?
The thin-content issue is info, not a verdict: for a product page it is normal, for
an article it is a signal. Filter it out if it does not apply to your pages.
Can it crawl my whole site?
No. It audits the URLs you give it. Use sitemapsInRobots from the first run to collect
the rest of your URLs, then feed those in.
Is personal data collected? No. These are properties of a web page.
What happens when a check is wrong? Every issue carries the measured value, so a disagreement is verifiable rather than a matter of opinion. If a rule is genuinely wrong, it gets fixed.