# SEO Meta & Content Auditor (`arched_friend/seo-content-auditor`) Actor

Crawl any website and get a page-by-page list of on-page SEO problems: missing or overlong titles, duplicate meta descriptions, missing or multiple H1s, accidental noindex tags, images with no alt text and thin content. Every issue names the page and what to fix.

- **URL**: https://apify.com/arched\_friend/seo-content-auditor.md
- **Developed by:** [Peach O](https://apify.com/arched_friend) (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

$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

## SEO Meta & Content Auditor: The On-Page Fix List for Any Site

Crawl a site and get back, page by page, exactly what is wrong on it: the missing titles, the descriptions that will be truncated, the pages with no H1 or three of them, the accidental `noindex`, the duplicate titles your template is generating, and the images with no alt text.

Not a score out of 100. A list of pages and what to change on each one.

### How it works

```mermaid
flowchart LR
    A["Your website"] --> B["Crawl pages"]
    B --> C["Read title, description,<br/>headings, canonical, robots"]
    C --> D["Apply on-page rules<br/>per page"]
    D --> E["Compare titles and<br/>descriptions across all pages"]
    E --> F[("Fix list,<br/>worst pages first")]
```

Duplicate titles and descriptions can only be found once every page is in, which is why this runs as a whole-site pass rather than a per-page check. On a template-driven site it is usually the single biggest finding.

### What it checks

| Severity | Issue |
| --- | --- |
| **Critical** | Missing title · Missing meta description · Missing H1 · `noindex` set |
| **Warning** | Title too long or too short · Description too long · Multiple H1s · Duplicate title or description · Thin content · Images with no alt text · Missing viewport · `nofollow` set |
| **Notice** | Missing canonical · Canonical pointing elsewhere · Title identical to H1 · Missing `lang` · Missing `og:title` |

Every issue carries a code, a severity and a sentence explaining what it means, so you can act on it without looking anything up.

### Built for

- **SEO teams** who want the work queue, not a dashboard
- **Agencies** auditing a prospect's site before a pitch
- **Developers** checking a release did not ship a stray `noindex`
- **Content teams** finding thin and duplicate pages worth consolidating

### Input

```json
{
  "startUrls": ["example.com"],
  "maxPages": 200,
  "onlyPagesWithIssues": true,
  "minSeverity": "warning"
}
```

| Setting | What it does |
| --- | --- |
| `startUrls` | The site to audit, as a domain or full URL |
| `maxPages` | How many pages to crawl. This is what the run is charged on |
| `minSeverity` | Report only warnings and above, or only critical issues |
| `onlyPagesWithIssues` | Leave clean pages out, so the output is a to-do list |
| `excludePatterns` | Skip areas like `/admin` or faceted filter URLs |

### Output

```json
{
  "url": "https://example.com/blog/post",
  "title": "Post",
  "titleLength": 4,
  "metaDescription": null,
  "metaDescriptionLength": 0,
  "h1": "Post",
  "h1Count": 1,
  "h2Count": 3,
  "canonical": "https://example.com/blog/post",
  "noindex": false,
  "wordCount": 210,
  "imageCount": 6,
  "imagesMissingAlt": 4,
  "issueCount": 4,
  "criticalCount": 1,
  "issueCodes": ["description-missing", "title-short", "thin-content", "images-missing-alt"],
  "issues": [
    { "code": "description-missing", "severity": "critical", "message": "This page has no meta description, so search engines will invent one." }
  ],
  "auditedAt": "2026-09-17T16:12:40.118Z"
}
```

Filter on `issueCodes` to pull one job at a time — every page missing a description, say — or sort on `criticalCount` to work worst-first. The run summary carries the ten worst pages and every duplicate group.

### The thresholds are conventions, not laws

Titles are flagged outside 30–60 characters and descriptions outside 70–160. Google truncates rather than penalises, so treat those as display guidance. Every issue names the rule that fired, so you can disregard one without distrusting the rest.

A page with no extractable text is **not** reported as thin content, because that would fire on every gallery and landing page. Thin content means a page that has text and does not have enough of it.

### Run it as an API

```bash
curl -X POST "https://api.apify.com/v2/acts/arched_friend~seo-content-auditor/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "startUrls": ["example.com"],
    "maxPages": 100,
    "minSeverity": "warning"
  }'
```

### Pricing

You pay $0.003 for each page audited. Every check on that page is included.

| Auditing a 500-page site monthly | Monthly cost |
| --- | --- |
| Checking by hand | days of work |
| Most SEO suites | $99 to $500 per month |
| This Actor | $1.50 per run |

### Common questions

**Why is a page flagged for a duplicate title when I only have one of it?** The same content is probably reachable at two URLs — `/` and `/index.html`, or with and without a trailing slash. That is a real duplicate-content problem, and a canonical tag is the usual fix.

**Does it check rankings or backlinks?** No. This is on-page only: what is in your HTML and under your control. Nothing here needs a third-party SEO API.

**Does it render JavaScript?** No, it reads the HTML your server sends, which is what a crawler sees first. If your titles and descriptions are injected client-side, that is itself worth knowing.

**Why did it find fewer pages than my sitemap lists?** It follows links. Pages not linked from anywhere it crawled are orphans, which is a finding in its own right.

### Related products

- **Broken Link Checker** for the dead links on the same pages
- **Website Change Monitor** to catch it when a fixed page regresses
- **Domain & SSL Monitor** for the certificate and expiry problems underneath
- **Tech Stack Checker** to profile the sites you audit
- **Website Lead Extractor** to pull contacts from the sites you audit

# Actor input Schema

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

The site to audit, as a domain or full URL.

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

Upper limit on how many pages to crawl and check. This is what the run is charged on.

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

How many clicks from the starting page to follow. 0 audits only the pages you list.

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

Also crawl blog.example.com when auditing example.com.

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

Do not crawl any URL containing one of these strings, for example /admin or ?filter=.

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

Leave clean pages out of the output, so the result is a to-do list rather than an inventory.

## `minSeverity` (type: `string`):

Report only issues at this level or above. Critical means things that stop a page ranking at all, such as a missing title or an accidental noindex.

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

How many pages to fetch at once. Lower this if a site rate limits you.

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

Optional, and off by default. Useful only if a site rate limits a large crawl.

## Actor input object example

```json
{
  "startUrls": [
    "apify.com"
  ],
  "maxPages": 200,
  "maxDepth": 5,
  "includeSubdomains": true,
  "onlyPagesWithIssues": true,
  "minSeverity": "notice",
  "maxConcurrency": 10
}
```

# Actor output Schema

## `pages` (type: `string`):

One row per page, with its title, description, headings and every on-page issue found, each with a severity and a plain explanation.

## `runSummary` (type: `string`):

Issue counts by type and severity, the ten worst pages, and every group of pages sharing a duplicate title or 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": [
        "apify.com"
    ]
};

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

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

```

## MCP server setup

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