# Technical SEO Audit - On-Page, Schema, Robots, DNS (`s-r/seo-audit-scraper`) Actor

Audit any page for the technical SEO problems that cost rankings: indexability, titles, meta descriptions, headings, canonicals, structured data, alt text, redirect chains, security headers, robots.txt, sitemaps, AI crawler access, SSL and DNS. Every page gets a score and a list of findings.

- **URL**: https://apify.com/s-r/seo-audit-scraper.md
- **Developed by:** [SR](https://apify.com/s-r) (community)
- **Categories:** SEO tools, Developer tools
- **Stats:** 1 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 actor run starteds

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

## Technical SEO Audit

Point this Actor at a page and it tells you, in plain language, what is wrong
with it. Not a wall of raw tags: a **score out of 100** and a list of findings,
each one graded by how much it actually costs you.

Most SEO tools give you a dashboard and leave the interpretation to you. This
one does the interpretation. Every finding says what is wrong, why it matters,
and what the number is. *"The title is 75 characters; past about 60 it gets
truncated in results."* You can act on that without knowing what a canonical
tag is.

### What it checks

**Whether the page can be indexed at all.** This comes first, because
everything else is decorative if the answer is no. Three separate switches can
keep a page out of a search index and the Actor checks all three: a `noindex`
in the robots meta tag, the same directive arriving as an `X-Robots-Tag`
response header, and a canonical tag pointing at a different URL. The header
case is the one people miss, because it is invisible when you view source.

**The tags that decide how you appear in results.** Title and meta description
with their lengths measured against the point where search engines cut them
off. Canonical URL, resolved to absolute and compared properly, so a canonical
of `https://example.com` on the page served at `https://example.com/` is
correctly read as the same address rather than reported as a conflict.

**Structure.** Heading counts by level, the text of every H1, and whether the
outline skips a level. Word count taken from visible prose only, with scripts
and styles excluded, plus the text-to-HTML ratio.

**Structured data.** Every JSON-LD block is parsed and its schema.org types
listed. Blocks that fail to parse are reported individually with the line the
JSON breaks on. Broken structured data is invisible on the page and silently
costs you rich results, so it is worth catching.

**Images and links.** Images with no `alt` attribute are counted separately
from images with a deliberately empty one, because an empty `alt` is correct
for decoration and flagging it would be wrong. Images with no declared width
and height are counted too, since those cause the layout to jump while a page
loads. Links are split into internal and external, with nofollow counted and
empty anchors flagged.

**The full redirect chain.** Every hop is recorded with its status code and
where it points. A page reached through three redirects wastes crawl budget,
and you cannot see that from the final URL alone.

**Security headers.** Six standard protective headers, each reported present or
missing, with a count.

**robots.txt, sitemaps and llms.txt.** Whether robots.txt exists, how many
rules it carries, which sitemaps it declares, and whether those sitemaps
actually resolve to real URLs. A sitemap is only counted when the response
carries a real sitemap root element, so a site that answers unknown paths with
its ordinary page shell is not credited with a sitemap it does not have.

**Which crawlers your robots.txt lets in.** Search crawlers and AI crawlers are
reported separately, because they are separate decisions. A site can be
perfectly open to search and closed to model training, and that is usually
deliberate rather than a mistake, so it is reported as something to know rather
than as a defect. Blocking search crawlers, by contrast, is treated as critical.
The allow-or-deny answer comes from the same rule matching a real crawler uses,
including group selection and wildcards, rather than from reading the
`Disallow` lines and hoping.

**The certificate.** Issuer, protocol version, cipher, expiry date, days
remaining, and whether the certificate actually covers the hostname you asked
for. An invalid or mismatched certificate is critical; one expiring within a
month is a warning.

**DNS.** A, AAAA, MX, NS, TXT and CNAME records, plus whether the domain
publishes SPF and DMARC. Missing SPF or DMARC makes your domain easier to spoof,
which is worth knowing even when you came here for search rankings. A lookup
that never answered is reported apart from one that answered empty, so "no
records" never gets confused with "we could not ask".

### Scoring

Each page starts at 100 and loses weight for every finding: 15 for a critical,
5 for a warning, 1 for a notice. Results are sorted worst first, so the page
that needs attention is the first row you see.

The three severities mean three different things:

- **critical** stops the page being indexed or served at all
- **warning** costs you ranking or clicks
- **notice** is worth knowing but is not a defect

### Input

Give it a list of URLs. A bare domain such as `example.com` is accepted and
audited as its homepage. You can also set how many pages to fetch at once, how
many times to retry, and optionally which country to request from when a site
serves different content by region.

Auditing many pages of **one** site is much cheaper than auditing many sites.
robots.txt, the sitemaps, DNS and the certificate belong to the site rather than
the page, so they are looked up once and shared across every page on that
origin. In practice fifty pages of one site cost fifty page requests plus one
set of site lookups.

### Output

One row per page, containing every fact listed above plus:

- `score`, and counts of critical, warning and notice findings
- `findings`, each with a severity, a stable code and a readable message
- `internal_urls`, the internal links found on the page, which you can feed
  straight back in as the input for a wider audit

A page that could not be fetched is still a row. It carries the error and a
critical finding rather than quietly disappearing from the results, because a
missing row is the one thing an audit must never produce.

The run summary holds the counts, the average score and the totals by severity.

### Notes

Only what the server sends is inspected. Content that a page builds in the
browser after loading is not part of the audit, so a fully client-rendered page
will honestly report the small shell that was actually served. That is usually
the finding rather than a limitation: it is also what a search engine sees on
its first pass.

Retries apply only to timeouts and server errors. A 404 is an answer, and it is
reported as one.

# Actor input Schema

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

Pages to audit. A bare domain such as example.com is accepted and audited as its homepage. Pages on the same site share one set of robots, sitemap, DNS and certificate lookups, so auditing many pages of one site is much cheaper than auditing many sites.

## `limit` (type: `integer`):

How many pages to audit in one run. Each page costs one fetch.

## `concurrency` (type: `integer`):

How many pages to fetch at the same time. Lower this if you are auditing a small server you do not want to load.

## `retries` (type: `integer`):

Retries with backoff before a page is reported as unreachable. Only timeouts and server errors are retried; a 404 is an answer and is reported as one.

## `country` (type: `string`):

Optional two-letter country code to request the pages from, for example us or de. Use this when the site serves different content or redirects by region. Leave empty to request from wherever the run happens to sit.

## Actor input object example

```json
{
  "urls": [
    "apify.com",
    "https://www.python.org/"
  ],
  "limit": 50,
  "concurrency": 5,
  "retries": 3,
  "country": "us"
}
```

# Actor output Schema

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

One row per page, worst score first.

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

Counts, average score and how many issues were found by severity.

## `errors` (type: `string`):

Failures with a code and a redacted message.

# 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": [
        "apify.com",
        "https://www.python.org/"
    ],
    "limit": 50,
    "concurrency": 5,
    "retries": 3
};

// Run the Actor and wait for it to finish
const run = await client.actor("s-r/seo-audit-scraper").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": [
        "apify.com",
        "https://www.python.org/",
    ],
    "limit": 50,
    "concurrency": 5,
    "retries": 3,
}

# Run the Actor and wait for it to finish
run = client.actor("s-r/seo-audit-scraper").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": [
    "apify.com",
    "https://www.python.org/"
  ],
  "limit": 50,
  "concurrency": 5,
  "retries": 3
}' |
apify call s-r/seo-audit-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,s-r/seo-audit-scraper"
        }
    }
}
```

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/bkk0amsmxGkWskjm4/builds/Wpr2Q8xad2FbHNKj5/openapi.json
