# Resource & Link Migration Auditor (`kingii98/resource-link-migration-auditor`) Actor

Audit destination pages after a migration: extract and check every resource and link reference, flag old-domain references, broken URLs, redirects, mixed content, and missing references.

- **URL**: https://apify.com/kingii98/resource-link-migration-auditor.md
- **Developed by:** [kingii98](https://apify.com/kingii98) (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

$2.00 / 1,000 page 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

## Resource & Link Migration Auditor

Audit destination pages after a website migration. Given the post-migration pages — from one XML sitemap or an explicit URL list — the Actor fetches each page over HTTP, extracts every resource and link reference (anchors, images and `srcset`, scripts, stylesheets, media, iframes, canonical and `hreflang` links), resolves relative URLs, and checks each unique reference once per run. It reports references that still point at the old domain, broken (4xx/5xx) resources and links, redirects, mixed-content references on HTTPS pages, missing or empty references, and cross-domain references, with a per-page and per-run summary.

This is migration QA, not a crawler: discovered links are checked for status but are never crawled, so the audit surface stays bounded by your input pages. Each run is stateless — nothing is persisted between runs.

The package reuses the safe HTTP and SSRF validation core shared by the other Actors in this repository (`sitemap_health_monitor`, `migration_qa_auditor`); those packages and their tests are unchanged.

### What it checks

- Every destination page's HTTP status and bounded redirect chain, with each hop target validated as a public address before it is followed
- References from `a[href]`, `img[src]`/`img[srcset]`, `script[src]`, stylesheet `link[href]`, `video[src]`/`video[poster]`, `audio[src]`, `source[src]`/`source[srcset]`, `iframe[src]`, canonical `link[href]`, and `hreflang` alternate `link[href]`
- Old-domain references: any reference whose host matches an `oldDomains` entry or its subdomains
- HTTP failures (4xx/5xx) and fetch errors per reference
- Redirects per reference, including over-long chains and redirect responses without a `Location`
- Mixed content: `http://` references on pages served over HTTPS
- Missing references (a tag without its primary attribute) and empty references (`href=""`, `src=""`)
- Cross-domain references, flagged via the `internal` field and optionally fetched with `checkExternal`
- Unsupported schemes (`mailto:`, `tel:`, `javascript:`, `data:`) and unsafe URLs, recorded without being fetched

Each unique reference URL is fetched at most once per run; every occurrence is reported with its own source page, tag, and attribute, so one shared stylesheet produces one network check and one record per page that uses it.

### Input

Provide either `sitemapUrl` or `urls` — not both.

```json
{
  "sitemapUrl": "https://new.example.com/sitemap.xml",
  "oldDomains": ["old.example.com"],
  "maxPages": 100,
  "maxReferencesPerPage": 100,
  "checkExternal": false
}
```

```json
{
  "urls": ["https://new.example.com/pricing", "https://new.example.com/about"],
  "oldDomains": ["old.example.com", "assets-old.example.com"]
}
```

| Field | Description |
|---|---|
| `sitemapUrl` | Public HTTP(S) XML sitemap listing destination pages. Nested sitemap indexes and gzip payloads are supported. |
| `urls` | Explicit destination page list, used instead of `sitemapUrl`. Entries are normalized and deduplicated. |
| `oldDomains` | Optional pre-migration domains. References to these domains or their subdomains are flagged as old-domain references. |
| `maxPages` | Hard cap on audited pages. Lists longer than this are rejected before any network work; sitemap-derived sets are truncated. Default 100; maximum 1,000. |
| `maxReferencesPerPage` | References extracted per page beyond this cap are dropped and counted as truncated. Default 100; maximum 500. |
| `maxTotalReferences` | Hard cap on unique reference URLs checked per run. Default and maximum 5,000. |
| `concurrency` | Concurrent page or reference fetches. Default 10; maximum 50. |
| `timeoutSecs` | Per-request timeout. Default 20 seconds; range 2-60. |
| `maxRedirects` | Maximum redirect hops followed per page or reference. Default 5; range 0-10. |
| `checkExternal` | Fetch cross-domain references too. Disabled by default: external references are recorded and flagged (`external-not-checked`) but not fetched. |

Invalid combinations (both source modes, neither mode, empty lists, URLs with credentials or non-HTTP schemes, malformed `oldDomains`, counts above caps) fail fast with a clear validation error before any network work.

### Output

Every run writes one summary record, one `page-result` record per audited page, and one `reference-result` record per reference occurrence to the default dataset.

Summary:

```json
{
  "recordType": "summary",
  "checkedAt": "2026-08-07T09:15:00+00:00",
  "pagesAudited": 2,
  "pageFailures": 0,
  "referencesDiscovered": 14,
  "uniqueReferences": 11,
  "referencesChecked": 10,
  "httpFailures": 1,
  "redirects": 2,
  "oldDomainReferences": 3,
  "mixedContentReferences": 1,
  "errors": 0,
  "truncatedReferences": 0
}
```

Page result:

```json
{
  "recordType": "page-result",
  "pageUrl": "https://new.example.com/pricing",
  "finalUrl": "https://new.example.com/pricing",
  "status": 200,
  "redirectChain": [
    {"url": "https://new.example.com/pricing", "status": 200}
  ],
  "referencesDiscovered": 9,
  "referencesTruncated": 0,
  "issueCodes": [],
  "error": null,
  "checkedAt": "2026-08-07T09:15:00+00:00"
}
```

Reference result:

```json
{
  "recordType": "reference-result",
  "sourcePage": "https://new.example.com/pricing",
  "tag": "img",
  "attribute": "src",
  "referenceUrl": "https://old.example.com/assets/hero.png",
  "finalUrl": "https://old.example.com/assets/hero.png",
  "status": 404,
  "redirectChain": [
    {"url": "https://old.example.com/assets/hero.png", "status": 404}
  ],
  "internal": false,
  "oldDomainReference": true,
  "mixedContent": false,
  "issueCodes": ["old-domain-reference", "http-4xx"],
  "error": null,
  "checkedAt": "2026-08-07T09:15:00+00:00"
}
```

Issue codes: `old-domain-reference`, `mixed-content`, `http-4xx`, `http-5xx`, `redirect`, `redirect-loop`, `redirect-chain-too-long`, `redirect-without-location`, `fetch-error`, `missing-reference`, `empty-reference`, `unsupported-scheme`, `unsafe-url`, `external-not-checked`, `non-html`.

A reference is `internal` when its host matches the source page's host. References that are missing, empty, non-HTTP, or unsafe are reported with `referenceUrl: null` and are never fetched. `truncatedReferences` counts references dropped by `maxReferencesPerPage` or `maxTotalReferences`.

### Pricing

The Actor uses Apify pay-per-event pricing with the `page-audited` charge event. When monetization is enabled, users are charged **$0.002 per page audited** — displayed as **$2 per 1,000 pages**. One `page-audited` event corresponds to one destination page audit, including extraction and checking of its bounded references; reference checks within a page do not generate extra events.

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 page prefix from the Actor charging budget before any page work, audits only that prefix, and stops before page checks if no page 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.
- URL credentials, localhost, and non-public, loopback, link-local, multicast, unspecified, or reserved addresses are rejected.
- Every redirect target — for pages and references alike — is resolved and validated before it is followed; a redirect to a private address fails that item with an error record instead of being fetched.
- Page counts, reference counts, concurrency, redirects, response bytes, 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; references are extracted from the raw HTML only.
- The audit is bounded by the input pages: links discovered on pages are status-checked but never crawled, so references on pages outside the input are not discovered.
- Sitemap input must be a valid XML sitemap; nested indexes are followed up to 20 files.
- Reference checks verify reachability (status and redirects), not content correctness; bodies of resources are not downloaded.
- With `checkExternal` disabled, cross-domain references are reported from markup only, without a live status.
- Network failures and rate limits are reported as per-item 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 reference hygiene; it does not provide legal, security-audit, or uptime guarantees.

# Actor input Schema

## `sitemapUrl` (type: `string`):

Public HTTP(S) XML sitemap listing the destination pages to audit. Provide this or urls, not both.

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

Explicit list of destination HTML pages to audit. Use instead of sitemapUrl. Entries are normalized and deduplicated.

## `oldDomains` (type: `array`):

Optional pre-migration domain names (for example old.example.com). References pointing at these domains or their subdomains are flagged as old-domain references.

## `maxPages` (type: `integer`):

Hard cap on destination pages audited per run. URL lists longer than this are rejected; sitemap-derived sets are truncated.

## `maxReferencesPerPage` (type: `integer`):

References extracted per page beyond this cap are dropped and counted as truncated.

## `maxTotalReferences` (type: `integer`):

Hard cap on unique reference URLs audited per run. New unique references beyond the cap are dropped and counted as truncated.

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

Maximum number of pages or references fetched concurrently.

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

Per-request timeout applied to sitemap, page, and reference fetches.

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

Maximum redirect hops followed per page or reference. Longer chains are reported as redirect-chain-too-long.

## `checkExternal` (type: `boolean`):

Fetch cross-domain references too. When disabled, external references are recorded and flagged but not fetched.

## Actor input object example

```json
{
  "maxPages": 100,
  "maxReferencesPerPage": 100,
  "maxTotalReferences": 5000,
  "concurrency": 10,
  "timeoutSecs": 20,
  "maxRedirects": 5,
  "checkExternal": false
}
```

# 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("kingii98/resource-link-migration-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("kingii98/resource-link-migration-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 '{}' |
apify call kingii98/resource-link-migration-auditor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,kingii98/resource-link-migration-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/Avx1KTPlAcBjqCEPa/builds/ZC1iwoxbsoCBOQJcz/openapi.json
