# Website SEO Audit - Broken Links, Meta Tags & Core Web Vitals (`siteauditlab/website-seo-audit`) Actor

Crawl whole websites and get a 0-100 SEO score per page: titles, meta descriptions, H1, canonical, noindex, broken links, Core Web Vitals (LCP, CLS, TBT), page weight, structured data, sitemap and robots.txt, plus a shareable HTML report.

- **URL**: https://apify.com/siteauditlab/website-seo-audit.md
- **Developed by:** [Site Audit Lab](https://apify.com/siteauditlab) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

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

## Website SEO Audit - Broken Links, Meta Tags & Core Web Vitals

Run a full technical and on-page SEO audit of entire websites in one go. Give a list of websites. This Actor reads each site's sitemap and follows its internal links, opening every page in a real Chrome browser. It returns:

- an **SEO score out of 100** for every page,
- every problem found, ranked **error / warning / notice**, with the value that caused it,
- **broken links** (404, 410, 5xx, unreachable), with the page where each one was found,
- **Core Web Vitals** lab metrics: LCP, CLS, Total Blocking Time, server response time and page weight,
- **duplicate titles and meta descriptions** across the site,
- a **shareable HTML report** per run, ready to send to a client.

It's built for SEO agencies, freelancers, web developers and site owners who need a fast site audit without a monthly SEO tool subscription.

### What it checks

| Area | Checks |
| --- | --- |
| Indexability | HTTP status, redirects, `noindex` (meta robots and X-Robots-Tag), canonical tag, robots.txt, sitemap.xml |
| On-page | Title (missing, too long, too short, duplicate), meta description (missing, length, duplicate), H1 (missing, multiple), H2/H3 counts, word count, `lang` attribute, hreflang |
| Links | Internal and external links on every page, broken links with anchor text, internal/external link counts |
| Speed | Largest Contentful Paint, Cumulative Layout Shift, Total Blocking Time, time to first byte, load time, page weight, number of requests |
| Mobile & security | Viewport meta tag, HTTPS, mixed content |
| Rich results & social | Structured data types (JSON-LD and microdata), Open Graph tags, Twitter card |
| Images | Images without alt text |

### Why use it

- **Whole sites, in bulk.** Audit hundreds of websites in one run, for example to qualify prospects or monitor client sites every week.
- **JavaScript sites included.** Pages are rendered in Chrome, so React, Vue and other single-page applications are audited as Google sees them.
- **Sitemap and links.** It finds orphan pages through the sitemap as well as linked pages.
- **Fast link checking.** Each unique link is checked once per run, even if it appears on every page.
- **Pay per page.** There is no subscription: you only pay for the pages audited.

### Input

| Field | Description | Default |
| --- | --- | --- |
| Websites to audit | One or more URLs. The audit starts there. | required |
| Maximum pages per website | Pages to audit per site. Use 1 to audit only the URLs you entered. | 20 |
| Use the sitemap | Also audit pages listed in sitemap.xml | Yes |
| Check links | All links, internal links only, or none | All |
| Maximum links checked per page | Limit for very large pages | 200 |
| Device | Desktop (1366 px) or mobile (390 px) | Desktop |
| Proxy | Only for websites that block automated visits | None |

Example input:

```json
{
  "startUrls": [{ "url": "https://example.com" }, { "url": "https://another-site.com" }],
  "maxPagesPerSite": 50,
  "checkLinks": "all",
  "device": "mobile"
}
```

### Output

One dataset item per page (shortened):

```json
{
  "url": "https://example.com/about",
  "statusCode": 200,
  "seoScore": 74,
  "errors": 1,
  "warnings": 2,
  "notices": 3,
  "issues": [
    { "id": "broken-links", "severity": "error", "message": "The page links to broken URLs (404, 410, 5xx or unreachable)", "details": "1 broken links" },
    { "id": "description-missing", "severity": "warning", "message": "Missing meta description" }
  ],
  "title": "About us | Example",
  "titleLength": 18,
  "metaDescription": null,
  "h1": ["About us"],
  "canonical": "https://example.com/about",
  "indexable": true,
  "structuredDataTypes": ["Organization"],
  "wordCount": 420,
  "brokenLinks": [{ "url": "https://example.com/old-page", "status": 404, "anchorText": "Our history", "internal": true }],
  "performance": { "ttfbMs": 180, "lcpMs": 1900, "cls": 0.02, "totalBlockingTimeMs": 120, "pageWeightKb": 1450, "requests": 48 }
}
```

The run's key-value store also contains:

- **REPORT**: an HTML report per website with the average score, issues by severity, broken links, duplicate titles and descriptions, and the pages to fix first.
- **SUMMARY**: the same summary as JSON.

You can export the dataset as JSON, CSV, Excel or HTML. You can also call the Actor through the Apify API, run it on a schedule, or use it from Make, Zapier or n8n.

### How the score works

Each page starts at 100. Each problem found removes points: 10 for an error, 5 for a warning, 1 for a notice. Use the score to compare pages and track progress over time.

### Good to know

- Speed metrics are lab measurements taken from a data center with a real Chrome browser. They are reliable for comparing pages and spotting slow ones, but real visitors (Google's field data) may see different values.
- Some websites, such as LinkedIn, refuse automated link checks. These links are counted as `linksUnverified`, not as broken.
- Only audit websites you own or have permission to test.

### FAQ

**Can I audit only a list of pages?**
Yes. Set "Maximum pages per website" to 1, turn off "Use the sitemap" and list the exact pages.

**Should I use desktop or mobile?**
Google indexes the mobile version of websites, so mobile gives the most relevant results for SEO.

**Does it also check accessibility?**
For a full WCAG / European Accessibility Act audit, use our Website Accessibility Audit Actor.

# Actor input Schema

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

Home page or any page of each website. The audit starts there, reads the sitemap and follows internal links of the same website.

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

How many pages to audit for each website. Use 1 to audit only the URLs you entered.

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

Also take pages from the website's sitemap.xml, so pages that are not linked from the home page are audited too.

## `checkLinks` (type: `string`):

Which links found on the pages are checked for errors (404, 410, 5xx, unreachable).

## `maxLinksPerPage` (type: `integer`):

Each unique link is checked once per run, even if it appears on many pages.

## `device` (type: `string`):

Screen size used to load the pages. Google indexes the mobile version of websites.

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

Only needed for websites that block automated visits.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://apify.com"
    }
  ],
  "maxPagesPerSite": 5,
  "useSitemap": true,
  "checkLinks": "all",
  "maxLinksPerPage": 200,
  "device": "desktop",
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

One item per audited page with SEO score, issues, meta tags, links and speed metrics.

## `report` (type: `string`):

Shareable HTML report per website.

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

Site-level summary with broken links and duplicates.

# 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://apify.com"
        }
    ],
    "maxPagesPerSite": 5
};

// Run the Actor and wait for it to finish
const run = await client.actor("siteauditlab/website-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://apify.com" }],
    "maxPagesPerSite": 5,
}

# Run the Actor and wait for it to finish
run = client.actor("siteauditlab/website-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://apify.com"
    }
  ],
  "maxPagesPerSite": 5
}' |
apify call siteauditlab/website-seo-audit --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,siteauditlab/website-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/c239Jx5Rb1dc48Xgg/builds/Yg0vrLmdQA3gQzlkP/openapi.json
