# URL Redirect Chain Checker (`automation-lab/url-redirect-chain-checker`) Actor

Trace public URLs through every redirect hop and export final destinations, HTTP status codes, loops, errors, and timing for SEO migrations and link audits.

- **URL**: https://apify.com/automation-lab/url-redirect-chain-checker.md
- **Developed by:** [Automation Lab](https://apify.com/automation-lab) (community)
- **Categories:** Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.08 / 1,000 url checkeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## URL Redirect Chain Checker

Audit batches of public links with a **URL redirect checker** that records every fetched hop, HTTP status, raw and resolved `Location`, final destination, loop or limit state, and timing. It is designed for SEO migrations, canonical-host checks, short-link verification, and recurring link-quality exports.

The Actor makes lightweight HTTP requests instead of rendering pages. It starts with `HEAD`, uses a bounded `GET` fallback only when an origin explicitly rejects `HEAD`, and never stores response bodies.

### What does URL Redirect Chain Checker do?

For each unique input URL, the Actor:

1. normalizes it to an absolute HTTP(S) URL;
2. confirms each destination resolves to public network addresses;
3. requests the first URL with automatic redirects disabled;
4. resolves relative `Location` headers exactly as a browser would;
5. follows the chain up to your configured limit;
6. detects repeated URLs, missing or invalid locations, request errors, and long chains;
7. exports one typed dataset row with the complete ordered `hops` array.

Unlike a basic status checker, the result preserves evidence for every fetched response rather than only the first or final status.

### Who is it for?

- **SEO teams** validating old-to-new mappings before and after a migration.
- **Site reliability teams** checking that canonical scheme and hostname redirects still work.
- **Affiliate and campaign operators** expanding short links and confirming destinations.
- **Data teams** exporting redirect evidence into spreadsheets, warehouses, or monitoring jobs.
- **Developers** diagnosing relative redirects, redirect limits, loops, and origins that reject HEAD.

For site-wide link discovery, use [Sitewide Broken Link Checker](https://apify.com/automation-lab/sitewide-broken-link-checker). This Actor checks only the URLs you supply and does not crawl pages to discover links.

### Why use this Actor?

- Batch up to 1,000 public URLs in one run.
- Preserve each hop's URL, status, timing, method, and headers.
- Resolve both absolute and relative redirect targets.
- Export explicit `complete`, `loop`, `max_redirects`, `invalid_redirect`, and `request_error` states.
- Retry only transient network and HTTP failures.
- Avoid full browser startup and page-body downloads.
- Block private, loopback, link-local, and credential-bearing destinations.
- Keep one stable output row per input URL, including truthful failures.

### Input parameters

| Field | Type | Default | Description |
|---|---:|---:|---|
| `startUrls` | array | required | Public HTTP(S) URLs or domains. Request-list objects and strings are accepted. |
| `maxItems` | integer | `1000` | Maximum unique URLs processed in input order. |
| `maxRedirects` | integer | `10` | Redirect transitions followed per input; allowed range 0–20. |
| `concurrency` | integer | `10` | URLs processed in parallel; allowed range 1–50. |
| `requestTimeoutSecs` | integer | `15` | Timeout for each hop request; allowed range 1–30 seconds. |
| `maxRetries` | integer | `1` | Retries for network failures, 408/425/429, and temporary 5xx responses; allowed range 0–3. |

A URL without a scheme is interpreted as HTTPS. Duplicate normalized URLs are checked once. Invalid input fails the run before requests begin.

### Getting started

1. Open the Actor in Apify Console.
2. Add one or more URLs under **URLs to check**.
3. Keep `maxRedirects` at 10 for a normal audit.
4. Use lower concurrency when many URLs point to the same small origin.
5. Click **Start**.
6. Open the default dataset and select the **Redirect audits** view.
7. Export JSON, CSV, Excel, XML, or another dataset format.

A small input:

```json
{
  "startUrls": [
    { "url": "http://github.com/apify" },
    { "url": "https://httpbin.org/relative-redirect/2" },
    { "url": "https://www.python.org" }
  ],
  "maxRedirects": 10,
  "concurrency": 3,
  "requestTimeoutSecs": 15,
  "maxRetries": 1
}
```

### Output fields

| Field | Meaning |
|---|---|
| `inputUrl` | Original user-supplied value. |
| `normalizedUrl` | Absolute normalized first URL. |
| `finalUrl` | Last reached URL, or repeated target for a loop. |
| `finalStatusCode` | Last fetched HTTP status; null if no response or a loop target was not fetched again. |
| `redirectCount` | Number of redirect transitions actually followed. |
| `hasRedirects` | Whether any fetched response was a redirect. |
| `isLoop` | Whether the next target was already visited. |
| `reachedMaxRedirects` | Whether the configured limit stopped tracing. |
| `state` | Terminal result state. |
| `errorCode`, `errorMessage` | Stable and human-readable diagnostics. |
| `totalTimeMs` | End-to-end elapsed time, including retries. |
| `checkedAt` | ISO 8601 completion time. |
| `hops` | Ordered response records with URL, status, location, timing, method, content type, and server. |

All schema fields are nullable so exports remain stable when an origin omits a header or a request fails.

### Output example

```json
{
  "inputUrl": "http://github.com/apify",
  "normalizedUrl": "http://github.com/apify",
  "finalUrl": "https://github.com/apify",
  "finalStatusCode": 200,
  "redirectCount": 1,
  "hasRedirects": true,
  "isLoop": false,
  "reachedMaxRedirects": false,
  "state": "complete",
  "errorCode": null,
  "errorMessage": null,
  "totalTimeMs": 184,
  "checkedAt": "2026-01-15T12:00:00.000Z",
  "hops": [
    {
      "hop": 0,
      "url": "http://github.com/apify",
      "statusCode": 301,
      "statusText": "Moved Permanently",
      "location": "https://github.com/apify",
      "resolvedLocation": "https://github.com/apify",
      "responseTimeMs": 42,
      "method": "HEAD",
      "usedGetFallback": false,
      "contentType": "text/html",
      "server": "GitHub.com"
    }
  ]
}
```

Header values vary by origin, so the live row may contain null `contentType` or `server` values.

### Understanding result states

- `complete`: a non-redirect response ended the chain.
- `loop`: a redirect pointed to a URL already visited in this chain.
- `max_redirects`: another redirect existed after the configured limit.
- `invalid_redirect`: a redirect had no usable `Location` value.
- `request_error`: DNS, timeout, TLS, network, or public-destination validation failed.

HTTP 4xx and 5xx responses are still valid completed HTTP evidence. Check `finalStatusCode` to classify them.

### Redirect timing and methods

`responseTimeMs` measures time until response headers arrive for one recorded hop. `totalTimeMs` includes all hop requests, retries, DNS checks, and backoff.

The Actor first uses `HEAD`. If a server returns 405 or 501, it performs a bounded `GET`, asks for one byte, and closes the response stream after headers arrive. The hop then reports `method: "GET"` and `usedGetFallback: true`.

### Safety and public URL rules

Only public HTTP and HTTPS destinations are supported. The Actor rejects:

- loopback and unspecified addresses;
- private IPv4 ranges;
- link-local addresses and cloud metadata ranges;
- private, link-local, and multicast IPv6 ranges;
- URLs containing embedded usernames or passwords;
- redirects that resolve to any blocked destination.

This makes the Actor unsuitable for checking intranet services. It is intentionally scoped to public web audits.

### Retries, limits, and failure behavior

Transient failures may be retried with bounded exponential backoff. Stable 4xx responses, malformed URLs, invalid redirect locations, and blocked destinations are not blindly retried.

The default run timeout is five minutes. A large batch containing slow origins can reach that limit; lower `maxItems`, reduce retries, or split the list into multiple Tasks. Request-error rows are exported for diagnosis but are not charged as `url-checked` events.

### How much does it cost to check URL redirect chains?

Pay-per-event pricing includes a **$0.0004 start fee** and one `url-checked` event for each non-transport-error URL audit. At the BRONZE tier, the current per-URL price is **$0.0018**.

Approximate BRONZE prices:

| Completed URL audits | BRONZE estimate (USD) |
|---:|---:|
| 1 | 0.0022 |
| 10 | 0.0184 |
| 100 | 0.1804 |
| 1,000 | 1.8004 |

FREE is $0.00207 per URL, SILVER is $0.001404, and GOLD/PLATINUM/DIAMOND are $0.00108. Actual billed totals depend on completed charge events and Apify's billing rules. Refunds, fraud, disputes, taxes, corrections, or clawbacks can affect final amounts.

### SEO migration workflow

1. Export your old-to-new URL map from the CMS.
2. Put the old URLs into `startUrls`.
3. Run once before cutover to capture the baseline.
4. Schedule the same Task after deployment.
5. Compare `finalUrl`, `finalStatusCode`, `redirectCount`, and `state` between datasets.
6. Investigate loops, multi-hop chains, 302/307 temporary redirects, and unexpected destinations.

The Actor reports observed redirects; it does not decide whether a destination matches your private migration plan.

### Recurring audits and integrations

Useful automation patterns include:

- schedule a weekly canonical-host check;
- send loop and request-error rows to Slack through an Apify integration;
- export datasets to Google Sheets for migration sign-off;
- load JSON results into BigQuery, Snowflake, or a data lake;
- trigger a webhook after each run and compare with a prior dataset;
- call the Actor from CI before replacing legacy routes.

Apify schedules, webhooks, dataset APIs, and integrations can automate these steps without changing Actor input.

### Run with the Apify API using cURL

```bash
curl -X POST \
  "https://api.apify.com/v2/acts/automation-lab~url-redirect-chain-checker/runs?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"startUrls":[{"url":"http://github.com/apify"}],"maxRedirects":10}'
```

Never commit an Apify token. Use environment variables or your secret manager.

### Run with JavaScript

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('automation-lab/url-redirect-chain-checker').call({
  startUrls: [{ url: 'https://httpbin.org/relative-redirect/2' }],
  maxRedirects: 10,
  maxRetries: 1,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

### Run with Python

```python
import os
from apify_client import ApifyClient

client = ApifyClient(os.environ['APIFY_TOKEN'])
run = client.actor('automation-lab/url-redirect-chain-checker').call(run_input={
    'startUrls': [{'url': 'http://www.python.org'}],
    'maxRedirects': 10,
})
items = client.dataset(run['defaultDatasetId']).list_items().items
print(items)
```

### Use with MCP and AI agents

Add the Apify MCP server to Claude Code:

```bash
claude mcp add --transport http apify \
  "https://mcp.apify.com?tools=automation-lab/url-redirect-chain-checker"
```

#### Claude Desktop, Cursor, and VS Code setup

Claude Desktop, Cursor, VS Code, and other MCP-capable editor clients can use:

```json
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com?tools=automation-lab/url-redirect-chain-checker"
    }
  }
}
```

Example prompts:

- “Trace these 25 old campaign URLs and list any chain longer than one redirect.”
- “Run my saved redirect audit Task and summarize loops and terminal 404s.”
- “Export the final destinations and timing for this URL migration list.”

### Tips for reliable audits

- Group URLs by origin and use moderate concurrency to avoid self-created rate limits.
- Keep retries low for scheduled audits so one slow host cannot dominate runtime.
- Use `maxRedirects: 0` when you only need the first response and raw `Location`.
- Compare `resolvedLocation`, not only the raw header, when origins use relative redirects.
- Treat a changing `server` header as diagnostic context, not stable identity.
- Run from a consistent schedule when comparing latency over time.

### Limitations

- The Actor observes server-side HTTP redirects, not JavaScript navigation, HTML meta refresh, SPA router changes, or links changed after page rendering.
- Some origins intentionally return different results for HEAD and GET. GET fallback occurs only for explicit 405/501 responses.
- Public DNS and origin behavior can vary by time and Apify data-center location.
- Authentication, cookies, custom request headers, and residential proxy routing are not supported.
- Response-body content and page titles are not downloaded or parsed.
- A DNS record resolving to both public and private addresses is rejected for safety.

### Troubleshooting

#### Why did I get `BLOCKED_DESTINATION`?

The URL or a redirect target resolved to a non-public address. Use this Actor only for publicly reachable web destinations. It cannot be configured to access an intranet or metadata service.

#### Why is `finalStatusCode` null?

No response was available for that final target. This occurs for request failures and when loop detection avoids fetching an already visited URL again. Read `state`, `errorCode`, and `errorMessage`.

#### Why did a URL use GET?

The origin returned 405 or 501 to HEAD. The Actor retried with a bounded GET and recorded `usedGetFallback: true`.

#### Why is a JavaScript redirect missing?

This is an HTTP redirect chain checker, not a browser. Client-side navigation requires rendered-page tooling such as a browser Actor.

### Legality and responsible use

Check only URLs you are authorized to test. Respect website terms, rate limits, robots guidance where applicable, and relevant privacy and computer-access laws. Do not use high concurrency to disrupt services. Dataset rows can contain user-supplied URLs, so review them before sharing exports publicly.

### Related Automation Lab Actors

- [HTTP Status Code Checker](https://apify.com/automation-lab/http-status-checker) for lightweight status checks and simpler redirect summaries.
- [Sitewide Broken Link Checker](https://apify.com/automation-lab/sitewide-broken-link-checker) to discover and verify links across a website.
- [Flexible HTTP Request Runner](https://apify.com/automation-lab/flexible-http-request-runner) when you need custom methods, headers, or response-body access.

### FAQ

#### Does it work as a free online redirect checker?

You can run small checks under your Apify plan, but the Actor uses pay-per-event pricing rather than claiming unlimited free service. See the pricing section for exact active event prices.

#### Does it follow 301, 302, 303, 307, and 308 responses?

Yes. It also treats HTTP 300 and 305 with a valid `Location` as redirect responses and records every fetched status.

#### Can I export every redirect hop?

Yes. Each dataset row includes an ordered `hops` array. JSON preserves the full nested structure; spreadsheet formats may serialize the array into a cell.

#### Are failed URLs included?

Yes. Request failures produce typed `request_error` rows and are not charged as completed URL checks. Malformed input fails before the run begins.

#### Can I check more than 1,000 URLs?

Split the list across several Actor runs or saved Tasks. The per-run cap keeps runtime, memory, and load on target origins bounded.

#### Does the Actor cache results?

No. Every run observes the current public HTTP behavior, which is appropriate for recurring migration and link audits.

# Changelog

This Actor's version history is a separate document: https://apify.com/automation-lab/url-redirect-chain-checker/changelog.md

# Actor input Schema

## `startUrls` (type: `array`):

Public HTTP(S) URLs or domains to trace. Duplicate normalized URLs are checked once. Private, loopback, link-local, and credential-bearing destinations are blocked.

## `maxItems` (type: `integer`):

Maximum number of unique input URLs to check, in input order.

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

Maximum number of redirect transitions to follow for each input URL. Set 0 to inspect only the first response.

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

Number of URLs checked in parallel. Reduce this for sensitive origins or very long chains.

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

Timeout for each HTTP request attempt within a chain.

## `maxRetries` (type: `integer`):

Retries per hop for network errors, rate limits, and temporary server errors. Deterministic HTTP errors are not retried.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "http://github.com/apify"
    },
    {
      "url": "https://httpbin.org/relative-redirect/2"
    },
    {
      "url": "https://www.python.org"
    }
  ],
  "maxItems": 20,
  "maxRedirects": 10,
  "concurrency": 10,
  "requestTimeoutSecs": 15,
  "maxRetries": 1
}
```

# Actor output Schema

## `dataset` (type: `string`):

Ordered redirect hops, final destinations, status, errors, and timing.

# 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 = {
    "startUrls": [
        {
            "url": "http://github.com/apify"
        },
        {
            "url": "https://httpbin.org/relative-redirect/2"
        },
        {
            "url": "https://www.python.org"
        }
    ],
    "maxItems": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation-lab/url-redirect-chain-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 = {
    "startUrls": [
        { "url": "http://github.com/apify" },
        { "url": "https://httpbin.org/relative-redirect/2" },
        { "url": "https://www.python.org" },
    ],
    "maxItems": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("automation-lab/url-redirect-chain-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 '{
  "startUrls": [
    {
      "url": "http://github.com/apify"
    },
    {
      "url": "https://httpbin.org/relative-redirect/2"
    },
    {
      "url": "https://www.python.org"
    }
  ],
  "maxItems": 20
}' |
apify call automation-lab/url-redirect-chain-checker --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,automation-lab/url-redirect-chain-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/Aphb4BMbSFhhWDLrt/builds/pUsHXo94a4DUqSF35/openapi.json
