# Migration Redirect Map Conformance Auditor (`kingii98/migration-redirect-map-conformance-auditor`) Actor

Check a domain-migration redirect map: for each old-to-new URL pair, follow the live redirect chain and verify it lands on the expected target, within bounded hops.

- **URL**: https://apify.com/kingii98/migration-redirect-map-conformance-auditor.md
- **Developed by:** [kingii98](https://apify.com/kingii98) (community)
- **Categories:**
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$1.50 / 1,000 pair 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?

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

## Migration Redirect Map Conformance Auditor

Check a domain-migration redirect map. Give the Actor a list of old-URL to
expected-new-URL pairs. For each pair, the Actor follows the live redirect
chain from the old URL and checks if it lands on the expected target. It
reports a verdict for each pair and a launch gate that is true only when
every pair passes.

This Actor is for the sign-off step before or after a cutover: confirm the
redirect map does what it claims, with no browser and no login.

### What it checks

- The live redirect chain from each `oldUrl`, up to a bounded number of hops
- If the chain lands on `expectedNewUrl`, exactly or after normalization
- The HTTP status code of the first hop, against `expectedStatus`
- Chains that are too long, that loop, that dead-end on a 404, or that hit a
  server error
- Protocol downgrades (`https` to `http` partway through the chain)
- Cross-host hops (a redirect that changes host)
- Response time for each pair, in milliseconds

Each hop is checked with `HEAD` first. If the origin rejects `HEAD` (status
405 or 501), the Actor retries that hop with a bounded `GET` and reads only
enough of the body to confirm the status and headers.

### Input

```json
{
  "pairs": [
    {
      "oldUrl": "https://old.example.com/blog/post-1",
      "expectedNewUrl": "https://new.example.com/blog/post-1"
    }
  ],
  "maxHops": 5,
  "requireSingleHop": true,
  "normalization": {
    "ignoreTrailingSlash": true,
    "ignoreCase": false,
    "ignoreQueryParams": ["utm_source"]
  },
  "expectedStatus": 301,
  "concurrency": 10,
  "timeoutSecs": 20
}
```

| Field | Description |
|---|---|
| `pairs` | Old-to-new URL pairs to check. Each item needs `oldUrl` and `expectedNewUrl`. Maximum 5,000 pairs per run. |
| `maxHops` | Maximum redirect hops followed per pair when `requireSingleHop` is `false`. Default 5; range 1-20. |
| `requireSingleHop` | When `true` (default), a pair must land on its target in exactly one redirect hop; any extra hop reports `CHAIN_TOO_LONG`. When `false`, `maxHops` applies instead. |
| `normalization` | Rules used only to decide a `MATCH_AFTER_NORMALIZATION` verdict: `ignoreTrailingSlash` (boolean), `ignoreCase` (boolean), `ignoreQueryParams` (list of query-parameter names to ignore when comparing the final URL to `expectedNewUrl`). |
| `expectedStatus` | The HTTP status the first hop must return for a pair to qualify as `EXACT`. Default 301. A pair that reaches the right target with a different status is `MATCH_AFTER_NORMALIZATION`, not `EXACT`. |
| `concurrency` | Pairs checked concurrently. Default 10; maximum 50. |
| `timeoutSecs` | Per-request timeout applied to each hop. Default 20 seconds; range 2-60. |

Invalid input (empty or oversized `pairs`, entries missing `oldUrl` or
`expectedNewUrl`, non-HTTP(S) URLs, URLs with credentials) fails fast with a
clear validation error before any network work.

### Output

Every run writes one summary record and one `pair-result` record per pair to
the default dataset.

Summary:

```json
{
  "recordType": "summary",
  "checkedAt": "2026-08-31T09:15:00+00:00",
  "pairsRequested": 2,
  "pairsChecked": 2,
  "verdictCounts": {
    "EXACT": 1,
    "MATCH_AFTER_NORMALIZATION": 0,
    "WRONG_TARGET": 0,
    "CHAIN_TOO_LONG": 0,
    "LOOP": 0,
    "DEAD_END_404": 1,
    "SERVER_ERROR": 0
  },
  "launchGate": false,
  "truncatedByChargeBudget": false
}
```

Pair result:

```json
{
  "recordType": "pair-result",
  "oldUrl": "https://old.example.com/blog/post-1",
  "expectedNewUrl": "https://new.example.com/blog/post-1",
  "finalUrl": "https://new.example.com/blog/post-1",
  "hopCount": 1,
  "hops": [
    {"url": "https://old.example.com/blog/post-1", "status": 301, "location": "https://new.example.com/blog/post-1"},
    {"url": "https://new.example.com/blog/post-1", "status": 200, "location": null}
  ],
  "verdict": "EXACT",
  "statusOfFirstHop": 301,
  "protocolDowngradeFlag": false,
  "crossHostFlag": true,
  "responseTimeMs": 184,
  "error": null,
  "checkedAt": "2026-08-31T09:15:00+00:00"
}
```

Verdicts: `EXACT` (right target, right status, within the hop limit),
`MATCH_AFTER_NORMALIZATION` (right target, but only after applying the
`normalization` rules, or with a first-hop status other than
`expectedStatus`), `WRONG_TARGET`, `CHAIN_TOO_LONG`, `LOOP`, `DEAD_END_404`,
`SERVER_ERROR`.

`launchGate` is `true` only when every pair in the run is `EXACT` or
`MATCH_AFTER_NORMALIZATION`, **and** `pairsChecked` equals `pairsRequested`.
If the run's maximum total charge stops the Actor from auditing every
submitted pair, `truncatedByChargeBudget` is `true`, `pairsChecked` is
lower than `pairsRequested`, and `launchGate` is `false` even if every
audited pair passed. This stops a partial audit from reporting a false
pass. Use `launchGate` as a single pass/fail signal before or
after a cutover.

### Pricing

The Actor uses Apify pay-per-event pricing with the `pair-checked` charge
event. When monetization is enabled, users are charged **$0.0015 per URL
pair checked**. One `pair-checked` event corresponds to one `pairs` entry
audited, including every hop the Actor follows for that pair.

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: it computes the
chargeable pair prefix from the Actor charging budget before any network
work, audits only that prefix, and stops before pair checks if no pair can
be charged.

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; no login flow is supported.
- URL credentials, localhost, and non-public, loopback, link-local,
  multicast, unspecified, or reserved addresses are rejected.
- Every hop target is resolved and validated as a public address before it
  is followed; a redirect to a private address fails that pair with an
  error instead of being fetched.
- Pair count, hop count, concurrency, response bytes (for the `GET`
  fallback), 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

- The Actor checks status codes and redirect targets; it does not render or
  compare page content.
- `expectedStatus` affects only the `EXACT` vs `MATCH_AFTER_NORMALIZATION`
  split; a pair that lands on the right target is never marked
  `WRONG_TARGET` because of its status code alone.
- Network failures and timeouts are reported as `SERVER_ERROR` with an
  `error` message; they are not automatically retried.
- The Actor does not send notifications itself. Use Apify schedules,
  webhooks, or an automation platform for the day 1 / day 7 / day 30 / day
  90 re-check schedule.

### 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 redirect-map conformance; it does not provide legal,
security-audit, or uptime guarantees.

# Actor input Schema

## `pairs` (type: `array`):

Old-to-new URL pairs to check. Each item needs oldUrl and expectedNewUrl. Maximum 5000 pairs.

## `maxHops` (type: `integer`):

Maximum redirect hops followed per pair when requireSingleHop is disabled. Chains longer than this are reported as CHAIN\_TOO\_LONG.

## `requireSingleHop` (type: `boolean`):

When enabled, a pair must land on its target in exactly one redirect hop; any extra hop reports CHAIN\_TOO\_LONG. When disabled, maxHops applies instead.

## `normalization` (type: `object`):

Rules applied when comparing the final URL to expectedNewUrl for a MATCH\_AFTER\_NORMALIZATION verdict. Fields: ignoreTrailingSlash (boolean), ignoreCase (boolean), ignoreQueryParams (list of query-parameter names to ignore).

## `expectedStatus` (type: `integer`):

The HTTP status code the first hop must return for a pair to qualify as EXACT. A correct final target reached with a different status code is reported as MATCH\_AFTER\_NORMALIZATION.

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

Maximum number of pairs checked concurrently.

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

Per-request timeout applied to each hop fetch.

## Actor input object example

```json
{
  "pairs": [
    {
      "oldUrl": "http://github.com/",
      "expectedNewUrl": "https://github.com/"
    }
  ],
  "maxHops": 5,
  "requireSingleHop": true,
  "expectedStatus": 301,
  "concurrency": 10,
  "timeoutSecs": 20
}
```

# 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 = {
    "pairs": [
        {
            "oldUrl": "http://github.com/",
            "expectedNewUrl": "https://github.com/"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("kingii98/migration-redirect-map-conformance-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 = { "pairs": [{
            "oldUrl": "http://github.com/",
            "expectedNewUrl": "https://github.com/",
        }] }

# Run the Actor and wait for it to finish
run = client.actor("kingii98/migration-redirect-map-conformance-auditor").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 '{
  "pairs": [
    {
      "oldUrl": "http://github.com/",
      "expectedNewUrl": "https://github.com/"
    }
  ]
}' |
apify call kingii98/migration-redirect-map-conformance-auditor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,kingii98/migration-redirect-map-conformance-auditor"
        }
    }
}

```

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/w1ACAmAelU3UcF464/builds/yICUEpmHfJsx3684m/openapi.json
