# SEO Audit Tool: Website SEO Checker, 0-100 Score, $5/1K Pages (`conserving_celerytop/seo-audit-tool`) Actor

SEO audit tool: crawl a website over plain HTTP and get a 0-100 SEO score per page, with titles, meta, H1, canonical, noindex, broken links, alt text, schema and duplicates. One free site summary row. Failed pages are free.

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

## Pricing

from $3.00 / 1,000 page audits

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 Tool: Website SEO Checker, 0-100 Score

This SEO audit tool crawls a website over plain HTTP and gives every page a score from 0 to 100, with a list of what is wrong and how to fix it. It also adds one summary row for the whole site. There is no browser involved, so a small site is done in a minute or two.

It follows robots.txt, stays on the domain you start from, and sends a few requests at a time so it does not load the site.

### How to run an SEO audit, step by step

1. Open the Actor and paste the site address into **Start URLs**, for example `https://www.example.com/`. The prefilled address is a public practice site, so you can try a run before using your own.
2. Set the **Page limit**. Ten pages is a quick test. Fifty is the default. You can go up to 10,000.
3. Optional: lower **Link depth** to stay near the home page, or add **Excluded URL patterns** such as `/cart` to skip sections.
4. Click Start. When the run finishes, open the Overview table. Sort by score to see the weakest pages first.
5. Open the Issues view to read each problem with its fix. The last row of the dataset has `type: "site"` and holds the site score and the most common issues.

An empty or invalid start URL ends the run with a message instead of an error.

### What it checks

Each distinct issue on a page costs points, and a page starts at 100. The same issue is only counted once per page. The score never goes below 0. Pages that return an HTTP error score 0.

| Severity | Points off | Issues |
|---|---|---|
| Critical | 15 | HTTP error (4xx or 5xx), noindex on the page, missing title |
| High | 8 | Missing H1, missing meta description, broken internal links, canonical pointing elsewhere |
| Medium | 4 | Title not 30 to 60 characters, meta description not 70 to 160, more than one H1, duplicate title, duplicate meta description |
| Low | 2 | Images without an alt attribute, no structured data, no Open Graph title or image |

Duplicates are found across the pages of one run. The first page with a title or description keeps it, and later pages with the same text point back to it in `duplicateTitleOf` or `duplicateMetaOf`.

The site row also reports whether robots.txt exists, whether a sitemap exists and how many URLs it lists, whether plain http redirects to https, whether `/llms.txt` exists, the site score (the average of the page scores) and the ten most common issues.

### How much it costs

You pay per audited page: **$5 per 1,000 pages on the Free plan** ($0.005 each, lower on paid plans). A page is charged only after its row is saved. Pages that fail, non-HTML files such as PDFs, and the site summary row are free. If you set a maximum charge for the run, the crawl stops when it is reached.

Example: a 200-page site costs about $1.00. A run with the default 50-page limit costs at most $0.25. If you turn on **Issues only**, pages with no issues are not saved and not charged.

### Input example

```json
{
  "startUrls": [{ "url": "https://books.toscrape.com/" }],
  "maxPages": 10,
  "maxDepth": 2,
  "excludeUrlPatterns": ["/cart"],
  "maxConcurrency": 4
}
```

Other options: `includeSubdomains`, `checkExternalLinks` (sends HEAD requests to outbound links, up to 25 per page, and respects those sites' robots.txt) and `outputIssuesOnly`.

### Output example

This is a real row from a test run against a small local site (URLs shortened, issue list trimmed to three of the seven found):

```json
{
  "type": "page",
  "url": "http://127.0.0.1:8777/notebooks.html",
  "statusCode": 200,
  "depth": 1,
  "title": "Notebooks",
  "titleLength": 9,
  "metaDescriptionLength": 123,
  "h1Count": 2,
  "wordCount": 3,
  "imagesTotal": 2,
  "imagesMissingAlt": 1,
  "schemaTypes": [],
  "duplicateTitleOf": "http://127.0.0.1:8777/journals.html",
  "issues": [
    { "code": "TITLE_LENGTH", "severity": "medium", "message": "The title is 9 characters (aim for 30 to 60).", "fix": "Rewrite the title so it fits roughly 30 to 60 characters." },
    { "code": "MULTIPLE_H1", "severity": "medium", "message": "The page has 2 H1 headings.", "fix": "Keep one H1 and turn the others into H2 or lower." },
    { "code": "DUPLICATE_TITLE", "severity": "medium", "message": "Same title as http://127.0.0.1:8777/journals.html.", "fix": "Give each page its own title." }
  ],
  "score": 78
}
```

The site row from the same run:

```json
{
  "type": "site",
  "siteScore": 52,
  "pagesCrawled": 6,
  "pagesAudited": 4,
  "pagesFailed": 2,
  "robotsTxtFound": true,
  "sitemapFound": true,
  "sitemapUrlCount": 2,
  "llmsTxtFound": false,
  "topIssues": [{ "code": "NO_SCHEMA", "severity": "low", "pages": 3 }, { "code": "NO_OPEN_GRAPH", "severity": "low", "pages": 3 }],
  "brokenLinkTargets": [{ "url": "http://127.0.0.1:8777/old-page.html", "status": 404, "foundOn": ["http://127.0.0.1:8777/"] }]
}
```

### Bulk lists

Use a long list of start URLs from an Apify Dataset or from a file. Pick the Dataset under **Dataset with the list** and write the name of its field under **Field in the dataset**. Leave the field name empty to use the first text field. Or paste the web address of a .txt, .csv or .json file under **List file URL**. A .csv file uses its first column. A header row is skipped. The file can be up to 10 MB. Entries from the Dataset and the file are added to the ones typed under **Start URLs**, and duplicates are removed. All entries are start points of the same crawl, and the run gives one site summary row. The price per audited page does not change.

### Related Actors

- [Tech Stack Detector](https://apify.com/conserving_celerytop/website-tech-stack-detector): Technologies, email provider and DNS for any website.
- [Domain Authority Checker](https://apify.com/conserving_celerytop/domain-authority-checker): Bulk domain authority scores for websites.
- [Sitemap Extractor](https://apify.com/conserving_celerytop/sitemap-url-extractor): All URLs from a sitemap.
- [Broken Link Checker](https://apify.com/conserving_celerytop/broken-link-checker): Find broken links on a site.
- [AI Crawler Access Audit](https://apify.com/conserving_celerytop/ai-crawler-access-audit): Which AI crawlers a site allows in robots.txt.

### FAQ

**Why do the scores differ from Lighthouse or other SEO tools?**
Every tool picks its own checks and weights. This one scores only what it can read from the HTML and headers, and the weights are in the table above. It does not measure speed, Core Web Vitals or backlinks, so use it for on-page and crawl problems, not as a full ranking forecast.

**Does it respect robots.txt?**
Always, and you cannot turn it off. Disallowed URLs are skipped and counted in `skippedByRobots`. Wildcard rules such as `Disallow: /*.pdf$` and a `Crawl-delay` are honoured. Retry-After headers are honoured too. If the site keeps answering 403 or 429, the run stops early and says so in `stoppedReason`. It does not rotate proxies or hide who it is. The user agent is `SEOAuditToolBot`.

**Does it run JavaScript?**
No. It reads the HTML the server sends. Pages that build their content in the browser may show as thin or missing a title.

**How are broken internal links and duplicates found?**
The crawl keeps every page result in memory until it has finished. Then it fills in `brokenLinks` from the status of every URL it fetched, marks duplicate titles and descriptions, recalculates the score, and only then saves and charges each page row. That is why rows appear in the dataset after the crawl, not one by one during it. If you set a maximum charge, saving stops when it is reached and the remaining pages are neither saved nor charged. A page is "broken" only if the link target was fetched in the same run, so links to pages beyond your page limit or depth are not checked.

**What are the limits?**
Up to 10,000 pages per run, depth up to 20, and 10 requests at a time per host (4 by default). Pages larger than about 2 MB are cut off. Only the domain of your start URL is crawled, plus its subdomains if you turn that on.

# Actor input Schema

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

The site to audit. The crawl starts here and stays on the same domain. The prefilled address is a public practice site that allows crawling.

## `listDatasetId` (type: `string`):

Pick an Apify Dataset that holds your start URLs, one entry per item. They are added to the entries typed under Start URLs.

## `listDatasetField` (type: `string`):

Name of the dataset field that holds the entry. Leave empty to use the first text field it finds.

## `listFileUrl` (type: `string`):

Web address of a .txt, .csv or .json file with your start URLs, one entry per line. A header row is skipped. Up to 10 MB.

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

Maximum number of pages to fetch in this run. You pay per page audited ($5 per 1,000 on the Free plan).

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

How many link clicks away from the start URL to follow. 0 audits only the start URLs.

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

Also crawl subdomains of the start domain, such as blog.example.com.

## `excludeUrlPatterns` (type: `array`):

Skip any URL that matches one of these regular expressions, for example /cart or ?sort=.

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

Send a HEAD request to outbound links (up to 25 per page) and report the broken ones. Other sites' robots.txt rules are respected. Off by default.

## `outputIssuesOnly` (type: `boolean`):

Save only pages that have at least one issue. Pages that are not saved are not charged.

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

Parallel requests to the audited site. Keep it low for small sites. A crawl-delay in robots.txt overrides this.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://books.toscrape.com/"
    }
  ],
  "maxPages": 10,
  "maxDepth": 2,
  "includeSubdomains": false,
  "checkExternalLinks": false,
  "outputIssuesOnly": false,
  "maxConcurrency": 4
}
```

# Actor output Schema

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

One row per audited page with a 0-100 score and its issues, plus one site summary row.

## `issues` (type: `string`):

The same pages with the issue list and fix hints in focus.

# 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://books.toscrape.com/"
        }
    ],
    "maxPages": 10,
    "maxDepth": 2
};

// Run the Actor and wait for it to finish
const run = await client.actor("conserving_celerytop/seo-audit-tool").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://books.toscrape.com/" }],
    "maxPages": 10,
    "maxDepth": 2,
}

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

```

## MCP server setup

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

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/0k6tlDh686DQSAeKm/builds/5W3HXkEprbhAJKg6d/openapi.json
