# Redirect Map & URL Status Checker (`sapph1re/redirect-map-url-status-checker`) Actor

Check small public URL lists and migration mappings with ordered HTTP hop evidence, conservative target verdicts and explicit limits.

- **URL**: https://apify.com/sapph1re/redirect-map-url-status-checker.md
- **Developed by:** [Roman V](https://apify.com/sapph1re) (community)
- **Categories:**
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$1.00 / 1,000 checked urls

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

## Redirect Map & URL Status Checker

Check a small public URL list or verify where migration redirects actually end. Supply an optional expected destination for each input. The dataset contains one evidence row per checked input, in input order, including duplicates.

Each row records the observed HTTP responses, raw accepted Location values, resolved redirect targets, terminal status and a conservative expected-target verdict. A loop, an exhausted hop allowance, an HTTP error and a failed connection have different outcomes.

### First run

Run with `{}` or no input to check `https://example.com/` once. An explicit `{"mappings": []}` checks nothing and produces an EMPTY summary.

```json
{
  "mappings": [
    {"url": "https://example.com/", "expectedUrl": "https://example.com/"}
  ]
}
```

For status-only checks, omit `expectedUrl`:

```json
{
  "mappings": [
    {"url": "https://example.com/"}
  ]
}
```

Replace these demonstration URLs with public URLs you are authorized to check. For a migration, each object can contain the old URL and its expected final URL. Both modes share one input format and one dataset.

### Results

| Field | Meaning |
| --- | --- |
| `inputIndex`, `inputUrl` | Original zero-based position and supplied source URL. Duplicates are checked separately. |
| `hops` | Ordered responses, each containing `url`, `status`, accepted raw `location` and `resolvedTarget`. |
| `terminalUrl`, `terminalStatus` | Populated only after a non-redirect HTTP response. A stopped chain has no invented terminal URL. |
| `lastObservedUrl`, `lastObservedStatus` | Last HTTP response actually returned by the HTTP client, including a stopped redirect. |
| `outcome`, `reason` | Stable result and any more specific failure or limit reason. |
| `targetMatch` | True/false only for a terminal 2xx response with an expected URL. Otherwise null. |
| `temporaryRedirects`, `permanentRedirects` | Observed 302/303/307 and 301/308 responses. These are HTTP classifications, not SEO scores. |
| `fetchAttempts`, `httpResponses` | Transport invocations and returned HTTP responses. DNS failures count as attempts; an attempt is not proof a GET reached the server. |

`MATCH` and `TARGET_MISMATCH` apply to expected destinations. `CHECKED` means a status-only check reached a 2xx response. `HTTP_ERROR` preserves 4xx/5xx responses. `HTTP_OTHER` covers other terminal HTTP statuses. A 404 at the expected URL is an HTTP error with an unknown match, never a successful migration.

`LOOP` means a normalized network URL repeated. `HOP_LIMIT` means the next redirect would exceed `maxHops`. `REDIRECT_INVALID` covers missing, duplicate or malformed Location values. `UNSAFE_REDIRECT` stops an explicitly forbidden target. A hostname that resolves to a private address returns `FETCH_FAILED` with `DNS_UNSAFE_ADDRESS`. No private connection is attempted.

Download JSON or CSV from the dataset. JSON preserves nested hop objects most directly. OUTPUT in the default key-value store contains one compact receipt per accepted input, counts, limits and delivery/billing status. It does not duplicate full paid hop evidence.

COMPLETE means the accepted inputs were processed within the collection budget. It does not mean every URL is healthy or every migration matches. PARTIAL means some checks could not finish or were excluded by a limit. All transport failures produce FAILED; invalid input produces a failed run before source access. EMPTY is an intentional zero-input result.

### Comparison rules and supported URLs

Comparison lowercases the scheme and hostname, removes the default port, treats an empty path as `/`, and ignores fragments. It preserves path case, trailing slashes, query values and ordering, and percent-encoded path/query bytes. It does not follow the expected URL separately or infer equivalence from page content. A different encoding can therefore be reported as a mismatch even when an origin serves identical content.

Inputs must use ASCII URLs, with non-ASCII characters percent-encoded and international hostnames written as punycode. Only HTTP port 80 and HTTPS port 443 are supported. Userinfo, sensitive query-key names, non-public IP addresses, local hostnames, trailing-dot hosts, IPv6 zone/transition forms and explicit empty query delimiters such as `/page?` are rejected. Invalid input rejects the whole batch; nothing is silently dropped. Some additional source-side restrictions can still prevent access.

Relative and scheme-relative redirects are resolved against the current URL. Fragments do not create an extra network request. Unsafe or sensitive Location values are withheld from evidence rather than copied into output. Ordinary accepted Location values remain unchanged.

### Limits

| Setting | Default | Allowed range |
| --- | ---: | ---: |
| `maxUrls` | 100 | 1 to 100 |
| `maxHops` | 5 | 0 to 10 |
| `maxRequests` | 300 | 0 to 1,100 |
| `requestTimeoutSecs` | 10 | 1 to 20 |
| `maxDurationSecs` | 60 | 1 to 120 |

Requests are serial with no retries. Each checked input can use at most `maxHops + 1` fetch attempts. The run-wide request and source-duration budgets also apply. Inputs excluded before their first attempt get NOT\_CHECKED receipts in OUTPUT and no dataset row. A chain stopped after work began keeps its partial evidence row with REQUEST\_LIMIT or TIME\_LIMIT.

The dataset payload is capped at 8 MiB per run. The client limits header fields to 4,096 bytes, header count to 64, retained aggregate headers to 64 KiB, and raw bytes admitted to the HTTP parser to 256 KiB per attempt. Response bodies are not extracted or decompressed. A GET can receive bounded body data before headers reach the application; malformed body framing or an oversized early read can cause FETCH\_FAILED. These are application/parser limits, not a claim of zero body bytes on the network or a total bandwidth invoice. HTTP payload corruption is not audited.

Public DNS answers are checked before connection and numeric addresses are pinned into the connector. Redirects repeat the checks. TLS certificate and hostname verification remain enabled. Environment proxies, `.netrc`, cookies and login are disabled. There is no crawling, JavaScript rendering, page-text extraction, proxy bypass, SEO scoring, persistent monitoring or notification feature.

### Billing and delivery

The price is **$0.001 per delivered checked-input row**, charged through Apify's automatic dataset-item event. This includes rows showing HTTP errors, failed connections, loops, invalid redirects and partial-chain evidence, as well as successful checks. Inputs marked NOT\_CHECKED without a dataset row do not incur a row charge. There is no start fee, report fee or separate usage surcharge.

Set a positive `maxTotalChargeUsd` to limit row charges. A `$0.0015` cap allows at most one row; `$0.0025` allows at most two rows; and 100 rows cost at most `$0.10`. The runtime checks the remaining budget before source work and uses decimal arithmetic for fractional caps. **Do not use `maxTotalChargeUsd=0` as a dry-run switch:** in hosted API testing, the platform treated zero as an unset cap. Smaller positive caps may be rejected by the platform. To make no checks, use the explicit empty input described above. The runtime requires a single automatic dataset-item event and rejects additional priced events. Billing is based on delivered evidence, not on whether the checked URL is healthy or matches an expected destination.

The runtime journals collection and delivery. Dataset POST retries and automatic redirects are disabled in the pinned SDK adapter. A confirmed push preserves its delivered and charged counts if later bookkeeping fails. An ambiguous write reports unknown counts and a failed run. It is never automatically retried. Completed restart state replays the receipt without fetching or pushing; incomplete or conflicting state fails before source work. Leave automatic restart off for hosted QA and production until hosted restart behavior is independently accepted.

### Local development

Requires Python 3.12. Use 256 MB of Actor memory. Dependencies, including transitive versions, are pinned in `requirements.txt`.

```sh
python3.12 -m venv .venv
.venv/bin/python -m pip install --only-binary=:all: -r requirements.txt
.venv/bin/python -I -B run_tests.py
.venv/bin/python -m src
```

Local tests use synthetic source fixtures. Before checking production URLs, review the input limits, set an explicit positive charge cap and keep automatic restart off.

# Actor input Schema

## `mappings` (type: `array`):

Up to 100 objects with url and optional expectedUrl. Duplicates remain separate. Empty array checks nothing. Only public HTTP port 80 and HTTPS port 443; use ASCII percent-encoded URLs.

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

Reject the entire input if more mappings are supplied.

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

The initial GET is in addition to this allowance. Zero still permits the initial GET.

## `maxRequests` (type: `integer`):

Run-wide cap on calls to the HTTP transport, including DNS failures and redirect hops. Not a count of confirmed wire requests. Zero checks nothing.

## `requestTimeoutSecs` (type: `integer`):

Shared timeout for DNS, TCP, TLS and response headers.

## `maxDurationSecs` (type: `integer`):

Run-wide source collection budget. Storage and SDK initialization are outside this deadline.

## Actor input object example

```json
{
  "mappings": [
    {
      "url": "https://example.com/"
    }
  ],
  "maxUrls": 100,
  "maxHops": 5,
  "maxRequests": 300,
  "requestTimeoutSecs": 10,
  "maxDurationSecs": 60
}
```

# Actor output Schema

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

No description

## `runReceipt` (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("sapph1re/redirect-map-url-status-checker").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("sapph1re/redirect-map-url-status-checker").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 sapph1re/redirect-map-url-status-checker --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,sapph1re/redirect-map-url-status-checker"
        }
    }
}

```

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/N5zD65a877eGQ9kOL/builds/Dd8ZnxHCdee7bSkdC/openapi.json
