# Website SEO Audit Tool - On-Page, Broken Links & AI Bots (`kantolabs/website-seo-audit`) Actor

Bulk on-page SEO audit with a 0-100 score and a prioritized fix list per page: titles, meta descriptions, canonicals, noindex, headings, alt text, schema, Open Graph, hreflang, HTTPS, broken links, robots.txt, sitemap, llms.txt and blocked AI crawlers. $4 per 1,000 pages.

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

## Pricing

from $4.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

## Website SEO Audit Tool - 0-100 score and a prioritized fix list for every page

**Audit any list of pages, or crawl whole websites, and get a 0-100 SEO score plus the exact issues to
fix on each page** - titles, meta descriptions, canonicals, noindex, headings, alt text, structured data,
Open Graph, hreflang, HTTPS, broken links, robots.txt and sitemaps - in bulk, as JSON, CSV or Excel.

- **$4 per 1,000 audited pages** ($0.004 each) - broken-link checking included, no subscription
- **Pages that cannot be fetched are free** - you pay only for pages that were actually audited
- **AI search readiness** - see which AI crawlers (GPTBot, ClaudeBot, PerplexityBot, Google-Extended, CCBot and more) your robots.txt blocks, and whether you publish an `llms.txt`
- **Fix list, not just a score** - every issue is `{severity, code, message}`, sorted most important first

### What does Website SEO Audit do?

For every URL you give it, the actor loads the page the way a search-engine crawler does, checks it
against the on-page SEO rules below and returns one row per page with a **score**, an **issue list**
and all the raw data behind it. With *Max pages per site* above 1 it also **crawls internal links**
(breadth-first, robots.txt respected) and audits those pages too.

**Per page**

| Area | Checks |
|---|---|
| Status | HTTP status, redirect chain, response time, page size |
| Title & description | Missing, too short, too long, more than one `<title>` on the page |
| Indexability | `noindex` in meta robots or `X-Robots-Tag`, canonical missing, pointing elsewhere, or declared more than once |
| Content | H1 missing or multiple, heading counts H1-H6, visible word count (thin content) |
| Images | Images without `alt` text, with sample URLs |
| Links | Internal / external / nofollow counts, optional **broken link check** (4xx, 5xx, unreachable) |
| Rich results | JSON-LD and microdata types found, invalid JSON-LD blocks |
| Social | Open Graph title/description/image, Twitter card |
| International | `hreflang` alternates |
| Mobile & basics | Viewport meta, `lang` attribute |
| Security | HTTPS, HSTS header, mixed `http://` resources |

**Per site** (once per host): `robots.txt` present, Googlebot allowed, sitemaps declared or found at
`/sitemap.xml`, `llms.txt` present, and the list of **AI bots blocked vs allowed**.

### Use cases

- **SEO agencies and freelancers** - audit a prospect's site in seconds and send the issue list as a spreadsheet.
- **Site owners** - schedule a weekly run and get alerted when a page turns `noindex` or links break.
- **Developers** - add an SEO check to CI before a release, or monitor many client sites from one API call.
- **Content teams** - find pages with missing or overlong titles and descriptions, thin content or missing alt text.
- **AI search visibility (GEO)** - check whether ChatGPT, Claude, Perplexity and Google's AI crawlers are allowed to read your content.
- **Migrations** - audit the old and new site and compare scores, canonicals and redirect chains.

### How to use it

1. Click **Try for free**.
2. Paste your pages or domains into **Pages or websites to audit**, one per line.
3. Leave *Max pages per site* at `1` to audit only those URLs, or raise it (e.g. `50`) to crawl each site.
4. Optionally switch on **Check links for 404s**.
5. Click **Start** and download the results as JSON, CSV, Excel or HTML, or read them through the API.

#### Input example

```json
{
    "urls": ["https://www.python.org", "https://wordpress.org/news/"],
    "maxPagesPerSite": 3,
    "checkBrokenLinks": true,
    "maxLinksToCheckPerPage": 25
}
```

| Field | What it does | Default |
|---|---|---|
| `urls` | Pages or domains to audit, one per line. Strings or `{"url": "..."}` objects both work. | two example sites |
| `maxPagesPerSite` | `1` = only the URLs you list; higher = crawl internal links (breadth-first, robots.txt respected) | `1` |
| `checkBrokenLinks` | Request every link on each page and report the broken ones | `false` |
| `maxLinksToCheckPerPage` | Cap on link checks per page | `100` |
| `maxConcurrency` | Pages fetched in parallel | `5` |
| `requestTimeoutSecs` | Per-page timeout (pages that time out are not charged) | `30` |
| `proxyConfiguration` | Optional Apify Proxy | off |

### Output example

One dataset item per audited page. Real output from a cloud run of this actor (a few fields and
lists shortened):

```json
{
    "url": "https://www.python.org/",
    "finalUrl": "https://www.python.org/",
    "statusCode": 200,
    "redirectChain": [],
    "pageSizeKb": 51.3,
    "title": "Welcome to Python.org",
    "titleLength": 21,
    "metaDescription": "The official home of the Python Programming Language",
    "metaDescriptionLength": 52,
    "canonical": null,
    "indexable": true,
    "lang": "en",
    "hasViewport": true,
    "h1": ["Intuitive Interpretation", "Compound Data Types", "All the Flow You’d Expect", "Functions Defined", "Quick & Easy to Learn"],
    "headingCounts": { "h1": 5, "h2": 9, "h3": 0, "h4": 0, "h5": 0, "h6": 0 },
    "wordCount": 984,
    "imageCount": 1,
    "imagesMissingAlt": 0,
    "internalLinkCount": 126,
    "externalLinkCount": 78,
    "openGraph": { "title": "Welcome to Python.org", "image": "https://www.python.org/static/opengraph-icon-200x200.png", "type": "website" },
    "structuredDataTypes": ["SearchAction", "WebSite"],
    "https": true,
    "hsts": true,
    "mixedContentCount": 1,
    "score": 86,
    "issueCount": 4,
    "issues": [
        { "severity": "medium", "code": "canonical-missing", "message": "No canonical link." },
        { "severity": "medium", "code": "mixed-content", "message": "1 resource/link URL(s) use http:// on an https page." },
        { "severity": "low", "code": "meta-description-short", "message": "Meta description is 52 characters; 120-160 is the usual target." },
        { "severity": "low", "code": "h1-multiple", "message": "5 <h1> headings on one page." }
    ],
    "site": {
        "robotsTxtFound": true,
        "googlebotAllowed": true,
        "sitemapFound": false,
        "llmsTxtFound": false,
        "aiBotsBlocked": [],
        "aiBotsAllowed": ["GPTBot", "ChatGPT-User", "OAI-SearchBot", "ClaudeBot", "Claude-User", "PerplexityBot",
                          "Google-Extended", "CCBot", "Applebot-Extended", "Bytespider", "meta-externalagent"],
        "siteIssues": [
            { "severity": "medium", "code": "sitemap-missing", "message": "No XML sitemap found in robots.txt or at /sitemap.xml." },
            { "severity": "low", "code": "llms-txt-missing", "message": "No /llms.txt for AI assistants (optional, emerging standard)." }
        ]
    },
    "error": null
}
```

With **Check links for 404s** on, each row also gets `linksChecked` and `brokenLinks`
(`[{"url": ..., "status": 404}]`), and a `broken-links` issue when any are found.

**How the score works**: it starts at 100 and subtracts 25 per critical, 10 per high, 5 per medium and
2 per low issue on that page (broken links subtract 10). Site-level findings are reported in
`site.siteIssues` and do not change page scores. The formula is fixed, so scores are comparable across
runs and sites.

### How much does it cost?

**$4 per 1,000 audited pages** ($0.004 per page), pay-per-event, broken-link checking included.
Apify platform compute is included. There is also a one-off start fee of $0.00005 per GB of run memory
(the default 1 GB run = $0.00005 per run).

| You audit | You pay |
|---|---|
| 1 homepage | $0.004 |
| A 50-page crawl of one site | $0.20 |
| 500 client pages every month | $2.00 per month |
| 10,000 pages | $40.00 |

**What is free:** pages that cannot be fetched at all - DNS failure, timeout, refused connection,
TLS error - are returned with an `error` message and cost nothing. A page that answers (including a
404 or 500, which is itself an SEO finding) is audited and charged once. A URL that redirects to a page
already audited in the same run is not charged twice.

**Cost cap:** set *Maximum cost per run* in the run options; the actor stops cleanly when it is
reached, so a crawl can never cost more than you decided. Apify's free plan includes monthly platform
credit you can use to try it.

### How it compares

| | This actor | Desktop crawlers | Lighthouse / page-speed tools |
|---|---|---|---|
| Runs in the cloud, on a schedule, via API | Yes | No (runs on your computer) | Per page |
| Bulk lists and whole-site crawls | Yes | Yes | One page at a time |
| On-page SEO issues with severity | Yes | Yes | Partly (SEO category) |
| AI crawler blocking + `llms.txt` | Yes | Varies | No |
| Performance lab metrics (Core Web Vitals) | No | Varies | Yes |
| Renders JavaScript | No | Optional | Yes |
| Output | JSON, CSV, Excel, API | Export files | Report |

Pick this actor for **fast, cheap, repeatable on-page and crawlability audits at scale**. For
performance lab metrics use a Lighthouse-style tool alongside it.

### Integrations and API

- **API**: `POST https://api.apify.com/v2/acts/kantolabs~website-seo-audit/run-sync-get-dataset-items?token=YOUR_TOKEN`
  with the input JSON as the body returns the audit in one call.
- **Schedules and alerts**: run weekly from Apify Schedules and send the results to Slack, email or a webhook.
- **Google Sheets, Make, Zapier, n8n**: standard Apify integrations.
- **AI agents (MCP)**: callable from Claude, ChatGPT, Cursor or any MCP client through the Apify MCP server.

### FAQ

**Does it render JavaScript?**
No - it reads the HTML the server sends, which is what most on-page SEO checks are based on. Sites
that build their entire content in the browser will show low word counts; that is itself a real SEO
risk worth knowing about.

**Does it respect robots.txt?**
Yes, for every page it discovers while crawling. URLs you list yourself are always audited.

**Do I pay for pages that fail?**
No. Pages that cannot be fetched (DNS error, timeout, refused connection) are listed with an `error`
and not charged.

**Is it the same as Lighthouse?**
No. This is an on-page and crawlability audit, not a performance lab test; it is much faster per page
and adds checks Lighthouse does not run, such as broken-link checking, sitemap discovery and AI crawler access.

**Which AI crawlers are checked?**
GPTBot, ChatGPT-User, OAI-SearchBot, ClaudeBot, Claude-User, PerplexityBot, Google-Extended, CCBot,
Applebot-Extended, Bytespider and meta-externalagent.

**Can I pass `{"url": "..."}` objects instead of strings?**
Yes. Both shapes are accepted in `urls`.

**Something looks wrong?**
Open an issue on the **Issues** tab with the URL and what you expected.

# Actor input Schema

## `urls` (type: `array`):

One URL or domain per line. Each one is audited; with 'Max pages per site' above 1 the actor also follows internal links from it.

## `maxPagesPerSite` (type: `integer`):

1 audits only the URLs you list. Higher values crawl internal links (breadth-first, robots.txt respected) up to this many pages per start URL.

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

Request every link on each audited page and report the broken ones (4xx/5xx/unreachable).

## `maxLinksToCheckPerPage` (type: `integer`):

Upper bound on link checks per page when broken-link checking is on.

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

Pages fetched in parallel across all sites.

## `requestTimeoutSecs` (type: `integer`):

Pages that do not load in time are reported as failed and not charged.

## `proxyConfiguration` (type: `object`):

Optional. Use Apify Proxy if a site blocks datacenter traffic.

## Actor input object example

```json
{
  "urls": [
    "https://www.python.org",
    "https://wordpress.org/news/"
  ],
  "maxPagesPerSite": 1,
  "checkBrokenLinks": false,
  "maxLinksToCheckPerPage": 100,
  "maxConcurrency": 5,
  "requestTimeoutSecs": 30,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `results` (type: `string`):

No description

# 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 = {
    "urls": [
        "https://www.python.org",
        "https://wordpress.org/news/"
    ],
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("kantolabs/website-seo-audit").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 = {
    "urls": [
        "https://www.python.org",
        "https://wordpress.org/news/",
    ],
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("kantolabs/website-seo-audit").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 '{
  "urls": [
    "https://www.python.org",
    "https://wordpress.org/news/"
  ],
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call kantolabs/website-seo-audit --silent --output-dataset

```

## MCP server setup

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

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/HaQ8cevScPx9KGlGf/builds/UkrwPsN9QPShb5jXY/openapi.json
