# On-Page SEO Audit – Bulk Meta, Headings & Schema Checker (`chris_elder/on-page-seo-audit`) Actor

Crawl URLs or a sitemap and score each page 0-100 for on-page SEO: title, meta description, H1, canonical, robots/noindex, hreflang, Open Graph, JSON-LD, image alt text and duplicates, with concrete fixes. $3 per 1,000 pages; failed pages are free.

- **URL**: https://apify.com/chris_elder/on-page-seo-audit.md
- **Developed by:** [Chris Elder](https://apify.com/chris_elder) (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

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

## On-Page SEO Audit – bulk meta tags, headings & schema checker

**On-Page SEO Audit** checks hundreds or thousands of pages for on-page SEO problems in minutes – no browser, no API key, no proxies. Give it a list of URLs, a whole website (it crawls internal links) or its XML sitemap, and get one clean row per page with a **0–100 SEO score**, every **issue with its severity**, and **a concrete fix** for each one.

It is a lightweight, pay-per-page **Screaming Frog / Sitebulb alternative** for the checks that matter most: titles, meta descriptions, H1–H6, canonical tags, noindex / X-Robots-Tag, robots.txt, hreflang, Open Graph and Twitter cards, JSON-LD / schema.org structured data, image alt text, empty links, mixed content, thin content, redirects, slow responses and **duplicate titles and descriptions** across the site.

### What does the SEO audit check?

| Area | Checks |
|---|---|
| **Titles & meta** | missing / too short / too long title, multiple titles, missing / short / long / duplicate meta description |
| **Headings** | missing, empty or multiple H1, skipped heading levels, H1–H6 counts |
| **Indexability** | HTTP status, redirect chains, `noindex` / `nofollow` in meta robots and **X-Robots-Tag** header, robots.txt blocking, canonical missing / pointing elsewhere / multiple |
| **International** | `<html lang>`, hreflang alternates and missing self-reference |
| **Social & rich results** | Open Graph (og:title, og:image…), Twitter cards, JSON-LD and microdata types, invalid JSON-LD |
| **Content & UX** | word count (thin content), images without alt, links without anchor text, meta viewport (mobile), HTTPS, mixed content, HTML size, server response time |
| **Links** | internal / external / nofollow link counts (and crawl of internal links) |

Every issue comes with a `code`, `severity` (critical / high / medium / low), a human-readable `message` and a `fix`. The score starts at 100 and subtracts 20 / 10 / 5 / 2 points per critical / high / medium / low issue.

### Who is it for?

- **SEO agencies & freelancers** – audit client sites and prospects in bulk, export to Google Sheets or Excel, re-run monthly on a schedule
- **In-house SEO & content teams** – find pages with missing descriptions, duplicate titles or accidental `noindex` before Google does
- **Developers & QA** – add an SEO regression check to your release pipeline via the API (fail the build if the score drops)
- **AI agents (MCP)** – a fast, deterministic "audit this URL's SEO" tool

### How much does it cost?

**$3 per 1,000 audited pages** ($0.003 per page) plus $0.0005 per run – up to **13× cheaper** than the most popular SEO audit tool on the Store ($0.04 per page). Higher Apify plans get automatic discounts (Silver $0.0025, Gold+ $0.002 per page).

| Job | Price |
|---|---|
| 100 pages | ~$0.30 |
| 1,000 pages | ~$3 |
| 10,000 pages | ~$30 |

**You only pay for successfully audited HTML pages (HTTP 2xx).** Pages that time out, return 4xx/5xx, are not HTML (PDFs, images) or are blocked by robots.txt are listed in the dataset for free. Set **Max pages** or Apify's **Max cost per run** to cap spending – the Actor stops cleanly at the limit.

### How to use it

1. Click **Try for free**.
2. Add one or more URLs.
3. Choose the mode:
   - **List mode** (default) – audits exactly the URLs you entered;
   - **Crawl the website** – follows internal links from the start URL up to *Max pages* / *Max crawl depth*;
   - **Add pages from the sitemap** – pulls URLs from the site's XML sitemap (found via robots.txt).
4. Optionally restrict with include/exclude regex patterns (e.g. only `/blog/`) and adjust thresholds (title 30–60 chars, description 70–160 chars by default).
5. Click **Start**. Open the **SEO overview** table (one row per page) or the **Issues & fixes** table (one row per issue), and export to JSON, CSV, Excel or Google Sheets.

#### Input example

```json
{
  "startUrls": [{ "url": "https://www.example.com" }],
  "crawl": true,
  "useSitemap": true,
  "maxPages": 500,
  "excludeUrlPatterns": ["/tag/", "\\?page="]
}
```

#### Output example (shortened)

```json
{
  "url": "https://example.com",
  "finalUrl": "https://example.com",
  "statusCode": 200,
  "score": 64,
  "issueCount": 7,
  "indexable": true,
  "title": "Example Domain",
  "titleLength": 14,
  "metaDescription": null,
  "h1": [],
  "canonical": null,
  "canonicalStatus": "missing",
  "structuredDataTypes": [],
  "wordCount": 25,
  "responseTimeMs": 11,
  "issues": [
    { "code": "TITLE_TOO_SHORT", "severity": "medium", "message": "Title is 14 characters (min 30).", "fix": "Expand the title with the main keyword and brand." },
    { "code": "META_DESCRIPTION_MISSING", "severity": "high", "message": "No meta description.", "fix": "Add a compelling meta description of 70–160 characters." },
    { "code": "H1_MISSING", "severity": "high", "message": "No <h1> heading.", "fix": "Add one <h1> that describes the page topic." }
  ],
  "openGraph": {}, "twitterCard": {}, "hreflang": [],
  "images": { "total": 0, "missingAlt": 0 },
  "links": { "internal": 0, "external": 1, "nofollow": 0, "emptyAnchor": 0 }
}
```

A `RUN_SUMMARY` record in the key-value store lists the most frequent issues across the site, failures and counts – a ready-made executive summary.

📊 **Want these results in Google Sheets?** Send them straight to a Sheet with [Google Sheets Sync](https://apify.com/chris_elder/google-sheets-sync) – run it after this Actor, or add it on this Actor's **Integrations** tab so every run exports automatically.

### Polite by default

- One request at a time per host with a **500 ms delay**, at most 4 pages in parallel overall
- Honest User-Agent (`OnPageSeoAuditBot`) and **robots.txt respected** (turn off only for sites you own)
- Retries with back-off, `Retry-After` honoured, 8 MB page cap, private/internal addresses refused

### Tips

- **JavaScript-heavy sites:** the audit reads the HTML that the server sends (what most crawlers see first). If a single-page app renders titles only in the browser, those pages will show as missing titles – which is itself a real SEO risk worth fixing.
- **Monitoring:** schedule a weekly run and send the dataset to Slack, Google Sheets or a webhook through Apify integrations.
- Pair it with our **Sitemap URL Extractor**, **Broken Link Checker** and **Bulk Lighthouse Auditor** for a complete technical SEO toolkit.

### Is it legal?

The Actor fetches public web pages the same way a search engine crawler does, respects robots.txt, keeps load minimal and collects no personal data. Audit sites you own or are authorised to review, and follow each website's terms. See [Is web scraping legal?](https://blog.apify.com/is-web-scraping-legal/).

### FAQ

**Does it render JavaScript?** No – it is fast and cheap because it parses server HTML. For performance and Core Web Vitals use a Lighthouse-based audit.

**Can I use it through the API, Make, Zapier, n8n or with AI agents?** Yes – like every Apify Actor.

**Found a false positive or want a new check?** Open an issue on the Issues tab with the URL; we respond quickly.

### Need the data done for you?

If you'd rather not set up runs yourself, the UK company behind these tools (GPT Shop Ltd) offers a fixed-price done-for-you scrape. Tell us the website and the fields you need, and you get a clean CSV or Google Sheet within 48 hours of the confirmed brief, up to 5,000 rows, for £149 or $179. If we can't get the data, you're refunded in full. [See the done-for-you scraper service](https://tools.gptshopltd.com/done-for-you-scraper/?utm_source=apify\&utm_medium=readme\&utm_campaign=on-page-seo-audit)

# Actor input Schema

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

Page URLs to audit. With 'Crawl the website' or 'Use sitemap' switched on, these are the starting points and more pages of the same site are discovered automatically.

## `crawl` (type: `boolean`):

Follow links to other pages on the same host, up to 'Max pages' and 'Max crawl depth'. Off = audit exactly the URLs you list.

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

Discover pages from the site's XML sitemap (found via robots.txt or /sitemap.xml).

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

Hard limit of pages audited per run (all sites together). You pay per audited page, so this caps your cost.

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

How many clicks away from the start URL to follow when crawling.

## `includeUrlPatterns` (type: `array`):

Only audit discovered URLs matching at least one of these regular expressions, e.g. /blog/

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

Skip discovered URLs matching any of these regular expressions, e.g. /tag/ or ?page=

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

Titles shorter than this are flagged.

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

Titles longer than this are flagged (likely truncated in Google).

## `descriptionMinLength` (type: `integer`):

Descriptions shorter than this are flagged.

## `descriptionMaxLength` (type: `integer`):

Descriptions longer than this are flagged.

## `thinContentWords` (type: `integer`):

Pages with fewer visible words are flagged as thin content.

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

Pages fetched in parallel across all sites. Requests to one host are additionally spaced by the delay below.

## `requestDelayMs` (type: `integer`):

Keeps the load on the audited website negligible.

## `pageTimeoutSecs` (type: `integer`):

Give up on a page after this long (it is retried twice first). Failed pages are free.

## `respectRobotsTxt` (type: `boolean`):

Skip URLs that robots.txt disallows. Turn off only for sites you own.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.gov.uk"
    }
  ],
  "crawl": false,
  "useSitemap": false,
  "maxPages": 100,
  "maxDepth": 3,
  "includeUrlPatterns": [],
  "excludeUrlPatterns": [],
  "titleMinLength": 30,
  "titleMaxLength": 60,
  "descriptionMinLength": 70,
  "descriptionMaxLength": 160,
  "thinContentWords": 300,
  "maxConcurrency": 4,
  "requestDelayMs": 500,
  "pageTimeoutSecs": 30,
  "respectRobotsTxt": true
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

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

No description

## `summary` (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": [
        {
            "url": "https://www.gov.uk"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("chris_elder/on-page-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 = { "startUrls": [{ "url": "https://www.gov.uk" }] }

# Run the Actor and wait for it to finish
run = client.actor("chris_elder/on-page-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 '{
  "startUrls": [
    {
      "url": "https://www.gov.uk"
    }
  ]
}' |
apify call chris_elder/on-page-seo-audit --silent --output-dataset

```

## MCP server setup

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