# SEO Analyzer (`perdurable_kitemark/seo-analyzer`) Actor

Analyze the on-page SEO of public web pages: title, meta description, headings, canonical, Open Graph tags, word count, and a documented issue list. Plain HTTP fetch, no JavaScript rendering.

- **URL**: https://apify.com/perdurable_kitemark/seo-analyzer.md
- **Developed by:** [Cesar Santana](https://apify.com/perdurable_kitemark) (community)
- **Categories:** SEO tools, Developer tools, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 analyzed pages

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

## SEO Analyzer

### What it does

Analyzes the on-page SEO of public web pages. For each URL it fetches the page
with a plain HTTP GET (no JavaScript rendering, no headless browser), parses the
HTML, and delivers one record with the title, meta description, canonical link,
robots meta, headings, Open Graph tags, language, word count, and a list of
detected issues from a documented rule set.

### Inputs

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `urls` | array of strings | yes | `["https://example.com"]` | Public page URLs to analyze. Maximum 500 per run. |

### Outputs

One dataset record per URL. `status: "ok"` records carry the full analysis;
`status: "error"` records (transport failures: DNS, TCP, TLS, timeout, invalid
URL) carry only `url`, `errorMessage`, `fetchedAt`.

| Field | Type | Description |
|---|---|---|
| `status` | string | `ok` (analysis delivered) or `error` (fetch failed). |
| `url` | string | The requested URL. |
| `finalUrl` | string | Final URL after redirects. |
| `statusCode` | integer | HTTP status of the final response. |
| `responseTimeMs` | integer | Fetch wall time in milliseconds. |
| `title` | string | null | `<title>` contents. |
| `metaDescription` | string | null | `meta[name=description]`. |
| `canonical` | string | null | `link[rel=canonical]` href. |
| `robotsMeta` | string | null | `meta[name=robots]`. |
| `h1` | array | Text of every `<h1>`. |
| `h2Count` | integer | Number of `<h2>` elements. |
| `ogTitle` / `ogDescription` / `ogImage` | string | null | Open Graph tags. |
| `lang` | string | null | `lang` attribute of `<html>`. |
| `wordCount` | integer | Visible body word count (script/style removed). |
| `issues` | array | Issue codes (see rule set below). |
| `errorMessage` | string | `[error]` Why the fetch failed. |
| `fetchedAt` | string | ISO 8601 timestamp. |

Issue rule set: `missing-title`, `title-too-short` (<30 chars),
`title-too-long` (>60 chars), `missing-meta-description`, `missing-h1`,
`multiple-h1`, `missing-canonical`, `non-200-status`.

Example record:

```json
{
    "status": "ok",
    "url": "https://example.com",
    "finalUrl": "https://example.com/",
    "statusCode": 200,
    "responseTimeMs": 83,
    "title": "Example Domain",
    "metaDescription": null,
    "canonical": null,
    "robotsMeta": null,
    "h1": ["Example Domain"],
    "h2Count": 0,
    "ogTitle": null,
    "ogDescription": null,
    "ogImage": null,
    "lang": null,
    "wordCount": 24,
    "issues": ["title-too-short", "missing-meta-description", "missing-canonical"],
    "fetchedAt": "2026-10-12T08:46:13.717Z"
}
```

### Pricing

Pay-per-event: one `result` event per analyzed URL — i.e. per record with
`status: "ok"`. Records with `status: "error"` (failed fetches) are delivered
**free**. You are never charged for a URL we could not fetch, and a run that
delivers zero analyses fails loudly instead of charging anything.

Final per-result price is set at publish under the Gate 1 rule (>= 3x measured
platform cost, <= usage-dominant incumbent's per-item price).

### Limits

- Plain HTTP GET only — no JavaScript rendering, no headless browser. Pages
  that require JS to render will be analyzed as their raw HTML.
- Maximum 500 URLs per run; 20s per-request timeout; up to 2 retries with
  backoff on network errors and HTTP 5xx (4xx is not retried).
- A persistent 5xx is still an HTTP response: it is delivered as an `ok`
  record carrying the `non-200-status` issue.
- URLs are fetched sequentially; runtime grows roughly linearly with URL count.
- If every URL fails to fetch, the run fails with a clear status message.

### Changelog

#### 0.0

- Initial release.

***

House conventions (not actor.json fields — set on the platform at publish):
pay-per-event monetization only; no Standby mode; limited permissions only.
Charge via `Actor.charge({ eventName })` ONLY after a result is delivered;
never charge on failures. Fail loudly (`Actor.fail`) when zero results are
delivered, so broken runs never look successful.

# Actor input Schema

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

Public web page URLs to analyze (plain HTTP GET, no JavaScript rendering). Maximum 500 per run.

## Actor input object example

```json
{
  "urls": [
    "https://example.com"
  ]
}
```

# Actor output Schema

## `results` (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 = {
    "urls": [
        "https://example.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("perdurable_kitemark/seo-analyzer").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"] }

# Run the Actor and wait for it to finish
run = client.actor("perdurable_kitemark/seo-analyzer").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"
  ]
}' |
apify call perdurable_kitemark/seo-analyzer --silent --output-dataset

```

## MCP server setup

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

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/7Ig3BCI5jIVbQz2Qt/builds/kXqpxBTsh6xLekRHu/openapi.json
