# SEO Audit & Broken Link Checker - Site Score 0-100 (`officina-tools/seo-audit-broken-link-checker`) Actor

Crawl any website and audit every page for technical SEO: titles, meta descriptions, H1, canonicals, noindex, alt text, HTTPS, speed, broken links and duplicates. Get a 0-100 score per page and per site, plus a shareable HTML report.

- **URL**: https://apify.com/officina-tools/seo-audit-broken-link-checker.md
- **Developed by:** [Officina Tools](https://apify.com/officina-tools) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $10.00 / 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.

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 & Broken Link Checker

Crawl any website and get a **technical SEO audit of every page** in seconds. You get a 0–100 score for each page and for the whole site, and a list of issues explained in plain English. It finds broken links and shows which pages link to them. It also checks the **XML sitemap** and **hreflang** tags, and gives you an **HTML report** ready to share.

No browser, no setup, no API key: paste a URL and press **Start**. **The first 5 pages of every run are free**, and 50 pages take about 10 seconds.

### What does SEO Audit & Broken Link Checker do?

It starts from your home page and follows internal links, as a search engine would. It reads robots.txt and the sitemap, then audits each page it finds. You get:

- a **site score** and a **score for every page**, so you know where to start;
- **every issue found on each page**, with severity (error, warning, notice) and a clear explanation, like *"Title is 107 characters (recommended 30-60)"*;
- **broken internal links** (and, if you want, external ones), each with the pages that link to it;
- **sitemap problems**: orphan pages, sitemap URLs that return errors or redirect, pages missing from the sitemap;
- **hreflang problems** on multilingual sites: invalid language codes, missing return links, alternates that are broken or not canonical;
- **duplicate titles and meta descriptions** across the site;
- a **REPORT.html** you can open in the browser or send to a client, plus a **SUMMARY** JSON for automations and AI agents.

### Who is it for?

- **SEO consultants and agencies**: run a first audit before a sales call, or check a client site every week.
- **Website owners and marketers**: find what to fix before it hurts rankings, without learning a desktop crawler.
- **Developers**: check a site after a migration or release. You can call it from CI, cron jobs or webhooks.
- **AI agents and automations**: one call returns a compact JSON summary an agent can reason about (via the Apify API or MCP).

### Why use it?

- **Fast and cheap.** It fetches pages over plain HTTP without a browser. 50 pages take about 10 seconds.
- **Checks that cheap audits usually skip.** It compares the sitemap with the crawl and checks hreflang return links, as well as the usual on-page checks.
- **Results you can act on.** A ranked list of top issues, the worst pages to fix first, and for every broken link the pages that link to it.
- **A report you can share.** A clean HTML report, ready to send to a client or your team.
- **Safe by design.** It respects `robots.txt`, stays on the site you give it and never logs in anywhere.

### How much does it cost?

**The first 5 pages of every run are free.** After that you pay **$0.01 per page audited**, plus a tiny $0.00005 fee per run. Platform usage is included: there is nothing else to pay.

| Pages audited | Cost |
| --- | --- |
| 5 | free (only the $0.00005 run fee) |
| 50 | $0.45 |
| 100 (default limit) | $0.95 |
| 500 | $4.95 |
| 1,000 | $9.95 |

The Apify free plan includes monthly platform credits, enough to audit a few hundred pages for free. **Maximum pages** caps the cost of each run. The run also stops cleanly at the maximum cost you set in Apify. Sitemap URLs the crawl did not reach are only status-checked, for free.

### How to run an SEO audit

1. Click **Try for free** (or **Start**). Set **Maximum pages** to 5 for a free first look.
2. Paste the website you want to audit, for example `https://example.com`.
3. Optionally set **Maximum pages** (default 100) and turn on **Check external links**.
4. Press **Start** and wait a few seconds.
5. Open **REPORT.html** in the Storage tab for the readable report, or the dataset for one row per page (export to Excel, CSV or JSON).

### Example: a real run

Audit of `https://crawlee.dev/` with **Maximum pages** set to 50:

- **Site score 87/100**, average page score 94, 50 pages audited in about 10 seconds.
- 1 error (`/blog` has no `<h1>`), 48 warnings, 28 notices.
- Top issues: titles too long on 47 pages (up to 107 characters), short meta descriptions on 16 pages, one page with 19 `<h1>` headings.
- Sitemap: 4,848 URLs read from the sitemap index, no errors. hreflang: present on all 50 pages, no missing return links.

One row of the dataset (shortened):

```json
{
  "url": "https://crawlee.dev/python/docs/quick-start",
  "statusCode": 200,
  "score": 94,
  "loadTimeMs": 47,
  "title": "Quick start | Crawlee for Python · Fast, reliable Python web crawlers.",
  "titleLength": 70,
  "metaDescriptionLength": 256,
  "h1Count": 1,
  "canonical": "https://crawlee.dev/python/docs/quick-start",
  "lang": "en",
  "hreflang": [
    { "lang": "en", "href": "https://crawlee.dev/python/docs/quick-start" },
    { "lang": "x-default", "href": "https://crawlee.dev/python/docs/quick-start" }
  ],
  "wordCount": 978,
  "imagesTotal": 12,
  "imagesMissingAlt": 0,
  "issueCounts": { "error": 0, "warning": 1, "notice": 1 },
  "issues": [
    { "code": "title-long", "severity": "warning", "message": "Title is 70 characters (recommended 30-60)." },
    { "code": "description-long", "severity": "notice", "message": "Meta description is 256 characters (recommended 70-160)." }
  ]
}
```

### What it checks

**On every page**

| Check | Severity |
| --- | --- |
| HTTP errors (4xx, 5xx, unreachable) | error |
| Missing `<title>`, or title too short / too long (30–60 characters) | error / warning |
| Missing `<h1>`, or more than one `<h1>` | error / warning |
| Page not served over HTTPS | error |
| Missing meta description, or too short / too long (70–160 characters) | warning / notice |
| `noindex` in meta robots | warning |
| Missing mobile viewport | warning |
| Images without `alt` text | warning |
| Mixed content (HTTP resources on an HTTPS page) | warning |
| Slow HTML response (over 3 seconds) | warning |
| Invalid hreflang codes (e.g. `en_US`, `en-UK`), the same language pointing to two URLs, no self-referencing hreflang, hreflang on a page whose canonical points elsewhere | warning |
| Missing canonical, or canonical pointing to another URL | notice |
| Missing `hreflang="x-default"`, self hreflang that contradicts `<html lang>` | notice |
| Missing `lang`, JSON-LD structured data, Open Graph tags | notice |
| Thin content (under 200 words), redirects, heavy HTML | notice |

**Across the site**

- Broken internal links, with up to 5 pages that link to each one
- Broken external links (optional, up to 300 checked)
- Duplicate titles and meta descriptions
- `robots.txt` and XML sitemap presence
- **Sitemap check** (reads sitemap indexes and `.xml.gz` files): orphan pages listed in the sitemap but not linked from any audited page, sitemap URLs that return errors or redirect, sitemap URLs marked `noindex`, and indexable pages missing from the sitemap
- **hreflang check**: alternate-language pages that do not link back (missing return links), and alternates that return errors, redirect, are not canonical or are marked `noindex`. Same-site alternates are audited even when no menu links to them
- Top issues ranked by severity and number of pages affected
- Worst pages to fix first

### How the score works

Each page starts at 100 and loses 15 points per error, 5 per warning and 1 per notice (minimum 0). The site score is the average page score, minus capped penalties for a missing sitemap or `robots.txt`, broken internal links, sitemap URLs that return errors, missing hreflang return links, and duplicate titles or descriptions.

### Input

| Field | Default | Description |
| --- | --- | --- |
| Website to audit | — | Start URL, usually the home page |
| Maximum pages to audit | 100 | Hard cap on pages, and so on cost |
| Check external links | off | Also report broken outbound links (free) |
| Include subdomains | off | Treat `blog.example.com` as the same site |
| Max parallel requests | 10 | Lower it for slow sites |

### Output

- **Dataset**: one row per page with its score, status, title, description, headings, canonical, hreflang, word count, load time, link counts and the full list of issues. Two views: *Pages* and *Issues* (one row per issue, easy to filter in a spreadsheet). Export to JSON, CSV, Excel or XML.
- **SUMMARY** (key-value store): site score, totals, top issues, broken links, sitemap and hreflang findings, duplicates and worst pages, as JSON. Ideal for AI agents and automations.
- **REPORT.html** (key-value store): a clean report you can open in the browser or send to a client.

### Integrations and API

Run it from your code, on a schedule, or from no-code tools:

- **Schedule** a weekly audit in Apify and get the results by email, Slack or webhook.
- **Make, Zapier, n8n**: start a run and send the SUMMARY wherever you need it.
- **API**: start a run and get the page rows in one call:

```bash
curl -X POST "https://api.apify.com/v2/acts/officina-tools~seo-audit-broken-link-checker/run-sync-get-dataset-items?token=<YOUR_APIFY_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"startUrls":[{"url":"https://example.com"}],"maxPages":50}'
```

```js
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('officina-tools/seo-audit-broken-link-checker').call({ startUrls: [{ url: 'https://example.com' }], maxPages: 50 });
const summary = await client.keyValueStore(run.defaultKeyValueStoreId).getRecord('SUMMARY');
console.log(summary.value.siteScore, summary.value.topIssues);
```

### Use it with AI agents (MCP)

Add this Actor to the Apify MCP server and your assistant (Claude, Cursor and others) can audit a site from a plain request like *"audit example.com and tell me the three most urgent fixes"*. The `SUMMARY` record gives the agent everything it needs in one read.

### FAQ

**Does it render JavaScript?**
No. It reads the HTML as the server sends it, which is fast and cheap, and close to what search engines see on their first pass. Sites that build all their content in the browser may show up as thin content.

**Is this the same as Lighthouse or Core Web Vitals?**
No. Load time here is the server response time for the HTML, measured from Apify's servers. Use it to find technical SEO problems, not to measure rendering speed.

**Why is my site score lower than the average page score?**
Site-wide problems lower it: a missing sitemap or `robots.txt`, broken internal links, sitemap URLs with errors, missing hreflang return links, and duplicate titles or descriptions. Each penalty is capped.

**Can I audit a big site?**
Yes, up to 10,000 pages per run. Start with 100 pages to see the main problems, since most issues repeat across page templates.

**Is it legal to run an SEO audit on any site?**
It only reads public pages, respects `robots.txt` and collects no personal data. Use it on your own sites, client sites, or sites you have permission to audit.

**Something looks wrong?**
Open an issue on the Issues tab with the URL and what you expected, and we will look into it.

### Good to know

- The crawler respects `robots.txt` and only follows links on the same site.
- Files such as PDFs and images are not audited as pages.
- Sitemap URLs the crawl did not reach are only status-checked (up to 200, free), not audited or charged as pages.

# Actor input Schema

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

The page where the audit starts, usually the home page. The crawler follows internal links from here. Only the first URL is used.

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

Stop after auditing this many pages. You pay per page audited, so this also caps the cost.

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

Also check links that point to other websites (up to 300) and report the broken ones. Free, but makes the run a little longer.

## `includeSubdomains` (type: `boolean`):

Treat subdomains (for example blog.example.com) as part of the same site.

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

How many pages to fetch at the same time. Lower it if the site is slow or rate-limits crawlers.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://crawlee.dev"
    }
  ],
  "maxPages": 100,
  "checkExternalLinks": false,
  "includeSubdomains": false,
  "maxConcurrency": 10
}
```

# Actor output Schema

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

One row per page: score, HTTP status, title, headings, word count, load time and the full list of issues.

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

Site score, issue totals, top issues, broken links, sitemap findings, duplicate titles and descriptions, worst pages.

## `report` (type: `string`):

A clean, shareable report of the whole audit.

# 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": [
        {
            "url": "https://crawlee.dev"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("officina-tools/seo-audit-broken-link-checker").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": [{ "url": "https://crawlee.dev" }] }

# Run the Actor and wait for it to finish
run = client.actor("officina-tools/seo-audit-broken-link-checker").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": [
    {
      "url": "https://crawlee.dev"
    }
  ]
}' |
apify call officina-tools/seo-audit-broken-link-checker --silent --output-dataset

```

## MCP server setup

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

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/ZkIdbN70dIADNEeYA/builds/HNrRLBNWaNICfuMql/openapi.json
