# SEO Audit Crawler — Broken Links, Duplicates & 0-100 Score (`northbell/seo-audit-crawler`) Actor

Crawl a website and audit every page for SEO: titles, meta descriptions, headings, canonical, noindex, alt text, viewport, Open Graph, structured data, speed, broken internal links and duplicate titles, with a 0-100 score. Rerun to see new and fixed issues. $2 per 1,000 pages.

- **URL**: https://apify.com/northbell/seo-audit-crawler.md
- **Developed by:** [Northbell](https://apify.com/northbell) (community)
- **Categories:** SEO tools, Developer tools
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.60 / 1,000 audited pages

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 Crawler — Broken Links, Duplicates & 0-100 Score

Give it a **website** and it crawls the pages (sitemap first, then links) and audits each one for SEO: **title, meta description, H1, canonical, noindex, alt text, viewport, Open Graph, structured data, word count, response time** — plus the site-level problems a single-page checker cannot see: **broken internal links (with the pages that link to them), redirects, duplicate titles and duplicate descriptions** — and, if you turn it on, **broken links to other websites** (404s, server errors, dead domains). Every page gets a **0-100 score** and a list of issues in plain words. Schedule it and turn on **Only pages with new issues** to get back only what broke since the last run.

**Try it now. The input is already filled in.** Press **Start**: 10 pages of `docs.apify.com/academy` — **10 pages audited in about 2 seconds, for $0.03** (measured on 28 Oct 2026), average score 90. Three of the rows:

| `url` | `score` | `issues` |
| --- | --- | --- |
| https://docs.apify.com/academy | 95 | images-missing-alt |
| https://docs.apify.com/sdk | 77 | missing-description, images-missing-alt, duplicate-title |
| https://docs.apify.com/open-source | 82 | missing-description, duplicate-title |

A free `summary` row closes the run: pages found (736 on that site), issue counts and which pages share a title.

### What you get

| Row | Fields |
|---|---|
| `page` | `url`, `statusCode`, `score`, `issues`, `issueDetails` (level and explanation), `newIssues`, `title`, `titleLength`, `description`, `descriptionLength`, `h1`, `h1Count`, `h2Count`, `canonicalUrl`, `canonicalIsSelf`, `robotsMeta`, `xRobotsTag`, `noindex`, `lang`, `hreflangCount`, `viewport`, `openGraph`, `wordCount`, `imageCount`, `imagesMissingAlt`, `internalLinkCount`, `externalLinkCount`, `structuredDataTypes`, `brokenLinks`, `brokenExternalLinks` (with *Check links to other websites*), `sameTitleAs`, `sameDescriptionAs`, `redirectedFrom`, `responseMs`, `htmlBytes`, `depth` |
| `page` (error status) | `url`, `statusCode` (404, 410, 5xx), `score: 0`, `linkedFrom` — the pages that link to it. Free |
| `summary` | one per website: `pagesFound`, `pagesAudited`, `averageScore`, `brokenPages`, `redirects`, `brokenExternalLinkCount` and `brokenExternalLinks` (each with `linkedFrom`), `issueCounts`, `duplicateTitles`, `newIssues`, `fixedIssues`, `notAudited`, `skippedByRobots`. Free |
| `error` | `url`, `message` — free |

Issues checked: `broken-page`, `noindex`, `missing-title`, `missing-h1`, `missing-description`, `canonical-elsewhere`, `duplicate-title`, `duplicate-description`, `links-to-broken-page`, `links-to-broken-external-page`, `thin-content` (under 200 words), `title-too-long` / `-short`, `description-too-long` / `-short`, `multiple-h1`, `images-missing-alt`, `missing-lang`, `missing-viewport`, `missing-open-graph`, `slow-response` (over 2 s), `large-html` (over 1 MB), `redirected`. The score starts at 100 and loses points per issue (a page that answers with an error scores 0).

### Use it for

- **Client SEO audits**: a full page-by-page sheet in minutes, ready to sort by score or filter by issue
- **Monitoring your own site**: schedule it weekly with *Only pages with new issues*: a new 404, a page that started saying `noindex`, a template that lost its H1
- **Before and after a migration or redesign**: `fixedIssues` and `newIssues` in the summary show what changed
- **Broken link checks**: internal and external links, with the exact pages to fix
- **Content cleanup**: duplicate titles and descriptions, thin pages, images without alt text

### Input

```json
{ "startUrls": ["https://docs.apify.com/academy"], "maxPages": 100, "scope": "path", "onlyNewIssues": false }
```

Measured on 23 October 2026: 15 pages of docs.apify.com/academy audited in 2.7 seconds (average score 98; 7 descriptions too long, 2 titles too long, 1 page with images missing alt text).

### How it crawls

- Each website's **robots.txt is read first** and its rules are followed (those for all robots and those addressed to Claude crawlers); if robots.txt cannot be read (server error), the site is skipped with a free error row. Private network addresses are not read.
- Pages are fetched over plain HTTP, 4 at a time, without a browser — fast and cheap, but pages that only show their content after JavaScript runs are audited as the server sends them (which is also what many crawlers see first).
- Pages that answer 401, 403 or 429 come back as free error rows, not as broken pages.
- *Check links to other websites* checks each external address once after the crawl (HEAD, or GET when HEAD is not accepted), after reading that site's robots.txt. 404, 410, 5xx and domains that no longer exist count as broken; 401, 403 and 429 mean the site refused an automated check and are not counted as broken; links its robots.txt does not allow are listed as not checked.
- *Only pages with new issues* compares with this Actor's last run on the same start URL.

### Pricing

**$2 per 1,000 audited pages** ($0.002 each), plus $0.01 per run. Error-status page rows, the summary and error rows are free; with *Only pages with new issues*, pages without new issues are free too. The $0.01 run-start fee is the one charge every run pays, even one that returns nothing.

### For AI agents

`{"startUrls": ["https://example.com"], "maxPages": 50}` returns one `page` row per page with `score` and `issues`, and one `summary` row with `averageScore` and `issueCounts`. Rows have `type`: `page`, `summary` or `error`.

# Actor input Schema

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

One website or start page per line (up to 20). The crawl reads robots.txt, then the sitemap, then follows links on the same site.

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

Pages audited per website. Pages that answer with an error (404, 5xx) are listed on top of this, free.

## `scope` (type: `string`):

The whole site (www. and the bare domain count as one), or only pages under the start URL's folder.

## `maxDepth` (type: `integer`):

How many clicks away from the start page the crawl follows links. Sitemap pages count as depth 1.

## `useSitemap` (type: `boolean`):

Read sitemap.xml (and the sitemaps named in robots.txt) to find pages, not only links.

## `includeGlobs` (type: `array`):

Glob patterns such as https://example.com/blog/\*\*. Empty = every page in scope.

## `excludeGlobs` (type: `array`):

Glob patterns to leave out, such as **/tag/**.

## `onlyNewIssues` (type: `boolean`):

Compare with this Actor's last run on the same website and return only the pages that have an issue they did not have before (the summary row always comes, with new and fixed counts). The first run returns every page.

## `checkExternalLinks` (type: `boolean`):

Also check every link that points to another website (once per address, after the crawl): 404s, server errors and domains that no longer exist are reported as broken external links, with the pages that link to them. Sites that refuse automated checks (401, 403, 429) are not counted as broken. Each linked site's robots.txt is read first. Included in the page price.

## `maxExternalLinks` (type: `integer`):

Different external addresses checked per website. Links beyond this are counted as not checked.

## Actor input object example

```json
{
  "startUrls": [
    "https://docs.apify.com/academy"
  ],
  "maxPages": 10,
  "scope": "domain",
  "maxDepth": 5,
  "useSitemap": true,
  "onlyNewIssues": false,
  "checkExternalLinks": false,
  "maxExternalLinks": 500
}
```

# Actor output Schema

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

Score, issues, title, description, headings, canonical, broken links and speed of every page.

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

Average score, issue counts, broken pages, duplicate titles, new and fixed issues.

# 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://docs.apify.com/academy"
    ],
    "maxPages": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("northbell/seo-audit-crawler").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://docs.apify.com/academy"],
    "maxPages": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("northbell/seo-audit-crawler").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://docs.apify.com/academy"
  ],
  "maxPages": 10
}' |
apify call northbell/seo-audit-crawler --silent --output-dataset

```

## MCP server setup

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

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/2IF4VsrQp2eYRzCMG/builds/VgUs61cTX9msbgaM5/openapi.json
