# SEO Audit Tool: letter grade for every URL (`bepolabs/seo-audit`) Actor

Audit up to 200 website pages a run: a 0 to 100 score, technical, content and performance subscores, Lighthouse and Google's Core Web Vitals. Costs 0.032 USD per audited page; unreadable pages cost nothing.

- **URL**: https://apify.com/bepolabs/seo-audit.md
- **Developed by:** [BEPO Labs](https://apify.com/bepolabs) (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

$32.00 / 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.

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

### 🩺 What is SEO Audit?

Paste a list of addresses and SEO Audit reads each one like a consultant would. Every website gets a report that puts the most damaging fixes on top, covering titles, headings, images, canonicals and load times. Schedule it weekly and watch what changes.

- **Agencies and freelancers**: audit a client's key pages in one run and send the ranked fix list as a report
- **Site owners and marketers**: check every landing page before a launch and after each release
- **SEO monitoring**: schedule weekly audits and get alerted when a score drops or links break
- **Lead generation for SEO services**: score prospects' homepages and open the conversation with their top three issues
- **AI agents**: let Claude or ChatGPT audit a page through MCP and explain the fixes

![SEO Audit: Any page to a 0 to 100 SEO score and grade, with the fixes that matter most first: on-page, links and Core Web Vitals.](https://api.apify.com/v2/key-value-stores/wdeAWGooHdIhvFgSY/records/seo-audit-hero.png)

#### ✨ Why use this Actor?

- ✅ **One number, clear priorities**: score, grade and four group scores, with issues sorted errors first, then by weight
- ✅ **On-page, links and speed in one run**: 19 checks from title length to unreachable targets and Core Web Vitals
- ✅ **Real-user Core Web Vitals**: Chrome user data first, lab values where it is missing, judged by Google's thresholds
- ✅ **Fair scores**: checks that did not run (links off, no PageSpeed key) are left out of the score instead of counting as failed
- ✅ **Polite crawling**: robots.txt respected for the page and every link, retries with backoff on 429 and 5xx
- ✅ **Pay only for audited pages**: unreadable ones get an error row and cost nothing

### 📦 What data can you get with SEO Audit?

| | | |
|---|---|---|
| 💯 SEO score (0 to 100) | 🅰️ Grade (A to F) | 📊 Technical, content, links, performance scores |
| 🛠️ Issues to fix, ranked | 📰 Title and meta description | 🔠 H1 and heading outline |
| 🔗 Canonical URL | 📝 Word count | 🖼️ Images and missing alt text |
| 💔 Broken and redirected links | ⚡ Lighthouse performance, LCP, CLS, INP | ✅ Every check with pass, warning or error |

> What is checked: HTTP status, HTTPS, noindex (meta robots and X-Robots-Tag), canonical, html lang, mobile viewport, title (one, 30 to 60 characters), description (70 to 160), exactly one H1, heading order, thin content (under 300 words), alt text, Open Graph tags, broken or redirected links, Lighthouse performance, LCP, CLS and INP.

### 🚀 How to use SEO Audit

SEO Audit works in a few clicks, no coding needed:

1. [Create a free Apify account](https://console.apify.com/sign-up) (the free plan includes 5 USD of monthly credit).
2. Open [SEO Audit](https://apify.com/bepolabs/seo-audit) and click **Try for free**.
3. Paste the pages you want audited, up to 200. Leave link checks and Core Web Vitals on for the full report.
4. Click **Start** and wait for the run to finish (most runs take seconds).
5. Download your data as JSON, CSV, Excel, XML or HTML, or send it straight to Google Sheets, Make, Zapier or your AI agent.

![How SEO Audit works](https://api.apify.com/v2/key-value-stores/wdeAWGooHdIhvFgSY/records/seo-audit-workflow.png)

### ⬇️ Input

Add the pages to audit; everything else is optional and has sensible defaults.

##### Link checks

On by default, `checkLinks` tests up to `maxLinksPerPage` links and images of each page (100 unless changed, up to 500) for 4xx, 5xx, unreachable and redirected targets. Turn it off to skip this step; the group is then left out of the score.

##### Core Web Vitals

On by default, `coreWebVitals` measures each page with Google PageSpeed Insights on the `strategy` device (mobile or desktop) and adds 10 to 30 seconds to its audit. Your own free API key for it goes in `apiKey`.

##### How the score works

Every check is a pass (full points), a warning (half) or an error (none), weighted by importance. Grades: A from 90, B from 80, C from 65, D from 50, F below.

| Field | Type | What it does | Default |
|---|---|---|---|
| **Pages** (`urls`) | array | Pages to audit, one report each. | `["https://apify.com/"]` |
| **Check links** (`checkLinks`) | boolean | Check every link and image on the page for broken (4xx, 5xx, unreachable) and redirected targets. | `true` |
| **Links checked per page** (`maxLinksPerPage`) | integer | At most this many links per page are checked (first on the page first). | `100` |
| **Core Web Vitals** (`coreWebVitals`) | boolean | Measure Lighthouse performance, LCP, CLS and INP with Google PageSpeed Insights (adds 10 to 30 seconds per page). | `true` |
| **Device** (`strategy`) | string | Device profile for Core Web Vitals. | `"mobile"` |
| **PageSpeed Insights API key** (`apiKey`) | string | Your own free Google API key with the PageSpeed Insights API enabled. |  |
| **Maximum retries** (`maxRetries`) | integer | Retries per request with exponential backoff on network errors, 429 and 5xx. | `2` |

![SEO Audit input form](https://api.apify.com/v2/key-value-stores/wdeAWGooHdIhvFgSY/records/seo-audit-input.png)

#### Input example

```json
{
    "urls": [
        "https://example.com/"
    ]
}
```

### ⬆️ Output

Every page becomes one row with its score, grade, group scores, ranked issues, on-page facts, link statuses, Core Web Vitals and every check with its result. A run summary in the key-value store (`OUTPUT`) counts the pages audited and lists any that failed.

![SEO Audit output table](https://api.apify.com/v2/key-value-stores/wdeAWGooHdIhvFgSY/records/seo-audit-output.png)

#### Output example

One row of the dataset (python.org, mobile; lists shortened):

```json
{
    "url": "https://www.python.org/",
    "finalUrl": "https://www.python.org/",
    "score": 85,
    "grade": "B",
    "groups": {
        "technical": 96,
        "content": 68,
        "links": 100,
        "performance": 83
    },
    "issues": [
        {
            "id": "title",
            "severity": "warning",
            "message": "Title is 21 characters (30 to 60 is best)"
        },
        {
            "id": "metaDescription",
            "severity": "warning",
            "message": "Meta description is 52 characters (70 to 160 is best)"
        },
        {
            "id": "h1",
            "severity": "warning",
            "message": "5 H1 headings"
        },
        {
            "id": "performance",
            "severity": "warning",
            "message": "Lighthouse performance 78"
        },
        {
            "id": "canonical",
            "severity": "warning",
            "message": "No canonical link"
        }
    ],
    "title": "Welcome to Python.org",
    "metaDescription": "The official home of the Python Programming Language",
    "h1": [
        "Functions Defined",
        "All the Flow You’d Expect",
        "Compound Data Types",
        "Intuitive Interpretation",
        "Quick & Easy to Learn"
    ],
    "headings": [
        {
            "level": 1,
            "text": "Functions Defined"
        },
        {
            "level": 1,
            "text": "All the Flow You’d Expect"
        }
    ],
    "canonical": null,
    "words": 877,
    "images": 1,
    "imagesWithoutAlt": 0,
    "links": {
        "checked": 50,
        "broken": [],
        "redirected": 7,
        "skipped": 2
    },
    "coreWebVitals": {
        "score": 78,
        "lcpMs": 1266,
        "cls": 0,
        "inpMs": 88,
        "source": "field (url)",
        "opportunities": [
            {
                "id": "unused-css-rules",
                "title": "Reduce unused CSS",
                "savingsMs": 600
            },
            {
                "id": "unused-javascript",
                "title": "Reduce unused JavaScript",
                "savingsMs": 300
            },
            {
                "id": "unminified-css",
                "title": "Minify CSS",
                "savingsMs": 150
            }
        ]
    },
    "coreWebVitalsNote": null,
    "checks": [
        {
            "id": "status",
            "group": "technical",
            "status": "pass",
            "message": "Page answers 200"
        },
        {
            "id": "https",
            "group": "technical",
            "status": "pass",
            "message": "Served over HTTPS"
        },
        {
            "id": "indexable",
            "group": "technical",
            "status": "pass",
            "message": "Page may be indexed"
        }
    ],
    "auditedAt": "2026-10-16T06:21:07.000Z",
    "error": null
}
```

#### Broken links in the report

When a link is broken, `links.broken` names each one with its status (iana.org/help/example-domains in our test run, 3 of 26 checked):

```json
{
    "checked": 26,
    "broken": [
        {
            "url": "https://www.icann.org/",
            "status": 403,
            "result": "broken"
        },
        {
            "url": "https://www.icann.org/privacy/policy",
            "status": null,
            "result": "broken: Timed out after 30 s without an answer (gave up after 3 attempts)"
        }
    ],
    "redirected": 2,
    "skipped": 0
}
```

View the results as a table or JSON in the Output tab and download them as JSON, CSV, Excel, XML, HTML or RSS, all fields or only those you pick.

#### ⚠️ Error items

An input that cannot be read does not stop the run and is never charged: it gets a row with an `error` field so you can see exactly what failed.

| Error | Meaning |
|---|---|
| `HTTP 404` | The page answered with an error status (any 4xx or 5xx) |
| `Not an HTML page` | The URL is a PDF, image or other file, not a web page |
| `blocked by robots.txt` | The site's robots.txt does not allow the page to be read |

### 💰 How much does SEO Audit cost?

SEO Audit is priced **per event**: **32 USD per 1,000 audits** (0.032 USD per audit), with no monthly rental. Each audited page is charged once; unreadable ones get an error row and cost nothing. Link checks and Core Web Vitals are included in the price.

| You get | You pay |
|---|---|
| 100 audits | 3.2 USD |
| 1,000 audits | 32 USD |
| 10,000 audits | 320 USD |
| 100,000 audits | 3,200 USD |

💡 Apify's free plan gives you 5 USD of credit every month, enough for about **156 audits** at no cost. Set a spending limit per run and the Actor stops there.

### ⚖️ SEO Audit vs. the alternatives

| | SEO Audit | Free browser SEO checkers | SEO suite subscriptions |
|---|---|---|---|
| **Score, grade and ranked fixes per page** | ✅ | ✅ | ✅ |
| **Broken link check on every page** | ✅ up to 500 links | often a separate tool | ✅ |
| **Real-user Core Web Vitals** | ✅ | often lab only | ✅ |
| **Bulk, scheduled, API and MCP** | ✅ up to 200 pages a run | ❌ one page at a time | ✅ |
| **Pay per page, no subscription** | ✅ 0.032 USD per page | ✅ free | ❌ monthly plans |

### 🔌 Integrations, API and MCP

#### Connect SEO Audit to your AI agent (MCP)

Give Claude, ChatGPT, Cursor or any MCP client direct access to SEO Audit through the [Apify MCP server](https://mcp.apify.com). Add this URL to its settings:

```text
https://mcp.apify.com/?actors=bepolabs/seo-audit
```

For Claude Desktop, add it under `mcpServers` in `claude_desktop_config.json`:

```json
{
    "mcpServers": {
        "seo-audit": {
            "command": "npx",
            "args": [
                "-y",
                "@apify/actors-mcp-server",
                "--actors",
                "bepolabs/seo-audit"
            ],
            "env": {
                "APIFY_TOKEN": "<your Apify API token>"
            }
        }
    }
}
```

Then ask: *"Run an SEO audit of https://www.python.org/ and tell me the three fixes that would raise its score the most."*

#### Run it from your code

Python (`pip install apify-client`):

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("bepolabs/seo-audit").call(run_input={"urls":["https://example.com/"]})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)
```

JavaScript (`npm install apify-client`):

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

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('bepolabs/seo-audit').call({"urls":["https://example.com/"]});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

#### No-code integrations

Send results to **Google Sheets, Slack, Airtable, HubSpot, Make, Zapier, n8n** and more from the Integrations tab, start runs on a **schedule**, or trigger your own endpoint with a **webhook** when a run finishes. The [Apify API](https://docs.apify.com/api/v2) gives you full control over runs and datasets.

### ❓ FAQ

##### What does the score mean?

It is the weighted share of checks the page passes: a pass counts fully, a warning half, an error not at all. 90 or more is an A, 80 a B, 65 a C, 50 a D, anything lower an F.

##### Why is the performance score empty?

Core Web Vitals need a Google PageSpeed Insights key (the Actor's own or yours in `apiKey`). Without one, or if the measurement fails, the other three groups are scored and `coreWebVitalsNote` says why.

##### Can it audit a whole website?

It audits the pages you list, up to 200 per run. To audit a whole site, get its URLs first, for example from its sitemap, and pass them in.

##### Mobile or desktop?

Core Web Vitals are measured for the device in `strategy` (mobile by default, as Google indexes mobile-first). Run twice to compare both.

##### How long does a run take?

Usually a few seconds per page for on-page and link checks, plus 10 to 30 more with Core Web Vitals. A target that never answers adds up to half a minute. Three pages are audited at a time.

##### Can I monitor my pages every week?

Yes: add a schedule in Apify and connect Slack, e-mail, Google Sheets or a webhook to get each new report.

##### Is it legal to use SEO Audit?

Yes, for your own sites and those of your clients. The Actor reads public pages the way a browser does, with its own user agent, one request per page and link, and obeys each site's robots.txt. Core Web Vitals come from the official PageSpeed Insights API under the Google APIs Terms of Service. No personal data is collected.

##### What if something is missing or does not work?

Open an issue on the **Issues** tab; we read each one and reply. Feature requests are welcome too.

### 💬 Your feedback

Tell us what SEO Audit should add on the **Issues** tab or leave a review; your notes decide what we work on next.

### 🔗 Related Actors

| Actor | What it does |
|---|---|
| 🔗 [Broken Link Checker](https://apify.com/bepolabs/link-checker) | Every link and image on your pages checked: HTTP status, each redirect hop, final URL and response time. |
| ⚡ [Bulk PageSpeed Insights](https://apify.com/bepolabs/pagespeed) | Lighthouse scores, lab metrics and real-user Core Web Vitals for any list of URLs, on mobile, desktop or both. |
| 🗺️ [Sitemap URL Extractor](https://apify.com/bepolabs/sitemap-urls) | Every URL of a website from its sitemaps, in one deduplicated list with lastmod, changefreq and priority. |
| 📸 [Website Screenshot](https://apify.com/bepolabs/website-screenshot) | Full-page PNG, JPEG or PDF of any URL, on desktop, tablet and phone, with cookie banners closed. |
| 📝 [Website to Markdown](https://apify.com/bepolabs/web-to-markdown) | Any web page or docs site to clean Markdown for LLMs, RAG and AI agents, with headings, links and ready-made chunks. |
| 🌐 [Domain RDAP and DNS](https://apify.com/bepolabs/domain-rdap) | Domains to registrar, creation and expiry dates, age, status, nameservers, DNSSEC and DNS records from official RDAP, in bulk. |
| 📈 [Google Trends Scraper](https://apify.com/bepolabs/google-trends) | Interest over time for up to five keywords on one 0 to 100 scale, any country or worldwide, plus top and rising related queries. |
| 🧩 [Tech Stack Detector](https://apify.com/bepolabs/tech-stack) | Any list of domains to the technologies behind each site: CMS, shop, framework, analytics, CDN and hosting. |

# Changelog

This Actor's version history is a separate document: https://apify.com/bepolabs/seo-audit/changelog.md

# Actor input Schema

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

Pages to audit, one report each. Up to 200.

## `checkLinks` (type: `boolean`):

Check every link and image on the page for broken (4xx, 5xx, unreachable) and redirected targets.

## `maxLinksPerPage` (type: `integer`):

At most this many links per page are checked (first on the page first).

## `coreWebVitals` (type: `boolean`):

Measure Lighthouse performance, LCP, CLS and INP with Google PageSpeed Insights (adds 10 to 30 seconds per page).

## `strategy` (type: `string`):

Device profile for Core Web Vitals.

## `apiKey` (type: `string`):

Your own free Google API key with the PageSpeed Insights API enabled. Optional when the Actor's own key is set.

## `maxRetries` (type: `integer`):

Retries per request with exponential backoff on network errors, 429 and 5xx.

## Actor input object example

```json
{
  "urls": [
    "https://apify.com/"
  ],
  "checkLinks": true,
  "maxLinksPerPage": 100,
  "coreWebVitals": true,
  "strategy": "mobile",
  "maxRetries": 2
}
```

# Actor output Schema

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

Dataset items, one row per result, in the overview table view.

# 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://apify.com/"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("bepolabs/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://apify.com/"] }

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

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,bepolabs/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/KL7h0em2XibA7f8gZ/builds/fsfcc8oTahTRhXNrt/openapi.json
