# 📉 SEO Regression Auditor - Technical Audit + Diff vs Last Run (`that_red_bird/seo-regression-auditor`) Actor

🔍 Six technical SEO audits merged into one crawl — redirect chains, broken links, canonical issues, duplicate titles/meta, heading structure, indexability — then diffed against the last run so it reports what REGRESSED. ✅ Scores the site 0-100. First run is a baseline.

- **URL**: https://apify.com/that\_red\_bird/seo-regression-auditor.md
- **Developed by:** [mohamed alaya](https://apify.com/that_red_bird) (community)
- **Categories:** SEO tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## SEO Regression Auditor

Six technical SEO audits, merged into one crawl, that DIFF against the last run and tell you
what regressed. Most SEO tools give you a fresh list of problems every time you run them —
this one remembers, so you find out the moment something breaks instead of noticing it three
months later in a traffic graph.

### The six audits

1. **Redirect chains** — follows every redirect hop-by-hop, reports the full chain, flags
   anything over one hop and any loop.
2. **Broken links** — every internal link discovered during the crawl gets a status check,
   reported with the page(s) that link to it.
3. **Canonical issues** — missing canonical, conflicting canonicals (multiple different tags
   on one page), a canonical pointing at a different page, and whether it self-references.
4. **Title / meta description** — missing, too long, too short, and **duplicated across
   pages** — the cross-page pass most single-page checkers skip entirely.
5. **Headings** — missing H1, multiple H1s, and skipped heading levels (H1 straight to H3).
6. **Indexability** — noindex via meta tag, robots.txt disallow rules, and orphan pages
   (listed in the sitemap but never linked, or linked but missing from the sitemap).

### The regression engine

Every audit is stored in a named key-value store, keyed per site. On the next run, each
finding is compared against the stored baseline:

- **NEW** — an issue that wasn't there last time
- **FIXED** — an issue that was there last time and is now gone
- **EXISTING** — unchanged since last time (included unless `onlyRegressions` is on)

Plus a per-category delta and the score change between runs. The **first run for any site is
always a baseline** — nothing is reported as a regression yet, because there is nothing to
compare against.

### Score

Every site gets a 0-100 score. It starts at 100 and loses points per finding (broken links and
missing H1s/titles cost the most; a rotated redirect with one hop costs little). The score
itself is stored and diffed too, so you get a `scoreDelta` between runs.

### What this does NOT do

- **No JavaScript rendering.** Pages are fetched as raw HTML; content injected client-side
  (React/Vue apps without SSR, lazy-loaded text) will not be seen. Point it at server-rendered
  pages, or pre-rendered/SSR output.
- **No Core Web Vitals or any real-user field data.** This is a structural/content audit, not
  a performance audit — it does not measure LCP, INP, CLS, or anything from CrUX.
- Broken-link checking is capped (`maxLinksChecked`) and uses a plain GET request, not a full
  browser — pages that require JS to render their links may under-report link coverage.
- Orphan-page detection is only as good as the crawl: a page outside `maxPages` won't be seen
  as "linked" even if it truly is.

### Typical use

Run it weekly or after every deploy. Point `resetBaseline` at true the first time you add a
new site, then leave it off — every run after that tells you exactly what changed.

# Actor input Schema

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

Full site URLs to crawl and audit, e.g. https://example.com. Each is audited independently and stored under its own baseline.

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

How many pages get fetched and audited for content issues (titles, canonicals, headings, indexability). Prefers sitemap.xml URLs; falls back to following same-origin links if no sitemap is found.

## `useSitemap` (type: `boolean`):

Discover pages from sitemap.xml (and sitemap indexes, one level deep) first. Also used for orphan-page detection under Indexability.

## `maxLinksChecked` (type: `integer`):

On top of the crawled pages, this many additional internal links found on those pages get a status-only request for the broken-link and redirect-chain audits (no content is stored for these).

## `titleMinLength` (type: `integer`):

A <title> shorter than this is flagged as too short.

## `titleMaxLength` (type: `integer`):

A <title> longer than this is flagged as too long (roughly where Google starts truncating it in results).

## `descMinLength` (type: `integer`):

A meta description shorter than this is flagged as too short.

## `descMaxLength` (type: `integer`):

A meta description longer than this is flagged as too long.

## `onlyRegressions` (type: `boolean`):

After the baseline run, push only issues that are NEW or FIXED since last time — skip issues that are unchanged from the previous run. The baseline run itself always outputs everything found.

## `resetBaseline` (type: `boolean`):

Forget the stored audit for these sites and start fresh — this run becomes the new baseline with no regressions reported.

## `maxItems` (type: `integer`):

Hard cap on issue/fixed rows pushed per site. 0 = no cap.

## `concurrency` (type: `integer`):

How many requests to run in parallel while crawling and link-checking.

## `timeoutMs` (type: `integer`):

Per-request timeout for every crawl, link-check and redirect hop.

## `stateStoreName` (type: `string`):

Named key-value store holding the previous audit per site. Use a different name per client to keep baselines separate.

## Actor input object example

```json
{
  "startUrls": [
    "https://example.com"
  ],
  "maxPages": 40,
  "useSitemap": true,
  "maxLinksChecked": 150,
  "titleMinLength": 15,
  "titleMaxLength": 60,
  "descMinLength": 50,
  "descMaxLength": 160,
  "onlyRegressions": false,
  "resetBaseline": false,
  "maxItems": 0,
  "concurrency": 5,
  "timeoutMs": 15000,
  "stateStoreName": "seo-regression-auditor-state"
}
```

# Actor output Schema

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

No description

## `downloadCsv` (type: `string`):

No description

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

No description

## `count` (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 = {
    "startUrls": [
        "https://example.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("that_red_bird/seo-regression-auditor").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://example.com"] }

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

```

## MCP server setup

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

```

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/22bjhvH4qEoon4JT6/builds/pU0zJcyM7lj84nHYc/openapi.json
