# Sitemap Diff: Added, Removed and Changed URLs (`zendren/sitemap-snapshot-audit`) Actor

Fetch one public sitemap.xml, or paste the XML, and compare it with an optional previous URL or pasted snapshot. JSON lists added, removed and changed URLs, duplicates and warnings. Child sitemaps are listed, not fetched. $0.005 per sitemap.

- **URL**: https://apify.com/zendren/sitemap-snapshot-audit.md
- **Developed by:** [Xander Lopez](https://apify.com/zendren) (community)
- **Categories:** Developer tools, SEO tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $5.00 / 1,000 processed documents

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

## Sitemap Diff – Added, Removed & Changed URLs to JSON

Give it a public `sitemap.xml` URL — or paste the XML — (optionally with a previous snapshot to diff against) and get a clean JSON audit: every URL with `lastmod`/`changefreq`/`priority`, plus **added**, **removed** and **changed** URLs, duplicates and warnings.

### Why use it

- **Change monitoring** — spot new pages, deleted pages and content updates on competitor or your own sites (schedule runs and diff against your last archived snapshot URL or pasted XML).
- **SEO QA** — catch duplicate URLs, invalid priorities and malformed entries before Google does.
- **Cheap and deterministic** — flat per-sitemap price, sorted unique output, SHA-256 per snapshot.
- **No crawling** — fetches only the sitemap files you list (no link following, no proxies).

### Input

| Field | Description |
|---|---|
| `publicDataOnly` | Must be `true`. |
| `sitemapUrls[]` | Public sitemap locations to fetch: plain URL strings or objects `{"url": "https://…", "previousUrl": "https://…"}` (`previousUrl` optional: an earlier snapshot of the same type to diff against). |
| `documents[]` | Optional pasted snapshots: objects with `xml` (required, current sitemap) and optional `previousXml`. |

Supply at least one of `sitemapUrls` or `documents`; **1–5 items in total per run** (URLs + pasted documents).

Supports standard `urlset` and `sitemapindex` (child sitemaps are listed, never fetched or recursed into). 1 MiB and 5,000 entries per snapshot. Compressed `.xml.gz` files are not unpacked; no DTDs/entities. Without `previousXml`, every URL is reported as added. JSON input under 8 MB, output under 1 MB per document. Fixed 256 MB memory. Each document is parsed in an isolated child process (no Apify credentials, 160 MiB memory, 15 s CPU / 20 s wall-time limits). Nothing in the input is executed.

Example input (the public sitemaps.org sitemap) is prefilled in Console and in `INPUT.example.json`:

```json
{"publicDataOnly": true, "sitemapUrls": [{"url": "https://www.sitemaps.org/sitemap.xml"}]}
```

#### Fetching from URLs: safety and limits

The Actor downloads only what you list, from the main process; the parser still runs in an isolated, network-free child process.

- Public `http`/`https` URLs only, ports 80 and 443 only, no credentials in the URL (`user:pass@`).
- The hostname is resolved and **every** resulting address must be publicly routable. Localhost, private (10/8, 172.16/12, 192.168/16), link-local/cloud-metadata (169.254/16), CGNAT (100.64/10), multicast, reserved and IPv6 loopback/ULA/site-local addresses are refused (`URL_BLOCKED`). The connection is pinned to the vetted address, so DNS rebinding cannot redirect it.
- Up to 3 redirects, each hop re-validated the same way. 10 s connect timeout, 20 s total per URL. No cookies, no authentication headers, no proxies.
- Responses are capped at 1 MiB per sitemap file (gzip transfer encoding accepted; the decompressed size is capped too, and the download is aborted once the cap is exceeded).
- Failed fetches (`URL_BLOCKED`, `FETCH_FAILED`, `FETCH_TIMEOUT`, `TOO_LARGE`, `WRONG_CONTENT`) are listed in `OUTPUT.errors` with `documentIndex`, the error code and the URL, and are **never charged**. Only successfully processed items are charged.
- `documentIndex` counts pasted `documents` first, then URL entries. Every output item has `sourceUrl` (`null` for pasted documents) and `fetchedUrls`; `OUTPUT.fetchedUrls` is the number of successful downloads in the run.
- Requests identify as `ZendrenApifyActor/0.2 (+https://apify.com/zendren)`. Only fetch content you are permitted to process.

### Output

```json
{"documentIndex": 0, "kind": "sitemap", "type": "urlset", "entryCount": 3, "comparedToPrevious": true,
 "sourceUrl": "https://www.sitemaps.org/sitemap.xml", "previousSourceUrl": null, "fetchedUrls": 1,
 "added": ["https://www.example.com/blog/launch"], "removed": ["https://www.example.com/old-page"],
 "changed": ["https://www.example.com/pricing"], "duplicates": [], "warnings": [],
 "entries": [{"url": "https://www.example.com/", "lastmod": "2026-10-15", "changefreq": null, "priority": 1.0}], "sha256": "3721ab5e…"}
```

The key-value store record `OUTPUT` summarises the run: `processed` and `fetchedUrls` counts, an `errors` list (with `documentIndex`, error code, message and, for URL items, the `url`) and `stoppedForSpendingLimit`. Invalid documents are reported there, are **not** written to the dataset and are **not** charged; valid siblings still process. Check `OUTPUT` even when the run status is *Succeeded*.

### Pricing

Pay per event, no subscription, no platform-usage pass-through:

| Event | Price | When |
|---|---|---|
| `processed-document` | **$0.005** | One sitemap snapshot (or pair) successfully processed and saved to the dataset |
| `apify-actor-start` | $0.00005 | Once per run (Apify synthetic start event) |

Example: 5 documents in one run = $0.02505. Invalid documents are free; the start event still applies to runs whose input is rejected. Set **Maximum cost per run** in Console or `?maxTotalChargeUsd=` in the API — the Actor stops before parsing a document it cannot pay for.

### Use it from the API, Make, n8n or an AI agent

```bash
curl -X POST "https://api.apify.com/v2/acts/zendren~sitemap-snapshot-audit/run-sync-get-dataset-items?maxTotalChargeUsd=0.05" \
  -H "Authorization: Bearer $APIFY_TOKEN" -H "Content-Type: application/json" \
  -d @INPUT.example.json
```

Send the token in the `Authorization` header, never in the URL. The Actor is also usable as an MCP tool through Apify's MCP server (`mcp.apify.com`).

### Responsible use

Supply only public data you are permitted to process, with no personal data. `publicDataOnly` is a policy acknowledgement, not an automated privacy detector. The Actor only downloads the exact public URLs you list (no crawling, no logins, no proxies) and never calls external AI services.

### Support

Found a bug or need a format supported? Open an issue on the Actor's **Issues** tab. Fixture attributions: see `NOTICE.md` in the source.

# Actor input Schema

## `publicDataOnly` (type: `boolean`):

Keep checked to confirm the documents are public, permitted to process, and contain no personal data. Runs with this unchecked are rejected (not charged). This is a policy acknowledgement, not automated PII detection.

## `sitemapUrls` (type: `array`):

Public sitemap.xml locations (urlset or sitemapindex, up to 5 in total together with pasted documents). Each entry is a URL string or an object {"url": "https://...", "previousUrl": "https://..."}; previousUrl is an optional older snapshot URL (e.g. an archived copy) to diff against. Each file is fetched by the Actor: at most 1 MiB (gzip accepted, decompressed size also capped), ports 80/443 only, no credentials in the URL, redirects limited to 3, 20-second timeout; URLs resolving to private/internal addresses are refused. Index sitemaps list their children and are never followed. Failed fetches are listed in the run summary and are not charged.

## `documents` (type: `array`):

Optional. Content you paste directly. Counts toward the 1-5 items per run together with the URL list. 1 MiB per XML snapshot, up to 5,000 entries each. Standard namespaced urlset/sitemapindex only. No gzip, sitemap recursion, live status requests or XML entities/DTDs. Index results list child sitemap references, never fetch them. previousXml is optional; types must match. Pricing: $0.005 per successful document + $0.00005/run start. XML entries/PDF chunks are nested, not separately charged. Total input under 8 MB.

## Actor input object example

```json
{
  "publicDataOnly": true,
  "sitemapUrls": [
    {
      "url": "https://www.sitemaps.org/sitemap.xml"
    }
  ]
}
```

# Actor output Schema

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

Processed documents (default dataset, overview view).

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

OUTPUT record: processed count, indexed input errors (not charged) and stoppedForSpendingLimit.

# 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 = {
    "sitemapUrls": [
        {
            "url": "https://www.sitemaps.org/sitemap.xml"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("zendren/sitemap-snapshot-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 = { "sitemapUrls": [{ "url": "https://www.sitemaps.org/sitemap.xml" }] }

# Run the Actor and wait for it to finish
run = client.actor("zendren/sitemap-snapshot-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 '{
  "sitemapUrls": [
    {
      "url": "https://www.sitemaps.org/sitemap.xml"
    }
  ]
}' |
apify call zendren/sitemap-snapshot-audit --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,zendren/sitemap-snapshot-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/kKuWK4bt1TMG3UImh/builds/dGhMuce0MZ8uYfdzS/openapi.json
