# Website Migration QA & Redirect Auditor (`believing_amelanchier_qbv/website-migration-qa-auditor`) Actor

Audit website migrations: validate redirect chains, destinations, canonicals, and indexability between old and new URL sets.

- **URL**: https://apify.com/believing\_amelanchier\_qbv/website-migration-qa-auditor.md
- **Developed by:** [kingii98](https://apify.com/believing_amelanchier_qbv) (community)
- **Categories:** SEO tools, Developer tools, Automation
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$1.00 / 1,000 url auditeds

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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Website Migration QA & Redirect Auditor

Audit a website migration before or after launch. Given the old URL set and the new URL set — as two XML sitemaps or two explicit lists — the Actor fetches every old URL without automatically following redirects, validates each redirect target as public, follows a bounded chain, classifies the outcome, and compares the final URL against the new URL set. It reports redirect loops and over-long chains, 4xx/5xx destinations, missing destinations, canonical and noindex regressions, and new URLs that no old URL maps to.

This is migration QA, not a generic crawler: every old URL is checked exactly once per run, and each run is stateless — no snapshot is persisted between runs.

The package reuses the safe HTTP and SSRF validation core of the `sitemap_health_monitor` Actor in this repository; that package and its tests are unchanged.

### What it checks

- HTTP status of every old URL, without hiding intermediate redirects
- Full redirect chain, with every hop target validated as a public address before it is followed
- Redirect loops and chains exceeding `maxRedirects`
- 4xx/5xx final responses and redirect responses without a `Location`
- Destination matching of the final URL against the new URL set (`exact`, `normalized`, or `none`)
- Canonical regressions on HTML destinations (canonical differs from the final URL), gated by `checkCanonical`
- Indexability regressions from meta robots and `X-Robots-Tag`, gated by `checkIndexability`
- New URLs that no audited old URL successfully resolves to (via exact or normalized final-destination match)

### Input

Provide either the sitemap pair or the URL lists — not both.

```json
{
  "oldSitemapUrl": "https://old.example.com/sitemap.xml",
  "newSitemapUrl": "https://new.example.com/sitemap.xml",
  "maxUrls": 500,
  "concurrency": 10,
  "timeoutSecs": 20,
  "maxRedirects": 5,
  "checkCanonical": true,
  "checkIndexability": true
}
```

```json
{
  "oldUrls": ["https://old.example.com/pricing", "https://old.example.com/about"],
  "newUrls": ["https://new.example.com/pricing", "https://new.example.com/about-us"]
}
```

| Field | Description |
|---|---|
| `oldSitemapUrl` / `newSitemapUrl` | Public HTTP(S) XML sitemaps for the old and new sites. Nested sitemap indexes and gzip payloads are supported. Both must be provided together. |
| `oldUrls` / `newUrls` | Explicit URL lists, used instead of the sitemap pair. Both must be non-empty; entries are normalized and deduplicated. |
| `maxUrls` | Hard cap on old and new URL counts. Lists longer than this are rejected before any network work; sitemap-derived sets are truncated. Default 500; maximum 5,000. |
| `concurrency` | Concurrent old-URL audits. Default 10; maximum 50. |
| `timeoutSecs` | Per-request timeout. Default 20 seconds; range 2-60. |
| `maxRedirects` | Maximum redirect hops followed per old URL. Default 5; range 0-10. |
| `checkCanonical` | Flag canonical mismatches on HTML destinations. Enabled by default. |
| `checkIndexability` | Flag `noindex` destinations. Enabled by default. |

Invalid combinations (a missing pair member, both modes at once, empty lists, URLs with credentials or non-HTTP schemes, counts above `maxUrls`) fail fast with a clear validation error before any network work.

### Output

Every run writes one summary record, one `url-result` record per audited old URL, and one `new-only` record per unreferenced new URL to the default dataset.

Summary:

```json
{
  "recordType": "summary",
  "checkedAt": "2026-08-07T09:15:00+00:00",
  "oldUrlCount": 2,
  "newUrlCount": 2,
  "redirected": 1,
  "direct": 0,
  "missingDestination": 1,
  "broken": 0,
  "loops": 0,
  "canonicalRegressions": 0,
  "indexabilityRegressions": 0,
  "newOnlyCount": 0,
  "errors": 0
}
```

Per-URL result:

```json
{
  "recordType": "url-result",
  "oldUrl": "https://old.example.com/pricing",
  "finalUrl": "https://new.example.com/pricing",
  "status": 200,
  "redirectChain": [
    {"url": "https://old.example.com/pricing", "status": 301},
    {"url": "https://new.example.com/pricing", "status": 200}
  ],
  "outcome": "redirected",
  "destinationMatched": true,
  "destinationMatchType": "exact",
  "canonicalUrl": "https://new.example.com/pricing",
  "indexable": true,
  "issues": [],
  "error": null,
  "checkedAt": "2026-08-07T09:15:00+00:00"
}
```

`outcome` is one of `direct` (2xx without redirects), `redirected` (2xx after redirects), `broken` (4xx/5xx or a redirect without `Location`), `loop` (redirect cycle or chain over `maxRedirects`), or `error` (fetch or validation failure, with details in `error`). The `loops` summary count includes chains that exceeded `maxRedirects`; the precise reason is in `issues`.

Issue codes: `redirect-loop`, `redirect-chain-too-long`, `http-4xx`, `http-5xx`, `redirect-without-location`, `missing-destination`, `canonical-mismatch`, `noindex`, `fetch-error`.

`destinationMatchType` is `exact` (final URL equals a new URL after normalization), `normalized` (matches when scheme case and trailing slashes are ignored), or `none`. `destinationMatched` and match type are only set when a final response was received.

New-only records have the form `{"recordType": "new-only", "url": "...", "checkedAt": "..."}` and list new URLs that no old URL successfully resolved to. A new URL is covered when an audited old URL ends on it with a `direct` or `redirected` outcome and an `exact` or `normalized` destination match — including migrations to a different domain. Results with `loop`, `error`, or `broken` outcomes never cover a new URL. Order follows the input `newUrls` or new sitemap order.

### Pricing

The Actor uses Apify pay-per-event pricing with the `url-audited` charge event. When monetization is enabled, users are charged **$0.001 per old URL audited**. One `url-audited` event corresponds to one old URL audit, including its bounded redirect-chain and destination checks.

Apify platform usage (compute units and other resources consumed by the run) may still be shown to users according to their plan and Apify's pricing rules, as described in the Actor's listing.

The Actor respects the run's maximum total charge: if the remaining budget cannot cover every old URL, it audits only the chargeable prefix; if no URL can be charged, it stops before page checks.

Final pricing is configured in the Apify Store listing and may change subject to Apify's pricing-change notice rules.

### Security and privacy

- Only public HTTP(S) targets are accepted.
- URL credentials, localhost, and non-public, loopback, link-local, multicast, unspecified, or reserved addresses are rejected.
- Every redirect target is resolved and validated before it is followed; a redirect to a private address fails that URL with an error record instead of being fetched.
- URL counts, concurrency, redirects, response bytes, sitemap sizes, and timeouts are all capped before or during network work.
- The Actor does not use a browser, proxy, LLM, external database, or third-party analytics service.
- Each run is stateless; results live only in the run's default dataset, subject to the retention and access settings of the Apify account running the Actor.

Do not place secrets, private URLs, or personal data in any input field.

### Limitations

- JavaScript-rendered pages are not rendered; checks use HTTP responses only.
- Sitemap inputs must be valid XML sitemaps; nested indexes are followed up to 20 files.
- Canonical and indexability checks apply to HTML responses only.
- New-only detection requires an audited old URL to successfully resolve to the new URL; a new URL whose only mapping is broken, looping, or failed is still reported as new-only.
- Network failures and rate limits are reported as per-URL errors; they are not automatically retried indefinitely.
- The Actor does not send notifications itself. Use Apify schedules, webhooks, or an automation platform.

### Support

For reproducible issues, open an issue from the Actor page and include the Apify run ID, sanitized input, expected result, and affected public URL. Do not include API tokens or private data.

This Actor audits migration mappings; it does not provide legal, security-audit, or uptime guarantees.

# Actor input Schema

## `oldSitemapUrl` (type: `string`):

Public HTTP(S) XML sitemap listing the pre-migration URLs. Provide together with newSitemapUrl, or use explicit URL lists instead.

## `newSitemapUrl` (type: `string`):

Public HTTP(S) XML sitemap listing the post-migration URLs. Provide together with oldSitemapUrl, or use explicit URL lists instead.

## `oldUrls` (type: `array`):

Explicit list of pre-migration URLs to audit. Use instead of oldSitemapUrl. Each entry is fetched and its redirect chain validated.

## `newUrls` (type: `array`):

Explicit list of post-migration destination URLs. Use instead of newSitemapUrl. Final redirect targets are matched against this set.

## `maxUrls` (type: `integer`):

Hard cap on old and new URL counts. Lists longer than this are rejected; sitemap-derived sets are truncated.

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

Maximum number of old URLs audited concurrently.

## `timeoutSecs` (type: `integer`):

Per-request timeout applied to sitemap and page fetches.

## `maxRedirects` (type: `integer`):

Maximum redirect hops followed per old URL. Longer chains are reported as redirect-chain-too-long.

## `checkCanonical` (type: `boolean`):

Flag HTML destinations whose canonical URL differs from the final URL.

## `checkIndexability` (type: `boolean`):

Flag HTML destinations marked noindex via meta robots or X-Robots-Tag.

## Actor input object example

```json
{
  "maxUrls": 500,
  "concurrency": 10,
  "timeoutSecs": 20,
  "maxRedirects": 5,
  "checkCanonical": true,
  "checkIndexability": true
}
```

# Actor output Schema

## `dataset` (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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("believing_amelanchier_qbv/website-migration-qa-auditor").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("believing_amelanchier_qbv/website-migration-qa-auditor").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{}' |
apify call believing_amelanchier_qbv/website-migration-qa-auditor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=believing_amelanchier_qbv/website-migration-qa-auditor",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/sMxgr59FYfQZgTxne/builds/YycUHeR0GhAe3ufMx/openapi.json
