# SEO Site Audit — Website SEO Checker & Crawler (`saliga/seo-site-audit`) Actor

Crawl a website and audit every page for technical and on-page SEO: a 0-100 score, issues with fix hints, broken internal links, duplicate titles, orphan pages, canonical and robots problems, plus a site summary. $3 per 1,000 pages.

- **URL**: https://apify.com/saliga/seo-site-audit.md
- **Developed by:** [Salih Küçükkayış](https://apify.com/saliga) (community)
- **Categories:** SEO tools, Developer tools
- **Stats:** 3 total users, 2 monthly users, 75.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

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

Crawl a website and audit every page for technical and on-page SEO. Each page gets a **0-100 score** and a list of **issues with a severity and a fix hint**: broken internal links, redirects, missing or duplicate titles and meta descriptions, headings, canonical and noindex problems, thin or duplicate content, images without alt text, structured data, mobile viewport, HTTPS and mixed content, slow pages, orphan pages and pages missing from the sitemap. A site summary adds robots.txt, sitemaps, HTTPS and www redirects, llms.txt and the technologies the site runs on.

**$3 per 1,000 pages audited.** Pages that robots.txt closes and URLs that do not answer are free. No platform usage fees.

### Quick start

Press **Start** with the example input. It audits up to 50 pages of python.org:

```json
{
  "startUrls": ["https://www.python.org"],
  "maxPagesToCrawl": 50
}
```

You get one dataset row per page, lowest score first (trimmed here; a real row from python.org):

```json
{
  "url": "https://www.python.org/about/help/",
  "status": 200,
  "score": 78,
  "indexable": true,
  "errors": 0,
  "warnings": 2,
  "notices": 4,
  "issues": [
    {
      "id": "title-too-short",
      "severity": "warning",
      "message": "Title shorter than 30 characters",
      "fix": "Make the title more descriptive (about 30-60 characters).",
      "detail": 17
    },
    {
      "id": "duplicate-description",
      "severity": "warning",
      "message": "Same meta description as other pages",
      "fix": "Write a unique description for every page.",
      "detail": ["https://www.python.org/", "https://www.python.org/jobs/", "https://www.python.org/about/"]
    }
  ],
  "title": "Help | Python.org",
  "titleLength": 17,
  "metaDescription": "The official home of the Python Programming Language",
  "metaDescriptionLength": 52,
  "h1": ["Help", "New to programming and to Python?", "…"],
  "wordCount": 737,
  "structuredData": ["WebSite"],
  "internalLinks": 54,
  "inlinks": 38,
  "depth": 1,
  "redirectingLinks": [
    { "url": "https://www.python.org/psf/", "finalUrl": "https://www.python.org/psf-landing/" }
  ],
  "responseTimeMs": 58,
  "htmlBytes": 38096
}
```

The run also saves a **SUMMARY** record: pages audited, average score, how many pages have each issue (errors first), and per site: robots.txt, sitemaps, whether HTTP redirects to HTTPS and the www variant to the main host, llms.txt, and technologies (CMS, framework, analytics, CDN, server).

### What it checks

| Severity | Issues |
|---|---|
| **Error** | Error status (4xx/5xx), no title, broken internal links, redirect loops, canonical pointing to a broken URL, noindex pages listed in the sitemap |
| **Warning** | Title too long or short, duplicate titles, missing or duplicate meta description, no H1, thin content (under 200 words), duplicate content, images without alt, no mobile viewport, plain HTTP, mixed content, slow server (over 1.5 s), redirect chains, orphan pages (in the sitemap, no links to them) |
| **Notice** | Description length, several H1s, no or non-self canonical, noindex, no lang, large HTML, redirected URL, links to redirecting URLs, page missing from the sitemap, more than 3 clicks deep, no structured data, no Open Graph tags, nofollow on internal links |

Score: 100 minus 20 per error, 7 per warning and 2 per notice (at least 0). Every issue in a row has an `id`, `severity`, `message`, `fix` and, where useful, a `detail` (a length, a count or the URLs involved).

### Typical uses

- **Technical SEO audit** of a client or your own site: sort by score, fix errors first.
- **After a migration or redesign:** find broken internal links, redirect chains and pages that lost their titles or canonicals.
- **Sitemap hygiene:** orphan pages, noindex pages in the sitemap, indexable pages missing from it.
- **Content checks:** duplicate and thin pages, missing descriptions and H1s.
- **Recurring checks:** schedule a weekly run and compare scores.
- **Give an AI agent an SEO tool** through the Apify MCP server: "audit example.com and tell me what to fix first".

### Input

| Field | Default | Meaning |
|---|---|---|
| `startUrls` | | Websites or sections to audit (`example.com`, `https://example.com/blog/`). |
| `maxPagesToCrawl` | `200` | Stop after this many pages (up to 50,000). |
| `useSitemaps` | `true` | Also audit pages listed in the XML sitemaps; needed for orphan and sitemap checks. |
| `detectTechnologies` | `true` | Technologies of each site, from its homepage. |
| `onlyPagesWithIssues` | `false` | Write only pages with at least one issue (pages left out are free). |
| `maxDepth` | `20` | Clicks from the start page to follow. |
| `includePatterns`, `excludePatterns` | | Regular expressions to limit which URLs are crawled. |

### Output

One row per crawled page:

| Field | Meaning |
|---|---|
| `url`, `finalUrl`, `status` | URL, URL after redirects, HTTP status |
| `score`, `errors`, `warnings`, `notices`, `issues` | Score and issues with fix hints |
| `indexable` | 200, not noindex, and canonical to itself (or none) |
| `title`, `titleLength`, `metaDescription`, `metaDescriptionLength` | Title and description |
| `h1`, `h2Count` | Headings |
| `canonical`, `metaRobots`, `xRobotsTag`, `lang`, `hreflangCount` | Indexing signals |
| `wordCount`, `imageCount`, `imagesMissingAlt`, `structuredData` | Content |
| `internalLinks`, `inlinks`, `depth`, `inSitemap` | Links on the page, links to it from crawled pages, clicks from the start page |
| `brokenLinks`, `redirectingLinks`, `redirectChain` | Internal links to fix and the redirects of this URL |
| `responseTimeMs`, `htmlBytes`, `contentType`, `mixedContent` | Speed and size |

Pages that robots.txt closes appear with `blockedByRobots: true` and are not charged.

### What it costs

| Run | Price |
|---|---|
| Small site, 50 pages | $0.15 |
| 1,000 pages | $3.00 |
| Weekly audit of a 500-page site for a month | $6.00 |

Pricing: $0.003 per page audited (any page that answered, including 404s, which are reported). Platform usage is included.

### Next steps with your results

- Check the speed and Core Web Vitals of the worst pages with [Lighthouse Bulk Audit](https://apify.com/saliga/lighthouse-bulk-audit).
- Check every external link and image, not only internal pages, with [Broken Link Checker](https://apify.com/saliga/broken-link-checker).
- Get every URL a site lists in its sitemaps, with lastmod and hreflang, from [Sitemap Extractor](https://apify.com/saliga/sitemap-extractor).
- See which keywords the site ranks for with [Competitor Keywords](https://apify.com/saliga/competitor-keywords).

### Good to know

- **Polite crawling.** robots.txt is honoured for every page (user agent `SEOSiteAudit`), one request at a time per host with a short pause. Only pages of the sites you give are crawled; links to other sites are counted, not followed.
- **The HTML as served is audited,** without running JavaScript. Pages that build their content in the browser (some single-page apps) may show fewer words and links than a browser sees.
- **Site-wide checks need the whole crawl,** so rows are written when the crawl ends, or when it stops at the page limit, the spending limit or shortly before the run timeout. Pages linked from beyond the limit are not judged (their links count as unknown, not broken).
- **Redirects.** A URL that redirects gets a short row of its own (`status` 301/302, `finalUrl`, `finalStatus`) and counts as one page; the page it leads to is audited in its own row.
- **Response time** is the time until the server's first answer, measured from Apify's data center.
- Private and internal addresses are refused. Technology detection uses the open Wappalyzer fingerprints (GPL-3.0).

### FAQ

**How is this different from Broken Link Checker?** Broken Link Checker checks every link and image, including other sites. SEO Site Audit checks each page itself (titles, descriptions, headings, canonical, content, structured data…) and the links between your own pages.

**Can I audit only part of a site?** Yes: start at the section (`https://example.com/blog/`) and add `/blog/` to `includePatterns`.

**Am I charged for pages that fail?** Pages that answer with an error (404, 500) are audited and charged, because the error is a finding. URLs that robots.txt closes, and hosts that cannot be reached, are free.

**Can I run it from Make, Zapier, n8n, my own code or an AI agent?** Yes, like any Apify Actor: through Apify's integrations, the Apify API or the Apify MCP server.

### More from this developer

- [Lighthouse Bulk Audit](https://apify.com/saliga/lighthouse-bulk-audit): PageSpeed Insights scores, Core Web Vitals with real-user data and AI agent readiness for hundreds of URLs, $4 per 1,000 audits.
- [Broken Link Checker](https://apify.com/saliga/broken-link-checker): crawl a site and find broken links, images and redirects.
- [Sitemap Extractor](https://apify.com/saliga/sitemap-extractor): every URL a website lists in its sitemaps, with lastmod, images and hreflang, $0.25 per 1,000 URLs.
- [Tech Stack Detector](https://apify.com/saliga/tech-stack-detector): which CMS, ecommerce platform, analytics and hosting a website uses, $1.50 per 1,000 websites.
- [Keyword Research](https://apify.com/saliga/keyword-research): keyword ideas with volume, difficulty and search intent, $2 per 1,000 keywords.
- [Competitor Keywords](https://apify.com/saliga/competitor-keywords): every Google keyword a domain or page ranks for, $2 per 1,000 rows.
- [Google SERP Rank Tracker](https://apify.com/saliga/serp-rank-tracker): Google positions for your keywords by country and device, $2 per 1,000 result pages.

Code examples (curl, Python, Node.js, n8n) for this and the other Actors: [github.com/msalihk/apify-actor-examples](https://github.com/msalihk/apify-actor-examples).

Questions or a missing feature: open an issue on the Actor page.

# Actor input Schema

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

Websites (or sections, like example.com/blog) to audit. Pages of the same site are followed; www. and the bare domain count as one site.

## `maxPagesToCrawl` (type: `integer`):

Stop after this many pages. Each audited page is one charge.

## `useSitemaps` (type: `boolean`):

Read the sitemaps listed in robots.txt (or /sitemap.xml) to find pages that no link reaches, and to report orphan pages and pages missing from the sitemap.

## `detectTechnologies` (type: `boolean`):

Report the CMS, framework, analytics, CDN and server of each site, from its homepage (open Wappalyzer fingerprints).

## `onlyPagesWithIssues` (type: `boolean`):

Write only pages that have at least one issue. Pages left out are not charged.

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

How many clicks from the start page to follow (0 = start page only, plus sitemap pages).

## `includePatterns` (type: `array`):

Regular expressions; when set, only matching URLs are crawled (for example /blog/).

## `excludePatterns` (type: `array`):

Regular expressions for URLs not to crawl (for example ?page=, /tag/).

## Actor input object example

```json
{
  "startUrls": [
    "https://www.python.org"
  ],
  "maxPagesToCrawl": 50,
  "useSitemaps": true,
  "detectTechnologies": true,
  "onlyPagesWithIssues": false,
  "maxDepth": 20
}
```

# Actor output Schema

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

One row per page: score, issues with fix hints, title, description, headings, canonical, links.

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

Issue counts across the site, average score, robots.txt, sitemaps, HTTPS and www redirects, llms.txt and technologies.

# 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://www.python.org"
    ],
    "maxPagesToCrawl": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("saliga/seo-site-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 = {
    "startUrls": ["https://www.python.org"],
    "maxPagesToCrawl": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("saliga/seo-site-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 '{
  "startUrls": [
    "https://www.python.org"
  ],
  "maxPagesToCrawl": 50
}' |
apify call saliga/seo-site-audit --silent --output-dataset

```

## MCP server setup

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