# SEO Audit Tool – Site Crawler, Broken Links & Meta Checker (`batuweb/seo-audit-toolkit`) Actor

Free-to-try technical SEO audit crawler & API: checks titles, meta descriptions, H1, canonicals, noindex, hreflang, schema, Open Graph, alt text, broken links, sitemap.xml and robots.txt. Site score 0–100 + prioritized fixes. JSON/CSV export.

- **URL**: https://apify.com/batuweb/seo-audit-toolkit.md
- **Developed by:** [Batulzii Ulziikhuu](https://apify.com/batuweb) (community)
- **Categories:** SEO tools, Developer tools, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 1,000 page auditeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## SEO Audit Toolkit – Website & Technical SEO Checker

**Audit any website for technical SEO problems in minutes.** Give it a domain or a list of URLs. The SEO Audit Toolkit reads `robots.txt` and the XML sitemap, crawls your pages and checks each one against 25+ on-page and technical SEO rules. You get a **site score from 0 to 100** and a **list of fixes ordered by priority**, plus a clean per-page dataset you can export to CSV, Excel or JSON, or pull through the API.

**New (optional): an automated WCAG 2.x accessibility scan (incl. WCAG 2.2 AA rules)** of the same pages in a real browser, with violations and "needs manual review" items per page. It helps you find issues; it is not a compliance certification (see the disclaimer below).

It's built for reliability: pages that fail are reported instead of crashing the run, and every request is retried. Broken targets show up as findings, not errors.

### What does the SEO Audit Toolkit check?

| Area | Checks |
|---|---|
| **Indexability** | HTTP status, redirects and redirect chains, `noindex` (meta robots and `X-Robots-Tag`), canonical missing or pointing elsewhere, HTTPS |
| **On-page** | Title missing, too short or too long, or duplicated; meta description missing, wrong length or duplicated; H1 missing or multiple; `<html lang>`; mobile viewport; thin content (word count) |
| **International** | `hreflang` tags, missing self-reference |
| **Rich results** | JSON-LD and microdata detection, schema types (Article, Product, Organization…), invalid JSON-LD |
| **Social** | Open Graph (`og:title`, `og:image`), Twitter card |
| **Images** | Images missing `alt` text, with sample URLs |
| **Links** | Broken internal links (4xx/5xx), internal `nofollow` links, internal and external link counts |
| **Site files** | `robots.txt` found, blocking all crawlers, sitemap declared; XML sitemap found, valid, reachable, and sitemap index support |
| **Technology** | CMS and framework detection (WordPress, Shopify, Wix, Webflow, Next.js, Astro, Nuxt…), analytics (GA4, GTM, Plausible, Matomo…), hosting (Cloudflare, Vercel, Netlify) |
| **Accessibility (optional)** | Automated axe-core rules for WCAG 2.0 A/AA, 2.1 A/AA and 2.2 AA in headless Chrome, based on axe-core®: missing alt text, colour contrast, form labels, link and button names, `lang`, ARIA misuse and more, mapped to WCAG success criteria; plus items that need manual review |
| **Speed (optional)** | Google PageSpeed Insights mobile Lighthouse performance score plus lab metrics (LCP, CLS, TBT), using your own free API key |

### Responsible crawling

- **Only audit sites you own or are authorised to audit.**
- `robots.txt` is respected by default: URLs it disallows for our crawler (`SEOAuditBot`) or for `*` are skipped, not charged, and listed in the summary (`skippedByRobotsTxt`, issue `SKIPPED_BY_ROBOTS`).
- `ignoreRobotsTxt` (default **off**) is only for site owners, e.g. to audit a staging site that blocks all crawlers.
- The crawler identifies itself honestly (`SEOAuditBot/1.0`) and never fetches private or internal network addresses.

### Use cases

- **Agencies and freelancers:** run a quick technical SEO audit before a client pitch or onboarding.
- **Site owners and bloggers:** catch broken links, missing meta descriptions and accidental `noindex` after a redesign or migration.
- **Scheduled monitoring:** schedule weekly runs and get alerted when the score drops.
- **Bulk checks:** audit dozens of domains at once, for example a portfolio of niche sites or a lead list.
- **AI agents and automations:** call it from Make, Zapier, n8n or the Apify MCP server and feed the prioritized issues to an LLM to write fix tickets.

### How to use it

1. Enter one or more **websites or URLs**, for example `example.com`.
2. Choose how pages are found. **Sitemap** is the recommended default. **Crawl** follows internal links, and **Only the URLs I list** checks just those pages.
3. Set **Max pages**. The default of 10 is good for a quick first check; raise it to check more of the site.
4. Click **Start**, then open the **Site summary** output (saved as `OUTPUT` in the key-value store and as the last dataset item, `type: "summary"`).

#### Input example

```json
{
  "startUrls": ["https://example.com"],
  "crawlMode": "sitemap",
  "maxPages": 10,
  "checkBrokenLinks": true,
  "detectTechStack": true
}
```

#### Output example: summary record

```json
{
  "type": "summary",
  "score": 97,
  "pagesAudited": 10,
  "pagesFailed": 0,
  "issuesBySeverity": { "critical": 0, "high": 1, "medium": 2, "low": 11 },
  "prioritizedIssues": [
    { "code": "BROKEN_INTERNAL_LINKS", "severity": "high", "pages": 1, "exampleUrls": ["https://example.com/about/"] },
    { "code": "TITLE_TOO_SHORT", "severity": "medium", "pages": 2, "exampleUrls": ["https://example.com/contact/"] },
    { "code": "STRUCTURED_DATA_MISSING", "severity": "low", "pages": 5, "exampleUrls": ["https://example.com/"] }
  ],
  "robotsTxt": { "https://example.com": { "status": 200, "sitemapsDeclared": ["https://example.com/sitemap-index.xml"], "blocksAllCrawlers": false } },
  "sitemaps": [{ "url": "https://example.com/sitemap-0.xml", "status": 200, "valid": true, "urlCount": 51 }]
}
```

#### Output example: page record

```json
{
  "type": "page",
  "url": "https://example.com/about/",
  "statusCode": 200,
  "score": 92,
  "issues": [{ "severity": "high", "code": "BROKEN_INTERNAL_LINKS", "message": "1 internal link(s) return an error" }],
  "title": "About us – Example",
  "titleLength": 18,
  "metaDescriptionLength": 142,
  "h1": ["About us"],
  "canonical": "https://example.com/about/",
  "noindex": false,
  "structuredData": { "jsonLdBlocks": 1, "invalidJsonLd": 0, "types": ["WebSite", "Organization"] },
  "images": { "total": 4, "missingAlt": 0 },
  "links": { "internal": 23, "external": 3, "brokenInternal": [{ "url": "https://example.com/old-page", "status": 404 }] },
  "wordCount": 296,
  "techStack": ["Astro", "Google Analytics", "Cloudflare"]
}
```

Pages that can't be fetched at all (DNS errors, timeouts) appear as `{"type": "error", "url": ..., "error": ...}`, so you always know what was skipped.

### Accessibility scan (WCAG 2.x) – optional

> **What this tool is, and what it is not.** This Actor runs automated accessibility tests (based on the open-source axe-core® engine) and reports issues it can detect against WCAG 2.x success criteria. Automated testing finds only part of all accessibility barriers (axe-core's maintainers estimate on average about 57%). Many criteria need manual and assistive-technology testing. The results **help you find and prioritise issues**. They are **not a conformity assessment, certification or proof of compliance** with WCAG, EN 301 549, the European Accessibility Act, the German BFSG, the ADA or any other law. They are **not legal advice**. Whether and how accessibility laws apply to your organisation must be assessed by a qualified professional. A result with zero violations does not mean a site is accessible.
>
> **Hinweis:** Dieses Tool führt automatisierte Barrierefreiheitstests durch und meldet erkennbare Probleme gegenüber den WCAG-2.x-Erfolgskriterien. Automatisierte Tests erkennen nur einen Teil der Barrieren; viele Kriterien erfordern manuelle Prüfung. Die Ergebnisse helfen, Probleme zu finden und zu priorisieren. Sie sind **keine Konformitätsbewertung, kein Zertifikat und kein Nachweis der Einhaltung** von WCAG, EN 301 549, European Accessibility Act oder BFSG und **keine Rechtsberatung**. Ob und wie das BFSG auf Ihr Unternehmen anwendbar ist, klären Sie bitte mit einer fachkundigen Stelle. „0 Verstöße“ bedeutet nicht, dass eine Website barrierefrei ist.
>
> axe-core® is a trademark of Deque Systems, Inc. in the US and other countries. This Actor is not affiliated with or endorsed by Deque Systems.

Turn on **Check accessibility** (`checkAccessibility: true`) to add a technical, automated WCAG 2.x scan to your SEO audit. It's useful when you prepare a site for the European Accessibility Act (EAA, in Germany the BFSG) or simply want to fix barriers that also hurt usability and SEO. Note that EN 301 549 V3.2.1, the current harmonised standard, references **WCAG 2.1 AA** (you can pick that rule set) and contains requirements beyond WCAG.

**Exact rule-set scope.** The default rule set runs the axe-core rules tagged `wcag2a`, `wcag2aa`, `wcag21a`, `wcag21aa` and `wcag22aa`. axe-core has no `wcag22a` tag: it has no automated rules for the success criteria WCAG 2.2 added at Level A, so those (like many other criteria) need manual review. Smaller sets: WCAG 2.1 (2.0 + 2.1 A/AA), WCAG 2.0 A/AA, WCAG 2.0 A.

**Fully vs partially checked pages.** robots.txt is checked for every request the browser makes, including iframes, redirects and resources on other hosts (unreachable robots.txt = not fetched). If a frame was not loaded (`framesBlockedByRobots`) or a stylesheet, script or frame failed or returned HTTP 400+ (`failedResources`, `renderComplete: false`), the page is reported with `status: "partial"` and `partialReasons`: findings are still listed, but results may differ from what visitors see. Fonts and media are deliberately not loaded by the scanner and don't count, and neither do requests the page blocks itself (its own Content-Security-Policy, mixed content), since visitors' browsers block those too. Only fully checked pages (`status: "checked"`) are ever charged for the accessibility check; partial and skipped pages are free.

**How it works**

- The first `accessibilityMaxPages` audited pages (default 10, max 200) are opened in headless Chrome and tested with the open-source axe-core® engine (MPL-2.0, used unmodified; source: github.com/dequelabs/axe-core).
- Each page record gets an `accessibility` object: counts, `violations` (rule, impact, WCAG criteria such as `1.4.3`, how to fix, affected elements) and a separate `needsManualReview` list for checks a machine can't decide.
- The summary record gets a site-level `accessibility` block (top issues across pages), and a Markdown report is saved as `ACCESSIBILITY_REPORT` in the key-value store, in English or German (`accessibilityLanguage`).
- Element HTML snippets are cut to 250 characters. Set `accessibilityIncludeHtmlSnippets: false` to leave them out completely (only CSS selectors are kept). No screenshots or full-page HTML are stored.
- The SEO score is not affected by accessibility results, and there is deliberately no accessibility "score" or pass/fail flag.

**Polite by design:** one page at a time, at least 1 second between pages on the same host, `Crawl-delay` honoured up to 10 seconds, back-off on HTTP 429/503 and a stop after repeated errors. If a site's `robots.txt` can't be fetched (5xx, 429, network error), the browser scan for that site is skipped for free. Fonts and media are not downloaded. If the run gets close to its timeout, the scan stops early so the SEO results are still delivered (the remaining pages are skipped, free). We recommend **1024 MB memory** or more when the scan is on (512 MB works but is about twice as slow).

#### Input example: SEO audit + accessibility scan

```json
{
  "startUrls": ["https://example.com"],
  "maxPages": 20,
  "checkAccessibility": true,
  "accessibilityMaxPages": 20,
  "accessibilityStandard": "wcag22aa",
  "accessibilityLanguage": "en"
}
```

#### Output example: `accessibility` on a page record

```json
{
  "status": "checked",
  "standard": "Automated axe-core rules for WCAG 2.0 A/AA, 2.1 A/AA and 2.2 AA (tags wcag2a, wcag2aa, wcag21a, wcag21aa, wcag22aa)",
  "counts": { "violations": 2, "violationElements": 5, "needsManualReview": 1, "passes": 31, "byImpact": { "critical": 1, "serious": 1, "moderate": 0, "minor": 0 } },
  "violations": [
    { "ruleId": "image-alt", "impact": "critical", "wcagCriteria": ["1.1.1"], "help": "Images must have alternative text", "elementCount": 3,
      "elements": [{ "target": "header > img", "html": "<img src=\"/logo.png\">", "failureSummary": "Fix any of the following: Element does not have an alt attribute" }] }
  ],
  "needsManualReview": [{ "ruleId": "color-contrast", "impact": "serious", "wcagCriteria": ["1.4.3"], "elementCount": 1 }],
  "disclaimer": "Automated WCAG scan only: finds part of all barriers, needs manual review, is not a conformity assessment, certification or legal advice; 0 violations does not mean accessible."
}
```

Pages that could not be scanned get `"status": "skipped"` with a `reason` (for example `ROBOTS_DISALLOWED`, `HTTP_404`, `LIMIT`).

### How the score works

Every page starts at 100 and loses points per issue: critical −15, high −8, medium −4, low −1. A page returning an HTTP error (4xx/5xx) scores 0, and unreachable URLs count as 0 in the site average. The site score is the average page score minus site-wide issues, such as a missing sitemap or `robots.txt` blocking everything. Issues are ranked by severity, then by how many pages they affect, so you can **fix the top of the list first**.

### How much does it cost?

You pay per page audited: **$0.004 per page** on the Free plan, getting cheaper on paid plans (down to $0.0015). There's also a tiny start fee of $0.00005 per run, and the optional performance analysis costs $0.01 per page (it runs with your own free Google PageSpeed Insights API key; the fee covers our processing, Google doesn't charge for its API). **A 50-page audit costs about $0.20.** The summary record and errors for unreachable URLs are free. Set *Maximum cost per run* and the Actor audits only as many pages as your budget allows.

**The optional accessibility check is free during launch**: you pay only the normal per-page audit fee. From **3 November 2026** it costs **$0.004 per fully checked page** on the Free plan, getting cheaper on paid plans ($0.003 / $0.0025 / $0.002). It's only charged when `checkAccessibility` is on. Partial and skipped pages are never charged. We recommend 1024 MB memory when the check is on.

### FAQ

**Does it render JavaScript?** The SEO checks don't: they read the server-rendered HTML, which is what search engines index first, and that keeps it fast and cheap. For sites that only exist client-side, a low word count and a missing H1 are the warning signs. The optional accessibility scan does use a real browser, so it sees the rendered page.

**Will it overload my server?** No. Concurrency defaults to 10 and you can lower it. It identifies itself with a clear user agent and audits only the number of pages you set.

**Do I need a PageSpeed API key?** No, it's optional. Get a free key in the Google Cloud console (PageSpeed Insights API) to add mobile Lighthouse performance metrics (lab data, not field Core Web Vitals) for the first few pages.

**Can the accessibility scan tell me whether my site meets the EAA or BFSG?** No, and no automated tool can. The scan finds a part of the barriers automatically and lists what needs manual review. Whether and how accessibility laws apply to your organisation, and whether a site conforms, has to be assessed by qualified people, including manual and assistive-technology testing.

**Can I audit several sites in one run?** Yes. Add several domains, and each origin's `robots.txt` and sitemap are checked.

**Can I export to Excel or Google Sheets?** Yes. Use the dataset export buttons (CSV, XLSX, JSON) or the Apify integrations.

**Is it legal?** It only reads publicly available pages, the way a search engine does. The SEO checks collect no personal data; the optional accessibility scan can include short element snippets (max 250 characters), which you can switch off with `accessibilityIncludeHtmlSnippets: false`. Please audit only sites you own or have permission to audit, and respect their terms.

### Integrations and API

Run it from the Apify API, the JavaScript or Python clients, schedules, webhooks, Make, Zapier, n8n, or the Apify MCP server for AI agents. Every audit result is available as a dataset.

### Related tools

- [Google Trends Scraper](https://apify.com/batuweb/google-trends-scraper) – find which topics and keywords are rising before you write or optimize pages.

# Actor input Schema

## `startUrls` (type: `array`):

Domains (example.com) or full page URLs to audit. In sitemap mode the sitemap of each domain is discovered automatically.

## `crawlMode` (type: `string`):

Where the Actor gets the list of pages to audit.

## `maxPages` (type: `integer`):

Upper limit of pages audited (and billed) per run.

## `checkBrokenLinks` (type: `boolean`):

Request every internal link found on audited pages (up to the limit below) and report 4xx/5xx targets.

## `maxLinkChecks` (type: `integer`):

Caps extra requests made for broken-link checking.

## `detectTechStack` (type: `boolean`):

Detect CMS, frameworks, analytics and hosting (WordPress, Shopify, Next.js, Astro, GA, Cloudflare…).

## `pageSpeedApiKey` (type: `string`):

If provided, the mobile Lighthouse performance score and lab metrics (LCP, CLS, TBT) are fetched for the first pages. Free key: Google Cloud console → PageSpeed Insights API.

## `pageSpeedMaxPages` (type: `integer`):

How many audited pages get a PageSpeed test (each takes ~10–30 s).

## `maxConcurrency` (type: `integer`):

Parallel requests. Lower it for small or fragile servers.

## `ignoreRobotsTxt` (type: `boolean`):

By default the auditor respects robots.txt and skips disallowed URLs (they are listed in the summary). Turn this on ONLY for a site you own or are authorised to audit, e.g. to check a staging site that blocks all crawlers.

## `checkAccessibility` (type: `boolean`):

Also run an automated accessibility scan (axe-core rules for WCAG 2.0 A/AA, 2.1 A/AA and 2.2 AA; axe-core has no automated rules for the new WCAG 2.2 Level A criteria) on audited pages in a headless browser. Results are added to each page record as 'accessibility' and summarised in the summary record and the ACCESSIBILITY_REPORT key-value record. Automated testing finds only part of all barriers; items that need manual review are listed separately. Not a compliance certification or legal advice. Use 1024 MB memory or more when this is on.

## `accessibilityMaxPages` (type: `integer`):

How many of the audited pages get the browser scan (one page at a time, at least 1 s between pages on the same host, robots.txt Crawl-delay honoured up to 10 s).

## `accessibilityStandard` (type: `string`):

Which automated WCAG success-criteria rules to run.

## `accessibilityIncludeHtmlSnippets` (type: `boolean`):

Include a short HTML snippet (max 250 characters) of each affected element. Turn off to keep page content (which may contain personal data) out of the results; the CSS selector of the element is still reported.

## `accessibilityLanguage` (type: `string`):

Language of rule texts and of the ACCESSIBILITY_REPORT. The disclaimer is always included in English and German.

## Actor input object example

```json
{
  "startUrls": [
    "https://crawlee.dev"
  ],
  "crawlMode": "sitemap",
  "maxPages": 10,
  "checkBrokenLinks": true,
  "maxLinkChecks": 100,
  "detectTechStack": true,
  "pageSpeedMaxPages": 3,
  "maxConcurrency": 10,
  "ignoreRobotsTxt": false,
  "checkAccessibility": false,
  "accessibilityMaxPages": 10,
  "accessibilityStandard": "wcag22aa",
  "accessibilityIncludeHtmlSnippets": true,
  "accessibilityLanguage": "en"
}
```

# Actor output Schema

## `pages` (type: `string`):

Per-page SEO audit records with score, issues and extracted SEO data.

## `summary` (type: `string`):

JSON summary: site score, issues by severity, prioritized issues with example URLs, robots.txt and sitemap validation.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "startUrls": [
        "https://crawlee.dev"
    ],
    "maxPages": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("batuweb/seo-audit-toolkit").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "startUrls": ["https://crawlee.dev"],
    "maxPages": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("batuweb/seo-audit-toolkit").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "startUrls": [
    "https://crawlee.dev"
  ],
  "maxPages": 10
}' |
apify call batuweb/seo-audit-toolkit --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,batuweb/seo-audit-toolkit"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/Q4gkHpBdVqi0uElqI/builds/xYRhablG3zHcVcBap/openapi.json
