# SEO Audit & Metadata Checker: Title, Meta, Canonical (`pavel.tashev/seo-metadata-audit`) Actor

Audit pages or crawl a website for on-page SEO: titles, meta descriptions, canonicals, hreflang, Open Graph, structured data, headings, alt text, redirects, robots and response time. Every page gets clear issues and a 0–100 score.

- **URL**: https://apify.com/pavel.tashev/seo-metadata-audit.md
- **Developed by:** [Pavel Tashev](https://apify.com/pavel.tashev) (community)
- **Categories:** SEO tools
- **Stats:** 2 total users, 1 monthly users, 50.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?

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

### What does SEO Audit & Metadata Checker do?

**SEO Audit & Metadata Checker audits web pages for on-page SEO and extracts their metadata: title, meta description, canonical, hreflang, Open Graph, Twitter cards, structured data, headings, image alt text, redirects, robots directives and server response time.** Every page gets a list of plain-language issues (error, warning or notice) and a **0–100 SEO score**.

Audit a list of pages, or turn on **crawling** to follow links across a whole website (optionally seeded from its sitemap) and get a **site summary** with duplicate titles, duplicate meta descriptions, the most common issues and the slowest pages. Because it runs on the Apify platform, you can schedule weekly audits, call it through the API, and send results to Google Sheets, Slack, Make, Zapier or n8n.

### Why use this SEO audit tool?

- **Find what hurts rankings before Google does:** missing or duplicate titles, noindex pages, canonicals pointing elsewhere, redirect chains, pages blocked for Googlebot, broken status codes.
- **Check social previews:** Open Graph and Twitter card tags for every page.
- **Validate structured data:** schema.org types from JSON-LD and microdata, and invalid JSON-LD blocks.
- **Monitor sites on a schedule** and compare scores over time.
- **Audit many client sites at once** for agencies, or run it before and after a site migration.
- **Affordable:** pay per page audited, a fraction of the price of most audit tools.

### How to audit a website for SEO

1. Click **Try for free**.
2. In **Pages or websites**, enter page URLs or a domain such as `example.com`.
3. To audit a whole site, turn on **Crawl the website** and set **Maximum pages per website**.
4. Click **Start**.
5. Open the **Output** tab: **Overview** shows score and key metrics per page, **Issues** lists the problems, **Metadata** and **Technical** show the details. In crawl mode, the **Site summary** link opens the per-site report.

### Input

| Field | Description | Default |
|---|---|---|
| **Pages or websites** | Page URLs or domains. | – |
| **Crawl the website** | Follow same-site links instead of auditing only the listed pages. | off |
| **Maximum pages per website** | Crawl limit per site (up to 5,000). | 50 |
| **Maximum crawl depth** | Clicks away from the start page. | 3 |
| **Also use the sitemap** | Add pages from the sitemap when crawling. | on |
| **Include subdomains** | Follow links to blog.example.com etc. | off |
| **Only crawl / Skip URLs matching** | Globs, text or `/regex/` patterns. | – |
| **Respect robots.txt** | Skip pages disallowed for crawlers. Turn off only for your own sites. | on |
| **Parallel requests per website** | Politeness setting. | 5 |
| **Slow response threshold (ms)** | Flag slower pages. | 2000 |
| **Include full structured data** | Add parsed JSON-LD objects. | off |

Example input:

```json
{
  "startUrls": ["example.com"],
  "crawl": true,
  "maxPagesPerSite": 200,
  "excludePatterns": ["*/tag/*"]
}
```

### Output

One item per page. A real example (shortened) from auditing https://www.python.org/about/:

```json
{
  "url": "https://www.python.org/about/",
  "finalUrl": "https://www.python.org/about/",
  "statusCode": 200,
  "score": 94,
  "errors": 0,
  "warnings": 0,
  "notices": 3,
  "indexable": true,
  "issues": [
    { "severity": "notice", "code": "title-too-short", "message": "The title has 26 characters; 30–60 is typical." },
    { "severity": "notice", "code": "meta-description-too-short", "message": "The meta description has only 52 characters." },
    { "severity": "notice", "code": "missing-canonical", "message": "The page has no canonical link." }
  ],
  "redirectCount": 0,
  "responseTimeMs": 112,
  "title": "About Python™ | Python.org",
  "titleLength": 26,
  "titlePixelWidth": 220,
  "metaDescription": "The official home of the Python Programming Language",
  "metaDescriptionLength": 52,
  "canonical": null,
  "lang": "en",
  "viewport": "width=device-width, initial-scale=1.0",
  "h1": ["Python is powerful... and fast; plays well with others; runs everywhere; is friendly & easy to learn; is Open."],
  "h1Count": 1,
  "wordCount": 692,
  "openGraph": { "title": "Welcome to Python.org", "image": "https://www.python.org/static/opengraph-icon-200x200.png", "type": "website" },
  "structuredDataTypes": ["WebSite"],
  "imagesCount": 1,
  "imagesMissingAlt": 0,
  "internalLinks": 163,
  "externalLinks": 17,
  "robotsTxtAllowed": true,
  "mixedContentCount": 0
}
```

You can download the dataset in various formats such as JSON, HTML, CSV, or Excel. Pages that could not be loaded, non-HTML files and pages skipped by robots.txt appear with an `error` message and are not charged.

### Checks and data fields

| Area | Fields | Issues flagged |
|---|---|---|
| HTTP | `statusCode`, `redirectChain`, `redirectCount`, `responseTimeMs`, `contentType` | `http-error`, `redirect-chain`, `redirected`, `slow-response`, `not-https` |
| Indexing | `indexable`, `metaRobots`, `xRobotsTag`, `canonical`, `canonicalIsSelf`, `robotsTxtAllowed` | `noindex`, `nofollow-page`, `canonical-to-other-url`, `multiple-canonicals`, `missing-canonical`, `blocked-by-robots-txt` |
| Title & description | `title`, `titleLength`, `titlePixelWidth` (estimate), `metaDescription`, `metaDescriptionLength` | `missing-title`, `empty-title`, `multiple-titles`, `title-too-long`, `title-too-short`, `missing-meta-description`, `meta-description-too-long/short`, `duplicate-title`, `duplicate-meta-description` (crawl) |
| Content | `h1`, `h1Count`, `headingCounts`, `wordCount`, `lang`, `charset`, `viewport` | `missing-h1`, `multiple-h1`, `thin-content`, `missing-lang`, `missing-viewport`, `missing-charset` |
| International | `hreflang` | `invalid-hreflang` |
| Social | `openGraph`, `twitterCard` | `missing-open-graph` |
| Structured data | `structuredDataTypes`, `microdataTypes`, `structuredData` (optional) | `invalid-structured-data`, `no-structured-data` |
| Images & links | `imagesCount`, `imagesMissingAlt`, `internalLinks`, `externalLinks`, `nofollowLinks`, `mixedContentCount`, `favicon` | `images-missing-alt`, `mixed-content` |

**Score:** 100 minus a penalty for each distinct issue type on the page: error −20, warning −8, notice −2 (minimum 0).

**Site summary (crawl mode):** key-value record `SITE_SUMMARY` with pages audited, average score, status codes, number of pages per issue, duplicate title and meta description groups, slowest pages and why the crawl stopped.

### How much does an SEO audit cost?

This Actor uses **pay-per-event pricing**: you pay per HTML page audited. Pages that fail to load, return an error status, are not HTML or are skipped by robots.txt are **free**. See the **Pricing** tab for the current price per 1,000 pages. You can set a maximum cost per run; crawling stops cleanly when it is reached.

### Tips

- **Start small:** crawl 50 pages first, then raise the limit.
- **Large sites:** use **Skip URLs matching** for tag, search and pagination URLs, and keep depth at 2–3.
- **Schedule** a weekly crawl and compare `averageScore` in the site summary.
- **Staging sites:** turn off **Respect robots.txt** only for sites you own.

### Related tools

More website audit tools:

- [Broken Link Checker](https://apify.com/pavel.tashev/broken-link-checker): crawl a website and find broken links, images and redirect chains.
- [Sitemap URL Extractor](https://apify.com/pavel.tashev/sitemap-url-extractor): list every URL of a website from its sitemaps, or only new and removed pages.
- [Website Tech Stack Detector](https://apify.com/pavel.tashev/website-tech-stack-detector): detect the CMS, frameworks, analytics and hosting of any website.

### FAQ and limitations

**Does it render JavaScript?** No. It audits the HTML the server returns, like most search engine crawlers see on the first pass. Content injected later by JavaScript is not included.

**Does it measure Core Web Vitals?** No. `responseTimeMs` is the server response time (including redirects), not a Lighthouse score.

**Is the title pixel width exact?** No, it is an estimate based on average character widths in Google's desktop font.

**Does it collect personal data?** No emails or phone numbers are extracted. Full structured data is off by default.

**Blocked pages?** Some sites refuse automated requests (HTTP 403 or 429); these pages are reported with an error and not charged.

**Need another check?** Open an issue in the **Issues** tab. Only audit websites you are allowed to crawl.

# Actor input Schema

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

Pages to audit (for example https://example.com/pricing) or domains (example.com). With crawling on, each entry is the starting point of a website crawl.

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

Off: audit exactly the pages listed above. On: follow links on the same website (and optionally its sitemap) and audit every page found, up to the page limit.

## `maxPagesPerSite` (type: `integer`):

When crawling, stop after this many pages per website.

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

When crawling, how many clicks away from the start page to follow links. Pages found in the sitemap count as depth 1.

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

When crawling, also add pages listed in the website's sitemap (found through robots.txt or /sitemap.xml).

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

When crawling, also follow links to subdomains such as blog.example.com.

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

When crawling, audit only URLs matching at least one pattern: a glob (*/blog/*), plain text (/products/) or a regular expression between slashes. Leave empty for all pages.

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

When crawling, skip URLs matching any pattern (same syntax), for example */tag/* or /?page=/.

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

Skip pages that the website's robots.txt disallows for crawlers. Turn off only for websites you own.

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

How many pages of one website are loaded at the same time. Keep it low to be polite.

## `slowResponseMs` (type: `integer`):

Flag pages whose server response (including redirects) takes longer than this.

## `includeStructuredData` (type: `boolean`):

Add the parsed JSON-LD objects to each page (up to 10 items). Off: only the schema types are listed.

## Actor input object example

```json
{
  "startUrls": [
    "https://apify.com",
    "https://www.python.org/about/"
  ],
  "crawl": false,
  "maxPagesPerSite": 50,
  "maxDepth": 3,
  "useSitemap": true,
  "includeSubdomains": false,
  "respectRobotsTxt": true,
  "maxConcurrency": 5,
  "slowResponseMs": 2000,
  "includeStructuredData": false
}
```

# Actor output Schema

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

No description

## `siteSummary` (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://apify.com",
        "https://www.python.org/about/"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("pavel.tashev/seo-metadata-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://apify.com",
        "https://www.python.org/about/",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("pavel.tashev/seo-metadata-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://apify.com",
    "https://www.python.org/about/"
  ]
}' |
apify call pavel.tashev/seo-metadata-audit --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,pavel.tashev/seo-metadata-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/YUIrV9PJMQhhUbGMX/builds/7FOJyKxYZrrR6eIoL/openapi.json
