# Site Health Watch – Daily SEO Accident Alerts (`petipeti/site-health-watch`) Actor

Schedule it daily and get told only when something silently drops your site from Google: noindex left on, robots.txt blocking the site, sitemap URLs returning 404/500, canonicals pointing elsewhere. Compares with the last run.

- **URL**: https://apify.com/petipeti/site-health-watch.md
- **Developed by:** [peti peti](https://apify.com/petipeti) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.50 / 1,000 page checkeds

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

## Site Health Watch – Daily SEO Accident Alerts

**Find out the morning after – not three weeks later in Search Console – when your site starts disappearing from Google.**

Schedule this Actor daily. It reads your sitemap, checks every page, compares with the previous run and puts **new accidents first**:

| Severity | Accident |
|---|---|
| 🔴 critical | **robots.txt blocks the whole site** for Googlebot, Bingbot or all crawlers (classic: staging robots.txt deployed to production) |
| 🔴 critical | **`noindex` on a page that is in your sitemap** (meta robots, googlebot meta or `X-Robots-Tag` header) |
| 🔴 critical | Sitemap URL returns **404 / 410 / 500** |
| 🔴 critical | Sitemap can't be found or read |
| 🟡 warning | **Canonical points to another page** (e.g. every page canonicalised to the home page after a template change) |
| 🟡 warning | Sitemap URL redirects, page has no `<title>`, page could not be checked |
| ⚪ info | URLs added to / removed from the sitemap, robots.txt edited, titles changed |

On a quiet day you get one summary row: `ok – No problems found on 184 page(s).`

### Why this one

Most link / status / SEO checkers on the Store run **once** and list everything every time. Site Health Watch **remembers the last run** (in a named key-value store in your account) and tells apart:

- **new** problems – marked `isNew`, sorted first, and counted in the summary (`2 critical NEW since last run`)
- problems that were already there – still listed (or hidden with *Only report new problems and changes*)
- problems that were **fixed** since last time – counted in the summary

So you can wire it to an email / Slack integration on the summary status `new-critical` and only be pinged when something actually broke.

### Polite by design

- Only the sites you list. It reads robots.txt first and **never fetches a disallowed URL**; if robots.txt returns a server error it stops (RFC 9309).
- One request at a time, at least 1 second apart (default 2 s), longer if `Crawl-delay` says so.
- Identifies itself: `SiteHealthWatch/1.0`. Collects no personal data.

### Input

```json
{
  "siteUrls": [{ "url": "https://www.example.com/" }],
  "maxPagesPerSite": 500,
  "onlyNewAndChanged": true
}
```

Then **Schedules → Create schedule → daily**. The first run saves a baseline (current problems are listed, none marked new).

### Output

```json
[
  { "type": "summary", "site": "https://www.example.com", "status": "new-critical",
    "message": "3 critical, 1 warning(s) – 2 critical NEW since last run. 1 earlier problem(s) fixed.",
    "critical": 3, "warnings": 1, "newIssues": 2, "fixed": 1, "pagesChecked": 184, "sitemapUrls": 184 },
  { "type": "page-noindex", "severity": "critical", "isNew": true, "url": "https://www.example.com/pricing",
    "message": "noindex is set on a URL listed in the sitemap – it will be removed from search results" },
  { "type": "urls-removed", "severity": "info", "message": "2 URL(s) removed from the sitemap", "urls": ["https://www.example.com/old-a", "https://www.example.com/old-b"] }
]
```

### Pricing

Pay per event, platform usage included:

| Event | Price |
|---|---|
| Actor start | $0.01 per run |
| `page-checked` – one page checked | $0.50 per 1,000 pages |

Example: a 200-page site checked every day ≈ 30 × ($0.01 + 200 × $0.0005) = **$3.30 per month**. Pages skipped because of robots.txt or errors are free. A run without a site only returns a setup reminder.

### 日本語での説明

自分のサイトを毎日チェックし、「検索から消える事故」が起きたときだけ知らせます。たとえば、robots.txt でサイト全体をブロックしてしまった、sitemap に載っているページに noindex が付いた、404・500 になった、canonical が別のページを指すようになった、などです。前回の結果を覚えているので、新しく起きた問題を先頭に出し、直った問題も数えます。robots.txt を守り、1秒以上の間隔を空けてアクセスします。

料金: 1回 $0.01 ＋ 1ページ $0.0005（200ページのサイトを毎日見ると、月 約 $3.30）。

# Actor input Schema

## `siteUrls` (type: `array`):

Home page of each website you own or manage (or the URL of its sitemap.xml). The sitemap is found via robots.txt or /sitemap.xml; every URL in it is checked.

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

Upper limit of sitemap URLs checked per site and run.

## `onlyNewAndChanged` (type: `boolean`):

On: after the first run, problems that already existed last time are left out, so a quiet day gives just the summary row. The summary still counts all problems.

## `delaySeconds` (type: `integer`):

At least 1 second. A longer Crawl-delay in robots.txt is used instead.

## `stateStoreName` (type: `string`):

Named key-value store in your account where each site's last snapshot is kept. Change it only to keep separate histories (e.g. staging vs production).

## Actor input object example

```json
{
  "siteUrls": [
    {
      "url": "https://example.com/"
    }
  ],
  "maxPagesPerSite": 200,
  "onlyNewAndChanged": false,
  "delaySeconds": 2,
  "stateStoreName": "site-health-watch-state"
}
```

# Actor output Schema

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

First row per site: summary (ok / warnings / critical / new-critical). Then one row per finding, new ones first.

# 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 = {
    "siteUrls": [
        {
            "url": "https://example.com/"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("petipeti/site-health-watch").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 = { "siteUrls": [{ "url": "https://example.com/" }] }

# Run the Actor and wait for it to finish
run = client.actor("petipeti/site-health-watch").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 '{
  "siteUrls": [
    {
      "url": "https://example.com/"
    }
  ]
}' |
apify call petipeti/site-health-watch --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,petipeti/site-health-watch"
        }
    }
}
```

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/WbRdXsLtR1Ah9alDu/builds/zB80fMRphhRcthNiq/openapi.json
