# Static SEO Audit - Score and Fixes for URL Lists (`quillray/static-seo-audit`) Actor

Audit a list of pages for 18 on-page SEO issues. Get a 0-100 score, a fix for every issue and what changed since your last audit. No browser, pay per page.

- **URL**: https://apify.com/quillray/static-seo-audit.md
- **Developed by:** [Quillray](https://apify.com/quillray) (community)
- **Categories:** SEO tools, Marketing
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$10.00 / 1,000 page audits

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

## Static SEO Audit

**Check a list of pages for on-page SEO problems and get a concrete fix for each one.**

Paste the addresses of the pages you want to check. For every page you get a score from 0 to 100, the list of issues with a fix for each, and what changed since your last audit of the same page. It reads the HTML your server sends, without a browser and without crawling the whole site, so it is fast and cheap. There is no subscription. You pay only for the pages audited.

### What it does

- Audits up to 100 pages per run against 18 on-page rules: title, meta description, headings, canonical link, noindex, image alt text, page language, viewport, structured data, Open Graph tags and redirects.
- Gives each page a score from 0 to 100 and lists every issue with its severity and a fix in plain English.
- Compares with your previous audit of the same page and shows the previous score, the change, new issues and resolved issues.
- Audits exactly the pages you list. It does not crawl the site.
- Follows robots.txt and identifies itself to site owners with its own User-Agent.
- Works with Apify Schedules, so you can watch a site week after week.

### How to use it

1. Click **Try for free**.
2. Paste the page URLs, one per line.
3. Click **Start**. A run with a few pages usually takes under a minute.
4. Open the **Output** tab to see scores and issues, or export them as CSV, Excel or JSON.
5. Optional: add a **schedule** to audit the same pages regularly, and an **integration** (email, Slack, Make, Zapier) to get the result after each run. The run's status message sums it up, for example `Audited 10 pages, average score 78, 2 pages got worse`.

Example input:

```json
{ "urls": ["https://example.com/", "https://www.iana.org/help/example-domains"] }
```

### Input

| Field  | Default  | What it does                                                       |
| ------ | -------- | ------------------------------------------------------------------ |
| `urls` | required | 1-100 page addresses (http or https). Duplicates are audited once. |

### Output

One row for each page. You can view the results as a table in the Output tab or export them.

Example row for a page audited for the second time:

```json
{
  "url": "https://example.com/",
  "status": "audited",
  "score": 80,
  "previousScore": 67,
  "scoreChange": 13,
  "newIssues": [],
  "resolvedIssues": ["h1-missing"],
  "issues": [
    {
      "id": "meta-description-missing",
      "severity": "error",
      "message": "The page has no <meta name=\"description\"> or its content is empty.",
      "fix": "Add <meta name=\"description\" content=\"...\"> with a short summary of the page."
    }
  ]
}
```

Main fields:

- **status**: `audited`, `blocked-by-robots` (the site's robots.txt does not allow the page) or `error` (the page could not be fetched, the `error` field says why).
- **score**: 0 to 100, only for audited pages. A failed page never gets a score, so a failure cannot look like a result.
- **previousScore, scoreChange**: the score from your last audit of this page and the difference. Negative means the page got worse.
- **newIssues, resolvedIssues**: issues that appeared or disappeared since the last audit.
- **issues**: every rule that fired, with `severity`, `message` and `fix`.
- Page details for a quick review: `title`, `metaDescription`, `h1`, `canonical`, `lang`, `structuredDataTypes` and `httpStatus`.

### Score and rules

The score starts at 100. Each error takes away 15 points, each warning 5 and each notice 2, down to 0. A rule counts once, however many elements it finds.

| Rule                       | Severity | What it checks                                                    |
| -------------------------- | -------- | ----------------------------------------------------------------- |
| `title-missing`            | error    | The page has no title, or it is empty.                            |
| `title-multiple`           | warning  | The page has more than one title.                                 |
| `title-length`             | warning  | The title is shorter than 15 or longer than 60 characters.        |
| `meta-description-missing` | error    | The page has no meta description, or it is empty.                 |
| `meta-description-length`  | warning  | The description is shorter than 50 or longer than 160 characters. |
| `h1-missing`               | error    | The page has no H1 heading with text.                             |
| `h1-multiple`              | notice   | The page has more than one H1 heading.                            |
| `heading-skip`             | notice   | A heading level is skipped, for example H2 followed by H4.        |
| `canonical-missing`        | warning  | The page has no canonical link.                                   |
| `canonical-multiple`       | error    | The page has more than one canonical link.                        |
| `canonical-invalid`        | error    | A canonical link is empty or not a valid web address.             |
| `noindex`                  | error    | The page tells search engines not to index it.                    |
| `img-alt-missing`          | warning  | An image has no alt attribute.                                    |
| `html-lang-missing`        | notice   | The page does not declare its language.                           |
| `viewport-missing`         | warning  | The page has no viewport tag for mobile screens.                  |
| `json-ld-invalid`          | error    | A structured data block is not valid JSON.                        |
| `open-graph-missing`       | notice   | The og:title, og:description or og:image tag is missing.          |
| `redirected`               | notice   | The address redirects to another one.                             |

The length limits and weights are this actor's settings. A good score is not a promise about search rankings.

### Cost

**10.00 USD per 1,000 audited pages** (event `page-audited`). Pages that end with an error or are blocked by robots.txt are free. Platform compute of this actor is not charged to you.

**Example:** auditing 50 pages once a week for a month (about 215 audits) costs about 2.15 USD.

Tip: set a **maximum cost per run** in the run options if you want a hard limit. Prices can change, and Apify shows the current ones before you run.

### Limitations

- Pages built by JavaScript are not rendered. If your title, headings or description are added by a script, the audit may report them as missing. Use a browser-based tool for such sites.
- robots.txt is followed. A page the site does not allow is reported as blocked and is not audited.
- Pages of about 1 MB of HTML or more are skipped with an error.
- One run audits up to 100 pages and stops starting new pages after 15 minutes.
- Run one schedule per list at a time. Two runs that audit the same page at the same moment can both report the same change.

### Privacy and responsibility

- **The actor runs in your Apify account.** The publisher of this actor does not store, receive or see your input, your results or the pages you audit.
- **What is stored, and where.** For each audited page the actor keeps only the time, the score and the list of issue ids (never the HTML or any text of the page) in a Key-Value Store named `seo-audit-history` in your Apify account. Results go to your run's dataset. You can view, export and delete both at any time in Apify Console.
- **You choose the pages and you are responsible for that choice.** Audit only pages you may fetch. Check each site's terms of use and the law that applies to you before you add it. The actor follows robots.txt, but that is not a legal check.
- **Personal data.** The actor does not look for personal data and the history holds only SEO fields. A result row may repeat short text from the page, such as its title. Whether you may process such a page is your decision and your responsibility.

### FAQ

**Will a score of 100 get my page to the top of Google?** No. The audit checks common on-page basics. Rankings depend on many other things.

**Why does it say my title is missing when I can see it?** Your site probably adds the title with JavaScript. This actor reads only the HTML the server sends.

**Why are there no score changes on my first run?** The first run has nothing to compare with. Changes show up from the second audit of the same page.

**Can I start the history over?** Yes. In Apify Console open Storage, then Key-value stores, and delete the store `seo-audit-history`.

**Am I charged for pages that fail?** No. Errors and blocked pages are free.

**Something not working, or missing a feature?** Open an issue on this actor's page. We read every one and reply as soon as we can.

# Actor input Schema

## `urls` (type: `array`):

Public http(s) pages to audit, 1 to 100, each at most 2048 characters. Duplicates are removed after normalizing the URL (host case, default port, #fragment). Addresses with a user name or password are rejected, and private or local addresses give an error row.

## Actor input object example

```json
{
  "urls": [
    "https://example.com/",
    "https://www.iana.org/help/example-domains"
  ]
}
```

# Actor output Schema

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

One row per page: score, issues with fixes, and the change since the previous audit.

# 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 = {
    "urls": [
        "https://example.com/",
        "https://www.iana.org/help/example-domains"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("quillray/static-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 = { "urls": [
        "https://example.com/",
        "https://www.iana.org/help/example-domains",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("quillray/static-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 '{
  "urls": [
    "https://example.com/",
    "https://www.iana.org/help/example-domains"
  ]
}' |
apify call quillray/static-seo-audit --silent --output-dataset

```

## MCP server setup

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